livekit-client 2.15.7 → 2.15.9
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/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 +253 -118
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2442 -323
- 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 +31 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts +29 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -0
- package/dist/src/api/utils.d.ts +2 -0
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/publishVideo.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/e2ee/E2eeManager.d.ts +16 -2
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +35 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +2 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +15 -0
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -0
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +3 -2
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +6 -6
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/index.d.ts +5 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +1 -0
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/options.d.ts +10 -2
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +1 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +6 -4
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +6 -3
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +3 -2
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -2
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +2 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +2 -2
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/token-source/TokenSource.d.ts +70 -0
- package/dist/src/room/token-source/TokenSource.d.ts.map +1 -0
- package/dist/src/room/token-source/types.d.ts +68 -0
- package/dist/src/room/token-source/types.d.ts.map +1 -0
- package/dist/src/room/token-source/utils.d.ts +5 -0
- package/dist/src/room/token-source/utils.d.ts.map +1 -0
- package/dist/src/room/track/LocalTrack.d.ts +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +7 -3
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +8 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/utils/camelToSnakeCase.d.ts +8 -0
- package/dist/src/utils/camelToSnakeCase.d.ts.map +1 -0
- package/dist/ts4.2/{src/api → api}/SignalClient.d.ts +31 -2
- package/dist/ts4.2/api/WebSocketStream.d.ts +29 -0
- package/dist/ts4.2/{src/api → api}/utils.d.ts +2 -0
- package/dist/ts4.2/{src/e2ee → e2ee}/E2eeManager.d.ts +16 -2
- package/dist/ts4.2/{src/e2ee → e2ee}/types.d.ts +35 -1
- package/dist/ts4.2/{src/e2ee → e2ee}/utils.d.ts +3 -0
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +15 -0
- package/dist/ts4.2/{src/e2ee → e2ee}/worker/ParticipantKeyHandler.d.ts +3 -2
- package/dist/ts4.2/{src/e2ee → e2ee}/worker/sifPayload.d.ts +6 -6
- package/dist/ts4.2/{src/index.d.ts → index.d.ts} +5 -3
- package/dist/ts4.2/{src/logger.d.ts → logger.d.ts} +1 -0
- package/dist/ts4.2/{src/options.d.ts → options.d.ts} +10 -2
- package/dist/ts4.2/{src/room → room}/PCTransport.d.ts +1 -0
- package/dist/ts4.2/{src/room → room}/PCTransportManager.d.ts +6 -4
- package/dist/ts4.2/{src/room → room}/RTCEngine.d.ts +6 -3
- package/dist/ts4.2/{src/room → room}/Room.d.ts +3 -2
- package/dist/ts4.2/{src/room → room}/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -1
- package/dist/ts4.2/{src/room → room}/errors.d.ts +2 -1
- package/dist/ts4.2/{src/room → room}/participant/Participant.d.ts +2 -2
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +71 -0
- package/dist/ts4.2/room/token-source/types.d.ts +68 -0
- package/dist/ts4.2/room/token-source/utils.d.ts +5 -0
- package/dist/ts4.2/{src/room → room}/track/LocalTrack.d.ts +1 -1
- package/dist/ts4.2/{src/room → room}/track/options.d.ts +10 -3
- package/dist/ts4.2/{src/room → room}/types.d.ts +1 -0
- package/dist/ts4.2/{src/room → room}/utils.d.ts +8 -1
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -0
- package/package.json +11 -10
- package/src/api/SignalClient.test.ts +688 -0
- package/src/api/SignalClient.ts +308 -161
- package/src/api/WebSocketStream.test.ts +625 -0
- package/src/api/WebSocketStream.ts +118 -0
- package/src/api/utils.ts +10 -0
- package/src/connectionHelper/checks/publishVideo.ts +5 -0
- package/src/connectionHelper/checks/turn.ts +1 -0
- package/src/connectionHelper/checks/webrtc.ts +1 -1
- package/src/connectionHelper/checks/websocket.ts +1 -0
- package/src/e2ee/E2eeManager.ts +94 -2
- package/src/e2ee/types.ts +44 -1
- package/src/e2ee/utils.ts +16 -0
- package/src/e2ee/worker/DataCryptor.test.ts +271 -0
- package/src/e2ee/worker/DataCryptor.ts +147 -0
- package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -3
- package/src/e2ee/worker/e2ee.worker.ts +47 -0
- package/src/e2ee/worker/sifPayload.ts +10 -6
- package/src/index.ts +16 -1
- package/src/logger.ts +1 -0
- package/src/options.ts +15 -2
- package/src/room/PCTransport.ts +7 -3
- package/src/room/PCTransportManager.ts +39 -35
- package/src/room/RTCEngine.ts +109 -22
- package/src/room/Room.ts +43 -18
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +64 -17
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -0
- package/src/room/defaults.ts +1 -0
- package/src/room/errors.ts +3 -0
- package/src/room/participant/LocalParticipant.ts +8 -6
- package/src/room/participant/Participant.ts +6 -1
- package/src/room/token-source/TokenSource.ts +285 -0
- package/src/room/token-source/types.ts +84 -0
- package/src/room/token-source/utils.test.ts +63 -0
- package/src/room/token-source/utils.ts +40 -0
- package/src/room/track/LocalAudioTrack.ts +1 -1
- package/src/room/track/LocalTrack.ts +1 -1
- package/src/room/track/options.ts +12 -4
- package/src/room/track/utils.ts +10 -2
- package/src/room/types.ts +1 -0
- package/src/room/utils.ts +37 -4
- package/src/utils/camelToSnakeCase.ts +16 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/ConnectionCheck.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/Checker.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/cloudRegion.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/connectionProtocol.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishAudio.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishVideo.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/reconnect.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/turn.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/webrtc.d.ts +0 -0
- /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/websocket.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/KeyProvider.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/constants.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/errors.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/events.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/index.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/FrameCryptor.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/e2ee.worker.d.ts +0 -0
- /package/dist/ts4.2/{src/e2ee → e2ee}/worker/naluUtils.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/DefaultReconnectPolicy.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/DeviceManager.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/ReconnectPolicy.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/RegionUrlProvider.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/attribute-typings.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/incoming/StreamReader.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/StreamWriter.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/defaults.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/events.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/LocalParticipant.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/ParticipantTrackPermission.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/RemoteParticipant.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/participant/publishUtils.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/rpc.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/stats.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/timers.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalAudioTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalTrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/LocalVideoTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteAudioTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteTrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/RemoteVideoTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/Track.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/TrackPublication.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/create.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/facingMode.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/processor/types.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/record.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/types.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/track/utils.d.ts +0 -0
- /package/dist/ts4.2/{src/test → test}/MockMediaStreamTrack.d.ts +0 -0
- /package/dist/ts4.2/{src/test → test}/mocks.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/AsyncQueue.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/browserParser.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/cloneDeep.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/dataPacketBuffer.d.ts +0 -0
- /package/dist/ts4.2/{src/utils → utils}/ttlmap.d.ts +0 -0
- /package/dist/ts4.2/{src/version.d.ts → version.d.ts} +0 -0
package/src/api/SignalClient.ts
CHANGED
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
AudioTrackFeature,
|
|
5
5
|
ClientInfo,
|
|
6
6
|
ConnectionQualityUpdate,
|
|
7
|
+
ConnectionSettings,
|
|
7
8
|
DisconnectReason,
|
|
9
|
+
JoinRequest,
|
|
8
10
|
JoinResponse,
|
|
9
11
|
LeaveRequest,
|
|
10
12
|
LeaveRequest_Action,
|
|
13
|
+
MediaSectionsRequirement,
|
|
11
14
|
MuteTrackRequest,
|
|
12
15
|
ParticipantInfo,
|
|
13
16
|
Ping,
|
|
@@ -38,6 +41,7 @@ import {
|
|
|
38
41
|
UpdateTrackSettings,
|
|
39
42
|
UpdateVideoLayers,
|
|
40
43
|
VideoLayer,
|
|
44
|
+
WrappedJoinRequest,
|
|
41
45
|
protoInt64,
|
|
42
46
|
} from '@livekit/protocol';
|
|
43
47
|
import log, { LoggerNames, getLogger } from '../logger';
|
|
@@ -46,7 +50,8 @@ import CriticalTimers from '../room/timers';
|
|
|
46
50
|
import type { LoggerOptions } from '../room/types';
|
|
47
51
|
import { getClientInfo, isReactNative, sleep } from '../room/utils';
|
|
48
52
|
import { AsyncQueue } from '../utils/AsyncQueue';
|
|
49
|
-
import {
|
|
53
|
+
import { type WebSocketConnection, WebSocketStream } from './WebSocketStream';
|
|
54
|
+
import { createRtcUrl, createValidateUrl, parseSignalResponse } from './utils';
|
|
50
55
|
|
|
51
56
|
// internal options
|
|
52
57
|
interface ConnectOpts extends SignalOptions {
|
|
@@ -65,6 +70,7 @@ export interface SignalOptions {
|
|
|
65
70
|
maxRetries: number;
|
|
66
71
|
e2eeEnabled: boolean;
|
|
67
72
|
websocketTimeout: number;
|
|
73
|
+
singlePeerConnection: boolean;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
type SignalMessage = SignalRequest['message'];
|
|
@@ -94,6 +100,9 @@ export enum SignalConnectionState {
|
|
|
94
100
|
DISCONNECTED,
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
/** specifies how much time (in ms) we allow for the ws to close its connection gracefully before continuing */
|
|
104
|
+
const MAX_WS_CLOSE_TIME = 250;
|
|
105
|
+
|
|
97
106
|
/** @internal */
|
|
98
107
|
export class SignalClient {
|
|
99
108
|
requestQueue: AsyncQueue;
|
|
@@ -151,9 +160,11 @@ export class SignalClient {
|
|
|
151
160
|
|
|
152
161
|
onRoomMoved?: (res: RoomMovedResponse) => void;
|
|
153
162
|
|
|
163
|
+
onMediaSectionsRequirement?: (requirement: MediaSectionsRequirement) => void;
|
|
164
|
+
|
|
154
165
|
connectOptions?: ConnectOpts;
|
|
155
166
|
|
|
156
|
-
ws?:
|
|
167
|
+
ws?: WebSocketStream;
|
|
157
168
|
|
|
158
169
|
get currentState() {
|
|
159
170
|
return this.state;
|
|
@@ -200,6 +211,8 @@ export class SignalClient {
|
|
|
200
211
|
|
|
201
212
|
private _requestId = 0;
|
|
202
213
|
|
|
214
|
+
private streamWriter: WritableStreamDefaultWriter<ArrayBuffer | string> | undefined;
|
|
215
|
+
|
|
203
216
|
constructor(useJSON: boolean = false, loggerOptions: LoggerOptions = {}) {
|
|
204
217
|
this.log = getLogger(loggerOptions.loggerName ?? LoggerNames.Signal);
|
|
205
218
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
|
@@ -251,39 +264,47 @@ export class SignalClient {
|
|
|
251
264
|
reconnect: true,
|
|
252
265
|
sid,
|
|
253
266
|
reconnectReason: reason,
|
|
254
|
-
})) as ReconnectResponse;
|
|
267
|
+
})) as ReconnectResponse | undefined;
|
|
255
268
|
return res;
|
|
256
269
|
}
|
|
257
270
|
|
|
258
|
-
private connect(
|
|
271
|
+
private async connect(
|
|
259
272
|
url: string,
|
|
260
273
|
token: string,
|
|
261
274
|
opts: ConnectOpts,
|
|
262
275
|
abortSignal?: AbortSignal,
|
|
263
276
|
): Promise<JoinResponse | ReconnectResponse | undefined> {
|
|
277
|
+
const unlock = await this.connectionLock.lock();
|
|
278
|
+
|
|
264
279
|
this.connectOptions = opts;
|
|
265
280
|
const clientInfo = getClientInfo();
|
|
266
|
-
const params =
|
|
281
|
+
const params = opts.singlePeerConnection
|
|
282
|
+
? createJoinRequestConnectionParams(token, clientInfo, opts)
|
|
283
|
+
: createConnectionParams(token, clientInfo, opts);
|
|
267
284
|
const rtcUrl = createRtcUrl(url, params);
|
|
268
285
|
const validateUrl = createValidateUrl(rtcUrl);
|
|
269
286
|
|
|
270
287
|
return new Promise<JoinResponse | ReconnectResponse | undefined>(async (resolve, reject) => {
|
|
271
|
-
const unlock = await this.connectionLock.lock();
|
|
272
288
|
try {
|
|
273
|
-
const
|
|
289
|
+
const timeoutAbortController = new AbortController();
|
|
290
|
+
|
|
291
|
+
const signals = abortSignal
|
|
292
|
+
? [timeoutAbortController.signal, abortSignal]
|
|
293
|
+
: [timeoutAbortController.signal];
|
|
294
|
+
|
|
295
|
+
const combinedAbort = AbortSignal.any(signals);
|
|
296
|
+
|
|
297
|
+
const abortHandler = async (event: Event) => {
|
|
274
298
|
this.close();
|
|
275
299
|
clearTimeout(wsTimeout);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
'room connection has been cancelled (signal)',
|
|
279
|
-
ConnectionErrorReason.Cancelled,
|
|
280
|
-
),
|
|
281
|
-
);
|
|
300
|
+
const target = event.currentTarget;
|
|
301
|
+
reject(target instanceof AbortSignal ? target.reason : target);
|
|
282
302
|
};
|
|
283
303
|
|
|
304
|
+
combinedAbort.addEventListener('abort', abortHandler);
|
|
305
|
+
|
|
284
306
|
const wsTimeout = setTimeout(() => {
|
|
285
|
-
|
|
286
|
-
reject(
|
|
307
|
+
timeoutAbortController.abort(
|
|
287
308
|
new ConnectionError(
|
|
288
309
|
'room connection has timed out (signal)',
|
|
289
310
|
ConnectionErrorReason.ServerUnreachable,
|
|
@@ -291,10 +312,13 @@ export class SignalClient {
|
|
|
291
312
|
);
|
|
292
313
|
}, opts.websocketTimeout);
|
|
293
314
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
315
|
+
const handleSignalConnected = (
|
|
316
|
+
connection: WebSocketConnection,
|
|
317
|
+
firstMessage?: SignalResponse,
|
|
318
|
+
) => {
|
|
319
|
+
this.handleSignalConnected(connection, wsTimeout, firstMessage);
|
|
320
|
+
};
|
|
321
|
+
|
|
298
322
|
const redactedUrl = new URL(rtcUrl);
|
|
299
323
|
if (redactedUrl.searchParams.has('access_token')) {
|
|
300
324
|
redactedUrl.searchParams.set('access_token', '<redacted>');
|
|
@@ -307,151 +331,130 @@ export class SignalClient {
|
|
|
307
331
|
if (this.ws) {
|
|
308
332
|
await this.close(false);
|
|
309
333
|
}
|
|
310
|
-
this.ws = new
|
|
311
|
-
this.ws.binaryType = 'arraybuffer';
|
|
334
|
+
this.ws = new WebSocketStream<ArrayBuffer>(rtcUrl, { signal: combinedAbort });
|
|
312
335
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this.ws.onerror = async (ev: Event) => {
|
|
318
|
-
if (this.state !== SignalConnectionState.CONNECTED) {
|
|
319
|
-
this.state = SignalConnectionState.DISCONNECTED;
|
|
320
|
-
clearTimeout(wsTimeout);
|
|
321
|
-
try {
|
|
322
|
-
const resp = await fetch(validateUrl);
|
|
323
|
-
if (resp.status.toFixed(0).startsWith('4')) {
|
|
324
|
-
const msg = await resp.text();
|
|
325
|
-
reject(new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status));
|
|
326
|
-
} else {
|
|
336
|
+
try {
|
|
337
|
+
this.ws.closed
|
|
338
|
+
.then((closeInfo) => {
|
|
339
|
+
if (this.isEstablishingConnection) {
|
|
327
340
|
reject(
|
|
328
341
|
new ConnectionError(
|
|
329
|
-
`
|
|
342
|
+
`Websocket got closed during a (re)connection attempt: ${closeInfo.reason}`,
|
|
330
343
|
ConnectionErrorReason.InternalError,
|
|
331
|
-
resp.status,
|
|
332
344
|
),
|
|
333
345
|
);
|
|
334
346
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
new ConnectionError(
|
|
338
|
-
e instanceof Error ? e.message : 'server was not reachable',
|
|
339
|
-
ConnectionErrorReason.ServerUnreachable,
|
|
340
|
-
),
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
// other errors, handle
|
|
346
|
-
this.handleWSError(ev);
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
this.ws.onmessage = async (ev: MessageEvent) => {
|
|
350
|
-
// not considered connected until JoinResponse is received
|
|
351
|
-
let resp: SignalResponse;
|
|
352
|
-
if (typeof ev.data === 'string') {
|
|
353
|
-
const json = JSON.parse(ev.data);
|
|
354
|
-
resp = SignalResponse.fromJson(json, { ignoreUnknownFields: true });
|
|
355
|
-
} else if (ev.data instanceof ArrayBuffer) {
|
|
356
|
-
resp = SignalResponse.fromBinary(new Uint8Array(ev.data));
|
|
357
|
-
} else {
|
|
358
|
-
this.log.error(
|
|
359
|
-
`could not decode websocket message: ${typeof ev.data}`,
|
|
360
|
-
this.logContext,
|
|
361
|
-
);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this.state !== SignalConnectionState.CONNECTED) {
|
|
366
|
-
let shouldProcessMessage = false;
|
|
367
|
-
// handle join message only
|
|
368
|
-
if (resp.message?.case === 'join') {
|
|
369
|
-
this.state = SignalConnectionState.CONNECTED;
|
|
370
|
-
abortSignal?.removeEventListener('abort', abortHandler);
|
|
371
|
-
this.pingTimeoutDuration = resp.message.value.pingTimeout;
|
|
372
|
-
this.pingIntervalDuration = resp.message.value.pingInterval;
|
|
373
|
-
|
|
374
|
-
if (this.pingTimeoutDuration && this.pingTimeoutDuration > 0) {
|
|
375
|
-
this.log.debug('ping config', {
|
|
347
|
+
if (closeInfo.closeCode !== 1000) {
|
|
348
|
+
this.log.warn(`websocket closed`, {
|
|
376
349
|
...this.logContext,
|
|
377
|
-
|
|
378
|
-
|
|
350
|
+
reason: closeInfo.reason,
|
|
351
|
+
code: closeInfo.closeCode,
|
|
352
|
+
wasClean: closeInfo.closeCode === 1000,
|
|
353
|
+
state: this.state,
|
|
379
354
|
});
|
|
380
|
-
this.startPingInterval();
|
|
381
355
|
}
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (resp.message?.case === 'reconnect') {
|
|
392
|
-
resolve(resp.message.value);
|
|
393
|
-
} else {
|
|
394
|
-
this.log.debug(
|
|
395
|
-
'declaring signal reconnected without reconnect response received',
|
|
396
|
-
this.logContext,
|
|
356
|
+
return;
|
|
357
|
+
})
|
|
358
|
+
.catch((reason) => {
|
|
359
|
+
if (this.isEstablishingConnection) {
|
|
360
|
+
reject(
|
|
361
|
+
new ConnectionError(
|
|
362
|
+
`Websocket error during a (re)connection attempt: ${reason}`,
|
|
363
|
+
ConnectionErrorReason.InternalError,
|
|
364
|
+
),
|
|
397
365
|
);
|
|
398
|
-
resolve(undefined);
|
|
399
|
-
shouldProcessMessage = true;
|
|
400
366
|
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
),
|
|
409
|
-
);
|
|
410
|
-
} else if (!opts.reconnect) {
|
|
411
|
-
// non-reconnect case, should receive join response first
|
|
412
|
-
reject(
|
|
413
|
-
new ConnectionError(
|
|
414
|
-
`did not receive join response, got ${resp.message?.case} instead`,
|
|
415
|
-
ConnectionErrorReason.InternalError,
|
|
416
|
-
),
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
if (!shouldProcessMessage) {
|
|
367
|
+
});
|
|
368
|
+
const connection = await this.ws.opened.catch(async (reason: unknown) => {
|
|
369
|
+
if (this.state !== SignalConnectionState.CONNECTED) {
|
|
370
|
+
this.state = SignalConnectionState.DISCONNECTED;
|
|
371
|
+
clearTimeout(wsTimeout);
|
|
372
|
+
const error = await this.handleConnectionError(reason, validateUrl);
|
|
373
|
+
reject(error);
|
|
420
374
|
return;
|
|
421
375
|
}
|
|
376
|
+
// other errors, handle
|
|
377
|
+
this.handleWSError(reason);
|
|
378
|
+
reject(reason);
|
|
379
|
+
return;
|
|
380
|
+
});
|
|
381
|
+
clearTimeout(wsTimeout);
|
|
382
|
+
if (!connection) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const signalReader = connection.readable.getReader();
|
|
386
|
+
const firstMessage = await signalReader.read();
|
|
387
|
+
signalReader.releaseLock();
|
|
388
|
+
if (!firstMessage.value) {
|
|
389
|
+
throw new ConnectionError(
|
|
390
|
+
'no message received as first message',
|
|
391
|
+
ConnectionErrorReason.InternalError,
|
|
392
|
+
);
|
|
422
393
|
}
|
|
423
394
|
|
|
424
|
-
|
|
425
|
-
|
|
395
|
+
const firstSignalResponse = parseSignalResponse(firstMessage.value);
|
|
396
|
+
|
|
397
|
+
// Validate the first message
|
|
398
|
+
const validation = this.validateFirstMessage(
|
|
399
|
+
firstSignalResponse,
|
|
400
|
+
opts.reconnect ?? false,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
if (!validation.isValid) {
|
|
404
|
+
reject(validation.error);
|
|
405
|
+
return;
|
|
426
406
|
}
|
|
427
|
-
this.handleSignalResponse(resp);
|
|
428
|
-
};
|
|
429
407
|
|
|
430
|
-
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
408
|
+
// Handle join response - set up ping configuration
|
|
409
|
+
if (firstSignalResponse.message?.case === 'join') {
|
|
410
|
+
this.pingTimeoutDuration = firstSignalResponse.message.value.pingTimeout;
|
|
411
|
+
this.pingIntervalDuration = firstSignalResponse.message.value.pingInterval;
|
|
412
|
+
|
|
413
|
+
if (this.pingTimeoutDuration && this.pingTimeoutDuration > 0) {
|
|
414
|
+
this.log.debug('ping config', {
|
|
415
|
+
...this.logContext,
|
|
416
|
+
timeout: this.pingTimeoutDuration,
|
|
417
|
+
interval: this.pingIntervalDuration,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
438
420
|
}
|
|
439
421
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
422
|
+
// Handle successful connection
|
|
423
|
+
const firstMessageToProcess = validation.shouldProcessFirstMessage
|
|
424
|
+
? firstSignalResponse
|
|
425
|
+
: undefined;
|
|
426
|
+
handleSignalConnected(connection, firstMessageToProcess);
|
|
427
|
+
resolve(validation.response);
|
|
428
|
+
} catch (e) {
|
|
429
|
+
clearTimeout(wsTimeout);
|
|
430
|
+
reject(e);
|
|
431
|
+
}
|
|
449
432
|
} finally {
|
|
450
433
|
unlock();
|
|
451
434
|
}
|
|
452
435
|
});
|
|
453
436
|
}
|
|
454
437
|
|
|
438
|
+
async startReadingLoop(
|
|
439
|
+
signalReader: ReadableStreamDefaultReader<string | ArrayBuffer>,
|
|
440
|
+
firstMessage?: SignalResponse,
|
|
441
|
+
) {
|
|
442
|
+
if (firstMessage) {
|
|
443
|
+
this.handleSignalResponse(firstMessage);
|
|
444
|
+
}
|
|
445
|
+
while (true) {
|
|
446
|
+
if (this.signalLatency) {
|
|
447
|
+
await sleep(this.signalLatency);
|
|
448
|
+
}
|
|
449
|
+
const { done, value } = await signalReader.read();
|
|
450
|
+
if (done) {
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
const resp = parseSignalResponse(value);
|
|
454
|
+
this.handleSignalResponse(resp);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
455
458
|
/** @internal */
|
|
456
459
|
resetCallbacks = () => {
|
|
457
460
|
this.onAnswer = undefined;
|
|
@@ -465,6 +468,7 @@ export class SignalClient {
|
|
|
465
468
|
this.onTokenRefresh = undefined;
|
|
466
469
|
this.onTrickle = undefined;
|
|
467
470
|
this.onClose = undefined;
|
|
471
|
+
this.onMediaSectionsRequirement = undefined;
|
|
468
472
|
};
|
|
469
473
|
|
|
470
474
|
async close(updateState: boolean = true) {
|
|
@@ -475,28 +479,16 @@ export class SignalClient {
|
|
|
475
479
|
this.state = SignalConnectionState.DISCONNECTING;
|
|
476
480
|
}
|
|
477
481
|
if (this.ws) {
|
|
478
|
-
this.ws.
|
|
479
|
-
this.ws.onopen = null;
|
|
480
|
-
this.ws.onclose = null;
|
|
482
|
+
this.ws.close({ closeCode: 1000, reason: 'Close method called on signal client' });
|
|
481
483
|
|
|
482
484
|
// calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
|
|
483
|
-
const closePromise =
|
|
484
|
-
if (this.ws) {
|
|
485
|
-
this.ws.onclose = () => {
|
|
486
|
-
resolve();
|
|
487
|
-
};
|
|
488
|
-
} else {
|
|
489
|
-
resolve();
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
if (this.ws.readyState < this.ws.CLOSING) {
|
|
494
|
-
this.ws.close();
|
|
495
|
-
// 250ms grace period for ws to close gracefully
|
|
496
|
-
await Promise.race([closePromise, sleep(250)]);
|
|
497
|
-
}
|
|
485
|
+
const closePromise = this.ws.closed;
|
|
498
486
|
this.ws = undefined;
|
|
487
|
+
this.streamWriter = undefined;
|
|
488
|
+
await Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]);
|
|
499
489
|
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
this.log.debug('websocket error while closing', { ...this.logContext, error: e });
|
|
500
492
|
} finally {
|
|
501
493
|
if (updateState) {
|
|
502
494
|
this.state = SignalConnectionState.DISCONNECTED;
|
|
@@ -675,7 +667,7 @@ export class SignalClient {
|
|
|
675
667
|
this.log.debug(`skipping signal request (type: ${message.case}) - SignalClient disconnected`);
|
|
676
668
|
return;
|
|
677
669
|
}
|
|
678
|
-
if (!this.
|
|
670
|
+
if (!this.streamWriter) {
|
|
679
671
|
this.log.error(
|
|
680
672
|
`cannot send signal request before connected, type: ${message?.case}`,
|
|
681
673
|
this.logContext,
|
|
@@ -686,9 +678,9 @@ export class SignalClient {
|
|
|
686
678
|
|
|
687
679
|
try {
|
|
688
680
|
if (this.useJSON) {
|
|
689
|
-
this.
|
|
681
|
+
await this.streamWriter.write(req.toJsonString());
|
|
690
682
|
} else {
|
|
691
|
-
this.
|
|
683
|
+
await this.streamWriter.write(req.toBinary());
|
|
692
684
|
}
|
|
693
685
|
} catch (e) {
|
|
694
686
|
this.log.error('error sending signal message', { ...this.logContext, error: e });
|
|
@@ -790,6 +782,10 @@ export class SignalClient {
|
|
|
790
782
|
if (this.onRoomMoved) {
|
|
791
783
|
this.onRoomMoved(msg.value);
|
|
792
784
|
}
|
|
785
|
+
} else if (msg.case === 'mediaSectionsRequirement') {
|
|
786
|
+
if (this.onMediaSectionsRequirement) {
|
|
787
|
+
this.onMediaSectionsRequirement(msg.value);
|
|
788
|
+
}
|
|
793
789
|
} else {
|
|
794
790
|
this.log.debug('unsupported message', { ...this.logContext, msgCase: msg.case });
|
|
795
791
|
}
|
|
@@ -818,8 +814,8 @@ export class SignalClient {
|
|
|
818
814
|
}
|
|
819
815
|
}
|
|
820
816
|
|
|
821
|
-
private handleWSError(
|
|
822
|
-
this.log.error('websocket error', { ...this.logContext, error
|
|
817
|
+
private handleWSError(error: unknown) {
|
|
818
|
+
this.log.error('websocket error', { ...this.logContext, error });
|
|
823
819
|
}
|
|
824
820
|
|
|
825
821
|
/**
|
|
@@ -872,6 +868,129 @@ export class SignalClient {
|
|
|
872
868
|
CriticalTimers.clearInterval(this.pingInterval);
|
|
873
869
|
}
|
|
874
870
|
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Handles the successful connection to the signal server
|
|
874
|
+
* @param connection The WebSocket connection
|
|
875
|
+
* @param timeoutHandle The timeout handle to clear
|
|
876
|
+
* @param firstMessage Optional first message to process
|
|
877
|
+
* @internal
|
|
878
|
+
*/
|
|
879
|
+
private handleSignalConnected(
|
|
880
|
+
connection: WebSocketConnection,
|
|
881
|
+
timeoutHandle: ReturnType<typeof setTimeout>,
|
|
882
|
+
firstMessage?: SignalResponse,
|
|
883
|
+
) {
|
|
884
|
+
this.state = SignalConnectionState.CONNECTED;
|
|
885
|
+
clearTimeout(timeoutHandle);
|
|
886
|
+
this.startPingInterval();
|
|
887
|
+
this.startReadingLoop(connection.readable.getReader(), firstMessage);
|
|
888
|
+
this.streamWriter = connection.writable.getWriter();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Validates the first message received from the signal server
|
|
893
|
+
* @param firstSignalResponse The first signal response received
|
|
894
|
+
* @param isReconnect Whether this is a reconnection attempt
|
|
895
|
+
* @returns Validation result with response or error
|
|
896
|
+
* @internal
|
|
897
|
+
*/
|
|
898
|
+
private validateFirstMessage(
|
|
899
|
+
firstSignalResponse: SignalResponse,
|
|
900
|
+
isReconnect: boolean,
|
|
901
|
+
): {
|
|
902
|
+
isValid: boolean;
|
|
903
|
+
response?: JoinResponse | ReconnectResponse;
|
|
904
|
+
error?: ConnectionError;
|
|
905
|
+
shouldProcessFirstMessage?: boolean;
|
|
906
|
+
} {
|
|
907
|
+
if (firstSignalResponse.message?.case === 'join') {
|
|
908
|
+
return {
|
|
909
|
+
isValid: true,
|
|
910
|
+
response: firstSignalResponse.message.value,
|
|
911
|
+
};
|
|
912
|
+
} else if (
|
|
913
|
+
this.state === SignalConnectionState.RECONNECTING &&
|
|
914
|
+
firstSignalResponse.message?.case !== 'leave'
|
|
915
|
+
) {
|
|
916
|
+
if (firstSignalResponse.message?.case === 'reconnect') {
|
|
917
|
+
return {
|
|
918
|
+
isValid: true,
|
|
919
|
+
response: firstSignalResponse.message.value,
|
|
920
|
+
};
|
|
921
|
+
} else {
|
|
922
|
+
// in reconnecting, any message received means signal reconnected and we still need to process it
|
|
923
|
+
this.log.debug(
|
|
924
|
+
'declaring signal reconnected without reconnect response received',
|
|
925
|
+
this.logContext,
|
|
926
|
+
);
|
|
927
|
+
return {
|
|
928
|
+
isValid: true,
|
|
929
|
+
response: undefined,
|
|
930
|
+
shouldProcessFirstMessage: true,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
} else if (this.isEstablishingConnection && firstSignalResponse.message?.case === 'leave') {
|
|
934
|
+
return {
|
|
935
|
+
isValid: false,
|
|
936
|
+
error: new ConnectionError(
|
|
937
|
+
'Received leave request while trying to (re)connect',
|
|
938
|
+
ConnectionErrorReason.LeaveRequest,
|
|
939
|
+
undefined,
|
|
940
|
+
firstSignalResponse.message.value.reason,
|
|
941
|
+
),
|
|
942
|
+
};
|
|
943
|
+
} else if (!isReconnect) {
|
|
944
|
+
// non-reconnect case, should receive join response first
|
|
945
|
+
return {
|
|
946
|
+
isValid: false,
|
|
947
|
+
error: new ConnectionError(
|
|
948
|
+
`did not receive join response, got ${firstSignalResponse.message?.case} instead`,
|
|
949
|
+
ConnectionErrorReason.InternalError,
|
|
950
|
+
),
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
isValid: false,
|
|
956
|
+
error: new ConnectionError('Unexpected first message', ConnectionErrorReason.InternalError),
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Handles WebSocket connection errors by validating with the server
|
|
962
|
+
* @param reason The error that occurred
|
|
963
|
+
* @param validateUrl The URL to validate the connection with
|
|
964
|
+
* @returns A ConnectionError with appropriate reason and status
|
|
965
|
+
* @internal
|
|
966
|
+
*/
|
|
967
|
+
private async handleConnectionError(
|
|
968
|
+
reason: unknown,
|
|
969
|
+
validateUrl: string,
|
|
970
|
+
): Promise<ConnectionError> {
|
|
971
|
+
try {
|
|
972
|
+
const resp = await fetch(validateUrl);
|
|
973
|
+
if (resp.status.toFixed(0).startsWith('4')) {
|
|
974
|
+
const msg = await resp.text();
|
|
975
|
+
return new ConnectionError(msg, ConnectionErrorReason.NotAllowed, resp.status);
|
|
976
|
+
} else if (reason instanceof ConnectionError) {
|
|
977
|
+
return reason;
|
|
978
|
+
} else {
|
|
979
|
+
return new ConnectionError(
|
|
980
|
+
`Encountered unknown websocket error during connection: ${reason}`,
|
|
981
|
+
ConnectionErrorReason.InternalError,
|
|
982
|
+
resp.status,
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
} catch (e) {
|
|
986
|
+
return e instanceof ConnectionError
|
|
987
|
+
? e
|
|
988
|
+
: new ConnectionError(
|
|
989
|
+
e instanceof Error ? e.message : 'server was not reachable',
|
|
990
|
+
ConnectionErrorReason.ServerUnreachable,
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
875
994
|
}
|
|
876
995
|
|
|
877
996
|
function fromProtoSessionDescription(sd: SessionDescription): RTCSessionDescriptionInit {
|
|
@@ -958,3 +1077,31 @@ function createConnectionParams(
|
|
|
958
1077
|
|
|
959
1078
|
return params;
|
|
960
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
function createJoinRequestConnectionParams(
|
|
1082
|
+
token: string,
|
|
1083
|
+
info: ClientInfo,
|
|
1084
|
+
opts: ConnectOpts,
|
|
1085
|
+
): URLSearchParams {
|
|
1086
|
+
const params = new URLSearchParams();
|
|
1087
|
+
params.set('access_token', token);
|
|
1088
|
+
|
|
1089
|
+
const joinRequest = new JoinRequest({
|
|
1090
|
+
clientInfo: info,
|
|
1091
|
+
connectionSettings: new ConnectionSettings({
|
|
1092
|
+
autoSubscribe: !!opts.autoSubscribe,
|
|
1093
|
+
adaptiveStream: !!opts.adaptiveStream,
|
|
1094
|
+
}),
|
|
1095
|
+
reconnect: !!opts.reconnect,
|
|
1096
|
+
participantSid: opts.sid ? opts.sid : undefined,
|
|
1097
|
+
});
|
|
1098
|
+
if (opts.reconnectReason) {
|
|
1099
|
+
joinRequest.reconnectReason = opts.reconnectReason;
|
|
1100
|
+
}
|
|
1101
|
+
const wrappedJoinRequest = new WrappedJoinRequest({
|
|
1102
|
+
joinRequest: joinRequest.toBinary(),
|
|
1103
|
+
});
|
|
1104
|
+
params.set('join_request', btoa(new TextDecoder('utf-8').decode(wrappedJoinRequest.toBinary())));
|
|
1105
|
+
|
|
1106
|
+
return params;
|
|
1107
|
+
}
|