Skip to content

feat(collab): handle CLIENT_MESSAGE suggestUserName on the client#6

Merged
SamTV12345 merged 1 commit into
mainfrom
feat/client-message
Jun 12, 2026
Merged

feat(collab): handle CLIENT_MESSAGE suggestUserName on the client#6
SamTV12345 merged 1 commit into
mainfrom
feat/client-message

Conversation

@SamTV12345

Copy link
Copy Markdown
Member

Summary

Client-side counterpart to the Go server's COLLABROOM CLIENT_MESSAGE handling (etherpad-go#276/#280): when another user suggests a name for an unnamed user (suggestUserName), the targeted client now adopts the name and propagates a USERINFO_UPDATE, mirroring the original pad_userlist/pad behavior.

  • New src/collab/clientMessage.ts (exported as ./collab/clientMessage.js):
    • createClientMessageHandler({getMyUserInfo, setMyUserName, sendMessage, bus?}) — adopts a suggested name when unnamedId matches this user and the user has no name yet; sends USERINFO_UPDATE; emits user:info:updated on the editor bus.
    • extractClientMessagePayload / handleFrame — tolerant unwrapping of all wire framings (socket.io-style array frames from the Go relay, {event,data} objects, bare COLLABROOM payloads).
  • padoptions and unknown payload types are tolerated as no-ops (no settings feature built).
  • Sending a suggestion (user-list UI) is not part of this package — the user list lives in the host app; this library only ships the receiving side.
  • Test infra: added a node unit vitest project alongside the existing storybook browser project; 12 new unit tests.

Test plan

  • pnpm typecheck, pnpm build clean
  • CI=true pnpm test — 16 files, 94 tests passed (15 story files + the new unit file)

🤖 Generated with Claude Code

… client

Adds src/collab/clientMessage.ts, the client-side counterpart of the Go
server's HandleClientMessage (COLLABROOM CLIENT_MESSAGE family),
mirroring the original Etherpad pad.handleClientMessage behavior:

- suggestUserName: when a received suggestion targets this (still
  unnamed) user, adopt the name locally, send a USERINFO_UPDATE back to
  the server, and emit user:info:updated on the editor bus.
- padoptions: tolerated as a no-op (pad-wide settings unsupported).
- unknown payload types and malformed frames are ignored without
  throwing.

extractClientMessagePayload unwraps both wire framings used on this
stack (server->client ["message", data] arrays and client->server
{event, data} objects) as well as already-unwrapped COLLABROOM
messages, so hosts can plug in at any level.

Sending suggestions is not implemented because this package has no
user list UI (EpUserBadge is display-only); the sending side lives in
the host's pad_userlist.

Tests run in a new "unit" vitest project (node) alongside the existing
"storybook" browser project; pnpm test now runs both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0)

Grey Divider


Remediation recommended

1. Handler may throw 🐞 Bug ☼ Reliability
Description
createClientMessageHandler claims it "never throws", but handleSuggestUserName calls host-provided
callbacks (getMyUserInfo/setMyUserName/sendMessage) without any exception handling. If any of those
throws, the exception will bubble up and can break the caller’s collab message processing loop.
Code

src/collab/clientMessage.ts[R162-191]

+  const handleSuggestUserName = (payload: Record<string, unknown>): boolean => {
+    const { newName, unnamedId } = payload;
+    const myUserInfo = context.getMyUserInfo();
+    // Mirror the original guard: only adopt suggestions that target this
+    // user, carry a non-empty name, and only while this user is unnamed.
+    if (
+      typeof newName !== 'string' ||
+      newName === '' ||
+      unnamedId !== myUserInfo.userId ||
+      myUserInfo.name
+    ) {
+      return false;
+    }
+
+    context.setMyUserName(newName);
+
+    // Propagate the adopted name to the server (collabClient.updateUserInfo
+    // equivalent) so all other clients learn the new name.
+    const userInfo: CollabUserInfo = {
+      ...myUserInfo,
+      name: newName,
+    };
+    context.sendMessage({ type: 'USERINFO_UPDATE', userInfo });
+
+    bus.emit('user:info:updated', {
+      userId: myUserInfo.userId,
+      name: newName,
+      colorId:
+        typeof myUserInfo.colorId === 'string' ? myUserInfo.colorId : undefined,
+    });
Evidence
The handler interface/docs state it "Never throws", but the implementation directly calls host
callbacks and performs side effects without any try/catch, so exceptions can propagate to callers.

src/collab/clientMessage.ts[84-96]
src/collab/clientMessage.ts[162-192]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`handleClientMessage`/`handleFrame` are documented as tolerant and non-throwing, but `handleSuggestUserName` invokes host integration points without guarding against exceptions. Any thrown error can escape and disrupt upstream message handling.

### Issue Context
This module is intended to be fed raw network frames and should be resilient even when host callbacks or integration glue (e.g., sendMessage) fails.

### Fix Focus Areas
- Wrap the side-effecting integration calls in a `try/catch` (either per-call or around the whole suggest handler) and ensure the handler returns a boolean rather than throwing.
- Optionally log (or emit) a debug/error signal when an exception occurs so hosts can diagnose.

### Fix Focus Areas (code references)
- src/collab/clientMessage.ts[162-193]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Whitespace names allowed 🐞 Bug ≡ Correctness
Description
handleSuggestUserName only rejects an empty string (""), so a suggestion such as "   " will be
adopted and propagated as the user’s name. This produces visually blank usernames in components that
render the name directly (e.g., EpUserBadge).
Code

src/collab/clientMessage.ts[R167-177]

+    if (
+      typeof newName !== 'string' ||
+      newName === '' ||
+      unnamedId !== myUserInfo.userId ||
+      myUserInfo.name
+    ) {
+      return false;
+    }
+
+    context.setMyUserName(newName);
+
Evidence
The guard only checks newName === '', so whitespace-only strings are accepted; EpUserBadge renders
name directly and derives an initial from charAt(0), so whitespace-only names appear
blank/space-like.

src/collab/clientMessage.ts[167-177]
src/EpUserBadge.ts[54-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The suggested name is only checked for `newName === ''`, so whitespace-only names pass validation and become the user’s name.

### Issue Context
Usernames are rendered as text; whitespace-only names are effectively blank and make users hard to identify.

### Fix Focus Areas
- Normalize the input with `const normalized = newName.trim()`.
- Use `normalized` for validation (`normalized === ''`) and for `setMyUserName`, the emitted bus event, and the USERINFO_UPDATE payload.

### Fix Focus Areas (code references)
- src/collab/clientMessage.ts[167-192]
- src/EpUserBadge.ts[54-66]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Handle COLLABROOM CLIENT_MESSAGE suggestUserName on the client
✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

Walkthroughs

Description
• Add client-side handler for COLLABROOM CLIENT_MESSAGE payloads, including suggestUserName.
• Adopt suggested name for unnamed users, then send USERINFO_UPDATE and emit a bus event.
• Add Node-based unit test project in Vitest alongside existing Storybook browser tests.
Diagram
graph TD
  S{{"Go collab server"}} --> F["Wire frame"] --> X["extractPayload"] --> H["ClientMessage handler"] --> U["setMyUserName()"]
  H --> M["sendMessage(USERINFO_UPDATE)"] --> S
  H --> B(("editorBus")) --> L["Host listeners"]
  subgraph Legend
    direction LR
    _ext{{"External"}} ~~~ _fn["Module/Function"] ~~~ _bus(("Event bus"))
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Implement inside an existing collab client class
  • ➕ Centralizes all collab message handling in one place
  • ➕ Easier to ensure ordering/consistency with other collab message types
  • ➖ Harder to reuse in hosts that already have their own transport/collab wrapper
  • ➖ Tighter coupling to a specific client implementation
2. Add runtime schema validation (e.g., zod/io-ts) for frames
  • ➕ More robust parsing and clearer diagnostics for malformed payloads
  • ➕ Easier to evolve payload types with explicit schemas
  • ➖ Adds dependency and bundle size for a small parsing surface
  • ➖ May be overkill given current 'tolerant/no-throw' requirements

Recommendation: The current approach (small, host-injected handler + tolerant frame unwrapping) is a good fit for a UI component library that shouldn’t own the transport layer. Consider optional schema validation only if future CLIENT_MESSAGE payloads become more complex or need better error reporting.

Grey Divider

File Changes

Enhancement (2)
clientMessage.ts Add CLIENT_MESSAGE frame unwrapping and suggestUserName handler +218/-0

Add CLIENT_MESSAGE frame unwrapping and suggestUserName handler

• Introduces a host-integrated CLIENT_MESSAGE handler that can unwrap multiple wire framings and safely ignore unknown/malformed inputs. Implements suggestUserName adoption for unnamed users, sending USERINFO_UPDATE and emitting user:info:updated; treats padoptions as a handled no-op.

src/collab/clientMessage.ts


index.ts Re-export clientMessage APIs from library root +10/-0

Re-export clientMessage APIs from library root

• Exports createClientMessageHandler/extractClientMessagePayload and associated types so hosts can consume the new collab handler via the main package entrypoint.

src/index.ts


Tests (1)
clientMessage.test.ts Add unit tests for suggestUserName handling and frame parsing +216/-0

Add unit tests for suggestUserName handling and frame parsing

• Adds Vitest unit coverage for suggestUserName adoption behavior, USERINFO_UPDATE emission, and EventBus notification. Verifies tolerant behavior for padoptions/unknown payloads and extraction across supported wire framings.

test/clientMessage.test.ts


Other (2)
package.json Export clientMessage entrypoint and add unit test scripts +4/-2

Export clientMessage entrypoint and add unit test scripts

• Adds a new package export for ./collab/clientMessage.js. Updates Vitest scripts to run both Storybook browser tests and the new unit project, and adds a dedicated test:unit script.

package.json


vitest.config.ts Split Vitest into storybook (browser) and unit (node) projects +21/-8

Split Vitest into storybook (browser) and unit (node) projects

• Refactors Vitest config to use multi-project setup: existing Storybook+Playwright browser tests and a new node-based unit test project that includes test/**/*.test.ts.

vitest.config.ts


Grey Divider

Qodo Logo

@SamTV12345 SamTV12345 merged commit 6ad97e8 into main Jun 12, 2026
3 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