livekit-client 2.22.3 → 2.23.0
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 +35 -0
- 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 +71 -8
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1746 -547
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +2 -1
- package/dist/livekit-client.fm.worker.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 +4 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts +1 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -1
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +12 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +14 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +100 -7
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +16 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +54 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +9 -8
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -1
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +25 -12
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +18 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +3 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +4 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/rpc/utils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +24 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +18 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +9 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +6 -0
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +11 -0
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +13 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +60 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/compressionStreamStubs.d.ts +12 -0
- package/dist/src/test/compressionStreamStubs.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +187 -185
- package/dist/ts4.2/api/SignalClientStateMachine.d.ts +85 -85
- package/dist/ts4.2/api/WebSocketStream.d.ts +34 -33
- package/dist/ts4.2/api/utils.d.ts +9 -9
- package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +30 -30
- package/dist/ts4.2/connectionHelper/checks/Checker.d.ts +62 -62
- package/dist/ts4.2/connectionHelper/checks/cloudRegion.d.ts +18 -18
- package/dist/ts4.2/connectionHelper/checks/connectionProtocol.d.ts +20 -20
- package/dist/ts4.2/connectionHelper/checks/publishAudio.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/publishVideo.d.ts +7 -7
- package/dist/ts4.2/connectionHelper/checks/reconnect.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/turn.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/webrtc.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/websocket.d.ts +6 -6
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +111 -111
- package/dist/ts4.2/e2ee/KeyProvider.d.ts +54 -54
- package/dist/ts4.2/e2ee/constants.d.ts +21 -21
- package/dist/ts4.2/e2ee/errors.d.ts +12 -12
- package/dist/ts4.2/e2ee/events.d.ts +39 -39
- package/dist/ts4.2/e2ee/index.d.ts +6 -6
- package/dist/ts4.2/e2ee/types.d.ts +231 -231
- package/dist/ts4.2/e2ee/utils.d.ts +32 -32
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +16 -16
- package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -21
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +209 -209
- package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +81 -84
- package/dist/ts4.2/e2ee/worker/e2ee.worker.d.ts +2 -2
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +39 -28
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +23 -23
- package/dist/ts4.2/frameMetadata/FrameMetadataManager.d.ts +53 -53
- package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +33 -33
- package/dist/ts4.2/frameMetadata/types.d.ts +62 -62
- package/dist/ts4.2/frameMetadata/utils.d.ts +9 -9
- package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -2
- package/dist/ts4.2/index.d.ts +76 -76
- package/dist/ts4.2/logger.d.ts +66 -66
- package/dist/ts4.2/options.d.ts +163 -149
- package/dist/ts4.2/room/BackOffStrategy.d.ts +27 -27
- package/dist/ts4.2/room/DefaultReconnectPolicy.d.ts +8 -8
- package/dist/ts4.2/room/DeviceManager.d.ts +12 -12
- package/dist/ts4.2/room/PCTransport.d.ts +268 -175
- package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
- package/dist/ts4.2/room/RTCEngine.d.ts +277 -262
- package/dist/ts4.2/room/ReconnectPolicy.d.ts +24 -24
- package/dist/ts4.2/room/RegionUrlProvider.d.ts +36 -36
- package/dist/ts4.2/room/Room.d.ts +407 -365
- package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +63 -62
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +107 -95
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -41
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -57
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -12
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -49
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -18
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +29 -29
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +80 -80
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +73 -73
- package/dist/ts4.2/room/data-stream/outgoing/StreamWriter.d.ts +16 -16
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -18
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +79 -80
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +50 -50
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +59 -59
- package/dist/ts4.2/room/data-track/frame.d.ts +22 -22
- package/dist/ts4.2/room/data-track/handle.d.ts +27 -27
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +124 -124
- package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -24
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +41 -41
- package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -20
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +126 -126
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +74 -74
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +23 -23
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +48 -48
- package/dist/ts4.2/room/data-track/packet/constants.d.ts +20 -20
- package/dist/ts4.2/room/data-track/packet/errors.d.ts +40 -40
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +69 -69
- package/dist/ts4.2/room/data-track/packet/index.d.ts +98 -98
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +13 -13
- package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -43
- package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -23
- package/dist/ts4.2/room/data-track/types.d.ts +22 -22
- package/dist/ts4.2/room/data-track/utils.d.ts +60 -60
- package/dist/ts4.2/room/debounce.d.ts +11 -11
- package/dist/ts4.2/room/defaults.d.ts +9 -9
- package/dist/ts4.2/room/errors.d.ts +180 -162
- package/dist/ts4.2/room/events.d.ts +638 -637
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +291 -287
- package/dist/ts4.2/room/participant/Participant.d.ts +142 -144
- package/dist/ts4.2/room/participant/ParticipantTrackPermission.d.ts +26 -26
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +85 -86
- package/dist/ts4.2/room/participant/publishUtils.d.ts +53 -53
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -43
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/index.d.ts +7 -7
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -44
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/utils.d.ts +131 -131
- package/dist/ts4.2/room/stats.d.ts +71 -71
- package/dist/ts4.2/room/statsSummary.d.ts +13 -13
- package/dist/ts4.2/room/timers.d.ts +12 -12
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +82 -82
- package/dist/ts4.2/room/token-source/types.d.ts +70 -70
- package/dist/ts4.2/room/token-source/utils.d.ts +8 -8
- package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -19
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +62 -42
- package/dist/ts4.2/room/track/LocalTrack.d.ts +143 -125
- package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +109 -100
- package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +61 -55
- package/dist/ts4.2/room/track/RemoteTrack.d.ts +38 -38
- package/dist/ts4.2/room/track/RemoteTrackPublication.d.ts +72 -75
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +73 -76
- package/dist/ts4.2/room/track/Track.d.ts +156 -166
- package/dist/ts4.2/room/track/TrackPublication.d.ts +75 -81
- package/dist/ts4.2/room/track/create.d.ts +25 -25
- package/dist/ts4.2/room/track/facingMode.d.ts +41 -41
- package/dist/ts4.2/room/track/options.d.ts +403 -403
- package/dist/ts4.2/room/track/processor/types.d.ts +42 -31
- package/dist/ts4.2/room/track/record.d.ts +13 -13
- package/dist/ts4.2/room/track/types.d.ts +28 -28
- package/dist/ts4.2/room/track/utils.d.ts +60 -47
- package/dist/ts4.2/room/types.d.ts +127 -127
- package/dist/ts4.2/room/utils.d.ts +273 -214
- package/dist/ts4.2/test/MockMediaStreamTrack.d.ts +26 -26
- package/dist/ts4.2/test/compressionStreamStubs.d.ts +12 -0
- package/dist/ts4.2/test/mocks.d.ts +12 -12
- package/dist/ts4.2/test/promiseState.d.ts +12 -12
- package/dist/ts4.2/test/signalToken.d.ts +26 -26
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -2
- package/dist/ts4.2/utils/AsyncQueue.d.ts +23 -23
- package/dist/ts4.2/utils/TypedPromise.d.ts +17 -17
- package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -13
- package/dist/ts4.2/utils/browserParser.d.ts +13 -13
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -8
- package/dist/ts4.2/utils/cloneDeep.d.ts +2 -2
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +34 -34
- package/dist/ts4.2/utils/deferrable-map.d.ts +32 -32
- package/dist/ts4.2/utils/machineInspector.d.ts +54 -54
- package/dist/ts4.2/utils/serializer.d.ts +48 -48
- package/dist/ts4.2/utils/subscribeToEvents.d.ts +15 -15
- package/dist/ts4.2/utils/ttlmap.d.ts +20 -20
- package/dist/ts4.2/version.d.ts +15 -15
- package/package.json +5 -4
- package/src/api/SignalClient.e2e.test.ts +1 -3
- package/src/api/SignalClient.test.ts +394 -10
- package/src/api/SignalClient.ts +132 -76
- package/src/api/WebSocketStream.test.ts +11 -0
- package/src/api/WebSocketStream.ts +22 -1
- package/src/api/utils.ts +1 -1
- package/src/e2ee/E2eeManager.test.ts +43 -0
- package/src/e2ee/E2eeManager.ts +6 -2
- package/src/e2ee/worker/FrameCryptor.test.ts +212 -90
- package/src/e2ee/worker/FrameCryptor.ts +21 -4
- package/src/e2ee/worker/naluUtils.test.ts +123 -0
- package/src/e2ee/worker/naluUtils.ts +57 -2
- package/src/index.ts +2 -0
- package/src/options.ts +16 -0
- package/src/room/DefaultReconnectPolicy.test.ts +58 -0
- package/src/room/DefaultReconnectPolicy.ts +14 -2
- package/src/room/PCTransport.test.ts +327 -9
- package/src/room/PCTransport.ts +339 -36
- package/src/room/PCTransportManager.ts +3 -1
- package/src/room/RTCEngine.test.ts +318 -2
- package/src/room/RTCEngine.ts +126 -12
- package/src/room/Room.test.ts +148 -6
- package/src/room/Room.ts +312 -80
- package/src/room/data-channel/DataChannelManager.test.ts +24 -7
- package/src/room/data-channel/DataChannelManager.ts +43 -38
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +60 -18
- package/src/room/data-channel/FlowControlledDataChannel.ts +40 -22
- package/src/room/data-channel/LossyDataChannel.test.ts +38 -0
- package/src/room/data-channel/LossyDataChannel.ts +10 -1
- package/src/room/data-channel/ReliableDataChannel.test.ts +72 -0
- package/src/room/data-channel/ReliableDataChannel.ts +16 -5
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +47 -20
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +16 -5
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +60 -39
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +4 -4
- package/src/room/defaults.ts +1 -0
- package/src/room/errors.test.ts +36 -0
- package/src/room/errors.ts +27 -0
- package/src/room/events.ts +2 -1
- package/src/room/participant/LocalParticipant.test.ts +136 -1
- package/src/room/participant/LocalParticipant.ts +66 -17
- package/src/room/participant/Participant.ts +6 -3
- package/src/room/participant/publishUtils.ts +0 -19
- package/src/room/participant/restartOnDefaultAudioDevice.test.ts +177 -0
- package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
- package/src/room/rpc/client/RpcClientManager.ts +32 -15
- package/src/room/rpc/utils.test.ts +32 -0
- package/src/room/rpc/utils.ts +7 -0
- package/src/room/track/LocalAudioTrack.ts +101 -10
- package/src/room/track/LocalTrack.ts +87 -19
- package/src/room/track/LocalVideoTrack.ts +17 -3
- package/src/room/track/RemoteAudioTrack.ts +59 -14
- package/src/room/track/RemoteVideoTrack.test.ts +210 -0
- package/src/room/track/RemoteVideoTrack.ts +29 -3
- package/src/room/track/processor/types.ts +11 -0
- package/src/room/track/utils.test.ts +206 -2
- package/src/room/track/utils.ts +117 -5
- package/src/room/utils.test.ts +250 -0
- package/src/room/utils.ts +152 -19
- package/src/test/compressionStreamStubs.ts +53 -0
package/src/api/SignalClient.ts
CHANGED
|
@@ -63,6 +63,7 @@ import type { LoggerOptions } from '../room/types';
|
|
|
63
63
|
import { getClientInfo, isCompressionStreamSupported, isReactNative, sleep } from '../room/utils';
|
|
64
64
|
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
65
65
|
import { AsyncQueue } from '../utils/AsyncQueue';
|
|
66
|
+
import { abortSignalTimeout } from '../utils/abort-signal-polyfill';
|
|
66
67
|
import { announceMachine } from '../utils/machineInspector';
|
|
67
68
|
import {
|
|
68
69
|
type SignalLifecycleState,
|
|
@@ -92,6 +93,8 @@ interface ConnectOpts extends SignalOptions {
|
|
|
92
93
|
export interface SignalOptions {
|
|
93
94
|
autoSubscribe: boolean;
|
|
94
95
|
adaptiveStream?: boolean;
|
|
96
|
+
disableIceLite?: boolean;
|
|
97
|
+
subscriberAllowPause?: boolean;
|
|
95
98
|
clientInfoCapabilities?: ClientInfo_Capability[];
|
|
96
99
|
maxRetries: number;
|
|
97
100
|
e2eeEnabled: boolean;
|
|
@@ -432,15 +435,29 @@ export class SignalClient {
|
|
|
432
435
|
): Promise<JoinResponse | ReconnectResponse | undefined> {
|
|
433
436
|
const unlock = await this.connectionLock.lock();
|
|
434
437
|
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
let rtcUrl: string;
|
|
439
|
+
let validateUrl: string;
|
|
440
|
+
try {
|
|
441
|
+
this.connectOptions = opts;
|
|
442
|
+
this.useV0SignalPath = useV0Path;
|
|
443
|
+
|
|
444
|
+
const clientInfo = getClientInfo(opts.clientInfoCapabilities);
|
|
445
|
+
const params = useV0Path
|
|
446
|
+
? createConnectionParams(token, clientInfo, opts)
|
|
447
|
+
: await createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer);
|
|
448
|
+
rtcUrl = createRtcUrl(url, params, useV0Path).toString();
|
|
449
|
+
validateUrl = createValidateUrl(rtcUrl).toString();
|
|
450
|
+
} catch (e) {
|
|
451
|
+
// the executor below owns the lock from here on
|
|
452
|
+
unlock();
|
|
453
|
+
throw e;
|
|
454
|
+
}
|
|
437
455
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const validateUrl = createValidateUrl(rtcUrl).toString();
|
|
456
|
+
if (abortSignal?.aborted) {
|
|
457
|
+
unlock();
|
|
458
|
+
this.log.debug('signal connect abandoned before opening a transport');
|
|
459
|
+
throw ConnectionError.cancelled('Connection aborted');
|
|
460
|
+
}
|
|
444
461
|
|
|
445
462
|
return new Promise<JoinResponse | ReconnectResponse | undefined>(async (resolve, reject) => {
|
|
446
463
|
try {
|
|
@@ -455,13 +472,13 @@ export class SignalClient {
|
|
|
455
472
|
// send leave if we have an active stream writer (connection is open)
|
|
456
473
|
if (this.streamWriter && !this.isDisconnected) {
|
|
457
474
|
this.sendLeave()
|
|
458
|
-
.then(() => this.close(reason))
|
|
475
|
+
.then(() => this.close(true, reason))
|
|
459
476
|
.catch((e) => {
|
|
460
477
|
this.log.error(e);
|
|
461
|
-
this.close();
|
|
478
|
+
this.close(true, reason);
|
|
462
479
|
});
|
|
463
480
|
} else {
|
|
464
|
-
this.close();
|
|
481
|
+
this.close(true, reason);
|
|
465
482
|
}
|
|
466
483
|
cleanupAbortHandlers();
|
|
467
484
|
// The caller may already have classified this failure: the connect timeout hands us a
|
|
@@ -496,6 +513,10 @@ export class SignalClient {
|
|
|
496
513
|
await this.teardownTransport('replaced by a new connection attempt');
|
|
497
514
|
this.log.debug(`closed previous ws connection in ${performance.now() - startClose}ms`);
|
|
498
515
|
}
|
|
516
|
+
if (alreadyAborted) {
|
|
517
|
+
this.log.debug('signal connect abandoned after tearing down the previous transport');
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
499
520
|
|
|
500
521
|
// the transport created below belongs to this attempt; events arriving from it after a
|
|
501
522
|
// newer attempt has started are dropped by the machine
|
|
@@ -560,6 +581,12 @@ export class SignalClient {
|
|
|
560
581
|
}
|
|
561
582
|
});
|
|
562
583
|
const connection = await this.ws.opened.catch(async (reason: unknown) => {
|
|
584
|
+
if (alreadyAborted) {
|
|
585
|
+
// the abort handler has already rejected this attempt; don't probe the validate
|
|
586
|
+
// endpoint for a failure nobody is waiting on
|
|
587
|
+
this.log.debug('signal connect abandoned while the websocket was opening');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
563
590
|
if (this.lifecycleState !== 'connected') {
|
|
564
591
|
// claimed synchronously, before the await below: the socket's close event is already
|
|
565
592
|
// on its way and would otherwise reject first with a less useful error
|
|
@@ -581,39 +608,69 @@ export class SignalClient {
|
|
|
581
608
|
this.streamWriter = connection.writable.getWriter();
|
|
582
609
|
|
|
583
610
|
// wsTimeout only guarded the upgrade; guard the first-message read with
|
|
584
|
-
// its own
|
|
585
|
-
|
|
586
|
-
|
|
611
|
+
// its own deadline so a silent server can't hang join() forever.
|
|
612
|
+
// During reconnect, drop any stray messages that arrive before the
|
|
613
|
+
// ReconnectResponse so we do not declare the channel reconnected on the
|
|
614
|
+
// wrong signal.
|
|
615
|
+
let firstSignalResponse: SignalResponse;
|
|
587
616
|
try {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
617
|
+
const deadline = Date.now() + JOIN_RESPONSE_TIMEOUT;
|
|
618
|
+
// eslint-disable-next-line no-constant-condition
|
|
619
|
+
while (true) {
|
|
620
|
+
const remaining = deadline - Date.now();
|
|
621
|
+
if (remaining <= 0) {
|
|
622
|
+
throw ConnectionError.timeout(
|
|
623
|
+
'signal connection timed out while waiting for the first message',
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
let readTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
627
|
+
const readResult = await Promise.race([
|
|
628
|
+
signalReader.read(),
|
|
629
|
+
new Promise<never>((_, rejectRead) => {
|
|
630
|
+
readTimeout = setTimeout(() => {
|
|
631
|
+
rejectRead(
|
|
632
|
+
ConnectionError.timeout(
|
|
633
|
+
'signal connection timed out while waiting for the first message',
|
|
634
|
+
),
|
|
635
|
+
);
|
|
636
|
+
}, remaining);
|
|
637
|
+
}),
|
|
638
|
+
]);
|
|
639
|
+
clearTimeout(readTimeout);
|
|
640
|
+
if (!readResult.value) {
|
|
641
|
+
throw ConnectionError.internal('no message received as first message');
|
|
642
|
+
}
|
|
643
|
+
const parsed = parseSignalResponse(readResult.value);
|
|
644
|
+
if (
|
|
645
|
+
this.lifecycleState === 'reconnecting' &&
|
|
646
|
+
parsed.message?.case !== 'reconnect' &&
|
|
647
|
+
parsed.message?.case !== 'leave'
|
|
648
|
+
) {
|
|
649
|
+
this.log.warn('dropping signal message while awaiting reconnect response', {
|
|
650
|
+
messageCase: parsed.message?.case,
|
|
651
|
+
});
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
firstSignalResponse = parsed;
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
600
657
|
} catch (e) {
|
|
601
|
-
// No first message in time: release the reader and tear down the ws
|
|
658
|
+
// No usable first message in time: release the reader and tear down the ws
|
|
602
659
|
// so we surface the timeout instead of leaking an open connection.
|
|
603
660
|
signalReader.releaseLock();
|
|
604
661
|
reject(e);
|
|
605
662
|
this.close();
|
|
606
663
|
return;
|
|
607
|
-
} finally {
|
|
608
|
-
clearTimeout(firstMessageTimeout);
|
|
609
664
|
}
|
|
610
665
|
signalReader.releaseLock();
|
|
611
|
-
if (
|
|
612
|
-
|
|
666
|
+
if (alreadyAborted) {
|
|
667
|
+
// The abort handler has already rejected this attempt and is closing the transport.
|
|
668
|
+
// Don't act on a join that raced it: `onJoined` would publish an abandoned session's
|
|
669
|
+
// state to the room.
|
|
670
|
+
this.log.debug('signal connect abandoned while waiting for the first message');
|
|
671
|
+
return;
|
|
613
672
|
}
|
|
614
673
|
|
|
615
|
-
const firstSignalResponse = parseSignalResponse(firstMessage.value);
|
|
616
|
-
|
|
617
674
|
// Validate the first message
|
|
618
675
|
const validation = this.validateFirstMessage(
|
|
619
676
|
firstSignalResponse,
|
|
@@ -643,11 +700,7 @@ export class SignalClient {
|
|
|
643
700
|
}
|
|
644
701
|
}
|
|
645
702
|
|
|
646
|
-
|
|
647
|
-
const firstMessageToProcess = validation.shouldProcessFirstMessage
|
|
648
|
-
? firstSignalResponse
|
|
649
|
-
: undefined;
|
|
650
|
-
this.handleSignalConnected(connection, wsTimeout, attemptId, firstMessageToProcess);
|
|
703
|
+
this.handleSignalConnected(connection, wsTimeout, attemptId);
|
|
651
704
|
resolve(validation.response);
|
|
652
705
|
} catch (e) {
|
|
653
706
|
reject(e);
|
|
@@ -660,13 +713,7 @@ export class SignalClient {
|
|
|
660
713
|
});
|
|
661
714
|
}
|
|
662
715
|
|
|
663
|
-
async startReadingLoop(
|
|
664
|
-
signalReader: ReadableStreamDefaultReader<string | ArrayBuffer>,
|
|
665
|
-
firstMessage?: SignalResponse,
|
|
666
|
-
) {
|
|
667
|
-
if (firstMessage) {
|
|
668
|
-
this.handleSignalResponse(firstMessage);
|
|
669
|
-
}
|
|
716
|
+
async startReadingLoop(signalReader: ReadableStreamDefaultReader<string | ArrayBuffer>) {
|
|
670
717
|
const attemptId = this.attemptId;
|
|
671
718
|
while (true) {
|
|
672
719
|
if (this.signalLatency) {
|
|
@@ -726,14 +773,15 @@ export class SignalClient {
|
|
|
726
773
|
private async teardownTransport(reason: string) {
|
|
727
774
|
try {
|
|
728
775
|
this.clearPingInterval();
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
// calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
|
|
733
|
-
const closePromise = this.ws.closed;
|
|
776
|
+
const ws = this.ws;
|
|
777
|
+
if (ws) {
|
|
778
|
+
// released before closing, so a close that throws cannot leave the transport attached
|
|
734
779
|
this.ws = undefined;
|
|
735
780
|
this.streamWriter = undefined;
|
|
736
|
-
|
|
781
|
+
ws.close({ closeCode: 1000, reason });
|
|
782
|
+
|
|
783
|
+
// calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
|
|
784
|
+
await Promise.race([ws.closed, sleep(MAX_WS_CLOSE_TIME)]);
|
|
737
785
|
}
|
|
738
786
|
} catch (e) {
|
|
739
787
|
this.log.debug('websocket error while closing', { error: e });
|
|
@@ -1099,7 +1147,11 @@ export class SignalClient {
|
|
|
1099
1147
|
* Handles a transport we lost without asking to. The client goes to `offline` rather than
|
|
1100
1148
|
* `closed`: whether this session gets resumed, restarted or given up on is the engine's call.
|
|
1101
1149
|
*/
|
|
1102
|
-
private async handleOnClose(
|
|
1150
|
+
private async handleOnClose(
|
|
1151
|
+
reason: string,
|
|
1152
|
+
attemptId: number = this.attemptId,
|
|
1153
|
+
simulateReconnectDelay?: number,
|
|
1154
|
+
) {
|
|
1103
1155
|
const onCloseCallback = this.onClose;
|
|
1104
1156
|
if (!this.sendLifecycleInput({ type: 'transportFailed', attemptId, reason })) {
|
|
1105
1157
|
// a close we caused ourselves, or one from a transport that has since been replaced: logged
|
|
@@ -1114,6 +1166,10 @@ export class SignalClient {
|
|
|
1114
1166
|
}
|
|
1115
1167
|
await this.teardownTransport(reason);
|
|
1116
1168
|
this.log.info(`websocket connection closed: ${reason}`, { reason });
|
|
1169
|
+
if (simulateReconnectDelay) {
|
|
1170
|
+
this.log.info(`waiting for ${simulateReconnectDelay}ms until reconnecting signal`);
|
|
1171
|
+
await sleep(simulateReconnectDelay);
|
|
1172
|
+
}
|
|
1117
1173
|
if (onCloseCallback) {
|
|
1118
1174
|
onCloseCallback(reason);
|
|
1119
1175
|
}
|
|
@@ -1184,7 +1240,6 @@ export class SignalClient {
|
|
|
1184
1240
|
connection: WebSocketConnection,
|
|
1185
1241
|
timeoutHandle: ReturnType<typeof setTimeout>,
|
|
1186
1242
|
attemptId: number,
|
|
1187
|
-
firstMessage?: SignalResponse,
|
|
1188
1243
|
) {
|
|
1189
1244
|
clearTimeout(timeoutHandle);
|
|
1190
1245
|
const established = this.sendLifecycleInput(
|
|
@@ -1205,7 +1260,7 @@ export class SignalClient {
|
|
|
1205
1260
|
}
|
|
1206
1261
|
this.log.info('signal connected');
|
|
1207
1262
|
this.startPingInterval();
|
|
1208
|
-
this.startReadingLoop(connection.readable.getReader()
|
|
1263
|
+
this.startReadingLoop(connection.readable.getReader());
|
|
1209
1264
|
}
|
|
1210
1265
|
|
|
1211
1266
|
/**
|
|
@@ -1222,7 +1277,6 @@ export class SignalClient {
|
|
|
1222
1277
|
isValid: boolean;
|
|
1223
1278
|
response?: JoinResponse | ReconnectResponse;
|
|
1224
1279
|
error?: ConnectionError;
|
|
1225
|
-
shouldProcessFirstMessage?: boolean;
|
|
1226
1280
|
} {
|
|
1227
1281
|
if (firstSignalResponse.message?.case === 'join') {
|
|
1228
1282
|
return {
|
|
@@ -1231,22 +1285,12 @@ export class SignalClient {
|
|
|
1231
1285
|
};
|
|
1232
1286
|
} else if (
|
|
1233
1287
|
this.lifecycleState === 'reconnecting' &&
|
|
1234
|
-
firstSignalResponse.message?.case
|
|
1288
|
+
firstSignalResponse.message?.case === 'reconnect'
|
|
1235
1289
|
) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
};
|
|
1241
|
-
} else {
|
|
1242
|
-
// in reconnecting, any message received means signal reconnected and we still need to process it
|
|
1243
|
-
this.log.debug('declaring signal reconnected without reconnect response received');
|
|
1244
|
-
return {
|
|
1245
|
-
isValid: true,
|
|
1246
|
-
response: undefined,
|
|
1247
|
-
shouldProcessFirstMessage: true,
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1290
|
+
return {
|
|
1291
|
+
isValid: true,
|
|
1292
|
+
response: firstSignalResponse.message.value,
|
|
1293
|
+
};
|
|
1250
1294
|
} else if (this.isEstablishingConnection && firstSignalResponse.message?.case === 'leave') {
|
|
1251
1295
|
return {
|
|
1252
1296
|
isValid: false,
|
|
@@ -1283,7 +1327,7 @@ export class SignalClient {
|
|
|
1283
1327
|
validateUrl: string,
|
|
1284
1328
|
): Promise<ConnectionError> {
|
|
1285
1329
|
try {
|
|
1286
|
-
const resp = await fetch(validateUrl);
|
|
1330
|
+
const resp = await fetch(validateUrl, { signal: abortSignalTimeout(3000) });
|
|
1287
1331
|
|
|
1288
1332
|
switch (resp.status) {
|
|
1289
1333
|
case 404:
|
|
@@ -1312,11 +1356,13 @@ export class SignalClient {
|
|
|
1312
1356
|
);
|
|
1313
1357
|
}
|
|
1314
1358
|
} catch (e) {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1359
|
+
if (e instanceof ConnectionError) {
|
|
1360
|
+
return e;
|
|
1361
|
+
} else {
|
|
1362
|
+
return ConnectionError.serverUnreachable(
|
|
1363
|
+
e instanceof Error ? e.message : 'server was not reachable',
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1320
1366
|
}
|
|
1321
1367
|
}
|
|
1322
1368
|
}
|
|
@@ -1394,6 +1440,14 @@ function createConnectionParams(
|
|
|
1394
1440
|
params.set('adaptive_stream', '1');
|
|
1395
1441
|
}
|
|
1396
1442
|
|
|
1443
|
+
if (opts.disableIceLite) {
|
|
1444
|
+
params.set('disable_ice_lite', '1');
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (typeof opts.subscriberAllowPause !== 'undefined') {
|
|
1448
|
+
params.set('subscriber_allow_pause', opts.subscriberAllowPause ? '1' : '0');
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1397
1451
|
if (opts.reconnectReason) {
|
|
1398
1452
|
params.set('reconnect_reason', opts.reconnectReason.toString());
|
|
1399
1453
|
}
|
|
@@ -1421,6 +1475,8 @@ async function createJoinRequestConnectionParams(
|
|
|
1421
1475
|
connectionSettings: new ConnectionSettings({
|
|
1422
1476
|
autoSubscribe: !!opts.autoSubscribe,
|
|
1423
1477
|
adaptiveStream: !!opts.adaptiveStream,
|
|
1478
|
+
disableIceLite: !!opts.disableIceLite,
|
|
1479
|
+
subscriberAllowPause: opts.subscriberAllowPause,
|
|
1424
1480
|
}),
|
|
1425
1481
|
reconnect: !!opts.reconnect,
|
|
1426
1482
|
participantSid: opts.sid ? opts.sid : undefined,
|
|
@@ -407,6 +407,17 @@ describe('WebSocketStream', () => {
|
|
|
407
407
|
wsStream.close();
|
|
408
408
|
expect(closeSpy).toHaveBeenCalledWith(undefined, undefined);
|
|
409
409
|
});
|
|
410
|
+
|
|
411
|
+
it('truncates a reason longer than WebSocket.close accepts', () => {
|
|
412
|
+
const wsStream = new WebSocketStream('wss://test.example.com');
|
|
413
|
+
|
|
414
|
+
const closeSpy = vi.spyOn(mockWebSocket, 'close');
|
|
415
|
+
|
|
416
|
+
// 'é' is two UTF-8 bytes, so the 123-byte limit falls in the middle of a character
|
|
417
|
+
wsStream.close({ closeCode: 1000, reason: 'é'.repeat(100) });
|
|
418
|
+
|
|
419
|
+
expect(closeSpy).toHaveBeenCalledWith(1000, 'é'.repeat(61));
|
|
420
|
+
});
|
|
410
421
|
});
|
|
411
422
|
|
|
412
423
|
describe('AbortSignal integration', () => {
|
|
@@ -55,7 +55,7 @@ export class WebSocketStream<T extends ArrayBuffer | string = ArrayBuffer | stri
|
|
|
55
55
|
this.ws = ws;
|
|
56
56
|
|
|
57
57
|
const closeWithInfo = ({ closeCode: code, reason }: WebSocketCloseInfo = {}) =>
|
|
58
|
-
ws.close(code, reason);
|
|
58
|
+
ws.close(code, reason === undefined ? undefined : truncateCloseReason(reason));
|
|
59
59
|
|
|
60
60
|
this.opened = new TypedPromise<WebSocketConnection<T>, WebsocketError>((resolve, reject) => {
|
|
61
61
|
const rejectHandler = () => {
|
|
@@ -140,3 +140,24 @@ export class WebSocketStream<T extends ArrayBuffer | string = ArrayBuffer | stri
|
|
|
140
140
|
this.close = closeWithInfo;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/** `WebSocket.close` throws a `SyntaxError` for a reason longer than this many UTF-8 bytes. */
|
|
145
|
+
const MAX_CLOSE_REASON_BYTES = 123;
|
|
146
|
+
|
|
147
|
+
export function truncateCloseReason(reason: string): string {
|
|
148
|
+
const encoder = new TextEncoder();
|
|
149
|
+
if (encoder.encode(reason).length <= MAX_CLOSE_REASON_BYTES) {
|
|
150
|
+
return reason;
|
|
151
|
+
}
|
|
152
|
+
// cut on code point boundaries, so a multi-byte character is never split
|
|
153
|
+
let truncated = '';
|
|
154
|
+
let bytes = 0;
|
|
155
|
+
for (const char of reason) {
|
|
156
|
+
bytes += encoder.encode(char).length;
|
|
157
|
+
if (bytes > MAX_CLOSE_REASON_BYTES) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
truncated += char;
|
|
161
|
+
}
|
|
162
|
+
return truncated;
|
|
163
|
+
}
|
package/src/api/utils.ts
CHANGED
|
@@ -44,7 +44,7 @@ export function parseSignalResponse(value: ArrayBuffer | string) {
|
|
|
44
44
|
export function getAbortReasonAsString(
|
|
45
45
|
signal: AbortSignal | Error | unknown,
|
|
46
46
|
defaultMessage = 'Unknown reason',
|
|
47
|
-
) {
|
|
47
|
+
): string {
|
|
48
48
|
// the connect timeout hands this an Error rather than a signal, and its message is the only
|
|
49
49
|
// description of what went wrong — without this a timeout reports itself as a generic abort
|
|
50
50
|
if (signal instanceof Error) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { LogLevel, getWorkerLogLevelListenerCount, setLogLevel, workerLogger } from '../logger';
|
|
3
3
|
import Room from '../room/Room';
|
|
4
|
+
import { ParticipantEvent } from '../room/events';
|
|
5
|
+
import { Track } from '../room/track/Track';
|
|
6
|
+
import type { VideoCodec } from '../room/track/options';
|
|
4
7
|
import { E2EEManager } from './E2eeManager';
|
|
5
8
|
import { BaseKeyProvider } from './KeyProvider';
|
|
6
9
|
|
|
@@ -194,3 +197,43 @@ describe('E2EEManager GC cleanup', () => {
|
|
|
194
197
|
},
|
|
195
198
|
);
|
|
196
199
|
});
|
|
200
|
+
|
|
201
|
+
describe('E2EEManager sender codec seeding', () => {
|
|
202
|
+
function publishVideo(videoCodec: VideoCodec | undefined) {
|
|
203
|
+
const { room, worker, manager } = makeManager();
|
|
204
|
+
room.localParticipant.identity = 'me';
|
|
205
|
+
manager.setup(room);
|
|
206
|
+
|
|
207
|
+
const sender = {
|
|
208
|
+
createEncodedStreams: () => ({
|
|
209
|
+
readable: new ReadableStream(),
|
|
210
|
+
writable: new WritableStream(),
|
|
211
|
+
}),
|
|
212
|
+
} as unknown as RTCRtpSender;
|
|
213
|
+
const track = {
|
|
214
|
+
isLocal: true,
|
|
215
|
+
kind: Track.Kind.Video,
|
|
216
|
+
mediaStreamID: 'ms1',
|
|
217
|
+
publishOptions: { videoCodec },
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
room.localParticipant.emit(ParticipantEvent.LocalSenderCreated, sender, track as any);
|
|
221
|
+
manager.dispose();
|
|
222
|
+
|
|
223
|
+
return worker.postMessage.mock.calls.map(([m]) => m).find((m: any) => m?.kind === 'encode');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
it('seeds the publish codec for h264', () => {
|
|
227
|
+
expect(publishVideo('h264')?.data.codec).toBe('h264');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('seeds the publish codec for h265', () => {
|
|
231
|
+
expect(publishVideo('h265')?.data.codec).toBe('h265');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('leaves vp8 unseeded so byte detection still decides', () => {
|
|
235
|
+
const encode = publishVideo('vp8');
|
|
236
|
+
expect(encode).toBeDefined();
|
|
237
|
+
expect(encode.data.codec).toBeUndefined();
|
|
238
|
+
});
|
|
239
|
+
});
|
package/src/e2ee/E2eeManager.ts
CHANGED
|
@@ -620,11 +620,15 @@ export class E2EEManager
|
|
|
620
620
|
if (!sender) this.log.warn('early return because sender is not ready');
|
|
621
621
|
return;
|
|
622
622
|
}
|
|
623
|
+
const isVideo = isVideoTrack(track);
|
|
624
|
+
// Safari exposes no per-frame codec info until trackInfo arrives. Seed only h264/h265:
|
|
625
|
+
// a wrong vp8 seed would skip the NALU path, a wrong NALU seed only costs a logged fallback.
|
|
626
|
+
const publishedCodec = isVideo ? track.publishOptions?.videoCodec : undefined;
|
|
623
627
|
this.handleSender(
|
|
624
628
|
sender,
|
|
625
629
|
track.mediaStreamID,
|
|
626
|
-
undefined,
|
|
627
|
-
|
|
630
|
+
publishedCodec === 'h264' || publishedCodec === 'h265' ? publishedCodec : undefined,
|
|
631
|
+
isVideo
|
|
628
632
|
? (track.publishOptions?.frameMetadata ?? track.publishOptions?.packetTrailer)
|
|
629
633
|
: undefined,
|
|
630
634
|
);
|