Skip to content

Add lightweight WebSocket bridge for browser access to ROS 2#1495

Merged
minggangw merged 3 commits intoRobotWebTools:developfrom
minggangw:fix-1494
Apr 30, 2026
Merged

Add lightweight WebSocket bridge for browser access to ROS 2#1495
minggangw merged 3 commits intoRobotWebTools:developfrom
minggangw:fix-1494

Conversation

@minggangw
Copy link
Copy Markdown
Member

@minggangw minggangw commented Apr 30, 2026

Adds a lightweight WebSocket bridge built into rclnodejs so a plain browser (or any WebSocket-capable client) can talk to ROS 2 with no JavaScript library on the client side — built-in WebSocket + JSON are enough.

Highlights

  • Resource-style URLs: ws://host:port/topic/<name>?type=<pkg>/msg/<Type> and ws://host:port/service/<name>?type=<pkg>/srv/<Type>; the URL is the topic/service, the WS frame is the ROS message as JSON.
  • Same Node.js process as your rclnodejs app — no extra Python service to deploy or version-match against ROS distros.
  • Lightweight alternative to rosbridge_suite + roslibjs for the common pub/sub + service-client cases (full comparison table in rosocket/README.md).
  • CLI shipped as a bin entrynpx rosocket --port 9000 --topic /chatter:std_msgs/msg/String; supports repeatable --topic/--service defaults, --host, --node-name, integer-validated --port, and clean SIGINT/SIGTERM shutdown with a hard-exit fallback.
  • Programmatic API: const { startRosocket } = require('rclnodejs/rosocket'); await startRosocket({ node, port, topicTypes, serviceTypes, verifyClient }).
  • Optional pre-declared types (topicTypes / serviceTypes) let browsers omit ?type=; without them the bridge stays generic.
  • Service envelope: bare {a,b} request shape or wrapped {id, request:{...}} for concurrent in-flight call correlation; responses echo the same shape.
  • 64-bit ints: accepts JSON numbers or BigInt-encoded strings ("12n"); responses use the rclnodejs toJSONSafe encoding.
  • Error/close conventions: {error: "<msg>"} frames; protocol violations close with 1008, server errors with 1011.
  • verifyClient hook wraps ws's native (info, cb) callback into the documented (req: IncomingMessage) => boolean signature.
  • Tests: 7 mocha cases covering unknown-path/missing-type rejection, configured topicTypes subscribe, ?type= publish, and bare + {id, request} service round-trips, exercised against example_interfaces/srv/AddTwoInts (BigInt-encoded int64 request fields).

Files

  • rosocket/index.js — implementation (startRosocket, debug namespace rclnodejs:rosocket).
  • rosocket/cli.jsrosocket CLI (also exposed as npm run rosocket).
  • rosocket/README.md — full feature doc, comparison table, URL scheme, server/CLI/browser snippets.
  • test/test-rosocket.js — mocha suite (7 tests).
  • README.md — new top-level "rosocket — ROS 2 in the browser, no library required" section + TOC entry.
  • package.json — adds ws ^8.18.0, bin.rosocket, scripts.rosocket.

Availability

Experimental; ships only on develop. Try it with npm install RobotWebTools/rclnodejs#develop.

Fix: 1494

Copilot AI review requested due to automatic review settings April 30, 2026 06:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new web_bridge/ module providing an in-process WebSocket bridge that exposes ROS 2 topics and services via resource-style URLs, plus a runnable CLI and accompanying documentation/tests.

Changes:

  • Added startWebBridge() implementation (/topic/<name>, /service/<name>) and module entrypoint.
  • Added a rclnodejs-web-bridge CLI (and npm run web-bridge) plus runtime dependency on ws.
  • Added documentation and Mocha coverage for bridge behavior and framing conventions.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
web_bridge/bridge.js Core WebSocket server + per-connection topic/service routing and JSON (de)serialization helpers.
web_bridge/cli.js CLI wrapper around startWebBridge() with argument parsing and shutdown handling.
web_bridge/index.js Subpath module entrypoint exporting startWebBridge.
web_bridge/README.md Detailed user-facing docs for URL scheme, usage, and conventions.
test/test-web-bridge.js Mocha tests validating path/type rejection and basic pub/sub + service round-trips.
package.json Adds ws dependency, CLI bin entry, and web-bridge npm script.
README.md Top-level documentation entry pointing users to the web bridge.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web_bridge/README.md Outdated
Comment on lines +54 to +56
- 64-bit integer fields may be sent as JSON numbers, BigInt-encoded strings
(`"12n"`), or decimal strings; responses use the rclnodejs `toJSONSafe`
encoding (BigInts become `"<n>n"` strings).
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

README states 64-bit integer fields may be sent as JSON numbers, BigInt-encoded strings ("12n"), or decimal strings, but the current reviveBigInts() implementation only converts strings that match /^-?\d+n$/ and will leave plain decimal strings as strings. Either update the documentation to match the actual accepted input formats, or extend reviveBigInts() to also parse decimal-string integers into BigInt (careful to not break non-integer string fields).

Suggested change
- 64-bit integer fields may be sent as JSON numbers, BigInt-encoded strings
(`"12n"`), or decimal strings; responses use the rclnodejs `toJSONSafe`
encoding (BigInts become `"<n>n"` strings).
- 64-bit integer fields may be sent as JSON numbers or BigInt-encoded
strings (`"12n"`); responses use the rclnodejs `toJSONSafe` encoding
(BigInts become `"<n>n"` strings).

Copilot uses AI. Check for mistakes.
Comment thread web_bridge/bridge.js Outdated
Comment on lines +75 to +76
* @param {(req: import('http').IncomingMessage) => boolean} [options.verifyClient] - Optional auth hook.
* @returns {Promise<{wss: WebSocketServer, close: () => Promise<void>, port: number}>}
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

The JSDoc for options.verifyClient says it is called as (req: IncomingMessage) => boolean, but startWebBridge passes this option straight through to ws.WebSocketServer. In ws, verifyClient is invoked with an info object (and optionally a callback), not the raw IncomingMessage, so consumers following this JSDoc will likely implement the hook with the wrong signature. Consider wrapping ws's verifyClient to call the user hook with info.req, and update the JSDoc type/signature accordingly.

Copilot uses AI. Check for mistakes.
Comment thread web_bridge/index.js Outdated
@@ -0,0 +1,6 @@
// Copyright (c) 2026 RobotWebTools. Apache-2.0.
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

This file uses a one-line copyright/SPDX-style header that differs from the Apache 2.0 license header block used throughout the repo’s JS sources (e.g., index.js, lib/message_serialization.js). To keep licensing/headers consistent across the codebase, update this header to match the standard Apache 2.0 header text used in other files.

Suggested change
// Copyright (c) 2026 RobotWebTools. Apache-2.0.
/*
* Copyright 2026 RobotWebTools
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

Copilot uses AI. Check for mistakes.
Comment thread web_bridge/cli.js Outdated
Comment on lines +73 to +74
opts.port = Number(need(i, a));
i++;
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

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

--port is parsed with Number(...), but there’s no validation that the result is a finite integer in a valid TCP port range. Passing a non-numeric value (or NaN) will later fail inside ws with a less actionable error. Add an explicit check (e.g., Number.isInteger, 0–65535) and exit with a clear message if invalid.

Copilot uses AI. Check for mistakes.
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 30, 2026

Coverage Status

coverage: 85.864%. remained the same — minggangw:fix-1494 into RobotWebTools:develop

@minggangw minggangw merged commit 3176aca into RobotWebTools:develop Apr 30, 2026
16 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.

3 participants