/* ============================================= HELLION NEWTAB — background.js Quick-Save Background fuer Chrome (Service-Worker) UND Firefox (Event-Page). Kein DOM/window. Listener synchron auf Top-Level. Geteilte Logik via importScripts. ============================================= */ // Geteiltes DOM-freies Helfer-Modul aus Phase 1: ensureInbox(boards), uid(), normalizeBookmark(...). // Chrome-Service-Worker laedt es via importScripts. Firefox-Event-Page hat KEIN importScripts — // dort kommt das Modul ueber background.scripts (manifest.firefox.json) in den Scope, ensureInbox // ist dann schon definiert. Der Guard verhindert den ReferenceError in der Event-Page. if (typeof importScripts === 'function' && typeof ensureInbox === 'undefined') { importScripts('quicksave-core.js'); } // chrome.storage.local-Lese-/Schreib-Helfer als Promises (kein Store-Modul im Worker, // das ist DOM/Seiten-gebunden). Identisches Verhalten: get -> Wert oder null. function bgGet(key) { return new Promise(resolve => { chrome.storage.local.get([key], r => resolve(r[key] ?? null)); }); } function bgSet(key, value) { return new Promise((resolve, reject) => { chrome.storage.local.set({ [key]: value }, () => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; } resolve(); }); }); } // Kurze Badge-Bestaetigung, dann automatisch wieder leeren. color optional (Default gruen). function flashBadge(text, color) { chrome.action.setBadgeText({ text }); // Hintergrundfarbe optional, ohne extra Permission moeglich. if (chrome.action.setBadgeBackgroundColor) { chrome.action.setBadgeBackgroundColor({ color: color || '#1f9d55' }); } setTimeout(() => chrome.action.setBadgeText({ text: '' }), 2000); } // Interne/nicht speicherbare Seiten (Browser-UI, Extension-Seiten) — kein sinnvolles Bookmark. const UNSAVEABLE_URL = /^(chrome|chrome-extension|about|edge|opera|moz-extension|brave|vivaldi|view-source|devtools):/i; // Quick-Save: aktiven Tab in die Pending-Queue haengen — NICHT boards schreiben. // Datensicherheit (Phase-4-Review 2b): boards schreibt ausschliesslich die NewTab-Seite. Der Worker // haengt nur an 'quicksave_pending' an; die Seite drained die Queue in die Inbox. So koennen Worker // und Seite sich nicht im boards-Array gegenseitig ueberschreiben (kein Lost-Update bestehender Daten). async function quickSaveActiveTab() { const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); const tab = tabs && tabs[0]; if (!tab || !tab.url || UNSAVEABLE_URL.test(tab.url)) { // Kein speicherbarer Tab: kurzer roter Marker (langer Text wird im Badge abgeschnitten). flashBadge('×', '#c0392b'); return; } try { // read-modify-write nur auf der EIGENEN Queue (bgGet/bgSet sind via quickSaveChain serialisiert). const pending = (await bgGet('quicksave_pending')) ?? []; pending.push({ id: uid(), title: tab.title || tab.url, url: tab.url }); await bgSet('quicksave_pending', pending); flashBadge(chrome.i18n.getMessage('quickSaveBadge')); } catch (e) { // Quota o.ae.: Badge zeigt nichts Gruenes, Fehler in die Worker-Konsole. console.error('Quick-Save fehlgeschlagen:', e.message); } } // Quick-Saves serialisieren: zwei schnelle Tastendruecke (Key-Repeat) wuerden sonst parallel // read-modify-write machen und sich gegenseitig ueberschreiben (lost update). Promise-Kette // sorgt fuer sequentielle Ausfuehrung. Listener bleibt SYNCHRON auf Top-Level registriert. let quickSaveChain = Promise.resolve(); chrome.commands.onCommand.addListener(command => { if (command === 'quick-save') { quickSaveChain = quickSaveChain.then(() => quickSaveActiveTab()).catch(e => console.error('Quick-Save:', e && e.message)); } });