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;
}>;
| 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 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. MultipleSocketProviderinstances 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.createdoes not need a socket. - Mount before any consumer of
useSocketoruseAiEventListenerin the tree — these throw if called outside.
Related
- OvokProvider documentation — the FHIR client + auth wrapper this depends on