SDK API 参考
官方 SDK 是两个框架无关、零运行时依赖的包,可以在 GitHub 中获取源码,地址为:https://github.com/gptbots/workspace-extension-sdk。
| 包名 | 运行位置 | 入口 |
|---|---|---|
@gptbots/workspace-extension-verify |
后端(Node ≥ 18) | verifyWsa, exchangeWorkspaceCode |
@gptbots/workspace-extension-sdk |
浏览器 | consumeHandoff, startWorkspaceLogin, completeWorkspaceLogin … |
两个包均为 ESM(
"type": "module")。CommonJS 项目请用动态import()或改用 ESM。
后端包 @gptbots/workspace-extension-verify
verifyWsa(token, options): WorkspaceIdentity
校验 wsa 并返回工作空间身份。校验顺序:签名 → iss → aud → 过期(exp±leeway,含 iat/nbf) → 必填声明。
interface VerifyOptions {
secret?: string; // HS256 密钥(per-app 或共享)。校验 HS256 时必填
publicKey?: string; // RS256 PEM 公钥。校验 RS256 时必填(路线图)
audience: string; // 扩展应用 host,必须等于令牌 aud(必填)
issuer?: string; // 默认 'gptbots-workspace'
leewaySeconds?: number; // 时钟漂移容忍(秒),默认 30
algorithms?: ('HS256' | 'RS256')[]; // 默认 ['HS256']
}
interface WorkspaceIdentity {
accountId: string; // = JWT sub
role: 'OWNER' | 'ADMIN' | 'MEMBER'; // 未知值归一化为 MEMBER
workspaceId: string; // = JWT workspace_id
username?: string; email?: string; avatar?: string; appName?: string;
issuedAt?: number; // = iat(秒)
expiresAt?: number; // = exp(秒)
}
exp强制:缺失或非有限数字 → 抛MissingClaim(不会被当作「永不过期」)。iat/nbf(如存在)在未来超出 leeway → 抛NotYetValid。- 失败抛
WsaVerificationError(带.code);调用方配置错误(如缺密钥)抛TypeError。
WsaVerificationError.code 取值:
| code | 含义 |
|---|---|
InvalidToken |
令牌为空 / 结构非法 / segment 不是 JSON 对象 |
InvalidSignature |
签名不匹配(密钥错、被篡改) |
Expired |
已过期(exp + leeway 之前) |
NotYetValid |
iat/nbf 在未来(超出 leeway) |
WrongIssuer |
iss ≠ 期望值 |
WrongAudience |
aud ≠ 你的 audience |
MissingClaim |
缺 exp / sub / role / workspace_id |
UnsupportedAlgorithm |
alg 不在白名单(默认仅 HS256) |
exchangeWorkspaceCode(options): Promise<WorkspaceCodeExchangeResult>
拉式登录用:在扩展应用后端把一次性 code + PKCE codeVerifier 换成 wsa。切勿在浏览器调用。
interface ExchangeWorkspaceCodeOptions {
tokenUrl: string; // 平台 /token 端点(绝对 URL)
code: string; // 回调拿到的一次性授权码
codeVerifier: string; // 与 code_challenge 对应的 PKCE verifier
fetch?: typeof fetch; // 可注入 fetch(测试/旧运行时);默认全局 fetch
timeoutMs?: number; // 请求超时,默认 10000;传 0 关闭
}
interface WorkspaceCodeExchangeResult {
wsa: string; // 签名后的 wsa,交给 verifyWsa
tokenType?: string; // 'Bearer'
expiresIn?: number; // wsa 有效期(秒)
}
- 传输失败 / 非 2xx / 业务
code≠0(如403209 invalid_grant、403210 invalid_verifier)/ 缺data.wsa→ 抛Error。 - 超时抛
token exchange timed out after <ms>ms(默认 10s,防平台慢响应挂住你的后端)。
Express 中间件示例
import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';
export function requireWorkspaceIdentity(secret: string, audience: string) {
return (req, res, next) => {
try {
req.identity = verifyWsa(req.body.wsa, { secret, audience });
next();
} catch (e) {
const code = e instanceof WsaVerificationError ? e.code : 'Error';
res.status(401).json({ code });
}
};
}
浏览器包 @gptbots/workspace-extension-sdk
推式(Push handoff)
readHandoffToken(search?, paramName?): string | null
纯函数,从查询串读取原始 wsa。search 默认 location.search,paramName 默认 'wsa'。
stripHandoffToken(paramName?, ctx?): void
用 history.replaceState 从当前 URL 移除 wsa,让它不残留在地址栏/历史/Referer。浏览器外安全空操作。ctx?: { history?, location? } 可注入(测试)。
consumeHandoff(options): Promise<WorkspaceIdentity>
use 档便捷流:读 wsa → POST 至扩展应用后端 → 成功后抹除 wsa → 返回身份。
interface ConsumeHandoffOptions {
exchangeUrl: string; // 扩展应用的后端校验接口
fetch?: typeof fetch; // 可注入
search?: string; // 默认 location.search
paramName?: string; // 默认 'wsa'
strip?: boolean; // 默认 true(仅成功时抹除)
}
令牌仅成功交换后抹除,以便瞬时失败可刷新重试。若希望失败也立即抹除,
catch后手动stripHandoffToken()。
拉式(Pull / M-Auth)
startWorkspaceLogin(options): Promise<WorkspaceLoginRequest>
生成 PKCE、存 sessionStorage、构建并(默认)跳转到 /authorize。需要安全上下文(HTTPS/localhost)。
interface StartWorkspaceLoginOptions {
authorizeUrl: string; // 平台 /authorize 端点(绝对 URL)
clientId: string; // 扩展应用的 app home URL
redirectUri: string; // 回调地址,host 须与 clientId 同域
state?: string; // 默认自动生成 16 字节随机 CSRF state
workspaceId?: string; // 预选工作空间,跳过组织选择页
storage?: StorageLike; // 默认 sessionStorage
redirect?: (url: string) => void; // 默认 location.assign
navigate?: boolean; // false = 只构建 URL 不跳转(popup/测试)
}
interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }
readAuthorizeCallback(search?, storage?): AuthorizeCallback | null
在回调落地页:读 code + state,校验 state(CSRF),返回 code + 存储的 codeVerifier。不会消费存储的请求(消费延迟到成功交换),以便瞬时失败可刷新重试。无 code 且无 error 时返回 null。
interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }
stripAuthorizeCallback(ctx?): void
从当前 URL 移除 code / state。
completeWorkspaceLogin(options): Promise<WorkspaceIdentity>
use 档便捷流:读并校验回调 → POST {code, codeVerifier} 给你后端 → 成功后抹除 URL → 返回身份。
interface CompleteWorkspaceLoginOptions {
exchangeUrl: string; // 你自己的后端换取接口
fetch?: typeof fetch;
search?: string; // 默认 location.search
storage?: StorageLike; // 默认 sessionStorage
strip?: boolean; // 默认 true
}
WorkspaceLoginError.code 取值
| code | 含义 |
|---|---|
NoCallback |
URL 里没有授权码 |
MissingRequest |
没有/损坏的已存登录请求(先调 startWorkspaceLogin) |
StateMismatch |
state CSRF 校验不通过 |
AuthorizeError |
回调是 OAuth 错误重定向(?error=...) |
NoFetch |
无可用 fetch 实现 |
ExchangeFailed |
交换接口返回非 2xx |
InvalidResponse |
交换接口返回非法 JSON |
CryptoUnavailable |
无 Web Crypto(需 HTTPS/localhost 安全上下文) |
StorageUnavailable |
无 sessionStorage(无法存 PKCE verifier) |
InvalidAuthorizeUrl |
authorizeUrl 不是绝对 URL |
三、从压缩包本地安装
unzip workspace-extension-sdk-0.1.0.zip
# 两个包已预构建 dist(main=dist/index.js, types=dist/index.d.ts)
npm i ./workspace-extension-sdk/packages/verify # 后端
npm i ./workspace-extension-sdk/packages/browser # 前端
如需自行构建 / 跑测试:
cd workspace-extension-sdk
npm install
npm run build # tsc → 各包 dist
npm test # node --test(零外部测试依赖,需 Node ≥ 22.6 以直跑 .ts)
npm run type-check # tsc --noEmit
