livekit-client 2.16.0 → 2.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -1
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +1 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1079 -1329
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -0
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +5 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +74 -5
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/token-source/TokenSource.d.ts +10 -2
- package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +0 -4
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +0 -6
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/mocks.d.ts.map +1 -1
- package/dist/ts4.2/api/utils.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +5 -0
- package/dist/ts4.2/room/Room.d.ts +1 -1
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -1
- package/dist/ts4.2/room/errors.d.ts +74 -5
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +1 -1
- package/dist/ts4.2/room/track/LocalTrack.d.ts +0 -4
- package/dist/ts4.2/room/track/processor/types.d.ts +0 -6
- package/package.json +10 -6
- package/src/api/SignalClient.test.ts +12 -19
- package/src/api/SignalClient.ts +13 -28
- package/src/api/utils.ts +1 -1
- package/src/connectionHelper/checks/turn.ts +7 -0
- package/src/connectionHelper/checks/websocket.ts +40 -11
- package/src/room/PCTransport.ts +1 -1
- package/src/room/PCTransportManager.ts +4 -19
- package/src/room/RTCEngine.ts +56 -18
- package/src/room/RegionUrlProvider.test.ts +8 -9
- package/src/room/RegionUrlProvider.ts +13 -12
- package/src/room/Room.ts +14 -16
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +0 -1
- package/src/room/errors.ts +144 -16
- package/src/room/participant/LocalParticipant.ts +1 -1
- package/src/room/token-source/TokenSource.ts +5 -1
- package/src/room/track/LocalTrack.ts +0 -4
- package/src/room/track/TrackPublication.ts +1 -1
- package/src/room/track/processor/types.ts +0 -6
- package/src/room/utils.ts +2 -1
- package/src/test/mocks.ts +0 -1
package/src/room/RTCEngine.ts
CHANGED
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
ConnectionError,
|
|
63
63
|
ConnectionErrorReason,
|
|
64
64
|
NegotiationError,
|
|
65
|
+
SignalReconnectError,
|
|
65
66
|
TrackInvalidError,
|
|
66
67
|
UnexpectedConnectionState,
|
|
67
68
|
} from './errors';
|
|
@@ -92,6 +93,8 @@ const reliableDataChannel = '_reliable';
|
|
|
92
93
|
const minReconnectWait = 2 * 1000;
|
|
93
94
|
const leaveReconnect = 'leave-reconnect';
|
|
94
95
|
const reliabeReceiveStateTTL = 30_000;
|
|
96
|
+
const lossyDataChannelBufferThresholdMin = 8 * 1024;
|
|
97
|
+
const lossyDataChannelBufferThresholdMax = 256 * 1024;
|
|
95
98
|
|
|
96
99
|
enum PCState {
|
|
97
100
|
New,
|
|
@@ -203,6 +206,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
203
206
|
|
|
204
207
|
private reliableReceivedState: TTLMap<string, number> = new TTLMap(reliabeReceiveStateTTL);
|
|
205
208
|
|
|
209
|
+
private lossyDataStatCurrentBytes: number = 0;
|
|
210
|
+
|
|
211
|
+
private lossyDataStatByterate: number = 0;
|
|
212
|
+
|
|
213
|
+
private lossyDataStatInterval: ReturnType<typeof setInterval> | undefined;
|
|
214
|
+
|
|
215
|
+
private lossyDataDropCount: number = 0;
|
|
216
|
+
|
|
206
217
|
private midToTrackId: { [key: string]: string } = {};
|
|
207
218
|
|
|
208
219
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
@@ -312,6 +323,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
312
323
|
this.removeAllListeners();
|
|
313
324
|
this.deregisterOnLineListener();
|
|
314
325
|
this.clearPendingReconnect();
|
|
326
|
+
this.cleanupLossyDataStats();
|
|
315
327
|
await this.cleanupPeerConnections();
|
|
316
328
|
await this.cleanupClient();
|
|
317
329
|
} finally {
|
|
@@ -347,6 +359,16 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
347
359
|
this.reliableReceivedState.clear();
|
|
348
360
|
}
|
|
349
361
|
|
|
362
|
+
cleanupLossyDataStats() {
|
|
363
|
+
this.lossyDataStatByterate = 0;
|
|
364
|
+
this.lossyDataStatCurrentBytes = 0;
|
|
365
|
+
if (this.lossyDataStatInterval) {
|
|
366
|
+
clearInterval(this.lossyDataStatInterval);
|
|
367
|
+
this.lossyDataStatInterval = undefined;
|
|
368
|
+
}
|
|
369
|
+
this.lossyDataDropCount = 0;
|
|
370
|
+
}
|
|
371
|
+
|
|
350
372
|
async cleanupClient() {
|
|
351
373
|
await this.client.close();
|
|
352
374
|
this.client.resetCallbacks();
|
|
@@ -360,10 +382,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
360
382
|
const publicationTimeout = setTimeout(() => {
|
|
361
383
|
delete this.pendingTrackResolvers[req.cid];
|
|
362
384
|
reject(
|
|
363
|
-
|
|
364
|
-
'publication of local track timed out, no response from server',
|
|
365
|
-
ConnectionErrorReason.Timeout,
|
|
366
|
-
),
|
|
385
|
+
ConnectionError.timeout('publication of local track timed out, no response from server'),
|
|
367
386
|
);
|
|
368
387
|
}, 10_000);
|
|
369
388
|
this.pendingTrackResolvers[req.cid] = {
|
|
@@ -713,6 +732,22 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
713
732
|
// handle buffer amount low events
|
|
714
733
|
this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
|
715
734
|
this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
|
|
735
|
+
|
|
736
|
+
this.cleanupLossyDataStats();
|
|
737
|
+
this.lossyDataStatInterval = setInterval(() => {
|
|
738
|
+
this.lossyDataStatByterate = this.lossyDataStatCurrentBytes;
|
|
739
|
+
this.lossyDataStatCurrentBytes = 0;
|
|
740
|
+
|
|
741
|
+
const dc = this.dataChannelForKind(DataPacket_Kind.LOSSY);
|
|
742
|
+
if (dc) {
|
|
743
|
+
// control buffered latency to ~100ms
|
|
744
|
+
const threshold = this.lossyDataStatByterate / 10;
|
|
745
|
+
dc.bufferedAmountLowThreshold = Math.min(
|
|
746
|
+
Math.max(threshold, lossyDataChannelBufferThresholdMin),
|
|
747
|
+
lossyDataChannelBufferThresholdMax,
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}, 1000);
|
|
716
751
|
}
|
|
717
752
|
|
|
718
753
|
private handleDataChannel = async ({ channel }: RTCDataChannelEvent) => {
|
|
@@ -1191,10 +1226,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1191
1226
|
} catch (e: any) {
|
|
1192
1227
|
// TODO do we need a `failed` state here for the PC?
|
|
1193
1228
|
this.pcState = PCState.Disconnected;
|
|
1194
|
-
throw
|
|
1195
|
-
`could not establish PC connection, ${e.message}`,
|
|
1196
|
-
ConnectionErrorReason.InternalError,
|
|
1197
|
-
);
|
|
1229
|
+
throw ConnectionError.internal(`could not establish PC connection, ${e.message}`);
|
|
1198
1230
|
}
|
|
1199
1231
|
}
|
|
1200
1232
|
|
|
@@ -1282,12 +1314,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1282
1314
|
|
|
1283
1315
|
const msg = packet.toBinary();
|
|
1284
1316
|
|
|
1285
|
-
await this.waitForBufferStatusLow(kind);
|
|
1286
|
-
|
|
1287
1317
|
const dc = this.dataChannelForKind(kind);
|
|
1288
1318
|
if (dc) {
|
|
1289
1319
|
if (kind === DataPacket_Kind.RELIABLE) {
|
|
1320
|
+
await this.waitForBufferStatusLow(kind);
|
|
1290
1321
|
this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence });
|
|
1322
|
+
} else {
|
|
1323
|
+
// lossy channel, drop messages to reduce latency
|
|
1324
|
+
if (!this.isBufferStatusLow(kind)) {
|
|
1325
|
+
this.lossyDataDropCount += 1;
|
|
1326
|
+
if (this.lossyDataDropCount % 100 === 0) {
|
|
1327
|
+
this.log.warn(
|
|
1328
|
+
`dropping lossy data channel messages, total dropped: ${this.lossyDataDropCount}`,
|
|
1329
|
+
this.logContext,
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
this.lossyDataStatCurrentBytes += msg.byteLength;
|
|
1291
1335
|
}
|
|
1292
1336
|
|
|
1293
1337
|
if (this.attemptingReconnect) {
|
|
@@ -1363,10 +1407,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1363
1407
|
const transport = subscriber ? this.pcManager.subscriber : this.pcManager.publisher;
|
|
1364
1408
|
const transportName = subscriber ? 'Subscriber' : 'Publisher';
|
|
1365
1409
|
if (!transport) {
|
|
1366
|
-
throw
|
|
1367
|
-
`${transportName} connection not set`,
|
|
1368
|
-
ConnectionErrorReason.InternalError,
|
|
1369
|
-
);
|
|
1410
|
+
throw ConnectionError.internal(`${transportName} connection not set`);
|
|
1370
1411
|
}
|
|
1371
1412
|
|
|
1372
1413
|
let needNegotiation = false;
|
|
@@ -1407,9 +1448,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1407
1448
|
await sleep(50);
|
|
1408
1449
|
}
|
|
1409
1450
|
|
|
1410
|
-
throw
|
|
1451
|
+
throw ConnectionError.internal(
|
|
1411
1452
|
`could not establish ${transportName} connection, state: ${transport.getICEConnectionState()}`,
|
|
1412
|
-
ConnectionErrorReason.InternalError,
|
|
1413
1453
|
);
|
|
1414
1454
|
}
|
|
1415
1455
|
|
|
@@ -1699,8 +1739,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1699
1739
|
}
|
|
1700
1740
|
}
|
|
1701
1741
|
|
|
1702
|
-
class SignalReconnectError extends Error {}
|
|
1703
|
-
|
|
1704
1742
|
export type EngineEventCallbacks = {
|
|
1705
1743
|
connected: (joinResp: JoinResponse) => void;
|
|
1706
1744
|
disconnected: (reason?: DisconnectReason) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RegionInfo, RegionSettings } from '@livekit/protocol';
|
|
2
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { RegionInfo, RegionSettings } from '@livekit/protocol';
|
|
3
3
|
import { RegionUrlProvider } from './RegionUrlProvider';
|
|
4
4
|
import { ConnectionError, ConnectionErrorReason } from './errors';
|
|
5
5
|
|
|
@@ -180,8 +180,9 @@ describe('RegionUrlProvider', () => {
|
|
|
180
180
|
const provider = new RegionUrlProvider('wss://test.livekit.cloud', 'token');
|
|
181
181
|
fetchMock.mockResolvedValue(createMockResponse(401));
|
|
182
182
|
|
|
183
|
-
await
|
|
184
|
-
|
|
183
|
+
const error = await provider.fetchRegionSettings().catch((e) => e);
|
|
184
|
+
expect(error).toBeInstanceOf(ConnectionError);
|
|
185
|
+
expect(error).toMatchObject({
|
|
185
186
|
reason: ConnectionErrorReason.NotAllowed,
|
|
186
187
|
status: 401,
|
|
187
188
|
});
|
|
@@ -191,11 +192,9 @@ describe('RegionUrlProvider', () => {
|
|
|
191
192
|
const provider = new RegionUrlProvider('wss://test.livekit.cloud', 'token');
|
|
192
193
|
fetchMock.mockResolvedValue(createMockResponse(500));
|
|
193
194
|
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
status: 500,
|
|
198
|
-
});
|
|
195
|
+
const error = await provider.fetchRegionSettings().catch((e) => e);
|
|
196
|
+
expect(error).toBeInstanceOf(ConnectionError);
|
|
197
|
+
expect(error.reason).toBe(ConnectionErrorReason.InternalError);
|
|
199
198
|
});
|
|
200
199
|
|
|
201
200
|
it('extracts max-age from Cache-Control header', async () => {
|
|
@@ -725,7 +724,7 @@ describe('RegionUrlProvider', () => {
|
|
|
725
724
|
|
|
726
725
|
expect(error).toBeInstanceOf(ConnectionError);
|
|
727
726
|
expect(error.reason).toBe(ConnectionErrorReason.ServerUnreachable);
|
|
728
|
-
expect(error.status).toBe(
|
|
727
|
+
expect(error.status).toBe(undefined);
|
|
729
728
|
expect(error.message).toContain('Failed to fetch');
|
|
730
729
|
});
|
|
731
730
|
|
|
@@ -45,26 +45,27 @@ export class RegionUrlProvider {
|
|
|
45
45
|
const regionSettings = (await regionSettingsResponse.json()) as RegionSettings;
|
|
46
46
|
return { regionSettings, updatedAtInMs: Date.now(), maxAgeInMs };
|
|
47
47
|
} else {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
if (regionSettingsResponse.status === 401) {
|
|
49
|
+
throw ConnectionError.notAllowed(
|
|
50
|
+
`Could not fetch region settings: ${regionSettingsResponse.statusText}`,
|
|
51
|
+
regionSettingsResponse.status,
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
throw ConnectionError.internal(
|
|
55
|
+
`Could not fetch region settings: ${regionSettingsResponse.statusText}`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
59
|
} catch (e: unknown) {
|
|
57
60
|
if (e instanceof ConnectionError) {
|
|
58
61
|
// rethrow connection errors
|
|
59
62
|
throw e;
|
|
60
63
|
} else if (signal?.aborted) {
|
|
61
|
-
throw
|
|
64
|
+
throw ConnectionError.cancelled(`Region fetching was aborted`);
|
|
62
65
|
} else {
|
|
63
|
-
// wrap other errors as connection errors
|
|
64
|
-
throw
|
|
66
|
+
// wrap other errors as connection errors
|
|
67
|
+
throw ConnectionError.serverUnreachable(
|
|
65
68
|
`Could not fetch region settings, ${e instanceof Error ? `${e.name}: ${e.message}` : e}`,
|
|
66
|
-
ConnectionErrorReason.ServerUnreachable,
|
|
67
|
-
500, // using 500 as a catch-all manually set error code here
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
} finally {
|
package/src/room/Room.ts
CHANGED
|
@@ -31,8 +31,9 @@ import {
|
|
|
31
31
|
protoInt64,
|
|
32
32
|
} from '@livekit/protocol';
|
|
33
33
|
import { EventEmitter } from 'events';
|
|
34
|
-
import type TypedEmitter from 'typed-emitter';
|
|
35
34
|
import 'webrtc-adapter';
|
|
35
|
+
import type TypedEmitter from 'typed-emitter';
|
|
36
|
+
import { ensureTrailingSlash } from '../api/utils';
|
|
36
37
|
import { EncryptionEvent } from '../e2ee';
|
|
37
38
|
import { type BaseE2EEManager, E2EEManager } from '../e2ee/E2eeManager';
|
|
38
39
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
@@ -644,7 +645,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
644
645
|
}
|
|
645
646
|
|
|
646
647
|
this.setAndEmitConnectionState(ConnectionState.Connecting);
|
|
647
|
-
if (this.regionUrlProvider?.getServerUrl().toString() !== url) {
|
|
648
|
+
if (this.regionUrlProvider?.getServerUrl().toString() !== ensureTrailingSlash(url)) {
|
|
648
649
|
this.regionUrl = undefined;
|
|
649
650
|
this.regionUrlProvider = undefined;
|
|
650
651
|
}
|
|
@@ -686,7 +687,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
686
687
|
try {
|
|
687
688
|
await BackOffStrategy.getInstance().getBackOffPromise(url);
|
|
688
689
|
if (abortController.signal.aborted) {
|
|
689
|
-
throw
|
|
690
|
+
throw ConnectionError.cancelled('Connection attempt aborted');
|
|
690
691
|
}
|
|
691
692
|
await this.attemptConnection(regionUrl ?? url, token, opts, abortController);
|
|
692
693
|
this.abortController = undefined;
|
|
@@ -894,12 +895,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
894
895
|
} catch (err) {
|
|
895
896
|
await this.engine.close();
|
|
896
897
|
this.recreateEngine();
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
);
|
|
898
|
+
|
|
899
|
+
const resultingError = abortController.signal.aborted
|
|
900
|
+
? ConnectionError.cancelled('Signal connection aborted')
|
|
901
|
+
: ConnectionError.serverUnreachable('could not establish signal connection');
|
|
902
|
+
|
|
903
903
|
if (err instanceof Error) {
|
|
904
904
|
resultingError.message = `${resultingError.message}: ${err.message}`;
|
|
905
905
|
}
|
|
@@ -917,7 +917,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
917
917
|
if (abortController.signal.aborted) {
|
|
918
918
|
await this.engine.close();
|
|
919
919
|
this.recreateEngine();
|
|
920
|
-
throw
|
|
920
|
+
throw ConnectionError.cancelled(`Connection attempt aborted`);
|
|
921
921
|
}
|
|
922
922
|
|
|
923
923
|
try {
|
|
@@ -938,7 +938,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
938
938
|
window.addEventListener('beforeunload', this.onPageLeave);
|
|
939
939
|
}
|
|
940
940
|
if (isWeb()) {
|
|
941
|
-
|
|
941
|
+
window.addEventListener('freeze', this.onPageLeave);
|
|
942
942
|
}
|
|
943
943
|
this.setAndEmitConnectionState(ConnectionState.Connected);
|
|
944
944
|
this.emit(RoomEvent.Connected);
|
|
@@ -974,9 +974,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
974
974
|
this.log.warn(msg, this.logContext);
|
|
975
975
|
this.abortController?.abort(msg);
|
|
976
976
|
// in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
|
|
977
|
-
this.connectFuture?.reject?.(
|
|
978
|
-
new ConnectionError('Client initiated disconnect', ConnectionErrorReason.Cancelled),
|
|
979
|
-
);
|
|
977
|
+
this.connectFuture?.reject?.(ConnectionError.cancelled('Client initiated disconnect'));
|
|
980
978
|
this.connectFuture = undefined;
|
|
981
979
|
}
|
|
982
980
|
|
|
@@ -1925,7 +1923,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1925
1923
|
});
|
|
1926
1924
|
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
|
1927
1925
|
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
|
1928
|
-
|
|
1926
|
+
this.log.warn(`RPC Response payload too large for ${method}`);
|
|
1929
1927
|
} else {
|
|
1930
1928
|
responsePayload = response;
|
|
1931
1929
|
}
|
|
@@ -1933,7 +1931,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
|
1933
1931
|
if (error instanceof RpcError) {
|
|
1934
1932
|
responseError = error;
|
|
1935
1933
|
} else {
|
|
1936
|
-
|
|
1934
|
+
this.log.warn(
|
|
1937
1935
|
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
|
1938
1936
|
error,
|
|
1939
1937
|
);
|
|
@@ -93,7 +93,6 @@ export default class OutgoingDataStreamManager {
|
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* @internal
|
|
96
|
-
* @experimental CAUTION, might get removed in a minor release
|
|
97
96
|
*/
|
|
98
97
|
async streamText(options?: StreamTextOptions): Promise<TextStreamWriter> {
|
|
99
98
|
const streamId = options?.streamId ?? crypto.randomUUID();
|
package/src/room/errors.ts
CHANGED
|
@@ -10,6 +10,14 @@ export class LivekitError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export class SimulatedError extends LivekitError {
|
|
14
|
+
readonly name = 'simulated';
|
|
15
|
+
|
|
16
|
+
constructor(message = 'Simulated failure') {
|
|
17
|
+
super(-1, message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
export enum ConnectionErrorReason {
|
|
14
22
|
NotAllowed,
|
|
15
23
|
ServerUnreachable,
|
|
@@ -17,80 +25,189 @@ export enum ConnectionErrorReason {
|
|
|
17
25
|
Cancelled,
|
|
18
26
|
LeaveRequest,
|
|
19
27
|
Timeout,
|
|
28
|
+
WebSocket,
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
type NotAllowed = {
|
|
32
|
+
reason: ConnectionErrorReason.NotAllowed;
|
|
33
|
+
status: number;
|
|
34
|
+
context?: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type InternalError = {
|
|
38
|
+
reason: ConnectionErrorReason.InternalError;
|
|
39
|
+
status: never;
|
|
40
|
+
context?: { status?: number; statusText?: string };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type ConnectionTimeout = {
|
|
44
|
+
reason: ConnectionErrorReason.Timeout;
|
|
45
|
+
status: never;
|
|
46
|
+
context: never;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type LeaveRequest = {
|
|
50
|
+
reason: ConnectionErrorReason.LeaveRequest;
|
|
51
|
+
status: never;
|
|
52
|
+
context: DisconnectReason;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type Cancelled = {
|
|
56
|
+
reason: ConnectionErrorReason.Cancelled;
|
|
57
|
+
status: never;
|
|
58
|
+
context: never;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type ServerUnreachable = {
|
|
62
|
+
reason: ConnectionErrorReason.ServerUnreachable;
|
|
63
|
+
status?: number;
|
|
64
|
+
context?: never;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type WebSocket = {
|
|
68
|
+
reason: ConnectionErrorReason.WebSocket;
|
|
23
69
|
status?: number;
|
|
70
|
+
context?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type ConnectionErrorVariants =
|
|
74
|
+
| NotAllowed
|
|
75
|
+
| ConnectionTimeout
|
|
76
|
+
| LeaveRequest
|
|
77
|
+
| InternalError
|
|
78
|
+
| Cancelled
|
|
79
|
+
| ServerUnreachable
|
|
80
|
+
| WebSocket;
|
|
24
81
|
|
|
25
|
-
|
|
82
|
+
export class ConnectionError<
|
|
83
|
+
Variant extends ConnectionErrorVariants = ConnectionErrorVariants,
|
|
84
|
+
> extends LivekitError {
|
|
85
|
+
status?: Variant['status'];
|
|
26
86
|
|
|
27
|
-
|
|
87
|
+
context: Variant['context'];
|
|
88
|
+
|
|
89
|
+
reason: Variant['reason'];
|
|
28
90
|
|
|
29
91
|
reasonName: string;
|
|
30
92
|
|
|
31
|
-
|
|
93
|
+
readonly name = 'ConnectionError';
|
|
94
|
+
|
|
95
|
+
protected constructor(
|
|
32
96
|
message: string,
|
|
33
|
-
reason:
|
|
34
|
-
status?:
|
|
35
|
-
context?:
|
|
97
|
+
reason: Variant['reason'],
|
|
98
|
+
status?: Variant['status'],
|
|
99
|
+
context?: Variant['context'],
|
|
36
100
|
) {
|
|
37
101
|
super(1, message);
|
|
38
|
-
this.name = 'ConnectionError';
|
|
39
102
|
this.status = status;
|
|
40
103
|
this.reason = reason;
|
|
41
104
|
this.context = context;
|
|
42
105
|
this.reasonName = ConnectionErrorReason[reason];
|
|
43
106
|
}
|
|
107
|
+
|
|
108
|
+
static notAllowed(message: string, status: number, context?: unknown) {
|
|
109
|
+
return new ConnectionError<NotAllowed>(
|
|
110
|
+
message,
|
|
111
|
+
ConnectionErrorReason.NotAllowed,
|
|
112
|
+
status,
|
|
113
|
+
context,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static timeout(message: string) {
|
|
118
|
+
return new ConnectionError<ConnectionTimeout>(message, ConnectionErrorReason.Timeout);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static leaveRequest(message: string, context: DisconnectReason) {
|
|
122
|
+
return new ConnectionError<LeaveRequest>(
|
|
123
|
+
message,
|
|
124
|
+
ConnectionErrorReason.LeaveRequest,
|
|
125
|
+
undefined,
|
|
126
|
+
context,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static internal(message: string, context?: { status?: number; statusText?: string }) {
|
|
131
|
+
return new ConnectionError<InternalError>(
|
|
132
|
+
message,
|
|
133
|
+
ConnectionErrorReason.InternalError,
|
|
134
|
+
undefined,
|
|
135
|
+
context,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static cancelled(message: string) {
|
|
140
|
+
return new ConnectionError<Cancelled>(message, ConnectionErrorReason.Cancelled);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
static serverUnreachable(message: string, status?: number) {
|
|
144
|
+
return new ConnectionError<ServerUnreachable>(
|
|
145
|
+
message,
|
|
146
|
+
ConnectionErrorReason.ServerUnreachable,
|
|
147
|
+
status,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static websocket(message: string, status?: number, reason?: string) {
|
|
152
|
+
return new ConnectionError<WebSocket>(message, ConnectionErrorReason.WebSocket, status, reason);
|
|
153
|
+
}
|
|
44
154
|
}
|
|
45
155
|
|
|
46
156
|
export class DeviceUnsupportedError extends LivekitError {
|
|
157
|
+
readonly name = 'DeviceUnsupportedError';
|
|
158
|
+
|
|
47
159
|
constructor(message?: string) {
|
|
48
160
|
super(21, message ?? 'device is unsupported');
|
|
49
|
-
this.name = 'DeviceUnsupportedError';
|
|
50
161
|
}
|
|
51
162
|
}
|
|
52
163
|
|
|
53
164
|
export class TrackInvalidError extends LivekitError {
|
|
165
|
+
readonly name = 'TrackInvalidError';
|
|
166
|
+
|
|
54
167
|
constructor(message?: string) {
|
|
55
168
|
super(20, message ?? 'track is invalid');
|
|
56
|
-
this.name = 'TrackInvalidError';
|
|
57
169
|
}
|
|
58
170
|
}
|
|
59
171
|
|
|
60
172
|
export class UnsupportedServer extends LivekitError {
|
|
173
|
+
readonly name = 'UnsupportedServer';
|
|
174
|
+
|
|
61
175
|
constructor(message?: string) {
|
|
62
176
|
super(10, message ?? 'unsupported server');
|
|
63
|
-
this.name = 'UnsupportedServer';
|
|
64
177
|
}
|
|
65
178
|
}
|
|
66
179
|
|
|
67
180
|
export class UnexpectedConnectionState extends LivekitError {
|
|
181
|
+
readonly name = 'UnexpectedConnectionState';
|
|
182
|
+
|
|
68
183
|
constructor(message?: string) {
|
|
69
184
|
super(12, message ?? 'unexpected connection state');
|
|
70
|
-
this.name = 'UnexpectedConnectionState';
|
|
71
185
|
}
|
|
72
186
|
}
|
|
73
187
|
|
|
74
188
|
export class NegotiationError extends LivekitError {
|
|
189
|
+
readonly name = 'NegotiationError';
|
|
190
|
+
|
|
75
191
|
constructor(message?: string) {
|
|
76
192
|
super(13, message ?? 'unable to negotiate');
|
|
77
|
-
this.name = 'NegotiationError';
|
|
78
193
|
}
|
|
79
194
|
}
|
|
80
195
|
|
|
81
196
|
export class PublishDataError extends LivekitError {
|
|
197
|
+
readonly name = 'PublishDataError';
|
|
198
|
+
|
|
82
199
|
constructor(message?: string) {
|
|
83
200
|
super(14, message ?? 'unable to publish data');
|
|
84
|
-
this.name = 'PublishDataError';
|
|
85
201
|
}
|
|
86
202
|
}
|
|
87
203
|
|
|
88
204
|
export class PublishTrackError extends LivekitError {
|
|
205
|
+
readonly name = 'PublishTrackError';
|
|
206
|
+
|
|
89
207
|
status: number;
|
|
90
208
|
|
|
91
209
|
constructor(message: string, status: number) {
|
|
92
210
|
super(15, message);
|
|
93
|
-
this.name = 'PublishTrackError';
|
|
94
211
|
this.status = status;
|
|
95
212
|
}
|
|
96
213
|
}
|
|
@@ -100,6 +217,8 @@ export type RequestErrorReason =
|
|
|
100
217
|
| 'TimeoutError';
|
|
101
218
|
|
|
102
219
|
export class SignalRequestError extends LivekitError {
|
|
220
|
+
readonly name = 'SignalRequestError';
|
|
221
|
+
|
|
103
222
|
reason: RequestErrorReason;
|
|
104
223
|
|
|
105
224
|
reasonName: string;
|
|
@@ -136,18 +255,27 @@ export enum DataStreamErrorReason {
|
|
|
136
255
|
}
|
|
137
256
|
|
|
138
257
|
export class DataStreamError extends LivekitError {
|
|
258
|
+
readonly name = 'DataStreamError';
|
|
259
|
+
|
|
139
260
|
reason: DataStreamErrorReason;
|
|
140
261
|
|
|
141
262
|
reasonName: string;
|
|
142
263
|
|
|
143
264
|
constructor(message: string, reason: DataStreamErrorReason) {
|
|
144
265
|
super(16, message);
|
|
145
|
-
this.name = 'DataStreamError';
|
|
146
266
|
this.reason = reason;
|
|
147
267
|
this.reasonName = DataStreamErrorReason[reason];
|
|
148
268
|
}
|
|
149
269
|
}
|
|
150
270
|
|
|
271
|
+
export class SignalReconnectError extends LivekitError {
|
|
272
|
+
readonly name = 'SignalReconnectError';
|
|
273
|
+
|
|
274
|
+
constructor(message?: string) {
|
|
275
|
+
super(18, message);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
151
279
|
export enum MediaDeviceFailure {
|
|
152
280
|
// user rejected permissions
|
|
153
281
|
PermissionDenied = 'PermissionDenied',
|
|
@@ -1824,7 +1824,7 @@ export default class LocalParticipant extends Participant {
|
|
|
1824
1824
|
resolve: (responsePayload: string | null, responseError: RpcError | null) => {
|
|
1825
1825
|
clearTimeout(responseTimeoutId);
|
|
1826
1826
|
if (this.pendingAcks.has(id)) {
|
|
1827
|
-
|
|
1827
|
+
this.log.warn('RPC response received before ack', id);
|
|
1828
1828
|
this.pendingAcks.delete(id);
|
|
1829
1829
|
clearTimeout(ackTimeoutId);
|
|
1830
1830
|
}
|
|
@@ -245,9 +245,13 @@ class TokenSourceSandboxTokenServer extends TokenSourceEndpoint {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
export {
|
|
248
|
+
/** The return type of {@link TokenSource.literal} */
|
|
248
249
|
type TokenSourceLiteral,
|
|
250
|
+
/** The return type of {@link TokenSource.custom} */
|
|
249
251
|
type TokenSourceCustom,
|
|
252
|
+
/** The return type of {@link TokenSource.endpoint} */
|
|
250
253
|
type TokenSourceEndpoint,
|
|
254
|
+
/** The return type of {@link TokenSource.sandboxTokenServer} */
|
|
251
255
|
type TokenSourceSandboxTokenServer,
|
|
252
256
|
decodeTokenPayload,
|
|
253
257
|
areTokenSourceFetchOptionsEqual,
|
|
@@ -273,7 +277,7 @@ export const TokenSource = {
|
|
|
273
277
|
/**
|
|
274
278
|
* TokenSource.endpoint creates a token source that fetches credentials from a given URL using
|
|
275
279
|
* the standard endpoint format:
|
|
276
|
-
*
|
|
280
|
+
* @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
|
|
277
281
|
*/
|
|
278
282
|
endpoint(url: string, options: EndpointOptions = {}) {
|
|
279
283
|
return new TokenSourceEndpoint(url, options);
|
|
@@ -518,8 +518,6 @@ export default abstract class LocalTrack<
|
|
|
518
518
|
* Sets a processor on this track.
|
|
519
519
|
* See https://github.com/livekit/track-processors-js for example usage
|
|
520
520
|
*
|
|
521
|
-
* @experimental
|
|
522
|
-
*
|
|
523
521
|
* @param processor
|
|
524
522
|
* @param showProcessedStreamLocally
|
|
525
523
|
* @returns
|
|
@@ -592,8 +590,6 @@ export default abstract class LocalTrack<
|
|
|
592
590
|
* Stops the track processor
|
|
593
591
|
* See https://github.com/livekit/track-processors-js for example usage
|
|
594
592
|
*
|
|
595
|
-
* @experimental
|
|
596
|
-
* @returns
|
|
597
593
|
*/
|
|
598
594
|
async stopProcessor(keepElement = true) {
|
|
599
595
|
const unlock = await this.trackChangeLock.lock();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Encryption_Type } from '@livekit/protocol';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
2
3
|
import type {
|
|
3
4
|
SubscriptionError,
|
|
4
5
|
TrackInfo,
|
|
5
6
|
UpdateSubscription,
|
|
6
7
|
UpdateTrackSettings,
|
|
7
8
|
} from '@livekit/protocol';
|
|
8
|
-
import { EventEmitter } from 'events';
|
|
9
9
|
import type TypedEventEmitter from 'typed-emitter';
|
|
10
10
|
import log, { LoggerNames, getLogger } from '../../logger';
|
|
11
11
|
import { TrackEvent } from '../events';
|
|
@@ -11,9 +11,6 @@ export type ProcessorOptions<T extends Track.Kind> = {
|
|
|
11
11
|
audioContext?: AudioContext;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* @experimental
|
|
16
|
-
*/
|
|
17
14
|
export interface AudioProcessorOptions extends ProcessorOptions<Track.Kind.Audio> {
|
|
18
15
|
audioContext: AudioContext;
|
|
19
16
|
}
|
|
@@ -23,9 +20,6 @@ export interface AudioProcessorOptions extends ProcessorOptions<Track.Kind.Audio
|
|
|
23
20
|
*/
|
|
24
21
|
export interface VideoProcessorOptions extends ProcessorOptions<Track.Kind.Video> {}
|
|
25
22
|
|
|
26
|
-
/**
|
|
27
|
-
* @experimental
|
|
28
|
-
*/
|
|
29
23
|
export interface TrackProcessor<
|
|
30
24
|
T extends Track.Kind,
|
|
31
25
|
U extends ProcessorOptions<T> = ProcessorOptions<T>,
|