| |
| |
| |
| |
| |
|
|
| export interface QueueItem { |
| id?: number; |
| url: string; |
| method: string; |
| headers: Record<string, string>; |
| body: string; |
| enqueuedAt: number; |
| |
| missionId: string | null; |
| |
| userId: string; |
| } |
|
|
| interface SyncRegistration extends ServiceWorkerRegistration { |
| sync: { |
| register: (tag: string) => Promise<void>; |
| }; |
| } |
|
|
| |
| const DB_NAME = 'carboany-queue'; |
| const DB_VERSION = 1; |
| const STORE = 'queue'; |
|
|
| let _db: IDBDatabase | null = null; |
|
|
| function openDb(): Promise<IDBDatabase> { |
| if (_db) return Promise.resolve(_db); |
| return new Promise((resolve, reject) => { |
| const req = indexedDB.open(DB_NAME, DB_VERSION); |
| req.onupgradeneeded = (e) => { |
| const db = (e.target as IDBOpenDBRequest).result; |
| if (!db.objectStoreNames.contains(STORE)) { |
| db.createObjectStore(STORE, { keyPath: 'id', autoIncrement: true }); |
| } |
| }; |
| req.onsuccess = (e) => { |
| _db = (e.target as IDBOpenDBRequest).result; |
| resolve(_db!); |
| }; |
| req.onerror = () => reject(req.error); |
| }); |
| } |
|
|
| |
| export async function enqueueSubmission(item: Omit<QueueItem, 'id' | 'enqueuedAt'>): Promise<void> { |
| const db = await openDb(); |
| const entry: Omit<QueueItem, 'id'> = { ...item, enqueuedAt: Date.now() }; |
| await new Promise<void>((res, rej) => { |
| const tx = db.transaction(STORE, 'readwrite'); |
| const req = tx.objectStore(STORE).add(entry); |
| req.onsuccess = () => res(); |
| req.onerror = () => rej(req.error); |
| }); |
|
|
| |
| if ('serviceWorker' in navigator && 'SyncManager' in window) { |
| const reg = await navigator.serviceWorker.ready; |
| await (reg as SyncRegistration).sync.register('carboany-offline-queue').catch(() => {}); |
| } |
| } |
|
|
| |
| export async function flushQueue( |
| onSuccess?: (item: QueueItem) => void, |
| onError?: (item: QueueItem, err: unknown) => void |
| ): Promise<void> { |
| const db = await openDb(); |
| const items = await getAllItems(db); |
| if (items.length === 0) return; |
|
|
| for (const item of items) { |
| try { |
| const res = await fetch(item.url, { |
| method: item.method, |
| headers: item.headers, |
| body: item.body, |
| }); |
| if (res.ok) { |
| await deleteItem(db, item.id!); |
| onSuccess?.(item); |
| } else { |
| onError?.(item, new Error(`HTTP ${res.status}`)); |
| } |
| } catch (err) { |
| onError?.(item, err); |
| } |
| } |
| } |
|
|
| |
| export async function getQueueSize(): Promise<number> { |
| const db = await openDb(); |
| return new Promise((res, rej) => { |
| const tx = db.transaction(STORE, 'readonly'); |
| const req = tx.objectStore(STORE).count(); |
| req.onsuccess = () => res(req.result); |
| req.onerror = () => rej(req.error); |
| }); |
| } |
|
|
| |
| function getAllItems(db: IDBDatabase): Promise<QueueItem[]> { |
| return new Promise((res, rej) => { |
| const tx = db.transaction(STORE, 'readonly'); |
| const req = tx.objectStore(STORE).getAll(); |
| req.onsuccess = () => res(req.result as QueueItem[]); |
| req.onerror = () => rej(req.error); |
| }); |
| } |
|
|
| function deleteItem(db: IDBDatabase, id: number): Promise<void> { |
| return new Promise((res, rej) => { |
| const tx = db.transaction(STORE, 'readwrite'); |
| const req = tx.objectStore(STORE).delete(id); |
| req.onsuccess = () => res(); |
| req.onerror = () => rej(req.error); |
| }); |
| } |
|
|