Hono, Workers, Bun and Deno
Mount the realtime avatar proxy on any Fetch-handler runtime — Hono, Cloudflare Workers, Bun or Deno — including the apiKey factory Workers require.
This adapter is for Hono and for anything else built on Fetch handlers — Cloudflare Workers, Bun, Deno. It takes a Request and returns a Response, which is why one adapter covers all four.
The server half
Mount on a wildcard so every operation reaches the handler, not just the mount path.
import { Hono } from "hono";
import { realtimeAvatarHono } from "realtime-avatar/hono";
const app = new Hono<{ Bindings: Env }>();
app.all(
"/api/realtime-avatar/*",
realtimeAvatarHono({
// A FACTORY, not a value. On Workers there is no process.env, and a
// module-scope read runs before any binding exists.
apiKey: () => env.REALTIME_AVATAR_API_KEY,
authorize: async ({ request, operation }) => {
const user = await currentUser(request);
if (!user) return new Response("Sign in", { status: 401 });
if (operation === "connect" && !user.credits) {
return Response.json({ code: "insufficient_credits" }, { status: 402 });
}
},
session: async ({ avatarId }) => ({
instructions: promptFor(avatarId),
}),
}),
);
export default app;On Node or Bun, where process.env exists, a plain string is fine: apiKey: process.env.REALTIME_AVATAR_API_KEY!. The factory form is what makes the Workers case work, and it is harmless everywhere else — the value is simply read per request instead of once at module scope.
The client half
import { AvatarCall, createProxyClient } from "realtime-avatar/react";
const client = createProxyClient({ proxyUrl: "/api/realtime-avatar" });
<AvatarCall client={client} avatarId={avatarId} />Next
- Authentication — keys, scopes, and what
authorizeis really gating. - Calls — everything the
sessionpolicy can decide.