diff options
| author | Adam Soltys <[email protected]> | 2026-06-28 15:39:55 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-28 15:39:55 -0700 |
| commit | 9c4c7060560f21246a4e4ce6feb83fd07e4aa481 (patch) | |
| tree | e69a8ce4d10bc3211c0b5aad6685938a32af9d64 /lib | |
| parent | 85de6adfd6fc0f3722adf5905b82f23ed6abb11d (diff) | |
| download | coinos-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')
| -rw-r--r-- | lib/nwc.ts | 50 |
1 files changed, 47 insertions, 3 deletions
@@ -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 |
