UNPKG

accounts

Version:

Tempo Accounts SDK

93 lines 4.1 kB
import { Provider as core_Provider } from 'ox'; import { DeviceCode, Transport, Wata, deviceCode as core_deviceCode } from 'wata'; import { fromRequest } from '../internal/fromRequest.js'; /** * Creates a device-code adapter that forwards wallet RPC through Wata. * * Each request runs one OAuth 2.0 Device Authorization Grant exchange * (RFC 8628 with PKCE): the host issues a user code surfaced via * `onPrompt`, the user approves on the host's verification page, and the * adapter polls until the response arrives. */ export function deviceCode(options) { const { actions, fetch, icon, keystores, meta, methods, name, onPrompt, pollingInterval, rdns, timeout = 300_000, url, } = options; return fromRequest({ ...(actions ? { actions } : {}), ...(icon ? { icon } : {}), ...(keystores ? { keystores } : {}), name, rdns, async request(request) { if (methods && !methods.includes(request.method)) throw new core_Provider.UnsupportedMethodError({ message: `\`${request.method}\` not supported by device-code adapter.`, }); // Device code is single-exchange: one register + poll cycle carries one // RPC request envelope, so a fresh Wata instance backs every request. const wata = Wata.create({ transports: [ core_deviceCode({ ...(fetch !== undefined ? { fetch } : {}), ...(meta ? { meta } : {}), ...(pollingInterval !== undefined ? { pollingInterval } : {}), url, }), ], }); const session = wata.start(); // A failed prompt surface (e.g. browser open) fails the exchange — the // user can never learn the code, so polling would only run out the clock. session.onPrompt((prompt) => { void (async () => onPrompt(prompt))().catch((error) => { void session.close(new PromptError(prompt, error)); }); }); const timer = setTimeout(() => { void session.close(new TimeoutError(session.prompt)); }, timeout); try { const { result } = await session.send({ method: request.method, params: request.params ?? [], ...(request.context ? { context: request.context } : {}), }); return result; } catch (error) { if (error instanceof DeviceCode.UserRejectedError) throw new core_Provider.UserRejectedRequestError({ message: 'User denied the device-code request.', }); if (error instanceof Transport.ClosedError) throw new Error('Device code expired before authorization completed.', { cause: error }); throw error; } finally { clearTimeout(timer); } }, }); } /** Thrown when the `onPrompt` callback fails (e.g. no browser could be opened). */ export class PromptError extends Error { cause; prompt; constructor(prompt, cause) { super(`Failed to surface device code ${prompt.userCode}. Open ${prompt.verificationUriFull ?? prompt.verificationUri} manually.`); this.name = 'PromptError'; this.cause = cause; this.prompt = prompt; } } /** Thrown when the user did not approve within {@link deviceCode.Options.timeout}. */ export class TimeoutError extends Error { prompt; constructor(prompt) { super(prompt ? `Timed out waiting for device code ${prompt.userCode}. Continue at ${prompt.verificationUriFull ?? prompt.verificationUri}.` : 'Timed out waiting for device code approval.'); this.name = 'TimeoutError'; this.prompt = prompt; } } //# sourceMappingURL=deviceCode.js.map