For most frontend work, hc() is enough. This page covers lower-level patterns: advanced useWebRTCCall() callbacks for @agentuity/react apps, plus the WebSocketManager and EventStreamManager utilities from @agentuity/frontend for custom transports. These managers are also re-exported by @agentuity/react, so existing imports still work.
For Agentuity WebSocket routes, hc<ApiRouter>() gives you .$ws(). For SSE routes, the standard EventSource API usually does the job. Reach for these managers when you want reconnection, message buffering, or a reusable abstraction around a custom endpoint.
In the manager-based examples below, useEffect is only for opening and disposing the connection. Keep send() and other user-triggered work in event handlers.
Advanced WebRTC Callbacks
useWebRTCCall() accepts the same advanced connection options and callback hooks exposed by the underlying WebRTCManager, while still giving you React state for the common case:
import { useState } from 'react';
import { useWebRTCCall } from '@agentuity/react';
export function ModeratedRoom({ roomId }: { roomId: string }) {
const [events, setEvents] = useState<string[]>([]);
const { state, remotePeerIds, connect, hangup, sendJSON } = useWebRTCCall({
roomId,
signalUrl: '/api/call/signal',
autoConnect: false,
media: false,
dataChannels: [{ label: 'presence', ordered: true }],
callbacks: {
onStateChange: (from, to) => {
setEvents((prev) => [`${from} -> ${to}`, ...prev]);
},
onPeerJoined: (peerId) => {
setEvents((prev) => [`peer joined: ${peerId}`, ...prev]);
},
onDataChannelMessage: (peerId, label, data) => {
setEvents((prev) => [`${label} from ${peerId}: ${String(data)}`, ...prev]);
},
},
});
return (
<div>
<p>State: {state}</p>
<p>Peers: {remotePeerIds.join(', ') || 'none yet'}</p>
<button onClick={connect}>Join room</button>
<button onClick={() => sendJSON('presence', { type: 'ping' })}>
Send presence event
</button>
<button onClick={hangup}>Leave</button>
<pre>{events.join('\n')}</pre>
</div>
);
}Use this pattern when you need richer call telemetry, custom data channel flows, or room-specific UX beyond the basic examples on WebRTC.
WebSocketManager
Use WebSocketManager to manage a custom WebSocket connection with reconnect handling and a typed message callback.
import { WebSocketManager } from '@agentuity/frontend';
import { useEffect, useRef, useState } from 'react';
type OutboundMessage = { message: string };
type InboundMessage = { echo: string; timestamp: number };
export function CustomWebSocket() {
const managerRef = useRef<WebSocketManager<OutboundMessage, InboundMessage> | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [messages, setMessages] = useState<InboundMessage[]>([]);
useEffect(() => {
const url = `${window.location.origin.replace(/^http/, 'ws')}/api/echo`;
const manager = new WebSocketManager<OutboundMessage, InboundMessage>({
url,
callbacks: {
onConnect: () => setIsConnected(true),
onDisconnect: () => setIsConnected(false),
onMessage: (message) => {
setMessages((prev) => [...prev, message]);
},
},
});
managerRef.current = manager;
manager.connect();
return () => {
manager.dispose();
};
}, []);
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<button
onClick={() => managerRef.current?.send({ message: 'ping' })}
disabled={!isConnected}
>
Send Ping
</button>
<pre>{JSON.stringify(messages, null, 2)}</pre>
</div>
);
}WebSocketManager and EventStreamManager are transport utilities. They do not automatically read AgentuityProvider state. Pass the full URL yourself, and use the auth mechanism your endpoint expects.
What you get:
| Capability | Notes |
|---|---|
connect() | Opens the socket and starts reconnect handling |
send(data) | Sends typed outbound messages, or queues them until the socket opens |
setMessageHandler(handler) | Registers a typed message handler and flushes buffered messages |
getState() | Returns isConnected, readyState, and error state |
close() / dispose() | Cleans up the connection and stops reconnects |
EventStreamManager
Use EventStreamManager for long-lived SSE connections when you want the same typed callback and reconnect behavior:
import { EventStreamManager } from '@agentuity/frontend';
import { useEffect, useRef, useState } from 'react';
type StatusEvent = { event: string; count: number };
export function CustomEventStream() {
const managerRef = useRef<EventStreamManager<StatusEvent> | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [latestEvent, setLatestEvent] = useState<StatusEvent | null>(null);
useEffect(() => {
const manager = new EventStreamManager<StatusEvent>({
url: `${window.location.origin}/api/events`,
callbacks: {
onConnect: () => setIsConnected(true),
onDisconnect: () => setIsConnected(false),
onMessage: (message) => setLatestEvent(message),
},
});
managerRef.current = manager;
manager.connect();
return () => {
manager.dispose();
};
}, []);
return (
<div>
<p>Stream: {isConnected ? 'Active' : 'Connecting...'}</p>
<pre>{JSON.stringify(latestEvent, null, 2)}</pre>
<button onClick={() => managerRef.current?.close()}>Stop Stream</button>
</div>
);
}Route-Native Patterns
For Agentuity routes, you often do not need the managers:
import { hc } from 'hono/client';
import type { ApiRouter } from '../api/index';
const client = hc<ApiRouter>('/api');
// WebSocket route
const ws = client.echo.$ws();
// SSE route
const events = new EventSource('/api/events');These route-native APIs are the best fit when you already control both ends of the connection.
If you are not in React, import the same low-level managers directly from @agentuity/frontend.
When to Use What
| Use Case | Recommended API |
|---|---|
| Standard HTTP routes | hc() or fetch |
| Agentuity WebSocket route | client.route.$ws() from hc() |
| Agentuity SSE route | EventSource |
| Custom or third-party WebSocket endpoint | WebSocketManager |
| Custom or third-party SSE endpoint | EventStreamManager |
| Peer-to-peer audio/video/data | useWebRTCCall |
Next Steps
- React Hooks: Auth, analytics, and WebRTC hooks from
@agentuity/react - RPC Client: Type-safe route calls with
hc() - WebSockets: Server-side WebSocket routes
- Server-Sent Events: Server-side SSE routes