import assert from "node:assert/strict"; import test from "node:test"; import vm from "node:vm"; import { readFile } from "node:fs/promises"; const workerSource = await readFile(new URL("../public/sw.js", import.meta.url), "utf8"); const sourceId = "4599d348-e053-4937-987d-1392746fb2ce", otherSource = "550caef2-b4bc-482c-bd9f-58045ed0a8a8"; const payload = (cursor = "5", extra = {}) => ({ version: 1, kind: "message", sourceId, messageId: `outbound:${cursor}`, cursor, title: "Sybil Agent", body: "You have a new message.", url: "/?agent=root", tag: `sybil-agent:${sourceId}`, ...extra }); function idb(records) { const request = value => { const result = {}; queueMicrotask(() => { result.result = structuredClone(value); result.onsuccess?.(); }); return result; }; return { open() { const opening = {}; queueMicrotask(() => { opening.result = { close() {}, transaction() { const transaction = { objectStore: () => ({ get: key => request(records.get(key)), getAll: () => request([...records.values()]), put: value => records.set(value.sourceId, structuredClone(value)), delete: key => records.delete(key), }) }; setImmediate(() => transaction.oncomplete?.()); return transaction; } }; opening.onsuccess?.(); }); return opening; } }; } function fixture(sharedRecords = new Map()) { const handlers = new Map(), shown = [], notifications = [], routes = [], messages = [], fetches = []; const normalClient = { id: "client", type: "window", url: "https://sybil.example.test/?chat=somewhere", postMessage: value => messages.push(value), navigate: async url => { routes.push(["navigate", url]); return normalClient; }, focus: async () => { routes.push(["focus"]); return normalClient; } }; let windows = [normalClient]; const makeNotification = value => { const notification = { tag: value.tag, data: value.data, closed: false, close() { notification.closed = true; } }; notifications.push(notification); return notification; }; const self = { addEventListener: (name, handler) => handlers.set(name, handler), skipWaiting() {}, location: { origin: "https://sybil.example.test" }, clients: { claim: async () => undefined, matchAll: async () => windows, get: async id => windows.find(client => client.id === id), openWindow: async url => { routes.push(["open", url]); } }, registration: { showNotification: async (title, options) => { shown.push({ title, ...structuredClone(options) }); for (const notification of notifications) if (notification.tag === options.tag) notification.close(); makeNotification(options); }, getNotifications: async ({ tag } = {}) => notifications.filter(value => !value.closed && (!tag || value.tag === tag)), }, }; vm.runInNewContext(workerSource, { self, indexedDB: idb(sharedRecords), URL, Date, Promise, BigInt, fetch: async request => { fetches.push(request); throw new Error("Network blocked in worker tests"); }, Request }); const emit = async (type, properties = {}) => { const waits = []; handlers.get(type)?.({ ...properties, waitUntil: value => waits.push(value) }); await Promise.all(waits); }; return { shown, notifications, routes, messages, fetches, records: sharedRecords, makeNotification, push: value => emit("push", { data: { text: () => typeof value === "string" ? value : JSON.stringify(value) } }), click: notification => emit("notificationclick", { notification }), message: (data, id = "client") => emit("message", { data, source: { id }, ports: [] }), emit, windows: value => { windows = value; }, normalClient, }; } test("actual worker push handler shows fixed generic notification and never fetches credentials", async () => { const f = fixture(); await f.push(payload()); assert.equal(f.shown.length, 1); assert.equal(f.shown[0].title, "Sybil Agent"); assert.equal(f.shown[0].body, "You have a new message."); assert.deepEqual(f.shown[0].data, { version: 1, kind: "message", sourceId, messageId: "outbound:5", cursor: "5" }); assert.equal(f.shown[0].tag, `sybil-agent:${sourceId}`); assert.equal(f.shown[0].renotify, false); assert.deepEqual(f.fetches, []); assert.ok(!JSON.stringify([...f.records]).includes("token")); }); test("every valid message push remains visible, including read or duplicate deliveries; stale payload never regresses identity", async () => { const records = new Map(), f = fixture(records); await f.message({ type: "sybil-agent-read", version: 1, sourceId, cursor: "10" }); await f.push(payload("8")); await f.push(payload("8")); await f.push(payload("5")); assert.equal(f.shown.length, 3, "Safari visibility requirement is preserved"); assert.ok(f.shown.every(notification => notification.data.cursor === "8")); const restarted = fixture(records); await restarted.push(payload("6")); assert.equal(restarted.shown[0].data.cursor, "8"); assert.equal(records.get(sourceId).readCursor, "10"); }); test("malformed/unexpected payloads cannot supply text, destinations, cross-source tags or silent read pushes", async () => { const f = fixture(); for (const value of ["not-json", "x".repeat(5000), payload("1", { title: "sensitive text" }), payload("1", { body: "transcript" }), payload("1", { url: "https://evil.example/" }), payload("1", { tag: "some-other-app" }), payload("-1"), payload("9007199254740992"), payload("1", { sourceId: "../../bad" }), payload("1", { messageId: "