aboutsummaryrefslogtreecommitdiffstats
path: root/nwcpush/server.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-14 17:42:04 -0700
committerAdam Soltys <[email protected]>2026-09-14 17:42:04 -0700
commit5a0f70fcedd7a02a159de1819082981a6fd6244f (patch)
tree41afd35f5e109e55a0767007fc20bab291fd39c4 /nwcpush/server.js
parent7a1a628366d7ef8888b49217ae04cc9bcb97799e (diff)
downloadcoinosv3-5a0f70fcedd7a02a159de1819082981a6fd6244f.tar.gz
coinosv3-5a0f70fcedd7a02a159de1819082981a6fd6244f.zip
A reply or a mention on nostr can buzz your phone.
The push notifier now watches kind 1 for the user's pubkey beside DMs and zap receipts, and pushes who, what, and an excerpt. The worker shows "New reply to your post" or "You were mentioned" with the text, and a tap lands in the Notifications list — as ?open=notifs when a window has to be opened, or as a worker message when one already is. It has its own switch, "Replies and mentions on nostr", in Settings → Notifications, on by default like payments and DMs. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
Diffstat (limited to 'nwcpush/server.js')
-rw-r--r--nwcpush/server.js21
1 files changed, 18 insertions, 3 deletions
diff --git a/nwcpush/server.js b/nwcpush/server.js
index 85fc7b5..d4eb6a5 100644
--- a/nwcpush/server.js
+++ b/nwcpush/server.js
@@ -138,7 +138,8 @@ function resubscribeNotify() {
if (!ptags.length && !authors.length) return;
const filters = [];
// DMs / direct invites / zap receipts land p-tagged at the user
- if (ptags.length) filters.push({ kinds: [1059, 9735, 9737], '#p': ptags, since: Math.floor(Date.now() / 1000) });
+ // ...as do replies and mentions (kind 1 naming the user)
+ if (ptags.length) filters.push({ kinds: [1059, 9735, 9737, 1], '#p': ptags, since: Math.floor(Date.now() / 1000) });
// community chat: wraps authored by a channel's derived stream key
if (authors.length) filters.push({ kinds: [1059], authors, since: Math.floor(Date.now() / 1000) });
// one sub per filter — the pool API takes a single filter object
@@ -151,7 +152,7 @@ function resubscribeNotify() {
// DMs are throttled barely at all now: the device drops the ones it decides
// are noise (a stranger, or our own sent-copy), and a server-side cooldown
// would let a discarded push swallow the friend's message that followed it.
-const COOLDOWN = { payment: 10_000, dm: 1_000, chat: 90_000 };
+const COOLDOWN = { payment: 10_000, dm: 1_000, chat: 90_000, mention: 5_000 };
const lastPush = new Map(); // endpointId:reason -> ts
const seenNotifyEvents = new Map(); // event id -> ts
function onNotifyEvent(ev) {
@@ -162,6 +163,20 @@ function onNotifyEvent(ev) {
for (const [k, t] of seenNotifyEvents) if (t < cut) seenNotifyEvents.delete(k);
}
const p = ev.tags?.find((t) => t[0] === 'p')?.[1];
+ // A public reply or mention: the note is readable here, so the device
+ // gets what it needs to say who and what — an excerpt, and whether it
+ // answers a post (e tag) or just names the person.
+ if (ev.kind === 1) {
+ const ps = (ev.tags || []).filter((t) => t[0] === 'p' && t[1]).map((t) => t[1]);
+ const reply = (ev.tags || []).some((t) => t[0] === 'e' && t[1]);
+ const extra = { from: ev.pubkey, reply, text: String(ev.content || '').replace(/\s+/g, ' ').slice(0, 140) };
+ for (const [id, r] of Object.entries(notifyRegs)) {
+ const mine = r.ptags || [];
+ if (mine.includes(ev.pubkey)) continue; // their own post, not news
+ if (ps.some((x) => mine.includes(x))) pushNotify(id, r, 'mention', extra).catch(() => {});
+ }
+ return;
+ }
const reason = ev.kind === 9735 || ev.kind === 9737 ? 'payment'
: p && notifyPtags().includes(p) ? 'dm' : 'chat';
// A wrapped DM hides its sender from us by design, so we can't decide here
@@ -423,7 +438,7 @@ const server = Bun.serve({
const id = Bun.hash(sub_.endpoint).toString(36);
// per-category opt-outs; absent means everything on (older clients)
const reasons = {};
- for (const k of ['payment', 'dm', 'chat']) {
+ for (const k of ['payment', 'dm', 'chat', 'mention']) {
if (typeof body.notify.reasons?.[k] === 'boolean') reasons[k] = body.notify.reasons[k];
}
notifyRegs[id] = {