Skip to content

Commit 6a6af9e

Browse files
authored
Invalidate token after first usage (#1309)
1 parent 7a757a2 commit 6a6af9e

5 files changed

Lines changed: 139 additions & 1838 deletions

File tree

.changeset/chatty-beers-scream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@livekit/components-react": patch
3+
---
4+
5+
Invalidate tokens originating from a cached token source after first usage

examples/nextjs/pages/agent.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,17 @@ const AgentExample: NextPage = () => {
4242
() => (typeof window !== 'undefined' ? new URLSearchParams(location.search) : null),
4343
[],
4444
);
45-
const roomName = useMemo(
46-
() => params?.get('room') ?? 'test-room-' + Math.random().toFixed(5),
47-
[params],
48-
);
45+
const [roomName, setRoomName] = useState(() => params?.get('room') ?? 'test');
46+
47+
useEffect(() => {
48+
if (!roomName) {
49+
setRoomName('test-room-' + Math.random().toFixed(5));
50+
}
51+
}, []);
4952
const [userIdentity] = useState(() => params?.get('user') ?? generateRandomUserId());
5053

54+
55+
5156
const session = useSession(tokenSource, {
5257
roomName,
5358
participantIdentity: userIdentity,
@@ -86,6 +91,14 @@ const AgentExample: NextPage = () => {
8691
<SessionProvider session={session}>
8792
<div className={styles.room}>
8893
<div className={styles.inner}>
94+
{!started && (
95+
<button
96+
className="lk-button"
97+
onClick={() => setRoomName('test-room-' + Math.random().toFixed(5))}
98+
>
99+
New Room: {roomName}
100+
</button>
101+
)}
89102
{started ? (
90103
<SimpleAgent />
91104
) : (

packages/react/src/hooks/useSession.ts

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -270,22 +270,24 @@ function useSessionTokenSourceFetch(
270270
) {
271271
return;
272272
}
273-
274273
memoizedTokenFetchOptionsRef.current = unstableRestOptions;
275274
}, [isConfigurable, unstableRestOptions]);
276275

277-
const tokenSourceFetch = React.useCallback(async () => {
278-
if (isConfigurable) {
279-
if (!memoizedTokenFetchOptionsRef.current) {
280-
throw new Error(
281-
`AgentSession - memoized token fetch options are not set, but the passed tokenSource was an instance of TokenSourceConfigurable. If you are seeing this please make a new GitHub issue!`,
282-
);
276+
const tokenSourceFetch = React.useCallback(
277+
async (force?: boolean) => {
278+
if (isConfigurable) {
279+
if (!memoizedTokenFetchOptionsRef.current) {
280+
throw new Error(
281+
`AgentSession - memoized token fetch options are not set, but the passed tokenSource was an instance of TokenSourceConfigurable. If you are seeing this please make a new GitHub issue!`,
282+
);
283+
}
284+
return tokenSource.fetch(memoizedTokenFetchOptionsRef.current, force);
285+
} else {
286+
return tokenSource.fetch();
283287
}
284-
return tokenSource.fetch(memoizedTokenFetchOptionsRef.current);
285-
} else {
286-
return tokenSource.fetch();
287-
}
288-
}, [isConfigurable, tokenSource]);
288+
},
289+
[isConfigurable, tokenSource],
290+
);
289291

290292
return tokenSourceFetch;
291293
}
@@ -546,6 +548,8 @@ export function useSession(
546548

547549
const tokenSourceFetch = useSessionTokenSourceFetch(tokenSource, restOptions);
548550

551+
const [wasSessionEndCalled, setWasSessionEndCalled] = React.useState(false);
552+
549553
const start = React.useCallback(
550554
async (connectOptions: SessionConnectOptions = {}) => {
551555
const {
@@ -555,12 +559,23 @@ export function useSession(
555559
} = connectOptions;
556560

557561
await waitUntilDisconnected(signal);
562+
setWasSessionEndCalled(false);
558563

559564
const onSignalAbort = () => {
560565
room.disconnect();
561566
};
562567
signal?.addEventListener('abort', onSignalAbort);
563568

569+
const onDisconnected = () => {
570+
// on disconnection force a new token to be fetched in order to avoid reusing the same room right after
571+
// this works around the fact that agents won't rejoin a room that existed previously
572+
// and depends on the assumption that the endpoint will return a token for a different room
573+
if (!wasSessionEndCalled) {
574+
tokenSourceFetch(true);
575+
}
576+
};
577+
room.once(RoomEvent.Disconnected, onDisconnected);
578+
564579
let tokenDispatchesAgent = false;
565580
await Promise.all([
566581
tokenSourceFetch().then(({ serverUrl, participantToken }) => {
@@ -603,12 +618,21 @@ export function useSession(
603618

604619
signal?.removeEventListener('abort', onSignalAbort);
605620
},
606-
[room, waitUntilDisconnected, tokenSourceFetch, waitUntilConnected, agent.waitUntilConnected],
621+
[
622+
room,
623+
waitUntilDisconnected,
624+
tokenSourceFetch,
625+
waitUntilConnected,
626+
agent.waitUntilConnected,
627+
wasSessionEndCalled,
628+
],
607629
);
608630

609631
const end = React.useCallback(async () => {
632+
setWasSessionEndCalled(true);
633+
tokenSourceFetch(true);
610634
await room.disconnect();
611-
}, [room]);
635+
}, [room, tokenSourceFetch]);
612636

613637
const prepareConnection = React.useCallback(async () => {
614638
const credentials = await tokenSourceFetch();

0 commit comments

Comments
 (0)