JavaScript / TypeScript clients

Use the existing browser client or the typed Node.js transport from @wsrpc/client. Both connect to Python and Node servers.

TypeScript / Node.js

@wsrpc/client/node provides a typed ws-based WSRPCClient. It connects to the same servers and supports callbacks, route classes, events and typed remote proxies. Install the client and its optional Node transport (or use the local archives from the TypeScript guide):

npm install @wsrpc/client ws
# For type checking a Node application:
npm install --save-dev @types/ws @types/node

Browser and Worker consumers install only @wsrpc/client; it does not pull in ws or Node declarations. Then run the Node client:

import { WSRPCClient } from "@wsrpc/client/node";

const client = await WSRPCClient.connect(
  process.argv[2] ?? "ws://127.0.0.1:8000/ws/",
  {
    routes: new Map([
      ["double", (_socket, { value }: { value: number }) => value * 2],
    ]),
  },
);
try {
  console.log(await client.call("echo", { message: "Hello" }));
  console.log(await client.call("callback", { value: 21 }));
} finally {
  await client.close();
}

Register callback routes through the routes option before connecting. Close the client in finally. client.call(method, params, options) returns a Promise; client.remote<Contract>() adds checked parameter and result types. Timeout values are milliseconds in TypeScript, compared with seconds in Python.

Browser

Install @wsrpc/client and serve its dist/wsrpc.min.js file through your framework’s static-file support. The script defines window.WSRPC. Python packages also bundle the same client and declarations in STATIC_DIR.

<script src="/js/wsrpc.min.js"></script>
<script>
  const scheme = window.location.protocol === "https:" ? "wss:" : "ws:";
  const rpc = new WSRPC(`${scheme}//${window.location.host}/ws/`);
  rpc.addRoute("double", ({ value }) => value * 2);
  rpc.connect();

  async function echo() {
    await rpc.onEvent("onconnect");
    try {
      console.log(await rpc.call("echo", { message: "Hello" }));
    } catch (error) {
      console.error(error);
    }
  }

  echo();
</script>

When using a bundler or ESM, import the default export:

import WSRPC from "@wsrpc/client";

The original wsrpc.es6.js, dist/wsrpc.js and dist/wsrpc.min.js paths remain available; CommonJS require("@wsrpc/client") returns the browser constructor. Existing JavaScript clients use the same protocol with either server.

Browser contracts in TypeScript

The existing constructor also accepts a generic contract. Its implementation and JavaScript call signatures stay unchanged:

import WSRPC from '@wsrpc/client';

interface Remote {
  echo(params: { value: number }): { value: number };
}
const rpc = new WSRPC<Remote>('ws://127.0.0.1:8000/ws/');
// Once connected, the proxy checks arguments and returns Promise<{ value: number }>.
// await rpc.proxy.echo({ value: 42 });

Browser call options use { noWait: true }. Node clients instead accept { timeout, signal }; the type declarations keep these transport options distinct. Both are checked by the repository’s npm run test:types, including expected compiler errors for incorrect calls.

Observe browser payloads

Existing application code written for 4.4.1 keeps using new WSRPC(url) or new WSRPC(url, reconnectTimeout). Only applications which need tracing opt in to the third constructor argument:

const rpc = new WSRPC('ws://127.0.0.1:8000/ws/', 1000, {
  onFrame({ direction, data }) {
    console.log(direction, data); // "in" or "out", raw application payload
  },
});
rpc.connect();

Incoming payloads arrive before JSON parsing, including malformed JSON. Outgoing payloads are reported once after WebSocket.send accepts them, including queued calls when flushed, callback responses and sendRaw data. Control frames are handled by the browser and are not exposed. This is observation only: return values are ignored and Promises are never awaited. Metadata is frozen and mutable binary payloads are copied. Observer failures, including rejected Promises and failures in console.error, do not interrupt RPC. DEBUG and TRACE retain their boolean behavior.

A compatibility test runs the same unmodified HTML/JavaScript application with the original npm 4.4.1 bundle and the new bundle, comparing results from calls, callbacks, events, binary sends, errors and reconnects. Unit tests also compare the serialized messages against the original source and UMD release.