From 21bc88b9fd3fe479300642d0169dd226fb6465db Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Mon, 10 Nov 2025 09:31:08 +0200 Subject: [PATCH 1/2] feat: add new areTokenSourceFetchOptionsEqual function This function is needed for use in `components-js` so I can fix an issue reported by Thom [here](https://github.com/livekit-examples/agent-starter-react/pull/298#discussion_r2504903389) related to the useSession return not being a stable reference due to fetch options changing every render. --- src/room/token-source/utils.test.ts | 36 ++++++++++++++++++++++++++++- src/room/token-source/utils.ts | 34 ++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/room/token-source/utils.test.ts b/src/room/token-source/utils.test.ts index d98eb2229a..d9b144c167 100644 --- a/src/room/token-source/utils.test.ts +++ b/src/room/token-source/utils.test.ts @@ -1,6 +1,6 @@ import { TokenSourceResponse } from '@livekit/protocol'; import { describe, expect, it } from 'vitest'; -import { decodeTokenPayload, isResponseTokenValid } from './utils'; +import { areTokenSourceFetchOptionsEqual, decodeTokenPayload, isResponseTokenValid } from './utils'; // Test JWTs created for test purposes only. // None of these actually auth against anything. @@ -61,3 +61,37 @@ describe('decodeTokenPayload', () => { expect(payload.roomConfig?.agents![0].metadata).toBe('test agent metadata'); }); }); + +describe('areTokenSourceFetchOptionsEqual', () => { + it('should ensure two identical options objects of different references are equal', () => { + expect( + areTokenSourceFetchOptionsEqual( + { agentName: 'my agent name' }, + { agentName: 'my agent name' }, + ), + ).to.equal(true); + }); + it('should ensure two empty options objects are equal', () => { + expect(areTokenSourceFetchOptionsEqual({}, {})).to.equal(true); + }); + it('should ensure empty on the left and filled on the right are not equal', () => { + expect(areTokenSourceFetchOptionsEqual({}, { agentName: 'my agent name' })).to.equal(false); + }); + it('should ensure filled on the left and empty on the right are not equal', () => { + expect(areTokenSourceFetchOptionsEqual({ agentName: 'my agent name' }, {})).to.equal(false); + }); + it('should ensure objects with different keys/values are not equal', () => { + expect( + areTokenSourceFetchOptionsEqual( + { agentName: 'foo' }, + { agentName: 'bar', agentMetadata: 'baz' }, + ), + ).to.equal(false); + expect( + areTokenSourceFetchOptionsEqual( + { agentName: 'bar', agentMetadata: 'baz' }, + { agentName: 'foo' }, + ), + ).to.equal(false); + }); +}); diff --git a/src/room/token-source/utils.ts b/src/room/token-source/utils.ts index c702199971..49ce9da10f 100644 --- a/src/room/token-source/utils.ts +++ b/src/room/token-source/utils.ts @@ -1,6 +1,6 @@ import { RoomConfiguration, type TokenSourceResponse } from '@livekit/protocol'; import { decodeJwt } from 'jose'; -import type { RoomConfigurationObject, TokenPayload } from './types'; +import type { RoomConfigurationObject, TokenPayload, TokenSourceFetchOptions } from './types'; const ONE_SECOND_IN_MILLISECONDS = 1000; const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS; @@ -39,3 +39,35 @@ export function decodeTokenPayload(token: string) { return mappedPayload; } + +/** Given two TokenSourceFetchOptions values, check to see if they are deep equal. */ +export function areTokenSourceFetchOptionsEqual( + a: TokenSourceFetchOptions, + b: TokenSourceFetchOptions, +) { + const allKeysSet = new Set([...Object.keys(a), ...Object.keys(b)]) as Set< + keyof TokenSourceFetchOptions + >; + + for (const key of allKeysSet) { + switch (key) { + case 'roomName': + case 'participantName': + case 'participantIdentity': + case 'participantMetadata': + case 'participantAttributes': + case 'agentName': + case 'agentMetadata': + if (a[key] !== b[key]) { + return false; + } + break; + default: + // ref: https://stackoverflow.com/a/58009992 + const exhaustiveCheckedKey: never = key; + throw new Error(`Options key ${exhaustiveCheckedKey} not being checked for equality!`); + } + } + + return true; +} From 312b8a546314a31f6b7156e08fdaff5edd7c75a7 Mon Sep 17 00:00:00 2001 From: Ryan Gaus Date: Mon, 10 Nov 2025 09:37:08 +0200 Subject: [PATCH 2/2] fix: add changeset --- .changeset/rich-actors-taste.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rich-actors-taste.md diff --git a/.changeset/rich-actors-taste.md b/.changeset/rich-actors-taste.md new file mode 100644 index 0000000000..59d124c8aa --- /dev/null +++ b/.changeset/rich-actors-taste.md @@ -0,0 +1,5 @@ +--- +'livekit-client': patch +--- + +Add new areTokenSourceFetchOptionsEqual function