livekit-client 2.20.2 → 2.21.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 (105) 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 +4 -0
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +1993 -791
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.fm.worker.js +1 -1
  8. package/dist/livekit-client.fm.worker.js.map +1 -1
  9. package/dist/livekit-client.fm.worker.mjs +4 -0
  10. package/dist/livekit-client.fm.worker.mjs.map +1 -1
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/index.d.ts +1 -1
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/room/RTCEngine.d.ts +28 -64
  16. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  17. package/dist/src/room/Room.d.ts +4 -0
  18. package/dist/src/room/Room.d.ts.map +1 -1
  19. package/dist/src/room/data-channel/DataChannelManager.d.ts +62 -0
  20. package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -0
  21. package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
  22. package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -0
  23. package/dist/src/room/data-channel/LossyDataChannel.d.ts +40 -0
  24. package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -0
  25. package/dist/src/room/data-channel/ReliableDataChannel.d.ts +56 -0
  26. package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -0
  27. package/dist/src/room/data-channel/types.d.ts +12 -0
  28. package/dist/src/room/data-channel/types.d.ts.map +1 -0
  29. package/dist/src/room/data-stream/compression.d.ts +49 -0
  30. package/dist/src/room/data-stream/compression.d.ts.map +1 -0
  31. package/dist/src/room/data-stream/constants.d.ts +18 -0
  32. package/dist/src/room/data-stream/constants.d.ts.map +1 -0
  33. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
  34. package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
  35. package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
  36. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
  37. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  38. package/dist/src/room/data-stream/outgoing/header-utils.d.ts +18 -0
  39. package/dist/src/room/data-stream/outgoing/header-utils.d.ts.map +1 -0
  40. package/dist/src/room/errors.d.ts +3 -1
  41. package/dist/src/room/errors.d.ts.map +1 -1
  42. package/dist/src/room/participant/LocalParticipant.d.ts +9 -1
  43. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  44. package/dist/src/room/participant/RemoteParticipant.d.ts +11 -2
  45. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  46. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
  47. package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -1
  48. package/dist/src/room/types.d.ts +17 -0
  49. package/dist/src/room/types.d.ts.map +1 -1
  50. package/dist/src/room/utils.d.ts +13 -0
  51. package/dist/src/room/utils.d.ts.map +1 -1
  52. package/dist/src/version.d.ts +5 -1
  53. package/dist/src/version.d.ts.map +1 -1
  54. package/dist/ts4.2/index.d.ts +1 -1
  55. package/dist/ts4.2/room/RTCEngine.d.ts +28 -64
  56. package/dist/ts4.2/room/Room.d.ts +4 -0
  57. package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -0
  58. package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
  59. package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -0
  60. package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -0
  61. package/dist/ts4.2/room/data-channel/types.d.ts +12 -0
  62. package/dist/ts4.2/room/data-stream/compression.d.ts +49 -0
  63. package/dist/ts4.2/room/data-stream/constants.d.ts +18 -0
  64. package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
  65. package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
  66. package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -0
  67. package/dist/ts4.2/room/errors.d.ts +3 -1
  68. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +9 -1
  69. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +11 -1
  70. package/dist/ts4.2/room/types.d.ts +17 -0
  71. package/dist/ts4.2/room/utils.d.ts +13 -0
  72. package/dist/ts4.2/version.d.ts +5 -1
  73. package/package.json +2 -2
  74. package/src/index.ts +2 -0
  75. package/src/room/RTCEngine.test.ts +58 -99
  76. package/src/room/RTCEngine.ts +92 -534
  77. package/src/room/Room.test.ts +4 -1
  78. package/src/room/Room.ts +40 -8
  79. package/src/room/data-channel/DataChannelManager.test.ts +152 -0
  80. package/src/room/data-channel/DataChannelManager.ts +237 -0
  81. package/src/room/data-channel/FlowControlledDataChannel.test.ts +154 -0
  82. package/src/room/data-channel/FlowControlledDataChannel.ts +216 -0
  83. package/src/room/data-channel/LossyDataChannel.test.ts +118 -0
  84. package/src/room/data-channel/LossyDataChannel.ts +125 -0
  85. package/src/room/data-channel/ReliableDataChannel.test.ts +158 -0
  86. package/src/room/data-channel/ReliableDataChannel.ts +153 -0
  87. package/src/room/data-channel/types.ts +30 -0
  88. package/src/room/data-stream/compression.ts +117 -0
  89. package/src/room/data-stream/constants.ts +18 -0
  90. package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +1601 -0
  91. package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +438 -86
  92. package/src/room/data-stream/incoming/StreamReader.ts +8 -2
  93. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +1292 -0
  94. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +489 -116
  95. package/src/room/data-stream/outgoing/header-utils.ts +87 -0
  96. package/src/room/errors.ts +6 -0
  97. package/src/room/participant/LocalParticipant.ts +13 -0
  98. package/src/room/participant/RemoteParticipant.ts +19 -5
  99. package/src/room/rpc/client/RpcClientManager.test.ts +20 -27
  100. package/src/room/rpc/client/RpcClientManager.ts +1 -4
  101. package/src/room/rpc/server/RpcServerManager.test.ts +24 -23
  102. package/src/room/rpc/server/RpcServerManager.ts +1 -3
  103. package/src/room/types.ts +18 -0
  104. package/src/room/utils.ts +67 -0
  105. package/src/version.ts +5 -1
@@ -1,6 +1,6 @@
1
1
  import { DataPacket, DataPacket_Kind, UserPacket } from '@livekit/protocol';
2
2
  import { afterEach, describe, expect, it, vi } from 'vitest';
3
- import type { DataPacketBuffer, DataPacketItem } from '../utils/dataPacketBuffer';
3
+ import type { DataPacketBuffer } from '../utils/dataPacketBuffer';
4
4
  import RTCEngine, { DataChannelKind } from './RTCEngine';
5
5
  import { roomOptionDefaults } from './defaults';
6
6
  import { PublishDataError, UnexpectedConnectionState } from './errors';
@@ -228,17 +228,16 @@ describe('RTCEngine', () => {
228
228
  engine: RTCEngine,
229
229
  maxDataPacketSize: number = MAX_DATA_PACKET_SIZE,
230
230
  ) {
231
- const send = vi.fn();
231
+ const dc = new FakeDataChannel();
232
232
  Object.assign(engine as unknown as Record<string, unknown>, {
233
+ _isClosed: false,
233
234
  ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
234
- waitForBufferHeadroomWithLock: vi.fn().mockResolvedValue(undefined),
235
- updateAndEmitDCBufferStatus: vi.fn(),
236
- dataChannelForKind: vi.fn(() => ({ send })),
237
235
  pcManager: {
238
236
  getMaxPublisherMessageSize: vi.fn(() => maxDataPacketSize),
239
237
  },
240
238
  });
241
- return send;
239
+ attachFakeChannel(engine, 'reliableChannel', dc);
240
+ return dc.send;
242
241
  }
243
242
 
244
243
  it('rejects packets larger than the max data packet size', async () => {
@@ -309,6 +308,23 @@ describe('RTCEngine', () => {
309
308
 
310
309
  const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
311
310
 
311
+ /** The reliable channel's private replay buffer — reached through casts, as tests do for engine privates. */
312
+ const reliableBuffer = (engine: RTCEngine) =>
313
+ (engine as unknown as { reliableChannel: { messageBuffer: DataPacketBuffer } }).reliableChannel
314
+ .messageBuffer;
315
+
316
+ type ChannelField = 'reliableChannel' | 'lossyChannel' | 'dataTrackChannel';
317
+ const engineChannel = (engine: RTCEngine, field: ChannelField) =>
318
+ (
319
+ engine as unknown as Record<
320
+ ChannelField,
321
+ { attach(dc: RTCDataChannel): void; invalidateWaiters(reason: string): void }
322
+ >
323
+ )[field];
324
+ /** Attach a fake handle to one of the engine's flow-control wrappers. */
325
+ const attachFakeChannel = (engine: RTCEngine, field: ChannelField, dc: FakeDataChannel) =>
326
+ engineChannel(engine, field).attach(dc as unknown as RTCDataChannel);
327
+
312
328
  describe('resendReliableMessagesForResume', () => {
313
329
  it('does not let a concurrent reliable send interleave into the resume replay', async () => {
314
330
  const engine = new RTCEngine(roomOptionDefaults);
@@ -316,18 +332,17 @@ describe('RTCEngine', () => {
316
332
  Object.assign(engine as unknown as Record<string, unknown>, {
317
333
  _isClosed: false,
318
334
  ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
319
- dataChannelForKind: vi.fn(() => dc),
320
335
  pcManager: {
321
336
  getMaxPublisherMessageSize: vi.fn(() => 64 * 1024 - 1),
322
337
  },
323
338
  });
339
+ attachFakeChannel(engine, 'reliableChannel', dc);
324
340
 
325
- // Two messages queued for replay, and a full buffer so the XX its first send.
341
+ // Two messages queued for replay, and a full buffer so the replay parks on
342
+ // waitForBufferHeadroom before its first send.
326
343
  const replayed1 = new Uint8Array([1]);
327
344
  const replayed2 = new Uint8Array([2]);
328
- const buffer = (
329
- engine as unknown as { reliableMessageBuffer: { push: (item: DataPacketItem) => void } }
330
- ).reliableMessageBuffer;
345
+ const buffer = reliableBuffer(engine);
331
346
  buffer.push({ data: replayed1, sequence: 1, sent: true });
332
347
  buffer.push({ data: replayed2, sequence: 2, sent: true });
333
348
  dc.bufferedAmount = 2 * 1024 * 1024; // above the reliable high-water mark
@@ -359,59 +374,6 @@ describe('RTCEngine', () => {
359
374
  expect(dc.send.mock.calls[2][0]).not.toBe(replayed1);
360
375
  expect(dc.send.mock.calls[2][0]).not.toBe(replayed2);
361
376
  });
362
-
363
- it('transmits a packet deferred mid-replay instead of marking it sent without sending', async () => {
364
- const engine = new RTCEngine(roomOptionDefaults);
365
- const dc = new FakeDataChannel();
366
- Object.assign(engine as unknown as Record<string, unknown>, {
367
- _isClosed: false,
368
- // Reconnect still active: a send arriving during replay takes the deferral path.
369
- attemptingReconnect: true,
370
- ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
371
- dataChannelForKind: vi.fn(() => dc),
372
- pcManager: {
373
- getMaxPublisherMessageSize: vi.fn(() => 64 * 1024 - 1),
374
- },
375
- });
376
-
377
- const buffer = (engine as unknown as { reliableMessageBuffer: DataPacketBuffer })
378
- .reliableMessageBuffer;
379
- const replayed = new Uint8Array([1]);
380
- buffer.push({ data: replayed, sequence: 1, sent: true });
381
- // Full buffer so replay parks on waitForBufferHeadroomWithLock before its first send.
382
- dc.bufferedAmount = 2 * 1024 * 1024;
383
-
384
- const replay = (
385
- engine as unknown as { resendReliableMessagesForResume: (seq: number) => Promise<void> }
386
- ).resendReliableMessagesForResume(0);
387
- await tick();
388
-
389
- // A reliable send arrives while replay is parked; attemptingReconnect defers it into the
390
- // buffer as sent:false, after the replay's first drain pass already started.
391
- const deferred = new Uint8Array([2]);
392
- const deferredSend = engine.sendDataPacket(
393
- new DataPacket({
394
- kind: DataPacket_Kind.RELIABLE,
395
- value: { case: 'user', value: new UserPacket({ payload: deferred }) },
396
- }),
397
- DataChannelKind.RELIABLE,
398
- );
399
- await tick();
400
-
401
- dc.bufferedAmount = 0;
402
- dc.dispatchEvent(new Event('bufferedamountlow'));
403
- await Promise.all([replay, deferredSend]);
404
-
405
- // Pre-fix, replay sends only its snapshot ([replayed]) and blanket-marks the deferred packet
406
- // sent without ever transmitting it — one send. The drain loop must transmit both (the
407
- // deferred packet is serialized through sendDataPacket, so it's distinct from `replayed`),
408
- // leaving nothing unsent for a later align to strand.
409
- const sent = dc.send.mock.calls.map(([d]) => d);
410
- expect(sent).toHaveLength(2);
411
- expect(sent[0]).toBe(replayed);
412
- expect(sent[1]).not.toBe(replayed);
413
- expect(buffer.getUnsent()).toHaveLength(0);
414
- });
415
377
  });
416
378
 
417
379
  describe('reliable sends during teardown windows', () => {
@@ -425,13 +387,12 @@ describe('RTCEngine', () => {
425
387
  Object.assign(engine as unknown as Record<string, unknown>, {
426
388
  _isClosed: false,
427
389
  ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
428
- dataChannelForKind: vi.fn(() => dc),
429
390
  pcManager: {
430
391
  getMaxPublisherMessageSize: vi.fn(() => 64 * 1024 - 1),
431
392
  },
432
393
  });
433
- return (engine as unknown as { reliableMessageBuffer: DataPacketBuffer })
434
- .reliableMessageBuffer;
394
+ attachFakeChannel(engine, 'reliableChannel', dc);
395
+ return reliableBuffer(engine);
435
396
  }
436
397
 
437
398
  it('resolves and queues the packet for replay when the wait is torn down transiently', async () => {
@@ -443,9 +404,7 @@ describe('RTCEngine', () => {
443
404
  dc.bufferedAmount = 2 * 1024 * 1024;
444
405
  const send = engine.sendDataPacket(makePacket(1), DataChannelKind.RELIABLE);
445
406
  await tick();
446
- (
447
- engine as unknown as { invalidateDataChannelWaiters: (reason: string) => void }
448
- ).invalidateDataChannelWaiters('data channels recreated');
407
+ engineChannel(engine, 'reliableChannel').invalidateWaiters('data channels recreated');
449
408
 
450
409
  // The send must not surface the teardown — the packet is queued for the resume replay.
451
410
  await expect(send).resolves.toBeUndefined();
@@ -471,9 +430,7 @@ describe('RTCEngine', () => {
471
430
  const send = engine.sendDataPacket(makePacket(1), DataChannelKind.RELIABLE);
472
431
  await tick();
473
432
  Object.assign(engine as unknown as Record<string, unknown>, { _isClosed: true });
474
- (
475
- engine as unknown as { invalidateDataChannelWaiters: (reason: string) => void }
476
- ).invalidateDataChannelWaiters('engine closed');
433
+ engineChannel(engine, 'reliableChannel').invalidateWaiters('engine closed');
477
434
 
478
435
  await expect(send).rejects.toBeInstanceOf(UnexpectedConnectionState);
479
436
  expect(dc.send).not.toHaveBeenCalled();
@@ -496,78 +453,80 @@ describe('RTCEngine', () => {
496
453
  });
497
454
  });
498
455
 
499
- describe('sendLossyBytes', () => {
456
+ describe('sendDataTrackFrame', () => {
500
457
  it('ensures the publisher is connected before sending (direct data-track path)', async () => {
501
458
  const engine = new RTCEngine(roomOptionDefaults);
502
459
  const dc = new FakeDataChannel();
503
460
  // The channel only becomes available once the publisher connection has been established —
504
461
  // mirroring the lazily negotiated publisher case that Room's packetAvailable path hits.
505
- let connected = false;
506
462
  const ensurePublisherConnected = vi.fn(async () => {
507
- connected = true;
463
+ attachFakeChannel(engine, 'dataTrackChannel', dc);
508
464
  });
509
465
  Object.assign(engine as unknown as Record<string, unknown>, {
510
466
  _isClosed: false,
511
467
  ensurePublisherConnected,
512
- dataChannelForKind: vi.fn(() => (connected ? dc : undefined)),
513
468
  });
514
469
 
515
- await engine.sendLossyBytes(new Uint8Array([1]), DataChannelKind.DATA_TRACK_LOSSY, 'wait');
470
+ await engine.sendDataTrackFrame(new Uint8Array([1]));
516
471
 
517
472
  expect(ensurePublisherConnected).toHaveBeenCalledWith(DataChannelKind.DATA_TRACK_LOSSY);
518
473
  expect(dc.send).toHaveBeenCalledTimes(1);
519
474
  });
520
475
 
521
- it('only counts LOSSY bytes into the byterate stat that tunes the lossy drop threshold', async () => {
476
+ it('keeps each channel’s byterate stat isolated from the other’s traffic', async () => {
522
477
  const engine = new RTCEngine(roomOptionDefaults);
523
478
  const dc = new FakeDataChannel();
524
479
  Object.assign(engine as unknown as Record<string, unknown>, {
525
480
  _isClosed: false,
526
481
  ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
527
- dataChannelForKind: vi.fn(() => dc),
528
482
  });
529
- const stat = () =>
530
- (engine as unknown as { lossyDataStatCurrentBytes: number }).lossyDataStatCurrentBytes;
531
-
532
- // Data-track traffic must not move the stat it would inflate the LOSSY channel's
533
- // dynamically tuned drop threshold with traffic that channel never carries.
534
- await engine.sendLossyBytes(new Uint8Array(1000), DataChannelKind.DATA_TRACK_LOSSY, 'wait');
535
- expect(stat()).toBe(0);
536
-
537
- await engine.sendLossyBytes(new Uint8Array(100), DataChannelKind.LOSSY, 'drop');
538
- expect(stat()).toBe(100);
483
+ attachFakeChannel(engine, 'lossyChannel', dc);
484
+ attachFakeChannel(engine, 'dataTrackChannel', dc);
485
+ const lossyStat = () =>
486
+ (engine as unknown as { lossyChannel: { statCurrentBytes: number } }).lossyChannel
487
+ .statCurrentBytes;
488
+
489
+ // Data-track traffic (sendDataTrackFrame → data-track channel) must not move the LOSSY channel's
490
+ // stat — it would inflate the lossy channel's dynamically tuned drop threshold with traffic
491
+ // that channel never carries.
492
+ await engine.sendDataTrackFrame(new Uint8Array(1000));
493
+ expect(lossyStat()).toBe(0);
494
+
495
+ // A plain lossy publishData packet goes through sendDataPacket → lossy channel.
496
+ const lossyPacket = new DataPacket({
497
+ kind: DataPacket_Kind.LOSSY,
498
+ value: { case: 'user', value: new UserPacket({ payload: new Uint8Array(100) }) },
499
+ });
500
+ await engine.sendDataPacket(lossyPacket, DataChannelKind.LOSSY);
501
+ expect(lossyStat()).toBeGreaterThan(0);
539
502
  });
540
503
  });
541
504
 
542
- describe('waitForBufferHeadroomWithLock', () => {
505
+ describe('waitForBufferHeadroom', () => {
543
506
  it('rejects parked waiters and releases the lock when the data channels are invalidated', async () => {
544
507
  const engine = new RTCEngine(roomOptionDefaults);
545
508
  const dc = new FakeDataChannel();
546
509
  Object.assign(engine as unknown as Record<string, unknown>, {
547
510
  _isClosed: false,
548
- dataChannelForKind: vi.fn(() => dc),
549
511
  });
512
+ attachFakeChannel(engine, 'reliableChannel', dc);
550
513
 
551
514
  // Park a waiter: buffer above the reliable high-water mark, holding the headroom lock.
552
515
  dc.bufferedAmount = 2 * 1024 * 1024;
553
- const parked = engine.waitForBufferHeadroomWithLock(DataChannelKind.RELIABLE);
516
+ const parked = engine.waitForBufferHeadroom(DataChannelKind.RELIABLE);
554
517
  // Swallow the expected rejection so it can't surface as unhandled before we assert on it.
555
518
  parked.catch(() => {});
556
519
  await tick();
557
520
 
558
521
  // The channel object gets abandoned (e.g. createDataChannels on the Safari resume path).
559
- (
560
- engine as unknown as { invalidateDataChannelWaiters: (reason: string) => void }
561
- ).invalidateDataChannelWaiters('data channels recreated');
522
+ engineChannel(engine, 'reliableChannel').invalidateWaiters('data channels recreated');
562
523
 
563
524
  await expect(parked).rejects.toBeInstanceOf(UnexpectedConnectionState);
564
525
 
565
526
  // The lock must be free again: a wait against the fresh, drained channel resolves instead
566
527
  // of queueing forever behind the stranded waiter.
567
528
  dc.bufferedAmount = 0;
568
- await expect(
569
- engine.waitForBufferHeadroomWithLock(DataChannelKind.RELIABLE),
570
- ).resolves.toBeUndefined();
529
+ await expect(engine.waitForBufferHeadroom(DataChannelKind.RELIABLE)).resolves.toBeUndefined();
571
530
  });
572
531
  });
573
532