import { createHash } from 'node:crypto'; import { createSingleFlight } from './single-flight'; export type GatewayAuthSession = { auth: TAuth; launch: TLaunch; source: 'hit' | 'miss' | 'coalesced'; }; /** * Launch rows are immutable for an executor token's lifetime. The cache still * caps each entry at the token expiry in getOrEstablish. */ export const GATEWAY_IMMUTABLE_SESSION_TTL_MS = 45_000; type StoredSession = { auth: TAuth; launch: TLaunch; expiresAt: number; runId: string; runAttempt: number | null; }; function abortReason(signal: AbortSignal): Error { return signal.reason instanceof Error ? signal.reason : new Error('Gateway session wait was cancelled.'); } async function awaitWithAbort( pending: Promise, signal: AbortSignal | undefined, ): Promise { if (!signal) return await pending; if (signal.aborted) throw abortReason(signal); return await new Promise((resolve, reject) => { const onAbort = () => reject(abortReason(signal)); signal.addEventListener('abort', onAbort, { once: true }); void pending.then( (value) => { signal.removeEventListener('abort', onAbort); resolve(value); }, (error) => { signal.removeEventListener('abort', onAbort); reject(error); }, ); }); } /** Briefly reuse an authenticated immutable launch, bounded by token identity. */ export function createGatewayAuthSessionCache(options: { ttlMs: number; maxEntries: number; identity: (auth: TAuth) => { runId: string | null; runAttempt: number | null; }; now?: () => number; }) { const sessions = new Map>(); const flights = createSingleFlight>(); const now = options.now ?? Date.now; const fingerprint = (token: string) => createHash('sha256').update(token).digest('base64url'); const prune = (at: number) => { for (const [key, session] of sessions) { if (session.expiresAt <= at) sessions.delete(key); } while (sessions.size >= options.maxEntries) { const oldest = sessions.keys().next().value; if (!oldest) break; sessions.delete(oldest); } }; return { async getOrEstablish(input: { token: string; tokenExpiresAt: number; establish: () => Promise<{ auth: TAuth; launch: TLaunch }>; /** Detach this waiter without cancelling the shared establishment. */ signal?: AbortSignal; }): Promise> { if (input.signal?.aborted) throw abortReason(input.signal); const key = fingerprint(input.token); const at = now(); const cached = sessions.get(key); if (cached && cached.expiresAt > at) { const identity = options.identity(cached.auth); if ( identity.runId === cached.runId && identity.runAttempt === cached.runAttempt ) { return { ...cached, source: 'hit' }; } } if (cached) sessions.delete(key); const coalesced = flights.has(key); const session = await awaitWithAbort( flights.run(key, async () => { const established = await input.establish(); const identity = options.identity(established.auth); if (!identity.runId) { throw new Error('Gateway authentication produced no run identity.'); } const establishedAt = now(); const stored: StoredSession = { ...established, expiresAt: Math.min( establishedAt + options.ttlMs, input.tokenExpiresAt, ), runId: identity.runId, runAttempt: identity.runAttempt, }; if (stored.expiresAt > establishedAt) { prune(establishedAt); sessions.set(key, stored); } return stored; }), input.signal, ); return { ...session, source: coalesced ? 'coalesced' : 'miss' }; }, size: () => sessions.size, }; }