// ── AI Detector · background.js (service worker) ───────────────────────── // // Responsibilities: // 1. Keep a canonical scan-result cache (in-memory + storage) // 2. Handle OPEN_SIDE_PANEL messages from popup // 3. Relay GET_SCAN_RESULT / CLEAR_SCAN_RESULT messages to side panel // ── Init side panel on action click ─────────────────────────────────────── chrome.runtime.onInstalled.addListener(() => { chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch((error) => console.error(error)); }); // ── In-memory result cache ──────────────────────────────────────────────── // Survives for the lifetime of the service worker (typically a few minutes // of inactivity). chrome.storage.local is the durable fallback. let cachedResult = null; const activeScans = new Set(); chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { // Orchestrate scan logic globally to avoid duplication and sync state if (msg.type === "START_SCAN") { const tabId = msg.tabId; if (activeScans.has(tabId)) { sendResponse({ ok: false }); return false; } activeScans.add(tabId); console.log("SCAN_STARTED"); cachedResult = { status: "scanning", tabId, timestamp: Date.now() }; chrome.storage.local.set({ scanResult: cachedResult, lastScreenshot: msg.screenshot }).catch(()=>{}); chrome.scripting.executeScript({ target: { tabId }, files: ["content.js"] }).then(() => { const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Scan timed out after 90 seconds")), 90000)); const scanPromise = new Promise((resolve, reject) => { chrome.tabs.sendMessage(tabId, { type: "SCAN_IMAGES", screenshot: msg.screenshot }, (resp) => { if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message)); else resolve(resp); }); }); return Promise.race([scanPromise, timeoutPromise]); }).then((response) => { if (!response) throw new Error("No response from content script"); let finalResult; if (response.stopped) { finalResult = { status: "stopped", risk: 0, count: 0, explanation: null, tabId, timestamp: Date.now() }; } else if (response.done) { const risk = typeof response.risk === "number" ? response.risk : 0; const count = typeof response.count === "number" ? response.count : 0; const explanation = response.explanation || null; const rawConfidence = typeof response.rawConfidence === "number" ? response.rawConfidence : risk / 100; finalResult = { status: "done", risk, count, explanation, rawConfidence, tabId, timestamp: Date.now() }; } else { throw new Error(response.error || "Unexpected response"); } console.log("SCAN_COMPLETED"); cachedResult = finalResult; chrome.storage.local.set({ scanResult: finalResult }).catch(()=>{}); }).catch(err => { console.warn("SCAN_ERROR:", err.message); const errResult = { status: "error", error: err.message, tabId, timestamp: Date.now() }; cachedResult = errResult; chrome.storage.local.set({ scanResult: errResult }).catch(()=>{}); }).finally(() => { activeScans.delete(tabId); }); sendResponse({ ok: true }); return false; } if (msg.type === "STOP_SCAN") { console.log("SCAN_STOPPED"); chrome.tabs.sendMessage(msg.tabId, { type: "STOP_SCAN" }, () => { void chrome.runtime.lastError; }); sendResponse({ ok: true }); return false; } // Side panel → background: retrieve latest scan result if (msg.type === "GET_SCAN_RESULT") { if (cachedResult) { sendResponse({ result: cachedResult }); return false; } // Fall back to storage (service worker may have been restarted) chrome.storage.local.get("scanResult", (data) => { if (chrome.runtime.lastError) { console.warn("[BG] storage read error:", chrome.runtime.lastError.message); sendResponse({ result: null }); return; } cachedResult = data.scanResult || null; sendResponse({ result: cachedResult }); }); return true; // async response } // Popup or side panel: clear stored result if (msg.type === "CLEAR_SCAN_RESULT") { cachedResult = null; chrome.storage.local.remove("scanResult"); sendResponse({ ok: true }); return false; } });