wskit-client
v1.0.2
Published
Universal WebSocket client. Auto-reconnect, message queue, channels, request/response, heartbeat. Zero dependencies.
Maintainers
Readme
WSKit — Universal WebSocket Client
A standalone, universal WebSocket client that works with any backend. One script tag. Zero dependencies.
Install
npm
npm install wskit-clientCommonJS
const WSKit = require('wskit-client');ES Modules
import WSKit from 'wskit-client';Script Tag
<script src="https://unpkg.com/wskit-client/wskit.js"></script>Quick Start
const ws = WSKit.connect('wss://yourserver.com/ws', {
onOpen: () => console.log('Connected'),
onMessage: (msg) => console.log('Received:', msg),
});
ws.send({ type: 'chat', text: 'Hello!' });Features
| Feature | Description |
|---|---|
| Auto-Reconnect | Exponential backoff (1s → 2s → 4s → ... up to 30s) |
| Message Queue | Buffers sends while disconnected, flushes on reconnect |
| Channels | Subscribe to message types: ws.on('chat', handler) |
| Request/Response | ws.request(data) returns a Promise |
| Heartbeat | Configurable keep-alive ping |
| Auto-JSON | Auto parse/stringify JSON messages |
| Debug Mode | debug: true for verbose console logging |
| TypeScript | Full type declarations included |
Channels
Route messages by type:
// Subscribe — returns an unsubscribe function
const off = ws.on('chat', (msg) => {
console.log(msg.user + ':', msg.text);
});
// Unsubscribe
off();
// Or unsubscribe by type
ws.off('chat');Messages are routed by the type field by default (configurable via typeField).
Request/Response
Send a message and wait for a matching response:
const user = await ws.request({ type: 'getUser', id: 123 });
console.log(user.name);The server should include the same _id field in its response. The _id is auto-generated and matched.
// Server-side example (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: '[email protected]',
}));
}
});Message Queue
Messages sent while disconnected are buffered and flushed on reconnect:
const ws = WSKit.connect(url, {
queueWhileDisconnected: true, // default
maxQueueSize: 100, // default
});
// These will be queued if not connected yet
ws.send({ type: 'init', token: 'abc' });
ws.send({ type: 'subscribe', channel: 'updates' });
// Check queue
console.log(ws.queueSize); // 2
// Clear queue if needed
ws.clearQueue();Front-End Configuration
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) => {},
});API
| 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 matching response. Returns Promise. |
| ws.on(type, handler) | Subscribe to message type. Returns unsubscribe function. |
| ws.off(type, handler?) | Unsubscribe. Omit handler to remove all for that type. |
| ws.disconnect() | Close connection and stop reconnecting. |
| ws.reconnect() | Manually reconnect. |
| ws.clearQueue() | Clear the message queue. |
| ws.state | "connecting", "open", "closed", or "reconnecting" |
| ws.queueSize | Number of queued messages |
| ws.socket | Raw WebSocket instance |
| ws.url | The WebSocket URL |
| ws.reconnectAttempts | Current reconnect attempt count |
Backend Examples
Node.js (ws)
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: '[email protected]',
}));
break;
case 'ping':
socket.send(JSON.stringify({ type: 'pong' }));
break;
}
});
});Python (websockets)
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": "[email protected]",
}))
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))Go (gorilla/websocket)
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": "[email protected]",
})
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 (Ratchet)
<?php
// composer require cboden/ratchet
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' => '[email protected]',
]));
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();Related Packages
| Package | Description | |---|---| | rte-rich-text-editor | Core editor — lightweight, 33 toolbar controls | | rte-rich-text-editor-ws | WebSocket connector for RTE | | rte-rich-text-editor-bundle | RTE + WebSocket in one file | | rte-rich-text-editor-pro | Pro editor — 16 toolbar groups, AI, slash commands, mentions | | rte-rich-text-editor-pro-ws | RTEPro + WebSocket in one file | | wskit-client | Universal WebSocket client | | websocket-toolkit | Universal WebSocket client (alternate name) |
Website: rte.whitneys.co · GitHub: MIR-2025/rte
License
MIT — phpMyDEV, LLC
