UNPKG

bun-ws-router

Version:

Lightweight client/server WebSocket router for Bun with type-safe Zod/Valibot validation.

194 lines (151 loc) 4.74 kB
# Browser WebSocket Client Type-safe WebSocket client for browsers with schema-based message validation. ## Features - 🔒 **Type-safe messaging** – Full TypeScript inference from schema to handler - 🔄 **Auto-reconnection** – Exponential backoff with configurable jitter - 📦 **Message queueing** – Configurable offline buffering - 🔐 **Auth support** – Query param or WebSocket protocol attachment - ⏱️ **Request/response** – RPC-style messaging with correlation tracking - 🎯 **Multi-handler** – Register multiple handlers per message type ## Usage ### Basic Client ```typescript import { z } from "zod"; import { createMessageSchema } from "bun-ws-router/zod"; import { createClient } from "bun-ws-router/zod/client"; // ✅ Typed client // Create schemas (shared with server) const { messageSchema } = createMessageSchema(z); const Hello = messageSchema("HELLO", { name: z.string() }); const HelloOk = messageSchema("HELLO_OK", { text: z.string() }); // Create typed client (use /zod/client or /valibot/client for type inference) const client = createClient({ url: "wss://example.com/ws" }); // Register handlers with full type inference client.on(HelloOk, (msg) => { // ✅ msg.payload.text is typed as string (no manual type assertions needed) console.log("Server says:", msg.payload.text); }); // Connect and send await client.connect(); client.send(Hello, { name: "Alice" }); ``` > **Typed vs Generic Client**: Use `/zod/client` or `/valibot/client` for automatic type inference in handlers. The generic client at `/client` requires manual type assertions and is only needed for custom validators. ### Request/Response ```typescript // RPC-style request/response const reply = await client.request(Hello, { name: "Bob" }, HelloOk, { timeoutMs: 5000, }); console.log(reply.payload.text); ``` ### Auto-Connection ```typescript const client = createClient({ url: "wss://example.com/ws", autoConnect: true, // Auto-connect on first send/request }); // No explicit connect() needed client.send(Hello, { name: "Charlie" }); // Triggers connection ``` ### Authentication ```typescript // Query parameter (default) const client = createClient({ url: "wss://api.example.com/ws", auth: { getToken: () => localStorage.getItem("access_token"), attach: "query", // Appends ?access_token=... }, }); // WebSocket protocol header const client = createClient({ url: "wss://api.example.com/ws", auth: { getToken: () => localStorage.getItem("access_token"), attach: "protocol", // Uses Sec-WebSocket-Protocol header protocolPrefix: "bearer.", }, }); ``` ### Reconnection ```typescript const client = createClient({ url: "wss://api.example.com/ws", reconnect: { enabled: true, maxAttempts: Infinity, initialDelayMs: 300, maxDelayMs: 10_000, jitter: "full", // Prevents thundering herd }, }); ``` ### Message Queueing ```typescript const client = createClient({ url: "wss://api.example.com/ws", queue: "drop-newest", // or "drop-oldest" or "off" queueSize: 1000, }); // Messages are queued while offline const sent = client.send(Hello, { name: "David" }); if (!sent) { console.warn("Message dropped (offline or buffer full)"); } ``` ## API Reference See [client.md](../specs/client.md) for full API documentation. ## Type Safety The client enforces schema constraints at compile time: ```typescript // ✅ Type-safe payload client.send(Hello, { name: "Eve" }); // ❌ Type error client.send(Hello, { name: 123 }); // ✅ Required extended meta const RoomMsg = messageSchema( "CHAT", { text: z.string() }, { roomId: z.string() }, // Required meta ); client.send( RoomMsg, { text: "hi" }, { meta: { roomId: "general" }, // Required }, ); ``` ## Error Handling ```typescript import { TimeoutError, ServerError, ConnectionClosedError, } from "bun-ws-router/client"; try { const reply = await client.request(Hello, { name: "Frank" }, HelloOk); } catch (error) { if (error instanceof TimeoutError) { console.warn(`Timeout after ${error.timeoutMs}ms`); } else if (error instanceof ServerError) { console.error(`Server error: ${error.code}`, error.context); } else if (error instanceof ConnectionClosedError) { console.warn("Connection closed before reply"); } } ``` ## State Management ```typescript // Monitor connection state client.onState((state) => { console.log("State:", state); // "closed" | "connecting" | "open" | "closing" | "reconnecting" }); // Wait for connection await client.onceOpen(); // Check current state console.log(client.state); // Check selected protocol console.log(client.protocol); // Selected WebSocket subprotocol ``` ## License MIT