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
|
@@ -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
|
-
|
|
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?:
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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.
|
|
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": "
|
|
74
|
-
"eslint-config-airbnb-
|
|
75
|
+
"eslint": "9.39.1",
|
|
76
|
+
"eslint-config-airbnb-extended": "^2.3.2",
|
|
75
77
|
"eslint-config-prettier": "10.1.8",
|
|
76
|
-
"eslint-plugin-
|
|
77
|
-
"eslint-plugin-import": "
|
|
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 --
|
|
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
|
|
181
|
-
|
|
182
|
-
|
|
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 =
|
|
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
|
-
|
|
396
|
+
ConnectionError.leaveRequest(
|
|
397
397
|
'Received leave request while trying to (re)connect',
|
|
398
|
-
|
|
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 =
|
|
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 =
|
|
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;
|
package/src/api/SignalClient.ts
CHANGED
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
protoInt64,
|
|
46
46
|
} from '@livekit/protocol';
|
|
47
47
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
48
|
-
import { ConnectionError
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1000
|
+
return ConnectionError.notAllowed(msg, resp.status);
|
|
1014
1001
|
} else if (reason instanceof ConnectionError) {
|
|
1015
1002
|
return reason;
|
|
1016
1003
|
} else {
|
|
1017
|
-
return
|
|
1004
|
+
return ConnectionError.internal(
|
|
1018
1005
|
`Encountered unknown websocket error during connection: ${reason}`,
|
|
1019
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|
package/src/room/PCTransport.ts
CHANGED
|
@@ -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
|
|
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
|
}
|