/** * tools/requestQueue.ts — Request queue for mobile * * Gestisce: * - Concorrenza limitata * - Timeout e retry * - Priority queue * - Abort support */ export interface QueuedRequest { id: string; priority: number; // 0-10, higher = more important fn: () => Promise; timeout: number; retries: number; resolve: (value: unknown) => void; reject: (error: Error) => void; abortSignal?: AbortSignal; } export class RequestQueue { private queue: QueuedRequest[] = []; private running = 0; private maxConcurrent = 2; // iPhone-friendly private requestId = 0; constructor(maxConcurrent: number = 2) { this.maxConcurrent = maxConcurrent; } /** * Add request to queue */ add( fn: () => Promise, options: { priority?: number; timeout?: number; retries?: number; abortSignal?: AbortSignal; } = {}, ): Promise { return new Promise((resolve, reject) => { const request: QueuedRequest = { id: `req_${++this.requestId}`, priority: options.priority ?? 5, fn, timeout: options.timeout ?? 30000, retries: options.retries ?? 1, resolve: resolve as (value: unknown) => void, reject, abortSignal: options.abortSignal, }; this.queue.push(request); this.queue.sort((a, b) => b.priority - a.priority); this.process(); }); } /** * Process queue */ private process(): void { while ( this.running < this.maxConcurrent && this.queue.length > 0 ) { const request = this.queue.shift(); if (!request) break; this.running++; this.executeRequest(request); } } /** * Execute single request with retry */ private async executeRequest( request: QueuedRequest, ): Promise { let lastError: Error | undefined; for (let attempt = 0; attempt <= request.retries; attempt++) { try { // Check abort signal if (request.abortSignal?.aborted) { throw new Error("Richiesta interrotta"); } // Execute with timeout const result = await Promise.race([ request.fn(), this.timeout(request.timeout), ]); request.resolve(result); this.running--; this.process(); return; } catch (error) { lastError = error as Error; // Don't retry on abort if ( lastError.message === "Richiesta interrotta" ) { break; } // Wait before retry if (attempt < request.retries) { await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1)), ); } } } request.reject( lastError || new Error("Errore sconosciuto"), ); this.running--; this.process(); } /** * Timeout promise */ private timeout(ms: number): Promise { return new Promise((_, reject) => setTimeout( () => reject(new Error("Timeout della richiesta")), ms, ), ); } /** * Get queue size */ getSize(): number { return this.queue.length; } /** * Clear queue */ clear(): void { for (const request of this.queue) { request.reject(new Error("Coda svuotata")); } this.queue = []; } /** * Set max concurrent */ setMaxConcurrent(max: number): void { this.maxConcurrent = max; this.process(); } } // Global queue instance let globalQueue: RequestQueue | null = null; export function getGlobalQueue(): RequestQueue { if (!globalQueue) { globalQueue = new RequestQueue(2); } return globalQueue; } export function setGlobalQueueConcurrency( max: number, ): void { getGlobalQueue().setMaxConcurrent(max); }