// Firing door actions without ever blocking the queue of people at the door. // // The UI flashes green and clears the input the moment staff taps; the write // happens here, in the background, with retries. Venue wifi drops constantly, so // every action carries an idempotency key: a retry that actually succeeded the // first time returns the original result instead of double-charging anyone. import { doorApi, type DoorCheckinRequest, type DoorCheckinResponse } from '@/lib/api'; /** UUID per action. crypto.randomUUID needs a secure context; fall back when absent. */ export function newIdempotencyKey(): string { const cryptoRef = typeof crypto !== 'undefined' ? crypto : undefined; if (cryptoRef?.randomUUID) return cryptoRef.randomUUID(); return `door-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`; } // Roughly 5 seconds of retrying in total — long enough to ride out a wifi blip, // short enough that staff learn about a real failure while the person is still // in front of them. const RETRY_DELAYS_MS = [400, 1200, 3000]; /** * Errors worth retrying are the ones a retry can fix: network failures, gateway * errors, rate limits. A 400 "ticket belongs to a different event" will fail * identically forever, so it surfaces immediately. */ function isRetryable(error: any): boolean { const status = error?.status; if (typeof status === 'number') return status >= 500 || status === 408 || status === 429; // No status at all means the request never reached the server (fetch rejects // with a TypeError when the connection drops) — exactly the case to retry. return true; } const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export async function submitDoorAction( eventId: string, body: DoorCheckinRequest, ): Promise { let lastError: any; for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) { try { return await doorApi.checkin(eventId, body); } catch (error: any) { lastError = error; if (attempt === RETRY_DELAYS_MS.length || !isRetryable(error)) break; await sleep(RETRY_DELAYS_MS[attempt]); } } throw lastError; } export async function undoDoorAction(eventId: string, idempotencyKey: string): Promise { await doorApi.undo(eventId, idempotencyKey); }