livekit-client 2.11.4 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) 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 +25 -14
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +155 -22
  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 +2 -1
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  12. package/dist/src/e2ee/KeyProvider.d.ts +8 -5
  13. package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
  14. package/dist/src/e2ee/events.d.ts +8 -3
  15. package/dist/src/e2ee/events.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +5 -1
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  19. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +4 -4
  20. package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
  21. package/dist/src/room/RTCEngine.d.ts +2 -1
  22. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  23. package/dist/src/room/Room.d.ts +2 -0
  24. package/dist/src/room/Room.d.ts.map +1 -1
  25. package/dist/src/room/events.d.ts +22 -2
  26. package/dist/src/room/events.d.ts.map +1 -1
  27. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  28. package/dist/src/room/participant/Participant.d.ts +13 -0
  29. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  30. package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
  31. package/dist/src/room/track/create.d.ts.map +1 -1
  32. package/dist/src/version.d.ts +1 -1
  33. package/dist/ts4.2/src/api/SignalClient.d.ts +2 -1
  34. package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +8 -5
  35. package/dist/ts4.2/src/e2ee/events.d.ts +8 -3
  36. package/dist/ts4.2/src/e2ee/types.d.ts +5 -1
  37. package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +4 -4
  38. package/dist/ts4.2/src/room/RTCEngine.d.ts +2 -1
  39. package/dist/ts4.2/src/room/Room.d.ts +2 -0
  40. package/dist/ts4.2/src/room/events.d.ts +22 -2
  41. package/dist/ts4.2/src/room/participant/Participant.d.ts +13 -0
  42. package/dist/ts4.2/src/version.d.ts +1 -1
  43. package/package.json +2 -2
  44. package/src/api/SignalClient.ts +10 -0
  45. package/src/e2ee/E2eeManager.ts +6 -1
  46. package/src/e2ee/KeyProvider.ts +13 -6
  47. package/src/e2ee/events.ts +12 -3
  48. package/src/e2ee/types.ts +8 -1
  49. package/src/e2ee/worker/FrameCryptor.ts +8 -4
  50. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +104 -4
  51. package/src/e2ee/worker/ParticipantKeyHandler.ts +22 -23
  52. package/src/e2ee/worker/e2ee.worker.ts +7 -2
  53. package/src/room/RTCEngine.ts +8 -2
  54. package/src/room/Room.ts +25 -0
  55. package/src/room/events.ts +23 -0
  56. package/src/room/participant/LocalParticipant.ts +1 -5
  57. package/src/room/participant/Participant.ts +47 -2
  58. package/src/room/track/RemoteAudioTrack.ts +3 -2
  59. package/src/room/track/create.ts +3 -5
  60. package/src/version.ts +1 -1
@@ -11,6 +11,7 @@ import type {
11
11
  KeyProviderOptions,
12
12
  RatchetMessage,
13
13
  RatchetRequestMessage,
14
+ RatchetResult,
14
15
  } from '../types';
15
16
  import { FrameCryptor, encryptionEnabledMap } from './FrameCryptor';
16
17
  import { ParticipantKeyHandler } from './ParticipantKeyHandler';
@@ -229,13 +230,17 @@ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
229
230
  });
230
231
  }
231
232
 
232
- function emitRatchetedKeys(material: CryptoKey, participantIdentity: string, keyIndex?: number) {
233
+ function emitRatchetedKeys(
234
+ ratchetResult: RatchetResult,
235
+ participantIdentity: string,
236
+ keyIndex?: number,
237
+ ) {
233
238
  const msg: RatchetMessage = {
234
239
  kind: `ratchetKey`,
235
240
  data: {
236
241
  participantIdentity,
237
242
  keyIndex,
238
- material,
243
+ ratchetResult,
239
244
  },
240
245
  };
241
246
  postMessage(msg);
@@ -16,6 +16,7 @@ import {
16
16
  type ReconnectResponse,
17
17
  RequestResponse,
18
18
  Room as RoomModel,
19
+ RoomMovedResponse,
19
20
  RpcAck,
20
21
  RpcResponse,
21
22
  SignalTarget,
@@ -529,6 +530,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
529
530
  this.emit(EngineEvent.SubscribedQualityUpdate, update);
530
531
  };
531
532
 
533
+ this.client.onRoomMoved = (res: RoomMovedResponse) => {
534
+ this.participantSid = res.participant?.sid;
535
+ this.emit(EngineEvent.RoomMoved, res);
536
+ };
537
+
532
538
  this.client.onClose = () => {
533
539
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
534
540
  };
@@ -616,8 +622,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
616
622
 
617
623
  // create data channels
618
624
  this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
619
- // will drop older packets that arrive
620
- ordered: true,
625
+ ordered: false,
621
626
  maxRetransmits: 0,
622
627
  });
623
628
  this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
@@ -1493,6 +1498,7 @@ export type EngineEventCallbacks = {
1493
1498
  dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
1494
1499
  participantUpdate: (infos: ParticipantInfo[]) => void;
1495
1500
  roomUpdate: (room: RoomModel) => void;
1501
+ roomMoved: (room: RoomMovedResponse) => void;
1496
1502
  connectionQualityUpdate: (update: ConnectionQualityUpdate) => void;
1497
1503
  speakersChanged: (speakerUpdates: SpeakerInfo[]) => void;
1498
1504
  streamStateChanged: (update: StreamStateUpdate) => void;
package/src/room/Room.ts CHANGED
@@ -589,6 +589,25 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
589
589
  trackPublication,
590
590
  this.localParticipant,
591
591
  );
592
+ })
593
+ .on(EngineEvent.RoomMoved, (roomMoved) => {
594
+ this.log.debug('room moved', roomMoved);
595
+
596
+ if (roomMoved.room) {
597
+ this.handleRoomUpdate(roomMoved.room);
598
+ }
599
+
600
+ this.remoteParticipants.forEach((participant, identity) => {
601
+ this.handleParticipantDisconnected(identity, participant);
602
+ });
603
+
604
+ this.emit(RoomEvent.Moved, roomMoved.room!.name, roomMoved.token);
605
+
606
+ if (roomMoved.participant) {
607
+ this.handleParticipantUpdates([roomMoved.participant, ...roomMoved.otherParticipants]);
608
+ } else {
609
+ this.handleParticipantUpdates(roomMoved.otherParticipants);
610
+ }
592
611
  });
593
612
 
594
613
  if (this.localParticipant) {
@@ -1625,6 +1644,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1625
1644
  participant.unpublishTrack(publication.trackSid, true);
1626
1645
  });
1627
1646
  this.emit(RoomEvent.ParticipantDisconnected, participant);
1647
+ participant.setDisconnected();
1628
1648
  this.localParticipant?.handleParticipantDisconnected(participant.identity);
1629
1649
  }
1630
1650
 
@@ -2221,6 +2241,9 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2221
2241
  status,
2222
2242
  participant,
2223
2243
  );
2244
+ })
2245
+ .on(ParticipantEvent.Active, () => {
2246
+ this.emitWhenConnected(RoomEvent.ParticipantActive, participant);
2224
2247
  });
2225
2248
 
2226
2249
  // update info at the end after callbacks have been set up
@@ -2611,6 +2634,7 @@ export type RoomEventCallbacks = {
2611
2634
  reconnected: () => void;
2612
2635
  disconnected: (reason?: DisconnectReason) => void;
2613
2636
  connectionStateChanged: (state: ConnectionState) => void;
2637
+ moved: (name: string, token: string) => void;
2614
2638
  mediaDevicesChanged: () => void;
2615
2639
  participantConnected: (participant: RemoteParticipant) => void;
2616
2640
  participantDisconnected: (participant: RemoteParticipant) => void;
@@ -2694,4 +2718,5 @@ export type RoomEventCallbacks = {
2694
2718
  chatMessage: (message: ChatMessage, participant?: RemoteParticipant | LocalParticipant) => void;
2695
2719
  localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
2696
2720
  metricsReceived: (metrics: MetricsBatch, participant?: Participant) => void;
2721
+ participantActive: (participant: Participant) => void;
2697
2722
  };
@@ -52,6 +52,16 @@ export enum RoomEvent {
52
52
  */
53
53
  ConnectionStateChanged = 'connectionStateChanged',
54
54
 
55
+ /**
56
+ * When participant has been moved to a different room by the service request.
57
+ * The behavior looks like the participant has been disconnected and reconnected to a different room
58
+ * seamlessly without connection state transition.
59
+ * A new token will be provided for reconnecting to the new room if needed.
60
+ *
61
+ * args: ([[room: string, token: string]])
62
+ */
63
+ Moved = 'moved',
64
+
55
65
  /**
56
66
  * When input or output devices on the machine have changed.
57
67
  */
@@ -193,6 +203,13 @@ export enum RoomEvent {
193
203
  */
194
204
  ParticipantAttributesChanged = 'participantAttributesChanged',
195
205
 
206
+ /**
207
+ * Emitted when the participant's state changes to ACTIVE and is ready to send/receive data messages
208
+ *
209
+ * args: (participant: [[Participant]])
210
+ */
211
+ ParticipantActive = 'participantActive',
212
+
196
213
  /**
197
214
  * Room metadata is a simple way for app-specific state to be pushed to
198
215
  * all users.
@@ -530,6 +547,11 @@ export enum ParticipantEvent {
530
547
 
531
548
  /** only emitted on local participant */
532
549
  ChatMessage = 'chatMessage',
550
+
551
+ /**
552
+ * Emitted when the participant's state changes to ACTIVE and is ready to send/receive data messages
553
+ */
554
+ Active = 'active',
533
555
  }
534
556
 
535
557
  /** @internal */
@@ -563,6 +585,7 @@ export enum EngineEvent {
563
585
  Offline = 'offline',
564
586
  SignalRequestResponse = 'signalRequestResponse',
565
587
  SignalConnected = 'signalConnected',
588
+ RoomMoved = 'roomMoved',
566
589
  }
567
590
 
568
591
  export enum TrackEvent {
@@ -1780,6 +1780,7 @@ export default class LocalParticipant extends Participant {
1780
1780
  streamId,
1781
1781
  topic: info.topic,
1782
1782
  timestamp: numberToBigInt(Date.now()),
1783
+ attributes: info.attributes,
1783
1784
  contentHeader: {
1784
1785
  case: 'byteHeader',
1785
1786
  value: new DataStream_ByteHeader({
@@ -2053,11 +2054,6 @@ export default class LocalParticipant extends Participant {
2053
2054
 
2054
2055
  /** @internal */
2055
2056
  updateInfo(info: ParticipantInfo): boolean {
2056
- if (info.sid !== this.sid) {
2057
- // drop updates that specify a wrong sid.
2058
- // the sid for local participant is only explicitly set on join and full reconnect
2059
- return false;
2060
- }
2061
2057
  if (!super.updateInfo(info)) {
2062
2058
  return false;
2063
2059
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DataPacket_Kind,
3
3
  ParticipantInfo,
4
+ ParticipantInfo_State,
4
5
  ParticipantInfo_Kind as ParticipantKind,
5
6
  ParticipantPermission,
6
7
  ConnectionQuality as ProtoQuality,
@@ -18,7 +19,7 @@ import { Track } from '../track/Track';
18
19
  import type { TrackPublication } from '../track/TrackPublication';
19
20
  import { diffAttributes } from '../track/utils';
20
21
  import type { ChatMessage, LoggerOptions, TranscriptionSegment } from '../types';
21
- import { isAudioTrack } from '../utils';
22
+ import { Future, isAudioTrack } from '../utils';
22
23
 
23
24
  export enum ConnectionQuality {
24
25
  Excellent = 'excellent',
@@ -93,6 +94,8 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
93
94
 
94
95
  protected loggerOptions?: LoggerOptions;
95
96
 
97
+ protected activeFuture?: Future<void>;
98
+
96
99
  protected get logContext() {
97
100
  return {
98
101
  ...this.loggerOptions?.loggerContextCb?.(),
@@ -110,6 +113,10 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
110
113
  return this.permissions?.agent || this.kind === ParticipantKind.AGENT;
111
114
  }
112
115
 
116
+ get isActive() {
117
+ return this.participantInfo?.state === ParticipantInfo_State.ACTIVE;
118
+ }
119
+
113
120
  get kind() {
114
121
  return this._kind;
115
122
  }
@@ -173,6 +180,28 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
173
180
  }
174
181
  }
175
182
 
183
+ /**
184
+ * Waits until the participant is active and ready to receive data messages
185
+ * @returns a promise that resolves when the participant is active
186
+ */
187
+ waitUntilActive(): Promise<void> {
188
+ if (this.isActive) {
189
+ return Promise.resolve();
190
+ }
191
+
192
+ if (this.activeFuture) {
193
+ return this.activeFuture.promise;
194
+ }
195
+
196
+ this.activeFuture = new Future<void>();
197
+
198
+ this.once(ParticipantEvent.Active, () => {
199
+ this.activeFuture?.resolve?.();
200
+ this.activeFuture = undefined;
201
+ });
202
+ return this.activeFuture.promise;
203
+ }
204
+
176
205
  get connectionQuality(): ConnectionQuality {
177
206
  return this._connectionQuality;
178
207
  }
@@ -224,12 +253,17 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
224
253
  this._setName(info.name);
225
254
  this._setMetadata(info.metadata);
226
255
  this._setAttributes(info.attributes);
256
+ if (
257
+ info.state === ParticipantInfo_State.ACTIVE &&
258
+ this.participantInfo?.state !== ParticipantInfo_State.ACTIVE
259
+ ) {
260
+ this.emit(ParticipantEvent.Active);
261
+ }
227
262
  if (info.permission) {
228
263
  this.setPermissions(info.permission);
229
264
  }
230
265
  // set this last so setMetadata can detect changes
231
266
  this.participantInfo = info;
232
- this.log.trace('update participant info', { ...this.logContext, info });
233
267
  return true;
234
268
  }
235
269
 
@@ -310,6 +344,16 @@ export default class Participant extends (EventEmitter as new () => TypedEmitter
310
344
  }
311
345
  }
312
346
 
347
+ /**
348
+ * @internal
349
+ */
350
+ setDisconnected() {
351
+ if (this.activeFuture) {
352
+ this.activeFuture.reject?.(new Error('Participant disconnected'));
353
+ this.activeFuture = undefined;
354
+ }
355
+ }
356
+
313
357
  /**
314
358
  * @internal
315
359
  */
@@ -387,4 +431,5 @@ export type ParticipantEventCallbacks = {
387
431
  attributesChanged: (changedAttributes: Record<string, string>) => void;
388
432
  localTrackSubscribed: (trackPublication: LocalTrackPublication) => void;
389
433
  chatMessage: (msg: ChatMessage) => void;
434
+ active: () => void;
390
435
  };
@@ -104,8 +104,9 @@ export default class RemoteAudioTrack extends RemoteTrack<Track.Kind.Audio> {
104
104
  }
105
105
 
106
106
  if (this.sinkId && supportsSetSinkId(element)) {
107
- /* @ts-ignore */
108
- element.setSinkId(this.sinkId);
107
+ element.setSinkId(this.sinkId).catch((e) => {
108
+ this.log.error('Failed to set sink id on remote audio track', e, this.logContext);
109
+ });
109
110
  }
110
111
  if (this.audioContext && needsNewWebAudioConnection) {
111
112
  this.log.debug('using audio context mapping', this.logContext);
@@ -78,18 +78,16 @@ export async function createLocalTracks(
78
78
  deviceId: { ideal: deviceId },
79
79
  };
80
80
  }
81
- // TODO if internal options don't have device Id specified, set it to 'default'
82
81
  if (
83
82
  internalOptions.audio === true ||
84
83
  (typeof internalOptions.audio === 'object' && !internalOptions.audio.deviceId)
85
84
  ) {
86
85
  internalOptions.audio = { deviceId: 'default' };
87
86
  }
88
- if (
89
- internalOptions.video === true ||
90
- (typeof internalOptions.video === 'object' && !internalOptions.video.deviceId)
91
- ) {
87
+ if (internalOptions.video === true) {
92
88
  internalOptions.video = { deviceId: 'default' };
89
+ } else if (typeof internalOptions.video === 'object' && !internalOptions.video.deviceId) {
90
+ internalOptions.video.deviceId = 'default';
93
91
  }
94
92
  const opts = mergeDefaultOptions(internalOptions, audioDefaults, videoDefaults);
95
93
  const constraints = constraintsForOptions(opts);
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { version as v } from '../package.json';
2
2
 
3
3
  export const version = v;
4
- export const protocolVersion = 15;
4
+ export const protocolVersion = 16;