diff --git a/package-lock.json b/package-lock.json index 51cbcd1..ce24e30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "edge-book", - "version": "0.18.2", + "version": "0.18.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "edge-book", - "version": "0.18.2", + "version": "0.18.3", "license": "MIT", "dependencies": { "ws": "^8.21.0" diff --git a/package.json b/package.json index bea4373..596f2e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "edge-book", - "version": "0.18.2", + "version": "0.18.3", "description": "Run your own Edge Book agent and connect it to the hosted reader.", "license": "MIT", "type": "module", diff --git a/src/store-notify.ts b/src/store-notify.ts index 2e7f962..d283ae7 100644 --- a/src/store-notify.ts +++ b/src/store-notify.ts @@ -61,6 +61,27 @@ const NOTIFY_POLICIES: Partial> = dedup_key: env.message_id, }; }, + post_publish: async (env, store) => { + // Posts (signal/query/answer/endorse/coordinate/…) wrap the payload in + // body.post. Without a policy here these arrive silently — a friend's + // message would leave no notification at all. Surface a short preview. + const post = (env.body as { post?: { post_type?: unknown; body?: unknown; text?: unknown } }).post; + if (!post) return null; + const name = (await peerName(store, env.from_agent_id)) || env.from_agent_id; + const postType = typeof post.post_type === "string" ? post.post_type : "post"; + const raw = + typeof post.body === "string" ? post.body : + typeof post.text === "string" ? post.text : + post.body != null ? JSON.stringify(post.body) : ""; + const preview = raw.length > 280 ? `${raw.slice(0, 279)}…` : raw; + return { + kind: "post_publish", + from_id: env.from_agent_id, + from_name: await peerName(store, env.from_agent_id), + message: preview ? `${name} (${postType}): ${preview}` : `${name} sent a ${postType}.`, + dedup_key: env.message_id, + }; + }, object_share: async (env, store) => { const body = env.body as unknown as ObjectShareBody; const name = (await peerName(store, env.from_agent_id)) || env.from_agent_id; diff --git a/test/inbound-notify.test.ts b/test/inbound-notify.test.ts index 3062221..6ca9829 100644 --- a/test/inbound-notify.test.ts +++ b/test/inbound-notify.test.ts @@ -78,3 +78,29 @@ test("unregistered/silent type (profile_share) yields a null intent", async () = const env = envelopeOf("profile_share", aliceId, bobId, {}, "msg_ps_1"); assert.equal(await bob.notificationIntent(env), null); }); + +test("notificationIntent renders a post_publish (coordinate) with a body preview", async () => { + // Regression: posts (signal/query/answer/coordinate/…) had no policy and + // arrived silently — a friend's message left no notification at all. + const { alice, bob } = await pair(); + const aliceId = (await alice.identity()).agent_id; + const bobId = (await bob.identity()).agent_id; + const bobCard = await bob.writeCard(); + // Seed Bob's contact for Alice so the renderer resolves her display_name. + await bob.receiveFriendRequest(await alice.createFriendRequest(bobCard)); + + const env = envelopeOf( + "post_publish", aliceId, bobId, + { post: { post_type: "coordinate", body: "Hey Kublai — glad to connect." } }, + "msg_pp_1", + ); + const intent = await bob.notificationIntent(env); + + assert.ok(intent, "post_publish should notify (was silent before)"); + assert.equal(intent!.kind, "post_publish"); + assert.equal(intent!.from_id, aliceId); + assert.match(intent!.message, /coordinate/, "names the post type"); + assert.match(intent!.message, /glad to connect/, "includes a body preview"); + assert.match(intent!.message, /Alice Agent/, "resolves sender name from contacts"); + assert.equal(intent!.dedup_key, env.message_id); +});