Skip to main content

Socket

The Socket module wraps a socket.io-client connection to the Ovok backend and exposes the live Socket instance through React context. It observes network state, but the current implementation does not re-register listeners on the replacement socket after an online transition. The new socket can therefore have no handlers and isConnected can remain stale. Mount one provider and treat reconnection as best effort.

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/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>​

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

Props​

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;
}>;
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.
onPingSocket.IO ping event latency/value supplied by the client transport.
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. When the network goes offline it calls socket.disconnect(). When the network returns it initializes another socket if the current state is not CONNECTED; the replacement currently does not receive the listener set, so apps should mount one provider and avoid relying on repeated offline/online cycles as a full socket reset. Default socket.io-client options are 5 reconnection attempts with 1s → 5s 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/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​

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

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.