File size: 4,587 Bytes
5c98911
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
 * offlineQueue.ts β€” WS-2 Field Expansion
 * μ˜€ν”„λΌμΈ 쀑 제좜 μš”μ²­μ„ IndexedDB에 νμž‰ν•˜κ³ ,
 * 온라인 볡귀 μ‹œ Background Sync(λ˜λŠ” 폴링) 둜 μž¬μ „μ†‘.
 */

export interface QueueItem {
  id?: number;
  url: string;
  method: string;
  headers: Record<string, string>;
  body: string;
  enqueuedAt: number;
  /** 원본 λ―Έμ…˜ ID β€” UI ν‘œμ‹œμš© */
  missionId: string | null;
  /** μ‚¬μš©μž ID */
  userId: string;
}

interface SyncRegistration extends ServiceWorkerRegistration {
  sync: {
    register: (tag: string) => Promise<void>;
  };
}

// ── DB μ΄ˆκΈ°ν™” ─────────────────────────────────────────────────────────────────
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);
  });
}

// ── Enqueue ───────────────────────────────────────────────────────────────────
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);
  });

  // Background Sync 등둝 (μ§€μ›ν•˜λŠ” λΈŒλΌμš°μ €)
  if ('serviceWorker' in navigator && 'SyncManager' in window) {
    const reg = await navigator.serviceWorker.ready;
    await (reg as SyncRegistration).sync.register('carboany-offline-queue').catch(() => {});
  }
}

// ── Flush (온라인 볡귀 μ‹œ 호좜) ───────────────────────────────────────────────
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);
  });
}