/** * oauth-token-store.ts, token persistence + honest lifecycle over the injected * secret store. Tokens (access + refresh) live ONLY in the secret store, never in * plain config and never echoed. The store computes an honest connection state from * the stored set + the clock, auto-refreshes when the access token is due, and, when * a refresh fails, records a durable `reconnect-needed` marker so every later read is * honest about the account being broken until the user reconnects. */ import type { CalendarProviderId, Clock, ConnectedAccount, ConnectionState, HttpFetch, ResolvedClientConfig, SecretStoreSlice, StoredTokenSet } from './oauth-types.js'; export interface CalendarTokenStoreOptions { readonly secrets: SecretStoreSlice; readonly clock?: Clock; /** How long before expiry a token counts as due for refresh. */ readonly refreshLeewayMs?: number; } /** A refresh failure the caller turns into a "reconnect needed" surface. */ export declare class TokenRefreshError extends Error { readonly provider: CalendarProviderId; constructor(provider: CalendarProviderId, message: string); } export declare class CalendarTokenStore { private readonly secrets; private readonly clock; private readonly leewayMs; /** * In-instance single-flight dedup for concurrent refreshes of the same provider. * Providers that rotate refresh tokens (Microsoft) invalidate the prior refresh * token the moment one refresh succeeds, a second concurrent call that still * holds the OLD refresh token would otherwise lose the race with invalid_grant * and stamp reconnect-needed over a perfectly working account. Every concurrent * caller for a given provider instead awaits the SAME in-flight refresh. */ private readonly inflightRefresh; constructor(options: CalendarTokenStoreOptions); /** Persist a fresh token set + account metadata; clears any reconnect marker. */ save(provider: CalendarProviderId, tokens: StoredTokenSet, account: ConnectedAccount): Promise; /** Read the stored token set for a provider, or null when disconnected. */ load(provider: CalendarProviderId): Promise; /** Read the account metadata for a provider, or null. */ loadAccount(provider: CalendarProviderId): Promise; /** List every connected account across providers. */ listAccounts(): Promise; /** The honest connection state, from the marker + stored set + clock. */ connectionState(provider: CalendarProviderId): Promise; /** * Return a usable access token, refreshing first when it is due. On a refresh * failure this records a durable reconnect-needed marker and throws * TokenRefreshError, never returns a stale/invalid token as if it were good. */ getFreshAccessToken(provider: CalendarProviderId, config: ResolvedClientConfig, fetchImpl: HttpFetch): Promise; private performRefresh; /** * Disconnect a provider: revoke the token at the provider when possible, then * delete every stored key. Returns whether the provider-side revocation succeeded * (false for providers without a revocation endpoint, disconnect is still local). */ disconnect(provider: CalendarProviderId, config: ResolvedClientConfig, fetchImpl: HttpFetch): Promise<{ readonly revokedRemotely: boolean; }>; /** * Stamp the durable reconnect-needed marker, UNLESS a valid (non-expired) token * set already exists. A refresh can fail here while a concurrent winner (this * instance's own single-flight already prevents an in-process race, but a * separate process/instance sharing the same secret store is not covered by * that) has already stored a fresh, working token set for the same provider. * Re-reading state immediately before writing the marker means a genuinely * working account is never overwritten with a false "reconnect needed". */ private markReconnectNeeded; private syntheticAccount; } //# sourceMappingURL=oauth-token-store.d.ts.map