aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-11-12 23:19:23 -0700
committerAdam Soltys <[email protected]>2026-11-12 23:19:23 -0700
commit5026227de10e424f590d2a042e5a8770266a6b5e (patch)
tree2692082e5ee8a4d7ca21f45fa3d837cb4c3eac93 /src
parent5465eccfce94a53ad48cfdafbc90e7462d387840 (diff)
downloadcoinosv3-5026227de10e424f590d2a042e5a8770266a6b5e.tar.gz
coinosv3-5026227de10e424f590d2a042e5a8770266a6b5e.zip
A relay group joined on one device shows up on the others. "Not seeing the pyramid runners chat I joined and messaged in on my laptop appear on my phone" (Adam, 2026-11-12). The joined groups live in the kind 10009 list, but it was read once, when Messages started (a phone open in the background since before the join never looked again), and written only to the four home relays and the user's own, of which relay.coinos.io refused kind 10009 (allowed since, desk write-policy) and nos.lol and damus kept nothing: the laptop's list sat on primal and ditto alone, beside a stale copy on purplepag.es. Now the list is followed live (a limit-1 subscription on the home relays; a newer copy is adopted and its groups join the list here), read again whenever Messages resubscribes after an absence (at most every 30 s), always read deep (home, index and own relays) before a write, and published to all of those, so the newest copy is where any device looks. Groups dropped from the list elsewhere are still kept here. nip29-group-test: the same twelve words open a second browser, which follows the list live and shows the group once the laptop's signed list arrives on that subscription
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMan6UKGJyuGyZFxddYRxS
Diffstat (limited to 'src')
-rw-r--r--src/nip29-client.js41
1 files changed, 36 insertions, 5 deletions
diff --git a/src/nip29-client.js b/src/nip29-client.js
index 040733c..4c27b64 100644
--- a/src/nip29-client.js
+++ b/src/nip29-client.js
@@ -197,18 +197,25 @@ export function nip29Client({ scope, pubkey, identity, on, listRelays, store = m
// Apply one change to the newest list the relays hold (never to a copy
// we only assume is current), then publish it.
async function writeList(change) {
- const cur = await readList(!list).catch(() => list);
+ const cur = await readList(true).catch(() => list);
const base = cur || { tags: [], content: '', at: 0 };
const tags = change(base.tags);
if (!tags) return;
const created_at = Math.max(now(), base.at + 1);
const ev = await sign({ kind: LIST, created_at, tags, content: base.content || '' });
- const ok = await publishOn(await listRelays(false), ev);
+ // everywhere a reader looks: a phone that asks only the relays a copy
+ // missed would never learn of a group joined on the laptop
+ const ok = await publishOn(await listRelays(true), ev);
if (ok) { list = { tags, content: base.content || '', at: created_at }; store.put('list', list).catch(() => {}); }
}
+ // The list as another device changed it: read at start, again whenever the
+ // app comes back (resubscribe), and followed live while it's open, so a
+ // group joined on the laptop shows up on the phone (Adam, 2026-11-12).
+ let adoptedAt = 0, listSub = null;
async function adoptList() {
+ adoptedAt = Date.now();
try {
- await readList(!list);
+ await readList(true);
const l = list; // the newest, a leave's own write included
if (!l || stopped) return;
for (const x of listGroups(l.tags)) {
@@ -299,15 +306,39 @@ export function nip29Client({ scope, pubkey, identity, on, listRelays, store = m
}).catch((e) => dlog('nip29: list write', e));
}
+ function adoptLive(ev) {
+ if (stopped || ev.pubkey !== pubkey || ev.kind !== LIST) return;
+ if (list && ev.created_at <= list.at) return;
+ list = { tags: ev.tags || [], content: ev.content || '', at: ev.created_at };
+ store.put('list', list).catch(() => {});
+ adoptList();
+ }
+ async function followList() {
+ if (listSub || stopped) return;
+ const relays = await listRelays(false);
+ if (stopped || listSub) return;
+ listSub = subscribeAuthed(relays, { kinds: [LIST], authors: [pubkey], limit: 1 }, adoptLive);
+ }
async function start() {
await load();
if (stopped) return;
for (const g of groups.values()) subscribe(g);
emit('loaded', null);
adoptList();
+ followList().catch(() => {});
+ }
+ function resubscribe() {
+ for (const g of groups.values()) { unsubscribe(g); subscribe(g); }
+ if (listSub) { try { listSub(); } catch {} listSub = null; }
+ followList().catch(() => {});
+ if (Date.now() - adoptedAt > 30_000) adoptList();
+ }
+ function stop() {
+ stopped = true;
+ for (const g of groups.values()) unsubscribe(g);
+ if (listSub) { try { listSub(); } catch {} listSub = null; }
+ flush();
}
- function resubscribe() { for (const g of groups.values()) { unsubscribe(g); subscribe(g); } }
- function stop() { stopped = true; for (const g of groups.values()) unsubscribe(g); flush(); }
const no = async () => { throw new Error('not supported'); };
return {