livekit-client 2.18.1 → 2.18.2

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 (35) hide show
  1. package/dist/livekit-client.esm.mjs +244 -134
  2. package/dist/livekit-client.esm.mjs.map +1 -1
  3. package/dist/livekit-client.umd.js +1 -1
  4. package/dist/livekit-client.umd.js.map +1 -1
  5. package/dist/src/connectionHelper/ConnectionCheck.d.ts +1 -1
  6. package/dist/src/connectionHelper/ConnectionCheck.d.ts.map +1 -1
  7. package/dist/src/index.d.ts +1 -0
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/room/RTCEngine.d.ts +6 -5
  10. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  11. package/dist/src/room/Room.d.ts +1 -0
  12. package/dist/src/room/Room.d.ts.map +1 -1
  13. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
  14. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  15. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  16. package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
  17. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  18. package/dist/src/utils/serializer.d.ts +48 -0
  19. package/dist/src/utils/serializer.d.ts.map +1 -0
  20. package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +2 -1
  21. package/dist/ts4.2/index.d.ts +2 -0
  22. package/dist/ts4.2/room/RTCEngine.d.ts +6 -5
  23. package/dist/ts4.2/room/Room.d.ts +1 -0
  24. package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +5 -1
  25. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +1 -0
  26. package/dist/ts4.2/utils/serializer.d.ts +48 -0
  27. package/package.json +1 -1
  28. package/src/connectionHelper/ConnectionCheck.ts +1 -1
  29. package/src/index.ts +7 -0
  30. package/src/room/RTCEngine.ts +47 -15
  31. package/src/room/Room.ts +19 -23
  32. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +26 -2
  33. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +7 -0
  34. package/src/room/participant/LocalParticipant.ts +17 -2
  35. package/src/utils/serializer.ts +72 -0
package/src/room/Room.ts CHANGED
@@ -187,6 +187,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
187
187
 
188
188
  private e2eeManager: BaseE2EEManager | undefined;
189
189
 
190
+ private e2eeStateMutex: Mutex = new Mutex();
191
+
190
192
  private connectionReconcileInterval?: ReturnType<typeof setInterval>;
191
193
 
192
194
  private regionUrlProvider?: RegionUrlProvider;
@@ -293,7 +295,6 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
293
295
  });
294
296
 
295
297
  this.disconnectLock = new Mutex();
296
-
297
298
  this.localParticipant = new LocalParticipant(
298
299
  '',
299
300
  '',
@@ -411,13 +412,21 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
411
412
  * @experimental
412
413
  */
413
414
  async setE2EEEnabled(enabled: boolean) {
414
- if (this.e2eeManager) {
415
- await Promise.all([this.localParticipant.setE2EEEnabled(enabled)]);
416
- if (this.localParticipant.identity !== '') {
417
- this.e2eeManager.setParticipantCryptorEnabled(enabled, this.localParticipant.identity);
415
+ const unlock = await this.e2eeStateMutex.lock();
416
+ try {
417
+ if (this.e2eeManager) {
418
+ if (this.isE2EEEnabled !== enabled) {
419
+ await this.localParticipant.setE2EEEnabled(enabled);
420
+
421
+ if (this.localParticipant.identity !== '') {
422
+ this.e2eeManager.setParticipantCryptorEnabled(enabled, this.localParticipant.identity);
423
+ }
424
+ }
425
+ } else {
426
+ throw Error('e2ee not configured, please set e2ee settings within the room options');
418
427
  }
419
- } else {
420
- throw Error('e2ee not configured, please set e2ee settings within the room options');
428
+ } finally {
429
+ unlock();
421
430
  }
422
431
  }
423
432
 
@@ -450,6 +459,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
450
459
  this.emit(RoomEvent.EncryptionError, error, participant);
451
460
  });
452
461
  this.e2eeManager?.setup(this);
462
+ this.e2eeManager?.setupEngine(this.engine);
453
463
  }
454
464
  }
455
465
 
@@ -895,7 +905,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
895
905
  roomOptions: InternalRoomOptions,
896
906
  abortController: AbortController,
897
907
  ): Promise<JoinResponse> => {
898
- const joinResponse = await engine.join(
908
+ const { joinResponse, serverInfo } = await engine.join(
899
909
  url,
900
910
  token,
901
911
  {
@@ -910,23 +920,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
910
920
  !roomOptions.singlePeerConnection,
911
921
  );
912
922
 
913
- let serverInfo: Partial<ServerInfo> | undefined = joinResponse.serverInfo;
914
- if (!serverInfo) {
915
- serverInfo = { version: joinResponse.serverVersion, region: joinResponse.serverRegion };
916
- }
917
923
  this.serverInfo = serverInfo;
918
924
 
919
- this.log.debug(
920
- `connected to Livekit Server ${Object.entries(serverInfo)
921
- .map(([key, value]) => `${key}: ${value}`)
922
- .join(', ')}`,
923
- {
924
- room: joinResponse.room?.name,
925
- roomSid: joinResponse.room?.sid,
926
- identity: joinResponse.participant?.identity,
927
- },
928
- );
929
-
930
925
  if (!serverInfo.version) {
931
926
  throw new UnsupportedServer('unknown server version');
932
927
  }
@@ -2475,6 +2470,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2475
2470
  return false;
2476
2471
  }
2477
2472
  this.state = state;
2473
+ this.incomingDataStreamManager.setConnected(state === ConnectionState.Connected);
2478
2474
  this.emit(RoomEvent.ConnectionStateChanged, this.state);
2479
2475
  return true;
2480
2476
  }
@@ -27,6 +27,25 @@ export default class IncomingDataStreamManager {
27
27
 
28
28
  private textStreamHandlers = new Map<string, TextStreamHandler>();
29
29
 
30
+ private isConnected = false;
31
+
32
+ private bufferedPackets: Array<{ packet: DataPacket; encryptionType: Encryption_Type }> = [];
33
+
34
+ setConnected(connected: boolean) {
35
+ this.isConnected = connected;
36
+ if (connected) {
37
+ this.flushBufferedPackets();
38
+ }
39
+ }
40
+
41
+ private flushBufferedPackets() {
42
+ const packets = this.bufferedPackets;
43
+ this.bufferedPackets = [];
44
+ for (const { packet, encryptionType } of packets) {
45
+ this.handleDataStreamPacket(packet, encryptionType);
46
+ }
47
+ }
48
+
30
49
  registerTextStreamHandler(topic: string, callback: TextStreamHandler) {
31
50
  if (this.textStreamHandlers.has(topic)) {
32
51
  throw new DataStreamError(
@@ -58,6 +77,7 @@ export default class IncomingDataStreamManager {
58
77
  clearControllers() {
59
78
  this.byteStreamControllers.clear();
60
79
  this.textStreamControllers.clear();
80
+ this.bufferedPackets = [];
61
81
  }
62
82
 
63
83
  validateParticipantHasNoActiveDataStreams(participantIdentity: string) {
@@ -88,7 +108,11 @@ export default class IncomingDataStreamManager {
88
108
  }
89
109
  }
90
110
 
91
- async handleDataStreamPacket(packet: DataPacket, encryptionType: Encryption_Type) {
111
+ handleDataStreamPacket(packet: DataPacket, encryptionType: Encryption_Type) {
112
+ if (!this.isConnected) {
113
+ this.bufferedPackets.push({ packet, encryptionType });
114
+ return;
115
+ }
92
116
  switch (packet.value.case) {
93
117
  case 'streamHeader':
94
118
  return this.handleStreamHeader(
@@ -105,7 +129,7 @@ export default class IncomingDataStreamManager {
105
129
  }
106
130
  }
107
131
 
108
- private async handleStreamHeader(
132
+ private handleStreamHeader(
109
133
  streamHeader: DataStream_Header,
110
134
  participantIdentity: string,
111
135
  encryptionType: Encryption_Type,
@@ -436,6 +436,9 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
436
436
  this.descriptors.delete(sid);
437
437
 
438
438
  if (descriptor.subscription.type === 'active') {
439
+ descriptor.subscription.streamControllers.forEach((controller) => {
440
+ controller.close();
441
+ });
439
442
  this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle);
440
443
  }
441
444
 
@@ -583,6 +586,10 @@ export default class IncomingDataTrackManager extends (EventEmitter as new () =>
583
586
  if (descriptor.subscription.type === 'pending') {
584
587
  descriptor.subscription.completionFuture.reject?.(DataTrackSubscribeError.disconnected());
585
588
  }
589
+
590
+ if (descriptor.subscription.type === 'active') {
591
+ descriptor.subscription.streamControllers.forEach((controller) => controller.close());
592
+ }
586
593
  }
587
594
  this.descriptors.clear();
588
595
  }
@@ -1,3 +1,4 @@
1
+ import { Mutex } from '@livekit/mutex';
1
2
  import {
2
3
  AddTrackRequest,
3
4
  AudioTrackFeature,
@@ -143,6 +144,8 @@ export default class LocalParticipant extends Participant {
143
144
 
144
145
  private encryptionType: Encryption_Type = Encryption_Type.NONE;
145
146
 
147
+ private e2eeStateMutex = new Mutex();
148
+
146
149
  private reconnectFuture?: Future<void, Error>;
147
150
 
148
151
  private signalConnectedFuture?: Future<void, Error>;
@@ -499,8 +502,20 @@ export default class LocalParticipant extends Participant {
499
502
 
500
503
  /** @internal */
501
504
  async setE2EEEnabled(enabled: boolean) {
502
- this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
503
- await this.republishAllTracks(undefined, false);
505
+ const unlock = await this.e2eeStateMutex.lock();
506
+ try {
507
+ this.encryptionType = enabled ? Encryption_Type.GCM : Encryption_Type.NONE;
508
+ await Promise.all(this.pendingPublishPromises.values());
509
+ if (
510
+ this.trackPublications.size === 0 ||
511
+ Array.from(this.trackPublications.values()).every((pub) => pub.isEncrypted === enabled)
512
+ ) {
513
+ return;
514
+ }
515
+ await this.republishAllTracks(undefined, false);
516
+ } finally {
517
+ unlock();
518
+ }
504
519
  }
505
520
 
506
521
  /**
@@ -0,0 +1,72 @@
1
+ const SerializerSymbol = Symbol.for('lk.serializer');
2
+
3
+ /**
4
+ * A bidirectional data format descriptor for message payloads.
5
+ *
6
+ * - `parse(raw)` decodes an incoming wire string into `Input` (used by handlers)
7
+ * - `serialize(val)` encodes an `Output` value to a wire string (used by handlers)
8
+ *
9
+ * For symmetric serializers (`serializers.raw`), `Input === Output === string`.
10
+ * For `serializers.json`, both default to `any` so each handler can annotate its own types.
11
+ * Use `serializers.custom` to supply your own `parse`/`serialize` pair.
12
+ *
13
+ * @beta
14
+ */
15
+ export type Serializer<Input, Output> = {
16
+ symbol: typeof SerializerSymbol;
17
+ parse: (raw: string) => Input;
18
+ serialize: (val: Output) => string;
19
+ };
20
+
21
+ export function isSerializer(v: unknown): v is Serializer<any, any> {
22
+ return typeof v === 'object' && v !== null && 'symbol' in v && v.symbol === SerializerSymbol;
23
+ }
24
+
25
+ export type SerializerInput<S> = S extends Serializer<infer Input, any> ? Input : any;
26
+ export type SerializerOutput<S> = S extends Serializer<any, infer Output> ? Output : any;
27
+
28
+ /** @internal */
29
+ function base<Input = any, Output = any>(
30
+ params: Omit<Serializer<Input, Output>, 'symbol'>,
31
+ ): Serializer<Input, Output> {
32
+ return { ...params, symbol: SerializerSymbol };
33
+ }
34
+
35
+ /**
36
+ * JSON serializer — `JSON.parse` on the way in, `JSON.stringify` on the way out.
37
+ * Defaults to `any` so individual handlers can annotate their own payload types.
38
+ */
39
+ function json<Input = any, Output = any>(): Serializer<Input, Output> {
40
+ return base({
41
+ parse: (rawString: string) => JSON.parse(rawString) as Input,
42
+ serialize: (val: unknown) => JSON.stringify(val),
43
+ });
44
+ }
45
+
46
+ /** Raw string serializer — passes payloads through as plain strings with no encoding. */
47
+ function raw() {
48
+ return base({
49
+ parse: (rawString: string) => rawString,
50
+ serialize: (val: string) => val,
51
+ });
52
+ }
53
+
54
+ /** Custom serializer - allows custom defined parse and serialize functions */
55
+ function custom<Input = any, Output = any>(
56
+ params: Omit<Serializer<Input, Output>, 'symbol'>,
57
+ ): Serializer<Input, Output> {
58
+ return base(params);
59
+ }
60
+
61
+ /**
62
+ * Serializer helpers for message payload encoding.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const a = serializers.raw(); // Serializer<string, string>
67
+ * const b = serializer.json<{ foo: string }, { bar: string }>(); // Serializer<{ foo: string }, { bar: string }>
68
+ * ```
69
+ *
70
+ * @beta
71
+ */
72
+ export const serializers = { json, raw, custom };