Skip to content

fix: align remote nostr protocol - #3

Merged
beeman merged 1 commit into
mainfrom
beeman/remote-nostr-spec
Jun 2, 2026
Merged

beeman merged 1 commit into
mainfrom
beeman/remote-nostr-spec

Conversation

@beeman

@beeman beeman commented Jun 2, 2026 •

Copy link
Copy Markdown
Member

Derive Nostr session routing from the association key and emit spec-shaped pairing URLs.

Require CONNECT and SESSION_END control messages, terminate on relay CLOSED/failed OK messages, and cover the protocol behavior with browser and node tests.

Summary by CodeRabbit

  • New Features

    • Added support for local and remote association modes for wallet pairing.
  • Bug Fixes

    • Improved remote wallet session reliability with enhanced error detection for relay disconnections and failed event submissions.
    • Enforced minimum timeout threshold for wallet handshake requests to ensure robust connection establishment.
  • Improvements

    • Session identifiers are now derived deterministically from association keys for consistent pairing behavior.

@changeset-bot

changeset-bot Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7b31a26

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@beeman, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 59 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 185c5113-772d-43d5-b9e4-2599093419bf

📥 Commits

Reviewing files that changed from the base of the PR and between a7a03ff and 7b31a26.

📒 Files selected for processing (8)
  • src/browser/create-remote-wallet-session.ts
  • src/node/connect-remote-wallet.ts
  • src/protocol/association-url.ts
  • src/protocol/types.ts
  • test/browser/create-remote-wallet-session.test.ts
  • test/node/connect-remote-wallet.test.ts
  • test/protocol/association-url.test.ts
  • test/react/use-remote-wallet-pairing.test.ts
📝 Walkthrough

Walkthrough

This PR refactors remote wallet session connections to derive session identifiers deterministically from association public keys, introduces per-session runtime options storage, enforces minimum connection timeouts, and adds explicit handling of relay failure signals (subscription closure and event rejection).

Changes

Deterministic Session Identifiers & Enhanced Failure Handling

Layer / File(s) Summary
Protocol foundation: deterministic session ID derivation and pairing details
src/protocol/types.ts, src/protocol/association-url.ts, test/protocol/association-url.test.ts
PairingDetails includes new associationMode field. deriveNostrSessionIdentifier() derives session IDs as hex-encoded SHA-256 of the association public key. Association URLs now embed mode in pathname (/v1/associate/{mode}/nostr), omit session query parameter, and parseNostrAssociationUrl derives the session ID and validates legacy session query parameters against the derived value. Tests cover remote/local URL shaping and legacy compatibility.
Browser session connection: runtime options, session preparation, and relay failure handling
src/browser/create-remote-wallet-session.ts, test/browser/create-remote-wallet-session.test.ts
Introduces MIN_DAPP_CONNECT_TIMEOUT_MS and WeakMap for per-session runtime options (identity, status callback). prepareRemoteWalletSession centralizes relay setup and enforces minimum timeout. createRemoteWalletSession derives session ID deterministically and connectRemoteWalletSession sets runtime options then prepares the session. connectToNostrRelay accepts callback getters, uses unified failSession helper for subscription closure and OK(false) rejection, and tightens CONNECT event filtering to validate msg tag. Tests validate pairing URL generation, subscription gating via EOSE, derived session ID usage, CONNECT event filtering, and relay closure rejection.
Node wallet connection: minimum handshake timeout and failure signal handling
src/node/connect-remote-wallet.ts, test/node/connect-remote-wallet.test.ts
Introduces MIN_WALLET_HELLO_REQ_TIMEOUT_MS and enforces minimum timeout via Math.max() for wallet handshake. Relay message handling explicitly fails on subscription CLOSED and OK(false) rejection via unified failSession helper. Tests validate signer state exposure, CONNECT event shape and tags, unexpected SESSION_END filtering, subscription closure rejection, and relay OK failure rejection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hop! No more random IDs in the dark,
Derived from keys with a SHA-256 arc,
Session options nestled in a WeakMap store,
With timeouts enforced and failures galore—
Local and remote, both paths now clear!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: align remote nostr protocol' is vague and does not clearly convey the primary changes, which involve deriving session identifiers from association keys, updating pairing URL generation, enforcing control message requirements, and adding comprehensive test coverage. Consider a more specific title such as 'refactor: derive session identifiers from association keys and align nostr protocol' to better communicate the main architectural changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch beeman/remote-nostr-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

bun add https://pkg.pr.new/wallet-ui/remote-wallet@3

commit: 7b31a26

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/browser/create-remote-wallet-session.ts (1)

374-407: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep expiresAt aligned with the enforced minimum timeout.

prepareRemoteWalletSession() clamps the live relay timeout to at least 30_000ms, but expiresAt is still computed from the raw timeoutMs. For callers that pass a smaller timeout, the session can still be active after the advertised expiry, which is likely to desync any UI countdown or expiry-based cleanup.

Compute one effective timeout up front and reuse it for both expiresAt and prepareRemoteWalletSession().

Also applies to: 461-466

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/browser/create-remote-wallet-session.ts` around lines 374 - 407, The
session's expiresAt uses the raw timeoutMs while prepareRemoteWalletSession
enforces a minimum (30_000ms), causing a mismatch; fix by computing an
effectiveTimeout once (e.g. const effectiveTimeout = Math.max(timeoutMs, 30_000)
or the appropriate MIN constant) and use effectiveTimeout when setting expiresAt
and when calling prepareRemoteWalletSession({ session, timeoutMs:
effectiveTimeout }); update the same pattern in the other create block
referenced (lines ~461-466) so both expiresAt and prepareRemoteWalletSession
share the same effective timeout.
src/protocol/association-url.ts (1)

81-87: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve ws:// for IPv6 loopback relays.

isLocalRelayHost() does not recognize bracketed IPv6 loopback hosts, so a pairing URL created from ws://[::1]:8080 is parsed back as wss://[::1]:8080. That breaks the local-association flow on IPv6 even though ::1 is explicitly intended to be supported here.

Suggested fix
 function isLocalRelayHost(hostname: string) {
-  return hostname === '127.0.0.1' || hostname === '::1' || hostname === 'localhost'
+  const normalizedHostname = hostname.startsWith('[') && hostname.endsWith(']')
+    ? hostname.slice(1, -1)
+    : hostname
+
+  return (
+    normalizedHostname === '127.0.0.1' ||
+    normalizedHostname === '::1' ||
+    normalizedHostname === 'localhost'
+  )
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/protocol/association-url.ts` around lines 81 - 87, Update
isLocalRelayHost to accept bracketed IPv6 hostnames by normalizing the input: if
hostname starts with '[' and ends with ']', strip the brackets before checking
equality. Ensure isLocalRelayHost still compares the normalized value against
'127.0.0.1', '::1', and 'localhost' so that inputs like '[::1]' or '[::1]:port'
(after URL parsing) correctly return true for local relays.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/browser/create-remote-wallet-session.ts`:
- Around line 461-466: The session identity getter used for connectToNostrRelay
(getIdentity: () => remoteWalletSessionRuntimeOptions.get(session)?.identity ??
getDefaultIdentity()) is not passed into signIn/reauthorize, so
RemoteWalletAuthorizedSession.signIn() → RemoteWalletRpcClient.signIn() →
authorizeRemoteWallet() can fall back to getDefaultIdentity() and present a
different identity; thread the same getter/value through these calls by adding a
getIdentity (or identityGetter) parameter to
RemoteWalletAuthorizedSession.signIn(), RemoteWalletRpcClient.signIn(), and
authorizeRemoteWallet(), update the callers (including the connect-time path and
the other similar site around the 682-688 range) to pass
remoteWalletSessionRuntimeOptions.get(session)?.identity ??
getDefaultIdentity(), and ensure authorizeRemoteWallet() uses the provided
getter and only falls back to getDefaultIdentity() if that getter returns
undefined.

---

Outside diff comments:
In `@src/browser/create-remote-wallet-session.ts`:
- Around line 374-407: The session's expiresAt uses the raw timeoutMs while
prepareRemoteWalletSession enforces a minimum (30_000ms), causing a mismatch;
fix by computing an effectiveTimeout once (e.g. const effectiveTimeout =
Math.max(timeoutMs, 30_000) or the appropriate MIN constant) and use
effectiveTimeout when setting expiresAt and when calling
prepareRemoteWalletSession({ session, timeoutMs: effectiveTimeout }); update the
same pattern in the other create block referenced (lines ~461-466) so both
expiresAt and prepareRemoteWalletSession share the same effective timeout.

In `@src/protocol/association-url.ts`:
- Around line 81-87: Update isLocalRelayHost to accept bracketed IPv6 hostnames
by normalizing the input: if hostname starts with '[' and ends with ']', strip
the brackets before checking equality. Ensure isLocalRelayHost still compares
the normalized value against '127.0.0.1', '::1', and 'localhost' so that inputs
like '[::1]' or '[::1]:port' (after URL parsing) correctly return true for local
relays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c77b468a-8382-4fc9-8dab-e10c789e56ab

📥 Commits

Reviewing files that changed from the base of the PR and between 22797d8 and a7a03ff.

📒 Files selected for processing (7)
  • src/browser/create-remote-wallet-session.ts
  • src/node/connect-remote-wallet.ts
  • src/protocol/association-url.ts
  • src/protocol/types.ts
  • test/browser/create-remote-wallet-session.test.ts
  • test/node/connect-remote-wallet.test.ts
  • test/protocol/association-url.test.ts

Comment thread src/browser/create-remote-wallet-session.ts
Derive Nostr session routing from the association key and emit spec-shaped pairing URLs.

Require CONNECT and SESSION_END control messages, terminate on relay CLOSED/failed OK messages, and cover the protocol behavior with browser and node tests.
@beeman
beeman force-pushed the beeman/remote-nostr-spec branch from a7a03ff to 7b31a26 Compare June 2, 2026 19:42
@beeman

beeman commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the review-body nits as well: expiresAt now uses the same enforced dapp connect timeout passed into relay preparation, and bracketed IPv6 loopback relay hosts normalize back to ws://. Verification: bun run lint, bun run check-types, bun test, and bun run test:e2e all pass locally.

@beeman

beeman commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@beeman
beeman merged commit 7b31a26 into main Jun 2, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant