# Client

## Vanilla (`SockaSession`)

Outside React, construct **`SockaSession`** from **`@firtoz/socka/client`** with the same **`contract`** as the server and a **`url`** (or a prebuilt **`webSocket`**). **`wireFormat`** defaults to **`"json"`** and must match every server session that receives this connection.

| Option | Typical use |
|--------|-------------|
| **`contract`**, **`url`** / **`webSocket`** | Required pairing: open socket to your upgrade URL, or inject a socket for tests. |
| **`wireFormat`** | **`"json"`** (text frames) vs **`"msgpack"`** (binary); must match the **server** session config for this connection. |
| **`autoConnect: false`** | Defer opening until **`await session.connect()`** (or first **`send`**). |
| **`serializeJson` / `deserializeJson`** | Custom JSON handling for the **outer** frame in JSON mode (e.g. replacers). |
| **`onOpen` / `onClose` / `onError`** | Lifecycle and telemetry. |
| **`pushHandlers`** | Up-front subscriptions for contract **`pushes`** (same as **`session.subscribe.on`**). |
| **`reportError`** | Non-RPC client pipeline failures; defaults to **`console.error`**. |

Full list: **[Reference — Client configuration](./reference.md#client-configuration)**.

### Fire-and-forget

Calls that **omit** **`output`** resolve **`await session.send.*`** as soon as the request is sent; they do **not** wait for **`serverResponse`**. If the server returns **`serverError`**, handle it with **`reportError`** (see **[Reference — Errors](./reference.md#errors-and-observability)**) — there is no rejected promise for that call. Prefer **`output: z.void()`** when you still want **`await`** to track success or failure through the returned promise.

### Fire-and-forget observability

For **output-less** calls, the **`send`** method returns a **`Promise<void>`** that resolves when the **outbound request is queued**; it does **not** reject if the server later answers with **`serverError`**. Patterns like **`void send.sendCursor({ ... }).catch(() => undefined)`** therefore **do not** observe **server-side** handler failures, validation errors on the response path, or structured **`SockaError`** for that procedure.

- **Client** — pass **`reportError`** to **`SockaSession`** or **`useSockaSession`** / **`useSocka`** (and **`SockaSessionProvider`**, which forwards session options). Inspect **`event.kind`**; fire-and-forget server failures often surface as **`clientFireAndForgetRpcError`**. See **[Reference — Optional output (fire-and-forget)](./reference.md#optional-output-fire-and-forget)** and **[Reference — RPC handler errors](./reference.md#rpc-handler-errors)**.
- **Server** — use **`onHandlerError`** and **`onValidationError`** on **`SockaWebSocketSessionConfig`** / **`SockaDoSessionConfig`** for logs and metrics when a handler throws or inbound frames fail before your handler.

**Call names** — For literal `calls` objects, **`defineSocka`** rejects names that would make **`session.send`** Promise-like or clash with object shape (e.g. **`then`**, **`toString`**). If you use a wide **`Record<string, SockaProcedureDef>`**, TypeScript cannot apply that check; **`SockaSession`** still validates at construction (see **`RESERVED_SOCKA_PROCEDURE_NAMES`** in **`@firtoz/socka/core`**).

```ts
import { SockaSession } from "@firtoz/socka/client";
import { myContract } from "./contract";

const session = new SockaSession({ contract: myContract, url: "wss://example.com/ws" });
const rows = await session.send.list();
```

Use **`SockaWebSocketClient`** directly if you need **`onResponse` / `onServerError` / `onEvent`** frame hooks without **`SockaSession`**’s typed **`send`** / **`subscribe`**; most apps use **`SockaSession`**.

**Connection status** — **`SockaWebSocketClient`** and **`SockaSession`** expose **`status`** (`"idle" | "connecting" | "open" | "reconnecting" | "closed"`) and **`onStatusChange`** for UI (e.g. “Reconnecting…”). Same fields power the React hooks below.

## React

### `useSockaSession` — typed `send`

Use **`useSockaSession(contract, options, deps)`** when you want **`send.*`** RPC methods directly (same shape as **`session.send`**). **`ready`** flips to **`true`** after the socket opens; **`deps`** remount the connection when identity (e.g. room id) changes. Also returns **`status`**, **`reconnecting`**, and **`reconnectAttempt`** (see **`useSocka`**).

```ts
import { useSockaSession } from "@firtoz/socka/react";
import { myContract } from "./contract";

function App() {
  const { ready, send } = useSockaSession(myContract, { url: "ws://..." }, []);
  // After `ready`, call `send.list()` / `send.insert(...)` from effects or event handlers (not during render).
  return null;
}

// Binary frames — set the same `wireFormat` on the server session
useSockaSession(myContract, { url: "wss://...", wireFormat: "msgpack" }, []);
```

Optional **`pushHandlers`** in the second argument matches **`Partial<InferSockaPushHandlers<typeof myContract>>`** — see **[Pushes — Typing `pushHandlers`](./pushes.md#typing-pushhandlers)**.

### SSR and WebSocket URLs

`useSockaSession` and **`SockaSessionProvider`** need a real **`url`** for **`new WebSocket(url)`**. In **React Router SSR** (or any **SSR** tree), `window` is missing on the server, and derived **`ws:` / `wss:`** URLs must not run during **render** with a **fake** placeholder like `ws://127.0.0.1/...` (it can connect to the wrong place or open before you know the page origin).

**Recommended pattern:** In the route module, set **`const [url, setUrl] = useState<string | null>(null)`**, and in **`useEffect`** (browser-only), build a **`wss://` / `ws://`** URL from **`window.location`** (protocol, host, path to your Worker’s WebSocket route). If **`url === null`**, render a **loading** shell; only render a child component that calls **`useSockaSession(myContract, { url, pushHandlers? }, [url])`** when **`url`** is set. Keep **`url`** in the hook **`deps`** so identity changes re-open the right socket.

**SSR checklist:**

1. **Never** call **`useSockaSession`** with a placeholder URL during SSR — the hook connects in **`useEffect`** and will throw if **`url`** is missing at runtime.
2. **Parent gates the child** — render **`ClientOnly`** / **`url === null ? <Loading /> : <ChatWithSocket url={url} />`** so the hook runs only in the browser with a real URL.
3. **Build the URL in `useEffect`** — derive **`wss:`** from **`window.location`**, room id from route params, and optional auth query params there (not during render).
4. **Pass `[url]` in hook deps** — room or tenant changes should remount the connection.
5. **`SockaSessionProvider`** follows the same rules — wrap only after **`url`** is known.

**React + Durable Object** end-to-end wiring: **[React + Durable Objects](./react-durable-objects.md)**.

### `useSocka` — hold a `SockaSession` ref

Use **`useSocka(options, deps)`** when you need the full **`SockaSession`** (e.g. **`session.subscribe`**, **`session.client`**, **`waitForPush`**, or passing the session into non-React helpers). It returns **`{ ready, sessionRef, status, reconnecting, reconnectAttempt }`** — read **`sessionRef.current`** in effects or callbacks (it is **`null`** until the effect runs). Use **`reconnecting`** or **`status === "reconnecting"`** for banners; **`reconnectAttempt`** counts backoff attempts.

| | **`useSockaSession`** | **`useSocka`** |
|--|------------------------|----------------|
| **Returns** | **`{ ready, send, status, reconnecting, reconnectAttempt }`** | **`{ ready, sessionRef, status, reconnecting, reconnectAttempt }`** |
| **Best for** | Most React UIs that only call RPCs | Subscriptions, low-level client access, imperative APIs |

```tsx
import { useEffect } from "react";
import { useSocka } from "@firtoz/socka/react";
import { myContract } from "./contract";

function App() {
  const { ready, sessionRef } = useSocka({ contract: myContract, url: "ws://..." }, []);

  useEffect(() => {
    const s = sessionRef.current;
    if (!ready || !s) return;
    const onNotify = (p: { msg: string }) => console.log(p.msg);
    s.subscribe.on("notify", onNotify);
    return () => s.subscribe.off("notify", onNotify);
  }, [ready, sessionRef]);

  return null;
}
```

*(Example assumes your contract defines a **`notify`** push; use the real push names from **`myContract`**.)*

### `useSockaPresence` — snapshot + join/leave deltas

Call **`useSockaPresence(sessionRef, ready, options, deps)`** after **`useSocka`** / **`useSockaSession`**: it runs your **`snapshot`** RPC once, subscribes to **`joinPush`** / **`leavePush`**, and returns **`{ users, selfUserId, loading }`**. Pass the same **`deps`** as the connection when room identity changes.

### One WebSocket for the whole tree

If many components need **`send`**, avoid calling **`useSockaSession`** in each one (each call owns a connection). Mount a provider once and read the session from context:

```tsx
import { SockaSessionProvider, useSockaSessionContext } from "@firtoz/socka/react";
import { myContract } from "./contract";

function Layout({ roomId }: { roomId: string }) {
  return (
    <SockaSessionProvider
      contract={myContract}
      deps={[roomId]}
      url={`wss://example.com/ws/${roomId}`}
    >
      <Child />
    </SockaSessionProvider>
  );
}

function Child() {
  const { ready, send, status, reconnecting } = useSockaSessionContext(myContract);
  // ...
}
```

Use the **same `contract` reference** on the provider and in **`useSockaSessionContext`** (checked at runtime).

## Deferred WebSocket connect

Use **`autoConnect: false`** on **`SockaWebSocketClient`** / **`SockaSession`** when you want to open the socket later (e.g. after user action). Call **`await session.connect()`** or **`await client.connect()`** before calls; each **`send`** call also awaits connect when the socket is not open yet.

## Client lifecycle

Treat each **`SockaSession`** / **`SockaWebSocketClient`** as bound to **one** underlying **`WebSocket`**. When the socket closes, pending calls reject on that instance unless you opt into client-side reconnect (see **[Reconnection](./reconnection.md)**). For a deliberate room/url change, construct a **new** client (in React, remount **`useSockaSession`** / **`SockaSessionProvider`** when **`url`** or identity **`deps`** change). Use **`ready`** / **`waitForOpen()`** before assuming the connection is usable; use **`onClose`** / **`onError`** (or **`reportError`**) for telemetry.

## Pushes

Server push and client subscriptions are covered in **[Pushes](./pushes.md)**.
