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,555 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { subscribeToEvents } from '../../../utils/subscribeToEvents';
4
+ import { type DataTrackFrame } from '../frame';
5
+ import { DataTrackHandle, DataTrackHandleAllocator } from '../handle';
6
+ import { PrefixingEncryptionProvider } from '../outgoing/OutgoingDataTrackManager.test';
7
+ import { DataTrackPacket, DataTrackPacketHeader, FrameMarker } from '../packet';
8
+ import { DataTrackE2eeExtension, DataTrackExtensions } from '../packet/extensions';
9
+ import { DataTrackTimestamp, WrapAroundUnsignedInt } from '../utils';
10
+ import IncomingDataTrackManager, {
11
+ type DataTrackIncomingManagerCallbacks,
12
+ } from './IncomingDataTrackManager';
13
+ import { DataTrackSubscribeError } from './errors';
14
+
15
+ describe('DataTrackIncomingManager', () => {
16
+ describe('Track publication', () => {
17
+ it('should test track publication additions / removals', async () => {
18
+ const manager = new IncomingDataTrackManager();
19
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
20
+ 'sfuUpdateSubscription',
21
+ 'trackPublished',
22
+ 'trackUnpublished',
23
+ ]);
24
+
25
+ // 1. Add a track, make sure the track available event was sent
26
+ await manager.receiveSfuPublicationUpdates(
27
+ new Map([
28
+ [
29
+ 'identity1',
30
+ [
31
+ {
32
+ sid: 'sid1',
33
+ pubHandle: DataTrackHandle.fromNumber(5),
34
+ name: 'test',
35
+ usesE2ee: false,
36
+ },
37
+ ],
38
+ ],
39
+ ]),
40
+ );
41
+
42
+ const trackPublishedEvent = await managerEvents.waitFor('trackPublished');
43
+ expect(trackPublishedEvent.track.info.sid).toStrictEqual('sid1');
44
+ expect(trackPublishedEvent.track.info.pubHandle).toStrictEqual(DataTrackHandle.fromNumber(5));
45
+ expect(trackPublishedEvent.track.info.name).toStrictEqual('test');
46
+ expect(trackPublishedEvent.track.info.usesE2ee).toStrictEqual(false);
47
+
48
+ // 2. Check to make sure the publication has been noted in internal state
49
+ expect((await manager.queryPublications()).map((p) => p.pubHandle)).to.deep.equal([
50
+ DataTrackHandle.fromNumber(5),
51
+ ]);
52
+
53
+ // 3. Remove all tracks, and make sure the internal state is cleared
54
+ await manager.receiveSfuPublicationUpdates(new Map([['identity1', []]]));
55
+ expect(await manager.queryPublications()).to.deep.equal([]);
56
+
57
+ const trackUnpublishedEvent = await managerEvents.waitFor('trackUnpublished');
58
+ expect(trackUnpublishedEvent.sid).toStrictEqual('sid1');
59
+ expect(trackUnpublishedEvent.publisherIdentity).toStrictEqual('identity1');
60
+ });
61
+
62
+ it('should process sfu publication updates idempotently', async () => {
63
+ const manager = new IncomingDataTrackManager();
64
+
65
+ // 1. Simulate three identical track publications being received
66
+ for (let i = 0; i < 3; i += 1) {
67
+ await manager.receiveSfuPublicationUpdates(
68
+ new Map([
69
+ [
70
+ 'identity1',
71
+ [
72
+ {
73
+ sid: 'sid1',
74
+ pubHandle: DataTrackHandle.fromNumber(5),
75
+ name: 'test',
76
+ usesE2ee: false,
77
+ },
78
+ ],
79
+ ],
80
+ ]),
81
+ );
82
+ }
83
+
84
+ // 2. Check to make sure the publication has been noted in internal state only once
85
+ expect((await manager.queryPublications()).map((p) => p.pubHandle)).to.deep.equal([
86
+ DataTrackHandle.fromNumber(5),
87
+ ]);
88
+ });
89
+ });
90
+
91
+ describe('Track subscription', () => {
92
+ it('should test data track subscribing (ok case)', async () => {
93
+ const manager = new IncomingDataTrackManager();
94
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
95
+ 'sfuUpdateSubscription',
96
+ 'trackPublished',
97
+ ]);
98
+
99
+ const senderIdentity = 'identity';
100
+ const sid = 'data track sid';
101
+ const handle = DataTrackHandle.fromNumber(5);
102
+
103
+ // 1. Make sure the data track publication is registered
104
+ await manager.receiveSfuPublicationUpdates(
105
+ new Map([[senderIdentity, [{ sid, pubHandle: handle, name: 'test', usesE2ee: false }]]]),
106
+ );
107
+ await managerEvents.waitFor('trackPublished');
108
+
109
+ // 2. Create a subscription readable stream (SFU subscription starts lazily in the background)
110
+ const [stream, sfuSubscriptionComplete] = manager.openSubscriptionStream(sid);
111
+ const reader = stream.getReader();
112
+
113
+ // 3. This subscribe request should be sent along to the SFU
114
+ const sfuUpdateSubscriptionEvent = await managerEvents.waitFor('sfuUpdateSubscription');
115
+ expect(sfuUpdateSubscriptionEvent.sid).toStrictEqual(sid);
116
+ expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(true);
117
+
118
+ // 4. Once the SFU has acknowledged the subscription, a handle is sent back representing
119
+ // the subscription
120
+ manager.receivedSfuSubscriberHandles(new Map([[handle, sid]]));
121
+
122
+ // 5. Wait for the subscription to be fully established
123
+ await sfuSubscriptionComplete;
124
+
125
+ // 6. Simulate receiving a packet
126
+ manager.packetReceived(
127
+ new DataTrackPacket(
128
+ new DataTrackPacketHeader({
129
+ extensions: new DataTrackExtensions(),
130
+ frameNumber: WrapAroundUnsignedInt.u16(0),
131
+ marker: FrameMarker.Single,
132
+ sequence: WrapAroundUnsignedInt.u16(0),
133
+ timestamp: DataTrackTimestamp.fromRtpTicks(0),
134
+ trackHandle: DataTrackHandle.fromNumber(5),
135
+ }),
136
+ new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]),
137
+ ).toBinary(),
138
+ );
139
+
140
+ // 7. Make sure that packet comes out of the ReadableStream
141
+ const { value, done } = await reader.read();
142
+ expect(done).toStrictEqual(false);
143
+ expect(value?.payload).toStrictEqual(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]));
144
+ });
145
+
146
+ it('should test data track subscribing with end to end encryption (ok case)', async () => {
147
+ const manager = new IncomingDataTrackManager({
148
+ e2eeManager: new PrefixingEncryptionProvider(),
149
+ });
150
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
151
+ 'sfuUpdateSubscription',
152
+ 'trackPublished',
153
+ ]);
154
+
155
+ const senderIdentity = 'identity';
156
+ const sid = 'data track sid';
157
+ const handle = DataTrackHandle.fromNumber(5);
158
+
159
+ // 1. Make sure the data track publication is registered
160
+ await manager.receiveSfuPublicationUpdates(
161
+ new Map([[senderIdentity, [{ sid, pubHandle: handle, name: 'test', usesE2ee: true }]]]),
162
+ );
163
+ await managerEvents.waitFor('trackPublished');
164
+
165
+ // 2. Create a subscription readable stream
166
+ const [stream, sfuSubscriptionComplete] = manager.openSubscriptionStream(sid);
167
+ const reader = stream.getReader();
168
+
169
+ // 3. This subscribe request should be sent along to the SFU
170
+ const sfuUpdateSubscriptionEvent = await managerEvents.waitFor('sfuUpdateSubscription');
171
+ expect(sfuUpdateSubscriptionEvent.sid).toStrictEqual(sid);
172
+ expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(true);
173
+
174
+ // 4. Once the SFU has acknowledged the subscription, a handle is sent back representing
175
+ // the subscription
176
+ manager.receivedSfuSubscriberHandles(new Map([[handle, sid]]));
177
+
178
+ // 5. Wait for the subscription to be fully established
179
+ await sfuSubscriptionComplete;
180
+
181
+ // 6. Simulate receiving a (fake) encrypted packet
182
+ manager.packetReceived(
183
+ new DataTrackPacket(
184
+ new DataTrackPacketHeader({
185
+ extensions: new DataTrackExtensions({
186
+ e2ee: new DataTrackE2eeExtension(0, new Uint8Array(12)),
187
+ }),
188
+ frameNumber: WrapAroundUnsignedInt.u16(0),
189
+ marker: FrameMarker.Single,
190
+ sequence: WrapAroundUnsignedInt.u16(0),
191
+ timestamp: DataTrackTimestamp.fromRtpTicks(0),
192
+ trackHandle: DataTrackHandle.fromNumber(5),
193
+ }),
194
+ new Uint8Array([
195
+ // Fake encryption bytes prefix
196
+ 0xde, 0xad, 0xbe, 0xef,
197
+ // Actual payload
198
+ 0x01, 0x02, 0x03, 0x04, 0x05,
199
+ ]),
200
+ ).toBinary(),
201
+ );
202
+
203
+ // 7. Make sure that packet comes out of the ReadableStream
204
+ const { value, done } = await reader.read();
205
+ expect(done).toStrictEqual(false);
206
+ expect(value?.payload).toStrictEqual(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]));
207
+ });
208
+
209
+ it('should fan out received events across multiple subscriptions', async () => {
210
+ const manager = new IncomingDataTrackManager();
211
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
212
+ 'sfuUpdateSubscription',
213
+ 'trackPublished',
214
+ ]);
215
+
216
+ const senderIdentity = 'identity';
217
+ const sid = 'data track sid';
218
+
219
+ const handleAllocator = new DataTrackHandleAllocator();
220
+
221
+ // 1. Make sure the data track publication is registered
222
+ const pubHandle = handleAllocator.get()!;
223
+ await manager.receiveSfuPublicationUpdates(
224
+ new Map([[senderIdentity, [{ sid, pubHandle, name: 'test', usesE2ee: false }]]]),
225
+ );
226
+ await managerEvents.waitFor('trackPublished');
227
+
228
+ // 2. Set up lots of subscribers
229
+ const readers: Array<ReadableStreamDefaultReader<DataTrackFrame>> = [];
230
+ for (let index = 0; index < 8; index += 1) {
231
+ // Create a subscription readable stream
232
+ const [stream, sfuSubscriptionComplete] = manager.openSubscriptionStream(sid);
233
+ readers.push(stream.getReader());
234
+
235
+ // Make sure that the sfu interactions ONLY happen for the first subscription opened.
236
+ if (index === 0) {
237
+ // This subscribe request should be sent along to the SFU
238
+ const sfuUpdateSubscriptionEvent = await managerEvents.waitFor('sfuUpdateSubscription');
239
+ expect(sfuUpdateSubscriptionEvent.sid).toStrictEqual(sid);
240
+ expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(true);
241
+
242
+ // Simulate the subscribe being acknowledged by the SFU
243
+ manager.receivedSfuSubscriberHandles(
244
+ new Map([[DataTrackHandle.fromNumber(1 /* publish handle */ + index), sid]]),
245
+ );
246
+ }
247
+
248
+ // 5. Wait for the subscription to be fully established
249
+ await sfuSubscriptionComplete;
250
+ }
251
+
252
+ // 6. Simulate receiving a packet
253
+ manager.packetReceived(
254
+ new DataTrackPacket(
255
+ new DataTrackPacketHeader({
256
+ extensions: new DataTrackExtensions(),
257
+ frameNumber: WrapAroundUnsignedInt.u16(0),
258
+ marker: FrameMarker.Single,
259
+ sequence: WrapAroundUnsignedInt.u16(0),
260
+ timestamp: DataTrackTimestamp.fromRtpTicks(0),
261
+ trackHandle: pubHandle,
262
+ }),
263
+ new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]),
264
+ ).toBinary(),
265
+ );
266
+
267
+ // 7. Make sure that packet comes out of all of the `ReadableStream`s
268
+ const results = await Promise.all(readers.map((reader) => reader.read()));
269
+ for (const { value, done } of results) {
270
+ expect(done).toStrictEqual(false);
271
+ expect(value?.payload).toStrictEqual(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05]));
272
+ }
273
+ });
274
+
275
+ it('should be unable to subscribe to a non existing data track', async () => {
276
+ const manager = new IncomingDataTrackManager();
277
+ await expect(manager.subscribeRequest('does not exist')).rejects.toThrowError(
278
+ 'Cannot subscribe to unknown track',
279
+ );
280
+ });
281
+
282
+ it('should terminate the sfu subscription if the abortsignal is triggered on the only subscription', async () => {
283
+ const manager = new IncomingDataTrackManager();
284
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
285
+ 'sfuUpdateSubscription',
286
+ 'trackPublished',
287
+ ]);
288
+
289
+ const sid = 'data track sid';
290
+
291
+ // 1. Make sure the data track publication is registered
292
+ await manager.receiveSfuPublicationUpdates(
293
+ new Map([
294
+ [
295
+ 'identity',
296
+ [{ sid, pubHandle: DataTrackHandle.fromNumber(5), name: 'test', usesE2ee: false }],
297
+ ],
298
+ ]),
299
+ );
300
+ await managerEvents.waitFor('trackPublished');
301
+
302
+ // 2. Subscribe to a data track
303
+ const controller = new AbortController();
304
+ const subscribeRequestPromise = manager.subscribeRequest(sid, controller.signal);
305
+ await managerEvents.waitFor('sfuUpdateSubscription');
306
+
307
+ // 3. Cancel the subscription
308
+ controller.abort();
309
+ await expect(subscribeRequestPromise).rejects.toThrowError(
310
+ 'Subscription to data track cancelled by caller',
311
+ );
312
+
313
+ // 4. Make sure the underlying sfu subscription is also terminated, since nothing needs it
314
+ // anymore.
315
+ const sfuUpdateSubscriptionEvent = await managerEvents.waitFor('sfuUpdateSubscription');
316
+ expect(sfuUpdateSubscriptionEvent.sid).toStrictEqual(sid);
317
+ expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(false);
318
+ });
319
+
320
+ it('should NOT terminate the sfu subscription if the abortsignal is triggered on one of two active subscriptions', async () => {
321
+ const manager = new IncomingDataTrackManager();
322
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
323
+ 'sfuUpdateSubscription',
324
+ 'trackPublished',
325
+ ]);
326
+
327
+ const sid = 'data track sid';
328
+
329
+ // 1. Make sure the data track publication is registered
330
+ await manager.receiveSfuPublicationUpdates(
331
+ new Map([
332
+ [
333
+ 'identity',
334
+ [{ sid, pubHandle: DataTrackHandle.fromNumber(5), name: 'test', usesE2ee: false }],
335
+ ],
336
+ ]),
337
+ );
338
+ await managerEvents.waitFor('trackPublished');
339
+
340
+ // 2. Subscribe to a data track twice
341
+ const controllerOne = new AbortController();
342
+ const subscribeRequestOnePromise = manager.subscribeRequest(sid, controllerOne.signal);
343
+ await managerEvents.waitFor('sfuUpdateSubscription'); // Subscription started
344
+
345
+ const controllerTwo = new AbortController();
346
+ manager.subscribeRequest(sid, controllerTwo.signal);
347
+
348
+ // 3. Cancel the first subscription
349
+ controllerOne.abort();
350
+ await expect(subscribeRequestOnePromise).rejects.toThrowError(
351
+ 'Subscription to data track cancelled by caller',
352
+ );
353
+
354
+ // 4. Make sure the underlying sfu subscription has not been also cancelled, there still is
355
+ // one data track subscription active
356
+ expect(managerEvents.areThereBufferedEvents('sfuUpdateSubscription')).toBe(false);
357
+ });
358
+
359
+ it('should terminate the sfu subscription if the abortsignal is already aborted', async () => {
360
+ const manager = new IncomingDataTrackManager();
361
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
362
+ 'sfuUpdateSubscription',
363
+ ]);
364
+
365
+ const sid = 'data track sid';
366
+
367
+ // Make sure the data track publication is registered
368
+ await manager.receiveSfuPublicationUpdates(
369
+ new Map([
370
+ [
371
+ 'identity',
372
+ [{ sid, pubHandle: DataTrackHandle.fromNumber(5), name: 'test', usesE2ee: false }],
373
+ ],
374
+ ]),
375
+ );
376
+
377
+ // Subscribe to a data track
378
+ const subscribeRequestPromise = manager.subscribeRequest(
379
+ sid,
380
+ AbortSignal.abort(/* already aborted */),
381
+ );
382
+ const start = await managerEvents.waitFor('sfuUpdateSubscription');
383
+ expect(start.subscribe).toBe(true);
384
+
385
+ // Make sure cancellation is immediately bubbled up
386
+ await expect(subscribeRequestPromise).rejects.toStrictEqual(
387
+ DataTrackSubscribeError.cancelled(),
388
+ );
389
+
390
+ // Make sure that there is immediately another "unsubscribe" sent
391
+ const end = await managerEvents.waitFor('sfuUpdateSubscription');
392
+ expect(end.subscribe).toBe(false);
393
+ });
394
+
395
+ it('should terminate the sfu subscription once all listeners have unsubscribed', async () => {
396
+ const manager = new IncomingDataTrackManager();
397
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
398
+ 'sfuUpdateSubscription',
399
+ 'trackPublished',
400
+ ]);
401
+
402
+ const sid = 'data track sid';
403
+
404
+ // 1. Make sure the data track publication is registered
405
+ await manager.receiveSfuPublicationUpdates(
406
+ new Map([
407
+ [
408
+ 'identity',
409
+ [{ sid, pubHandle: DataTrackHandle.fromNumber(5), name: 'test', usesE2ee: false }],
410
+ ],
411
+ ]),
412
+ );
413
+ await managerEvents.waitFor('trackPublished');
414
+
415
+ // 2. Create subscription A
416
+ const controllerA = new AbortController();
417
+ const subscribeAPromise = manager.subscribeRequest(sid, controllerA.signal);
418
+ const startEvent = await managerEvents.waitFor('sfuUpdateSubscription');
419
+ expect(startEvent.sid).toStrictEqual(sid);
420
+ expect(startEvent.subscribe).toStrictEqual(true);
421
+
422
+ // 2. Create subscription B
423
+ const controllerB = new AbortController();
424
+ const subscribeBPromise = manager.subscribeRequest(sid, controllerB.signal);
425
+ expect(managerEvents.areThereBufferedEvents('sfuUpdateSubscription')).toStrictEqual(false);
426
+
427
+ // 3. Cancel the subscription A
428
+ controllerA.abort();
429
+ expect(managerEvents.areThereBufferedEvents('sfuUpdateSubscription')).toStrictEqual(false);
430
+
431
+ // 4. Cancel the subscription B, make sure the underlying sfu subscription is disposed
432
+ controllerB.abort();
433
+ const endEvent = await managerEvents.waitFor('sfuUpdateSubscription');
434
+ expect(endEvent.sid).toStrictEqual(sid);
435
+ expect(endEvent.subscribe).toStrictEqual(false);
436
+
437
+ await expect(subscribeAPromise).rejects.toThrow();
438
+ await expect(subscribeBPromise).rejects.toThrow();
439
+ });
440
+
441
+ it('should terminate PENDING sfu subscriptions if the participant disconnects', async () => {
442
+ const manager = new IncomingDataTrackManager();
443
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
444
+ 'sfuUpdateSubscription',
445
+ 'trackPublished',
446
+ ]);
447
+
448
+ const senderIdentity = 'identity';
449
+ const sid = 'data track sid';
450
+ const handle = DataTrackHandle.fromNumber(5);
451
+
452
+ // 1. Make sure the data track publication is registered
453
+ await manager.receiveSfuPublicationUpdates(
454
+ new Map([[senderIdentity, [{ sid, pubHandle: handle, name: 'test', usesE2ee: false }]]]),
455
+ );
456
+ await managerEvents.waitFor('trackPublished');
457
+
458
+ // 2. Begin subscribing to a data track
459
+ const promise = manager.subscribeRequest(sid);
460
+
461
+ // 3. Simulate the remote participant disconnecting
462
+ manager.handleRemoteParticipantDisconnected(senderIdentity);
463
+
464
+ // 4. Make sure the pending subscribe was terminated
465
+ await expect(promise).rejects.toThrowError(
466
+ 'Cannot subscribe to data track when disconnected',
467
+ );
468
+ });
469
+
470
+ it('should terminate ACTIVE sfu subscriptions if the participant disconnects', async () => {
471
+ const manager = new IncomingDataTrackManager();
472
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
473
+ 'sfuUpdateSubscription',
474
+ 'trackPublished',
475
+ ]);
476
+
477
+ const senderIdentity = 'identity';
478
+ const sid = 'data track sid';
479
+ const handle = DataTrackHandle.fromNumber(5);
480
+
481
+ // 1. Make sure the data track publication is registered
482
+ await manager.receiveSfuPublicationUpdates(
483
+ new Map([[senderIdentity, [{ sid, pubHandle: handle, name: 'test', usesE2ee: false }]]]),
484
+ );
485
+ await managerEvents.waitFor('trackPublished');
486
+
487
+ // 2. Subscribe to a data track, and send the handle back as if the SFU acknowledged it
488
+ const [stream, sfuSubscriptionComplete] = manager.openSubscriptionStream(sid);
489
+ const reader = stream.getReader();
490
+ const sfuUpdateSubscriptionEvent = await managerEvents.waitFor('sfuUpdateSubscription');
491
+ expect(sfuUpdateSubscriptionEvent.sid).toStrictEqual(sid);
492
+ expect(sfuUpdateSubscriptionEvent.subscribe).toStrictEqual(true);
493
+ manager.receivedSfuSubscriberHandles(new Map([[handle, sid]]));
494
+
495
+ // 3. Start an active stream read for later
496
+ await sfuSubscriptionComplete;
497
+
498
+ // 4. Simulate the remote participant disconnecting
499
+ manager.handleRemoteParticipantDisconnected(senderIdentity);
500
+
501
+ // 5. Make sure the sfu unsubscribes
502
+ const endEvent = await managerEvents.waitFor('sfuUpdateSubscription');
503
+ expect(endEvent.sid).toStrictEqual(sid);
504
+ expect(endEvent.subscribe).toStrictEqual(false);
505
+
506
+ // 6. Make sure the in flight stream read was closed
507
+ await reader.closed;
508
+ });
509
+
510
+ it('should terminate the sfu subscription once all downstream ReadableStreams are cancelled', async () => {
511
+ const manager = new IncomingDataTrackManager();
512
+ const managerEvents = subscribeToEvents<DataTrackIncomingManagerCallbacks>(manager, [
513
+ 'sfuUpdateSubscription',
514
+ 'trackPublished',
515
+ ]);
516
+
517
+ const senderIdentity = 'identity';
518
+ const sid = 'data track sid';
519
+ const handle = DataTrackHandle.fromNumber(5);
520
+
521
+ // 1. Make sure the data track publication is registered
522
+ await manager.receiveSfuPublicationUpdates(
523
+ new Map([[senderIdentity, [{ sid, pubHandle: handle, name: 'test', usesE2ee: false }]]]),
524
+ );
525
+ await managerEvents.waitFor('trackPublished');
526
+
527
+ // 2. Create a subscription readable stream
528
+ const [stream, sfuSubscriptionComplete] = manager.openSubscriptionStream(sid);
529
+ const reader = stream.getReader();
530
+
531
+ // 3. This subscribe request should be sent along to the SFU
532
+ const sfuUpdateSubscriptionInitEvent = await managerEvents.waitFor('sfuUpdateSubscription');
533
+ expect(sfuUpdateSubscriptionInitEvent.sid).toStrictEqual(sid);
534
+ expect(sfuUpdateSubscriptionInitEvent.subscribe).toStrictEqual(true);
535
+
536
+ // 4. Once the SFU has acknowledged the subscription, a handle is sent back representing
537
+ // the subscription
538
+ manager.receivedSfuSubscriberHandles(new Map([[handle, sid]]));
539
+
540
+ // 5. Wait for the subscription to be fully established
541
+ await sfuSubscriptionComplete;
542
+
543
+ // 6. Manually cancel the readable stream
544
+ await reader.cancel();
545
+
546
+ // 7. Make sure the underlying SFU subscription is terminated
547
+ const sfuUpdateSubscriptionCancelEvent = await managerEvents.waitFor('sfuUpdateSubscription');
548
+ expect(sfuUpdateSubscriptionCancelEvent.sid).toStrictEqual(sid);
549
+ expect(sfuUpdateSubscriptionCancelEvent.subscribe).toStrictEqual(false);
550
+
551
+ // 8. Make sure the in flight stream is now complete
552
+ await expect(reader.read()).resolves.toStrictEqual({ value: undefined, done: true });
553
+ });
554
+ });
555
+ });