// Push queue worker: drains pending items, POSTs to the ingest endpoint, // removes on success, backs off exponentially on failure, marks failed // after MAX_RETRIES attempts. Driven by alarms in background.js. // // ★ 无状态:每次 processQueue 自己 loadConfig() 从 chrome.storage 读配置, // 不缓存到模块级变量 —— MV3 SW 休眠重启会清空模块状态,缓存会导致 worker 永不推送(B)。 import { getPending, markStatus } from './queue.js'; import { buildIngestPayload } from './payload.js'; import { loadConfig } from './config.js'; const MAX_RETRIES = 8; export async function processQueue() { const cfg = await loadConfig(); if (!cfg || !cfg.enabled || !cfg.serverUrl || !cfg.token) { return { processed: 0 }; } const items = await getPending(3); let processed = 0; for (const it of items) { try { const payload = buildIngestPayload(it.payload.record, it.payload.messages); const res = await fetch(`${cfg.serverUrl}/api/ingest`, { method: 'POST', headers: { Authorization: `Bearer ${cfg.token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (res.ok) { // 标 sent 保留记录供 getStats 统计;旧实现 remove 会让 popup"已推"永远 0(显示 bug) await markStatus(it.id, 'sent'); processed++; } else { await bumpRetry(it, `HTTP ${res.status}`); } } catch (e) { await bumpRetry(it, String((e && e.message) || e)); } } return { processed }; } async function bumpRetry(it, err) { const retries = (it.retries || 0) + 1; if (retries >= MAX_RETRIES) { await markStatus(it.id, 'failed', { retries, lastError: err }); } else { const delay = Math.min(Math.pow(2, retries) * 1000, 3600 * 1000); await markStatus(it.id, 'pending', { retries, nextRetry: Date.now() + delay, lastError: err, }); } }