livekit-client 2.15.6 → 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.
Files changed (173) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +253 -118
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +1892 -153
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
  10. package/dist/src/e2ee/E2eeManager.d.ts +16 -2
  11. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  12. package/dist/src/e2ee/types.d.ts +35 -1
  13. package/dist/src/e2ee/types.d.ts.map +1 -1
  14. package/dist/src/e2ee/utils.d.ts +2 -0
  15. package/dist/src/e2ee/utils.d.ts.map +1 -1
  16. package/dist/src/e2ee/worker/DataCryptor.d.ts +15 -0
  17. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -0
  18. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +3 -2
  19. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  20. package/dist/src/e2ee/worker/sifPayload.d.ts +6 -6
  21. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  22. package/dist/src/index.d.ts +5 -3
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/logger.d.ts +1 -0
  25. package/dist/src/logger.d.ts.map +1 -1
  26. package/dist/src/options.d.ts +4 -2
  27. package/dist/src/options.d.ts.map +1 -1
  28. package/dist/src/room/PCTransport.d.ts.map +1 -1
  29. package/dist/src/room/RTCEngine.d.ts +5 -2
  30. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  31. package/dist/src/room/Room.d.ts +3 -2
  32. package/dist/src/room/Room.d.ts.map +1 -1
  33. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -2
  34. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  35. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  36. package/dist/src/room/errors.d.ts +2 -1
  37. package/dist/src/room/errors.d.ts.map +1 -1
  38. package/dist/src/room/participant/LocalParticipant.d.ts +1 -3
  39. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  40. package/dist/src/room/participant/Participant.d.ts +2 -2
  41. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  42. package/dist/src/room/token-source/TokenSource.d.ts +70 -0
  43. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -0
  44. package/dist/src/room/token-source/types.d.ts +68 -0
  45. package/dist/src/room/token-source/types.d.ts.map +1 -0
  46. package/dist/src/room/token-source/utils.d.ts +5 -0
  47. package/dist/src/room/token-source/utils.d.ts.map +1 -0
  48. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  49. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  50. package/dist/src/room/track/options.d.ts +7 -3
  51. package/dist/src/room/track/options.d.ts.map +1 -1
  52. package/dist/src/room/track/utils.d.ts.map +1 -1
  53. package/dist/src/room/types.d.ts +1 -0
  54. package/dist/src/room/types.d.ts.map +1 -1
  55. package/dist/src/room/utils.d.ts +2 -1
  56. package/dist/src/room/utils.d.ts.map +1 -1
  57. package/dist/src/utils/camelToSnakeCase.d.ts +8 -0
  58. package/dist/src/utils/camelToSnakeCase.d.ts.map +1 -0
  59. package/dist/ts4.2/{src/e2ee → e2ee}/E2eeManager.d.ts +16 -2
  60. package/dist/ts4.2/{src/e2ee → e2ee}/types.d.ts +35 -1
  61. package/dist/ts4.2/{src/e2ee → e2ee}/utils.d.ts +3 -0
  62. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +15 -0
  63. package/dist/ts4.2/{src/e2ee → e2ee}/worker/ParticipantKeyHandler.d.ts +3 -2
  64. package/dist/ts4.2/{src/e2ee → e2ee}/worker/sifPayload.d.ts +6 -6
  65. package/dist/ts4.2/{src/index.d.ts → index.d.ts} +5 -3
  66. package/dist/ts4.2/{src/logger.d.ts → logger.d.ts} +1 -0
  67. package/dist/ts4.2/{src/options.d.ts → options.d.ts} +4 -2
  68. package/dist/ts4.2/{src/room → room}/RTCEngine.d.ts +5 -2
  69. package/dist/ts4.2/{src/room → room}/Room.d.ts +3 -2
  70. package/dist/ts4.2/{src/room → room}/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -1
  71. package/dist/ts4.2/{src/room → room}/errors.d.ts +2 -1
  72. package/dist/ts4.2/{src/room → room}/participant/LocalParticipant.d.ts +1 -3
  73. package/dist/ts4.2/{src/room → room}/participant/Participant.d.ts +2 -2
  74. package/dist/ts4.2/room/token-source/TokenSource.d.ts +71 -0
  75. package/dist/ts4.2/room/token-source/types.d.ts +68 -0
  76. package/dist/ts4.2/room/token-source/utils.d.ts +5 -0
  77. package/dist/ts4.2/{src/room → room}/track/LocalTrack.d.ts +1 -1
  78. package/dist/ts4.2/{src/room → room}/track/options.d.ts +10 -3
  79. package/dist/ts4.2/{src/room → room}/types.d.ts +1 -0
  80. package/dist/ts4.2/{src/room → room}/utils.d.ts +2 -1
  81. package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -0
  82. package/package.json +14 -12
  83. package/src/connectionHelper/checks/publishVideo.ts +5 -0
  84. package/src/e2ee/E2eeManager.ts +94 -2
  85. package/src/e2ee/types.ts +44 -1
  86. package/src/e2ee/utils.ts +16 -0
  87. package/src/e2ee/worker/DataCryptor.test.ts +271 -0
  88. package/src/e2ee/worker/DataCryptor.ts +147 -0
  89. package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -3
  90. package/src/e2ee/worker/e2ee.worker.ts +47 -0
  91. package/src/e2ee/worker/sifPayload.ts +10 -6
  92. package/src/index.ts +14 -1
  93. package/src/logger.ts +1 -0
  94. package/src/options.ts +8 -2
  95. package/src/room/PCTransport.ts +14 -5
  96. package/src/room/RTCEngine.ts +55 -6
  97. package/src/room/Room.ts +39 -17
  98. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +64 -17
  99. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -0
  100. package/src/room/errors.ts +3 -0
  101. package/src/room/participant/LocalParticipant.ts +17 -29
  102. package/src/room/participant/Participant.ts +6 -1
  103. package/src/room/token-source/TokenSource.ts +285 -0
  104. package/src/room/token-source/types.ts +84 -0
  105. package/src/room/token-source/utils.ts +35 -0
  106. package/src/room/track/LocalAudioTrack.ts +1 -1
  107. package/src/room/track/LocalTrack.ts +1 -1
  108. package/src/room/track/options.ts +12 -4
  109. package/src/room/track/utils.ts +10 -2
  110. package/src/room/types.ts +1 -0
  111. package/src/room/utils.ts +8 -4
  112. package/src/utils/camelToSnakeCase.ts +16 -0
  113. /package/dist/ts4.2/{src/api → api}/SignalClient.d.ts +0 -0
  114. /package/dist/ts4.2/{src/api → api}/utils.d.ts +0 -0
  115. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/ConnectionCheck.d.ts +0 -0
  116. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/Checker.d.ts +0 -0
  117. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/cloudRegion.d.ts +0 -0
  118. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/connectionProtocol.d.ts +0 -0
  119. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishAudio.d.ts +0 -0
  120. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishVideo.d.ts +0 -0
  121. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/reconnect.d.ts +0 -0
  122. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/turn.d.ts +0 -0
  123. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/webrtc.d.ts +0 -0
  124. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/websocket.d.ts +0 -0
  125. /package/dist/ts4.2/{src/e2ee → e2ee}/KeyProvider.d.ts +0 -0
  126. /package/dist/ts4.2/{src/e2ee → e2ee}/constants.d.ts +0 -0
  127. /package/dist/ts4.2/{src/e2ee → e2ee}/errors.d.ts +0 -0
  128. /package/dist/ts4.2/{src/e2ee → e2ee}/events.d.ts +0 -0
  129. /package/dist/ts4.2/{src/e2ee → e2ee}/index.d.ts +0 -0
  130. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/FrameCryptor.d.ts +0 -0
  131. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/e2ee.worker.d.ts +0 -0
  132. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/naluUtils.d.ts +0 -0
  133. /package/dist/ts4.2/{src/room → room}/DefaultReconnectPolicy.d.ts +0 -0
  134. /package/dist/ts4.2/{src/room → room}/DeviceManager.d.ts +0 -0
  135. /package/dist/ts4.2/{src/room → room}/PCTransport.d.ts +0 -0
  136. /package/dist/ts4.2/{src/room → room}/PCTransportManager.d.ts +0 -0
  137. /package/dist/ts4.2/{src/room → room}/ReconnectPolicy.d.ts +0 -0
  138. /package/dist/ts4.2/{src/room → room}/RegionUrlProvider.d.ts +0 -0
  139. /package/dist/ts4.2/{src/room → room}/attribute-typings.d.ts +0 -0
  140. /package/dist/ts4.2/{src/room → room}/data-stream/incoming/StreamReader.d.ts +0 -0
  141. /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -0
  142. /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/StreamWriter.d.ts +0 -0
  143. /package/dist/ts4.2/{src/room → room}/defaults.d.ts +0 -0
  144. /package/dist/ts4.2/{src/room → room}/events.d.ts +0 -0
  145. /package/dist/ts4.2/{src/room → room}/participant/ParticipantTrackPermission.d.ts +0 -0
  146. /package/dist/ts4.2/{src/room → room}/participant/RemoteParticipant.d.ts +0 -0
  147. /package/dist/ts4.2/{src/room → room}/participant/publishUtils.d.ts +0 -0
  148. /package/dist/ts4.2/{src/room → room}/rpc.d.ts +0 -0
  149. /package/dist/ts4.2/{src/room → room}/stats.d.ts +0 -0
  150. /package/dist/ts4.2/{src/room → room}/timers.d.ts +0 -0
  151. /package/dist/ts4.2/{src/room → room}/track/LocalAudioTrack.d.ts +0 -0
  152. /package/dist/ts4.2/{src/room → room}/track/LocalTrackPublication.d.ts +0 -0
  153. /package/dist/ts4.2/{src/room → room}/track/LocalVideoTrack.d.ts +0 -0
  154. /package/dist/ts4.2/{src/room → room}/track/RemoteAudioTrack.d.ts +0 -0
  155. /package/dist/ts4.2/{src/room → room}/track/RemoteTrack.d.ts +0 -0
  156. /package/dist/ts4.2/{src/room → room}/track/RemoteTrackPublication.d.ts +0 -0
  157. /package/dist/ts4.2/{src/room → room}/track/RemoteVideoTrack.d.ts +0 -0
  158. /package/dist/ts4.2/{src/room → room}/track/Track.d.ts +0 -0
  159. /package/dist/ts4.2/{src/room → room}/track/TrackPublication.d.ts +0 -0
  160. /package/dist/ts4.2/{src/room → room}/track/create.d.ts +0 -0
  161. /package/dist/ts4.2/{src/room → room}/track/facingMode.d.ts +0 -0
  162. /package/dist/ts4.2/{src/room → room}/track/processor/types.d.ts +0 -0
  163. /package/dist/ts4.2/{src/room → room}/track/record.d.ts +0 -0
  164. /package/dist/ts4.2/{src/room → room}/track/types.d.ts +0 -0
  165. /package/dist/ts4.2/{src/room → room}/track/utils.d.ts +0 -0
  166. /package/dist/ts4.2/{src/test → test}/MockMediaStreamTrack.d.ts +0 -0
  167. /package/dist/ts4.2/{src/test → test}/mocks.d.ts +0 -0
  168. /package/dist/ts4.2/{src/utils → utils}/AsyncQueue.d.ts +0 -0
  169. /package/dist/ts4.2/{src/utils → utils}/browserParser.d.ts +0 -0
  170. /package/dist/ts4.2/{src/utils → utils}/cloneDeep.d.ts +0 -0
  171. /package/dist/ts4.2/{src/utils → utils}/dataPacketBuffer.d.ts +0 -0
  172. /package/dist/ts4.2/{src/utils → utils}/ttlmap.d.ts +0 -0
  173. /package/dist/ts4.2/{src/version.d.ts → version.d.ts} +0 -0
@@ -165,10 +165,11 @@ export default class PCTransport extends EventEmitter {
165
165
  } else if (sd.type === 'answer') {
166
166
  const sdpParsed = parse(sd.sdp ?? '');
167
167
  sdpParsed.media.forEach((media) => {
168
+ const mid = getMidString(media.mid!);
168
169
  if (media.type === 'audio') {
169
170
  // mung sdp for opus bitrate settings
170
171
  this.trackBitrates.some((trackbr): boolean => {
171
- if (!trackbr.transceiver || media.mid != trackbr.transceiver.mid) {
172
+ if (!trackbr.transceiver || mid != trackbr.transceiver.mid) {
172
173
  return false;
173
174
  }
174
175
 
@@ -593,6 +594,9 @@ function ensureAudioNackAndStereo(
593
594
  stereoMids: string[],
594
595
  nackMids: string[],
595
596
  ) {
597
+ // sdp-transform types don't include number however the parser outputs mids as numbers in some cases
598
+ const mid = getMidString(media.mid!);
599
+
596
600
  // found opus codec to add nack fb
597
601
  let opusPayload = 0;
598
602
  media.rtp.some((rtp): boolean => {
@@ -610,7 +614,7 @@ function ensureAudioNackAndStereo(
610
614
  }
611
615
 
612
616
  if (
613
- nackMids.includes(media.mid!) &&
617
+ nackMids.includes(mid) &&
614
618
  !media.rtcpFb.some((fb) => fb.payload === opusPayload && fb.type === 'nack')
615
619
  ) {
616
620
  media.rtcpFb.push({
@@ -619,7 +623,7 @@ function ensureAudioNackAndStereo(
619
623
  });
620
624
  }
621
625
 
622
- if (stereoMids.includes(media.mid!)) {
626
+ if (stereoMids.includes(mid)) {
623
627
  media.fmtp.some((fmtp): boolean => {
624
628
  if (fmtp.payload === opusPayload) {
625
629
  if (!fmtp.config.includes('stereo=1')) {
@@ -642,6 +646,7 @@ function extractStereoAndNackAudioFromOffer(offer: RTCSessionDescriptionInit): {
642
646
  const sdpParsed = parse(offer.sdp ?? '');
643
647
  let opusPayload = 0;
644
648
  sdpParsed.media.forEach((media) => {
649
+ const mid = getMidString(media.mid!);
645
650
  if (media.type === 'audio') {
646
651
  media.rtp.some((rtp): boolean => {
647
652
  if (rtp.codec === 'opus') {
@@ -652,13 +657,13 @@ function extractStereoAndNackAudioFromOffer(offer: RTCSessionDescriptionInit): {
652
657
  });
653
658
 
654
659
  if (media.rtcpFb?.some((fb) => fb.payload === opusPayload && fb.type === 'nack')) {
655
- nackMids.push(media.mid!);
660
+ nackMids.push(mid);
656
661
  }
657
662
 
658
663
  media.fmtp.some((fmtp): boolean => {
659
664
  if (fmtp.payload === opusPayload) {
660
665
  if (fmtp.config.includes('sprop-stereo=1')) {
661
- stereoMids.push(media.mid!);
666
+ stereoMids.push(mid);
662
667
  }
663
668
  return true;
664
669
  }
@@ -682,3 +687,7 @@ function ensureIPAddrMatchVersion(media: MediaDescription) {
682
687
  }
683
688
  }
684
689
  }
690
+
691
+ function getMidString(mid: string | number) {
692
+ return typeof mid === 'number' ? mid.toFixed(0) : mid;
693
+ }
@@ -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
- if (this.options.e2ee) {
359
- if ('e2eeManager' in this.options.e2ee) {
360
- this.e2eeManager = this.options.e2ee.e2eeManager;
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(this.options.e2ee);
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.options.e2ee && this.e2eeManager) {
805
+ if (this.e2eeManager) {
793
806
  try {
794
807
  this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
795
808
  } catch (e: any) {
@@ -970,7 +983,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
970
983
  * @internal for testing
971
984
  */
972
985
  async simulateScenario(scenario: SimulationScenario, arg?: any) {
973
- let postAction = () => {};
986
+ let postAction = async () => {};
974
987
  let req: SimulateScenario | undefined;
975
988
  switch (scenario) {
976
989
  case 'signal-reconnect':
@@ -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(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
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(packet.value.value, packet.participantIdentity);
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(streamHeader: DataStream_Header, participantIdentity: string) {
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 (chunk.content.length > 0) {
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 (chunk.content.length > 0) {
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.attributes = {
231
- ...textBuffer.info.attributes,
232
- ...trailer.attributes,
233
- };
234
- textBuffer.controller.close();
235
- this.textStreamControllers.delete(trailer.streamId);
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({
@@ -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 {
@@ -9,7 +9,6 @@ import {
9
9
  Encryption_Type,
10
10
  JoinResponse,
11
11
  ParticipantInfo,
12
- ParticipantPermission,
13
12
  RequestResponse,
14
13
  RequestResponse_Reason,
15
14
  RpcAck,
@@ -453,16 +452,6 @@ export default class LocalParticipant extends Participant {
453
452
  return this.setTrackEnabled(Track.Source.ScreenShare, enabled, options, publishOptions);
454
453
  }
455
454
 
456
- /** @internal */
457
- setPermissions(permissions: ParticipantPermission): boolean {
458
- const prevPermissions = this.permissions;
459
- const changed = super.setPermissions(permissions);
460
- if (changed && prevPermissions) {
461
- this.emit(ParticipantEvent.ParticipantPermissionsChanged, prevPermissions);
462
- }
463
- return changed;
464
- }
465
-
466
455
  /** @internal */
467
456
  async setE2EEEnabled(enabled: boolean) {
468
457
  this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
@@ -841,19 +830,20 @@ export default class LocalParticipant extends Participant {
841
830
  return existingPublication;
842
831
  }
843
832
 
833
+ const opts: TrackPublishOptions = {
834
+ ...this.roomOptions.publishDefaults,
835
+ ...options,
836
+ };
844
837
  const isStereoInput =
845
838
  ('channelCount' in track.mediaStreamTrack.getSettings() &&
846
839
  // @ts-ignore `channelCount` on getSettings() is currently only available for Safari, but is generally the best way to determine a stereo track https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings/channelCount
847
840
  track.mediaStreamTrack.getSettings().channelCount === 2) ||
848
841
  track.mediaStreamTrack.getConstraints().channelCount === 2;
849
- const isStereo = options?.forceStereo ?? isStereoInput;
842
+ const isStereo = opts.forceStereo ?? isStereoInput;
850
843
 
851
844
  // disable dtx for stereo track if not enabled explicitly
852
845
  if (isStereo) {
853
- if (!options) {
854
- options = {};
855
- }
856
- if (options.dtx === undefined) {
846
+ if (opts.dtx === undefined) {
857
847
  this.log.info(
858
848
  `Opus DTX will be disabled for stereo tracks by default. Enable them explicitly to make it work.`,
859
849
  {
@@ -862,18 +852,14 @@ export default class LocalParticipant extends Participant {
862
852
  },
863
853
  );
864
854
  }
865
- if (options.red === undefined) {
855
+ if (opts.red === undefined) {
866
856
  this.log.info(
867
857
  `Opus RED will be disabled for stereo tracks by default. Enable them explicitly to make it work.`,
868
858
  );
869
859
  }
870
- options.dtx ??= false;
871
- options.red ??= false;
860
+ opts.dtx ??= false;
861
+ opts.red ??= false;
872
862
  }
873
- const opts: TrackPublishOptions = {
874
- ...this.roomOptions.publishDefaults,
875
- ...options,
876
- };
877
863
 
878
864
  if (!isE2EESimulcastSupported() && this.roomOptions.e2ee) {
879
865
  this.log.info(
@@ -1648,16 +1634,18 @@ export default class LocalParticipant extends Participant {
1648
1634
  const destinationIdentities = options.destinationIdentities;
1649
1635
  const topic = options.topic;
1650
1636
 
1637
+ let userPacket = new UserPacket({
1638
+ participantIdentity: this.identity,
1639
+ payload: data,
1640
+ destinationIdentities,
1641
+ topic,
1642
+ });
1643
+
1651
1644
  const packet = new DataPacket({
1652
1645
  kind: kind,
1653
1646
  value: {
1654
1647
  case: 'user',
1655
- value: new UserPacket({
1656
- participantIdentity: this.identity,
1657
- payload: data,
1658
- destinationIdentities,
1659
- topic,
1660
- }),
1648
+ value: userPacket,
1661
1649
  },
1662
1650
  });
1663
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: (payload: Uint8Array, kind: DataPacket_Kind) => void;
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[],