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 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; +}