Skip to content

Commit ffb74c3

Browse files
integrate TokenSource
1 parent 3ee3b1a commit ffb74c3

File tree

8 files changed

+98
-179
lines changed

8 files changed

+98
-179
lines changed

app/api/connection-details/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { NextResponse } from 'next/server';
22
import { AccessToken, type AccessTokenOptions, type VideoGrant } from 'livekit-server-sdk';
33
import { RoomConfiguration } from '@livekit/protocol';
4-
import type { ConnectionDetails } from '@/hooks/useConnectionDetails';
4+
5+
type ConnectionDetails = {
6+
serverUrl: string;
7+
roomName: string;
8+
participantName: string;
9+
participantToken: string;
10+
};
511

612
// NOTE: you are expected to define the following environment variables in `.env.local`:
713
const API_KEY = process.env.LIVEKIT_API_KEY;

app/ui/_room-provider.tsx

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,17 @@
11
'use client';
22

33
import React from 'react';
4-
import { Room } from 'livekit-client';
54
import { RoomContext } from '@livekit/components-react';
65
import { AppConfig } from '@/app-config';
7-
import { toastAlert } from '@/components/livekit/alert-toast';
8-
import useConnectionDetails from '@/hooks/useConnectionDetails';
6+
import { useRoom } from '@/hooks/useRoom';
97

10-
export function RoomProvider({
11-
appConfig,
12-
children,
13-
}: {
8+
interface RoomProviderProps {
149
appConfig: AppConfig;
1510
children: React.ReactNode;
16-
}) {
17-
const { connectionDetails } = useConnectionDetails(appConfig);
18-
const room = React.useMemo(() => new Room(), []);
11+
}
1912

20-
React.useEffect(() => {
21-
if (room.state === 'disconnected' && connectionDetails) {
22-
Promise.all([
23-
room.localParticipant.setMicrophoneEnabled(true, undefined, {
24-
preConnectBuffer: true,
25-
}),
26-
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken),
27-
]).catch((error) => {
28-
toastAlert({
29-
title: 'There was an error connecting to the agent',
30-
description: `${error.name}: ${error.message}`,
31-
});
32-
});
33-
}
34-
return () => {
35-
room.disconnect();
36-
};
37-
}, [room, connectionDetails]);
13+
export function RoomProvider({ appConfig, children }: RoomProviderProps) {
14+
const { room } = useRoom(appConfig);
3815

3916
return <RoomContext.Provider value={room}>{children}</RoomContext.Provider>;
4017
}

app/ui/layout.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@ import { RoomProvider } from './_room-provider';
66
export default async function ComponentsLayout({ children }: { children: React.ReactNode }) {
77
const hdrs = await headers();
88
const appConfig = await getAppConfig(hdrs);
9+
910
return (
10-
<div className="bg-muted/20 min-h-svh p-8">
11-
<div className="mx-auto max-w-3xl space-y-8">
12-
<header className="space-y-2">
13-
<h1 className="text-5xl font-bold tracking-tight">LiveKit UI</h1>
14-
<p className="text-muted-foreground max-w-prose text-balance">
15-
A set of beautifully designed components that you can customize, extend, and build on.
16-
</p>
17-
<p className="text-muted-foreground max-w-prose text-balance">
18-
Built with Shadcn conventions.
19-
</p>
20-
<p className="text-foreground max-w-prose text-balance">Open Source. Open Code.</p>
21-
</header>
11+
<RoomProvider appConfig={appConfig}>
12+
<div className="bg-muted/20 min-h-svh p-8">
13+
<div className="mx-auto max-w-3xl space-y-8">
14+
<header className="space-y-2">
15+
<h1 className="text-5xl font-bold tracking-tight">LiveKit UI</h1>
16+
<p className="text-muted-foreground max-w-prose text-balance">
17+
A set of beautifully designed components that you can customize, extend, and build on.
18+
</p>
19+
<p className="text-muted-foreground max-w-prose text-balance">
20+
Built with Shadcn conventions.
21+
</p>
22+
<p className="text-foreground max-w-prose text-balance">Open Source. Open Code.</p>
23+
</header>
2224

23-
<RoomProvider appConfig={appConfig}>
2425
<main className="space-y-20">{children}</main>
25-
</RoomProvider>
26+
</div>
2627
</div>
27-
</div>
28+
</RoomProvider>
2829
);
2930
}

components/app/app.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,25 @@ interface AppProps {
3434
}
3535

3636
export function App({ appConfig }: AppProps) {
37-
const { room, sessionStarted, setSessionStarted } = useRoom(appConfig);
37+
const { room, isSessionStarted, startSession } = useRoom(appConfig);
3838
const { startButtonText } = appConfig;
3939

40-
const handleStartCall = () => {
41-
setSessionStarted(true);
42-
};
43-
4440
return (
4541
<RoomContext.Provider value={room}>
4642
<main className="grid h-svh grid-cols-1 place-content-center">
4743
<AnimatePresence mode="wait">
4844
{/* Welcome screen */}
49-
{!sessionStarted && (
45+
{!isSessionStarted && (
5046
<MotionWelcomeView
5147
key="welcome"
5248
{...VIEW_MOTION_PROPS}
5349
startButtonText={startButtonText}
54-
onStartCall={handleStartCall}
50+
onStartCall={startSession}
5551
/>
5652
)}
5753

5854
{/* Session view */}
59-
{sessionStarted && (
55+
{isSessionStarted && (
6056
<MotionSessionView key="session-view" {...VIEW_MOTION_PROPS} appConfig={appConfig} />
6157
)}
6258
</AnimatePresence>

hooks/useConnectionDetails.ts

Lines changed: 0 additions & 92 deletions
This file was deleted.

hooks/useRoom.ts

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import { useEffect, useMemo, useState } from 'react';
2-
import { Room, RoomEvent } from 'livekit-client';
2+
import { Room, RoomEvent, TokenSource } from 'livekit-client';
33
import { AppConfig } from '@/app-config';
44
import { toastAlert } from '@/components/livekit/alert-toast';
5-
import useConnectionDetails from '@/hooks/useConnectionDetails';
65

76
export function useRoom(appConfig: AppConfig) {
87
const room = useMemo(() => new Room(), []);
9-
const [sessionStarted, setSessionStarted] = useState(false);
10-
const { refreshConnectionDetails, existingOrRefreshConnectionDetails } =
11-
useConnectionDetails(appConfig);
8+
const [isSessionStarted, setIsSessionStarted] = useState(false);
129

1310
useEffect(() => {
1411
function onDisconnected() {
15-
setSessionStarted(false);
16-
refreshConnectionDetails();
12+
setIsSessionStarted(false);
1713
}
1814

1915
function onMediaDevicesError(error: Error) {
@@ -30,20 +26,50 @@ export function useRoom(appConfig: AppConfig) {
3026
room.off(RoomEvent.Disconnected, onDisconnected);
3127
room.off(RoomEvent.MediaDevicesError, onMediaDevicesError);
3228
};
33-
}, [room, refreshConnectionDetails]);
29+
}, [room]);
3430

3531
useEffect(() => {
3632
let aborted = false;
3733

38-
if (sessionStarted && room.state === 'disconnected') {
34+
const tokenSource = TokenSource.custom(async () => {
35+
const url = new URL(
36+
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? '/api/connection-details',
37+
window.location.origin
38+
);
39+
40+
try {
41+
const res = await fetch(url.toString(), {
42+
method: 'POST',
43+
headers: {
44+
'Content-Type': 'application/json',
45+
'X-Sandbox-Id': appConfig.sandboxId ?? '',
46+
},
47+
body: JSON.stringify({
48+
room_config: appConfig.agentName
49+
? {
50+
agents: [{ agent_name: appConfig.agentName }],
51+
}
52+
: undefined,
53+
}),
54+
});
55+
return await res.json();
56+
} catch (error) {
57+
console.error('Error fetching connection details:', error);
58+
throw new Error('Error fetching connection details!');
59+
}
60+
});
61+
62+
if (isSessionStarted && room.state === 'disconnected') {
3963
const { isPreConnectBufferEnabled } = appConfig;
4064
Promise.all([
4165
room.localParticipant.setMicrophoneEnabled(true, undefined, {
4266
preConnectBuffer: isPreConnectBufferEnabled,
4367
}),
44-
existingOrRefreshConnectionDetails().then((connectionDetails) =>
45-
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
46-
),
68+
tokenSource
69+
.fetch({ agentName: appConfig.agentName })
70+
.then((connectionDetails) =>
71+
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
72+
),
4773
]).catch((error) => {
4874
if (aborted) {
4975
// Once the effect has cleaned up after itself, drop any errors
@@ -65,7 +91,11 @@ export function useRoom(appConfig: AppConfig) {
6591
aborted = true;
6692
room.disconnect();
6793
};
68-
}, [room, sessionStarted, appConfig, existingOrRefreshConnectionDetails]);
94+
}, [room, isSessionStarted, appConfig]);
95+
96+
const startSession = () => {
97+
setIsSessionStarted(true);
98+
};
6999

70-
return { room, sessionStarted, setSessionStarted };
100+
return { room, isSessionStarted, startSession };
71101
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"class-variance-authority": "^0.7.1",
2525
"clsx": "^2.1.1",
2626
"jose": "^6.0.12",
27-
"livekit-client": "^2.15.5",
27+
"livekit-client": "^2.15.8",
2828
"livekit-server-sdk": "^2.13.2",
2929
"mime": "^4.0.7",
3030
"motion": "^12.16.0",

0 commit comments

Comments
 (0)