feat(agent): preview public replies in push notifications
TestFlight / Build and upload (push) Successful in 1m53s
TestFlight / Build and upload (push) Successful in 1m53s
This commit is contained in:
@@ -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") });
|
||||
|
||||
Reference in New Issue
Block a user