livekit-client 2.17.3 → 2.18.1

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 (183) 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 +8 -7
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +7823 -5772
  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 +12 -4
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/e2ee/constants.d.ts.map +1 -1
  12. package/dist/src/e2ee/types.d.ts +6 -0
  13. package/dist/src/e2ee/types.d.ts.map +1 -1
  14. package/dist/src/e2ee/utils.d.ts +2 -1
  15. package/dist/src/e2ee/utils.d.ts.map +1 -1
  16. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  17. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  18. package/dist/src/index.d.ts +5 -4
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/room/PCTransport.d.ts +5 -0
  21. package/dist/src/room/PCTransport.d.ts.map +1 -1
  22. package/dist/src/room/PCTransportManager.d.ts +1 -1
  23. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts +27 -9
  25. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  26. package/dist/src/room/Room.d.ts +13 -3
  27. package/dist/src/room/Room.d.ts.map +1 -1
  28. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  29. package/dist/src/room/data-track/LocalDataTrack.d.ts +48 -0
  30. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -0
  31. package/dist/src/room/data-track/RemoteDataTrack.d.ts +46 -0
  32. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -0
  33. package/dist/src/room/data-track/depacketizer.d.ts +6 -6
  34. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  35. package/dist/src/room/data-track/frame.d.ts +14 -0
  36. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  37. package/dist/src/room/data-track/handle.d.ts +2 -2
  38. package/dist/src/room/data-track/handle.d.ts.map +1 -1
  39. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +104 -0
  40. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -0
  41. package/dist/src/room/data-track/incoming/errors.d.ts +24 -0
  42. package/dist/src/room/data-track/incoming/errors.d.ts.map +1 -0
  43. package/dist/src/room/data-track/incoming/pipeline.d.ts +38 -0
  44. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -0
  45. package/dist/src/room/data-track/incoming/types.d.ts +20 -0
  46. package/dist/src/room/data-track/incoming/types.d.ts.map +1 -0
  47. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +63 -28
  48. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
  49. package/dist/src/room/data-track/outgoing/errors.d.ts +20 -10
  50. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
  51. package/dist/src/room/data-track/outgoing/pipeline.d.ts +9 -8
  52. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
  53. package/dist/src/room/data-track/outgoing/types.d.ts +16 -7
  54. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  55. package/dist/src/room/data-track/packet/errors.d.ts +2 -4
  56. package/dist/src/room/data-track/packet/errors.d.ts.map +1 -1
  57. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  58. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  60. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  61. package/dist/src/room/data-track/packet/serializable.d.ts +4 -4
  62. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packetizer.d.ts +6 -6
  64. package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
  65. package/dist/src/room/data-track/track-interfaces.d.ts +23 -0
  66. package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -0
  67. package/dist/src/room/data-track/types.d.ts +15 -0
  68. package/dist/src/room/data-track/types.d.ts.map +1 -0
  69. package/dist/src/room/events.d.ts +24 -3
  70. package/dist/src/room/events.d.ts.map +1 -1
  71. package/dist/src/room/participant/LocalParticipant.d.ts +11 -1
  72. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  73. package/dist/src/room/participant/RemoteParticipant.d.ts +14 -1
  74. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  75. package/dist/src/room/token-source/TokenSource.d.ts +1 -1
  76. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  77. package/dist/src/room/token-source/types.d.ts +1 -0
  78. package/dist/src/room/token-source/types.d.ts.map +1 -1
  79. package/dist/src/room/utils.d.ts +2 -1
  80. package/dist/src/room/utils.d.ts.map +1 -1
  81. package/dist/src/utils/abort-signal-polyfill.d.ts +13 -0
  82. package/dist/src/utils/abort-signal-polyfill.d.ts.map +1 -0
  83. package/dist/src/utils/deferrable-map.d.ts +32 -0
  84. package/dist/src/utils/deferrable-map.d.ts.map +1 -0
  85. package/dist/src/utils/subscribeToEvents.d.ts +3 -0
  86. package/dist/src/utils/subscribeToEvents.d.ts.map +1 -1
  87. package/dist/ts4.2/api/SignalClient.d.ts +12 -4
  88. package/dist/ts4.2/e2ee/types.d.ts +6 -0
  89. package/dist/ts4.2/e2ee/utils.d.ts +2 -1
  90. package/dist/ts4.2/index.d.ts +5 -4
  91. package/dist/ts4.2/room/PCTransport.d.ts +5 -0
  92. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
  93. package/dist/ts4.2/room/RTCEngine.d.ts +27 -9
  94. package/dist/ts4.2/room/Room.d.ts +13 -3
  95. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +48 -0
  96. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +46 -0
  97. package/dist/ts4.2/room/data-track/depacketizer.d.ts +6 -6
  98. package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
  99. package/dist/ts4.2/room/data-track/handle.d.ts +2 -2
  100. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +110 -0
  101. package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -0
  102. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +38 -0
  103. package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -0
  104. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +63 -29
  105. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +20 -10
  106. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +9 -8
  107. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +16 -7
  108. package/dist/ts4.2/room/data-track/packet/errors.d.ts +2 -4
  109. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  110. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -6
  111. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +4 -4
  112. package/dist/ts4.2/room/data-track/packetizer.d.ts +6 -6
  113. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -0
  114. package/dist/ts4.2/room/data-track/types.d.ts +15 -0
  115. package/dist/ts4.2/room/events.d.ts +24 -3
  116. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +11 -1
  117. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +14 -1
  118. package/dist/ts4.2/room/token-source/TokenSource.d.ts +1 -1
  119. package/dist/ts4.2/room/token-source/types.d.ts +1 -0
  120. package/dist/ts4.2/room/utils.d.ts +2 -1
  121. package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -0
  122. package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
  123. package/dist/ts4.2/utils/subscribeToEvents.d.ts +3 -0
  124. package/package.json +4 -2
  125. package/src/api/SignalClient.test.ts +9 -4
  126. package/src/api/SignalClient.ts +116 -9
  127. package/src/e2ee/constants.ts +1 -0
  128. package/src/e2ee/types.ts +6 -0
  129. package/src/e2ee/utils.ts +4 -3
  130. package/src/e2ee/worker/DataCryptor.ts +1 -4
  131. package/src/e2ee/worker/FrameCryptor.ts +1 -4
  132. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
  133. package/src/index.ts +6 -4
  134. package/src/room/PCTransport.ts +41 -1
  135. package/src/room/PCTransportManager.ts +1 -1
  136. package/src/room/RTCEngine.ts +274 -112
  137. package/src/room/Room.ts +152 -15
  138. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +9 -9
  139. package/src/room/data-track/LocalDataTrack.ts +126 -0
  140. package/src/room/data-track/RemoteDataTrack.ts +80 -0
  141. package/src/room/data-track/depacketizer.ts +23 -26
  142. package/src/room/data-track/frame.ts +28 -2
  143. package/src/room/data-track/handle.ts +2 -8
  144. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +555 -0
  145. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +589 -0
  146. package/src/room/data-track/incoming/errors.ts +57 -0
  147. package/src/room/data-track/incoming/pipeline.ts +121 -0
  148. package/src/room/data-track/incoming/types.ts +22 -0
  149. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +240 -27
  150. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +165 -84
  151. package/src/room/data-track/outgoing/errors.ts +40 -11
  152. package/src/room/data-track/outgoing/pipeline.ts +25 -23
  153. package/src/room/data-track/outgoing/types.ts +14 -6
  154. package/src/room/data-track/packet/errors.ts +2 -14
  155. package/src/room/data-track/packet/extensions.ts +21 -26
  156. package/src/room/data-track/packet/index.test.ts +22 -33
  157. package/src/room/data-track/packet/index.ts +4 -6
  158. package/src/room/data-track/packet/serializable.ts +4 -4
  159. package/src/room/data-track/packetizer.test.ts +2 -2
  160. package/src/room/data-track/packetizer.ts +7 -10
  161. package/src/room/data-track/track-interfaces.ts +53 -0
  162. package/src/room/data-track/types.ts +31 -0
  163. package/src/room/events.ts +26 -1
  164. package/src/room/participant/LocalParticipant.ts +57 -6
  165. package/src/room/participant/RemoteParticipant.ts +26 -1
  166. package/src/room/token-source/TokenSource.ts +8 -2
  167. package/src/room/token-source/types.ts +4 -0
  168. package/src/room/utils.ts +5 -1
  169. package/src/utils/abort-signal-polyfill.ts +63 -0
  170. package/src/utils/deferrable-map.ts +109 -0
  171. package/src/utils/subscribeToEvents.ts +18 -1
  172. package/dist/src/room/data-track/e2ee.d.ts +0 -12
  173. package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
  174. package/dist/src/room/data-track/track.d.ts +0 -30
  175. package/dist/src/room/data-track/track.d.ts.map +0 -1
  176. package/dist/src/utils/throws.d.ts +0 -36
  177. package/dist/src/utils/throws.d.ts.map +0 -1
  178. package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
  179. package/dist/ts4.2/room/data-track/track.d.ts +0 -30
  180. package/dist/ts4.2/utils/throws.d.ts +0 -39
  181. package/src/room/data-track/e2ee.ts +0 -14
  182. package/src/room/data-track/track.ts +0 -50
  183. package/src/utils/throws.ts +0 -42
@@ -0,0 +1,121 @@
1
+ import type { Throws } from '@livekit/throws-transformer/throws';
2
+ import type { BaseE2EEManager } from '../../../e2ee/E2eeManager';
3
+ import { LoggerNames, getLogger } from '../../../logger';
4
+ import DataTrackDepacketizer, { DataTrackDepacketizerDropError } from '../depacketizer';
5
+ import type { DataTrackFrameInternal } from '../frame';
6
+ import { DataTrackPacket } from '../packet';
7
+ import { type DataTrackInfo } from '../types';
8
+
9
+ const log = getLogger(LoggerNames.DataTracks);
10
+
11
+ /**
12
+ * Options for creating a {@link IncomingDataTrackPipeline}.
13
+ */
14
+ type Options = {
15
+ info: DataTrackInfo;
16
+ publisherIdentity: string;
17
+ e2eeManager: BaseE2EEManager | null;
18
+ };
19
+
20
+ /**
21
+ * Pipeline for an individual data track subscription.
22
+ */
23
+ export default class IncomingDataTrackPipeline {
24
+ private publisherIdentity: string;
25
+
26
+ private e2eeManager: BaseE2EEManager | null;
27
+
28
+ private depacketizer: DataTrackDepacketizer;
29
+
30
+ /**
31
+ * Creates a new pipeline with the given options.
32
+ */
33
+ constructor(options: Options) {
34
+ const hasProvider = options.e2eeManager !== null;
35
+ if (options.info.usesE2ee !== hasProvider) {
36
+ // @throws-transformer ignore - this should be treated as a "panic" and not be caught
37
+ throw new Error(
38
+ 'IncomingDataTrackPipeline: DataTrackInfo.usesE2ee must match presence of decryptionProvider',
39
+ );
40
+ }
41
+
42
+ const depacketizer = new DataTrackDepacketizer();
43
+
44
+ this.publisherIdentity = options.publisherIdentity;
45
+ this.e2eeManager = options.e2eeManager ?? null;
46
+ this.depacketizer = depacketizer;
47
+ }
48
+
49
+ updateE2eeManager(e2eeManager: BaseE2EEManager | null) {
50
+ this.e2eeManager = e2eeManager;
51
+ }
52
+
53
+ async processPacket(
54
+ packet: DataTrackPacket,
55
+ ): Promise<Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError>> {
56
+ const frame = this.depacketize(packet);
57
+ if (!frame) {
58
+ return null;
59
+ }
60
+
61
+ const decrypted = await this.decryptIfNeeded(frame);
62
+ if (!decrypted) {
63
+ return null;
64
+ }
65
+
66
+ return decrypted;
67
+ }
68
+
69
+ /**
70
+ * Depacketize the given frame, log if a drop occurs.
71
+ */
72
+ private depacketize(
73
+ packet: DataTrackPacket,
74
+ ): Throws<DataTrackFrameInternal | null, DataTrackDepacketizerDropError> {
75
+ let frame: DataTrackFrameInternal | null;
76
+ try {
77
+ frame = this.depacketizer.push(packet);
78
+ } catch (err) {
79
+ // In a future version, use this to maintain drop statistics.
80
+ // FIXME: is this a good idea?
81
+ log.warn(`Data frame depacketize error: ${err}`);
82
+ return null;
83
+ }
84
+ return frame;
85
+ }
86
+
87
+ /**
88
+ * Decrypt the frame's payload if E2EE is enabled for this track.
89
+ */
90
+ private async decryptIfNeeded(
91
+ frame: DataTrackFrameInternal,
92
+ ): Promise<DataTrackFrameInternal | null> {
93
+ const e2eeManager = this.e2eeManager;
94
+
95
+ if (!e2eeManager) {
96
+ return frame;
97
+ }
98
+
99
+ const e2ee = frame.extensions?.e2ee ?? null;
100
+ if (!e2ee) {
101
+ log.error('Missing E2EE meta');
102
+ return null;
103
+ }
104
+
105
+ let result;
106
+ try {
107
+ result = await e2eeManager.handleEncryptedData(
108
+ frame.payload,
109
+ e2ee.iv,
110
+ this.publisherIdentity,
111
+ e2ee.keyIndex,
112
+ );
113
+ } catch (err) {
114
+ log.error(`Error decrypting packet: ${err}`);
115
+ return null;
116
+ }
117
+
118
+ frame.payload = result.payload;
119
+ return frame;
120
+ }
121
+ }
@@ -0,0 +1,22 @@
1
+ import type Participant from '../../participant/Participant';
2
+ import type RemoteDataTrack from '../RemoteDataTrack';
3
+ import { type DataTrackSid } from '../types';
4
+
5
+ /** Request sent to the SFU to update the subscription for a data track. */
6
+ export type EventSfuUpdateSubscription = {
7
+ /** Identifier of the affected track. */
8
+ sid: DataTrackSid;
9
+ /** Whether to subscribe or unsubscribe. */
10
+ subscribe: boolean;
11
+ };
12
+
13
+ /** A track has been published by a remote participant and is available to be subscribed to. */
14
+ export type EventTrackAvailable = {
15
+ track: RemoteDataTrack;
16
+ };
17
+
18
+ /** A track has been unpublished by a remote participant and can no longer be subscribed to. */
19
+ export type EventTrackUnavailable = {
20
+ sid: DataTrackSid;
21
+ publisherIdentity: Participant['identity'];
22
+ };
@@ -1,31 +1,76 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-vars */
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import {
4
+ type DecryptDataResponseMessage,
5
+ type EncryptDataResponseMessage,
6
+ LocalDataTrack,
7
+ } from '../../..';
8
+ import { type BaseE2EEManager } from '../../../e2ee/E2eeManager';
3
9
  import { subscribeToEvents } from '../../../utils/subscribeToEvents';
4
- import { EncryptionProvider } from '../e2ee';
10
+ import RTCEngine from '../../RTCEngine';
11
+ import Room from '../../Room';
5
12
  import { DataTrackHandle } from '../handle';
6
13
  import { DataTrackPacket, FrameMarker } from '../packet';
7
14
  import OutgoingDataTrackManager, {
8
- DataTrackOutgoingManagerCallbacks,
15
+ type DataTrackOutgoingManagerCallbacks,
9
16
  Descriptor,
10
17
  } from './OutgoingDataTrackManager';
11
18
  import { DataTrackPublishError } from './errors';
12
19
 
13
- /** A fake "encryption" provider used for test purposes. Adds a prefix to the payload. */
14
- const PrefixingEncryptionProvider: EncryptionProvider = {
15
- encrypt(payload: Uint8Array) {
20
+ /** Fake encryption provider for testing e2ee data track features. */
21
+ export class PrefixingEncryptionProvider implements BaseE2EEManager {
22
+ isEnabled = true;
23
+
24
+ isDataChannelEncryptionEnabled = true;
25
+
26
+ setup(_room: Room) {}
27
+
28
+ setupEngine(_engine: RTCEngine) {}
29
+
30
+ setParticipantCryptorEnabled(_enabled: boolean, _participantIdentity: string) {}
31
+
32
+ setSifTrailer(_trailer: Uint8Array) {}
33
+
34
+ on(_event: any, _listener: any): this {
35
+ return this;
36
+ }
37
+
38
+ /** A fake "encryption" provider used for test purposes. Adds a prefix to the payload. */
39
+ async encryptData(data: Uint8Array): Promise<EncryptDataResponseMessage['data']> {
16
40
  const prefix = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
17
41
 
18
- const output = new Uint8Array(prefix.length + payload.length);
42
+ const output = new Uint8Array(prefix.length + data.length);
19
43
  output.set(prefix, 0);
20
- output.set(payload, prefix.length);
44
+ output.set(data, prefix.length);
21
45
 
22
46
  return {
47
+ uuid: crypto.randomUUID(),
23
48
  payload: output,
24
49
  iv: new Uint8Array(12), // Just leaving this empty, is this a bad idea?
25
50
  keyIndex: 0,
26
51
  };
27
- },
28
- };
52
+ }
53
+
54
+ /** A fake "decryption" provider used for test purposes. Assumes the payload is prefixed with
55
+ * 0xdeafbeef, which is stripped off. */
56
+ async handleEncryptedData(
57
+ payload: Uint8Array,
58
+ _iv: Uint8Array,
59
+ _participantIdentity: string,
60
+ _keyIndex: number,
61
+ ): Promise<DecryptDataResponseMessage['data']> {
62
+ if (payload[0] !== 0xde || payload[1] !== 0xad || payload[2] !== 0xbe || payload[3] !== 0xef) {
63
+ throw new Error(
64
+ `PrefixingEncryptionProvider: first four bytes of payload were not 0xdeadbeef, found ${payload.slice(0, 4)}`,
65
+ );
66
+ }
67
+
68
+ return {
69
+ uuid: crypto.randomUUID(),
70
+ payload: payload.slice(4),
71
+ };
72
+ }
73
+ }
29
74
 
30
75
  describe('DataTrackOutgoingManager', () => {
31
76
  it('should test track publishing (ok case)', async () => {
@@ -34,8 +79,11 @@ describe('DataTrackOutgoingManager', () => {
34
79
  'sfuPublishRequest',
35
80
  ]);
36
81
 
82
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
83
+ expect(localDataTrack.isPublished()).toStrictEqual(false);
84
+
37
85
  // 1. Publish a data track
38
- const publishRequestPromise = manager.publishRequest({ name: 'test' });
86
+ const publishRequestPromise = localDataTrack.publish();
39
87
 
40
88
  // 2. This publish request should be sent along to the SFU
41
89
  const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
@@ -55,7 +103,7 @@ describe('DataTrackOutgoingManager', () => {
55
103
  });
56
104
 
57
105
  // Make sure that the original input event resolves.
58
- const localDataTrack = await publishRequestPromise;
106
+ await publishRequestPromise;
59
107
  expect(localDataTrack.isPublished()).toStrictEqual(true);
60
108
  });
61
109
 
@@ -66,7 +114,8 @@ describe('DataTrackOutgoingManager', () => {
66
114
  ]);
67
115
 
68
116
  // 1. Publish a data track
69
- const publishRequestPromise = manager.publishRequest({ name: 'test' });
117
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
118
+ const publishRequestPromise = localDataTrack.publish();
70
119
 
71
120
  // 2. This publish request should be sent along to the SFU
72
121
  const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
@@ -78,7 +127,9 @@ describe('DataTrackOutgoingManager', () => {
78
127
  });
79
128
 
80
129
  // Make sure that the rejection bubbles back to the caller
81
- expect(publishRequestPromise).rejects.toThrowError('Data track publication limit reached');
130
+ await expect(publishRequestPromise).rejects.toThrowError(
131
+ 'Data track publication limit reached',
132
+ );
82
133
  });
83
134
 
84
135
  it('should test track publishing (cancellation half way through)', async () => {
@@ -90,7 +141,8 @@ describe('DataTrackOutgoingManager', () => {
90
141
 
91
142
  // 1. Publish a data track
92
143
  const controller = new AbortController();
93
- const publishRequestPromise = manager.publishRequest({ name: 'test' }, controller.signal);
144
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
145
+ const publishRequestPromise = localDataTrack.publish(controller.signal);
94
146
 
95
147
  // 2. This publish request should be sent along to the SFU
96
148
  const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
@@ -107,7 +159,96 @@ describe('DataTrackOutgoingManager', () => {
107
159
  expect(sfuUnpublishEvent.handle).toStrictEqual(handle);
108
160
 
109
161
  // 5. Make sure cancellation is bubbled up as an error to stop further execution
110
- expect(publishRequestPromise).rejects.toStrictEqual(DataTrackPublishError.cancelled());
162
+ await expect(publishRequestPromise).rejects.toStrictEqual(DataTrackPublishError.cancelled());
163
+ });
164
+
165
+ it('should test track publishing (cancellation before it starts)', async () => {
166
+ const manager = new OutgoingDataTrackManager();
167
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
168
+ 'sfuPublishRequest',
169
+ 'sfuUnpublishRequest',
170
+ ]);
171
+
172
+ // Publish a data track
173
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
174
+ const publishRequestPromise = localDataTrack.publish(AbortSignal.abort(/* already aborted */));
175
+
176
+ // Make sure cancellation is immediately bubbled up
177
+ await expect(publishRequestPromise).rejects.toStrictEqual(DataTrackPublishError.cancelled());
178
+
179
+ // And there were no pending sfu publish requests sent
180
+ expect(managerEvents.areThereBufferedEvents('sfuPublishRequest')).toBe(false);
181
+ });
182
+
183
+ it('should test track publishing, unpublishing, and republishing again', async () => {
184
+ const manager = new OutgoingDataTrackManager();
185
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
186
+ 'sfuPublishRequest',
187
+ 'sfuUnpublishRequest',
188
+ ]);
189
+
190
+ // 1. Create a local data track
191
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
192
+ expect(localDataTrack.isPublished()).toStrictEqual(false);
193
+
194
+ // 2. Publish it
195
+ const publishRequestPromise = localDataTrack.publish();
196
+
197
+ // 3. This publish request should be sent along to the SFU
198
+ const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
199
+ expect(sfuPublishEvent.name).toStrictEqual('test');
200
+ expect(sfuPublishEvent.usesE2ee).toStrictEqual(false);
201
+ const handle = sfuPublishEvent.handle;
202
+
203
+ // 4. Respond to the SFU publish request with an OK response
204
+ manager.receivedSfuPublishResponse(handle, {
205
+ type: 'ok',
206
+ data: {
207
+ sid: 'bogus-sid',
208
+ pubHandle: sfuPublishEvent.handle,
209
+ name: 'test',
210
+ usesE2ee: false,
211
+ },
212
+ });
213
+
214
+ // Make sure that the original input event resolves.
215
+ await publishRequestPromise;
216
+
217
+ // 5. Now the data track should be published
218
+ expect(localDataTrack.isPublished()).toStrictEqual(true);
219
+
220
+ // 6. Unpublish the data track
221
+ const unpublishRequestPromise = localDataTrack.unpublish();
222
+ const sfuUnpublishEvent = await managerEvents.waitFor('sfuUnpublishRequest');
223
+ manager.receivedSfuUnpublishResponse(sfuUnpublishEvent.handle);
224
+ await unpublishRequestPromise;
225
+
226
+ // 7. Now the data track should be unpublished
227
+ expect(localDataTrack.isPublished()).toStrictEqual(false);
228
+
229
+ // 8. Now, republish the track and make sure that be done a second time
230
+ const publishRequestPromise2 = localDataTrack.publish();
231
+ const sfuPublishEvent2 = await managerEvents.waitFor('sfuPublishRequest');
232
+ expect(sfuPublishEvent2.name).toStrictEqual('test');
233
+ expect(sfuPublishEvent2.usesE2ee).toStrictEqual(false);
234
+ const handle2 = sfuPublishEvent2.handle;
235
+ manager.receivedSfuPublishResponse(handle2, {
236
+ type: 'ok',
237
+ data: {
238
+ sid: 'bogus-sid',
239
+ pubHandle: sfuPublishEvent2.handle,
240
+ name: 'test',
241
+ usesE2ee: false,
242
+ },
243
+ });
244
+ await publishRequestPromise2;
245
+
246
+ // 9. Ensure that the track is published again
247
+ expect(localDataTrack.isPublished()).toStrictEqual(true);
248
+
249
+ // 10. Also ensure that the handle used on the second publish attempt differs from the first
250
+ // publish attempt.
251
+ expect(handle).not.toStrictEqual(handle2);
111
252
  });
112
253
 
113
254
  it.each([
@@ -187,18 +328,17 @@ describe('DataTrackOutgoingManager', () => {
187
328
  ]),
188
329
  );
189
330
  const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
190
- 'packetsAvailable',
331
+ 'packetAvailable',
191
332
  ]);
192
333
 
193
- const localDataTrack = manager.createLocalDataTrack(5)!;
194
- expect(localDataTrack).not.toStrictEqual(null);
334
+ const localDataTrack = LocalDataTrack.withExplicitHandle({ name: 'track name' }, manager, 5);
195
335
 
196
336
  // Kick off sending the bytes...
197
- localDataTrack.tryPush(inputBytes);
337
+ localDataTrack.tryPush({ payload: inputBytes });
198
338
 
199
339
  // ... and make sure the corresponding events are emitted to tell the SFU to send the packets
200
340
  for (const outputPacketJson of outputPacketsJson) {
201
- const packetBytes = await managerEvents.waitFor('packetsAvailable');
341
+ const packetBytes = await managerEvents.waitFor('packetAvailable');
202
342
  const [packet] = DataTrackPacket.fromBinary(packetBytes.bytes);
203
343
 
204
344
  expect(packet.toJSON()).toStrictEqual(outputPacketJson);
@@ -208,15 +348,16 @@ describe('DataTrackOutgoingManager', () => {
208
348
 
209
349
  it('should send e2ee encrypted datatrack payload', async () => {
210
350
  const manager = new OutgoingDataTrackManager({
211
- encryptionProvider: PrefixingEncryptionProvider,
351
+ e2eeManager: new PrefixingEncryptionProvider(),
212
352
  });
213
353
  const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
214
354
  'sfuPublishRequest',
215
- 'packetsAvailable',
355
+ 'packetAvailable',
216
356
  ]);
217
357
 
218
358
  // 1. Publish a data track
219
- const publishRequestPromise = manager.publishRequest({ name: 'test' });
359
+ const localDataTrack = new LocalDataTrack({ name: 'test' }, manager);
360
+ const publishRequestPromise = localDataTrack.publish();
220
361
 
221
362
  // 2. This publish request should be sent along to the SFU
222
363
  const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
@@ -236,14 +377,14 @@ describe('DataTrackOutgoingManager', () => {
236
377
  });
237
378
 
238
379
  // Get the connected local data track
239
- const localDataTrack = await publishRequestPromise;
380
+ await publishRequestPromise;
240
381
  expect(localDataTrack.isPublished()).toStrictEqual(true);
241
382
 
242
383
  // Kick off sending the payload bytes
243
- localDataTrack.tryPush(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]));
384
+ localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
244
385
 
245
386
  // Make sure the packet that was sent was encrypted with the PrefixingEncryptionProvider
246
- const packetBytes = await managerEvents.waitFor('packetsAvailable');
387
+ const packetBytes = await managerEvents.waitFor('packetAvailable');
247
388
  const [packet] = DataTrackPacket.fromBinary(packetBytes.bytes);
248
389
 
249
390
  expect(packet.toJSON()).toStrictEqual({
@@ -311,6 +452,76 @@ describe('DataTrackOutgoingManager', () => {
311
452
  expect(manager.getDescriptor(5)).toStrictEqual(null);
312
453
  });
313
454
 
455
+ it('should test a full reconnect', async () => {
456
+ const pubHandle = 5;
457
+ // Create a manager prefilled with a descriptor
458
+ const manager = OutgoingDataTrackManager.withDescriptors(
459
+ new Map([
460
+ [
461
+ DataTrackHandle.fromNumber(5),
462
+ Descriptor.active(
463
+ {
464
+ sid: 'bogus-sid',
465
+ pubHandle,
466
+ name: 'test',
467
+ usesE2ee: false,
468
+ },
469
+ null,
470
+ ),
471
+ ],
472
+ ]),
473
+ );
474
+ const managerEvents = subscribeToEvents<DataTrackOutgoingManagerCallbacks>(manager, [
475
+ 'sfuPublishRequest',
476
+ 'packetAvailable',
477
+ 'sfuUnpublishRequest',
478
+ ]);
479
+ const localDataTrack = LocalDataTrack.withExplicitHandle({ name: 'track name' }, manager, 5);
480
+
481
+ // Make sure the descriptor is in there
482
+ expect(manager.getDescriptor(5)?.type).toStrictEqual('active');
483
+
484
+ // Simulate a full reconnect, which means that any published tracks will need to be republished.
485
+ manager.sfuWillRepublishTracks();
486
+
487
+ // Even though behind the scenes the SFU publications are not active, the user should still see
488
+ // it as "published", sfu reconnects are an implementation detail
489
+ expect(localDataTrack.isPublished()).toStrictEqual(true);
490
+
491
+ // But, even though `isPublished` is true, pushing data should drop (no sfu to send them to!)
492
+ await expect(() =>
493
+ localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) }),
494
+ ).rejects.toThrowError('Frame was dropped');
495
+
496
+ // 2. This publish request should be sent along to the SFU
497
+ const sfuPublishEvent = await managerEvents.waitFor('sfuPublishRequest');
498
+ expect(sfuPublishEvent.name).toStrictEqual('test');
499
+ expect(sfuPublishEvent.usesE2ee).toStrictEqual(false);
500
+ const handle = sfuPublishEvent.handle;
501
+ expect(handle).toStrictEqual(pubHandle);
502
+
503
+ // 3. Respond to the SFU publish request with an OK response
504
+ manager.receivedSfuPublishResponse(handle, {
505
+ type: 'ok',
506
+ data: {
507
+ sid: 'bogus-sid-REPUBLISHED',
508
+ pubHandle: sfuPublishEvent.handle,
509
+ name: 'test',
510
+ usesE2ee: false,
511
+ },
512
+ });
513
+
514
+ // After all this, the local data track should still be published
515
+ expect(localDataTrack.isPublished()).toStrictEqual(true);
516
+
517
+ // And the sid should be the new value
518
+ expect(localDataTrack.info!.sid).toStrictEqual('bogus-sid-REPUBLISHED');
519
+
520
+ // And now that the tracks are backed by the SFU again, pushes should function!
521
+ await localDataTrack.tryPush({ payload: new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]) });
522
+ await managerEvents.waitFor('packetAvailable');
523
+ });
524
+
314
525
  it('should query currently active descriptors', async () => {
315
526
  // Create a manager prefilled with a descriptor
316
527
  const manager = OutgoingDataTrackManager.withDescriptors(
@@ -378,7 +589,9 @@ describe('DataTrackOutgoingManager', () => {
378
589
  const shutdownPromise = manager.shutdown();
379
590
 
380
591
  // The pending data track should be cancelled
381
- expect(pendingDescriptor.completionFuture.promise).rejects.toThrowError('Room disconnected');
592
+ await expect(pendingDescriptor.completionFuture.promise).rejects.toThrowError(
593
+ 'Room disconnected',
594
+ );
382
595
 
383
596
  // And the active data track should be requested to be unpublished
384
597
  const unpublishEvent = await managerEvents.waitFor('sfuUnpublishRequest');