@rantingjs/socket
v0.1.0
Published
A WebSocket server for ranting apps, speaking the Pusher protocol: broadcasting to browsers without a hosted service
Maintainers
Readme
@rantingjs/socket
A WebSocket server for ranting apps. It speaks the Pusher protocol, so browsers connect with the Pusher JS client or
Echo, and the app broadcasts to it with the framework's pusher broadcaster: real-time events without a hosted service.
bun add @rantingjs/socket
bunx ranting install @rantingjs/socket --skip-add # SOCKET_APP_ID / KEY / SECRET, BROADCAST_CONNECTION=socket
bunx ranting socket:start # ranting dev starts it tooThe app broadcasts through the socket connection of config/broadcasting.ts (SOCKET_APP_ID, SOCKET_APP_KEY,
SOCKET_APP_SECRET, SOCKET_HOST, SOCKET_PORT, SOCKET_SCHEME). In the browser:
const pusher = new Pusher(SOCKET_APP_KEY, {
wsHost: 'localhost', wsPort: 8080, forceTLS: false, enabledTransports: ['ws', 'wss'], cluster: '',
channelAuthorization: { endpoint: '/broadcasting/auth', transport: 'ajax' },
});
pusher.subscribe('private-orders.1').bind('OrderShipped', (order) => { ... });Laravel Echo works too, configured as for Reverb (broadcaster: 'reverb'): the framework's README has the example.
What it serves
GET /app/<key>: the WebSocket.pusher:connection_establishedgives the socket id and the activity timeout;pusher:subscribe/unsubscribe,pusher:ping/pong.- Channels: public,
private-*andpresence-*(signed by the app's/broadcasting/auth), andcache-*(private-cache-*,presence-cache-*), which keep their last event for whoever subscribes next (pusher:cache_misswhen there is none). Presence channels send the members on subscribing, andmember_added/member_removedonce per user, not per tab. - Client events (
client-*) on private and presence channels, from members only (acceptClientEventsFrom:members,allornone); the sender does not get its own. - The HTTP API, signed as Pusher's libraries sign it:
POST /apps/<id>/events(withinfo),POST .../batch_events,GET .../channels(filter_by_prefix,info=user_count,subscription_count),GET .../channels/<name>,GET .../channels/<name>/users,GET .../connections,POST .../users/<id>/terminate_connections, andGET /up. - Upkeep: every
pruneEveryseconds (60), connections that did not answer the last ping get error 4201 and are dropped, then those quiet for longer thanpingIntervalare pinged. - Limits per app: allowed origins (
*.example.com),maxConnections(4004),maxMessageSize(closes with 1009), and a rate limit on messages (4301;terminateOnLimitcloses the connection).
Errors are pusher:error with Pusher's codes: 4001 unknown app, 4004 over quota, 4009 origin or signature refused,
4200 a malformed message, 4201 no pong, 4301 rate limit or client events off. Refusals in the 4000 range end the
connection, so the client does not reconnect into the same refusal.
Config
bunx ranting publish @rantingjs/socket copies src/config/socket.ts:
| Key | Default | |
|---|---|---|
| server.host, server.port | SOCKET_SERVER_HOST 0.0.0.0, SOCKET_SERVER_PORT 8080 | Where it listens (--host, --port). |
| server.path | SOCKET_SERVER_PATH, empty | A prefix for every route (--path /ws); the broadcaster's path follows it. |
| server.tls | SOCKET_TLS_CERT, SOCKET_TLS_KEY | Certificate and key files: wss:// and https://. |
| server.maxRequestSize | 10000 | Bytes an API request may carry (413). |
| server.pruneEvery | 60 | Seconds between prune-and-ping rounds. |
| server.scaling | off | enabled (SOCKET_SCALING_ENABLED), channel, the redis connection, timeout for counts. |
| apps[] | one app from SOCKET_APP_* | id, key, secret, allowedOrigins, pingInterval (60), activityTimeout (30), maxConnections, maxMessageSize (10000), acceptClientEventsFrom, rateLimiting. |
Several servers
Behind a load balancer, turn on server.scaling: every event, user termination and count goes through Redis pub/sub,
so a broadcast reaches subscribers on every server, presence members are counted once across servers, and the API
answers with totals. Servers that do not answer within timeout seconds are left out of a count.
Commands and events
socket:start [--host] [--port] [--path] [--debug]runs the server;--debugprints every message in and out. Ctrl+C closes every connection first.socket:restarttells running servers to close their connections and stop (checked every 5 seconds), for a supervisor to start the new code.
The server fires, through the app's events: MessageReceived and MessageSent (connection and message),
ChannelCreated, ChannelRemoved and ConnectionPruned. Only events something listens to are made.
new SocketServer({ apps, port, ... }).start() runs it without the CLI (tests, or another process manager);
server.broadcast(appId, channels, event, data) sends from the same process.
