/** * The one place a caller turns "I need a credential" into a usable token. * * Both transports go through this. The WebSocket path grew a good version of this * logic first; the HTTP path had none at all and simply sent whatever was in * storage, which is what let an expired token reach the server and come back as a * misleading policy denial. * * THE CONTRACT, which callers depend on: * * returns null -> there is no session. Send the request anonymously; public * reads must keep working for logged-out visitors. * returns token -> use exactly this token. * throws -> there IS a session and it is unusable. NEVER fall back to an * anonymous request, or the caller lands in policy as an * anonymous user and gets the same misleading 403 this whole * change exists to remove. * * Four corrections over the WebSocket original, each of which was a real defect: * * - No sticky module-global failure. The original latched any refresh failure * into `ambientAuthFailure` and rethrew it on every later call, so ONE * transient network blip disabled refresh until an unrelated auth-state * change reset it. * - "Expiring soon" and "already expired" are different. The original collapsed * both behind a 60s buffer, so a transient refresh failure threw away a token * that was still valid for another 59 seconds. * - A missing or non-numeric `exp` is unusable, not fresh. `payload.exp * 1000` * is NaN, and `now > NaN` is false, so such a token read as perfectly fresh. * - Storage is cleared only on a DEFINITIVE rejection, never on a transient one. */ /** * Stable machine code for "there is a session and it cannot be used". * * The VALUE is deliberately unchanged from when this lived in subscription-v2 as * `WS_AUTH_EXPIRED_CODE`: it is public surface that consumers branch on * (`e.code === 'auth_expired'`), so renaming it would break them for no gain. Only * the identifier lost its `WS_` prefix, now that HTTP raises it too. * * The realtime worker's 401 body uses `invalid_or_expired_session` for the same * condition on the wire; this is the client-side counterpart. */ export declare const AUTH_EXPIRED_CODE = "auth_expired"; /** The principal changed under a request that was already in flight. */ export declare const AUTH_CHANGED_CODE = "auth_changed"; export declare function makeAuthExpiredError(message: string, status?: number | string): Error; export declare function makeAuthChangedError(message: string): Error; export declare function isAuthExpiredError(error: unknown): boolean; export declare function normalizeAuthExpiredError(error: unknown, fallbackMessage: string): Error; /** Refresh this many ms before `exp`, so a token cannot die mid-flight. */ export declare const TOKEN_EXPIRY_SKEW_MS = 60000; export type TokenFreshness = /** Good, and not about to expire. Use as-is. */ 'fresh' /** Still valid, but inside the skew window. Refresh, and fall back to it if that fails. */ | 'expiring-soon' /** Past `exp`. Unusable. */ | 'expired' /** Undecodable, or no numeric `exp`. Treated as unusable, never as fresh. */ | 'unusable'; export declare function classifyToken(token: string, nowMs?: number): TokenFreshness; /** Short, non-secret fingerprint used only to key the in-flight map. Never logged. */ export declare function tokenFingerprint(token: string): string; /** * Get a token that is safe to send right now, refreshing if needed. * * `null` means anonymous is fine. A throw means there is a session that cannot be * used; the caller must surface it rather than downgrade to anonymous. */ export declare function getFreshAuthToken(isServer?: boolean, options?: { /** * The server already rejected the stored token, so local expiry says nothing * useful: retrying with the same credential would just be refused again. * Skip the freshness shortcut and require a genuinely new token. */ force?: boolean; }): Promise; /** Test seam: drop any in-flight refresh transactions. */ export declare function resetRefreshTransactions(): void;