/** Raised when the OAuth client can't restore a user's session: the grant * was revoked, the refresh token expired, or the client deleted the stored * session after an invalid_grant. Mapped to non-retryable 401 in * parsePdsError so we don't retry-spam a session that won't come back. * * Lives in its own module so action test mocks of ./pds.js don't have to * re-export it for `instanceof` checks in delivery.ts to keep working. */ export class PdsSessionMissingError extends Error { readonly did: string; constructor(did: string, cause?: unknown) { super(`OAuth session for ${did} is missing; user must reconnect Airglow.`, { cause }); this.name = "PdsSessionMissingError"; this.did = did; } } /** Raised when a PDS XRPC call answers with a non-2xx status. Carries the * status and the rate-limit reset so callers can park on a 429 until the * PDS says the window resets. The message keeps the `(NNN)` status format * that `parsePdsError` in lib/actions/delivery.ts matches, so existing * callers keep classifying it exactly as before. */ export class PdsHttpError extends Error { readonly status: number; /** `ratelimit-reset` header as epoch seconds, when the PDS sent one. */ readonly rateLimitReset: number | null; readonly body: string; constructor(nsid: string, status: number, body: string, rateLimitReset: number | null) { super(`PDS ${nsid} failed (${status}): ${body}`); this.name = "PdsHttpError"; this.status = status; this.body = body; this.rateLimitReset = rateLimitReset; } } /** Parse a `ratelimit-reset` header (epoch seconds) into a number, or null. */ export function parseRateLimitReset(header: string | null): number | null { if (!header) return null; const value = Number(header.trim()); return Number.isFinite(value) && value > 0 ? value : null; }