import { request } from '../request.js'; import { AgentIdPError } from '../errors.js'; import type { IntrospectResponse } from '../types.js'; /** * Client for OAuth 2.0 token operations (introspect and revoke). * Token issuance is handled separately by TokenManager. */ export class TokenClient { constructor( private readonly baseUrl: string, private readonly getToken: () => Promise, ) {} /** * Introspect a token to check whether it is currently active. * Always returns 200 — check the `active` field to determine validity. * Requires a Bearer token with `tokens:read` scope. */ async introspectToken(tokenToCheck: string): Promise { const token = await this.getToken(); const body = new URLSearchParams({ token: tokenToCheck }); let response: Response; try { response = await fetch(new URL('/api/v1/token/introspect', this.baseUrl).toString(), { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); } catch (err) { throw new AgentIdPError( 'NETWORK_ERROR', `Network error: ${err instanceof Error ? err.message : String(err)}`, 0, ); } const data: unknown = await response.json(); if (!response.ok) { throw AgentIdPError.fromApiError(data, response.status); } return data as IntrospectResponse; } /** * Revoke a token immediately. Idempotent — revoking an already-revoked * or expired token is not an error (RFC 7009). */ async revokeToken(tokenToRevoke: string): Promise { const token = await this.getToken(); const body = new URLSearchParams({ token: tokenToRevoke }); let response: Response; try { response = await fetch(new URL('/api/v1/token/revoke', this.baseUrl).toString(), { method: 'POST', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded', }, body: body.toString(), }); } catch (err) { throw new AgentIdPError( 'NETWORK_ERROR', `Network error: ${err instanceof Error ? err.message : String(err)}`, 0, ); } if (!response.ok) { const data: unknown = await response.json(); throw AgentIdPError.fromApiError(data, response.status); } } }