logo
Desarrollo
Buscar
Referencia de la API del SDK

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, usa import() 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 → issaud → 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) }
                      
                      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)
}

                    
Este bloque de código en una ventana flotante
  • exp obligatorio: si falta o no es un número finito → lanza MissingClaim (no se trata como «nunca expira»).
  • iat / nbf (si existen) en el futuro más allá del leeway → lanza NotYetValid.
  • Ante un fallo lanza WsaVerificationError (con .code); un error de configuración de quien invoca (como la falta de la clave) lanza TypeError.

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) }
                      
                      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)
}

                    
Este bloque de código en una ventana flotante
  • Fallo de transporte / no 2xx / code≠0 de negocio (como 403209 invalid_grant, 403210 invalid_verifier) / falta de data.wsa → lanza Error.
  • 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 }); } }; }
                      
                      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 });
    }
  };
}

                    
Este bloque de código en una ventana flotante

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) }
                      
                      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)
}

                    
Este bloque de código en una ventana flotante

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 del catch.

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; }
                      
                      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; }

                    
Este bloque de código en una ventana flotante

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; }
                      
                      interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }

                    
Este bloque de código en una ventana flotante

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 }
                      
                      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
}

                    
Este bloque de código en una ventana flotante

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
                      
                      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

                    
Este bloque de código en una ventana flotante

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
                      
                      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

                    
Este bloque de código en una ventana flotante