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