livekit-client 2.19.1 → 2.20.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 (124) 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 +204 -145
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +664 -526
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.fm.worker.js +2 -0
  8. package/dist/{livekit-client.pt.worker.js.map → livekit-client.fm.worker.js.map} +1 -1
  9. package/dist/{livekit-client.pt.worker.mjs → livekit-client.fm.worker.mjs} +133 -99
  10. package/dist/livekit-client.fm.worker.mjs.map +1 -0
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/api/SignalClient.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +1 -1
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +5 -5
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/worker/FrameCryptor.d.ts +20 -10
  19. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  20. package/dist/src/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
  21. package/dist/src/{packetTrailer/PacketTrailerManager.d.ts.map → frameMetadata/FrameMetadataManager.d.ts.map} +1 -1
  22. package/dist/src/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +7 -7
  23. package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -0
  24. package/dist/src/{packetTrailer → frameMetadata}/types.d.ts +10 -6
  25. package/dist/src/frameMetadata/types.d.ts.map +1 -0
  26. package/dist/src/frameMetadata/utils.d.ts +9 -0
  27. package/dist/src/{packetTrailer → frameMetadata}/utils.d.ts.map +1 -1
  28. package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
  29. package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts.map +1 -0
  30. package/dist/src/index.d.ts +10 -2
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/options.d.ts +8 -4
  33. package/dist/src/options.d.ts.map +1 -1
  34. package/dist/src/room/PCTransport.d.ts +1 -0
  35. package/dist/src/room/PCTransport.d.ts.map +1 -1
  36. package/dist/src/room/PCTransportManager.d.ts +1 -0
  37. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  38. package/dist/src/room/RTCEngine.d.ts +11 -1
  39. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  40. package/dist/src/room/Room.d.ts +2 -2
  41. package/dist/src/room/Room.d.ts.map +1 -1
  42. package/dist/src/room/participant/LocalParticipant.d.ts +2 -2
  43. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  44. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  45. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
  46. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  47. package/dist/src/room/token-source/types.d.ts +1 -0
  48. package/dist/src/room/token-source/types.d.ts.map +1 -1
  49. package/dist/src/room/token-source/utils.d.ts.map +1 -1
  50. package/dist/src/room/track/FrameMetadataExtractor.d.ts +19 -0
  51. package/dist/src/room/track/FrameMetadataExtractor.d.ts.map +1 -0
  52. package/dist/src/room/track/RemoteVideoTrack.d.ts +7 -7
  53. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  54. package/dist/src/room/track/options.d.ts +17 -5
  55. package/dist/src/room/track/options.d.ts.map +1 -1
  56. package/dist/ts4.2/e2ee/E2eeManager.d.ts +1 -1
  57. package/dist/ts4.2/e2ee/types.d.ts +5 -5
  58. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +19 -9
  59. package/dist/ts4.2/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
  60. package/dist/ts4.2/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +7 -7
  61. package/dist/ts4.2/{packetTrailer → frameMetadata}/types.d.ts +10 -6
  62. package/dist/ts4.2/frameMetadata/utils.d.ts +9 -0
  63. package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
  64. package/dist/ts4.2/index.d.ts +9 -3
  65. package/dist/ts4.2/options.d.ts +8 -4
  66. package/dist/ts4.2/room/PCTransport.d.ts +1 -0
  67. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
  68. package/dist/ts4.2/room/RTCEngine.d.ts +11 -1
  69. package/dist/ts4.2/room/Room.d.ts +2 -2
  70. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +2 -2
  71. package/dist/ts4.2/room/token-source/types.d.ts +1 -0
  72. package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -0
  73. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +7 -7
  74. package/dist/ts4.2/room/track/options.d.ts +17 -5
  75. package/package.json +25 -20
  76. package/src/api/SignalClient.test.ts +4 -4
  77. package/src/api/SignalClient.ts +7 -3
  78. package/src/api/WebSocketStream.test.ts +7 -7
  79. package/src/e2ee/E2eeManager.ts +13 -11
  80. package/src/e2ee/types.ts +5 -5
  81. package/src/e2ee/worker/FrameCryptor.test.ts +5 -5
  82. package/src/e2ee/worker/FrameCryptor.ts +69 -30
  83. package/src/e2ee/worker/e2ee.worker.ts +5 -5
  84. package/src/e2ee/worker/tsconfig.json +4 -1
  85. package/src/{packetTrailer/PacketTrailerManager.test.ts → frameMetadata/FrameMetadataManager.test.ts} +10 -8
  86. package/src/{packetTrailer/PacketTrailerManager.ts → frameMetadata/FrameMetadataManager.ts} +21 -15
  87. package/src/{packetTrailer/packetTrailer.test.ts → frameMetadata/frameMetadata.test.ts} +1 -1
  88. package/src/{packetTrailer/packetTrailer.ts → frameMetadata/frameMetadata.ts} +9 -9
  89. package/src/{packetTrailer → frameMetadata}/types.ts +12 -6
  90. package/src/{packetTrailer → frameMetadata}/utils.test.ts +19 -19
  91. package/src/{packetTrailer → frameMetadata}/utils.ts +11 -11
  92. package/src/{packetTrailer/worker/packetTrailer.worker.ts → frameMetadata/worker/frameMetadata.worker.ts} +5 -5
  93. package/src/{packetTrailer → frameMetadata}/worker/tsconfig.json +4 -1
  94. package/src/index.ts +13 -2
  95. package/src/options.ts +9 -4
  96. package/src/room/PCTransport.ts +4 -0
  97. package/src/room/PCTransportManager.ts +4 -0
  98. package/src/room/RTCEngine.test.ts +143 -9
  99. package/src/room/RTCEngine.ts +156 -48
  100. package/src/room/Room.ts +10 -10
  101. package/src/room/participant/LocalParticipant.test.ts +19 -19
  102. package/src/room/participant/LocalParticipant.ts +21 -16
  103. package/src/room/participant/publishUtils.test.ts +6 -0
  104. package/src/room/participant/publishUtils.ts +4 -1
  105. package/src/room/rpc/client/RpcClientManager.ts +6 -3
  106. package/src/room/rpc/server/RpcServerManager.ts +1 -1
  107. package/src/room/token-source/TokenSource.ts +9 -23
  108. package/src/room/token-source/types.ts +1 -0
  109. package/src/room/token-source/utils.ts +1 -0
  110. package/src/room/track/{PacketTrailerExtractor.ts → FrameMetadataExtractor.ts} +8 -8
  111. package/src/room/track/RemoteVideoTrack.ts +7 -11
  112. package/src/room/track/options.ts +19 -5
  113. package/dist/livekit-client.pt.worker.js +0 -2
  114. package/dist/livekit-client.pt.worker.mjs.map +0 -1
  115. package/dist/src/packetTrailer/packetTrailer.d.ts.map +0 -1
  116. package/dist/src/packetTrailer/types.d.ts.map +0 -1
  117. package/dist/src/packetTrailer/utils.d.ts +0 -9
  118. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
  119. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +0 -1
  120. package/dist/src/room/track/PacketTrailerExtractor.d.ts +0 -19
  121. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +0 -1
  122. package/dist/ts4.2/packetTrailer/utils.d.ts +0 -9
  123. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
  124. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +0 -19
@@ -1,25 +1,25 @@
1
1
  import { PacketTrailerFeature } from '@livekit/protocol';
2
2
  import { isInsertableStreamSupported } from '../e2ee/utils';
3
3
  import { isScriptTransformSupportedForWorker } from '../room/utils';
4
- import type { PacketTrailerOptions } from './PacketTrailerManager';
5
- import type { PacketTrailerPublishOptions } from './types';
4
+ import type { FrameMetadataOptions } from './FrameMetadataManager';
5
+ import type { FrameMetadataPublishOptions } from './types';
6
6
 
7
- export function shouldUsePacketTrailerScriptTransform() {
7
+ export function shouldUseFrameMetadataScriptTransform() {
8
8
  return isScriptTransformSupportedForWorker();
9
9
  }
10
10
 
11
- export function isPacketTrailerSupported(options?: PacketTrailerOptions) {
11
+ export function isFrameMetadataSupported(options?: FrameMetadataOptions) {
12
12
  return (
13
- !!options?.worker && (isInsertableStreamSupported() || shouldUsePacketTrailerScriptTransform())
13
+ !!options?.worker && (isInsertableStreamSupported() || shouldUseFrameMetadataScriptTransform())
14
14
  );
15
15
  }
16
16
 
17
- export function hasPacketTrailerPublishOptions(options?: PacketTrailerPublishOptions): boolean {
17
+ export function hasFrameMetadataPublishOptions(options?: FrameMetadataPublishOptions): boolean {
18
18
  return !!(options?.timestamp || options?.frameId);
19
19
  }
20
20
 
21
- export function getPacketTrailerFeatures(
22
- options?: PacketTrailerPublishOptions,
21
+ export function getFrameMetadataFeatures(
22
+ options?: FrameMetadataPublishOptions,
23
23
  ): PacketTrailerFeature[] {
24
24
  const features: PacketTrailerFeature[] = [];
25
25
  if (options?.timestamp) {
@@ -31,14 +31,14 @@ export function getPacketTrailerFeatures(
31
31
  return features;
32
32
  }
33
33
 
34
- export function getPacketTrailerPublishOptions(
34
+ export function getFrameMetadataPublishOptions(
35
35
  features?: PacketTrailerFeature[],
36
- ): PacketTrailerPublishOptions | undefined {
36
+ ): FrameMetadataPublishOptions | undefined {
37
37
  if (!features || features.length === 0) {
38
38
  return undefined;
39
39
  }
40
40
 
41
- const options: PacketTrailerPublishOptions = {};
41
+ const options: FrameMetadataPublishOptions = {};
42
42
  if (features.includes(PacketTrailerFeature.PTF_USER_TIMESTAMP)) {
43
43
  options.timestamp = true;
44
44
  }
@@ -1,11 +1,11 @@
1
- import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../packetTrailer';
1
+ import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../frameMetadata';
2
2
  import type {
3
+ FrameMetadataPublishOptions,
3
4
  PTMetadataMessage,
4
5
  PTScriptTransformOptions,
5
6
  PTWorkerMessage,
6
- PacketTrailerPublishOptions,
7
7
  } from '../types';
8
- import { hasPacketTrailerPublishOptions } from '../utils';
8
+ import { hasFrameMetadataPublishOptions } from '../utils';
9
9
 
10
10
  /**
11
11
  * Holds the trackId currently associated with a pipeline. A mutable
@@ -97,9 +97,9 @@ function setupDecodeTransform(
97
97
  function setupEncodeTransform(
98
98
  readable: ReadableStream,
99
99
  writable: WritableStream,
100
- packetTrailer?: PacketTrailerPublishOptions,
100
+ packetTrailer?: FrameMetadataPublishOptions,
101
101
  ) {
102
- if (!hasPacketTrailerPublishOptions(packetTrailer)) {
102
+ if (!hasFrameMetadataPublishOptions(packetTrailer)) {
103
103
  readable.pipeTo(writable).catch(() => {});
104
104
  return;
105
105
  }
@@ -8,7 +8,10 @@
8
8
  "ES2018.Promise",
9
9
  "WebWorker",
10
10
  "ES2021.WeakRef",
11
- "DOM.AsyncIterable"
11
+ "DOM.AsyncIterable",
12
+ "ES2020.BigInt",
13
+ "ES2018.AsyncIterable",
14
+ "ES2018.AsyncGenerator"
12
15
  ]
13
16
  }
14
17
  }
package/src/index.ts CHANGED
@@ -64,11 +64,22 @@ import {
64
64
  import { getBrowser } from './utils/browserParser';
65
65
 
66
66
  export { RpcError, type RpcInvocationData, type PerformRpcParams } from './room/rpc';
67
- export type { PacketTrailerMetadata, PacketTrailerPublishOptions } from './packetTrailer/types';
67
+ export type {
68
+ FrameMetadata,
69
+ FrameMetadataPublishOptions,
70
+ /** @deprecated Use {@link FrameMetadata} instead. */
71
+ PacketTrailerMetadata,
72
+ /** @deprecated Use {@link FrameMetadataPublishOptions} instead. */
73
+ PacketTrailerPublishOptions,
74
+ } from './frameMetadata/types';
68
75
  export {
76
+ FrameMetadataManager,
77
+ /** @deprecated Use {@link FrameMetadataManager} instead. */
69
78
  PacketTrailerManager,
79
+ type FrameMetadataOptions,
80
+ /** @deprecated Use {@link FrameMetadataOptions} instead. */
70
81
  type PacketTrailerOptions,
71
- } from './packetTrailer/PacketTrailerManager';
82
+ } from './frameMetadata/FrameMetadataManager';
72
83
 
73
84
  export * from './connectionHelper/ConnectionCheck';
74
85
  export * from './connectionHelper/checks/Checker';
package/src/options.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { E2EEOptions } from './e2ee/types';
2
- import type { PacketTrailerOptions } from './packetTrailer/PacketTrailerManager';
2
+ import type { FrameMetadataOptions } from './frameMetadata/FrameMetadataManager';
3
3
  import type { ReconnectPolicy } from './room/ReconnectPolicy';
4
4
  import type {
5
5
  AudioCaptureOptions,
@@ -103,10 +103,15 @@ export interface InternalRoomOptions {
103
103
 
104
104
  /**
105
105
  * @experimental
106
- * Options for enabling packet trailers on video tracks.
107
- * Packet trailers carry frame-level metadata such as user timestamps and frame IDs.
106
+ * Options for enabling frame metadata on video tracks.
107
+ * Frame metadata carries frame-level information such as user timestamps and frame IDs.
108
108
  */
109
- packetTrailer?: PacketTrailerOptions;
109
+ frameMetadata?: FrameMetadataOptions;
110
+
111
+ /**
112
+ * @deprecated Use {@link InternalRoomOptions.frameMetadata} instead.
113
+ */
114
+ packetTrailer?: FrameMetadataOptions;
110
115
 
111
116
  /**
112
117
  * will attempt to connect via single peer connection mode.
@@ -498,6 +498,10 @@ export default class PCTransport extends (EventEmitter as new () => TypedEmitter
498
498
  return this.pc.getStats();
499
499
  }
500
500
 
501
+ getMaxMessageSize() {
502
+ return this._pc?.sctp?.maxMessageSize;
503
+ }
504
+
501
505
  async getConnectedAddress(): Promise<string | undefined> {
502
506
  if (!this._pc) {
503
507
  return;
@@ -310,6 +310,10 @@ export class PCTransportManager {
310
310
  return matchingTransceiver?.mid;
311
311
  }
312
312
 
313
+ getMaxPublisherMessageSize() {
314
+ return this.publisher.getMaxMessageSize();
315
+ }
316
+
313
317
  addPublisherTrack(track: MediaStreamTrack) {
314
318
  return this.publisher.addTrack(track);
315
319
  }
@@ -1,6 +1,8 @@
1
+ import { DataPacket, DataPacket_Kind, UserPacket } from '@livekit/protocol';
1
2
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import RTCEngine from './RTCEngine';
3
+ import RTCEngine, { DataChannelKind } from './RTCEngine';
3
4
  import { roomOptionDefaults } from './defaults';
5
+ import { PublishDataError } from './errors';
4
6
 
5
7
  describe('RTCEngine', () => {
6
8
  const originalRTCRtpSender = window.RTCRtpSender;
@@ -56,12 +58,12 @@ describe('RTCEngine', () => {
56
58
  ).makeRTCConfiguration();
57
59
  }
58
60
 
59
- function setupPacketTrailerSender(engine: RTCEngine, sender: RTCRtpSender, opts = {}) {
61
+ function setupFrameMetadataSender(engine: RTCEngine, sender: RTCRtpSender, opts = {}) {
60
62
  (
61
63
  engine as unknown as {
62
- setupPacketTrailerSender: (sender: RTCRtpSender, opts?: unknown) => void;
64
+ setupFrameMetadataSender: (sender: RTCRtpSender, opts?: unknown) => void;
63
65
  }
64
- ).setupPacketTrailerSender(sender, opts);
66
+ ).setupFrameMetadataSender(sender, opts);
65
67
  }
66
68
 
67
69
  it('does not enable encoded insertable streams without E2EE or a packet trailer worker', () => {
@@ -128,7 +130,7 @@ describe('RTCEngine', () => {
128
130
  createEncodedStreams,
129
131
  } as unknown as RTCRtpSender;
130
132
 
131
- setupPacketTrailerSender(engine, sender);
133
+ setupFrameMetadataSender(engine, sender);
132
134
 
133
135
  expect(createEncodedStreams).not.toHaveBeenCalled();
134
136
  });
@@ -145,7 +147,7 @@ describe('RTCEngine', () => {
145
147
  createEncodedStreams,
146
148
  } as unknown as RTCRtpSender;
147
149
 
148
- setupPacketTrailerSender(engine, sender);
150
+ setupFrameMetadataSender(engine, sender);
149
151
 
150
152
  expect(createEncodedStreams).not.toHaveBeenCalled();
151
153
  });
@@ -165,7 +167,7 @@ describe('RTCEngine', () => {
165
167
  createEncodedStreams,
166
168
  } as unknown as RTCRtpSender;
167
169
 
168
- setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true, frameId: true } });
170
+ setupFrameMetadataSender(engine, sender, { packetTrailer: { timestamp: true, frameId: true } });
169
171
 
170
172
  expect(createEncodedStreams).toHaveBeenCalledTimes(1);
171
173
  expect(worker.postMessage).toHaveBeenCalledWith(
@@ -185,7 +187,9 @@ describe('RTCEngine', () => {
185
187
  stubScriptTransformSupport();
186
188
 
187
189
  const transform = {};
188
- const RTCRtpScriptTransform = vi.fn(() => transform);
190
+ const RTCRtpScriptTransform = vi.fn(function () {
191
+ return transform;
192
+ });
189
193
  Object.defineProperty(window, 'RTCRtpScriptTransform', {
190
194
  configurable: true,
191
195
  value: RTCRtpScriptTransform,
@@ -207,7 +211,7 @@ describe('RTCEngine', () => {
207
211
  createEncodedStreams,
208
212
  } as unknown as RTCRtpSender;
209
213
 
210
- setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true } });
214
+ setupFrameMetadataSender(engine, sender, { packetTrailer: { timestamp: true } });
211
215
 
212
216
  expect(RTCRtpScriptTransform).toHaveBeenCalledWith(worker, {
213
217
  kind: 'encode',
@@ -216,4 +220,134 @@ describe('RTCEngine', () => {
216
220
  expect((sender as unknown as { transform: unknown }).transform).toBe(transform);
217
221
  expect(createEncodedStreams).not.toHaveBeenCalled();
218
222
  });
223
+
224
+ describe('sendDataPacket', () => {
225
+ const MAX_DATA_PACKET_SIZE = 64 * 1024 - 1; // 65535 bytes (64 KB - 1)
226
+ function stubConnectedEngine(
227
+ engine: RTCEngine,
228
+ maxDataPacketSize: number = MAX_DATA_PACKET_SIZE,
229
+ ) {
230
+ const send = vi.fn();
231
+ Object.assign(engine as unknown as Record<string, unknown>, {
232
+ ensurePublisherConnected: vi.fn().mockResolvedValue(undefined),
233
+ waitForBufferStatusLow: vi.fn().mockResolvedValue(undefined),
234
+ updateAndEmitDCBufferStatus: vi.fn(),
235
+ dataChannelForKind: vi.fn(() => ({ send })),
236
+ pcManager: {
237
+ getMaxPublisherMessageSize: vi.fn(() => maxDataPacketSize),
238
+ },
239
+ });
240
+ return send;
241
+ }
242
+
243
+ it('rejects packets larger than the max data packet size', async () => {
244
+ const engine = new RTCEngine(roomOptionDefaults);
245
+ const send = stubConnectedEngine(engine);
246
+
247
+ // The serialized packet includes protobuf framing on top of the payload, so a payload at the
248
+ // limit is already guaranteed to exceed it once serialized.
249
+ const packet = new DataPacket({
250
+ kind: DataPacket_Kind.RELIABLE,
251
+ value: {
252
+ case: 'user',
253
+ value: new UserPacket({ payload: new Uint8Array(MAX_DATA_PACKET_SIZE) }),
254
+ },
255
+ });
256
+
257
+ await expect(engine.sendDataPacket(packet, DataChannelKind.RELIABLE)).rejects.toBeInstanceOf(
258
+ PublishDataError,
259
+ );
260
+ expect(send).not.toHaveBeenCalled();
261
+ });
262
+
263
+ it('does not reject packets if the max data packet size is 0', async () => {
264
+ const engine = new RTCEngine(roomOptionDefaults);
265
+ const send = stubConnectedEngine(engine, 0);
266
+
267
+ const packet = new DataPacket({
268
+ kind: DataPacket_Kind.RELIABLE,
269
+ value: {
270
+ case: 'user',
271
+ value: new UserPacket({ payload: new Uint8Array(100) }),
272
+ },
273
+ });
274
+
275
+ // Sending the packet should succeed, there isn't a size limit
276
+ await expect(
277
+ engine.sendDataPacket(packet, DataChannelKind.RELIABLE),
278
+ ).resolves.toBeUndefined();
279
+ expect(send).toHaveBeenCalledTimes(1);
280
+ });
281
+
282
+ it('sends packets within the max data packet size', async () => {
283
+ const engine = new RTCEngine(roomOptionDefaults);
284
+ const send = stubConnectedEngine(engine);
285
+
286
+ const packet = new DataPacket({
287
+ kind: DataPacket_Kind.RELIABLE,
288
+ value: {
289
+ case: 'user',
290
+ value: new UserPacket({ payload: new Uint8Array(1024) }),
291
+ },
292
+ });
293
+
294
+ await expect(
295
+ engine.sendDataPacket(packet, DataChannelKind.RELIABLE),
296
+ ).resolves.toBeUndefined();
297
+ expect(send).toHaveBeenCalledTimes(1);
298
+ });
299
+ });
300
+
301
+ describe('handleDataChannelClose', () => {
302
+ function stubCloseEnv(
303
+ engine: RTCEngine,
304
+ { closed, publisherState }: { closed: boolean; publisherState: RTCPeerConnectionState },
305
+ ) {
306
+ const error = vi.fn();
307
+ Object.assign(engine as unknown as Record<string, unknown>, {
308
+ _isClosed: closed,
309
+ log: { error },
310
+ pcManager: {
311
+ publisher: { getConnectionState: () => publisherState },
312
+ },
313
+ });
314
+ return error;
315
+ }
316
+
317
+ function fireClose(engine: RTCEngine, kind: DataChannelKind) {
318
+ (
319
+ engine as unknown as {
320
+ handleDataChannelClose: (kind: DataChannelKind) => () => void;
321
+ }
322
+ ).handleDataChannelClose(kind)();
323
+ }
324
+
325
+ it('logs an error when a publisher channel closes while connected', () => {
326
+ const engine = new RTCEngine(roomOptionDefaults);
327
+ const error = stubCloseEnv(engine, { closed: false, publisherState: 'connected' });
328
+
329
+ fireClose(engine, DataChannelKind.RELIABLE);
330
+
331
+ expect(error).toHaveBeenCalledOnce();
332
+ expect(error.mock.calls[0][0]).toContain('RELIABLE');
333
+ });
334
+
335
+ it('stays quiet when the engine is already closed', () => {
336
+ const engine = new RTCEngine(roomOptionDefaults);
337
+ const error = stubCloseEnv(engine, { closed: true, publisherState: 'connected' });
338
+
339
+ fireClose(engine, DataChannelKind.RELIABLE);
340
+
341
+ expect(error).not.toHaveBeenCalled();
342
+ });
343
+
344
+ it('stays quiet when the publisher PC is no longer connected', () => {
345
+ const engine = new RTCEngine(roomOptionDefaults);
346
+ const error = stubCloseEnv(engine, { closed: false, publisherState: 'closed' });
347
+
348
+ fireClose(engine, DataChannelKind.RELIABLE);
349
+
350
+ expect(error).not.toHaveBeenCalled();
351
+ });
352
+ });
219
353
  });