Overview

ws(url, config?) creates a typed WebSocket connection with a reactive status atom, send, connect, and disconnect.

Subscribe to messages with onSocket(handler, [socket]) inside a component — handlers are removed automatically on unmount. Prefer the dedicated onSocket hook page for the subscribe-only API.

ws vs onSocket

API
Role
Notes
ws(url, config?) Connection Returns WsConnection — send, status, connect/disconnect.
onSocket(fn, sockets) Subscribe Component-scoped message handler; auto-cleanup.

WsConfig quick reference

import { ws } from 'mates';
import type { WsConfig } from 'mates';

const config: WsConfig = {
  reconnect: true,           // default true
  reconnectDelay: 1000,      // ms before first retry
  reconnectMaxDelay: 30_000, // backoff cap
  reconnectMaxAttempts: Infinity,
  protocols: ['json'],
  auth: () => ({ token: '…' }), // lazy query params each connect
  autoConnect: true,         // false → call connect() yourself
};

// Inside a component outer function only:
const socket = ws('wss://api.example.com/ws', config);
socket.send({ type: 'ping' });
socket.status(); // reactive atom
socket.disconnect();

Simulated message stream (offline)

Docs sandbox has no WebSocket server. This mirrors onSocket + status with onInterval.

import { html, atom, onInterval, renderApp } from 'mates';
// Offline demo — no WebSocket server in the docs sandbox.
// Real app: ws(url, config) + onSocket(handler, [sock]) in the outer function.

const LINES = [
'Server: connected',
'Server: heartbeat',
'Server: user joined',
'Server: payload ok',
];

const App = () => {
const messages = atom([]);
const status = atom('connected');
let i = 0;

onInterval(() => {
if (status() !== 'connected') return;
messages.set((prev) => [
...prev.slice(-5),
{ text: LINES[i % LINES.length], t: new Date().toLocaleTimeString() },
]);
i++;
}, 1200);

return () => html`
<div class="m-col m-gap">
<div class="m-flex m-gap-sm m-items-center">
<span class="tag ${status() === 'connected' ? 'tag-success' : 'tag-danger'}">
${status()}
</span>
<button class="btn-ghost" @click=${() =>
status.set(status() === 'connected' ? 'disconnected' : 'connected')}>
Toggle
</button>
</div>

Status atom pattern

chat.status() cycles connecting → connected → disconnected → error in a real connection.

import { html, atom, renderApp } from 'mates';
// Mirrors chat.status() — the only built-in reactive field on WsConnection.
// Accumulate messages yourself with onSocket + an atom.

const App = () => {
const status = atom('connecting');
const cycle = () => {
const order = ['connecting', 'connected', 'disconnected', 'error'];
const i = order.indexOf(status());
status.set(order[(i + 1) % order.length]);
};

return () => html`
<div class="m-col m-gap">
<p class="muted m-t-5">status=${status()}</p>
<button class="btn-primary" @click=${cycle}>Next status</button>
<p class="hint">Real: chat.status() is an atom updated by ws().</p>
</div>
`;
};
renderApp(App, document.getElementById('app'));