feat(agent): preview public replies in push notifications
TestFlight / Build and upload (push) Successful in 1m53s

This commit is contained in:
2026-10-14 23:36:38 -07:00
parent b9a6f5dd7d
commit 7510603b77
14 changed files with 335 additions and 32 deletions
+21 -7
View File
@@ -45,7 +45,7 @@ function fixture(sharedRecords = new Map()) {
getNotifications: async ({ tag } = {}) => notifications.filter(value => !value.closed && (!tag || value.tag === tag)),
},
};
vm.runInNewContext(workerSource, { self, indexedDB: idb(sharedRecords), URL, Date, Promise, BigInt,
vm.runInNewContext(workerSource, { self, indexedDB: idb(sharedRecords), URL, Date, Promise, BigInt, TextEncoder,
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);
@@ -58,9 +58,9 @@ function fixture(sharedRecords = new Map()) {
};
}
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.");
test("actual worker push handler displays supplied public message text and never fetches credentials", async () => {
const f = fixture(); await f.push(payload("5", { body: "Your report is ready. 日本語 🌧️" }));
assert.equal(f.shown.length, 1); assert.equal(f.shown[0].title, "Sybil Agent"); assert.equal(f.shown[0].body, "Your report is ready. 日本語 🌧️");
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"));
@@ -69,22 +69,36 @@ test("actual worker push handler shows fixed generic notification and never fetc
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"));
await f.push(payload("8", { body: "Latest reply" })); await f.push(payload("8", { body: "Latest reply" })); await f.push(payload("5", { body: "Older reply" }));
assert.equal(f.shown.length, 3, "Safari visibility requirement is preserved");
assert.ok(f.shown.every(notification => notification.data.cursor === "8"));
assert.ok(f.shown.every(notification => notification.body === "Latest reply"));
const restarted = fixture(records); await restarted.push(payload("6"));
assert.equal(restarted.shown[0].data.cursor, "8");
assert.equal(restarted.shown[0].body, "Latest reply");
assert.equal(records.get(sourceId).readCursor, "10");
});
test("malformed/unexpected payloads cannot supply text, destinations, cross-source tags or silent read pushes", async () => {
test("malformed/unexpected payloads cannot supply invalid bodies, 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" }),
for (const value of ["not-json", "x".repeat(5000), payload("1", { title: "sensitive text" }), payload("1", { body: { text: "invalid" } }),
payload("1", { body: " " }), payload("1", { body: "界".repeat(1001) }), payload("1", { body: "x".repeat(3001) }),
payload("1", { body: "界".repeat(900), extra: "界".repeat(500) }),
payload("1", { url: "https://evil.example/" }), payload("1", { tag: "some-other-app" }), payload("-1"), payload("9007199254740992"),
payload("1", { sourceId: "../../bad" }), payload("1", { messageId: "<script>" }), { version: 1, kind: "read_state", sourceId, readCursor: "8" }]) await f.push(value);
assert.equal(f.shown.length, 0); assert.equal(f.fetches.length, 0);
});
test("worker accepts bounded text, renders HTML literally and remains compatible with old generic pushes and stored identities", async () => {
const f = fixture();
for (const body of ["界".repeat(1000), "🌧".repeat(750), '<b>public literal text</b>', "You have a new message."]) {
await f.push(payload("9", { body })); assert.equal(f.shown.at(-1).body, body);
}
const records = new Map([[sourceId, { sourceId, latest: payload("10") }]]);
const upgraded = fixture(records); await upgraded.push(payload("8", { body: "Earlier reply" }));
assert.equal(upgraded.shown[0].data.cursor, "10"); assert.equal(upgraded.shown[0].body, "You have a new message.");
});
test("authenticated page read sync closes only owned matching source at or below monotonic read cursor", async () => {
const f = fixture();
const older = f.makeNotification({ tag: `sybil-agent:${sourceId}`, data: payload("4") });