Referencia de la API del SDK
El SDK oficial son dos paquetes independientes del framework y sin dependencias en tiempo de ejecución; puedes obtener el código fuente en GitHub, en la dirección: https://github.com/gptbots/workspace-extension-sdk.
| Nombre del paquete | Ubicación de ejecución | Entrada |
|---|---|---|
@gptbots/workspace-extension-verify |
Backend (Node ≥ 18) | verifyWsa, exchangeWorkspaceCode |
@gptbots/workspace-extension-sdk |
Navegador | consumeHandoff, startWorkspaceLogin, completeWorkspaceLogin … |
Ambos paquetes son ESM (
"type": "module"). Para proyectos CommonJS, usaimport()dinámico o migra a ESM.
Paquete de backend @gptbots/workspace-extension-verify
verifyWsa(token, options): WorkspaceIdentity
Verifica el wsa y devuelve la identidad del espacio de trabajo. Orden de verificación: firma → iss → aud → expiración (exp±leeway, incluidos iat/nbf) → claims obligatorios.
interface VerifyOptions {
secret?: string; // clave HS256 (per-app o compartida). Obligatoria al verificar HS256
publicKey?: string; // clave pública RS256 PEM. Obligatoria al verificar RS256 (roadmap)
audience: string; // host de la app de extensión, debe ser igual al aud del token (obligatorio)
issuer?: string; // por defecto 'gptbots-workspace'
leewaySeconds?: number; // tolerancia de deriva del reloj (segundos), por defecto 30
algorithms?: ('HS256' | 'RS256')[]; // por defecto ['HS256']
}
interface WorkspaceIdentity {
accountId: string; // = sub del JWT
role: 'OWNER' | 'ADMIN' | 'MEMBER'; // los valores desconocidos se normalizan a MEMBER
workspaceId: string; // = workspace_id del JWT
username?: string; email?: string; avatar?: string; appName?: string;
issuedAt?: number; // = iat (segundos)
expiresAt?: number; // = exp (segundos)
}
expobligatorio: si falta o no es un número finito → lanzaMissingClaim(no se trata como «nunca expira»).iat/nbf(si existen) en el futuro más allá del leeway → lanzaNotYetValid.- Ante un fallo lanza
WsaVerificationError(con.code); un error de configuración de quien invoca (como la falta de la clave) lanzaTypeError.
Valores de WsaVerificationError.code:
| code | Significado |
|---|---|
InvalidToken |
El token está vacío / con estructura inválida / un segmento no es un objeto JSON |
InvalidSignature |
La firma no coincide (clave incorrecta, token manipulado) |
Expired |
Ha expirado (antes de exp + leeway) |
NotYetValid |
iat/nbf en el futuro (más allá del leeway) |
WrongIssuer |
iss ≠ el valor esperado |
WrongAudience |
aud ≠ tu audience |
MissingClaim |
Falta exp / sub / role / workspace_id |
UnsupportedAlgorithm |
alg no está en la lista blanca (por defecto solo HS256) |
exchangeWorkspaceCode(options): Promise<WorkspaceCodeExchangeResult>
Para el inicio de sesión pull: en el backend de la app de extensión, canjea el code de un solo uso + el codeVerifier de PKCE por un wsa. Nunca lo invoques en el navegador.
interface ExchangeWorkspaceCodeOptions {
tokenUrl: string; // endpoint /token de la plataforma (URL absoluta)
code: string; // código de autorización de un solo uso obtenido en el callback
codeVerifier: string; // verifier de PKCE correspondiente al code_challenge
fetch?: typeof fetch; // fetch inyectable (pruebas/runtimes antiguos); por defecto el fetch global
timeoutMs?: number; // timeout de la petición, por defecto 10000; pasa 0 para desactivarlo
}
interface WorkspaceCodeExchangeResult {
wsa: string; // el wsa firmado, entrégaselo a verifyWsa
tokenType?: string; // 'Bearer'
expiresIn?: number; // validez del wsa (segundos)
}
- Fallo de transporte / no 2xx /
code≠0de negocio (como403209 invalid_grant,403210 invalid_verifier) / falta dedata.wsa→ lanzaError. - El timeout lanza
token exchange timed out after <ms>ms(por defecto 10s, para evitar que una respuesta lenta de la plataforma bloquee tu backend).
Ejemplo de middleware de 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 });
}
};
}
Paquete de navegador @gptbots/workspace-extension-sdk
Push (Push handoff)
readHandoffToken(search?, paramName?): string | null
Función pura, lee el wsa original de la query string. search por defecto es location.search, y paramName por defecto es 'wsa'.
stripHandoffToken(paramName?, ctx?): void
Usa history.replaceState para eliminar el wsa de la URL actual, para que no quede en la barra de direcciones/historial/Referer. Fuera del navegador es una operación segura sin efecto. ctx?: { history?, location? } es inyectable (pruebas).
consumeHandoff(options): Promise<WorkspaceIdentity>
Flujo cómodo del nivel use: lee el wsa → hace POST al backend de la app de extensión → tras el éxito elimina el wsa → devuelve la identidad.
interface ConsumeHandoffOptions {
exchangeUrl: string; // endpoint de verificación del backend de la app de extensión
fetch?: typeof fetch; // inyectable
search?: string; // por defecto location.search
paramName?: string; // por defecto 'wsa'
strip?: boolean; // por defecto true (solo elimina en caso de éxito)
}
El token solo se elimina tras un intercambio exitoso, de modo que un fallo transitorio pueda reintentarse actualizando la página. Si deseas eliminarlo de inmediato también en caso de fallo, invoca manualmente
stripHandoffToken()después delcatch.
Pull (Pull / M-Auth)
startWorkspaceLogin(options): Promise<WorkspaceLoginRequest>
Genera PKCE, guarda en sessionStorage, construye y (por defecto) redirige a /authorize. Requiere un contexto seguro (HTTPS/localhost).
interface StartWorkspaceLoginOptions {
authorizeUrl: string; // endpoint /authorize de la plataforma (URL absoluta)
clientId: string; // la app home URL de la app de extensión
redirectUri: string; // dirección de callback, el host debe ser del mismo dominio que clientId
state?: string; // por defecto genera automáticamente un state CSRF aleatorio de 16 bytes
workspaceId?: string; // espacio de trabajo preseleccionado, omite la página de selección de organización
storage?: StorageLike; // por defecto sessionStorage
redirect?: (url: string) => void; // por defecto location.assign
navigate?: boolean; // false = solo construye la URL sin redirigir (popup/pruebas)
}
interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }
readAuthorizeCallback(search?, storage?): AuthorizeCallback | null
En la página de aterrizaje del callback: lee code + state, verifica el state (CSRF), y devuelve el code + el codeVerifier almacenado. No consume la petición almacenada (el consumo se difiere hasta un intercambio exitoso), de modo que un fallo transitorio pueda reintentarse actualizando la página. Cuando no hay code ni error, devuelve null.
interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }
stripAuthorizeCallback(ctx?): void
Elimina el code / state de la URL actual.
completeWorkspaceLogin(options): Promise<WorkspaceIdentity>
Flujo cómodo del nivel use: lee y verifica el callback → hace POST {code, codeVerifier} a tu backend → tras el éxito elimina la URL → devuelve la identidad.
interface CompleteWorkspaceLoginOptions {
exchangeUrl: string; // tu propio endpoint de canje en el backend
fetch?: typeof fetch;
search?: string; // por defecto location.search
storage?: StorageLike; // por defecto sessionStorage
strip?: boolean; // por defecto true
}
Valores de WorkspaceLoginError.code
| code | Significado |
|---|---|
NoCallback |
No hay código de autorización en la URL |
MissingRequest |
Petición de login almacenada ausente/corrupta (invoca primero startWorkspaceLogin) |
StateMismatch |
La verificación CSRF del state no pasa |
AuthorizeError |
El callback es una redirección de error de OAuth (?error=...) |
NoFetch |
No hay una implementación de fetch disponible |
ExchangeFailed |
El endpoint de canje devolvió un no 2xx |
InvalidResponse |
El endpoint de canje devolvió un JSON inválido |
CryptoUnavailable |
No hay Web Crypto (requiere un contexto seguro HTTPS/localhost) |
StorageUnavailable |
No hay sessionStorage (no se puede guardar el verifier de PKCE) |
InvalidAuthorizeUrl |
El authorizeUrl no es una URL absoluta |
Tres. Instalación local desde el paquete comprimido
unzip workspace-extension-sdk-0.1.0.zip
# Ambos paquetes ya tienen un dist preconstruido (main=dist/index.js, types=dist/index.d.ts)
npm i ./workspace-extension-sdk/packages/verify # backend
npm i ./workspace-extension-sdk/packages/browser # frontend
Si necesitas construir por tu cuenta / ejecutar las pruebas:
cd workspace-extension-sdk
npm install
npm run build # tsc → dist de cada paquete
npm test # node --test (sin dependencias de prueba externas, requiere Node ≥ 22.6 para ejecutar .ts directamente)
npm run type-check # tsc --noEmit
