Skip to content

Commit 4c226d2

Browse files
authored
Port back to initial token refresh (#238)
1 parent 9f6013d commit 4c226d2

File tree

5 files changed

+59
-12
lines changed

5 files changed

+59
-12
lines changed

app/components/layout.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,18 @@ import useConnectionDetails from '@/hooks/useConnectionDetails';
1010
import { cn } from '@/lib/utils';
1111

1212
export default function ComponentsLayout({ children }: { children: React.ReactNode }) {
13-
const { fetchConnectionDetails } = useConnectionDetails();
13+
const { connectionDetails } = useConnectionDetails();
1414

1515
const pathname = usePathname();
1616
const room = React.useMemo(() => new Room(), []);
1717

1818
React.useEffect(() => {
19-
if (room.state === 'disconnected') {
19+
if (room.state === 'disconnected' && connectionDetails) {
2020
Promise.all([
2121
room.localParticipant.setMicrophoneEnabled(true, undefined, {
2222
preConnectBuffer: true,
2323
}),
24-
fetchConnectionDetails().then((connectionDetails) =>
25-
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
26-
),
24+
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken),
2725
]).catch((error) => {
2826
toastAlert({
2927
title: 'There was an error connecting to the agent',
@@ -34,7 +32,7 @@ export default function ComponentsLayout({ children }: { children: React.ReactNo
3432
return () => {
3533
room.disconnect();
3634
};
37-
}, [room, fetchConnectionDetails]);
35+
}, [room, connectionDetails]);
3836

3937
return (
4038
<div className="mx-auto min-h-svh max-w-3xl space-y-8 px-4 py-8">

components/app.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ interface AppProps {
2121
export function App({ appConfig }: AppProps) {
2222
const room = useMemo(() => new Room(), []);
2323
const [sessionStarted, setSessionStarted] = useState(false);
24-
const { fetchConnectionDetails } = useConnectionDetails();
24+
const { refreshConnectionDetails, existingOrRefreshConnectionDetails } = useConnectionDetails();
2525

2626
useEffect(() => {
2727
const onDisconnected = () => {
2828
setSessionStarted(false);
29+
refreshConnectionDetails();
2930
};
3031
const onMediaDevicesError = (error: Error) => {
3132
toastAlert({
@@ -39,7 +40,7 @@ export function App({ appConfig }: AppProps) {
3940
room.off(RoomEvent.Disconnected, onDisconnected);
4041
room.off(RoomEvent.MediaDevicesError, onMediaDevicesError);
4142
};
42-
}, [room]);
43+
}, [room, refreshConnectionDetails]);
4344

4445
useEffect(() => {
4546
let aborted = false;
@@ -48,7 +49,7 @@ export function App({ appConfig }: AppProps) {
4849
room.localParticipant.setMicrophoneEnabled(true, undefined, {
4950
preConnectBuffer: appConfig.isPreConnectBufferEnabled,
5051
}),
51-
fetchConnectionDetails().then((connectionDetails) =>
52+
existingOrRefreshConnectionDetails().then((connectionDetails) =>
5253
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
5354
),
5455
]).catch((error) => {
@@ -71,7 +72,7 @@ export function App({ appConfig }: AppProps) {
7172
aborted = true;
7273
room.disconnect();
7374
};
74-
}, [room, sessionStarted, fetchConnectionDetails, appConfig.isPreConnectBufferEnabled]);
75+
}, [room, sessionStarted, appConfig.isPreConnectBufferEnabled]);
7576

7677
const { startButtonText } = appConfig;
7778

hooks/useConnectionDetails.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { useCallback } from 'react';
1+
import { useCallback, useEffect, useState } from 'react';
2+
import { decodeJwt } from 'jose';
23
import { ConnectionDetails } from '@/app/api/connection-details/route';
34

5+
const ONE_MINUTE_IN_MILLISECONDS = 60 * 1000;
6+
47
export default function useConnectionDetails() {
58
// Generate room connection details, including:
69
// - A random Room name
@@ -11,7 +14,10 @@ export default function useConnectionDetails() {
1114
// In real-world application, you would likely allow the user to specify their
1215
// own participant name, and possibly to choose from existing rooms to join.
1316

17+
const [connectionDetails, setConnectionDetails] = useState<ConnectionDetails | null>(null);
18+
1419
const fetchConnectionDetails = useCallback(async () => {
20+
setConnectionDetails(null);
1521
const url = new URL(
1622
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? '/api/connection-details',
1723
window.location.origin
@@ -26,8 +32,41 @@ export default function useConnectionDetails() {
2632
throw new Error('Error fetching connection details!');
2733
}
2834

35+
setConnectionDetails(data);
2936
return data;
3037
}, []);
3138

32-
return { fetchConnectionDetails };
39+
useEffect(() => {
40+
fetchConnectionDetails();
41+
}, [fetchConnectionDetails]);
42+
43+
const isConnectionDetailsExpired = useCallback(() => {
44+
const token = connectionDetails?.participantToken;
45+
if (!token) {
46+
return true;
47+
}
48+
49+
const jwtPayload = decodeJwt(token);
50+
if (!jwtPayload.exp) {
51+
return true;
52+
}
53+
const expiresAt = new Date(jwtPayload.exp - ONE_MINUTE_IN_MILLISECONDS);
54+
55+
const now = new Date();
56+
return expiresAt >= now;
57+
}, [connectionDetails?.participantToken]);
58+
59+
const existingOrRefreshConnectionDetails = useCallback(async () => {
60+
if (isConnectionDetailsExpired() || !connectionDetails) {
61+
return fetchConnectionDetails();
62+
} else {
63+
return connectionDetails;
64+
}
65+
}, [connectionDetails, fetchConnectionDetails, isConnectionDetailsExpired]);
66+
67+
return {
68+
connectionDetails,
69+
refreshConnectionDetails: fetchConnectionDetails,
70+
existingOrRefreshConnectionDetails,
71+
};
3372
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"buffer-image-size": "^0.6.4",
2323
"class-variance-authority": "^0.7.1",
2424
"clsx": "^2.1.1",
25+
"jose": "^6.0.12",
2526
"livekit-client": "^2.13.3",
2627
"livekit-server-sdk": "^2.13.0",
2728
"mime": "^4.0.7",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)