# Socket

Canonical page: https://docs.ovok.com/native-sdk/guide/components/socket

The Socket module wraps a `socket.io-client` connection to the Ovok backend and exposes
the live `Socket` instance through React context. It connects when the native next
client has a session, disconnects on sign-out, and reconnects the same socket when
network access returns.

This is the channel real-time SDK features (AI streaming, server-pushed logs, presence) ride on. Most apps only need it if they consume one of those features; REST traffic uses the native `Ovok` client.

## Exports [#exports]

```ts
// from @ovok/native/socket
export { SocketProvider } from "./components/socket-provider";
export { SocketContext } from "./contexts/socket-context";
export type { SocketContextType } from "./types/socket-context-type";
export type { SocketLogPayload } from "./types/socket-log-payload";
export { useAiEventListener } from "./hooks/use-ai-event-listener";
export { useSocket } from "./hooks/use-socket";
```

Source: `src/modules/socket/index.ts`.

## `<SocketProvider>` [#socketprovider]

Wrap your app (inside `OvokProvider` so the client + access token are available) to open the socket connection.

### Props [#props]

```ts
import type { SocketLogPayload } from "@ovok/native/socket";

type SocketProviderProps = React.PropsWithChildren<{
  onError?: (error: Error) => void;
  onConnectionError?: (error: Error) => void;
  onPing?: (latency: number) => void;
  onConnect?: () => void;
  onDisconnect?: () => void;
  onLogs?: (message: SocketLogPayload) => void;
}>;
```

| Prop | When it fires |
|---|---|
| `onConnect` | Socket transport opened and authenticated with the bearer token. |
| `onDisconnect` | Socket transport closed (network change, manual disconnect, idle timeout). |
| `onError` | `error` event from the socket — typically protocol-level failures. |
| `onConnectionError` | `connect_error` event — the handshake or authentication failed. Both `onError` and `onConnectionError` flip internal state back to `disconnected`. |
| `onPing` | Socket.IO `ping` event latency/value supplied by the client transport. |
| `onLogs` | Server-pushed log line. `SocketLogPayload = { message: string; level: 'info' \| 'warn' \| 'error' }`. |

`SocketProvider` reads the native client's `baseUrl` and active session access token from `OvokProvider`, so it must be a descendant of that provider. The connection URL is `${baseUrl}events`.

### Reconnection [#reconnection]

`SocketProvider` registers a `NetInfo` listener. When the network goes offline it calls
`socket.disconnect()`. When the network returns it reconnects that socket with its
listeners intact. An account switch closes the previous socket before connecting with
the new token. Default `socket.io-client` options are 5 reconnection attempts with
1s → 5s delay.

## `useSocket()` [#usesocket]

Read the live socket + connection state from context.

```ts
type SocketContextType = {
  socket: ReturnType<typeof io> | null;
  isConnected: boolean;
};

const { socket, isConnected } = useSocket();
```

Throws if called outside `<SocketProvider>`. Returned `socket` is `null` without a session; gate connected operations on `isConnected`.

## `useAiEventListener()` [#useaieventlistener]

Subscribe to AI events streamed over the socket (currently the `/ai/session/message` channel).

```ts
type MessageEvent = {
  content: string;
  mode: "user" | "assistant";
  reference: string;
  sender: string;
  sentAt: string;
  session: string;
};

const { subscribe } = useAiEventListener();
```

Use:

```tsx
import { useAiEventListener } from "@ovok/native/socket";

function ChatView() {
  const { subscribe } = useAiEventListener();
  const [messages, setMessages] = React.useState<MessageEvent[]>([]);

  React.useEffect(() => {
    const unsubscribe = subscribe("message", (event) => {
      setMessages((prev) => [...prev, event as MessageEvent]);
    });
    return () => unsubscribe?.();
  }, [subscribe]);

  return <ChatList messages={messages} />;
}
```

`subscribe(type, callback)` returns an unsubscribe function. Multiple subscribers per event type are supported; each receives every event independently.

## Full integration example [#full-integration-example]

```tsx
import { OvokProvider } from "@ovok/core/next/react";
import {
  ThemeProvider as OvokThemeProvider,
  DEFAULT_COLORS,
} from "@ovok/native";
import { Ovok } from "@ovok/native/next";
import { SocketProvider } from "@ovok/native/socket";
import * as SecureStore from "expo-secure-store";

const client = new Ovok({
  baseUrl: "https://api.ovok.com/",
  tenantCode: "your-tenant",
  secureStore: SecureStore,
});

function App() {
  return (
    <OvokThemeProvider theme={{ colors: DEFAULT_COLORS, dark: false }}>
      <OvokProvider client={client}>
        <SocketProvider
          onConnect={() => console.log("socket up")}
          onDisconnect={() => console.log("socket down")}
          onConnectionError={(e) => console.error("handshake failed", e)}
          onLogs={(payload) => console.log(`[${payload.level}]`, payload.message)}
        >
          <YourAppRoot />
        </SocketProvider>
      </OvokProvider>
    </OvokThemeProvider>
  );
}
```

## When to mount it [#when-to-mount-it]

- Mount **once at the root**, inside `OvokProvider`. Multiple `SocketProvider` instances create multiple sockets, which counts against the backend's per-user connection limit.
- Mount **only if** you consume AI events, server-pushed logs, or another socket-only feature. REST traffic via `useFhirSearch` / `client.create` does not need a socket.
- Mount **before** any consumer of `useSocket` or `useAiEventListener` in the tree — these throw if called outside.

## Related [#related]

- [OvokProvider documentation](https://docs.ovok.com/) — the FHIR client + auth wrapper this depends on
