aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/nwc.ts71
1 files changed, 18 insertions, 53 deletions
diff --git a/lib/nwc.ts b/lib/nwc.ts
index 245b654c..40a6bd80 100644
--- a/lib/nwc.ts
+++ b/lib/nwc.ts
@@ -62,10 +62,6 @@ export default () => {
let heartbeatInterval: any;
let infoCheckInterval: any;
- // Tracks which server pubkeys already have their kind 13194 info event on the
- // relay, populated by the existence query in ensureInfo() below.
- const infoSeen = new Set<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
@@ -86,11 +82,10 @@ export default () => {
},
hexToBytes(sk),
);
+ // No success log: this now runs every 2 minutes per pubkey, so a "published"
+ // line each time is pure noise. Only the failure paths below log.
for (let attempt = 1; attempt <= 3; attempt++) {
- if (await sendEventConfirmed(info)) {
- l("nwc published 13194 info event", pk);
- return true;
- }
+ if (await sendEventConfirmed(info)) return true;
warn(`nwc 13194 publish not confirmed (attempt ${attempt})`, pk);
await sleep(1000);
}
@@ -129,22 +124,20 @@ export default () => {
}
// The kind 13194 info event (NWC capability advertisement) is REPLACEABLE, so
- // it's meant to be published once and persist on the relay. NWC clients (Alby
- // Go, Lightning Piggies, etc.) fetch it before they'll talk to the wallet —
- // without it they report "no info event" and show an unknown balance.
+ // it's meant to persist on the relay. NWC clients (Alby Go, Lightning Piggies,
+ // etc.) fetch it before they'll talk to the wallet — without it they report
+ // "no info event" and show an unknown balance.
//
- // Rather than blindly republishing, query the relay on connect and publish
- // only the pubkeys whose info event is missing. This is normally a no-op, but
- // self-heals the case where the event went missing (e.g. the strfry restart on
- // 2026-05-29 that left clients broken for days). The "infocheck" subscription
- // is handled by the event/eose branches below.
+ // We used to query the relay and publish only the missing pubkeys, but the
+ // stored event kept vanishing between checks (cause never pinned down), which
+ // left clients broken until the next hourly check. Republishing both server
+ // info events unconditionally on a short interval is cheap (a replaceable
+ // event, so it just overwrites in place) and self-heals immediately.
+ // publishInfo() uses its own short-lived socket, so this works regardless of
+ // the long-lived nwc socket's state.
function ensureInfo() {
- if (!r?.ws || r.ws.readyState !== 1) return;
- infoSeen.clear();
- r.subscribe("infocheck", {
- kinds: [13194],
- authors: [serverPubkey, serverPubkey2],
- });
+ publishInfo(serverPubkey, serverSecret);
+ publishInfo(serverPubkey2, serverSecret2);
}
function connect() {
@@ -172,25 +165,12 @@ export default () => {
}, 30000);
r.subscribe("nwc", { kinds: [23194], "#p": [serverPubkey, serverPubkey2], since: Math.floor(Date.now() / 1000) - nwcEventMaxAgeSeconds });
- // Verify the 13194 info events exist on the relay, publish any missing.
+ // Publish the 13194 info events immediately, then keep republishing every
+ // 2 minutes so a vanished event is never missing for long.
ensureInfo();
-
- // Re-check hourly so a relay restart that leaves our socket intact (or any
- // other cause of the info event going missing) self-heals without waiting
- // for the next app reconnect.
- infoCheckInterval = setInterval(ensureInfo, 60 * 60 * 1000);
+ infoCheckInterval = setInterval(ensureInfo, 2 * 60 * 1000);
});
- r.on("eose", (sub) => {
- if (sub !== "infocheck") return;
- for (const [pk, sk] of [
- [serverPubkey, serverSecret],
- [serverPubkey2, serverSecret2],
- ]) {
- if (!infoSeen.has(pk)) publishInfo(pk, sk);
- }
- try { r.unsubscribe("infocheck"); } catch (_) {}
- });
r.on("close", () => {
warn("nwc strfry connection lost, reconnecting in 5s");
@@ -203,21 +183,6 @@ export default () => {
r.on("event", async (sub, ev) => {
try {
- if (sub === "infocheck") {
- // Only count the stored info event as present if it's CURRENT —
- // otherwise a stale replaceable event (old method list, missing
- // encryption tag) would never get refreshed, since we only publish
- // the ones not "seen".
- if (ev.kind === 13194) {
- const current =
- ev.content === methods.join(" ") &&
- ev.tags?.some(
- (t) => t[0] === "encryption" && t[1] === encryptionSchemes,
- );
- if (current) infoSeen.add(ev.pubkey);
- }
- return;
- }
if (sub !== "nwc") return;
const now = Math.floor(Date.now() / 1000);
if (ev.created_at && now - ev.created_at > nwcEventMaxAgeSeconds) return;