livekit-client 2.20.1 → 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.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +37 -7
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2279 -651
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +37 -7
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +3 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +2 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +31 -26
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +4 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts +40 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts +56 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/types.d.ts +12 -0
- package/dist/src/room/data-channel/types.d.ts.map +1 -0
- package/dist/src/room/data-stream/compression.d.ts +49 -0
- package/dist/src/room/data-stream/compression.d.ts.map +1 -0
- package/dist/src/room/data-stream/constants.d.ts +18 -0
- package/dist/src/room/data-stream/constants.d.ts.map +1 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts.map +1 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +11 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +7 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +11 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +13 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/signalToken.d.ts +26 -0
- package/dist/src/test/signalToken.d.ts.map +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts +18 -0
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +5 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/ts4.2/index.d.ts +1 -1
- package/dist/ts4.2/logger.d.ts +3 -1
- package/dist/ts4.2/room/PCTransport.d.ts +2 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +31 -26
- package/dist/ts4.2/room/Room.d.ts +4 -0
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -0
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -0
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -0
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -0
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -0
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/ts4.2/room/errors.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +11 -1
- package/dist/ts4.2/room/participant/publishUtils.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/ts4.2/room/track/options.d.ts +11 -1
- package/dist/ts4.2/room/types.d.ts +17 -0
- package/dist/ts4.2/room/utils.d.ts +13 -0
- package/dist/ts4.2/test/signalToken.d.ts +26 -0
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +18 -0
- package/dist/ts4.2/version.d.ts +5 -1
- package/package.json +8 -4
- package/src/api/SignalClient.e2e.test.ts +264 -0
- package/src/api/SignalClient.ts +34 -2
- package/src/index.ts +2 -0
- package/src/logger.ts +2 -0
- package/src/room/PCTransport.ts +66 -31
- package/src/room/PCTransportManager.ts +10 -6
- package/src/room/RTCEngine.test.ts +238 -6
- package/src/room/RTCEngine.ts +101 -338
- package/src/room/RegionUrlProvider.ts +4 -2
- package/src/room/Room.test.ts +4 -1
- package/src/room/Room.ts +47 -9
- package/src/room/data-channel/DataChannelManager.test.ts +152 -0
- package/src/room/data-channel/DataChannelManager.ts +237 -0
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +154 -0
- package/src/room/data-channel/FlowControlledDataChannel.ts +216 -0
- package/src/room/data-channel/LossyDataChannel.test.ts +118 -0
- package/src/room/data-channel/LossyDataChannel.ts +125 -0
- package/src/room/data-channel/ReliableDataChannel.test.ts +158 -0
- package/src/room/data-channel/ReliableDataChannel.ts +153 -0
- package/src/room/data-channel/types.ts +30 -0
- package/src/room/data-stream/compression.ts +117 -0
- package/src/room/data-stream/constants.ts +18 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +1601 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +438 -86
- package/src/room/data-stream/incoming/StreamReader.ts +8 -2
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +1292 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +489 -116
- package/src/room/data-stream/outgoing/header-utils.ts +87 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +303 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +70 -8
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +2 -0
- package/src/room/errors.ts +10 -4
- package/src/room/participant/LocalParticipant.ts +29 -6
- package/src/room/participant/Participant.ts +5 -0
- package/src/room/participant/RemoteParticipant.ts +19 -5
- package/src/room/participant/publishUtils.ts +14 -11
- package/src/room/rpc/client/RpcClientManager.test.ts +20 -27
- package/src/room/rpc/client/RpcClientManager.ts +1 -4
- package/src/room/rpc/server/RpcServerManager.test.ts +24 -23
- package/src/room/rpc/server/RpcServerManager.ts +1 -3
- package/src/room/track/LocalAudioTrack.ts +16 -0
- package/src/room/track/Track.ts +10 -6
- package/src/room/track/options.ts +11 -1
- package/src/room/types.ts +18 -0
- package/src/room/utils.ts +67 -0
- package/src/test/signalServerSetup.ts +151 -0
- package/src/test/signalToken.ts +59 -0
- package/src/test/vitest-context.d.ts +10 -0
- package/src/utils/dataPacketBuffer.test.ts +100 -0
- package/src/utils/dataPacketBuffer.ts +46 -1
- package/src/version.ts +5 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DataPacket, DataPacket_Kind, UserPacket } from '@livekit/protocol';
|
|
2
2
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import type { DataPacketBuffer } from '../utils/dataPacketBuffer';
|
|
3
4
|
import RTCEngine, { DataChannelKind } from './RTCEngine';
|
|
4
5
|
import { roomOptionDefaults } from './defaults';
|
|
5
|
-
import { PublishDataError } from './errors';
|
|
6
|
+
import { PublishDataError, UnexpectedConnectionState } from './errors';
|
|
6
7
|
|
|
7
8
|
describe('RTCEngine', () => {
|
|
8
9
|
const originalRTCRtpSender = window.RTCRtpSender;
|
|
@@ -227,17 +228,16 @@ describe('RTCEngine', () => {
|
|
|
227
228
|
engine: RTCEngine,
|
|
228
229
|
maxDataPacketSize: number = MAX_DATA_PACKET_SIZE,
|
|
229
230
|
) {
|
|
230
|
-
const
|
|
231
|
+
const dc = new FakeDataChannel();
|
|
231
232
|
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
233
|
+
_isClosed: false,
|
|
232
234
|
ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
|
|
233
|
-
waitForBufferStatusLow: vi.fn().mockResolvedValue(undefined),
|
|
234
|
-
updateAndEmitDCBufferStatus: vi.fn(),
|
|
235
|
-
dataChannelForKind: vi.fn(() => ({ send })),
|
|
236
235
|
pcManager: {
|
|
237
236
|
getMaxPublisherMessageSize: vi.fn(() => maxDataPacketSize),
|
|
238
237
|
},
|
|
239
238
|
});
|
|
240
|
-
|
|
239
|
+
attachFakeChannel(engine, 'reliableChannel', dc);
|
|
240
|
+
return dc.send;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
it('rejects packets larger than the max data packet size', async () => {
|
|
@@ -298,6 +298,238 @@ describe('RTCEngine', () => {
|
|
|
298
298
|
});
|
|
299
299
|
});
|
|
300
300
|
|
|
301
|
+
class FakeDataChannel extends EventTarget {
|
|
302
|
+
bufferedAmount = 0;
|
|
303
|
+
|
|
304
|
+
bufferedAmountLowThreshold = 64 * 1024;
|
|
305
|
+
|
|
306
|
+
send = vi.fn();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const tick = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
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
|
+
|
|
328
|
+
describe('resendReliableMessagesForResume', () => {
|
|
329
|
+
it('does not let a concurrent reliable send interleave into the resume replay', async () => {
|
|
330
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
331
|
+
const dc = new FakeDataChannel();
|
|
332
|
+
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
333
|
+
_isClosed: false,
|
|
334
|
+
ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
|
|
335
|
+
pcManager: {
|
|
336
|
+
getMaxPublisherMessageSize: vi.fn(() => 64 * 1024 - 1),
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
attachFakeChannel(engine, 'reliableChannel', dc);
|
|
340
|
+
|
|
341
|
+
// Two messages queued for replay, and a full buffer so the replay parks on
|
|
342
|
+
// waitForBufferHeadroom before its first send.
|
|
343
|
+
const replayed1 = new Uint8Array([1]);
|
|
344
|
+
const replayed2 = new Uint8Array([2]);
|
|
345
|
+
const buffer = reliableBuffer(engine);
|
|
346
|
+
buffer.push({ data: replayed1, sequence: 1, sent: true });
|
|
347
|
+
buffer.push({ data: replayed2, sequence: 2, sent: true });
|
|
348
|
+
dc.bufferedAmount = 2 * 1024 * 1024; // above the reliable high-water mark
|
|
349
|
+
|
|
350
|
+
const replay = (
|
|
351
|
+
engine as unknown as { resendReliableMessagesForResume: (seq: number) => Promise<void> }
|
|
352
|
+
).resendReliableMessagesForResume(0);
|
|
353
|
+
await tick();
|
|
354
|
+
|
|
355
|
+
// A send racing the replay: its sequence is assigned immediately, but it must not hit the
|
|
356
|
+
// wire before the replayed (lower-sequence) messages, or receivers discard those as dupes.
|
|
357
|
+
const concurrentSend = engine.sendDataPacket(
|
|
358
|
+
new DataPacket({
|
|
359
|
+
kind: DataPacket_Kind.RELIABLE,
|
|
360
|
+
value: { case: 'user', value: new UserPacket({ payload: new Uint8Array([3]) }) },
|
|
361
|
+
}),
|
|
362
|
+
DataChannelKind.RELIABLE,
|
|
363
|
+
);
|
|
364
|
+
await tick();
|
|
365
|
+
|
|
366
|
+
// Buffer drains: the replay must finish its whole batch before the concurrent send.
|
|
367
|
+
dc.bufferedAmount = 0;
|
|
368
|
+
dc.dispatchEvent(new Event('bufferedamountlow'));
|
|
369
|
+
await Promise.all([replay, concurrentSend]);
|
|
370
|
+
|
|
371
|
+
expect(dc.send).toHaveBeenCalledTimes(3);
|
|
372
|
+
expect(dc.send.mock.calls[0][0]).toBe(replayed1);
|
|
373
|
+
expect(dc.send.mock.calls[1][0]).toBe(replayed2);
|
|
374
|
+
expect(dc.send.mock.calls[2][0]).not.toBe(replayed1);
|
|
375
|
+
expect(dc.send.mock.calls[2][0]).not.toBe(replayed2);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('reliable sends during teardown windows', () => {
|
|
380
|
+
const makePacket = (byte: number) =>
|
|
381
|
+
new DataPacket({
|
|
382
|
+
kind: DataPacket_Kind.RELIABLE,
|
|
383
|
+
value: { case: 'user', value: new UserPacket({ payload: new Uint8Array([byte]) }) },
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
function stubEngine(engine: RTCEngine, dc: FakeDataChannel) {
|
|
387
|
+
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
388
|
+
_isClosed: false,
|
|
389
|
+
ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
|
|
390
|
+
pcManager: {
|
|
391
|
+
getMaxPublisherMessageSize: vi.fn(() => 64 * 1024 - 1),
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
attachFakeChannel(engine, 'reliableChannel', dc);
|
|
395
|
+
return reliableBuffer(engine);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
it('resolves and queues the packet for replay when the wait is torn down transiently', async () => {
|
|
399
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
400
|
+
const dc = new FakeDataChannel();
|
|
401
|
+
const buffer = stubEngine(engine, dc);
|
|
402
|
+
|
|
403
|
+
// Park the send on a full buffer, then invalidate the channel (reconnect/replacement).
|
|
404
|
+
dc.bufferedAmount = 2 * 1024 * 1024;
|
|
405
|
+
const send = engine.sendDataPacket(makePacket(1), DataChannelKind.RELIABLE);
|
|
406
|
+
await tick();
|
|
407
|
+
engineChannel(engine, 'reliableChannel').invalidateWaiters('data channels recreated');
|
|
408
|
+
|
|
409
|
+
// The send must not surface the teardown — the packet is queued for the resume replay.
|
|
410
|
+
await expect(send).resolves.toBeUndefined();
|
|
411
|
+
expect(dc.send).not.toHaveBeenCalled();
|
|
412
|
+
expect(buffer.length).toBe(1);
|
|
413
|
+
expect(buffer.getAll()[0].sent).toBe(false);
|
|
414
|
+
|
|
415
|
+
// The replay then delivers it.
|
|
416
|
+
dc.bufferedAmount = 0;
|
|
417
|
+
await (
|
|
418
|
+
engine as unknown as { resendReliableMessagesForResume: (seq: number) => Promise<void> }
|
|
419
|
+
).resendReliableMessagesForResume(0);
|
|
420
|
+
expect(dc.send).toHaveBeenCalledTimes(1);
|
|
421
|
+
expect(buffer.getAll()[0].sent).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('still rejects when the engine is closed while waiting', async () => {
|
|
425
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
426
|
+
const dc = new FakeDataChannel();
|
|
427
|
+
stubEngine(engine, dc);
|
|
428
|
+
|
|
429
|
+
dc.bufferedAmount = 2 * 1024 * 1024;
|
|
430
|
+
const send = engine.sendDataPacket(makePacket(1), DataChannelKind.RELIABLE);
|
|
431
|
+
await tick();
|
|
432
|
+
Object.assign(engine as unknown as Record<string, unknown>, { _isClosed: true });
|
|
433
|
+
engineChannel(engine, 'reliableChannel').invalidateWaiters('engine closed');
|
|
434
|
+
|
|
435
|
+
await expect(send).rejects.toBeInstanceOf(UnexpectedConnectionState);
|
|
436
|
+
expect(dc.send).not.toHaveBeenCalled();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('queues without waiting while a reconnect attempt is in progress', async () => {
|
|
440
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
441
|
+
const dc = new FakeDataChannel();
|
|
442
|
+
const buffer = stubEngine(engine, dc);
|
|
443
|
+
Object.assign(engine as unknown as Record<string, unknown>, { attemptingReconnect: true });
|
|
444
|
+
|
|
445
|
+
// Even with a full buffer, the send resolves immediately instead of parking.
|
|
446
|
+
dc.bufferedAmount = 2 * 1024 * 1024;
|
|
447
|
+
await expect(
|
|
448
|
+
engine.sendDataPacket(makePacket(1), DataChannelKind.RELIABLE),
|
|
449
|
+
).resolves.toBeUndefined();
|
|
450
|
+
expect(dc.send).not.toHaveBeenCalled();
|
|
451
|
+
expect(buffer.length).toBe(1);
|
|
452
|
+
expect(buffer.getAll()[0].sent).toBe(false);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe('sendDataTrackFrame', () => {
|
|
457
|
+
it('ensures the publisher is connected before sending (direct data-track path)', async () => {
|
|
458
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
459
|
+
const dc = new FakeDataChannel();
|
|
460
|
+
// The channel only becomes available once the publisher connection has been established —
|
|
461
|
+
// mirroring the lazily negotiated publisher case that Room's packetAvailable path hits.
|
|
462
|
+
const ensurePublisherConnected = vi.fn(async () => {
|
|
463
|
+
attachFakeChannel(engine, 'dataTrackChannel', dc);
|
|
464
|
+
});
|
|
465
|
+
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
466
|
+
_isClosed: false,
|
|
467
|
+
ensurePublisherConnected,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await engine.sendDataTrackFrame(new Uint8Array([1]));
|
|
471
|
+
|
|
472
|
+
expect(ensurePublisherConnected).toHaveBeenCalledWith(DataChannelKind.DATA_TRACK_LOSSY);
|
|
473
|
+
expect(dc.send).toHaveBeenCalledTimes(1);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('keeps each channel’s byterate stat isolated from the other’s traffic', async () => {
|
|
477
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
478
|
+
const dc = new FakeDataChannel();
|
|
479
|
+
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
480
|
+
_isClosed: false,
|
|
481
|
+
ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
|
|
482
|
+
});
|
|
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);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe('waitForBufferHeadroom', () => {
|
|
506
|
+
it('rejects parked waiters and releases the lock when the data channels are invalidated', async () => {
|
|
507
|
+
const engine = new RTCEngine(roomOptionDefaults);
|
|
508
|
+
const dc = new FakeDataChannel();
|
|
509
|
+
Object.assign(engine as unknown as Record<string, unknown>, {
|
|
510
|
+
_isClosed: false,
|
|
511
|
+
});
|
|
512
|
+
attachFakeChannel(engine, 'reliableChannel', dc);
|
|
513
|
+
|
|
514
|
+
// Park a waiter: buffer above the reliable high-water mark, holding the headroom lock.
|
|
515
|
+
dc.bufferedAmount = 2 * 1024 * 1024;
|
|
516
|
+
const parked = engine.waitForBufferHeadroom(DataChannelKind.RELIABLE);
|
|
517
|
+
// Swallow the expected rejection so it can't surface as unhandled before we assert on it.
|
|
518
|
+
parked.catch(() => {});
|
|
519
|
+
await tick();
|
|
520
|
+
|
|
521
|
+
// The channel object gets abandoned (e.g. createDataChannels on the Safari resume path).
|
|
522
|
+
engineChannel(engine, 'reliableChannel').invalidateWaiters('data channels recreated');
|
|
523
|
+
|
|
524
|
+
await expect(parked).rejects.toBeInstanceOf(UnexpectedConnectionState);
|
|
525
|
+
|
|
526
|
+
// The lock must be free again: a wait against the fresh, drained channel resolves instead
|
|
527
|
+
// of queueing forever behind the stranded waiter.
|
|
528
|
+
dc.bufferedAmount = 0;
|
|
529
|
+
await expect(engine.waitForBufferHeadroom(DataChannelKind.RELIABLE)).resolves.toBeUndefined();
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
301
533
|
describe('handleDataChannelClose', () => {
|
|
302
534
|
function stubCloseEnv(
|
|
303
535
|
engine: RTCEngine,
|