Skip to main content

Socket

The Socket module wraps a socket.io-client connection to the Ovok backend, handles auto-reconnect on network state changes (via @react-native-community/netinfo), and exposes the live Socket instance through React context.

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 goes through OvokClient (from @ovok/core), not this socket.

Exports

// from @ovok/native
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>

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

Props

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

type SocketProviderProps = React.PropsWithChildren<{
onError?: (error: Error) => void;
onConnectionError?: (error: Error) => void;
onPing?: (latency: number) => void;
onConnect?: () => void;
onDisconnect?: () => void;
onLogs?: (message: SocketLogPayload) => void;
}>;
PropWhen it fires
onConnectSocket transport opened and authenticated with the bearer token.
onDisconnectSocket transport closed (network change, manual disconnect, idle timeout).
onErrorerror event from the socket — typically protocol-level failures.
onConnectionErrorconnect_error event — the handshake or authentication failed. Both onError and onConnectionError flip internal state back to disconnected.
onPingHeartbeat round-trip latency in ms. Useful for surface diagnostics.
onLogsServer-pushed log line. SocketLogPayload = { message: string; level: 'info' | 'warn' | 'error' }.

SocketProvider reads client.getBaseUrl() + client.getAccessToken() from the OvokClient context, so it must be a descendant of OvokProvider. The connection URL is ${baseUrl}events.

Reconnection

SocketProvider registers a NetInfo listener: whenever the device reconnects to the network and the socket is not currently CONNECTED, it tears down the old socket and constructs a new one. When the network goes offline, it actively calls socket.disconnect() so the next reconnect attempt starts from a clean state. Default socket.io-client options: 5 reconnection attempts, 1s → 5s exponential delay.

useSocket()

Read the live socket + connection state from context.

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

const { socket, isConnected } = useSocket();

Throws if called outside <SocketProvider>. Returned socket is null until the first connect event resolves; gate any usage on isConnected.

useAiEventListener()

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

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

const { subscribe } = useAiEventListener();

Use:

import { useAiEventListener } from "@ovok/native";

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

import { OvokProvider, OvokClient, ExpoClientStorage } from "@ovok/core";
import {
ThemeProvider as OvokThemeProvider,
SocketProvider,
DEFAULT_COLORS,
} from "@ovok/native";

const client = new OvokClient({
baseUrl: "https://api.ovok.com/",
storage: new ExpoClientStorage(),
});

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

  • 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.