One script tag. Zero dependencies. Works with any backend.
wskit.js is a standalone, universal WebSocket client that handles the boilerplate of working with WebSockets: auto-reconnect, message queuing, channel-based routing, request/response with Promises, heartbeat keep-alive, and automatic JSON serialization.
Exponential backoff from 1s up to 30s. Configurable max attempts.
Buffers sends while disconnected and flushes automatically on reconnect.
Route messages by type. Subscribe, unsubscribe, and handle with ease.
Send a message and await a matching response. Built-in timeout.
Configurable keep-alive ping to detect stale connections.
Automatically parse incoming and stringify outgoing JSON messages.
npm install wskit-client
import WSKit from 'wskit-client';
const WSKit = require('wskit-client');
<script src="https://unpkg.com/wskit-client/wskit.js"></script>
<script src="https://rte.whitneys.co/wskit.js"></script>
<script src="https://unpkg.com/wskit-client/wskit.js"></script>
<script>
const ws = WSKit.connect('wss://yourserver.com/ws', {
onOpen: () => console.log('Connected'),
onMessage: (msg) => console.log('Received:', msg),
onClose: () => console.log('Disconnected'),
});
ws.send({ type: 'chat', text: 'Hello!' });
</script>
That's it. WSKit connects, auto-serializes JSON, and starts reconnecting if the connection drops.
Route incoming messages by their type field (configurable via typeField):
// Subscribe — returns an unsubscribe function
const off = ws.on('chat', (msg) => {
console.log(msg.user + ':', msg.text);
});
// Unsubscribe later
off();
// Or unsubscribe by type
ws.off('chat');
You can subscribe multiple handlers to the same type. The global onMessage callback still fires for every message regardless of channel subscriptions.
ws.on('chat', (msg) => {
appendMessage(msg.user, msg.text);
});
ws.on('notification', (msg) => {
showToast(msg.title, msg.body);
});
ws.on('presence', (msg) => {
updateUserList(msg.users);
});
Send a message and wait for a matching response using Promises:
const user = await ws.request({ type: 'getUser', id: 123 });
console.log(user.name); // "John"
WSKit auto-generates a unique _id field on the outgoing message. Your server must echo the same _id in its response:
// Server-side (Node.js)
socket.on('message', (raw) => {
const msg = JSON.parse(raw);
if (msg.type === 'getUser') {
socket.send(JSON.stringify({
_id: msg._id, // echo back the _id
name: 'John',
email: 'john@example.com',
}));
}
});
// Override the default 10s timeout
const result = await ws.request({ type: 'slowQuery', q: 'data' }, 30000);
_id field name is configurable via the idField option.
Messages sent while disconnected are buffered and automatically flushed when the connection is restored:
const ws = WSKit.connect(url, {
queueWhileDisconnected: true, // default
maxQueueSize: 100, // default
});
// These will be queued if not yet connected
ws.send({ type: 'init', token: 'abc' });
ws.send({ type: 'subscribe', channel: 'updates' });
// Check queue size
console.log(ws.queueSize); // 2
// Clear queue if needed
ws.clearQueue();
ws.send() returns true if the message was sent immediately, or false if it was queued. When the queue is full, new messages are dropped.
Pass an options object as the second argument to WSKit.connect():
const ws = WSKit.connect('wss://yourserver.com/ws', {
// Reconnection
reconnect: true, // auto-reconnect (default: true)
reconnectBaseMs: 1000, // initial delay (default: 1000)
reconnectMaxMs: 30000, // max delay (default: 30000)
maxReconnectAttempts: 0, // 0 = unlimited (default: 0)
// Heartbeat
heartbeatMs: 30000, // ping interval (default: 30000)
heartbeatMessage: { type: 'ping' },
// Message queue
queueWhileDisconnected: true, // buffer sends (default: true)
maxQueueSize: 100, // max queue size (default: 100)
// JSON
autoJSON: true, // auto parse/stringify (default: true)
typeField: 'type', // channel routing field (default: 'type')
// Request/response
requestTimeout: 10000, // timeout in ms (default: 10000)
idField: '_id', // matching field (default: '_id')
// Debug
debug: false, // console logging (default: false)
// Callbacks
onOpen: () => {},
onClose: (e) => {},
onError: (e) => {},
onMessage: (data) => {},
onReconnect: (attempt) => {},
onStateChange: (newState, prevState) => {},
});
| Option | Type | Default | Description |
|---|---|---|---|
reconnect | Boolean | true | Auto-reconnect on disconnect |
reconnectBaseMs | Number | 1000 | Initial reconnect delay in ms |
reconnectMaxMs | Number | 30000 | Max reconnect delay in ms |
maxReconnectAttempts | Number | 0 | Max attempts (0 = unlimited) |
heartbeatMs | Number | 30000 | Ping interval in ms (0 to disable) |
heartbeatMessage | Any | { type: "ping" } | Heartbeat payload |
queueWhileDisconnected | Boolean | true | Buffer sends while disconnected |
maxQueueSize | Number | 100 | Max queued messages |
autoJSON | Boolean | true | Auto parse/stringify JSON |
typeField | String | "type" | Field for channel routing |
requestTimeout | Number | 10000 | Request/response timeout in ms |
idField | String | "_id" | Field for request/response matching |
debug | Boolean | false | Enable verbose console logging |
| Callback | Arguments | Description |
|---|---|---|
onOpen | (none) | WebSocket connected |
onClose | CloseEvent | WebSocket closed |
onError | Event | Error | WebSocket error |
onMessage | data | Every incoming message (parsed if autoJSON) |
onReconnect | attempt | Before each reconnect attempt |
onStateChange | newState, prevState | Connection state changed |
WSKit.connect() returns a connection object:
| Method / Property | Description |
|---|---|
ws.send(data) | Send data (auto-JSON if enabled). Returns true if sent, false if queued. |
ws.request(data, timeout?) | Send and wait for a matching response. Returns a Promise. |
ws.on(type, handler) | Subscribe to a message type. Returns an unsubscribe function. |
ws.off(type, handler?) | Unsubscribe from a type. Omit handler to remove all handlers for that type. |
ws.disconnect() | Close connection and stop reconnecting. Rejects all pending requests. |
ws.reconnect() | Manually reconnect. Resets attempt counter. |
ws.clearQueue() | Clear the message queue. |
ws.state | "connecting", "open", "closed", or "reconnecting" |
ws.queueSize | Number of messages in the queue |
ws.socket | The raw WebSocket instance |
ws.url | The WebSocket URL |
ws.reconnectAttempts | Current reconnect attempt count |
WSKit uses exponential backoff for reconnection:
reconnectMaxMs (default 30s)On successful reconnect, the message queue is flushed automatically. Set maxReconnectAttempts to limit retries:
const ws = WSKit.connect(url, {
reconnect: true,
reconnectBaseMs: 500,
reconnectMaxMs: 15000,
maxReconnectAttempts: 10, // give up after 10 attempts
onReconnect: (attempt) => {
console.log('Reconnecting... attempt', attempt);
},
onStateChange: (state) => {
document.getElementById('status').textContent = state;
},
});
WSKit sends a configurable ping message at regular intervals to keep the connection alive and detect stale connections:
const ws = WSKit.connect(url, {
heartbeatMs: 30000, // every 30 seconds (default)
heartbeatMessage: { type: 'ping' }, // payload (default)
});
// Disable heartbeat
const ws2 = WSKit.connect(url, {
heartbeatMs: 0, // disabled
});
Your server can respond with a pong or simply ignore the ping. The heartbeat stops when disconnected and restarts on reconnect.
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (socket) => {
socket.on('message', (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case 'chat':
// Broadcast to all clients
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(JSON.stringify(msg));
}
});
break;
case 'getUser':
// Request/response — echo back _id
socket.send(JSON.stringify({
_id: msg._id,
name: 'John',
email: 'john@example.com',
}));
break;
case 'ping':
socket.send(JSON.stringify({ type: 'pong' }));
break;
}
});
});
import asyncio, json, websockets
connected = set()
async def handler(ws):
connected.add(ws)
try:
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "chat":
for client in connected:
if client != ws:
await client.send(json.dumps(msg))
elif msg.get("type") == "getUser":
await ws.send(json.dumps({
"_id": msg["_id"],
"name": "John",
"email": "john@example.com",
}))
elif msg.get("type") == "ping":
await ws.send(json.dumps({"type": "pong"}))
finally:
connected.discard(ws)
asyncio.run(websockets.serve(handler, "0.0.0.0", 8080))
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
func handler(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
for {
_, raw, err := conn.ReadMessage()
if err != nil { break }
var msg map[string]interface{}
json.Unmarshal(raw, &msg)
switch msg["type"] {
case "getUser":
resp, _ := json.Marshal(map[string]interface{}{
"_id": msg["_id"],
"name": "John",
"email": "john@example.com",
})
conn.WriteMessage(websocket.TextMessage, resp)
case "ping":
resp, _ := json.Marshal(map[string]string{"type": "pong"})
conn.WriteMessage(websocket.TextMessage, resp)
}
}
}
func main() {
http.HandleFunc("/ws", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
<?php
// composer require ratchet/pawl react/event-loop
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
require __DIR__ . '/vendor/autoload.php';
class Handler implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
}
public function onMessage(ConnectionInterface $from, $raw) {
$msg = json_decode($raw, true);
switch ($msg['type'] ?? '') {
case 'chat':
// Broadcast to all other clients
foreach ($this->clients as $client) {
if ($client !== $from) {
$client->send(json_encode($msg));
}
}
break;
case 'getUser':
// Request/response — echo back _id
$from->send(json_encode([
'_id' => $msg['_id'],
'name' => 'John',
'email' => 'john@example.com',
]));
break;
case 'ping':
$from->send(json_encode(['type' => 'pong']));
break;
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
}
$server = IoServer::factory(
new HttpServer(new WsServer(new Handler())),
8080
);
$server->run();
WSKit is available on npm as wskit-client.
npm install wskit-client