fix(api): stop treating a LiveKit 404 as an empty room (#2595)

This commit is contained in:
Hampus
2026-09-08 19:02:43 +02:00
committed by GitHub
parent ceec183d38
commit 1b81c14c48
2 changed files with 77 additions and 4 deletions
@@ -324,9 +324,6 @@ export class LiveKitService extends ILiveKitService {
participants: participants.map((participant) => ({identity: participant.identity})),
};
} catch (error) {
if (LiveKitService.isHttp404(error)) {
return {status: 'ok', participants: []};
}
Logger.warn({error, regionId, serverId, roomName}, 'LiveKit listParticipants failed');
const status = LiveKitService.getHttpStatus(error);
const isRetryable = status != null && status >= 500;
@@ -2,7 +2,8 @@
import {AccessToken, TrackSource} from 'livekit-server-sdk';
import {describe, expect, it} from 'vitest';
import {computeLiveKitPublishSources, VOICE_TOKEN_TTL_SECONDS} from '../LiveKitService';
import {createChannelID, createGuildID} from '../../BrandedTypes';
import {computeLiveKitPublishSources, LiveKitService, VOICE_TOKEN_TTL_SECONDS} from '../LiveKitService';
function decodeJwtPayload(token: string): Record<string, unknown> {
const [, payload] = token.split('.');
@@ -57,3 +58,78 @@ describe('LiveKitService publish permissions', () => {
expect(exp - nowSeconds).toBeGreaterThan(0);
});
});
class FakeTwirpError extends Error {
status: number;
code?: string;
constructor(message: string, status: number, code?: string) {
super(message);
this.name = 'TwirpError';
this.status = status;
this.code = code;
}
}
function createServiceWithRoomServiceClient(roomServiceClient: unknown): LiveKitService {
const service = Object.create(LiveKitService.prototype) as LiveKitService;
Reflect.set(
service,
'serverClients',
new Map([
[
'region-1',
new Map([
[
'region-1-server-1',
{
endpoint: 'ws://livekit.test/livekit',
apiKey: 'test-key',
apiSecret: 'test-secret',
isActive: true,
roomServiceClient,
},
],
]),
],
]),
);
return service;
}
describe('LiveKitService listParticipants', () => {
const params = {
guildId: createGuildID(1n),
channelId: createChannelID(2n),
regionId: 'region-1',
serverId: 'region-1-server-1',
};
it('reports a 404 as an unreadable room instead of an empty one', async () => {
const service = createServiceWithRoomServiceClient({
listParticipants: async () => {
throw new FakeTwirpError('not_found', 404, 'not_found');
},
});
const result = await service.listParticipants(params);
expect(result.status).toBe('error');
});
it('reports a bad_route 404 as an unreadable room instead of an empty one', async () => {
const service = createServiceWithRoomServiceClient({
listParticipants: async () => {
throw new FakeTwirpError('invalid path prefix', 404, 'bad_route');
},
});
const result = await service.listParticipants(params);
expect(result.status).toBe('error');
expect(result.status === 'error' && result.retryable).toBe(false);
});
it('still reports a genuinely empty room as empty', async () => {
const service = createServiceWithRoomServiceClient({
listParticipants: async () => [],
});
const result = await service.listParticipants(params);
expect(result).toEqual({status: 'ok', participants: []});
});
});