Advanced Hooks — Agentuity Documentation

Advanced Hooks

Advanced WebRTC callbacks plus low-level WebSocket and SSE client utilities

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.

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>
  );
}

What you get:

CapabilityNotes
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 CaseRecommended API
Standard HTTP routeshc() or fetch
Agentuity WebSocket routeclient.route.$ws() from hc()
Agentuity SSE routeEventSource
Custom or third-party WebSocket endpointWebSocketManager
Custom or third-party SSE endpointEventStreamManager
Peer-to-peer audio/video/datauseWebRTCCall

Next Steps