Skip to content

fix(devtools): let birpc be singlesource of turth for server<->client comms#978

Open
huang-julien wants to merge 3 commits intomainfrom
fix/extendsRpc
Open

fix(devtools): let birpc be singlesource of turth for server<->client comms#978
huang-julien wants to merge 3 commits intomainfrom
fix/extendsRpc

Conversation

@huang-julien
Copy link
Copy Markdown
Member

@huang-julien huang-julien commented Apr 5, 2026

… comms

🔗 Linked issue

📚 Description

Found bug when testing out nuxt/hints#318 with Vite 8 + devtools 4.0.0-alpha.3

This solves a race condiftion and remove extendedRpcMap to make the code a bit simpler. if extendClientRpc is called while connectDevToolsRpc( is mid-flight (after it already iterated extendedRpcMap but before it sets rpcClient), neither path registers the handlers.

Need also #977 to fix the issue with RPC in nuxt hints.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 20b8c881-e9a3-4482-b513-c95c89c13ff3

📥 Commits

Reviewing files that changed from the base of the PR and between 9bfd78e and 1f87b76.

📒 Files selected for processing (2)
  • packages/devtools/client/composables/client.ts
  • packages/devtools/client/composables/rpc.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/devtools/client/composables/client.ts

📝 Walkthrough

Walkthrough

The diff removes the centralized extendedRpcMap and replaces it with a shared reactive rpcClient and exported connectPromise. extendClientRpc no longer mutates a map or returns local function references; it registers function-valued entries directly on the DevTools RPC client using ${namespace}:${name} (immediately if rpcClient.value exists or deferred until connectPromise resolves) and returns a proxy whose get yields async per-key functions that await the active RPC client and call client.call with ${namespace}:${key}. The module-level rpc proxy now always delegates to client.call(method, ...) via rpcClient.value/connectPromise.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: removing extendedRpcMap to make birpc the single source of truth for RPC communications, which directly addresses the race condition bug being fixed.
Description check ✅ Passed The description relates to the changeset by explaining the race condition bug, why extendedRpcMap is being removed, and how birpc becomes the single source of truth—all aligned with the code changes.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/extendsRpc

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/devtools/client/composables/client.ts (1)

63-73: Make extendClientRpc() re-registration idempotent.

registerClientFunctions() in packages/devtools/client/composables/rpc.ts already uses update() with a register() fallback. Mirroring that here would keep ${namespace}:${name} safe across HMR/re-init instead of assuming register() replaces an existing handler.

♻️ Proposed change
         const register = (client: DevToolsRpcClient) => {
           for (const [name, handler] of Object.entries(functions)) {
             if (typeof handler === 'function') {
-              client.client.register({
-                name: `${namespace}:${name}`,
-                type: 'event',
-                handler: handler as any,
-              })
+              try {
+                client.client.update({
+                  name: `${namespace}:${name}`,
+                  type: 'event',
+                  handler: handler as any,
+                })
+              }
+              catch {
+                client.client.register({
+                  name: `${namespace}:${name}`,
+                  type: 'event',
+                  handler: handler as any,
+                })
+              }
             }
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/client/composables/client.ts` around lines 63 - 73, The
current register loop in the register function unconditionally calls
client.client.register for each `${namespace}:${name}`, which can clash on
HMR/re-init; make re-registration idempotent by attempting
client.client.update({ name: `${namespace}:${name}`, type: 'event', handler })
first and only calling client.client.register(...) as a fallback if update
fails/not available. Update the register function (and extendClientRpc usage if
present) to use update() with register() fallback for each entry in functions to
keep handlers safe across re-inits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/devtools/client/composables/client.ts`:
- Around line 75-78: The branch that calls connectPromise.then(register) drops
the returned promise and can cause an unhandled rejection if the RPC connection
fails; update the code around rpcClient/register/connectPromise to consume
rejections (for example by chaining .catch and forwarding the error to the same
wsError/console handler or invoking the existing error handler) so the promise
returned by connectPromise.then(register) is handled and its errors are
processed consistently with the other failure path.

---

Nitpick comments:
In `@packages/devtools/client/composables/client.ts`:
- Around line 63-73: The current register loop in the register function
unconditionally calls client.client.register for each `${namespace}:${name}`,
which can clash on HMR/re-init; make re-registration idempotent by attempting
client.client.update({ name: `${namespace}:${name}`, type: 'event', handler })
first and only calling client.client.register(...) as a fallback if update
fails/not available. Update the register function (and extendClientRpc usage if
present) to use update() with register() fallback for each entry in functions to
keep handlers safe across re-inits.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da97b456-c8bd-437c-9f81-ec0f4995d125

📥 Commits

Reviewing files that changed from the base of the PR and between 4c594ce and 3472304.

📒 Files selected for processing (2)
  • packages/devtools/client/composables/client.ts
  • packages/devtools/client/composables/rpc.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 5, 2026

Deploying nuxt-devtools with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1f87b76
Status: ✅  Deploy successful!
Preview URL: https://ddc3b255.nuxt-devtools.pages.dev
Branch Preview URL: https://fix-extendsrpc.nuxt-devtools.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/devtools/client/composables/client.ts (1)

63-78: Consider applying the update/register fallback pattern to extendClientRpc.

The register helper uses only client.client.register() without the update/register fallback pattern used in registerClientFunctions (see packages/devtools/client/composables/rpc.ts lines 67-85). Since extendClientRpc is a public API that external consumers can call, they may inadvertently call it multiple times with the same namespace and function name. To be defensive, apply the same pattern:

Suggested change
         for (const [name, handler] of Object.entries(functions)) {
           if (typeof handler === 'function') {
-            client.client.register({
-              name: `${namespace}:${name}`,
-              type: 'event',
-              handler: handler as any,
-            })
+            try {
+              client.client.update({
+                name: `${namespace}:${name}`,
+                type: 'event',
+                handler: handler as any,
+              })
+            }
+            catch {
+              client.client.register({
+                name: `${namespace}:${name}`,
+                type: 'event',
+                handler: handler as any,
+              })
+            }
           }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/client/composables/client.ts` around lines 63 - 78, The
current register helper in extendClientRpc calls only
client.client.register(...) which can fail or duplicate when consumers call
extendClientRpc multiple times; mirror the update/register fallback used in
registerClientFunctions by attempting client.client.update({...}) first and, if
that fails (catch), call client.client.register({...}) as a fallback. Update the
register function (the one defined inside extendClientRpc) to use
client.client.update and fallback to client.client.register for each { name:
`${namespace}:${name}`, type: 'event', handler } to make the API idempotent and
defensive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/devtools/client/composables/client.ts`:
- Around line 63-78: The current register helper in extendClientRpc calls only
client.client.register(...) which can fail or duplicate when consumers call
extendClientRpc multiple times; mirror the update/register fallback used in
registerClientFunctions by attempting client.client.update({...}) first and, if
that fails (catch), call client.client.register({...}) as a fallback. Update the
register function (the one defined inside extendClientRpc) to use
client.client.update and fallback to client.client.register for each { name:
`${namespace}:${name}`, type: 'event', handler } to make the API idempotent and
defensive.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b458595f-d13c-4e07-bd8a-586610fac1e3

📥 Commits

Reviewing files that changed from the base of the PR and between 3472304 and 9bfd78e.

📒 Files selected for processing (1)
  • packages/devtools/client/composables/client.ts

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