# 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
