aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/dmNotifications.ts143
1 files changed, 125 insertions, 18 deletions
diff --git a/lib/dmNotifications.ts b/lib/dmNotifications.ts
index 41591450..ca6b032a 100644
--- a/lib/dmNotifications.ts
+++ b/lib/dmNotifications.ts
@@ -9,56 +9,163 @@ if (config.vapid) {
webpush.setVapidDetails(`mailto:${config.support}`, config.vapid.pk, config.vapid.sk);
}
-const handleDmEvent = async (event: any) => {
- const pTag = event.tags.find((t: any) => t[0] === "p");
- l(`dm event ${event.id.slice(0, 8)} p=${pTag?.[1]?.slice(0, 12) || "none"}`);
- if (!pTag) return;
-
- const recipientPubkey = pTag[1];
+// ---------------------------------------------------------------------------
+// Push notification delivery
+// ---------------------------------------------------------------------------
+const sendPush = async (pubkey: string, url = "/messages") => {
try {
- const user = await getUser(recipientPubkey);
+ const user = await getUser(pubkey);
if (!user?.id) return;
const subscriptions = await db.sMembers(`${user.id}:subscriptions`);
if (!subscriptions?.length) return;
- l(`dm push to ${user.username} (${subscriptions.length} subs)`);
+ l(`push to ${user.username} (${subscriptions.length} subs)`);
const payload = JSON.stringify({
title: "New message",
body: "You have a new encrypted message",
- url: "/messages",
+ url,
});
for (const s of subscriptions as any) {
webpush
.sendNotification(JSON.parse(s as string), payload)
.catch((e) => {
- warn("dm push failed", e.message);
+ warn("push failed", e.message);
db.sRem(`${user.id}:subscriptions`, s);
});
}
- } catch (e) {
- // User not found for this pubkey - not a coinos user
+ } catch {
+ // User not found — not a coinos user
+ }
+};
+
+// ---------------------------------------------------------------------------
+// Kind 1059 (gift wrap / invite) — notifies recipient via p tag
+// ---------------------------------------------------------------------------
+
+const handleInviteEvent = async (event: any) => {
+ const pTag = event.tags.find((t: any) => t[0] === "p");
+ if (!pTag) return;
+ await sendPush(pTag[1]);
+};
+
+// ---------------------------------------------------------------------------
+// Kind 445 (MLS group message) — notifies all group members except sender
+// ---------------------------------------------------------------------------
+
+// In-memory group registry: nostrGroupId → { members, relays }
+interface GroupEntry {
+ members: string[];
+ relays: string[];
+}
+
+const groupRegistry = new Map<string, GroupEntry>();
+
+// Track active relay subscriptions for kind 445
+const activeGroupRelays = new Set<string>();
+const groupRelaySubs: (() => void)[] = [];
+
+const handleGroupMessage = async (event: any) => {
+ const hTag = event.tags.find((t: any) => t[0] === "h");
+ if (!hTag) return;
+
+ const nostrGroupId = hTag[1];
+ const group = groupRegistry.get(nostrGroupId);
+ if (!group) return;
+
+ const sender = event.pubkey;
+ for (const member of group.members) {
+ if (member !== sender) sendPush(member);
+ }
+};
+
+const subscribeGroupRelay = (url: string) => {
+ if (activeGroupRelays.has(url)) return;
+ activeGroupRelays.add(url);
+
+ Relay.connect(url)
+ .then((relay) => {
+ const sub = relay.subscribe(
+ [{ kinds: [445], since: Math.floor(Date.now() / 1000) }],
+ { onevent: handleGroupMessage },
+ );
+ groupRelaySubs.push(() => sub.close());
+ l(`listening for group messages on ${url}`);
+ })
+ .catch((e) => {
+ warn(`group listener failed ${url}`, e.message);
+ activeGroupRelays.delete(url);
+ setTimeout(() => subscribeGroupRelay(url), 5000);
+ });
+};
+
+/** Called when a client syncs its groups. Updates registry and subscribes to new relays. */
+export const syncGroups = async (groups: { nostrGroupId: string; members: string[]; relays: string[] }[]) => {
+ for (const g of groups) {
+ groupRegistry.set(g.nostrGroupId, { members: g.members, relays: g.relays });
+ for (const url of g.relays) subscribeGroupRelay(url);
}
+
+ l(`group registry: ${groupRegistry.size} groups, ${activeGroupRelays.size} relays`);
};
-const subscribeRelay = (url: string) => {
+// ---------------------------------------------------------------------------
+// Kind 1059 relay subscriptions (invites / NIP-17 DMs)
+// ---------------------------------------------------------------------------
+
+const subscribeInviteRelay = (url: string) => {
Relay.connect(url)
.then((relay) => {
relay.subscribe([{ kinds: [1059], since: Math.floor(Date.now() / 1000) }], {
- onevent: handleDmEvent,
+ onevent: handleInviteEvent,
});
- l(`listening for DM notifications on ${url}`);
+ l(`listening for invites on ${url}`);
})
.catch((e) => {
- warn(`DM listener failed ${url}`, e.message);
- setTimeout(() => subscribeRelay(url), 5000);
+ warn(`invite listener failed ${url}`, e.message);
+ setTimeout(() => subscribeInviteRelay(url), 5000);
});
};
+// ---------------------------------------------------------------------------
+// Boot: restore group registry from Redis, start listeners
+// ---------------------------------------------------------------------------
+
+const REGISTRY_KEY = "mls:groupRegistry";
+
+const saveRegistry = async () => {
+ const obj: Record<string, GroupEntry> = {};
+ for (const [id, entry] of groupRegistry) obj[id] = entry;
+ await db.set(REGISTRY_KEY, JSON.stringify(obj));
+};
+
+const loadRegistry = async () => {
+ try {
+ const raw = await db.get(REGISTRY_KEY);
+ if (!raw) return;
+ const obj = JSON.parse(raw as string);
+ for (const [id, entry] of Object.entries(obj) as [string, GroupEntry][]) {
+ groupRegistry.set(id, entry);
+ for (const url of entry.relays) subscribeGroupRelay(url);
+ }
+ l(`restored ${groupRegistry.size} groups from registry`);
+ } catch {}
+};
+
+/** Called from syncGroups to persist after updates */
+export const syncGroupsAndSave = async (groups: { nostrGroupId: string; members: string[]; relays: string[] }[]) => {
+ await syncGroups(groups);
+ await saveRegistry();
+};
+
export const listenForDMs = () => {
+ // Subscribe to kind 1059 on configured relays
const relays = new Set([config.nostr, ...config.relays.filter((r: string) => r.startsWith("wss://"))]);
- for (const url of relays) subscribeRelay(url);
+ for (const url of relays) subscribeInviteRelay(url);
+
+ // Restore persisted group registry and subscribe to kind 445
+ loadRegistry();
};