livekit-client 2.22.2 → 2.22.4
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 +488 -438
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +954 -301
- 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 +10 -2
- 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.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/e2ee/E2eeManager.d.ts +26 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +15 -1
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts +21 -0
- package/dist/src/e2ee/worker/ErrorRateLimiter.d.ts.map +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -18
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/logger.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/PCTransport.d.ts +78 -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 +9 -0
- package/dist/src/room/RTCEngine.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/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.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +16 -0
- 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/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/options.d.ts +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 +66 -0
- 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 +33 -33
- package/dist/ts4.2/api/utils.d.ts +9 -8
- 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 -85
- 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 -217
- 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 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +209 -226
- 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 +28 -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 -62
- 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 +246 -175
- package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
- package/dist/ts4.2/room/RTCEngine.d.ts +269 -261
- 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 +354 -365
- package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -62
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +94 -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 +287 -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 -37
- 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 +39 -42
- package/dist/ts4.2/room/track/LocalTrack.d.ts +125 -125
- package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +102 -93
- package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +55 -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 +31 -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 +246 -180
- 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 +160 -10
- package/src/api/SignalClient.ts +75 -54
- package/src/api/WebSocketStream.ts +3 -8
- package/src/api/utils.ts +10 -0
- package/src/e2ee/E2eeManager.test.ts +196 -0
- package/src/e2ee/E2eeManager.ts +114 -19
- package/src/e2ee/types.ts +19 -1
- package/src/e2ee/worker/DataCryptor.ts +2 -1
- package/src/e2ee/worker/ErrorRateLimiter.test.ts +53 -0
- package/src/e2ee/worker/ErrorRateLimiter.ts +52 -0
- package/src/e2ee/worker/FrameCryptor.ts +20 -70
- package/src/e2ee/worker/e2ee.worker.ts +34 -11
- package/src/logger.ts +22 -0
- package/src/options.ts +16 -0
- package/src/room/PCTransport.test.ts +183 -7
- package/src/room/PCTransport.ts +197 -36
- package/src/room/PCTransportManager.ts +3 -1
- package/src/room/RTCEngine.test.ts +84 -0
- package/src/room/RTCEngine.ts +52 -12
- package/src/room/Room.test.ts +32 -6
- package/src/room/Room.ts +27 -7
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +16 -20
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +5 -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.ts +30 -14
- package/src/room/participant/publishUtils.test.ts +133 -0
- package/src/room/participant/publishUtils.ts +54 -38
- package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
- package/src/room/rpc/client/RpcClientManager.ts +32 -15
- package/src/room/track/LocalTrack.ts +18 -7
- package/src/room/track/LocalVideoTrack.ts +25 -8
- package/src/room/track/RemoteAudioTrack.ts +3 -3
- package/src/room/track/RemoteVideoTrack.test.ts +210 -0
- package/src/room/track/RemoteVideoTrack.ts +29 -3
- package/src/room/track/options.ts +1 -1
- package/src/room/track/utils.test.ts +201 -2
- package/src/room/track/utils.ts +88 -0
- package/src/room/utils.test.ts +211 -0
- package/src/room/utils.ts +129 -0
- package/src/test/compressionStreamStubs.ts +53 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TODO code inspired by https://github.com/webrtc/samples/blob/gh-pages/src/content/insertable-streams/endtoend-encryption/js/worker.js
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type TypedEventEmitter from 'typed-emitter';
|
|
4
|
+
import { getErrorDescription } from '../../api/utils';
|
|
4
5
|
import {
|
|
5
6
|
appendPacketTrailerToEncodedFrame,
|
|
6
7
|
processPacketTrailer,
|
|
@@ -22,6 +23,7 @@ import type {
|
|
|
22
23
|
RatchetResult,
|
|
23
24
|
} from '../types';
|
|
24
25
|
import { deriveKeys, isVideoFrame, needsRbspUnescaping, parseRbsp, writeRbsp } from '../utils';
|
|
26
|
+
import { ErrorRateLimiter } from './ErrorRateLimiter';
|
|
25
27
|
import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
|
26
28
|
import { processNALUsForEncryption } from './naluUtils';
|
|
27
29
|
import { identifySifPayload } from './sifPayload';
|
|
@@ -111,18 +113,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
111
113
|
|
|
112
114
|
private frameMetadataFrameId = 0;
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
* Throttling mechanism for decryption errors to prevent memory leaks
|
|
116
|
-
*/
|
|
117
|
-
private lastErrorTimestamp: Map<string, number> = new Map();
|
|
118
|
-
|
|
119
|
-
private errorCounts: Map<string, number> = new Map();
|
|
120
|
-
|
|
121
|
-
private readonly ERROR_THROTTLE_MS = 1000; // Emit error at most once per second
|
|
122
|
-
|
|
123
|
-
private readonly MAX_ERRORS_PER_MINUTE = 5; // Maximum errors to emit per minute per key
|
|
124
|
-
|
|
125
|
-
private readonly ERROR_WINDOW_MS = 60000; // 1 minute window
|
|
116
|
+
private errorLimiter = new ErrorRateLimiter();
|
|
126
117
|
|
|
127
118
|
private undecryptedTrackTimeout?: ReturnType<typeof setTimeout>;
|
|
128
119
|
|
|
@@ -196,8 +187,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
196
187
|
clearTimeout(this.undecryptedTrackTimeout);
|
|
197
188
|
this.undecryptedTrackTimeout = undefined;
|
|
198
189
|
this.participantIdentity = undefined;
|
|
199
|
-
this.
|
|
200
|
-
this.errorCounts = new Map();
|
|
190
|
+
this.errorLimiter.reset();
|
|
201
191
|
}
|
|
202
192
|
|
|
203
193
|
isEnabled() {
|
|
@@ -421,64 +411,24 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
421
411
|
this.sifTrailer = trailer;
|
|
422
412
|
}
|
|
423
413
|
|
|
424
|
-
/**
|
|
425
|
-
* Checks if we should emit an error based on throttling rules to prevent memory leaks
|
|
426
|
-
* @param errorKey - unique key identifying the error context
|
|
427
|
-
* @returns true if the error should be emitted, false otherwise
|
|
428
|
-
*/
|
|
429
|
-
private shouldEmitError(errorKey: string): boolean {
|
|
430
|
-
const now = Date.now();
|
|
431
|
-
const lastErrorTime = this.lastErrorTimestamp.get(errorKey) ?? 0;
|
|
432
|
-
const errorCount = this.errorCounts.get(errorKey) ?? 0;
|
|
433
|
-
|
|
434
|
-
// Reset count if we're in a new time window
|
|
435
|
-
if (now - lastErrorTime > this.ERROR_WINDOW_MS) {
|
|
436
|
-
this.errorCounts.set(errorKey, 0);
|
|
437
|
-
this.lastErrorTimestamp.set(errorKey, now);
|
|
438
|
-
return true;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Check if we've exceeded the throttle time
|
|
442
|
-
if (now - lastErrorTime < this.ERROR_THROTTLE_MS) {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Check if we've exceeded the max errors per window
|
|
447
|
-
if (errorCount >= this.MAX_ERRORS_PER_MINUTE) {
|
|
448
|
-
// Only log a warning once when hitting the limit
|
|
449
|
-
if (errorCount === this.MAX_ERRORS_PER_MINUTE) {
|
|
450
|
-
workerLogger.warn(`Suppressing further decryption errors for ${this.participantIdentity}`, {
|
|
451
|
-
...this.logContext,
|
|
452
|
-
errorKey,
|
|
453
|
-
});
|
|
454
|
-
this.errorCounts.set(errorKey, errorCount + 1);
|
|
455
|
-
}
|
|
456
|
-
return false;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Update tracking
|
|
460
|
-
this.lastErrorTimestamp.set(errorKey, now);
|
|
461
|
-
this.errorCounts.set(errorKey, errorCount + 1);
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Emits a throttled error to prevent memory leaks from repeated decryption failures
|
|
467
|
-
* @param error - the CryptorError to emit
|
|
468
|
-
*/
|
|
469
414
|
private emitThrottledError(error: CryptorError) {
|
|
470
415
|
const errorKey = `${this.participantIdentity}-${error.reason}-decrypt`;
|
|
416
|
+
const emit = this.errorLimiter.shouldEmit(errorKey, () => {
|
|
417
|
+
workerLogger.warn(`Suppressing further decryption errors for ${this.participantIdentity}`, {
|
|
418
|
+
...this.logContext,
|
|
419
|
+
errorKey,
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
if (!emit) return;
|
|
471
423
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
this.emit(CryptorEvent.Error, error);
|
|
424
|
+
const count = this.errorLimiter.countFor(errorKey);
|
|
425
|
+
if (count > 1) {
|
|
426
|
+
workerLogger.debug(`Decryption error (${count} occurrences in window)`, {
|
|
427
|
+
...this.logContext,
|
|
428
|
+
reason: CryptorErrorReason[error.reason],
|
|
429
|
+
});
|
|
481
430
|
}
|
|
431
|
+
this.emit(CryptorEvent.Error, error);
|
|
482
432
|
}
|
|
483
433
|
|
|
484
434
|
/**
|
|
@@ -587,7 +537,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
587
537
|
return controller.enqueue(encodedFrame);
|
|
588
538
|
} catch (e: any) {
|
|
589
539
|
// TODO: surface this to the app.
|
|
590
|
-
workerLogger.error(e);
|
|
540
|
+
workerLogger.error(`error while encrypting`, { ...this.logContext, error: e });
|
|
591
541
|
}
|
|
592
542
|
} else {
|
|
593
543
|
workerLogger.debug('failed to encrypt, emitting error', this.logContext);
|
|
@@ -858,7 +808,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
|
858
808
|
}
|
|
859
809
|
} else {
|
|
860
810
|
throw new CryptorError(
|
|
861
|
-
`Decryption failed: ${error
|
|
811
|
+
`Decryption failed: ${getErrorDescription(error, 'decryption')}`,
|
|
862
812
|
CryptorErrorReason.InvalidKey,
|
|
863
813
|
this.participantIdentity,
|
|
864
814
|
);
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
ScriptTransformOptions,
|
|
19
19
|
} from '../types';
|
|
20
20
|
import { DataCryptor } from './DataCryptor';
|
|
21
|
+
import { ErrorRateLimiter } from './ErrorRateLimiter';
|
|
21
22
|
import { FrameCryptor, encryptionEnabledMap } from './FrameCryptor';
|
|
22
23
|
import { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
|
23
24
|
|
|
@@ -36,8 +37,21 @@ let keyProviderOptions: KeyProviderOptions = KEY_PROVIDER_DEFAULTS;
|
|
|
36
37
|
|
|
37
38
|
let rtpMap: Map<number, VideoCodec> = new Map();
|
|
38
39
|
|
|
40
|
+
const dataDecryptErrorLimiter = new ErrorRateLimiter();
|
|
41
|
+
|
|
39
42
|
workerLogger.setDefaultLevel('info');
|
|
40
43
|
|
|
44
|
+
// Forward worker log calls to the main thread so they reach any
|
|
45
|
+
// setLogExtension consumer installed there. The main-thread workerLogger
|
|
46
|
+
// re-emits them, which invokes both the console and the extension.
|
|
47
|
+
workerLogger.methodFactory = (methodName) => (msg, context) => {
|
|
48
|
+
postMessage({
|
|
49
|
+
kind: 'log',
|
|
50
|
+
data: { level: methodName, msg, context },
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
workerLogger.setLevel(workerLogger.getLevel());
|
|
54
|
+
|
|
41
55
|
onmessage = (ev) => {
|
|
42
56
|
messageQueue.run(async () => {
|
|
43
57
|
const { kind, data }: E2EEWorkerMessage = ev.data;
|
|
@@ -45,7 +59,7 @@ onmessage = (ev) => {
|
|
|
45
59
|
switch (kind) {
|
|
46
60
|
case 'init':
|
|
47
61
|
workerLogger.setLevel(data.loglevel);
|
|
48
|
-
workerLogger.info('worker initialized');
|
|
62
|
+
workerLogger.info('e2ee worker initialized');
|
|
49
63
|
keyProviderOptions = data.keyProviderOptions;
|
|
50
64
|
useSharedKey = !!data.keyProviderOptions.sharedKey;
|
|
51
65
|
// acknowledge init successful
|
|
@@ -55,6 +69,9 @@ onmessage = (ev) => {
|
|
|
55
69
|
};
|
|
56
70
|
postMessage(ackMsg);
|
|
57
71
|
break;
|
|
72
|
+
case 'setLogLevel':
|
|
73
|
+
workerLogger.setLevel(data.level);
|
|
74
|
+
break;
|
|
58
75
|
case 'enable':
|
|
59
76
|
setEncryptionEnabled(data.enabled, data.participantIdentity);
|
|
60
77
|
workerLogger.info(
|
|
@@ -96,11 +113,6 @@ onmessage = (ev) => {
|
|
|
96
113
|
data.payload,
|
|
97
114
|
getParticipantKeyHandler(data.participantIdentity),
|
|
98
115
|
);
|
|
99
|
-
console.log('encrypted payload', {
|
|
100
|
-
original: data.payload,
|
|
101
|
-
encrypted: encryptedPayload,
|
|
102
|
-
iv,
|
|
103
|
-
});
|
|
104
116
|
postMessage({
|
|
105
117
|
kind: 'encryptDataResponse',
|
|
106
118
|
data: {
|
|
@@ -125,12 +137,23 @@ onmessage = (ev) => {
|
|
|
125
137
|
data: { payload: decryptedPayload, uuid: data.uuid },
|
|
126
138
|
} satisfies DecryptDataResponseMessage);
|
|
127
139
|
} catch (error) {
|
|
128
|
-
// Send error back to main thread with uuid so it can reject the corresponding promise
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
// Send error back to main thread with uuid so it can reject the corresponding promise.
|
|
141
|
+
// The error response must always be posted so the awaiting future resolves; only the
|
|
142
|
+
// log is throttled to avoid flooding when a broken key keeps producing failures.
|
|
143
|
+
const errorKey = `${data.participantIdentity}-datadecrypt`;
|
|
144
|
+
const shouldLog = dataDecryptErrorLimiter.shouldEmit(errorKey, () => {
|
|
145
|
+
workerLogger.warn(
|
|
146
|
+
`Suppressing further data decryption errors for ${data.participantIdentity}`,
|
|
147
|
+
{ errorKey },
|
|
148
|
+
);
|
|
133
149
|
});
|
|
150
|
+
if (shouldLog) {
|
|
151
|
+
workerLogger.error('DataCryptor decryption failed', {
|
|
152
|
+
error,
|
|
153
|
+
participantIdentity: data.participantIdentity,
|
|
154
|
+
uuid: data.uuid,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
134
157
|
postMessage({
|
|
135
158
|
kind: 'error',
|
|
136
159
|
data: {
|
package/src/logger.ts
CHANGED
|
@@ -131,3 +131,25 @@ export function setLogExtension(extension: LogExtension, logger?: StructuredLogg
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
export const workerLogger = log.getLogger(LoggerNames.E2EE) as StructuredLogger;
|
|
134
|
+
|
|
135
|
+
const workerLogLevelListeners = new Set<(level: LogLevel) => void>();
|
|
136
|
+
|
|
137
|
+
const originalWorkerSetLevel = workerLogger.setLevel.bind(workerLogger);
|
|
138
|
+
workerLogger.setLevel = ((level: log.LogLevelDesc, persist?: boolean) => {
|
|
139
|
+
originalWorkerSetLevel(level, persist);
|
|
140
|
+
const numeric = workerLogger.getLevel() as LogLevel;
|
|
141
|
+
workerLogLevelListeners.forEach((cb) => cb(numeric));
|
|
142
|
+
}) as typeof workerLogger.setLevel;
|
|
143
|
+
|
|
144
|
+
/** @internal Subscribe to workerLogger level changes (so E2EE workers can be kept in sync). */
|
|
145
|
+
export function onWorkerLogLevelChanged(cb: (level: LogLevel) => void): () => void {
|
|
146
|
+
workerLogLevelListeners.add(cb);
|
|
147
|
+
return () => {
|
|
148
|
+
workerLogLevelListeners.delete(cb);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @internal Test-only accessor: current number of workerLogger level listeners. */
|
|
153
|
+
export function getWorkerLogLevelListenerCount(): number {
|
|
154
|
+
return workerLogLevelListeners.size;
|
|
155
|
+
}
|
package/src/options.ts
CHANGED
|
@@ -166,6 +166,22 @@ export interface InternalRoomConnectOptions {
|
|
|
166
166
|
*/
|
|
167
167
|
rtcConfig?: RTCConfiguration;
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* when the server runs ICE lite, asks it to use a full ICE agent for this participant.
|
|
171
|
+
* this is a server-side setting that the server may ignore, defaults to false
|
|
172
|
+
*/
|
|
173
|
+
disableIceLite: boolean;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* whether the SFU may pause subscribed video tracks when this participant's downstream
|
|
177
|
+
* connection is congested, so that other tracks can continue to stream smoothly.
|
|
178
|
+
* paused tracks resume automatically when conditions allow, and `RoomEvent.TrackStreamStateChanged`
|
|
179
|
+
* fires on each transition.
|
|
180
|
+
* when left undefined, the server's default applies (the project setting on LiveKit Cloud,
|
|
181
|
+
* or `rtc.congestion_control.allow_pause` when self-hosting)
|
|
182
|
+
*/
|
|
183
|
+
subscriberAllowPause?: boolean;
|
|
184
|
+
|
|
169
185
|
/** specifies how often an initial join connection is allowed to retry (only applicable if server is not reachable) */
|
|
170
186
|
maxRetries: number;
|
|
171
187
|
|
|
@@ -2,11 +2,15 @@ import { type MediaDescription, parse } from 'sdp-transform';
|
|
|
2
2
|
import { describe, expect, it } from 'vitest';
|
|
3
3
|
import {
|
|
4
4
|
applyVideoStartBitrate,
|
|
5
|
+
computeConnectionStartBitrate,
|
|
6
|
+
computeTrackStartBitrate,
|
|
5
7
|
conformBundledCodecFmtp,
|
|
6
8
|
ensureAudioNackAndStereo,
|
|
7
9
|
ensureVideoDDExtension,
|
|
8
10
|
extractStereoAndNackAudioFromOffer,
|
|
11
|
+
findTrackCodecPayload,
|
|
9
12
|
fmtpConfigHasParam,
|
|
13
|
+
isDuplicateAnswer,
|
|
10
14
|
placeholderMidsFromTransceivers,
|
|
11
15
|
} from './PCTransport';
|
|
12
16
|
import { ddExtensionURI } from './utils';
|
|
@@ -61,9 +65,7 @@ a=recvonly
|
|
|
61
65
|
a=rtpmap:49 H265/90000
|
|
62
66
|
a=fmtp:49 level-id=180;profile-id=1;tier-flag=0;tx-mode=SRST`;
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
it('applies the bitrate only to the section whose msid track ID matches the cid', () => {
|
|
66
|
-
const { media } = parse(`v=0
|
|
68
|
+
const TWO_VIDEO_SECTIONS = `v=0
|
|
67
69
|
o=- 0 0 IN IP4 127.0.0.1
|
|
68
70
|
s=-
|
|
69
71
|
t=0 0
|
|
@@ -79,15 +81,159 @@ c=IN IP4 0.0.0.0
|
|
|
79
81
|
a=mid:1
|
|
80
82
|
a=sendonly
|
|
81
83
|
a=msid:PA_remote|camera camera-cid
|
|
82
|
-
a=rtpmap:96 VP8/90000
|
|
84
|
+
a=rtpmap:96 VP8/90000`;
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
// The same bundle after the screen share is unpublished: `unpublishTrack` sets the
|
|
87
|
+
// transceiver to `inactive`, but the section keeps its `a=msid`, so it still matches the
|
|
88
|
+
// append-only trackBitrates entry. Only the camera is still sending.
|
|
89
|
+
const UNPUBLISHED_SCREEN_SHARE = `v=0
|
|
90
|
+
o=- 0 0 IN IP4 127.0.0.1
|
|
91
|
+
s=-
|
|
92
|
+
t=0 0
|
|
93
|
+
a=group:BUNDLE 0 1
|
|
94
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
95
|
+
c=IN IP4 0.0.0.0
|
|
96
|
+
a=mid:0
|
|
97
|
+
a=inactive
|
|
98
|
+
a=msid:PA_remote|camera other-track
|
|
99
|
+
a=rtpmap:96 VP8/90000
|
|
100
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
101
|
+
c=IN IP4 0.0.0.0
|
|
102
|
+
a=mid:1
|
|
103
|
+
a=sendonly
|
|
104
|
+
a=msid:PA_remote|camera camera-cid
|
|
105
|
+
a=rtpmap:96 VP8/90000`;
|
|
106
|
+
|
|
107
|
+
// The legacy `addTrack` fallback (no `addTransceiver` support) reuses a transceiver instead
|
|
108
|
+
// of creating a sendonly one, so the published camera lands on a `sendrecv` section. Mid 1
|
|
109
|
+
// omits the direction attribute entirely, which SDP also defaults to sendrecv. Both send.
|
|
110
|
+
const LEGACY_ADD_TRACK_SECTIONS = `v=0
|
|
111
|
+
o=- 0 0 IN IP4 127.0.0.1
|
|
112
|
+
s=-
|
|
113
|
+
t=0 0
|
|
114
|
+
a=group:BUNDLE 0 1
|
|
115
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
116
|
+
c=IN IP4 0.0.0.0
|
|
117
|
+
a=mid:0
|
|
118
|
+
a=sendrecv
|
|
119
|
+
a=msid:PA_remote|camera camera-cid
|
|
120
|
+
a=rtpmap:96 VP8/90000
|
|
121
|
+
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
122
|
+
c=IN IP4 0.0.0.0
|
|
123
|
+
a=mid:1
|
|
124
|
+
a=msid:PA_remote|camera other-track
|
|
125
|
+
a=rtpmap:96 VP8/90000`;
|
|
126
|
+
|
|
127
|
+
describe('video start bitrate', () => {
|
|
128
|
+
it('matches only the section whose msid track ID matches the cid', () => {
|
|
129
|
+
const { media } = parse(TWO_VIDEO_SECTIONS);
|
|
130
|
+
|
|
131
|
+
expect(findTrackCodecPayload(media[0], 'camera-cid', 'VP8')).toBeUndefined();
|
|
132
|
+
expect(findTrackCodecPayload(media[1], 'camera-cid', 'VP8')).toBe(96);
|
|
133
|
+
// Section belongs to the track but does not offer the codec.
|
|
134
|
+
expect(findTrackCodecPayload(media[1], 'camera-cid', 'AV1')).toBe(0);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('applies the bitrate only to the section it is given', () => {
|
|
138
|
+
const { media } = parse(TWO_VIDEO_SECTIONS);
|
|
139
|
+
|
|
140
|
+
applyVideoStartBitrate(media[1], 96, 900);
|
|
87
141
|
|
|
88
142
|
expect(fmtpOf(media, '0', 96)).toBeUndefined();
|
|
89
143
|
expect(paramSet(fmtpOf(media, '1', 96)!)).toContain('x-google-start-bitrate=900');
|
|
90
144
|
});
|
|
145
|
+
|
|
146
|
+
it('caps camera at 1 Mbps but leaves screen share uncapped', () => {
|
|
147
|
+
const camera = { cid: 'c', codec: 'VP8', maxbr: 3_000 };
|
|
148
|
+
const screenShare = { ...camera, isScreenShare: true };
|
|
149
|
+
|
|
150
|
+
expect(computeTrackStartBitrate(camera)).toBe(1_000);
|
|
151
|
+
expect(computeTrackStartBitrate(screenShare)).toBe(2_700);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('gives no hint below the 300 kbps target floor', () => {
|
|
155
|
+
expect(computeTrackStartBitrate({ cid: 'c', codec: 'VP8', maxbr: 299 })).toBeUndefined();
|
|
156
|
+
expect(computeTrackStartBitrate({ cid: 'c', codec: 'VP8', maxbr: 300 })).toBe(270);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('uses one connection-level value: the largest hint across video sections', () => {
|
|
160
|
+
const { media } = parse(TWO_VIDEO_SECTIONS);
|
|
161
|
+
|
|
162
|
+
const startBitrate = computeConnectionStartBitrate(media, [
|
|
163
|
+
{ cid: 'camera-cid', codec: 'VP8', maxbr: 1_000 },
|
|
164
|
+
{ cid: 'other-track', codec: 'VP8', maxbr: 3_000, isScreenShare: true },
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
expect(startBitrate).toBe(2_700);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('ignores registered tracks with no section in the current SDP', () => {
|
|
171
|
+
const { media } = parse(TWO_VIDEO_SECTIONS);
|
|
172
|
+
|
|
173
|
+
const startBitrate = computeConnectionStartBitrate(media, [
|
|
174
|
+
{ cid: 'camera-cid', codec: 'VP8', maxbr: 1_000 },
|
|
175
|
+
// Stale entry: trackBitrates is append-only and outlives an unpublish.
|
|
176
|
+
{ cid: 'unpublished-cid', codec: 'VP8', maxbr: 8_000, isScreenShare: true },
|
|
177
|
+
]);
|
|
178
|
+
|
|
179
|
+
expect(startBitrate).toBe(900);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('ignores a section that stopped sending but kept its msid', () => {
|
|
183
|
+
const trackBitrates = [
|
|
184
|
+
{ cid: 'camera-cid', codec: 'VP8', maxbr: 1_000 },
|
|
185
|
+
{ cid: 'other-track', codec: 'VP8', maxbr: 8_000, isScreenShare: true },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
// While both send, the uncapped screen share wins the connection-level max.
|
|
189
|
+
expect(computeConnectionStartBitrate(parse(TWO_VIDEO_SECTIONS).media, trackBitrates)).toBe(
|
|
190
|
+
7_200,
|
|
191
|
+
);
|
|
192
|
+
// Once it is unpublished its entry is stale, so only the capped camera counts. Both
|
|
193
|
+
// directions a removed sender can land on are excluded: `inactive` from a sendonly
|
|
194
|
+
// transceiver, `recvonly` from the sendrecv one the addTrack fallback reuses.
|
|
195
|
+
expect(
|
|
196
|
+
computeConnectionStartBitrate(parse(UNPUBLISHED_SCREEN_SHARE).media, trackBitrates),
|
|
197
|
+
).toBe(900);
|
|
198
|
+
expect(
|
|
199
|
+
computeConnectionStartBitrate(
|
|
200
|
+
parse(UNPUBLISHED_SCREEN_SHARE.replace('a=inactive', 'a=recvonly')).media,
|
|
201
|
+
trackBitrates,
|
|
202
|
+
),
|
|
203
|
+
).toBe(900);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('gives no connection value when no section maps to a published track', () => {
|
|
207
|
+
const { media } = parse(TWO_VIDEO_SECTIONS);
|
|
208
|
+
|
|
209
|
+
expect(computeConnectionStartBitrate(media, [])).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('counts sendrecv and direction-less sections, which still send local media', () => {
|
|
213
|
+
// The legacy addTrack fallback never produces `sendonly`, so a strict match on it would
|
|
214
|
+
// leave those clients with no hint at all.
|
|
215
|
+
const { media } = parse(LEGACY_ADD_TRACK_SECTIONS);
|
|
216
|
+
|
|
217
|
+
expect(
|
|
218
|
+
computeConnectionStartBitrate(media, [{ cid: 'camera-cid', codec: 'VP8', maxbr: 1_000 }]),
|
|
219
|
+
).toBe(900);
|
|
220
|
+
expect(
|
|
221
|
+
computeConnectionStartBitrate(media, [
|
|
222
|
+
{ cid: 'other-track', codec: 'VP8', maxbr: 2_000, isScreenShare: true },
|
|
223
|
+
]),
|
|
224
|
+
).toBe(1_800);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('leaves the hint unset when only non-sending sections match', () => {
|
|
228
|
+
// Nothing is published, so the one-shot hint must not be consumed on a dead section.
|
|
229
|
+
const { media } = parse(UNPUBLISHED_SCREEN_SHARE);
|
|
230
|
+
|
|
231
|
+
expect(
|
|
232
|
+
computeConnectionStartBitrate(media, [
|
|
233
|
+
{ cid: 'other-track', codec: 'VP8', maxbr: 8_000, isScreenShare: true },
|
|
234
|
+
]),
|
|
235
|
+
).toBeUndefined();
|
|
236
|
+
});
|
|
91
237
|
});
|
|
92
238
|
|
|
93
239
|
describe('placeholderMidsFromTransceivers', () => {
|
|
@@ -390,3 +536,33 @@ a=extmap:3 ${ddExtensionURI}`);
|
|
|
390
536
|
expect(ddOf(sdp.media, '1')).toBe(12);
|
|
391
537
|
});
|
|
392
538
|
});
|
|
539
|
+
|
|
540
|
+
describe('isDuplicateAnswer', () => {
|
|
541
|
+
it('drops an answer while the same offer is still being answered', () => {
|
|
542
|
+
expect(isDuplicateAnswer(3, 3, 0, 'have-local-offer', false)).toBe(true);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('drops a repeat of an unnumbered answer while the first is still applying', () => {
|
|
546
|
+
expect(isDuplicateAnswer(0, 0, 0, 'have-local-offer', false)).toBe(true);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('drops an answer for an offer whose answer was already applied', () => {
|
|
550
|
+
expect(isDuplicateAnswer(3, undefined, 3, 'stable', false)).toBe(true);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('accepts a retry after a failed apply: nothing in flight, nothing applied', () => {
|
|
554
|
+
expect(isDuplicateAnswer(3, undefined, 2, 'have-local-offer', false)).toBe(false);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('accepts the answer for a new offer', () => {
|
|
558
|
+
expect(isDuplicateAnswer(4, undefined, 3, 'have-local-offer', false)).toBe(false);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('without an offerId, drops an answer in stable', () => {
|
|
562
|
+
expect(isDuplicateAnswer(0, undefined, 0, 'stable', false)).toBe(true);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('without an offerId, accepts the answer to a deferred initial offer', () => {
|
|
566
|
+
expect(isDuplicateAnswer(0, undefined, 0, 'stable', true)).toBe(false);
|
|
567
|
+
});
|
|
568
|
+
});
|