aboutsummaryrefslogtreecommitdiffstats
path: root/src/dm-inbox.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-18 09:01:40 -0700
committerAdam Soltys <[email protected]>2026-10-18 09:01:41 -0700
commitbfa52c7a2cf1cc06455536d9a6676b0003a30452 (patch)
tree436e2411ea1238a124543dadeb13c77b85d1f270 /src/dm-inbox.js
parent39083f3ca2f80c7c51fefffb1e473f3d0a7d4ed6 (diff)
downloadcoinosv3-bfa52c7a2cf1cc06455536d9a6676b0003a30452.tar.gz
coinosv3-bfa52c7a2cf1cc06455536d9a6676b0003a30452.zip
Calls ring a closed app: the notifier wakes the phone for a call offer, the service worker fetches the offer's wrap, opens it with your key and rings — "Alice is calling", until answered or missed — for someone you follow or talk to; the tap opens coinos ringing with the offer it kept. A call answered from a push still connects: the caller sends its candidates again once answered. Settings → Notifications → Calls. Also: the worker's DM key mirror retries when the wallet's keys aren't there yet (it waited five minutes).
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'src/dm-inbox.js')
-rw-r--r--src/dm-inbox.js24
1 files changed, 24 insertions, 0 deletions
diff --git a/src/dm-inbox.js b/src/dm-inbox.js
index b9eec3a..1a026c1 100644
--- a/src/dm-inbox.js
+++ b/src/dm-inbox.js
@@ -15,6 +15,8 @@
// notification instead of a filtered one.
import { unwrapDM } from './dm.js';
+import { openCall, CALL, RING_S } from './calls.js';
+import * as nip44 from 'nostr-tools/nip44';
const DB = 'coinos-dm-inbox';
const STORE = 'inbox';
@@ -115,3 +117,25 @@ export function shouldNotifyDm(verdict) {
if (verdict.followed || verdict.known) return true;
return !verdict.hasList;
}
+
+// A call ringing in (NIP-AC), the wrap a push pointed at: opened with the key
+// it's for, and worth ringing only as a fresh offer from someone this
+// account follows or talks to, and hasn't muted. Returns { offer, name,
+// type } to ring with, or null for anything else (a candidate, a stranger,
+// a stale one, a wrap we hold no key for).
+export async function classifyCall(wrap, inboxes, nowS = Math.floor(Date.now() / 1000)) {
+ for (const rec of inboxes) {
+ if (!rec || !rec.sk) continue;
+ let sk;
+ try { sk = unhex(rec.sk); } catch { continue; }
+ const ev = await openCall(wrap, async (pk, ct) => nip44.decrypt(ct, nip44.getConversationKey(sk, pk)));
+ if (!ev) continue;
+ const from = ev.pubkey;
+ if (ev.kind !== CALL.OFFER || from === rec.pubkey || nowS - ev.created_at > RING_S) return null;
+ if ((rec.muted || []).includes(from)) return null;
+ if (!(rec.follows || []).includes(from) && !(rec.known || []).includes(from)) return null;
+ const type = (ev.tags.find((x) => x[0] === 'call-type') || [])[1] === 'video' ? 'video' : 'voice';
+ return { offer: ev, name: (rec.names || {})[from] || null, type };
+ }
+ return null;
+}