A single failed reconnect attempt (`ws.onerror` or connect timeout)
set `skipReconnection = true`, which made `handleHardClose` close
every subscription on the relay and remove it from the pool. The
intent of fb7de7f was to give up on the *initial* connect failure
(a relay that's dead from the start) — but the same code path also
fires for retries, so any outage longer than the first 10s backoff
slot turned a transient drop into permanent silence on every
long-running client.
Symptom in the wild: a long-running daemon goes silently deaf on a
subscription after the relay operator restarts the relay, with no
error from the SDK because the pool still considers the relay
connected after a later `ensureRelay`. Only `skipReconnection` on
the first attempt (`reconnectAttempts === 0`), so retries fall
through to `handleHardClose` and schedule another attempt as the
`resubscribeBackoff` array clearly intended.
NIP-47 connection strings can specify multiple relay params, but
parseConnectionString only returned the first one.
Adds a 'relays' field (string[]) to NWCConnection while keeping the
existing 'relay' field (deprecated) for backward compatibility.
Fixes#494
Fixes#531. When automaticallyAuth is configured and a relay closes the
connection between sending the AUTH challenge and the client sending
the signed response, the SendingOnClosedConnection error was propagating
as an unhandled promise rejection.
This is a race condition that commonly occurs with relays that enforce
WoT or whitelists - they accept the WebSocket, send an AUTH challenge,
but close the connection before the client can respond.
The fix catches the SendingOnClosedConnection error specifically in the
AUTH case handler, preventing unhandled rejections while still propagating
other errors.
* nip44: support encryption of payloads larger than 65535 bytes
Implement the extended prefix format from nostr-protocol/nips#1907
with bug fixes:
- When plaintext length >= 65536, pad with a 6-byte prefix (2 zero
sentinel bytes + 4-byte u32 BE length) instead of the 2-byte u16
- Detect the sentinel in unpad() and use dynamic prefix_len (2 or 6)
for both data extraction and padding validation
- Remove upper-bound size limits in decodePayload() (was rejecting
payloads > 87472 chars / 65603 bytes)
- Raise maxPlaintextSize from 0xffff to 0xffffffff (2^32-1)
- Add writeU32BE() helper for encoding large lengths
- Fix unpad() to use padded.byteOffset for correct DataView on
subarrays (was using padded.buffer directly which could be wrong
if padded is a subarray of a larger buffer)
Tests: add boundary tests at 65535/65536/65537 bytes and full
encrypt/decrypt round-trips at 65536 and 100000 bytes.
All 10 tests pass (5 existing + 5 new).
* nip44: fix calcPaddedLen overflow, enforce canonical prefix, add thorough tests
Fix three bugs found during security audit:
- calcPaddedLen: use 2** instead of 1<< to avoid signed 32-bit
overflow in JS for plaintext lengths above 2^30
- unpad: reject extended 6-byte prefix when decoded length is below
65536 (enforce canonical encoding, prevent ambiguous padding)
- decodePayload: document that callers should validate payload size
before calling to prevent DoS from oversized inputs
- Fix stale size-range comments to match corrected spec arithmetic
Add 10 new tests covering:
- Non-canonical extended prefix rejection (len=1, 1000, 65535)
- Truncated extended prefix buffer
- calcPaddedLen at 2^30+1, 2^31, and 2^32-1
- Multi-byte UTF-8 at the 65536 byte boundary (pad/unpad + e2e)
- Spec test vectors with SHA-256 checksums for 65535/65536/65537
Adds a skipSwitchRelays option that prevents automatic switchRelays()
call in fromURI. This is useful when users want to call switchRelays
manually at their own pace, avoiding race conditions when making
immediate RPC calls after connection.
Fixes#526
Addresses #494. The NIP-47 spec allows multiple relay parameters in
connection strings, but parseConnectionString only returned the first one.
Changes:
- Add 'relays' field to NWCConnection interface (string array)
- Use searchParams.getAll('relay') to capture all relays
- Keep 'relay' field for backwards compatibility (returns first relay)
This is backwards compatible - existing code using connection.relay
will continue to work, while new code can use connection.relays to
access all specified relays.