@ byokit/ 连接
连接一个人的在线文件,邮件,日历,笔记,设计,代码或其他远程账户. 签名打开提供者自己的页面. 证书留在键盘上 传球 瞄准装置上的那个人 没有账户池,代理, 环境认证查询、记录或额外选择切换.
npm install @byokit/connect
主要导出为可移植到Node,Electron,浏览器和PWA. 这需要 fetch, (中文(简体) ),
网络加密, btoa,以及 URL API; 本地主机必须先提供这些全局
导入它。 可选 /node 导出只拥有回回调监听器.
主条目对节点或密钥后端没有运行时间依赖.
仅测试端点覆盖
仅针对当地融合测试, ConnectOptions.endpoints 接受完整的提供者 URL;省略的值使用生产终点. 例如,模拟0.12.2服务于谷歌授权 http://localhost:4000/o/oauth2/v2/auth符号 http://localhost:4000/oauth2/token,并使用 Gmail API 基础 http://localhost:4000/gmail/v1。通过相同的 endpoints 对象 MailReader 或者说 MailSender ,以路由 Gmail 调用其 Google API . Connection.apiBase 曝光配置的供應器自動呼叫的API Base; 套件本身不做GitHub REST API呼叫. GitHub 设备代码和令牌 URL 对于本地设备流服务器来说可以被覆盖,因为模拟 01.2.2 不执行这些路线.
使用应用程序提供的定向签名
import { connect } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
// Supply your app's store, scoped to this person on this device.
declare const store: Keystore;
const drive = connect('drive', {
store,
person: 'Umer',
redirectUri: 'https://your-app.example/connect/callback',
client: { id: 'your-google-client-id', secret: 'your-google-client-secret' },
});
const flow = await drive.signIn();
// Open flow.url in the host's browser/auth session.
// Route its complete callback URL to the same flow instance:
await flow.finish('https://your-app.example/connect/callback?state=...&code=...');
const accessToken = await drive.token(); // Trusted API calls only; never show/log it.
await drive.disconnect();
connect(target, options) 创建一个手柄. signIn() 返回时 { url, redirectUri, finish, cancel }调用 cancel() 当关闭登录表时。 呼吁
connected(), (中文(简体) ), token() 财务报告和审定财务报表 disconnect() 重建手柄后的工作
拥有同一商店,个人,目标及配置的客户端ID. 重定向端口
可以在不丢失已保存的连接的情况下更改。 更新的登录将取消
更古老的流量;回调有15分钟的期限,只能使用一次.
Google要求主机注册客户端. 使用有回回路的桌面客户端,
或为该客户端登记精确的 HTTPS/内置重定向。 预设保留谷歌
离线/ 同意参数并请求一次服务 : google (身份),
drive (单位:千美元)drive.file, (中文(简体) ), gmail (只读;通过) scopes 发送)和 calendar (活动). 主机拥有
Google同意-屏幕发布和API授权. 包里没有探测器
无文件提供者网页或强制实行产品专用审批政策.
notion, (中文(简体) ), canva 和任意 HTTPS MCP URL 发现其保护资源
元数据、授权服务器和动态注册端点。 如果服务器
不提供注册,通过注册的应用程序 client自定义 Provider
对象可以提供 oauth 结束点, a issuer、范围和额外授权
参数。 选项 scopes 覆盖预设/发现的范围; 安全
参数(state, PKCE, 响应类型, 客户端和重定向) 无法覆盖
结束 extra。没有保存明显不完整的授予范围的登录.
房子谷歌客户端
一个Google OAuth客户端服务于该馆运行的每条Google路线: 此工具包的Gmail 读者和发送者,以及Crewhouse的谷歌连接.
- 客户端 : 类型 桌面应用程序在房子的谷歌云项目中
Gmail, 日历和驱动 API 启用 。 桌面客户端接受任何回回端口, 所以
connectLoopback不需要重定向注册. - 同意: 外部出版(生产· ; 测试 赠款停止
7天之后). 同意筛选清单
gmail.readonly, (中文(简体) ),gmail.send, (中文(简体) ),calendar.events财务报告和审定财务报表drive.file。每个连接仍然需要一种服务; 发送添加gmail.send结束scopes。 。 。 . - 秘诀 : jSON Google提供下载,未作改动,地点:
~/.config/byokit/google-oauth-client.json,模式600。 从未在寄存器, 日志 或测试家.
import { homedir } from 'node:os';
import { join } from 'node:path';
import { connectLoopback, googleClientFile } from '@byokit/connect/node';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
declare const yourApp: { openExternal(url: string): Promise<void>; setUpGoogle(): never };
const client = await googleClientFile(join(homedir(), '.config', 'byokit', 'google-oauth-client.json'));
if (!client) yourApp.setUpGoogle(); // No file yet: Google is not set up on this computer.
const attempt = await connectLoopback('gmail', { store, person: 'Umer', client,
scopes: ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send'],
open: url => yourApp.openExternal(url) });
await attempt.done; // Then MailReader/MailSender on attempt.connection.
googleClientFile(path) 仅读取主机通过的道路。 它回来了 null 何时
文件缺席, 拒绝其它用户可以读取的文件, 非常规文件
路径, 或除桌面应用程序以外的任何客户端类型, 并带有纯信息
绝不重复文件的内容.
检查应用程序细节并检查补助金使用寿命
import type { Connection } from '@byokit/connect';
declare const drive: Connection;
declare const enteredClientId: string;
declare const enteredClientSecret: string;
const check = await drive.verifyClient(); // Uses options.client.
// Or check newly entered details without changing the saved connection:
const entered = await drive.verifyClient({ id: enteredClientId, secret: enteredClientSecret });
const grant = await drive.grant(); // Trusted app code only; contains credentials.
grant() 返回快照或 null 也不给任何供货商打电话。 时间是Unix
毫秒长. refreshTokenExpiresIn 保存提供者的
refresh_token_expires_in 秒数; refreshTokenExpiresAt 绝对期限.
签名/重新答复计算收到时的截止日期,省略刷新
寿命字段保留上一个最后期限。 需要过期的更新补助金
登录而不发送另一个刷新请求.
verifyClient(client?) 使用选中的符号端点,并刻意使用
无需阅读或更改个人补助金。 对于
Google, (英语), invalid_client 产量 outcome: 'invalid'; a 400 (韩语) invalid_grant 产量
'valid'; 网络故障和所有其他回复均产生 'inconclusive'每一个
结果包括平原 message; 失败的提供者回复可能包含同样内容
消毒诊断 cause 作为 ConnectError需要保密。 这个检查
只有客户端认证; 它不检查同意-屏幕发布, 启用
API,重定向注册或范围. 测试只使用被注入的假终点.
用设备代码连接 GitHub
import { connect, deviceSignIn } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
const options = { store, person: 'Umer', client: { id: 'your-github-oauth-app-client-id' }, scopes: ['repo'] };
const flow = await deviceSignIn('github', options);
// Show flow.userCode and flow.verificationUri (github.com/login/device);
// the person types the code there on any device.
await flow.finish(); // Resolves once they approve; call flow.cancel() when the sheet closes.
const github = connect('github', options);
await github.connected(); // true
deviceSignIn(provider, options) 启动 RFC 8628 设备签名并返回
{ userCode, verificationUri, expiresAt, finish, cancel }· ; expiresAt 是 Unix 时
毫秒长. 它需要应用自己的OAuth客户端ID,并启用设备流;
没有房子 GitHub 客户端,没有 redirectUri。 。 。 . finish() 民意调查
提供者的间隔, 增加5秒 slow_down,并不断等待 通过一个
民意调查无法通过。 只有经批准的密码才能储存一笔赠款
密钥记录 connect() 读取,所以 connected(), (中文(简体) ), grant() 财务报告和审定财务报表 token()
之后工作时。 过期的代码拒绝方式 ConnectError 代码 expired页:1
个人按 GitHub 键取消,或 cancel(),拒绝方式 declined两者
存储什么。 任何提供者 device: { code, token } 端点工作
同样的方式.
在电脑上循环
import { connectLoopback } from '@byokit/connect/node';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
declare const yourApp: { openExternal(url: string): Promise<void> };
const attempt = await connectLoopback('notion', {
store, person: 'Umer',
open: url => yourApp.openExternal(url),
});
await attempt.done;
const connection = attempt.connection;
// Or attempt.cancel() to close the listener and reject done.
听者只绑住 127.0.0.1,有一个OS指定端口, 并关闭成功,
取消、 失败或超时 。 主机打开浏览器;工具包从不运行CLI.
使用 connect() 与 redirectUri 用于网络/PWA或本地验证会话.
完整的 MCP 客户端
import { connect, CallToolResultSchema } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
const notion = connect('notion', {
store, person: 'Umer', redirectUri: 'https://your-app.example/connect/callback',
});
// Sign in once, then reconstruct this handle across app restarts.
const mcp = await notion.mcp({
clientInfo: { name: 'your-app', version: '1' },
clientOptions: { capabilities: {} },
configure: client => { /* register SDK request/notification handlers here */ },
});
try {
const page = await mcp.listTools(); // Follow nextCursor when present.
const result = await mcp.callTool({ name: page.tools[0].name, arguments: {} }, CallToolResultSchema);
// result preserves text, images, structuredContent, isError and other MCP data.
const resources = await mcp.listResources();
const prompts = await mcp.listPrompts();
} finally {
await mcp.close();
}
mcp() 返回官方 SDK Client被困在1.31.0号 它输入的工具,
资源、提示、标记、取消、采样/删除处理器,
通知,通用计划验证 request() 和运输谈判中止
可用。 软件包再出口 Client 和 SDK 的协议类型/计划
通过。 流式HTTP支持JSON和SSE响应和会话头.
每个请求都获得该人的当前符号,范围被限制在被连接的资源上.
401会触发一次刷新/重试;拒绝重定向. 先关闭客户端
disconnect() 释放出他们的溪流。 任意无认证的 MCP 服务器和
遗留的 HTTP + SSE 传输在此签名包初始范围之外.
列出要连接的应用程序
import { catalog, connect, knownApps, mcpDirectory } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
const apps = await catalog([knownApps, mcpDirectory('https://directory.your-app.example/v0/servers?version=latest')]);
const app = apps[0]; // { id, name, icon?, auth, ...the Provider connect() takes }
const connection = connect(app, { store, person: 'Umer', redirectUri: 'https://your-app.example/connect/callback' });
const flow = await connection.signIn(); // Or connectLoopback(app, ...); deviceSignIn(app, ...) when app.auth is 'device'.
catalog(sources) 按此顺序列出主机所选择来源的应用程序; a
id两次列出它的第一个条目。 没有列出,也没有读到
默认。 每个上市的应用程序都是 Provider,所以它通过同样的
connect(), (中文(简体) ), connectLoopback() 财务报告和审定财务报表 deviceSignIn() 如上,在同项下
键盘记录为该id的预设. auth 说如何: oauth 需要应用程序
登记 client, (中文(简体) ), discovery 在应用程序的 MCP 服务器上找到签名
登记自己提供, device 显示输入的代码. icon 是一个 HTTPS
来自列表的图像地址; 使其成为不可信内容 .
knownApps列出工具包的预设providers)并读出无.mcpDirectory(url, { fetch?, timeoutMs? })读取远程 MCP 目录 mCP 登记册中的答复GET /v0/servers形状,后面metadata.nextCursor(最多100页),仅在上市时使用. 它保持服务器 有可流的HTTP遥控器,没有地址变量或所需的信头; 本地软件包,遗留的SSE远程和密钥头服务器被忽略. URL 网络 必须是 HTTPS( 或回转); 对于 MCP 登记册添加version=latest。 。 。 .- 任何对象
list(): Promise<CatalogApp[]>是一个源, 如主机 拥有其服务器后方的管理连接器目录 .
disconnect() 返回时 { revokedAtApp: true } 当应用程序接受撤销,否则 { revokedAtApp: false, reason: 'failed' | 'unsupported' }。当授权元数据发布 RFC 7009 撤销端点时,它会在删除本地记录之前发送刷新符和访问符。 一个失败的请求从不会阻止本地删除 .
当 MCP 请求返回 403 WWW-Authenticate: Bearer error="insufficient_scope"它拒绝 ConnectError 代码 insufficient_scope 财务报告和审定财务报表 requiredScopes 当服务器提供范围提示时。 开始 connection.stepUp(error.requiredScopes ?? ['scope']);返回的登录请求增加范围,所节省的赠款一直可用到 finish() 存储升级的赠款.
以另一个形状拒绝失败或回答的目录 ConnectError
代码 catalog 联合国 status),是无法接触的 network;整个
catalog() 打电话拒绝, 所以列出一个来源 单独保存其余.
主机为它命名的目录提供担保: 签入一个列出的应用程序谈话
到列出名称的服务器.
读取和搜索 Gmail
import { connect, MailReader } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
const gmail = connect('gmail', { store, person: 'Umer', redirectUri: 'https://your-app.example/connect/callback' });
const mail = new MailReader(gmail, { maxBodyChars: 20_000 });
const page = await mail.search('invoice'); // Gmail syntax; follow page.nextPageToken.
const message = await mail.get(page.messages[0].id); // Envelope plus bounded body.
search编号 :list 返回一页信封; get 添加已解码的正文, 切到
maxBodyChars (单位:千美元)body.truncated 说吧 401次重复,然后读作
签出; 费率限制携带 until 从 Retry-After。 。 。 .
发送Gmail, 每封信一次批准
import { connect, MailSender, type OutgoingMail } from '@byokit/connect';
import type { Keystore } from '@byokit/secrets';
declare const store: Keystore;
declare function askPerson(mail: OutgoingMail): Promise<boolean>; // The host's own confirm step.
const gmail = connect('gmail', { store, person: 'Umer', redirectUri: 'https://your-app.example/connect/callback',
scopes: ['https://www.googleapis.com/auth/gmail.send'] });
const sender = new MailSender(gmail, mail => askPerson(mail));
const sent = await sender.send({ to: '[email protected]', subject: 'Invoice paid', body: 'Thanks, Umer' });
发送需要 gmail.send 范围,请求通过 scopes (无赠款的赠款)
无法登录 scope) (中文(简体) ). 每个 send 要求批准一次 与被冻结
信件将编码; 只 true 发送,和任何其他,包括一发,
实值 MailError 代码 denied 在任何代号或提供商呼叫之前 每条一条
调用:不进行分批、排队、重试或背景发送。 网络故障离开
结果未知, 请在请求再次发送前先检查 Sent 。 简单文字正文
只显示赤裸的地址: 没有 cc/bcc, 显示名称、 HTML, 附件, 答复 a
线程或标签.
所有者入境的历史和出口
import { MailHistory, type Connection } from '@byokit/connect';
declare const gmail: Connection; // Signed in for Umer.
const history = new MailHistory(gmail, { owner: 'Umer' });
const rows = await history.messages({ principal: 'Umer', query: 'invoice' });
const csv = await history.export({ principal: 'Umer', query: 'invoice', format: 'csv' }); // Or 'json'.
主机绑定 owner 一次;每次呼叫名称 principal 还有别的
被拒绝(HistoryError 代码 denied在任何提供者获取之前。 检索
页面通过 search编号 :list 最多 maxMessages (默认100起,最多500起).
导出只写信封——消息机构从不被导出. 供应商
失败者留着 MailError 代码, status 财务报告和审定财务报表 until 下级 HistoryError。 。 。 .
刷新、出错和存储
重新刷新值在过期后一分钟内或401后进行。 没错
单飞相通柄共享相同的键盘对象/人/接.
旋转的刷新令牌被坚持;省略刷新令牌的回复保留
前一个。 短暂的失败保留了登录。 只有明确
invalid_grant 将取去. 网络后仍可使用未过期的令牌
失败; 过期的符号永远不会返回 。 没有 expires_in 保留
无效直至被拒绝,然后尽可能刷新。 不需要背景计时器 .
ConnectError.code 是一个打入的应用程序诊断和 message 是一个简单的句子.
OAuth 错误显示可选键 cause 与 error 财务报告和审定财务报表
error_description,根据提供/归还的证书和URL进行消毒.
信息仍然是一句平话。 将提供者描述视为不信任
文本;它们是对宿主的诊断,而不是即时放弃UI. 无出错或
标记被登录。 MCP结果/错误是私人应用内容,应当
将相应对待. fetch, (中文(简体) ), now,请求和流出时间可以注射.
按键依赖提供共享类型Script合同;没有后端
运行时导入。 传出任何 @byokit/secrets 后端( 或一个)
执行它的主机适配器 get编号 :set编号 :delete (合同)。 包里写着一个
JSON秘密,包括赠款、刷新信使、范围、发行人/端点和
按固定散列时间计算
名称。 使用耐用设备
存储; 不将此记录发送到共享服务器。 见 警卫
用于货币和回调边界.
密封桌面令牌存储
在节点或Electron的主要过程中,在通过前将主机的耐用商店包好 它连接。 此适配器只写已认证的密码文本, 包括之后 刷新;店名被捆绑在被封装的有效载荷内.
import { connect } from '@byokit/connect';
import { osKeyringSeal, type Keystore } from '@byokit/secrets/node';
// Your app's durable, per-person store; it receives ciphertext only.
declare const ciphertextStore: Keystore;
const seal = osKeyringSeal({ service: 'crewhouse-connect-Umer', dualWrap: true });
const store: Keystore = {
async get(name) {
const value = await ciphertextStore.get(name);
if (value === null) return null;
const record = JSON.parse(seal.decryptString(Buffer.from(value, 'base64')));
if (record.name !== name || typeof record.secret !== 'string') throw new Error('Stored sign-in could not be opened.');
return record.secret;
},
async set(name, secret) {
const bytes = seal.encryptString(JSON.stringify({ name, secret }));
await ciphertextStore.set(name, Buffer.from(bytes).toString('base64'));
},
delete: name => ciphertextStore.delete(name),
};
const notion = connect('notion', {
store, person: 'Umer', redirectUri: 'https://your-app.example/connect/callback',
});
每人保留一个适配器/存储实例和应用程序,如果
多个进程共享存储. 双层包装许可证只通过所有者读取
当密钥环被锁定时, 主机密钥; 将密钥与密码文本备份分开 .
奥米 dualWrap 通过 fallback: false 当需要可用的OS密钥环时.
见 保密指南 页:1
平台行为、密钥保护和旋转。 签名并正常刷新通过
连接控件; 永远不要将其返回的访问符分开.
船员领养
替换 OAuth、 令牌文件和 RemoteMcp 页:1 src/connections.ts 与
每个成员/应用程序一个处理器,列出来自 catalog([knownApps]) (编) mcpDirectory(url)
或主机自己的来源列出更多). 通过会员的密钥和会员ID,房子谷歌
客户端来自 googleClientFile 以及机组人员的回电地址 保留
流控在主机的回调路由表中直到完成/取消.
乘务员保留了自己的连接屏幕,Google设置了指导和工具门.
对于远程应用程序,呼叫 mcp() 和使用情况 listTools编号 :callTool * 取得全面成果;
用于 Gmail 用户 MailReader编号 :MailSender 下面; 用于日历 token() 直到其输入的工具包
坐在这个连接上。 任何原始的OAuth,刷新或MCP运输都需要留在消费者体内.
核查
npm run build
npm run check
sh scripts/test.sh 'packages/connect/test/*.test.ts'
packages/connect/test/journey-connect.test.ts 驱动已建软件包的方式 a
主机app does - connect编号 :signIn编号 :deviceSignIn编号 :token编号 :mcp, (中文(简体) ), catalog 超过已知的应用程序和a
循环回放 MCP 目录( 一个上市的应用连接端到端), 循环回放听器, 家
谷歌客户端, MailReader编号 :MailSender 财务报告和审定财务报表 MailHistory假的供应商和一个真正的
只能回回调 。 包括PKCE/状态和精确召回装订,每人
隔离、更新种族和取消、发现/DCR/资源约束、丰富的MCP
JSON/SSE 响应,批准/拒绝发送,所有者覆盖的历史和浏览器
捆绑. 不使用真账或出网通话. 现场直播
这些测试不要求供应商/设备的登录.