// @ts-nocheck import { withStore } from './idb'; import { getDeviceId } from '../lib/device'; import { createUpload } from './xhr'; import { notify } from './notify'; type SyncManager = { register(tag: string): Promise; }; export const buildQueueUploadUrl = (eventToken: string) => `/api/v1/events/${encodeURIComponent(eventToken)}/upload`; export type QueueItem = { id?: number; eventToken: string; fileName: string; blob: Blob; emotion_id?: number | null; task_id?: number | null; live_show_opt_in?: boolean | null; status: 'pending' | 'uploading' | 'done' | 'error'; retries: number; nextAttemptAt?: number | null; createdAt: number; photoId?: number; }; let processing = false; export async function enqueue(item: Omit) { const now = Date.now(); await withStore('readwrite', (store) => { store.add({ ...item, status: 'pending', retries: 0, createdAt: now }); }); // Register background sync if available if ('serviceWorker' in navigator && 'SyncManager' in window) { try { const reg = await navigator.serviceWorker.ready; (reg as ServiceWorkerRegistration & { sync?: SyncManager }).sync?.register('upload-queue'); } catch (error) { console.warn('Background sync registration failed', error); } } } export async function list(): Promise { return withStore('readonly', (store) => new Promise((resolve) => { const req = store.getAll(); req.onsuccess = () => resolve(req.result as QueueItem[]); })); } export async function clearDone() { const items = await list(); await withStore('readwrite', (store) => { for (const it of items) { if (it.status === 'done') store.delete(it.id!); } }); } export async function processQueue() { if (processing) return; processing = true; try { const now = Date.now(); const items = await list(); for (const it of items) { if (it.status === 'done') continue; if (it.nextAttemptAt && it.nextAttemptAt > now) continue; await markStatus(it.id!, 'uploading'); const ok = await attemptUpload(it); if (ok) { await markStatus(it.id!, 'done'); } else { const retries = (it.retries ?? 0) + 1; const backoffSec = Math.min(60, Math.pow(2, Math.min(retries, 5))); // 2,4,8,16,32,60 await update(it.id!, { status: 'error', retries, nextAttemptAt: Date.now() + backoffSec * 1000 }); } } } finally { processing = false; } } async function attemptUpload(it: QueueItem): Promise { if (!navigator.onLine) return false; try { const json = await createUpload( buildQueueUploadUrl(it.eventToken), it, getDeviceId(), (pct) => { try { window.dispatchEvent(new CustomEvent('queue-progress', { detail: { id: it.id, progress: pct } })); } catch (error) { console.warn('Queue progress dispatch failed', error); } } ); // mark my-photo-ids for "Meine" try { const raw = localStorage.getItem('my-photo-ids'); const arr: number[] = raw ? JSON.parse(raw) : []; if (json.id && !arr.includes(json.id)) localStorage.setItem('my-photo-ids', JSON.stringify([json.id, ...arr])); } catch (error) { console.warn('Failed to persist my-photo-ids', error); } notify('Upload erfolgreich', 'success'); return true; } catch { notify('Upload fehlgeschlagen', 'error'); return false; } } async function markStatus(id: number, status: QueueItem['status']) { await update(id, { status }); } async function update(id: number, patch: Partial) { await withStore('readwrite', (store) => new Promise((resolve, reject) => { const getReq = store.get(id); getReq.onsuccess = () => { const val = getReq.result as QueueItem; store.put({ ...val, ...patch, id }); resolve(); }; getReq.onerror = () => reject(getReq.error); })); }