Files
Hellion-NewTab/src/js/quicksave-core.js
T
JonKazama-Hellion 327bcd3385 fix(quick-save): Drain idempotent (srcId-Dedup) + isSafeUrl-Gate
Zwei Befunde aus der Integrations-Review:
- Race: der Page-Drain und der Worker machen je ein read-modify-write auf
  'quicksave_pending' ohne kontextuebergreifende Atomizitaet. Ein Worker-Append im
  await-Fenster des Drains konnte einen bereits gedrainten Eintrag in der Queue belassen,
  den ein Folge-Drain erneut in die Inbox schrieb (Duplikat). Jede eingespielte Bookmark
  traegt jetzt die Pending-id als srcId; ein erneut auftauchender Eintrag wird uebersprungen
  statt doppelt eingefuegt. boards-Write bleibt vor der Queue-Bereinigung -> kein Verlust.
- Validierung: der Drain hat e.url ohne isSafeUrl gepusht, anders als jeder andere
  Bookmark-Schreibpfad. isSafeUrl (jetzt im DOM-freien quicksave-core, http/https/ftp)
  filtert unsichere/leere Protokolle vor dem Schreiben ins Board.
2026-06-14 19:53:38 +02:00

70 lines
2.9 KiB
JavaScript

/* =============================================
HELLION NEWTAB — quicksave-core.js
DOM-freie geteilte Helfer fuer Seite UND Background-Worker.
Laeuft als <script> (newtab.html) und via importScripts (Service-Worker/Event-Page).
KEIN window/document/Store-Zugriff. Alle Exporte auf globalThis.
============================================= */
(function (root) {
'use strict';
// Feste, nicht-zufaellige ID des Inbox-Boards (Quick-Save-Ziel).
// Bewusst KEIN uid(): die Inbox muss von Seite und Worker deterministisch
// wiedererkennbar sein, sonst entstehen Duplikate (QS-08).
const INBOX_ID = 'inbox';
// Kollisionsarme Kurz-ID. Identisch zur frueheren state.js-Variante,
// damit bestehende Aufrufer (boards.js, data.js, app.js, bookmark-import.js) unveraendert weiterlaufen.
function uid() {
return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
}
// Sichert, dass im uebergebenen boards-Array genau EIN Inbox-Board existiert,
// und gibt dieses Inbox-Board-Objekt zurueck. Idempotent: findet ein Board mit
// id === INBOX_ID, sonst legt es eins mit fester id vorne an (unshift) und mutiert
// das uebergebene Array dabei IN-PLACE. Der Worker schreibt anschliessend dasselbe
// (mutierte) boards-Array via storage zurueck; der Rueckgabewert ist das Board,
// damit Aufrufer direkt inbox.bookmarks.push(...) machen koennen (QS-08).
function ensureInbox(boardsArr) {
const list = Array.isArray(boardsArr) ? boardsArr : [];
let inbox = list.find(b => b && b.id === INBOX_ID);
if (!inbox) {
inbox = { id: INBOX_ID, title: 'Inbox', bookmarks: [], blurred: false };
list.unshift(inbox);
}
return inbox;
}
// Sicheres URL-Protokoll (http/https/ftp). Inhaltlich identisch zur data.js-Variante, aber
// DOM-frei und auf globalThis, damit der Quick-Save-Drain (app.js) dieselbe Validierung nutzt
// wie jeder andere Bookmark-Schreibpfad. URL ist in Worker UND Seite verfuegbar.
function isSafeUrl(url) {
try {
return ['http:', 'https:', 'ftp:'].includes(new URL(url).protocol);
} catch (_) {
return false;
}
}
// Normalisiert eine Bookmark in die kanonische Form { id, title, url, desc }.
// title-Fallback auf url, desc auf ''. Begrenzt Laengen wie data.js (200/500),
// damit Quick-Save-Eintraege das gleiche Schema wie Import/Manuell haben.
function normalizeBookmark(raw) {
const url = (raw && typeof raw.url === 'string') ? raw.url : '';
const title = (raw && typeof raw.title === 'string' && raw.title.trim())
? raw.title.trim()
: url;
return {
id: (raw && raw.id) ? raw.id : uid(),
title: String(title).slice(0, 200),
url: url,
desc: String((raw && raw.desc) || '').slice(0, 500)
};
}
root.INBOX_ID = INBOX_ID;
root.uid = uid;
root.isSafeUrl = isSafeUrl;
root.ensureInbox = ensureInbox;
root.normalizeBookmark = normalizeBookmark;
})(typeof globalThis !== 'undefined' ? globalThis : self);