aboutsummaryrefslogtreecommitdiffstats
path: root/lib/nwc.ts
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-28 15:39:55 -0700
committerAdam Soltys <[email protected]>2026-06-28 15:39:55 -0700
commit9c4c7060560f21246a4e4ce6feb83fd07e4aa481 (patch)
treee69a8ce4d10bc3211c0b5aad6685938a32af9d64 /lib/nwc.ts
parent85de6adfd6fc0f3722adf5905b82f23ed6abb11d (diff)
downloadcoinos-server-9c4c7060560f21246a4e4ce6feb83fd07e4aa481.tar.gz
coinos-server-9c4c7060560f21246a4e4ce6feb83fd07e4aa481.zip
nwc: confirm 13194 info-event publish so it cannot be silently dropped
publishInfo sent the kind-13194 info event over the long-lived NWC socket fire-and-forget. The nostr lib Relay.send is async (await wait_connected; ws.send) but was called without await, and success was logged immediately. A send that rejected (e.g. ws in CLOSING state during a strfry reconnect) was silently dropped while the log claimed success, leaving NWC clients with "no info event" until a manual republish. Publish over a fresh short-lived socket and wait for the relay OK, retrying up to 3x with honest logging (warn on unconfirmed, err on give-up). ensureInfos receive-based detection is unchanged. Verified on prod: deleted both info events, restarted, both republished within seconds. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'lib/nwc.ts')
-rw-r--r--lib/nwc.ts50
1 files changed, 47 insertions, 3 deletions
diff --git a/lib/nwc.ts b/lib/nwc.ts
index af4dc7b7..9d54f62f 100644
--- a/lib/nwc.ts
+++ b/lib/nwc.ts
@@ -60,7 +60,13 @@ export default () => {
// relay, populated by the existence query in ensureInfo() below.
const infoSeen = new Set<string>();
- function publishInfo(pk: string, sk: string) {
+ // Publish the kind 13194 info event and CONFIRM the relay stored it. The
+ // long-lived NWC socket's send is fire-and-forget (await wait_connected; ws.send)
+ // and was called without await — so a send that rejected (e.g. ws in CLOSING
+ // state during a reconnect) was silently dropped while we logged success,
+ // leaving clients with "no info event" until a manual republish. Publish over a
+ // fresh short-lived socket and wait for the relay's OK, retrying a few times.
+ async function publishInfo(pk: string, sk: string) {
const info = finalizeEvent(
{
created_at: Math.floor(Date.now() / 1000),
@@ -73,8 +79,46 @@ export default () => {
},
hexToBytes(sk),
);
- r.send(["EVENT", info]);
- l("nwc published 13194 info event", pk);
+ for (let attempt = 1; attempt <= 3; attempt++) {
+ if (await sendEventConfirmed(info)) {
+ l("nwc published 13194 info event", pk);
+ return true;
+ }
+ warn(`nwc 13194 publish not confirmed (attempt ${attempt})`, pk);
+ await sleep(1000);
+ }
+ err("nwc failed to publish 13194 info event after retries", pk);
+ return false;
+ }
+
+ // Send an event to strfry over a fresh socket and resolve true once the relay
+ // replies OK=accepted. Independent of the long-lived NWC socket, whose
+ // fire-and-forget send proved unreliable for the persisted info event.
+ function sendEventConfirmed(ev: any, timeoutMs = 5000): Promise<boolean> {
+ return new Promise((resolve) => {
+ let ws: WebSocket;
+ let done = false;
+ const finish = (ok: boolean) => {
+ if (done) return;
+ done = true;
+ try { ws.close(); } catch (_) {}
+ resolve(ok);
+ };
+ try {
+ ws = new WebSocket("ws://sf:7777");
+ } catch (_) {
+ return resolve(false);
+ }
+ ws.onopen = () => ws.send(JSON.stringify(["EVENT", ev]));
+ ws.onmessage = (e: any) => {
+ try {
+ const m = JSON.parse(e.data);
+ if (m[0] === "OK" && m[1] === ev.id) finish(!!m[2]);
+ } catch (_) {}
+ };
+ ws.onerror = () => finish(false);
+ setTimeout(() => finish(false), timeoutMs);
+ });
}
// The kind 13194 info event (NWC capability advertisement) is REPLACEABLE, so