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.
Files changed (63) hide show
  1. package/README.md +105 -1
  2. package/dist/livekit-client.e2ee.worker.js +1 -1
  3. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  4. package/dist/livekit-client.e2ee.worker.mjs +1 -0
  5. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  6. package/dist/livekit-client.esm.mjs +1079 -1329
  7. package/dist/livekit-client.esm.mjs.map +1 -1
  8. package/dist/livekit-client.umd.js +1 -1
  9. package/dist/livekit-client.umd.js.map +1 -1
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/api/utils.d.ts +1 -0
  12. package/dist/src/api/utils.d.ts.map +1 -1
  13. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  14. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  15. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  16. package/dist/src/room/RTCEngine.d.ts +5 -0
  17. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  18. package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
  19. package/dist/src/room/Room.d.ts +1 -1
  20. package/dist/src/room/Room.d.ts.map +1 -1
  21. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -1
  22. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  23. package/dist/src/room/errors.d.ts +74 -5
  24. package/dist/src/room/errors.d.ts.map +1 -1
  25. package/dist/src/room/token-source/TokenSource.d.ts +10 -2
  26. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  27. package/dist/src/room/track/LocalTrack.d.ts +0 -4
  28. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  29. package/dist/src/room/track/TrackPublication.d.ts.map +1 -1
  30. package/dist/src/room/track/processor/types.d.ts +0 -6
  31. package/dist/src/room/track/processor/types.d.ts.map +1 -1
  32. package/dist/src/room/utils.d.ts +1 -1
  33. package/dist/src/room/utils.d.ts.map +1 -1
  34. package/dist/src/test/mocks.d.ts.map +1 -1
  35. package/dist/ts4.2/api/utils.d.ts +1 -0
  36. package/dist/ts4.2/room/RTCEngine.d.ts +5 -0
  37. package/dist/ts4.2/room/Room.d.ts +1 -1
  38. package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -1
  39. package/dist/ts4.2/room/errors.d.ts +74 -5
  40. package/dist/ts4.2/room/token-source/TokenSource.d.ts +1 -1
  41. package/dist/ts4.2/room/track/LocalTrack.d.ts +0 -4
  42. package/dist/ts4.2/room/track/processor/types.d.ts +0 -6
  43. package/package.json +10 -6
  44. package/src/api/SignalClient.test.ts +12 -19
  45. package/src/api/SignalClient.ts +13 -28
  46. package/src/api/utils.ts +1 -1
  47. package/src/connectionHelper/checks/turn.ts +7 -0
  48. package/src/connectionHelper/checks/websocket.ts +40 -11
  49. package/src/room/PCTransport.ts +1 -1
  50. package/src/room/PCTransportManager.ts +4 -19
  51. package/src/room/RTCEngine.ts +56 -18
  52. package/src/room/RegionUrlProvider.test.ts +8 -9
  53. package/src/room/RegionUrlProvider.ts +13 -12
  54. package/src/room/Room.ts +14 -16
  55. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +0 -1
  56. package/src/room/errors.ts +144 -16
  57. package/src/room/participant/LocalParticipant.ts +1 -1
  58. package/src/room/token-source/TokenSource.ts +5 -1
  59. package/src/room/track/LocalTrack.ts +0 -4
  60. package/src/room/track/TrackPublication.ts +1 -1
  61. package/src/room/track/processor/types.ts +0 -6
  62. package/src/room/utils.ts +2 -1
  63. package/src/test/mocks.ts +0 -1
@@ -3,45 +3,108 @@ export declare class LivekitError extends Error {
3
3
  code: number;
4
4
  constructor(code: number, message?: string);
5
5
  }
6
+ export declare class SimulatedError extends LivekitError {
7
+ readonly name = "simulated";
8
+ constructor(message?: string);
9
+ }
6
10
  export declare enum ConnectionErrorReason {
7
11
  NotAllowed = 0,
8
12
  ServerUnreachable = 1,
9
13
  InternalError = 2,
10
14
  Cancelled = 3,
11
15
  LeaveRequest = 4,
12
- Timeout = 5
16
+ Timeout = 5,
17
+ WebSocket = 6
13
18
  }
14
- export declare class ConnectionError extends LivekitError {
19
+ type NotAllowed = {
20
+ reason: ConnectionErrorReason.NotAllowed;
21
+ status: number;
22
+ context?: unknown;
23
+ };
24
+ type InternalError = {
25
+ reason: ConnectionErrorReason.InternalError;
26
+ status: never;
27
+ context?: {
28
+ status?: number;
29
+ statusText?: string;
30
+ };
31
+ };
32
+ type ConnectionTimeout = {
33
+ reason: ConnectionErrorReason.Timeout;
34
+ status: never;
35
+ context: never;
36
+ };
37
+ type LeaveRequest = {
38
+ reason: ConnectionErrorReason.LeaveRequest;
39
+ status: never;
40
+ context: DisconnectReason;
41
+ };
42
+ type Cancelled = {
43
+ reason: ConnectionErrorReason.Cancelled;
44
+ status: never;
45
+ context: never;
46
+ };
47
+ type ServerUnreachable = {
48
+ reason: ConnectionErrorReason.ServerUnreachable;
15
49
  status?: number;
16
- context?: unknown | DisconnectReason;
17
- reason: ConnectionErrorReason;
50
+ context?: never;
51
+ };
52
+ type WebSocket = {
53
+ reason: ConnectionErrorReason.WebSocket;
54
+ status?: number;
55
+ context?: string;
56
+ };
57
+ type ConnectionErrorVariants = NotAllowed | ConnectionTimeout | LeaveRequest | InternalError | Cancelled | ServerUnreachable | WebSocket;
58
+ export declare class ConnectionError<Variant extends ConnectionErrorVariants = ConnectionErrorVariants> extends LivekitError {
59
+ status?: Variant['status'];
60
+ context: Variant['context'];
61
+ reason: Variant['reason'];
18
62
  reasonName: string;
19
- constructor(message: string, reason: ConnectionErrorReason, status?: number, context?: unknown | DisconnectReason);
63
+ readonly name = "ConnectionError";
64
+ protected constructor(message: string, reason: Variant['reason'], status?: Variant['status'], context?: Variant['context']);
65
+ static notAllowed(message: string, status: number, context?: unknown): ConnectionError<NotAllowed>;
66
+ static timeout(message: string): ConnectionError<ConnectionTimeout>;
67
+ static leaveRequest(message: string, context: DisconnectReason): ConnectionError<LeaveRequest>;
68
+ static internal(message: string, context?: {
69
+ status?: number;
70
+ statusText?: string;
71
+ }): ConnectionError<InternalError>;
72
+ static cancelled(message: string): ConnectionError<Cancelled>;
73
+ static serverUnreachable(message: string, status?: number): ConnectionError<ServerUnreachable>;
74
+ static websocket(message: string, status?: number, reason?: string): ConnectionError<WebSocket>;
20
75
  }
21
76
  export declare class DeviceUnsupportedError extends LivekitError {
77
+ readonly name = "DeviceUnsupportedError";
22
78
  constructor(message?: string);
23
79
  }
24
80
  export declare class TrackInvalidError extends LivekitError {
81
+ readonly name = "TrackInvalidError";
25
82
  constructor(message?: string);
26
83
  }
27
84
  export declare class UnsupportedServer extends LivekitError {
85
+ readonly name = "UnsupportedServer";
28
86
  constructor(message?: string);
29
87
  }
30
88
  export declare class UnexpectedConnectionState extends LivekitError {
89
+ readonly name = "UnexpectedConnectionState";
31
90
  constructor(message?: string);
32
91
  }
33
92
  export declare class NegotiationError extends LivekitError {
93
+ readonly name = "NegotiationError";
34
94
  constructor(message?: string);
35
95
  }
36
96
  export declare class PublishDataError extends LivekitError {
97
+ readonly name = "PublishDataError";
37
98
  constructor(message?: string);
38
99
  }
39
100
  export declare class PublishTrackError extends LivekitError {
101
+ readonly name = "PublishTrackError";
40
102
  status: number;
41
103
  constructor(message: string, status: number);
42
104
  }
43
105
  export type RequestErrorReason = Exclude<RequestResponse_Reason, RequestResponse_Reason.OK> | 'TimeoutError';
44
106
  export declare class SignalRequestError extends LivekitError {
107
+ readonly name = "SignalRequestError";
45
108
  reason: RequestErrorReason;
46
109
  reasonName: string;
47
110
  constructor(message: string, reason: RequestErrorReason);
@@ -56,10 +119,15 @@ export declare enum DataStreamErrorReason {
56
119
  EncryptionTypeMismatch = 8
57
120
  }
58
121
  export declare class DataStreamError extends LivekitError {
122
+ readonly name = "DataStreamError";
59
123
  reason: DataStreamErrorReason;
60
124
  reasonName: string;
61
125
  constructor(message: string, reason: DataStreamErrorReason);
62
126
  }
127
+ export declare class SignalReconnectError extends LivekitError {
128
+ readonly name = "SignalReconnectError";
129
+ constructor(message?: string);
130
+ }
63
131
  export declare enum MediaDeviceFailure {
64
132
  PermissionDenied = "PermissionDenied",
65
133
  NotFound = "NotFound",
@@ -69,4 +137,5 @@ export declare enum MediaDeviceFailure {
69
137
  export declare namespace MediaDeviceFailure {
70
138
  function getFailure(error: any): MediaDeviceFailure | undefined;
71
139
  }
140
+ export {};
72
141
  //# sourceMappingURL=errors.d.ts.map
@@ -56,7 +56,7 @@ export declare const TokenSource: {
56
56
  /**
57
57
  * TokenSource.endpoint creates a token source that fetches credentials from a given URL using
58
58
  * the standard endpoint format:
59
- * FIXME: add docs link here in the future!
59
+ * @see https://cloud.livekit.io/projects/p_/sandbox/templates/token-server
60
60
  */
61
61
  endpoint(url: string, options?: EndpointOptions): TokenSourceEndpoint;
62
62
  /**
@@ -87,8 +87,6 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
87
87
  * Sets a processor on this track.
88
88
  * See https://github.com/livekit/track-processors-js for example usage
89
89
  *
90
- * @experimental
91
- *
92
90
  * @param processor
93
91
  * @param showProcessedStreamLocally
94
92
  * @returns
@@ -99,8 +97,6 @@ export default abstract class LocalTrack<TrackKind extends Track.Kind = Track.Ki
99
97
  * Stops the track processor
100
98
  * See https://github.com/livekit/track-processors-js for example usage
101
99
  *
102
- * @experimental
103
- * @returns
104
100
  */
105
101
  stopProcessor(keepElement?: boolean): Promise<void>;
106
102
  /**
@@ -9,9 +9,6 @@ export type ProcessorOptions<T extends Track.Kind> = {
9
9
  element?: HTMLMediaElement;
10
10
  audioContext?: AudioContext;
11
11
  };
12
- /**
13
- * @experimental
14
- */
15
12
  export interface AudioProcessorOptions extends ProcessorOptions<Track.Kind.Audio> {
16
13
  audioContext: AudioContext;
17
14
  }
@@ -20,9 +17,6 @@ export interface AudioProcessorOptions extends ProcessorOptions<Track.Kind.Audio
20
17
  */
21
18
  export interface VideoProcessorOptions extends ProcessorOptions<Track.Kind.Video> {
22
19
  }
23
- /**
24
- * @experimental
25
- */
26
20
  export interface TrackProcessor<T extends Track.Kind, U extends ProcessorOptions<T> = ProcessorOptions<T>> {
27
21
  name: string;
28
22
  init: (opts: U) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.16.0",
3
+ "version": "2.16.1",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -55,6 +55,7 @@
55
55
  "@babel/preset-env": "7.28.5",
56
56
  "@bufbuild/protoc-gen-es": "^1.10.0",
57
57
  "@changesets/cli": "2.29.7",
58
+ "@eslint/js": "9.39.1",
58
59
  "@livekit/changesets-changelog-github": "^0.0.4",
59
60
  "@rollup/plugin-babel": "6.1.0",
60
61
  "@rollup/plugin-commonjs": "28.0.9",
@@ -63,6 +64,7 @@
63
64
  "@rollup/plugin-terser": "^0.4.4",
64
65
  "@size-limit/file": "^11.2.0",
65
66
  "@size-limit/webpack": "^11.2.0",
67
+ "@stylistic/eslint-plugin": "^3.1.0",
66
68
  "@trivago/prettier-plugin-sort-imports": "^5.0.0",
67
69
  "@types/events": "^3.0.3",
68
70
  "@types/sdp-transform": "2.15.0",
@@ -70,11 +72,12 @@
70
72
  "@typescript-eslint/eslint-plugin": "7.18.0",
71
73
  "@typescript-eslint/parser": "7.18.0",
72
74
  "downlevel-dts": "^0.11.0",
73
- "eslint": "8.57.1",
74
- "eslint-config-airbnb-typescript": "18.0.0",
75
+ "eslint": "9.39.1",
76
+ "eslint-config-airbnb-extended": "^2.3.2",
75
77
  "eslint-config-prettier": "10.1.8",
76
- "eslint-plugin-ecmascript-compat": "^3.2.1",
77
- "eslint-plugin-import": "2.32.0",
78
+ "eslint-plugin-compat": "^6.0.2",
79
+ "eslint-plugin-import-x": "^4.16.1",
80
+ "eslint-plugin-prettier": "^5.5.4",
78
81
  "gh-pages": "6.3.0",
79
82
  "happy-dom": "^17.2.0",
80
83
  "jsdom": "^26.1.0",
@@ -86,6 +89,7 @@
86
89
  "typedoc": "0.28.14",
87
90
  "typedoc-plugin-no-inherit": "1.6.1",
88
91
  "typescript": "5.8.3",
92
+ "typescript-eslint": "^8.47.0",
89
93
  "vite": "7.2.2",
90
94
  "vitest": "^3.0.0"
91
95
  },
@@ -105,7 +109,7 @@
105
109
  "format:check": "prettier --check src examples/**/*.ts",
106
110
  "ci:publish": "pnpm build:clean && pnpm compat && changeset publish",
107
111
  "downlevel-dts": "downlevel-dts ./dist/src ./dist/ts4.2 --to=4.2",
108
- "compat": "eslint --no-eslintrc --config ./.eslintrc.dist.cjs ./dist/livekit-client.umd.js",
112
+ "compat": "eslint --config ./eslint.config.dist.mjs --no-inline-config ./dist/livekit-client.esm.mjs",
109
113
  "size-limit": "size-limit"
110
114
  }
111
115
  }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ DisconnectReason,
2
3
  JoinResponse,
3
4
  LeaveRequest,
4
5
  ReconnectResponse,
@@ -177,9 +178,12 @@ describe('SignalClient.connect', () => {
177
178
  websocketTimeout: 100,
178
179
  };
179
180
 
180
- await expect(
181
- signalClient.join('wss://test.livekit.io', 'test-token', shortTimeoutOptions),
182
- ).rejects.toThrow(ConnectionError);
181
+ const error = await signalClient
182
+ .join('wss://test.livekit.io', 'test-token', shortTimeoutOptions)
183
+ .catch((e) => e);
184
+
185
+ expect(error).toBeInstanceOf(ConnectionError);
186
+ expect(error.reason).toBe(ConnectionErrorReason.Cancelled);
183
187
  });
184
188
  });
185
189
 
@@ -333,11 +337,7 @@ describe('SignalClient.connect', () => {
333
337
  });
334
338
 
335
339
  it('should handle ConnectionError from WebSocket rejection', async () => {
336
- const customError = new ConnectionError(
337
- 'Custom error',
338
- ConnectionErrorReason.InternalError,
339
- 500,
340
- );
340
+ const customError = ConnectionError.internal('Custom error', { status: 500 });
341
341
 
342
342
  mockWebSocketStream({
343
343
  opened: Promise.reject(customError),
@@ -393,11 +393,9 @@ describe('SignalClient.connect', () => {
393
393
  await expect(
394
394
  signalClient.join('wss://test.livekit.io', 'test-token', defaultOptions),
395
395
  ).rejects.toMatchObject(
396
- new ConnectionError(
396
+ ConnectionError.leaveRequest(
397
397
  'Received leave request while trying to (re)connect',
398
- ConnectionErrorReason.LeaveRequest,
399
- undefined,
400
- 1,
398
+ DisconnectReason.CLIENT_INITIATED,
401
399
  ),
402
400
  );
403
401
  });
@@ -700,11 +698,7 @@ describe('SignalClient.handleConnectionError', () => {
700
698
  });
701
699
 
702
700
  it('should return ConnectionError as-is if it is already a ConnectionError', async () => {
703
- const connectionError = new ConnectionError(
704
- 'Custom error',
705
- ConnectionErrorReason.InternalError,
706
- 500,
707
- );
701
+ const connectionError = ConnectionError.internal('Custom error');
708
702
 
709
703
  (global.fetch as any).mockResolvedValueOnce({
710
704
  status: 500,
@@ -737,7 +731,6 @@ describe('SignalClient.handleConnectionError', () => {
737
731
 
738
732
  expect(result).toBeInstanceOf(ConnectionError);
739
733
  expect(result.reason).toBe(ConnectionErrorReason.InternalError);
740
- expect(result.status).toBe(500);
741
734
  }
742
735
  });
743
736
 
@@ -755,7 +748,7 @@ describe('SignalClient.handleConnectionError', () => {
755
748
  });
756
749
 
757
750
  it('should handle fetch throwing ConnectionError', async () => {
758
- const fetchError = new ConnectionError('Fetch failed', ConnectionErrorReason.ServerUnreachable);
751
+ const fetchError = ConnectionError.serverUnreachable('Fetch failed');
759
752
  (global.fetch as any).mockRejectedValueOnce(fetchError);
760
753
 
761
754
  const handleMethod = (signalClient as any).handleConnectionError;
@@ -45,7 +45,7 @@ import {
45
45
  protoInt64,
46
46
  } from '@livekit/protocol';
47
47
  import log, { LoggerNames, getLogger } from '../logger';
48
- import { ConnectionError, ConnectionErrorReason } from '../room/errors';
48
+ import { ConnectionError } from '../room/errors';
49
49
  import CriticalTimers from '../room/timers';
50
50
  import type { LoggerOptions } from '../room/types';
51
51
  import { getClientInfo, isReactNative, sleep } from '../room/utils';
@@ -319,7 +319,7 @@ export class SignalClient {
319
319
  this.close();
320
320
  }
321
321
  cleanupAbortHandlers();
322
- reject(target instanceof AbortSignal ? target.reason : target);
322
+ reject(ConnectionError.cancelled(reason));
323
323
  };
324
324
 
325
325
  abortSignal?.addEventListener('abort', abortHandler);
@@ -330,12 +330,7 @@ export class SignalClient {
330
330
  };
331
331
 
332
332
  const wsTimeout = setTimeout(() => {
333
- abortHandler(
334
- new ConnectionError(
335
- 'room connection has timed out (signal)',
336
- ConnectionErrorReason.ServerUnreachable,
337
- ),
338
- );
333
+ abortHandler(ConnectionError.timeout('room connection has timed out (signal)'));
339
334
  }, opts.websocketTimeout);
340
335
 
341
336
  const handleSignalConnected = (
@@ -364,9 +359,8 @@ export class SignalClient {
364
359
  .then((closeInfo) => {
365
360
  if (this.isEstablishingConnection) {
366
361
  reject(
367
- new ConnectionError(
362
+ ConnectionError.internal(
368
363
  `Websocket got closed during a (re)connection attempt: ${closeInfo.reason}`,
369
- ConnectionErrorReason.InternalError,
370
364
  ),
371
365
  );
372
366
  }
@@ -387,9 +381,8 @@ export class SignalClient {
387
381
  .catch((reason) => {
388
382
  if (this.isEstablishingConnection) {
389
383
  reject(
390
- new ConnectionError(
384
+ ConnectionError.internal(
391
385
  `Websocket error during a (re)connection attempt: ${reason}`,
392
- ConnectionErrorReason.InternalError,
393
386
  ),
394
387
  );
395
388
  }
@@ -416,10 +409,7 @@ export class SignalClient {
416
409
  const firstMessage = await signalReader.read();
417
410
  signalReader.releaseLock();
418
411
  if (!firstMessage.value) {
419
- throw new ConnectionError(
420
- 'no message received as first message',
421
- ConnectionErrorReason.InternalError,
422
- );
412
+ throw ConnectionError.internal('no message received as first message');
423
413
  }
424
414
 
425
415
  const firstSignalResponse = parseSignalResponse(firstMessage.value);
@@ -971,10 +961,8 @@ export class SignalClient {
971
961
  } else if (this.isEstablishingConnection && firstSignalResponse.message?.case === 'leave') {
972
962
  return {
973
963
  isValid: false,
974
- error: new ConnectionError(
964
+ error: ConnectionError.leaveRequest(
975
965
  'Received leave request while trying to (re)connect',
976
- ConnectionErrorReason.LeaveRequest,
977
- undefined,
978
966
  firstSignalResponse.message.value.reason,
979
967
  ),
980
968
  };
@@ -982,16 +970,15 @@ export class SignalClient {
982
970
  // non-reconnect case, should receive join response first
983
971
  return {
984
972
  isValid: false,
985
- error: new ConnectionError(
973
+ error: ConnectionError.internal(
986
974
  `did not receive join response, got ${firstSignalResponse.message?.case} instead`,
987
- ConnectionErrorReason.InternalError,
988
975
  ),
989
976
  };
990
977
  }
991
978
 
992
979
  return {
993
980
  isValid: false,
994
- error: new ConnectionError('Unexpected first message', ConnectionErrorReason.InternalError),
981
+ error: ConnectionError.internal('Unexpected first message'),
995
982
  };
996
983
  }
997
984
 
@@ -1010,22 +997,20 @@ export class SignalClient {
1010
997
  const resp = await fetch(validateUrl);
1011
998
  if (resp.status.toFixed(0).startsWith('4')) {
1012
999
  const msg = await resp.text();
1013
- return new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status);
1000
+ return ConnectionError.notAllowed(msg, resp.status);
1014
1001
  } else if (reason instanceof ConnectionError) {
1015
1002
  return reason;
1016
1003
  } else {
1017
- return new ConnectionError(
1004
+ return ConnectionError.internal(
1018
1005
  `Encountered unknown websocket error during connection: ${reason}`,
1019
- ConnectionErrorReason.InternalError,
1020
- resp.status,
1006
+ { status: resp.status, statusText: resp.statusText },
1021
1007
  );
1022
1008
  }
1023
1009
  } catch (e) {
1024
1010
  return e instanceof ConnectionError
1025
1011
  ? e
1026
- : new ConnectionError(
1012
+ : ConnectionError.serverUnreachable(
1027
1013
  e instanceof Error ? e.message : 'server was not reachable',
1028
- ConnectionErrorReason.ServerUnreachable,
1029
1014
  );
1030
1015
  }
1031
1016
  }
package/src/api/utils.ts CHANGED
@@ -14,7 +14,7 @@ export function createValidateUrl(rtcWsUrl: string) {
14
14
  return appendUrlPath(urlObj, 'validate');
15
15
  }
16
16
 
17
- function ensureTrailingSlash(path: string) {
17
+ export function ensureTrailingSlash(path: string) {
18
18
  return path.endsWith('/') ? path : `${path}/`;
19
19
  }
20
20
 
@@ -1,4 +1,6 @@
1
1
  import { SignalClient } from '../../api/SignalClient';
2
+ import { RegionUrlProvider } from '../../room/RegionUrlProvider';
3
+ import { isCloud } from '../../room/utils';
2
4
  import { Checker } from './Checker';
3
5
 
4
6
  export class TURNCheck extends Checker {
@@ -7,6 +9,11 @@ export class TURNCheck extends Checker {
7
9
  }
8
10
 
9
11
  async perform(): Promise<void> {
12
+ if (isCloud(new URL(this.url))) {
13
+ this.appendMessage('Using region specific url');
14
+ this.url =
15
+ (await new RegionUrlProvider(this.url, this.token).getNextBestRegionUrl()) ?? this.url;
16
+ }
10
17
  const signalClient = new SignalClient();
11
18
  const joinRes = await signalClient.join(this.url, this.token, {
12
19
  autoSubscribe: true,
@@ -1,5 +1,7 @@
1
- import { ServerInfo_Edition } from '@livekit/protocol';
1
+ import { JoinResponse, ServerInfo_Edition } from '@livekit/protocol';
2
2
  import { SignalClient } from '../../api/SignalClient';
3
+ import { RegionUrlProvider } from '../../room/RegionUrlProvider';
4
+ import { isCloud } from '../../room/utils';
3
5
  import { Checker } from './Checker';
4
6
 
5
7
  export class WebSocketCheck extends Checker {
@@ -13,16 +15,43 @@ export class WebSocketCheck extends Checker {
13
15
  }
14
16
 
15
17
  let signalClient = new SignalClient();
16
- const joinRes = await signalClient.join(this.url, this.token, {
17
- autoSubscribe: true,
18
- maxRetries: 0,
19
- e2eeEnabled: false,
20
- websocketTimeout: 15_000,
21
- singlePeerConnection: false,
22
- });
23
- this.appendMessage(`Connected to server, version ${joinRes.serverVersion}.`);
24
- if (joinRes.serverInfo?.edition === ServerInfo_Edition.Cloud && joinRes.serverInfo?.region) {
25
- this.appendMessage(`LiveKit Cloud: ${joinRes.serverInfo?.region}`);
18
+ let joinRes: JoinResponse | undefined;
19
+ try {
20
+ joinRes = await signalClient.join(this.url, this.token, {
21
+ autoSubscribe: true,
22
+ maxRetries: 0,
23
+ e2eeEnabled: false,
24
+ websocketTimeout: 15_000,
25
+ singlePeerConnection: false,
26
+ });
27
+ } catch (e: any) {
28
+ if (isCloud(new URL(this.url))) {
29
+ this.appendMessage(
30
+ `Initial connection failed with error ${e.message}. Retrying with region fallback`,
31
+ );
32
+ const regionProvider = new RegionUrlProvider(this.url, this.token);
33
+ const regionUrl = await regionProvider.getNextBestRegionUrl();
34
+ if (regionUrl) {
35
+ joinRes = await signalClient.join(regionUrl, this.token, {
36
+ autoSubscribe: true,
37
+ maxRetries: 0,
38
+ e2eeEnabled: false,
39
+ websocketTimeout: 15_000,
40
+ singlePeerConnection: false,
41
+ });
42
+ this.appendMessage(
43
+ `Fallback to region worked. To avoid initial connections failing, ensure you're calling room.prepareConnection() ahead of time`,
44
+ );
45
+ }
46
+ }
47
+ }
48
+ if (joinRes) {
49
+ this.appendMessage(`Connected to server, version ${joinRes.serverVersion}.`);
50
+ if (joinRes.serverInfo?.edition === ServerInfo_Edition.Cloud && joinRes.serverInfo?.region) {
51
+ this.appendMessage(`LiveKit Cloud: ${joinRes.serverInfo?.region}`);
52
+ }
53
+ } else {
54
+ this.appendError(`Websocket connection could not be established`);
26
55
  }
27
56
  await signalClient.close();
28
57
  }
@@ -1,8 +1,8 @@
1
1
  import { Mutex } from '@livekit/mutex';
2
2
  import { EventEmitter } from 'events';
3
- import type { MediaDescription, SessionDescription } from 'sdp-transform';
4
3
  import { parse, write } from 'sdp-transform';
5
4
  import { debounce } from 'ts-debounce';
5
+ import type { MediaDescription, SessionDescription } from 'sdp-transform';
6
6
  import log, { LoggerNames, getLogger } from '../logger';
7
7
  import { NegotiationError, UnexpectedConnectionState } from './errors';
8
8
  import type { LoggerOptions } from './types';
@@ -3,7 +3,7 @@ import { SignalTarget } from '@livekit/protocol';
3
3
  import log, { LoggerNames, getLogger } from '../logger';
4
4
  import PCTransport, { PCEvents } from './PCTransport';
5
5
  import { roomConnectOptionDefaults } from './defaults';
6
- import { ConnectionError, ConnectionErrorReason } from './errors';
6
+ import { ConnectionError } from './errors';
7
7
  import CriticalTimers from './timers';
8
8
  import type { LoggerOptions } from './types';
9
9
  import { sleep } from './utils';
@@ -345,12 +345,7 @@ export class PCTransportManager {
345
345
  this.log.warn('abort transport connection', this.logContext);
346
346
  CriticalTimers.clearTimeout(connectTimeout);
347
347
 
348
- reject(
349
- new ConnectionError(
350
- 'room connection has been cancelled',
351
- ConnectionErrorReason.Cancelled,
352
- ),
353
- );
348
+ reject(ConnectionError.cancelled('room connection has been cancelled'));
354
349
  };
355
350
  if (abortController?.signal.aborted) {
356
351
  abortHandler();
@@ -359,23 +354,13 @@ export class PCTransportManager {
359
354
 
360
355
  const connectTimeout = CriticalTimers.setTimeout(() => {
361
356
  abortController?.signal.removeEventListener('abort', abortHandler);
362
- reject(
363
- new ConnectionError(
364
- 'could not establish pc connection',
365
- ConnectionErrorReason.InternalError,
366
- ),
367
- );
357
+ reject(ConnectionError.internal('could not establish pc connection'));
368
358
  }, timeout);
369
359
 
370
360
  while (this.state !== PCTransportState.CONNECTED) {
371
361
  await sleep(50); // FIXME we shouldn't rely on `sleep` in the connection paths, as it invokes `setTimeout` which can be drastically throttled by browser implementations
372
362
  if (abortController?.signal.aborted) {
373
- reject(
374
- new ConnectionError(
375
- 'room connection has been cancelled',
376
- ConnectionErrorReason.Cancelled,
377
- ),
378
- );
363
+ reject(ConnectionError.cancelled('room connection has been cancelled'));
379
364
  return;
380
365
  }
381
366
  }