npm i workersocket
Link to this section Overview
This is a library I made to run a WebSocket inside of a Web Worker in the browser. It exports a WorkerSocket
object which behaves as closely as possible to a browser WebSocket.
Link to this section Motivation
I wrote this while implementing my fork of roslib, a library for communicating with a ros
server through the rosbridge
protocol. roslib
is typically used to build a web-based monitoring UI for some type of robot.
The original roslib
hijacked a browserify library called webworkify
to run a WebSocket through a Web Worker, but webworkify
doesn't bundle with Vite or Webpack 5.
The reason roslib
defaults to putting a WebSocket inside of a Web Worker is to make sure that data is pulled away from the server as quickly as possible, even if it ends up building up in the web worker. Due to an oversight in the rosbridge
server, if a client can't pull information quickly enough, then the server will use excessive resources caching data. This pull request for the original roslib
describes the issue in more detail. Generally, the software on the robot is more important than the monitoring UI, so offloading the queueing to a background thread in the UI client makes sense.
Link to this section Technical Description
The library consists of two parts: the web worker itself, and the WorkerSocket
implementation.
The WorkerSocket
class mimics the API of a WebSocket
, but forwards messages to the web worker. The web worker then handles the actual socket. The WorkerSocket
class maintains an array of listeners for each event and calls each one as necessary. It also generates a unique ID for each WorkerSocket
class instantiated, which is then sent along with all messages to the web worker. The worker maintains a mapping from IDs to socket instances.
The web worker is constructed using an object URL. This technique allows the worker to be bundled with any bundler without any special handling. It is somewhat unintuitive, though, using Function.toString()
to turn the worker implementation into a string, then placing it into an immediately-invoked function expression using string manipulation. This is then turned into an object URL from which the worker is loaded.
const workerURL = URL.createObjectURL(new Blob(["(" + workerImpl.toString() + ")();"], {type: "text/javascript",}));const worker = new Worker(workerURL);
Testing is performed using Chai in the browser (and using Puppeteer to run headlessly).
Link to this section Results
It seems to work well. That said, there are so many edge cases with the behavior of WebSockets in the browser that I'm a bit hesitant to use it. I've written plenty of tests to cover common use cases, and I don't know what I would add, but I also don't feel like the test suite is entirely complete.
I've published the result to npm
though, in case others want to make use of this.