A resilient WebSocket client that holds on when the connection stretches.
By Harry Ashton
Tether is a zero-dependency TypeScript WebSocket client built for production failure modes: dropped connections, slow networks, forced disconnects, and auth expiry — without falling over.
Like a tether, it stays linked. Messages queue while offline, replay in order on reconnect, and back off with full jitter until the socket is open again.
A control-panel UI exercises every resilience feature in real time — reconnection, backoff, queue drain, bufferedAmount backpressure, multiplexing, and auth refresh.
tether-demo-wheat.vercel.app — or run locally. Deployed on Vercel with WebSocket Functions (public beta).
| Reconnection | Full-jitter exponential backoff with live attempt + delay telemetry |
| Backpressure | Queue flush pauses on real socket.bufferedAmount — not a synthetic rate limiter |
| Multiplexing | Multiple logical channels over one physical WebSocket |
| Auth refresh | Token rotation over the existing open socket — no disconnect |
Clone the monorepo and install:
git clone git@github.com:thehashton/Tether.git
cd Tether
pnpm install
pnpm build
pnpm testpackages/tether-ws → client library (npm: tether-ws)
apps/demo → Next.js demo + WebSocket server
npm install tether-wsimport { TetherClient } from 'tether-ws';
const client = new TetherClient({
url: 'wss://example.com/ws',
auth: { getToken: () => fetch('/api/token').then((r) => r.text()) },
});
client.on('reconnecting', ({ attempt, delayMs }) => {
console.log(`retry #${attempt} in ${delayMs}ms`);
});
client.connect();
client.send({ hello: 'world' }, { channel: 'chat' });
client.subscribe('chat', (msg) => console.log(msg));Full API reference → packages/tether-ws/README.md
cd apps/demo
cp .env.local.example .env.local
pnpm devOpen http://localhost:3000.
| Command | What it does |
|---|---|
pnpm dev |
Standalone WebSocket server on :3001 + Next.js UI |
pnpm dev:vercel |
Vercel runtime with native WebSocket upgrade |
Try Kill connection → watch backoff + queue replay. Flood 500 messages → inbound backpressure counter climbs. Force token refresh → auth rotates without a close event.
- Import the repo and set Root Directory to
apps/demo - Ensure Fluid Compute is enabled (default on new projects)
- Deploy — the WebSocket route lives at
app/api/ws/route.tsviaexperimental_upgradeWebSocket()
MIT © Harry Ashton
