livekit-client 2.15.7 → 2.15.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) 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 +2442 -323
  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/api/SignalClient.d.ts +31 -2
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/api/WebSocketStream.d.ts +29 -0
  12. package/dist/src/api/WebSocketStream.d.ts.map +1 -0
  13. package/dist/src/api/utils.d.ts +2 -0
  14. package/dist/src/api/utils.d.ts.map +1 -1
  15. package/dist/src/connectionHelper/checks/publishVideo.d.ts.map +1 -1
  16. package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
  17. package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
  18. package/dist/src/e2ee/E2eeManager.d.ts +16 -2
  19. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  20. package/dist/src/e2ee/types.d.ts +35 -1
  21. package/dist/src/e2ee/types.d.ts.map +1 -1
  22. package/dist/src/e2ee/utils.d.ts +2 -0
  23. package/dist/src/e2ee/utils.d.ts.map +1 -1
  24. package/dist/src/e2ee/worker/DataCryptor.d.ts +15 -0
  25. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -0
  26. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +3 -2
  27. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  28. package/dist/src/e2ee/worker/sifPayload.d.ts +6 -6
  29. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  30. package/dist/src/index.d.ts +5 -3
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/logger.d.ts +1 -0
  33. package/dist/src/logger.d.ts.map +1 -1
  34. package/dist/src/options.d.ts +10 -2
  35. package/dist/src/options.d.ts.map +1 -1
  36. package/dist/src/room/PCTransport.d.ts +1 -0
  37. package/dist/src/room/PCTransport.d.ts.map +1 -1
  38. package/dist/src/room/PCTransportManager.d.ts +6 -4
  39. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  40. package/dist/src/room/RTCEngine.d.ts +6 -3
  41. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  42. package/dist/src/room/Room.d.ts +3 -2
  43. package/dist/src/room/Room.d.ts.map +1 -1
  44. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -2
  45. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  46. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  47. package/dist/src/room/defaults.d.ts.map +1 -1
  48. package/dist/src/room/errors.d.ts +2 -1
  49. package/dist/src/room/errors.d.ts.map +1 -1
  50. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  51. package/dist/src/room/participant/Participant.d.ts +2 -2
  52. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  53. package/dist/src/room/token-source/TokenSource.d.ts +70 -0
  54. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -0
  55. package/dist/src/room/token-source/types.d.ts +68 -0
  56. package/dist/src/room/token-source/types.d.ts.map +1 -0
  57. package/dist/src/room/token-source/utils.d.ts +5 -0
  58. package/dist/src/room/token-source/utils.d.ts.map +1 -0
  59. package/dist/src/room/track/LocalTrack.d.ts +1 -1
  60. package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
  61. package/dist/src/room/track/options.d.ts +7 -3
  62. package/dist/src/room/track/options.d.ts.map +1 -1
  63. package/dist/src/room/track/utils.d.ts.map +1 -1
  64. package/dist/src/room/types.d.ts +1 -0
  65. package/dist/src/room/types.d.ts.map +1 -1
  66. package/dist/src/room/utils.d.ts +8 -1
  67. package/dist/src/room/utils.d.ts.map +1 -1
  68. package/dist/src/utils/camelToSnakeCase.d.ts +8 -0
  69. package/dist/src/utils/camelToSnakeCase.d.ts.map +1 -0
  70. package/dist/ts4.2/{src/api → api}/SignalClient.d.ts +31 -2
  71. package/dist/ts4.2/api/WebSocketStream.d.ts +29 -0
  72. package/dist/ts4.2/{src/api → api}/utils.d.ts +2 -0
  73. package/dist/ts4.2/{src/e2ee → e2ee}/E2eeManager.d.ts +16 -2
  74. package/dist/ts4.2/{src/e2ee → e2ee}/types.d.ts +35 -1
  75. package/dist/ts4.2/{src/e2ee → e2ee}/utils.d.ts +3 -0
  76. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +15 -0
  77. package/dist/ts4.2/{src/e2ee → e2ee}/worker/ParticipantKeyHandler.d.ts +3 -2
  78. package/dist/ts4.2/{src/e2ee → e2ee}/worker/sifPayload.d.ts +6 -6
  79. package/dist/ts4.2/{src/index.d.ts → index.d.ts} +5 -3
  80. package/dist/ts4.2/{src/logger.d.ts → logger.d.ts} +1 -0
  81. package/dist/ts4.2/{src/options.d.ts → options.d.ts} +10 -2
  82. package/dist/ts4.2/{src/room → room}/PCTransport.d.ts +1 -0
  83. package/dist/ts4.2/{src/room → room}/PCTransportManager.d.ts +6 -4
  84. package/dist/ts4.2/{src/room → room}/RTCEngine.d.ts +6 -3
  85. package/dist/ts4.2/{src/room → room}/Room.d.ts +3 -2
  86. package/dist/ts4.2/{src/room → room}/data-stream/incoming/IncomingDataStreamManager.d.ts +2 -1
  87. package/dist/ts4.2/{src/room → room}/errors.d.ts +2 -1
  88. package/dist/ts4.2/{src/room → room}/participant/Participant.d.ts +2 -2
  89. package/dist/ts4.2/room/token-source/TokenSource.d.ts +71 -0
  90. package/dist/ts4.2/room/token-source/types.d.ts +68 -0
  91. package/dist/ts4.2/room/token-source/utils.d.ts +5 -0
  92. package/dist/ts4.2/{src/room → room}/track/LocalTrack.d.ts +1 -1
  93. package/dist/ts4.2/{src/room → room}/track/options.d.ts +10 -3
  94. package/dist/ts4.2/{src/room → room}/types.d.ts +1 -0
  95. package/dist/ts4.2/{src/room → room}/utils.d.ts +8 -1
  96. package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -0
  97. package/package.json +11 -10
  98. package/src/api/SignalClient.test.ts +688 -0
  99. package/src/api/SignalClient.ts +308 -161
  100. package/src/api/WebSocketStream.test.ts +625 -0
  101. package/src/api/WebSocketStream.ts +118 -0
  102. package/src/api/utils.ts +10 -0
  103. package/src/connectionHelper/checks/publishVideo.ts +5 -0
  104. package/src/connectionHelper/checks/turn.ts +1 -0
  105. package/src/connectionHelper/checks/webrtc.ts +1 -1
  106. package/src/connectionHelper/checks/websocket.ts +1 -0
  107. package/src/e2ee/E2eeManager.ts +94 -2
  108. package/src/e2ee/types.ts +44 -1
  109. package/src/e2ee/utils.ts +16 -0
  110. package/src/e2ee/worker/DataCryptor.test.ts +271 -0
  111. package/src/e2ee/worker/DataCryptor.ts +147 -0
  112. package/src/e2ee/worker/ParticipantKeyHandler.ts +4 -3
  113. package/src/e2ee/worker/e2ee.worker.ts +47 -0
  114. package/src/e2ee/worker/sifPayload.ts +10 -6
  115. package/src/index.ts +16 -1
  116. package/src/logger.ts +1 -0
  117. package/src/options.ts +15 -2
  118. package/src/room/PCTransport.ts +7 -3
  119. package/src/room/PCTransportManager.ts +39 -35
  120. package/src/room/RTCEngine.ts +109 -22
  121. package/src/room/Room.ts +43 -18
  122. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +64 -17
  123. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -0
  124. package/src/room/defaults.ts +1 -0
  125. package/src/room/errors.ts +3 -0
  126. package/src/room/participant/LocalParticipant.ts +8 -6
  127. package/src/room/participant/Participant.ts +6 -1
  128. package/src/room/token-source/TokenSource.ts +285 -0
  129. package/src/room/token-source/types.ts +84 -0
  130. package/src/room/token-source/utils.test.ts +63 -0
  131. package/src/room/token-source/utils.ts +40 -0
  132. package/src/room/track/LocalAudioTrack.ts +1 -1
  133. package/src/room/track/LocalTrack.ts +1 -1
  134. package/src/room/track/options.ts +12 -4
  135. package/src/room/track/utils.ts +10 -2
  136. package/src/room/types.ts +1 -0
  137. package/src/room/utils.ts +37 -4
  138. package/src/utils/camelToSnakeCase.ts +16 -0
  139. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/ConnectionCheck.d.ts +0 -0
  140. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/Checker.d.ts +0 -0
  141. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/cloudRegion.d.ts +0 -0
  142. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/connectionProtocol.d.ts +0 -0
  143. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishAudio.d.ts +0 -0
  144. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/publishVideo.d.ts +0 -0
  145. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/reconnect.d.ts +0 -0
  146. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/turn.d.ts +0 -0
  147. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/webrtc.d.ts +0 -0
  148. /package/dist/ts4.2/{src/connectionHelper → connectionHelper}/checks/websocket.d.ts +0 -0
  149. /package/dist/ts4.2/{src/e2ee → e2ee}/KeyProvider.d.ts +0 -0
  150. /package/dist/ts4.2/{src/e2ee → e2ee}/constants.d.ts +0 -0
  151. /package/dist/ts4.2/{src/e2ee → e2ee}/errors.d.ts +0 -0
  152. /package/dist/ts4.2/{src/e2ee → e2ee}/events.d.ts +0 -0
  153. /package/dist/ts4.2/{src/e2ee → e2ee}/index.d.ts +0 -0
  154. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/FrameCryptor.d.ts +0 -0
  155. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/e2ee.worker.d.ts +0 -0
  156. /package/dist/ts4.2/{src/e2ee → e2ee}/worker/naluUtils.d.ts +0 -0
  157. /package/dist/ts4.2/{src/room → room}/DefaultReconnectPolicy.d.ts +0 -0
  158. /package/dist/ts4.2/{src/room → room}/DeviceManager.d.ts +0 -0
  159. /package/dist/ts4.2/{src/room → room}/ReconnectPolicy.d.ts +0 -0
  160. /package/dist/ts4.2/{src/room → room}/RegionUrlProvider.d.ts +0 -0
  161. /package/dist/ts4.2/{src/room → room}/attribute-typings.d.ts +0 -0
  162. /package/dist/ts4.2/{src/room → room}/data-stream/incoming/StreamReader.d.ts +0 -0
  163. /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/OutgoingDataStreamManager.d.ts +0 -0
  164. /package/dist/ts4.2/{src/room → room}/data-stream/outgoing/StreamWriter.d.ts +0 -0
  165. /package/dist/ts4.2/{src/room → room}/defaults.d.ts +0 -0
  166. /package/dist/ts4.2/{src/room → room}/events.d.ts +0 -0
  167. /package/dist/ts4.2/{src/room → room}/participant/LocalParticipant.d.ts +0 -0
  168. /package/dist/ts4.2/{src/room → room}/participant/ParticipantTrackPermission.d.ts +0 -0
  169. /package/dist/ts4.2/{src/room → room}/participant/RemoteParticipant.d.ts +0 -0
  170. /package/dist/ts4.2/{src/room → room}/participant/publishUtils.d.ts +0 -0
  171. /package/dist/ts4.2/{src/room → room}/rpc.d.ts +0 -0
  172. /package/dist/ts4.2/{src/room → room}/stats.d.ts +0 -0
  173. /package/dist/ts4.2/{src/room → room}/timers.d.ts +0 -0
  174. /package/dist/ts4.2/{src/room → room}/track/LocalAudioTrack.d.ts +0 -0
  175. /package/dist/ts4.2/{src/room → room}/track/LocalTrackPublication.d.ts +0 -0
  176. /package/dist/ts4.2/{src/room → room}/track/LocalVideoTrack.d.ts +0 -0
  177. /package/dist/ts4.2/{src/room → room}/track/RemoteAudioTrack.d.ts +0 -0
  178. /package/dist/ts4.2/{src/room → room}/track/RemoteTrack.d.ts +0 -0
  179. /package/dist/ts4.2/{src/room → room}/track/RemoteTrackPublication.d.ts +0 -0
  180. /package/dist/ts4.2/{src/room → room}/track/RemoteVideoTrack.d.ts +0 -0
  181. /package/dist/ts4.2/{src/room → room}/track/Track.d.ts +0 -0
  182. /package/dist/ts4.2/{src/room → room}/track/TrackPublication.d.ts +0 -0
  183. /package/dist/ts4.2/{src/room → room}/track/create.d.ts +0 -0
  184. /package/dist/ts4.2/{src/room → room}/track/facingMode.d.ts +0 -0
  185. /package/dist/ts4.2/{src/room → room}/track/processor/types.d.ts +0 -0
  186. /package/dist/ts4.2/{src/room → room}/track/record.d.ts +0 -0
  187. /package/dist/ts4.2/{src/room → room}/track/types.d.ts +0 -0
  188. /package/dist/ts4.2/{src/room → room}/track/utils.d.ts +0 -0
  189. /package/dist/ts4.2/{src/test → test}/MockMediaStreamTrack.d.ts +0 -0
  190. /package/dist/ts4.2/{src/test → test}/mocks.d.ts +0 -0
  191. /package/dist/ts4.2/{src/utils → utils}/AsyncQueue.d.ts +0 -0
  192. /package/dist/ts4.2/{src/utils → utils}/browserParser.d.ts +0 -0
  193. /package/dist/ts4.2/{src/utils → utils}/cloneDeep.d.ts +0 -0
  194. /package/dist/ts4.2/{src/utils → utils}/dataPacketBuffer.d.ts +0 -0
  195. /package/dist/ts4.2/{src/utils → utils}/ttlmap.d.ts +0 -0
  196. /package/dist/ts4.2/{src/version.d.ts → version.d.ts} +0 -0
@@ -9,9 +9,13 @@ 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,
18
+ MediaSectionsRequirement,
15
19
  ParticipantInfo,
16
20
  ReconnectReason,
17
21
  type ReconnectResponse,
@@ -43,6 +47,8 @@ import {
43
47
  SignalConnectionState,
44
48
  toProtoSessionDescription,
45
49
  } from '../api/SignalClient';
50
+ import type { BaseE2EEManager } from '../e2ee/E2eeManager';
51
+ import { asEncryptablePacket } from '../e2ee/utils';
46
52
  import log, { LoggerNames, getLogger } from '../logger';
47
53
  import type { InternalRoomOptions } from '../options';
48
54
  import { DataPacketBuffer } from '../utils/dataPacketBuffer';
@@ -116,6 +122,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
116
122
  */
117
123
  latestRemoteOfferId: number = 0;
118
124
 
125
+ /** @internal */
126
+ e2eeManager: BaseE2EEManager | undefined;
127
+
119
128
  get isClosed() {
120
129
  return this._isClosed;
121
130
  }
@@ -203,7 +212,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
203
212
  this.client = new SignalClient(undefined, this.loggerOptions);
204
213
  this.client.signalLatency = this.options.expSignalLatency;
205
214
  this.reconnectPolicy = this.options.reconnectPolicy;
206
- this.registerOnLineListener();
207
215
  this.closingLock = new Mutex();
208
216
  this.dataProcessLock = new Mutex();
209
217
  this.dcBufferStatus = new Map([
@@ -260,9 +268,12 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
260
268
 
261
269
  // create offer
262
270
  if (!this.subscriberPrimary || joinResponse.fastPublish) {
263
- this.negotiate();
271
+ this.negotiate().catch((err) => {
272
+ log.error(err, this.logContext);
273
+ });
264
274
  }
265
275
 
276
+ this.registerOnLineListener();
266
277
  this.clientConfiguration = joinResponse.clientConfiguration;
267
278
  this.emit(EngineEvent.SignalConnected, joinResponse);
268
279
  return joinResponse;
@@ -415,7 +426,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
415
426
 
416
427
  this.pcManager = new PCTransportManager(
417
428
  rtcConfig,
418
- joinResponse.subscriberPrimary,
429
+ this.options.singlePeerConnection
430
+ ? 'publisher-only'
431
+ : joinResponse.subscriberPrimary
432
+ ? 'subscriber-primary'
433
+ : 'publisher-primary',
419
434
  this.loggerOptions,
420
435
  );
421
436
 
@@ -445,7 +460,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
445
460
  }
446
461
  } else if (connectionState === PCTransportState.FAILED) {
447
462
  // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
448
- if (this.pcState === PCState.Connected) {
463
+ if (this.pcState === PCState.Connected || this.pcState === PCState.Reconnecting) {
449
464
  this.pcState = PCState.Disconnected;
450
465
 
451
466
  this.handleDisconnect(
@@ -471,6 +486,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
471
486
  }
472
487
  };
473
488
  this.pcManager.onTrack = (ev: RTCTrackEvent) => {
489
+ // this fires after the underlying transceiver is stopped and potentially
490
+ // peer connection closed, so do not bubble up if there are no streams
491
+ if (ev.streams.length === 0) return;
474
492
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
475
493
  };
476
494
 
@@ -556,6 +574,18 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
556
574
  this.emit(EngineEvent.RoomMoved, res);
557
575
  };
558
576
 
577
+ this.client.onMediaSectionsRequirement = (requirement: MediaSectionsRequirement) => {
578
+ const transceiverInit: RTCRtpTransceiverInit = { direction: 'recvonly' };
579
+ for (let i: number = 0; i < requirement.numAudios; i++) {
580
+ this.pcManager?.addPublisherTransceiverOfKind('audio', transceiverInit);
581
+ }
582
+ for (let i: number = 0; i < requirement.numVideos; i++) {
583
+ this.pcManager?.addPublisherTransceiverOfKind('video', transceiverInit);
584
+ }
585
+
586
+ this.negotiate();
587
+ };
588
+
559
589
  this.client.onClose = () => {
560
590
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
561
591
  };
@@ -710,12 +740,34 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
710
740
  if (dp.value?.case === 'speaker') {
711
741
  // dispatch speaker updates
712
742
  this.emit(EngineEvent.ActiveSpeakersUpdate, dp.value.value.speakers);
743
+ } else if (dp.value?.case === 'encryptedPacket') {
744
+ if (!this.e2eeManager) {
745
+ this.log.error('Received encrypted packet but E2EE not set up', this.logContext);
746
+ return;
747
+ }
748
+ const decryptedData = await this.e2eeManager?.handleEncryptedData(
749
+ dp.value.value.encryptedValue,
750
+ dp.value.value.iv,
751
+ dp.participantIdentity,
752
+ dp.value.value.keyIndex,
753
+ );
754
+ const decryptedPacket = EncryptedPacketPayload.fromBinary(decryptedData.payload);
755
+ const newDp = new DataPacket({
756
+ value: decryptedPacket.value,
757
+ participantIdentity: dp.participantIdentity,
758
+ participantSid: dp.participantSid,
759
+ });
760
+ if (newDp.value?.case === 'user') {
761
+ // compatibility
762
+ applyUserDataCompat(newDp, newDp.value.value);
763
+ }
764
+ this.emit(EngineEvent.DataPacketReceived, newDp, dp.value.value.encryptionType);
713
765
  } else {
714
766
  if (dp.value?.case === 'user') {
715
767
  // compatibility
716
768
  applyUserDataCompat(dp, dp.value.value);
717
769
  }
718
- this.emit(EngineEvent.DataPacketReceived, dp);
770
+ this.emit(EngineEvent.DataPacketReceived, dp, Encryption_Type.NONE);
719
771
  }
720
772
  } finally {
721
773
  unlock();
@@ -1191,11 +1243,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1191
1243
  // make sure we do have a data connection
1192
1244
  await this.ensurePublisherConnected(kind);
1193
1245
 
1246
+ if (this.e2eeManager && this.e2eeManager.isDataChannelEncryptionEnabled) {
1247
+ const encryptablePacket = asEncryptablePacket(packet);
1248
+ if (encryptablePacket) {
1249
+ const encryptedData = await this.e2eeManager.encryptData(encryptablePacket.toBinary());
1250
+ packet.value = {
1251
+ case: 'encryptedPacket',
1252
+ value: new EncryptedPacket({
1253
+ encryptedValue: encryptedData.payload,
1254
+ iv: encryptedData.iv,
1255
+ keyIndex: encryptedData.keyIndex,
1256
+ }),
1257
+ };
1258
+ }
1259
+ }
1260
+
1194
1261
  if (kind === DataPacket_Kind.RELIABLE) {
1195
1262
  packet.sequence = this.reliableDataSequence;
1196
1263
  this.reliableDataSequence += 1;
1197
1264
  }
1265
+
1198
1266
  const msg = packet.toBinary();
1267
+
1199
1268
  const dc = this.dataChannelForKind(kind);
1200
1269
  if (dc) {
1201
1270
  if (kind === DataPacket_Kind.RELIABLE) {
@@ -1293,7 +1362,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1293
1362
  }
1294
1363
  if (needNegotiation) {
1295
1364
  // start negotiation
1296
- this.negotiate();
1365
+ this.negotiate().catch((err) => {
1366
+ log.error(err, this.logContext);
1367
+ });
1297
1368
  }
1298
1369
 
1299
1370
  const targetChannel = this.dataChannelForKind(kind, subscriber);
@@ -1429,8 +1500,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1429
1500
  this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
1430
1501
  return;
1431
1502
  }
1432
- const previousAnswer = this.pcManager.subscriber.getLocalDescription();
1433
- const previousOffer = this.pcManager.subscriber.getRemoteDescription();
1503
+ const previousPublisherOffer = this.pcManager.publisher.getLocalDescription();
1504
+ const previousPublisherAnswer = this.pcManager.publisher.getRemoteDescription();
1505
+ const previousSubscriberOffer = this.pcManager.subscriber?.getRemoteDescription();
1506
+ const previousSubscriberAnswer = this.pcManager.subscriber?.getLocalDescription();
1434
1507
 
1435
1508
  /* 1. autosubscribe on, so subscribed tracks = all tracks - unsub tracks,
1436
1509
  in this case, we send unsub tracks, so server add all tracks to this
@@ -1452,18 +1525,32 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1452
1525
 
1453
1526
  this.client.sendSyncState(
1454
1527
  new SyncState({
1455
- answer: previousAnswer
1456
- ? toProtoSessionDescription({
1457
- sdp: previousAnswer.sdp,
1458
- type: previousAnswer.type,
1459
- })
1460
- : undefined,
1461
- offer: previousOffer
1462
- ? toProtoSessionDescription({
1463
- sdp: previousOffer.sdp,
1464
- type: previousOffer.type,
1465
- })
1466
- : undefined,
1528
+ answer: this.options.singlePeerConnection
1529
+ ? previousPublisherAnswer
1530
+ ? toProtoSessionDescription({
1531
+ sdp: previousPublisherAnswer.sdp,
1532
+ type: previousPublisherAnswer.type,
1533
+ })
1534
+ : undefined
1535
+ : previousSubscriberAnswer
1536
+ ? toProtoSessionDescription({
1537
+ sdp: previousSubscriberAnswer.sdp,
1538
+ type: previousSubscriberAnswer.type,
1539
+ })
1540
+ : undefined,
1541
+ offer: this.options.singlePeerConnection
1542
+ ? previousPublisherOffer
1543
+ ? toProtoSessionDescription({
1544
+ sdp: previousPublisherOffer.sdp,
1545
+ type: previousPublisherOffer.type,
1546
+ })
1547
+ : undefined
1548
+ : previousSubscriberOffer
1549
+ ? toProtoSessionDescription({
1550
+ sdp: previousSubscriberOffer.sdp,
1551
+ type: previousSubscriberOffer.type,
1552
+ })
1553
+ : undefined,
1467
1554
  subscription: new UpdateSubscription({
1468
1555
  trackSids,
1469
1556
  subscribe: !autoSubscribe,
@@ -1558,9 +1645,9 @@ export type EngineEventCallbacks = {
1558
1645
  receiver: RTCRtpReceiver,
1559
1646
  ) => void;
1560
1647
  activeSpeakersUpdate: (speakers: Array<SpeakerInfo>) => void;
1561
- dataPacketReceived: (packet: DataPacket) => void;
1648
+ dataPacketReceived: (packet: DataPacket, encryptionType: Encryption_Type) => void;
1562
1649
  transcriptionReceived: (transcription: Transcription) => void;
1563
- transportsCreated: (publisher: PCTransport, subscriber: PCTransport) => void;
1650
+ transportsCreated: (publisher: PCTransport, subscriber?: PCTransport) => void;
1564
1651
  /** @internal */
1565
1652
  trackSenderAdded: (track: Track, sender: RTCRtpSender) => void;
1566
1653
  rtpVideoMapUpdate: (rtpMap: Map<number, VideoCodec>) => void;
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,17 @@ 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;
373
+ this.e2eeManager.isDataChannelEncryptionEnabled = dcEncryptionEnabled;
361
374
  } else {
362
- this.e2eeManager = new E2EEManager(this.options.e2ee);
375
+ this.e2eeManager = new E2EEManager(e2eeOptions, dcEncryptionEnabled);
363
376
  }
364
377
  this.e2eeManager.on(
365
378
  EncryptionEvent.ParticipantEncryptionStatusChanged,
@@ -443,6 +456,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
443
456
  }
444
457
 
445
458
  this.engine = new RTCEngine(this.options);
459
+ this.engine.e2eeManager = this.e2eeManager;
446
460
 
447
461
  this.engine
448
462
  .on(EngineEvent.ParticipantUpdate, this.handleParticipantUpdates)
@@ -748,6 +762,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
748
762
  maxRetries: connectOptions.maxRetries,
749
763
  e2eeEnabled: !!this.e2eeManager,
750
764
  websocketTimeout: connectOptions.websocketTimeout,
765
+ singlePeerConnection: roomOptions.singlePeerConnection,
751
766
  },
752
767
  abortController.signal,
753
768
  );
@@ -789,7 +804,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
789
804
  this.localParticipant.identity = pi.identity;
790
805
  this.localParticipant.setEnabledPublishCodecs(joinResponse.enabledPublishCodecs);
791
806
 
792
- if (this.options.e2ee && this.e2eeManager) {
807
+ if (this.e2eeManager) {
793
808
  try {
794
809
  this.e2eeManager.setSifTrailer(joinResponse.sifTrailer);
795
810
  } catch (e: any) {
@@ -926,8 +941,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
926
941
  this.isResuming
927
942
  ) {
928
943
  // try aborting pending connection attempt
929
- this.log.warn('abort connection attempt', this.logContext);
930
- this.abortController?.abort();
944
+ const msg = 'Abort connection attempt due to user initiated disconnect';
945
+ this.log.warn(msg, this.logContext);
946
+ this.abortController?.abort(msg);
931
947
  // in case the abort controller didn't manage to cancel the connection attempt, reject the connect promise explicitly
932
948
  this.connectFuture?.reject?.(
933
949
  new ConnectionError('Client initiated disconnect', ConnectionErrorReason.Cancelled),
@@ -1711,11 +1727,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1711
1727
  pub.setSubscriptionError(update.err);
1712
1728
  };
1713
1729
 
1714
- private handleDataPacket = (packet: DataPacket) => {
1730
+ private handleDataPacket = (packet: DataPacket, encryptionType: Encryption_Type) => {
1715
1731
  // find the participant
1716
1732
  const participant = this.remoteParticipants.get(packet.participantIdentity);
1717
1733
  if (packet.value.case === 'user') {
1718
- this.handleUserPacket(participant, packet.value.value, packet.kind);
1734
+ this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType);
1719
1735
  } else if (packet.value.case === 'transcription') {
1720
1736
  this.handleTranscription(participant, packet.value.value);
1721
1737
  } else if (packet.value.case === 'sipDtmf') {
@@ -1729,7 +1745,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1729
1745
  packet.value.case === 'streamChunk' ||
1730
1746
  packet.value.case === 'streamTrailer'
1731
1747
  ) {
1732
- this.handleDataStream(packet);
1748
+ this.handleDataStream(packet, encryptionType);
1733
1749
  } else if (packet.value.case === 'rpcRequest') {
1734
1750
  const rpc = packet.value.value;
1735
1751
  this.handleIncomingRpcRequest(
@@ -1747,11 +1763,19 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1747
1763
  participant: RemoteParticipant | undefined,
1748
1764
  userPacket: UserPacket,
1749
1765
  kind: DataPacket_Kind,
1766
+ encryptionType: Encryption_Type,
1750
1767
  ) => {
1751
- this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic);
1768
+ this.emit(
1769
+ RoomEvent.DataReceived,
1770
+ userPacket.payload,
1771
+ participant,
1772
+ kind,
1773
+ userPacket.topic,
1774
+ encryptionType,
1775
+ );
1752
1776
 
1753
1777
  // also emit on the participant
1754
- participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind);
1778
+ participant?.emit(ParticipantEvent.DataReceived, userPacket.payload, kind, encryptionType);
1755
1779
  };
1756
1780
 
1757
1781
  private handleSipDtmf = (participant: RemoteParticipant | undefined, dtmf: SipDTMF) => {
@@ -1791,8 +1815,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1791
1815
  this.emit(RoomEvent.MetricsReceived, metrics, participant);
1792
1816
  };
1793
1817
 
1794
- private handleDataStream = (packet: DataPacket) => {
1795
- this.incomingDataStreamManager.handleDataStreamPacket(packet);
1818
+ private handleDataStream = (packet: DataPacket, encryptionType: Encryption_Type) => {
1819
+ this.incomingDataStreamManager.handleDataStreamPacket(packet, encryptionType);
1796
1820
  };
1797
1821
 
1798
1822
  private async handleIncomingRpcRequest(
@@ -2596,6 +2620,7 @@ export type RoomEventCallbacks = {
2596
2620
  participant?: RemoteParticipant,
2597
2621
  kind?: DataPacket_Kind,
2598
2622
  topic?: string,
2623
+ encryptionType?: Encryption_Type,
2599
2624
  ) => void;
2600
2625
  sipDTMFReceived: (dtmf: SipDTMF, participant?: RemoteParticipant) => void;
2601
2626
  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({
@@ -42,6 +42,7 @@ export const roomOptionDefaults: InternalRoomOptions = {
42
42
  reconnectPolicy: new DefaultReconnectPolicy(),
43
43
  disconnectOnPageLeave: true,
44
44
  webAudioMix: false,
45
+ singlePeerConnection: false,
45
46
  } as const;
46
47
 
47
48
  export const roomConnectOptionDefaults: InternalRoomConnectOptions = {
@@ -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: new UserPacket({
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: (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[],