@unicitylabs/nostr-js-sdk 0.5.0-dev.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9542,6 +9542,25 @@ const DEFAULT_PING_INTERVAL_MS = 30000;
9542
9542
  * "do not pick this name."
9543
9543
  */
9544
9544
  const PING_SUB_ID = '__nostr-sdk-keepalive__';
9545
+ /**
9546
+ * Filter id used by the keepalive REQ. We need a filter the relay can
9547
+ * resolve immediately (so EOSE comes back fast = relay is alive), but
9548
+ * which can NOT match any real event past EOSE (so the live tail stays
9549
+ * empty).
9550
+ *
9551
+ * Scoping by `authors:[selfPubkey]` was the original approach but it
9552
+ * matched every event the wallet itself published — including kind-31113
9553
+ * token transfers — which the relay then echoed back on the keepalive
9554
+ * sub. Some relays dedupe events across overlapping subs, so the
9555
+ * wallet's own consumer subscription would not receive its own echo and
9556
+ * any flow waiting on that echo would time out.
9557
+ *
9558
+ * The filter `{ ids: ['00...00'] }` asks the relay for a single event
9559
+ * whose id is exactly the all-zero hash. Real Nostr event ids are
9560
+ * SHA-256 over a canonical JSON serialization, so the all-zero hash is
9561
+ * unreachable in practice. Result: instant EOSE, empty live tail.
9562
+ */
9563
+ const KEEPALIVE_NEVER_MATCH_ID = '0'.repeat(64);
9545
9564
  /**
9546
9565
  * Delay before resubscribing after NIP-42 authentication.
9547
9566
  * This gives the relay time to process the AUTH response before we send
@@ -9919,14 +9938,21 @@ class NostrClient {
9919
9938
  return;
9920
9939
  }
9921
9940
  // Send a subscription request as a ping (relays respond with EOSE).
9922
- // The filter MUST be tightly scoped an open `{ limit: 1 }` filter
9923
- // with no kinds/authors/#p will, after EOSE, stream every event the
9924
- // relay receives (NIP-01 live tail), saturating the connection and
9925
- // exhausting per-connection subscription slots on busy relays.
9926
- // Scoping by `authors:[self]` keeps the live tail empty in practice
9927
- // (the relay would only forward our own future events).
9941
+ // The filter MUST be tightly scoped to something no real event can
9942
+ // match both for the initial query AND the post-EOSE live tail.
9943
+ //
9944
+ // Earlier iterations used `authors:[self]` reasoning that "the
9945
+ // relay would only forward our own future events". That reasoning
9946
+ // was wrong: it precisely DOES forward every event the wallet
9947
+ // publishes, including kind-31113 token transfers. Some relays
9948
+ // dedupe events across overlapping subs, so the wallet's own
9949
+ // consumer subscription would miss its echo and any flow waiting
9950
+ // on it would time out.
9951
+ //
9952
+ // {@link KEEPALIVE_NEVER_MATCH_ID} (the all-zero SHA-256 hash) is
9953
+ // unreachable in real event-id space, so the relay returns EOSE
9954
+ // immediately and the live tail never matches.
9928
9955
  try {
9929
- const selfPubkey = this.keyManager.getPublicKeyHex();
9930
9956
  // First close any existing ping subscription to ensure we don't accumulate
9931
9957
  const closeMessage = JSON.stringify(['CLOSE', PING_SUB_ID]);
9932
9958
  relay.socket.send(closeMessage);
@@ -9934,7 +9960,7 @@ class NostrClient {
9934
9960
  const pingMessage = JSON.stringify([
9935
9961
  'REQ',
9936
9962
  PING_SUB_ID,
9937
- { authors: [selfPubkey], limit: 1 },
9963
+ { ids: [KEEPALIVE_NEVER_MATCH_ID], limit: 1 },
9938
9964
  ]);
9939
9965
  relay.socket.send(pingMessage);
9940
9966
  relay.unansweredPings++;