livekit-client 2.15.7 → 2.15.8
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 +1882 -138
- 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/connectionHelper/checks/publishVideo.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 +4 -2
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +5 -2
- 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/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 +2 -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/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} +4 -2
- package/dist/ts4.2/{src/room → room}/RTCEngine.d.ts +5 -2
- 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 +2 -1
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -0
- package/package.json +11 -10
- package/src/connectionHelper/checks/publishVideo.ts +5 -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 +14 -1
- package/src/logger.ts +1 -0
- package/src/options.ts +8 -2
- package/src/room/RTCEngine.ts +55 -6
- package/src/room/Room.ts +38 -16
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +64 -17
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -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.ts +35 -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 +8 -4
- package/src/utils/camelToSnakeCase.ts +16 -0
- /package/dist/ts4.2/{src/api → api}/SignalClient.d.ts +0 -0
- /package/dist/ts4.2/{src/api → api}/utils.d.ts +0 -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}/PCTransport.d.ts +0 -0
- /package/dist/ts4.2/{src/room → room}/PCTransportManager.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/room/RTCEngine.ts
CHANGED
@@ -9,6 +9,9 @@ import {
|
|
9
9
|
DataPacket,
|
10
10
|
DataPacket_Kind,
|
11
11
|
DisconnectReason,
|
12
|
+
EncryptedPacket,
|
13
|
+
EncryptedPacketPayload,
|
14
|
+
Encryption_Type,
|
12
15
|
type JoinResponse,
|
13
16
|
type LeaveRequest,
|
14
17
|
LeaveRequest_Action,
|
@@ -43,6 +46,8 @@ import {
|
|
43
46
|
SignalConnectionState,
|
44
47
|
toProtoSessionDescription,
|
45
48
|
} from '../api/SignalClient';
|
49
|
+
import type { BaseE2EEManager } from '../e2ee/E2eeManager';
|
50
|
+
import { asEncryptablePacket } from '../e2ee/utils';
|
46
51
|
import log, { LoggerNames, getLogger } from '../logger';
|
47
52
|
import type { InternalRoomOptions } from '../options';
|
48
53
|
import { DataPacketBuffer } from '../utils/dataPacketBuffer';
|
@@ -116,6 +121,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
116
121
|
*/
|
117
122
|
latestRemoteOfferId: number = 0;
|
118
123
|
|
124
|
+
/** @internal */
|
125
|
+
e2eeManager: BaseE2EEManager | undefined;
|
126
|
+
|
119
127
|
get isClosed() {
|
120
128
|
return this._isClosed;
|
121
129
|
}
|
@@ -203,7 +211,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
203
211
|
this.client = new SignalClient(undefined, this.loggerOptions);
|
204
212
|
this.client.signalLatency = this.options.expSignalLatency;
|
205
213
|
this.reconnectPolicy = this.options.reconnectPolicy;
|
206
|
-
this.registerOnLineListener();
|
207
214
|
this.closingLock = new Mutex();
|
208
215
|
this.dataProcessLock = new Mutex();
|
209
216
|
this.dcBufferStatus = new Map([
|
@@ -260,9 +267,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
260
267
|
|
261
268
|
// create offer
|
262
269
|
if (!this.subscriberPrimary || joinResponse.fastPublish) {
|
263
|
-
this.negotiate()
|
270
|
+
this.negotiate().catch((err) => {
|
271
|
+
log.error(err, this.logContext);
|
272
|
+
});
|
264
273
|
}
|
265
274
|
|
275
|
+
this.registerOnLineListener();
|
266
276
|
this.clientConfiguration = joinResponse.clientConfiguration;
|
267
277
|
this.emit(EngineEvent.SignalConnected, joinResponse);
|
268
278
|
return joinResponse;
|
@@ -445,7 +455,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
445
455
|
}
|
446
456
|
} else if (connectionState === PCTransportState.FAILED) {
|
447
457
|
// on Safari, PeerConnection will switch to 'disconnected' during renegotiation
|
448
|
-
if (this.pcState === PCState.Connected) {
|
458
|
+
if (this.pcState === PCState.Connected || this.pcState === PCState.Reconnecting) {
|
449
459
|
this.pcState = PCState.Disconnected;
|
450
460
|
|
451
461
|
this.handleDisconnect(
|
@@ -710,12 +720,32 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
710
720
|
if (dp.value?.case === 'speaker') {
|
711
721
|
// dispatch speaker updates
|
712
722
|
this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
|
723
|
+
} else if (dp.value?.case === 'encryptedPacket') {
|
724
|
+
if (!this.e2eeManager) {
|
725
|
+
this.log.error('Received encrypted packet but E2EE not set up', this.logContext);
|
726
|
+
return;
|
727
|
+
}
|
728
|
+
const decryptedData = await this.e2eeManager?.handleEncryptedData(
|
729
|
+
dp.value.value.encryptedValue,
|
730
|
+
dp.value.value.iv,
|
731
|
+
dp.participantIdentity,
|
732
|
+
dp.value.value.keyIndex,
|
733
|
+
);
|
734
|
+
const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload);
|
735
|
+
const newDp = new DataPacket({
|
736
|
+
value: decryptedPacket.value,
|
737
|
+
});
|
738
|
+
if (newDp.value?.case === 'user') {
|
739
|
+
// compatibility
|
740
|
+
applyUserDataCompat(newDp, newDp.value.value);
|
741
|
+
}
|
742
|
+
this.emit(EngineEvent.DataPacketReceived, newDp, dp.value.value.encryptionType);
|
713
743
|
} else {
|
714
744
|
if (dp.value?.case === 'user') {
|
715
745
|
// compatibility
|
716
746
|
applyUserDataCompat(dp, dp.value.value);
|
717
747
|
}
|
718
|
-
this.emit(EngineEvent.DataPacketReceived, dp);
|
748
|
+
this.emit(EngineEvent.DataPacketReceived, dp, Encryption_Type.NONE);
|
719
749
|
}
|
720
750
|
} finally {
|
721
751
|
unlock();
|
@@ -1191,11 +1221,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1191
1221
|
// make sure we do have a data connection
|
1192
1222
|
await this.ensurePublisherConnected(kind);
|
1193
1223
|
|
1224
|
+
if (this.e2eeManager && this.e2eeManager.isDataChannelEncryptionEnabled) {
|
1225
|
+
const encryptablePacket = asEncryptablePacket(packet);
|
1226
|
+
if (encryptablePacket) {
|
1227
|
+
const encryptedData = await this.e2eeManager.encryptData(encryptablePacket.toBinary());
|
1228
|
+
packet.value = {
|
1229
|
+
case: 'encryptedPacket',
|
1230
|
+
value: new EncryptedPacket({
|
1231
|
+
encryptedValue: encryptedData.payload,
|
1232
|
+
iv: encryptedData.iv,
|
1233
|
+
keyIndex: encryptedData.keyIndex,
|
1234
|
+
}),
|
1235
|
+
};
|
1236
|
+
}
|
1237
|
+
}
|
1238
|
+
|
1194
1239
|
if (kind === DataPacket_Kind.RELIABLE) {
|
1195
1240
|
packet.sequence = this.reliableDataSequence;
|
1196
1241
|
this.reliableDataSequence += 1;
|
1197
1242
|
}
|
1243
|
+
|
1198
1244
|
const msg = packet.toBinary();
|
1245
|
+
|
1199
1246
|
const dc = this.dataChannelForKind(kind);
|
1200
1247
|
if (dc) {
|
1201
1248
|
if (kind === DataPacket_Kind.RELIABLE) {
|
@@ -1293,7 +1340,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
1293
1340
|
}
|
1294
1341
|
if (needNegotiation) {
|
1295
1342
|
// start negotiation
|
1296
|
-
this.negotiate()
|
1343
|
+
this.negotiate().catch((err) => {
|
1344
|
+
log.error(err, this.logContext);
|
1345
|
+
});
|
1297
1346
|
}
|
1298
1347
|
|
1299
1348
|
const targetChannel = this.dataChannelForKind(kind, subscriber);
|
@@ -1558,7 +1607,7 @@ export type EngineEventCallbacks = {
|
|
1558
1607
|
receiver: RTCRtpReceiver,
|
1559
1608
|
) => void;
|
1560
1609
|
activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
|
1561
|
-
dataPacketReceived: (packet: DataPacket) => void;
|
1610
|
+
dataPacketReceived: (packet: DataPacket, encryptionType: Encryption_Type) => void;
|
1562
1611
|
transcriptionReceived: (transcription: Transcription) => void;
|
1563
1612
|
transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
|
1564
1613
|
/** @internal */
|
package/src/room/Room.ts
CHANGED
@@ -5,6 +5,7 @@ import {
|
|
5
5
|
type DataPacket,
|
6
6
|
DataPacket_Kind,
|
7
7
|
DisconnectReason,
|
8
|
+
Encryption_Type,
|
8
9
|
JoinResponse,
|
9
10
|
LeaveRequest,
|
10
11
|
LeaveRequest_Action,
|
@@ -198,6 +199,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
198
199
|
|
199
200
|
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
200
201
|
|
202
|
+
get hasE2EESetup(): boolean {
|
203
|
+
return this.e2eeManager !== undefined;
|
204
|
+
}
|
205
|
+
|
201
206
|
/**
|
202
207
|
* Creates a new Room, the primary construct for a LiveKit session.
|
203
208
|
* @param options
|
@@ -241,6 +246,12 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
241
246
|
this.outgoingDataStreamManager,
|
242
247
|
);
|
243
248
|
|
249
|
+
if (this.options.e2ee || this.options.encryption) {
|
250
|
+
this.setupE2EE();
|
251
|
+
}
|
252
|
+
|
253
|
+
this.engine.e2eeManager = this.e2eeManager;
|
254
|
+
|
244
255
|
if (this.options.videoCaptureDefaults.deviceId) {
|
245
256
|
this.localParticipant.activeDeviceMap.set(
|
246
257
|
'videoinput',
|
@@ -260,10 +271,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
260
271
|
).catch((e) => this.log.warn(`Could not set audio output: ${e.message}`, this.logContext));
|
261
272
|
}
|
262
273
|
|
263
|
-
if (this.options.e2ee) {
|
264
|
-
this.setupE2EE();
|
265
|
-
}
|
266
|
-
|
267
274
|
if (isWeb()) {
|
268
275
|
const abortController = new AbortController();
|
269
276
|
|
@@ -355,11 +362,16 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
355
362
|
}
|
356
363
|
|
357
364
|
private setupE2EE() {
|
358
|
-
|
359
|
-
|
360
|
-
|
365
|
+
// when encryption is enabled via `options.encryption`, we enable data channel encryption
|
366
|
+
|
367
|
+
const dcEncryptionEnabled = !!this.options.encryption;
|
368
|
+
const e2eeOptions = this.options.encryption || this.options.e2ee;
|
369
|
+
|
370
|
+
if (e2eeOptions) {
|
371
|
+
if ('e2eeManager' in e2eeOptions) {
|
372
|
+
this.e2eeManager = e2eeOptions.e2eeManager;
|
361
373
|
} else {
|
362
|
-
this.e2eeManager = new E2EEManager(
|
374
|
+
this.e2eeManager = new E2EEManager(e2eeOptions, dcEncryptionEnabled);
|
363
375
|
}
|
364
376
|
this.e2eeManager.on(
|
365
377
|
EncryptionEvent.ParticipantEncryptionStatusChanged,
|
@@ -443,6 +455,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
443
455
|
}
|
444
456
|
|
445
457
|
this.engine = new RTCEngine(this.options);
|
458
|
+
this.engine.e2eeManager = this.e2eeManager;
|
446
459
|
|
447
460
|
this.engine
|
448
461
|
.on(EngineEvent.ParticipantUpdate, this.handleParticipantUpdates)
|
@@ -789,7 +802,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
789
802
|
this.localParticipant.identity = pi.identity;
|
790
803
|
this.localParticipant.setEnabledPublishCodecs(joinResponse.enabledPublishCodecs);
|
791
804
|
|
792
|
-
if (this.
|
805
|
+
if (this.e2eeManager) {
|
793
806
|
try {
|
794
807
|
this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
|
795
808
|
} catch (e: any) {
|
@@ -1711,11 +1724,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1711
1724
|
pub.setSubscriptionError(update.err);
|
1712
1725
|
};
|
1713
1726
|
|
1714
|
-
private handleDataPacket = (packet: DataPacket) => {
|
1727
|
+
private handleDataPacket = (packet: DataPacket, encryptionType: Encryption_Type) => {
|
1715
1728
|
// find the participant
|
1716
1729
|
const participant = this.remoteParticipants.get(packet.participantIdentity);
|
1717
1730
|
if (packet.value.case === 'user') {
|
1718
|
-
this.handleUserPacket(participant, packet.value.value, packet.kind);
|
1731
|
+
this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType);
|
1719
1732
|
} else if (packet.value.case === 'transcription') {
|
1720
1733
|
this.handleTranscription(participant, packet.value.value);
|
1721
1734
|
} else if (packet.value.case === 'sipDtmf') {
|
@@ -1729,7 +1742,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1729
1742
|
packet.value.case === 'streamChunk' ||
|
1730
1743
|
packet.value.case === 'streamTrailer'
|
1731
1744
|
) {
|
1732
|
-
this.handleDataStream(packet);
|
1745
|
+
this.handleDataStream(packet, encryptionType);
|
1733
1746
|
} else if (packet.value.case === 'rpcRequest') {
|
1734
1747
|
const rpc = packet.value.value;
|
1735
1748
|
this.handleIncomingRpcRequest(
|
@@ -1747,11 +1760,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1747
1760
|
participant: RemoteParticipant | undefined,
|
1748
1761
|
userPacket: UserPacket,
|
1749
1762
|
kind: DataPacket_Kind,
|
1763
|
+
encryptionType: Encryption_Type,
|
1750
1764
|
) => {
|
1751
|
-
this.emit(
|
1765
|
+
this.emit(
|
1766
|
+
RoomEvent.DataReceived,
|
1767
|
+
userPacket.payload,
|
1768
|
+
participant,
|
1769
|
+
kind,
|
1770
|
+
userPacket.topic,
|
1771
|
+
encryptionType,
|
1772
|
+
);
|
1752
1773
|
|
1753
1774
|
// also emit on the participant
|
1754
|
-
participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
|
1775
|
+
participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind, encryptionType);
|
1755
1776
|
};
|
1756
1777
|
|
1757
1778
|
private handleSipDtmf = (participant: RemoteParticipant | undefined, dtmf: SipDTMF) => {
|
@@ -1791,8 +1812,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
1791
1812
|
this.emit(RoomEvent.MetricsReceived, metrics, participant);
|
1792
1813
|
};
|
1793
1814
|
|
1794
|
-
private handleDataStream = (packet: DataPacket) => {
|
1795
|
-
this.incomingDataStreamManager.handleDataStreamPacket(packet);
|
1815
|
+
private handleDataStream = (packet: DataPacket, encryptionType: Encryption_Type) => {
|
1816
|
+
this.incomingDataStreamManager.handleDataStreamPacket(packet, encryptionType);
|
1796
1817
|
};
|
1797
1818
|
|
1798
1819
|
private async handleIncomingRpcRequest(
|
@@ -2596,6 +2617,7 @@ export type RoomEventCallbacks = {
|
|
2596
2617
|
participant?: RemoteParticipant,
|
2597
2618
|
kind?: DataPacket_Kind,
|
2598
2619
|
topic?: string,
|
2620
|
+
encryptionType?: Encryption_Type,
|
2599
2621
|
) => void;
|
2600
2622
|
sipDTMFReceived: (dtmf: SipDTMF, participant?: RemoteParticipant) => void;
|
2601
2623
|
transcriptionReceived: (
|
@@ -3,6 +3,7 @@ import {
|
|
3
3
|
DataStream_Chunk,
|
4
4
|
DataStream_Header,
|
5
5
|
DataStream_Trailer,
|
6
|
+
Encryption_Type,
|
6
7
|
} from '@livekit/protocol';
|
7
8
|
import log from '../../../logger';
|
8
9
|
import { DataStreamError, DataStreamErrorReason } from '../../errors';
|
@@ -89,20 +90,28 @@ export default class IncomingDataStreamManager {
|
|
89
90
|
}
|
90
91
|
}
|
91
92
|
|
92
|
-
async handleDataStreamPacket(packet: DataPacket) {
|
93
|
+
async handleDataStreamPacket(packet: DataPacket, encryptionType: Encryption_Type) {
|
93
94
|
switch (packet.value.case) {
|
94
95
|
case 'streamHeader':
|
95
|
-
return this.handleStreamHeader(
|
96
|
+
return this.handleStreamHeader(
|
97
|
+
packet.value.value,
|
98
|
+
packet.participantIdentity,
|
99
|
+
encryptionType,
|
100
|
+
);
|
96
101
|
case 'streamChunk':
|
97
|
-
return this.handleStreamChunk(packet.value.value);
|
102
|
+
return this.handleStreamChunk(packet.value.value, encryptionType);
|
98
103
|
case 'streamTrailer':
|
99
|
-
return this.handleStreamTrailer(packet.value.value);
|
104
|
+
return this.handleStreamTrailer(packet.value.value, encryptionType);
|
100
105
|
default:
|
101
106
|
throw new Error(`DataPacket of value "${packet.value.case}" is not data stream related!`);
|
102
107
|
}
|
103
108
|
}
|
104
109
|
|
105
|
-
private async handleStreamHeader(
|
110
|
+
private async handleStreamHeader(
|
111
|
+
streamHeader: DataStream_Header,
|
112
|
+
participantIdentity: string,
|
113
|
+
encryptionType: Encryption_Type,
|
114
|
+
) {
|
106
115
|
if (streamHeader.contentHeader.case === 'byteHeader') {
|
107
116
|
const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic);
|
108
117
|
if (!streamHandlerCallback) {
|
@@ -115,6 +124,9 @@ export default class IncomingDataStreamManager {
|
|
115
124
|
|
116
125
|
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
117
126
|
const outOfBandFailureRejectingFuture = new Future<never>();
|
127
|
+
outOfBandFailureRejectingFuture.promise.catch((err) => {
|
128
|
+
this.log.error(err);
|
129
|
+
});
|
118
130
|
|
119
131
|
const info: ByteStreamInfo = {
|
120
132
|
id: streamHeader.streamId,
|
@@ -124,6 +136,7 @@ export default class IncomingDataStreamManager {
|
|
124
136
|
topic: streamHeader.topic,
|
125
137
|
timestamp: bigIntToNumber(streamHeader.timestamp),
|
126
138
|
attributes: streamHeader.attributes,
|
139
|
+
encryptionType,
|
127
140
|
};
|
128
141
|
const stream = new ReadableStream({
|
129
142
|
start: (controller) => {
|
@@ -168,6 +181,10 @@ export default class IncomingDataStreamManager {
|
|
168
181
|
|
169
182
|
let streamController: ReadableStreamDefaultController<DataStream_Chunk>;
|
170
183
|
const outOfBandFailureRejectingFuture = new Future<never>();
|
184
|
+
outOfBandFailureRejectingFuture.promise.catch((err) => {
|
185
|
+
this.log.error(err);
|
186
|
+
});
|
187
|
+
|
171
188
|
const info: TextStreamInfo = {
|
172
189
|
id: streamHeader.streamId,
|
173
190
|
mimeType: streamHeader.mimeType,
|
@@ -175,6 +192,7 @@ export default class IncomingDataStreamManager {
|
|
175
192
|
topic: streamHeader.topic,
|
176
193
|
timestamp: Number(streamHeader.timestamp),
|
177
194
|
attributes: streamHeader.attributes,
|
195
|
+
encryptionType,
|
178
196
|
};
|
179
197
|
|
180
198
|
const stream = new ReadableStream<DataStream_Chunk>({
|
@@ -209,39 +227,68 @@ export default class IncomingDataStreamManager {
|
|
209
227
|
}
|
210
228
|
}
|
211
229
|
|
212
|
-
private handleStreamChunk(chunk: DataStream_Chunk) {
|
230
|
+
private handleStreamChunk(chunk: DataStream_Chunk, encryptionType: Encryption_Type) {
|
213
231
|
const fileBuffer = this.byteStreamControllers.get(chunk.streamId);
|
214
232
|
if (fileBuffer) {
|
215
|
-
if (
|
233
|
+
if (fileBuffer.info.encryptionType !== encryptionType) {
|
234
|
+
fileBuffer.controller.error(
|
235
|
+
new DataStreamError(
|
236
|
+
`Encryption type mismatch for stream ${chunk.streamId}. Expected ${encryptionType}, got ${fileBuffer.info.encryptionType}`,
|
237
|
+
DataStreamErrorReason.EncryptionTypeMismatch,
|
238
|
+
),
|
239
|
+
);
|
240
|
+
this.byteStreamControllers.delete(chunk.streamId);
|
241
|
+
} else if (chunk.content.length > 0) {
|
216
242
|
fileBuffer.controller.enqueue(chunk);
|
217
243
|
}
|
218
244
|
}
|
219
245
|
const textBuffer = this.textStreamControllers.get(chunk.streamId);
|
220
246
|
if (textBuffer) {
|
221
|
-
if (
|
247
|
+
if (textBuffer.info.encryptionType !== encryptionType) {
|
248
|
+
textBuffer.controller.error(
|
249
|
+
new DataStreamError(
|
250
|
+
`Encryption type mismatch for stream ${chunk.streamId}. Expected ${encryptionType}, got ${textBuffer.info.encryptionType}`,
|
251
|
+
DataStreamErrorReason.EncryptionTypeMismatch,
|
252
|
+
),
|
253
|
+
);
|
254
|
+
this.textStreamControllers.delete(chunk.streamId);
|
255
|
+
} else if (chunk.content.length > 0) {
|
222
256
|
textBuffer.controller.enqueue(chunk);
|
223
257
|
}
|
224
258
|
}
|
225
259
|
}
|
226
260
|
|
227
|
-
private handleStreamTrailer(trailer: DataStream_Trailer) {
|
261
|
+
private handleStreamTrailer(trailer: DataStream_Trailer, encryptionType: Encryption_Type) {
|
228
262
|
const textBuffer = this.textStreamControllers.get(trailer.streamId);
|
229
263
|
if (textBuffer) {
|
230
|
-
textBuffer.info.
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
264
|
+
if (textBuffer.info.encryptionType !== encryptionType) {
|
265
|
+
textBuffer.controller.error(
|
266
|
+
new DataStreamError(
|
267
|
+
`Encryption type mismatch for stream ${trailer.streamId}. Expected ${encryptionType}, got ${textBuffer.info.encryptionType}`,
|
268
|
+
DataStreamErrorReason.EncryptionTypeMismatch,
|
269
|
+
),
|
270
|
+
);
|
271
|
+
} else {
|
272
|
+
textBuffer.info.attributes = { ...textBuffer.info.attributes, ...trailer.attributes };
|
273
|
+
textBuffer.controller.close();
|
274
|
+
this.textStreamControllers.delete(trailer.streamId);
|
275
|
+
}
|
236
276
|
}
|
237
277
|
|
238
278
|
const fileBuffer = this.byteStreamControllers.get(trailer.streamId);
|
239
279
|
if (fileBuffer) {
|
240
|
-
{
|
280
|
+
if (fileBuffer.info.encryptionType !== encryptionType) {
|
281
|
+
fileBuffer.controller.error(
|
282
|
+
new DataStreamError(
|
283
|
+
`Encryption type mismatch for stream ${trailer.streamId}. Expected ${encryptionType}, got ${fileBuffer.info.encryptionType}`,
|
284
|
+
DataStreamErrorReason.EncryptionTypeMismatch,
|
285
|
+
),
|
286
|
+
);
|
287
|
+
} else {
|
241
288
|
fileBuffer.info.attributes = { ...fileBuffer.info.attributes, ...trailer.attributes };
|
242
289
|
fileBuffer.controller.close();
|
243
|
-
this.byteStreamControllers.delete(trailer.streamId);
|
244
290
|
}
|
291
|
+
this.byteStreamControllers.delete(trailer.streamId);
|
245
292
|
}
|
246
293
|
}
|
247
294
|
}
|
@@ -8,6 +8,7 @@ import {
|
|
8
8
|
DataStream_OperationType,
|
9
9
|
DataStream_TextHeader,
|
10
10
|
DataStream_Trailer,
|
11
|
+
Encryption_Type,
|
11
12
|
} from '@livekit/protocol';
|
12
13
|
import { type StructuredLogger } from '../../../logger';
|
13
14
|
import type RTCEngine from '../../RTCEngine';
|
@@ -104,6 +105,9 @@ export default class OutgoingDataStreamManager {
|
|
104
105
|
topic: options?.topic ?? '',
|
105
106
|
size: options?.totalSize,
|
106
107
|
attributes: options?.attributes,
|
108
|
+
encryptionType: this.engine.e2eeManager?.isDataChannelEncryptionEnabled
|
109
|
+
? Encryption_Type.GCM
|
110
|
+
: Encryption_Type.NONE,
|
107
111
|
};
|
108
112
|
const header = new DataStream_Header({
|
109
113
|
streamId,
|
@@ -231,6 +235,9 @@ export default class OutgoingDataStreamManager {
|
|
231
235
|
attributes: options?.attributes,
|
232
236
|
size: options?.totalSize,
|
233
237
|
name: options?.name ?? 'unknown',
|
238
|
+
encryptionType: this.engine.e2eeManager?.isDataChannelEncryptionEnabled
|
239
|
+
? Encryption_Type.GCM
|
240
|
+
: Encryption_Type.NONE,
|
234
241
|
};
|
235
242
|
|
236
243
|
const header = new DataStream_Header({
|
package/src/room/errors.ts
CHANGED
@@ -130,6 +130,9 @@ export enum DataStreamErrorReason {
|
|
130
130
|
|
131
131
|
// Unable to register a stream handler more than once.
|
132
132
|
HandlerAlreadyRegistered = 7,
|
133
|
+
|
134
|
+
// Encryption type mismatch.
|
135
|
+
EncryptionTypeMismatch = 8,
|
133
136
|
}
|
134
137
|
|
135
138
|
export class DataStreamError extends LivekitError {
|
@@ -1634,16 +1634,18 @@ export default class LocalParticipant extends Participant {
|
|
1634
1634
|
const destinationIdentities = options.destinationIdentities;
|
1635
1635
|
const topic = options.topic;
|
1636
1636
|
|
1637
|
+
let userPacket = new UserPacket({
|
1638
|
+
participantIdentity: this.identity,
|
1639
|
+
payload: data,
|
1640
|
+
destinationIdentities,
|
1641
|
+
topic,
|
1642
|
+
});
|
1643
|
+
|
1637
1644
|
const packet = new DataPacket({
|
1638
1645
|
kind: kind,
|
1639
1646
|
value: {
|
1640
1647
|
case: 'user',
|
1641
|
-
value:
|
1642
|
-
participantIdentity: this.identity,
|
1643
|
-
payload: data,
|
1644
|
-
destinationIdentities,
|
1645
|
-
topic,
|
1646
|
-
}),
|
1648
|
+
value: userPacket,
|
1647
1649
|
},
|
1648
1650
|
});
|
1649
1651
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import {
|
2
2
|
DataPacket_Kind,
|
3
|
+
Encryption_Type,
|
3
4
|
ParticipantInfo,
|
4
5
|
ParticipantInfo_State,
|
5
6
|
ParticipantInfo_Kind as ParticipantKind,
|
@@ -408,7 +409,11 @@ export type ParticipantEventCallbacks = {
|
|
408
409
|
localSenderCreated: (sender: RTCRtpSender, track: Track) => void;
|
409
410
|
participantMetadataChanged: (prevMetadata: string | undefined, participant?: any) => void;
|
410
411
|
participantNameChanged: (name: string) => void;
|
411
|
-
dataReceived: (
|
412
|
+
dataReceived: (
|
413
|
+
payload: Uint8Array,
|
414
|
+
kind: DataPacket_Kind,
|
415
|
+
encryptionType?: Encryption_Type,
|
416
|
+
) => void;
|
412
417
|
sipDTMFReceived: (dtmf: SipDTMF) => void;
|
413
418
|
transcriptionReceived: (
|
414
419
|
transcription: TranscriptionSegment[],
|