https://github.com/jakeg/websocket-functions
Run functions over WebSockets - for Bun and web browsers
https://github.com/jakeg/websocket-functions
bun chat javascript json json-api messaging multiplayer npm remote-procedure-calls rpc rpc-api rpc-client rpc-framework rpc-server websocket websocket-client websocket-server
Last synced: about 2 months ago
JSON representation
Run functions over WebSockets - for Bun and web browsers
- Host: GitHub
- URL: https://github.com/jakeg/websocket-functions
- Owner: jakeg
- Created: 2025-10-30T11:52:23.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2025-10-31T05:00:06.000Z (about 2 months ago)
- Last Synced: 2025-10-31T06:31:46.510Z (about 2 months ago)
- Topics: bun, chat, javascript, json, json-api, messaging, multiplayer, npm, remote-procedure-calls, rpc, rpc-api, rpc-client, rpc-framework, rpc-server, websocket, websocket-client, websocket-server
- Language: JavaScript
- Homepage: https://www.npmjs.com/package/websocket-functions
- Size: 15.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# WebSocket Functions
```js
let sum = await ws.func('addNums', [3, 5])
```
...and get the return value back over a WebSocket connection.
Abstract away those pesky messages and just invoke remote functions.
Works with Bun (server and clients) and web clients.
Under 4KB. Single file. No dependencies.
## Install
```bash
bun install websocket-functions
```
... or just copy the tiny `index.js` file into your project.
## Quick start
For servers (only Bun currently supported):
```js
import { wsServer } from 'websocket-functions'
// these functions can be called by clients
let handlers = {
addNums: (nums) => nums.reduce((acc, v) => acc + v, 0)
}
let server = wsServer(Bun.serve, handlers, {
routes: {
'/ws': (req) => server.upgrade(req),
// ... other routes
}
})
console.log(`Server listening at ${server.url}`)
```
---
For clients (web or Bun):
```js
import { wsClient } from 'websocket-functions'
let ws = wsClient(new WebSocket('ws://localhost:3000/ws'))
ws.onopen = async () => {
let sum = await ws.func('addNums', [3, 5])
console.log(sum)
}
```
You can also use `ws.proc('funcName', params)` if no return value is needed.
---
Clients can also have handlers which can be called from the server:
```js
import { wsClient } from 'websocket-functions'
// these functions can be called from the server
let handlers = {
addNums: (nums) => nums.reduce((acc, v) => acc + v, 0)
}
// pass the handlers as the optional 2nd argument to wsClient()
let ws = wsClient(new WebSocket('ws://localhost:3000/ws'), handlers)
```
---
Use Bun's `pub/sub` on the server to run a function on all clients subscribed to a room/channel:
```js
// run on all clients in 'some-room' (excluding ws itself)
ws.publishProc('some-room', 'addNums', [5, 7])
// run on all clients in 'some-room' (including ws itself)
server.publishProc('some-room', 'addNums', [5, 7])
```
## Bun server with web client example
Put these 3 files in the same folder.
`server.js`:
```js
import { wsServer } from 'websocket-functions'
import clientPage from './client.html'
// these functions can be called by clients
let handlers = {
logMsg: (msg) => console.log('Your message:', msg),
addNums: (nums) => nums.reduce((acc, v) => acc + v, 0)
}
let server = wsServer(Bun.serve, handlers, {
routes: {
'/': clientPage,
'/ws': (req) => server.upgrade(req)
}
})
console.log(`Server listening at ${server.url}`)
```
`client.html`:
```html
Example RPC client
pre { display: inline; background: #eee; padding: 5px; }
Try eg ws.proc('logMsg', 'Hello world') or await ws.func('addNums', [3, 5]) in the console.
```
`client.js`:
```js
import { wsClient } from 'websocket-functions'
globalThis.ws = wsClient(new WebSocket('ws://localhost:3000/ws'))
ws.onopen = async () => {
// run a procedure that doesn't have a return value
ws.proc('logMsg', 'From client, running on server')
// run a function and do something with the value returned
console.log(await ws.func('addNums', [3, 5]))
}
```
Start the server with `bun server.js` then open in a browser and in the developer tools console run eg `await ws.func('addNums', [3, 5])`.
## API documentation
Assuming imported with `import * as wsFunc from 'websocket-functions'`.
### `wsFunc.wsClient(ws, handlers = {})`
For usage on WebSocket clients (Bun and web browsers).
Attaches extra methods `proc()` and `func()` to the `WebSocket` object passed as the first argument. Also adds a `message` event listener to handle JSON messages with `{ jsonrpc: '2.0' }` in them.
If attaching your own `message` event listener, do so with `ws.addEventListener('message', func)` rather than `ws.onmessage = func` so as not to override our event listener. You'll probably want to ignore messages with `{ jsonrpc: '2.0' }` in them.
To provide functions which the server can invoke over a WebSocket connection, send an optional second argument being an object of functions.
### `wsFunc.wsServer(serve, handlers, opt)`
For usage with Bun servers.
Starts your Bun server as normal, but with extra methods `proc()`, `func()` and `publishProc()` attached to each WebSocket connection and a `publishProc()` method attached to the server instance. Also adds an additional `message` handler to handle JSON messages with `{ jsonrpc: '2.0' }` in them.
Pass `Bun.serve` as the first argument, an object of functions callable by clients as the second argument, and the normal Bun server configuration options as the third argument. Adds a `websocket` property to Bun's server configuration options if you don't provide one, or adds to yours if you do.
Provide functions which connected WebSocket clients can invoke over a WebSocket connection as the second argument, or an empty `{}` object if none required.
### `wsFunc.config`
A configuration object. Currently with just one property `timeout` that defaults to `30_000`ms. Change the value of `timeout` to set a global maximum time in ms before `ws.func()` calls are rejected.
### `handlers` object
An object of functions which can be provided to server or clients which can then be invoked by the other over a WebSocket connection, eg:
```js
let handlers = {
logMsg: (msg) => console.log('Your message:', msg),
addNums: (nums) => nums.reduce((acc, v) => acc + v, 0)
}
```
You might chose to put these in a separate file and import them with eg `import * as handlers from './handlers.js'`:
```js
// File: handlers.js
export function logMsg (msg) {
console.log('Your message', msg)
}
export function addNums (nums) {
return nums.reduce((acc, v) => acc + v, 0)
}
export function logName (_, ws) {
console.log('User is called', ws.data.userName)
}
export function getUserProp (prop, ws) {
return ws.data.user?.[prop]
}
```
### `handler(params, ws)`
Each handler in your `handlers` object will be called with two parameters:
- `params` the value provided by eg `ws.proc('funcName', params)`; can be of any type
- `ws` the WebSocket connection, allowing you to eg get data from it with `ws.data.userName`
If called with `ws.func()` as opposed to `ws.proc()` be sure to provide a `return` value, which will be sent back over the WebSocket connection. If no return value is needed, use `ws.proc()` instead, or you'll be unnecessarily sending an empty response back.
### `ws.proc(method, params)`
When called from the client, will invoke the given handler `method` on the server, and vice-versa.
Does not receive a return value, unlike `ws.func()`.
### `await ws.func(method, params, timeout)`
When called from the client, will invoke the given handler `method` on the server, and vice-versa.
An asynchronous function which returns a `Promise` which you should `await`. Once the other side of the WebSocket connection has a return value from the function, it will be sent back over the WebSocket connection and the `Promise` will resolve.
The `Promise` will `reject()` if a response isn't received within `timeout` (default: `30_000`ms or whatever is set in `wsFunc.config.timeout`).
Use `ws.proc()` as opposed to `ws.func()` when your method has no return value to prevent an unnecessary message being exchanged.
### `ws.publishProc(room, method, params)`
Only available from servers, uses Bun's `ws.publish()` method to to invoke `method` on all clients subscribed to the `room` channel (other than `ws` themselves). Does not receive a return value.
### `server.publishProc(room, method, params)`
As per `ws.publishProc()`, but for all subscribers to `room` (while `ws.publishProc()` doesn't send to self). `server` is a Bun server object.
## About
Provides an incomplete implementation of [JSON-RPC 2.0](https://www.jsonrpc.org/specification) - exchanges JSON messages over WebSocket connections with property `{ jsonrpc: '2.0' }`.
While HTTP provides _requests_ and associated _responses_, WebSockets only provides _messages_. This modules gives each message a unique `id` so when a message is sent back as a response (to provide a `return` value for a function) it can be associated with the outgoing message.