wscall-client
v0.4.0
Published
High-performance JavaScript client SDK for the WSCALL WebSocket RPC framework
Maintainers
Readme
wscall-client
High-performance JavaScript client SDK for the WSCALL WebSocket RPC framework.
Works in both Node.js (≥18) and modern browsers (native WebSocket + Web Crypto).
Features
- Protocol v3 binary codec — compact 5-byte frame header, single-letter JSON keys, raw binary attachments (zero Base64 overhead).
- Encryption — ChaCha20-Poly1305 and AES-256-GCM at frame level; no TLS required.
- ECDH key agreement — X25519 dynamic per-connection session key (forward secrecy), no pre-shared key needed.
- Connection authentication — submit a credential (e.g. token) during the handshake; rejected connections fail fast at
connecttime (requires server ≥ 0.6.0). - Bidirectional messaging — request/response RPC + server-pushed events with ACK correlation.
- Automatic reconnect — exponential backoff with jitter; sticky failover across multiple server URLs.
- Zero-copy attachments — send and receive binary files inline with RPC params or event data.
Installation
npm install wscall-clientFor Node.js environments you also need a WebSocket implementation:
npm install wsBrowsers provide WebSocket natively — no extra dependency.
Quick Start
ECDH mode (recommended — no pre-shared key)
import { WscallClient, WscallClientConfig } from 'wscall-client';
const client = await WscallClient.connect(
'ws://127.0.0.1:9001/socket',
WscallClientConfig.ecdh()
);
// RPC call (resolves directly with the response data)
const res = await client.call('system.echo', { message: 'hello' });
console.log(res); // { message: 'hello' }
// Subscribe to server events
client.onEvent('chat.message', (event) => {
console.log('New message:', event.data);
return { received: true }; // sent back as ACK receipt
});
// Emit an event (resolves with the ACK receipt)
const receipt = await client.sendEvent('chat.message', { text: 'hi!' });
console.log('Server acknowledged:', receipt);
client.close();PSK mode (pre-shared key)
import { WscallClient, WscallClientConfig } from 'wscall-client';
const key = new Uint8Array(32).fill(0x42); // 32-byte shared key
const config = WscallClientConfig.pskChaCha20(key);
const client = await WscallClient.connect('ws://127.0.0.1:9001/socket', config);Failover across multiple servers
const config = WscallClientConfig.ecdh()
.withFailoverUrl('ws://backup1:9001/socket')
.withFailoverUrl('ws://backup2:9001/socket');
const client = await WscallClient.connect('ws://primary:9001/socket', config);On disconnect the client iterates through [primary, ...failover] starting from the last successfully connected URL (sticky failover).
Authenticated connection (server ≥ 0.6.0)
When the server registers an auth_handler, submit your credential at connect time. The credential is sent as an encrypted frame right after key agreement — no tokens in URLs or HTTP headers.
const client = await WscallClient.connect(
'ws://127.0.0.1:9001/socket',
WscallClientConfig.ecdh().withCredential('my-token')
);If the server rejects the credential, connect rejects with a ClientError whose code is the server error code (e.g. unauthorized) and whose details carries the full error payload.
File attachments
import { createTextAttachment, attachmentRef } from 'wscall-client';
const att = createTextAttachment('f1', 'hello.txt', 'text/plain', 'Hello, world!');
const res = await client.call(
'files.inspect',
{ file: attachmentRef('f1') },
[att]
);API Overview
WscallClient.connect(url, config?) → Promise<WscallClient>
Static factory. Connects to a WSCALL server and returns a ready client.
WscallClientConfig
| Factory / Builder | Description |
|-------------------|-------------|
| WscallClientConfig.ecdh() | ECDH + ChaCha20 + auto-reconnect (default) |
| WscallClientConfig.plaintext() | No encryption |
| WscallClientConfig.pskChaCha20(key) | PSK with ChaCha20-Poly1305 |
| WscallClientConfig.pskAes256(key) | PSK with AES-256-GCM |
| .withAutoReconnect(bool) | Enable/disable auto-reconnect |
| .withTimeout(ms) | Default request timeout (default 10s). Raise it for long-running workloads, or pass a per-call options.timeout |
| .withHeartbeatInterval(ms) | Keep-alive ping interval (default 15s) |
| .withIdleTimeout(ms) | Inbound idle timeout (default 45s) |
| .withReconnectBaseDelay(ms) | Reconnect backoff base delay (default 3s) |
| .withReconnectMaxDelay(ms) | Reconnect backoff upper bound (default 30s) |
| .withAuthTimeout(ms) | Auth handshake timeout (default 10s) |
| .withMetadata(obj) | Default metadata sent with requests |
| .withFailoverUrl(url) | Append a failover URL |
| .withFailoverUrls(urls) | Set all failover URLs |
| .withCredential(credential) | Credential (token) for the auth handshake |
client
| Method | Description |
|--------|-------------|
| call(route, params?, attachments?, opts?) | RPC call → Promise<data> (response data directly) |
| sendEvent(name, data?, attachments?, opts?) | Emit event → Promise<receipt> (ACK receipt) |
| onEvent(name, handler) | Subscribe to server events |
| offEvent(name, handler?) | Unsubscribe |
| onConnected(handler) | Connection established hook |
| onDisconnected(handler) | Disconnection hook |
| close() | Graceful shutdown (stops reconnect) |
Reconnect behavior
- Unexpected disconnects trigger automatic reconnect (default: enabled).
- First retry after 3 s (
reconnectBaseDelayMs), then exponential backoff (×2), capped at 30 s (reconnectMaxDelayMs). - Random sub-second jitter prevents thundering-herd storms.
- With
failoverUrls, each cycle tries all URLs before applying backoff. close()stops all reconnect attempts.
Protocol Compatibility
This SDK implements WSCALL Protocol v3 (5-byte frame header, connection-level encryption). It requires a server running wscall ≥ 0.5.1; the credential handshake (withCredential) requires server ≥ 0.6.0.
| SDK version | Protocol | Server compatibility | |-------------|----------|---------------------| | 0.4.x | v3 | wscall ≥ 0.5.1 (auth: ≥ 0.6.0; timing knobs pair best with server ≥ 0.7.0) | | 0.3.x | v3 | wscall ≥ 0.5.1 (auth: ≥ 0.6.0) | | 0.2.x | v2 | wscall 0.4.x – 0.5.0 |
Browser Usage
<script type="module">
import { WscallClient, WscallClientConfig } from './node_modules/wscall-client/src/index.js';
const client = await WscallClient.connect('ws://localhost:9001/socket', WscallClientConfig.ecdh());
const res = await client.call('system.echo', { msg: 'from browser' });
console.log(res);
</script>In browsers the ws package is not needed — the native WebSocket global is used automatically.
Dependencies
| Package | Purpose |
|---------|---------|
| @noble/ciphers | ChaCha20-Poly1305 AEAD (pure JS, audited) |
| @noble/curves | X25519 ECDH key agreement (pure JS, audited) |
| ws (optional peer) | WebSocket implementation for Node.js |
License
MIT
