@fribble/headless-websocket-core
v0.1.0
Published
A headless WebSocket core library with reconnection, heartbeat, and message queue support
Maintainers
Readme
@airise/headless-websocket-core
A headless WebSocket client library with reconnection, heartbeat, and message queue support. Built with TypeScript, zero runtime dependencies.
Features
- Adapter Pattern - Works with any WebSocket implementation (browser, Node.js, mocks)
- Auto Reconnection - Exponential backoff reconnection with configurable attempts
- Heartbeat - Configurable ping/pong with timeout detection
- Message Queue - Buffers messages while disconnected, auto-flushes on reconnect
- State Machine - Strict state transitions with change events
- Event-Driven - Typed event system for all lifecycle events
- Send with Ack - Request-response pattern with timeout support
- Dual Format - ESM + CJS output with TypeScript declarations
Installation
npm install @airise/headless-websocket-coreQuick Start
import { HeadlessWebSocket } from '@airise/headless-websocket-core';
// 1. Create a WebSocket adapter
const adapter = {
// ... implement WebSocketAdapter interface
};
// 2. Create the client
const ws = new HeadlessWebSocket({
url: 'ws://localhost:8080',
adapter,
reconnect: { enabled: true, maxAttempts: 5 },
heartbeat: { enabled: true, interval: 30000, timeout: 5000 },
});
// 3. Listen for events
ws.on('open', () => console.log('Connected'));
ws.on('message', ({ data }) => console.log('Received:', data));
ws.on('close', ({ code, reason }) => console.log('Closed:', code, reason));
ws.on('error', (err) => console.error('Error:', err));
// 4. Connect
ws.connect();
// 5. Send messages
ws.send({ type: 'hello', payload: 'world' });
// 6. Send with acknowledgment
const response = await ws.sendWithAck({ type: 'request' }, { timeout: 5000 });
// 7. Disconnect
ws.disconnect();WebSocketAdapter
You must provide a WebSocketAdapter implementation that wraps your preferred WebSocket:
import type { WebSocketAdapter } from '@airise/headless-websocket-core';
const browserAdapter: WebSocketAdapter = {
private socket: WebSocket | null = null;
connect(url: string) {
this.socket = new WebSocket(url);
},
send(data: string) {
this.socket?.send(data);
},
close(code?: number, reason?: string) {
this.socket?.close(code, reason);
},
onOpen(callback: () => void) {
// bind to your WebSocket instance
},
onClose(callback: (code: number, reason: string) => void) {
// bind to your WebSocket instance
},
onError(callback: (error: Error) => void) {
// bind to your WebSocket instance
},
onMessage(callback: (data: string) => void) {
// bind to your WebSocket instance
},
};Configuration
HeadlessWSConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | required | WebSocket server URL |
| adapter | WebSocketAdapter | required | WebSocket adapter implementation |
| reconnect | ReconnectConfig | { enabled: false } | Reconnection settings |
| heartbeat | HeartbeatConfig | { enabled: false } | Heartbeat settings |
| queue | QueueConfig | { enabled: true } | Message queue settings |
ReconnectConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | false | Enable auto reconnection |
| maxAttempts | number | 0 (infinite) | Max reconnect attempts |
| initialDelay | number | 1000 | Initial delay in ms |
| maxDelay | number | 30000 | Max delay in ms |
| backoffMultiplier | number | 2 | Backoff multiplier |
HeartbeatConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | false | Enable heartbeat |
| interval | number | 30000 | Ping interval in ms |
| timeout | number | 5000 | Pong timeout in ms |
| pingMessage | string \| (() => string) | 'ping' | Custom ping message |
| isPong | (data: unknown) => boolean | Built-in check | Custom pong detection |
QueueConfig
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | true | Enable message queue |
| maxSize | number | 100 | Max queue size |
API
Methods
| Method | Description |
|--------|-------------|
| connect() | Establish WebSocket connection |
| disconnect(code?, reason?) | Close the connection |
| send(data) | Send data (queues if disconnected) |
| sendWithAck(data, options?) | Send data and wait for ack response |
| on(event, handler) | Subscribe to an event, returns unsubscribe function |
| off(event, handler) | Unsubscribe from an event |
| once(event, handler) | Subscribe to an event once |
| getState() | Get current connection state |
| isConnected() | Check if connected |
| destroy() | Destroy instance and release all resources |
Connection States
IDLE -> CONNECTING -> CONNECTED -> DISCONNECTING -> DISCONNECTED
| |
+-----> RECONNECTING ----------+
|
v
ERROREvents
| Event | Payload | Description |
|-------|---------|-------------|
| open | - | Connection established |
| close | { code, reason } | Connection closed |
| error | Error | Error occurred |
| message | { data } | Message received |
| stateChange | { from, to } | Connection state changed |
| reconnecting | { attempt, maxAttempts } | Reconnection attempt started |
| reconnected | - | Successfully reconnected |
| reconnectFailed | - | All reconnection attempts failed |
| heartbeatTimeout | { failures } | Heartbeat pong not received |
| messageSent | { id, data } | Message sent |
| messageAcked | { id, response } | Ack response received |
| queueChanged | { pending, sent } | Queue status changed |
Error Types
| Error | Description |
|-------|-------------|
| HeadlessWSError | Base error for all library errors |
| ConnectionError | Connection-related errors |
| TimeoutError | Operation timed out |
| QueueOverflowError | Message queue exceeded max size |
| InvalidStateError | Invalid state transition or operation |
Utilities
import { encode, decode, calculateBackoff, generateMessageId } from '@airise/headless-websocket-core';
// JSON encode/decode with error handling
const json = encode({ foo: 'bar' });
const data = decode(json);
// Exponential backoff
const delay = calculateBackoff({ attempt: 3, initialDelay: 1000, multiplier: 2, maxDelay: 30000 });
// Generate unique message ID
const id = generateMessageId(); // "msg_1708100000000_a1b2c3d4"Build
npm run buildOutput format: ESM (.mjs) + CJS (.js) with TypeScript declarations (.d.ts).
Bundle size: < 6KB gzipped.
Test
npm test # Run all tests
npm run test:coverage # Run with coverage reportLicense
MIT
