mockrtc 0.1.0 → 0.3.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/.github/workflows/ci.yml +2 -2
- package/README.md +2 -2
- package/dist/client/mockrtc-admin-request-builder.d.ts +27 -0
- package/dist/client/mockrtc-admin-request-builder.js +201 -0
- package/dist/client/mockrtc-admin-request-builder.js.map +1 -0
- package/dist/client/mockrtc-client.d.ts +12 -4
- package/dist/client/mockrtc-client.js +38 -22
- package/dist/client/mockrtc-client.js.map +1 -1
- package/dist/client/mockrtc-remote-peer.d.ts +3 -2
- package/dist/client/mockrtc-remote-peer.js.map +1 -1
- package/dist/handling/handler-builder.d.ts +16 -9
- package/dist/handling/handler-builder.js +11 -1
- package/dist/handling/handler-builder.js.map +1 -1
- package/dist/handling/handler-step-definitions.d.ts +43 -25
- package/dist/handling/handler-step-definitions.js +61 -19
- package/dist/handling/handler-step-definitions.js.map +1 -1
- package/dist/handling/handler-steps.d.ts +4 -1
- package/dist/handling/handler-steps.js +27 -17
- package/dist/handling/handler-steps.js.map +1 -1
- package/dist/main-browser.d.ts +3 -0
- package/dist/main-browser.js +6 -1
- package/dist/main-browser.js.map +1 -1
- package/dist/main.d.ts +8 -5
- package/dist/main.js +6 -1
- package/dist/main.js.map +1 -1
- package/dist/matching/matcher-definitions.d.ts +51 -0
- package/dist/matching/matcher-definitions.js +94 -0
- package/dist/matching/matcher-definitions.js.map +1 -0
- package/dist/matching/matchers.d.ts +27 -0
- package/dist/matching/matchers.js +87 -0
- package/dist/matching/matchers.js.map +1 -0
- package/dist/mockrtc-base.d.ts +16 -0
- package/dist/mockrtc-base.js +19 -0
- package/dist/mockrtc-base.js.map +1 -0
- package/dist/mockrtc-peer.d.ts +44 -6
- package/dist/mockrtc.d.ts +200 -1
- package/dist/mockrtc.js +1 -0
- package/dist/mockrtc.js.map +1 -1
- package/dist/rule-builder.d.ts +86 -0
- package/dist/rule-builder.js +113 -0
- package/dist/rule-builder.js.map +1 -0
- package/dist/server/mockrtc-admin-plugin.d.ts +2 -2
- package/dist/server/mockrtc-admin-plugin.js +165 -3
- package/dist/server/mockrtc-admin-plugin.js.map +1 -1
- package/dist/server/mockrtc-server-peer.d.ts +8 -2
- package/dist/server/mockrtc-server-peer.js +119 -6
- package/dist/server/mockrtc-server-peer.js.map +1 -1
- package/dist/server/mockrtc-server.d.ts +20 -5
- package/dist/server/mockrtc-server.js +87 -14
- package/dist/server/mockrtc-server.js.map +1 -1
- package/dist/webrtc/datachannel-stream.d.ts +4 -0
- package/dist/webrtc/datachannel-stream.js +27 -1
- package/dist/webrtc/datachannel-stream.js.map +1 -1
- package/dist/webrtc/mediatrack-stream.d.ts +6 -0
- package/dist/webrtc/mediatrack-stream.js +28 -2
- package/dist/webrtc/mediatrack-stream.js.map +1 -1
- package/dist/webrtc/mockrtc-connection.d.ts +1 -1
- package/dist/webrtc/mockrtc-connection.js +77 -60
- package/dist/webrtc/mockrtc-connection.js.map +1 -1
- package/dist/webrtc/rtc-connection.d.ts +28 -5
- package/dist/webrtc/rtc-connection.js +62 -23
- package/dist/webrtc/rtc-connection.js.map +1 -1
- package/dist/webrtc-hooks.js +10 -2
- package/dist/webrtc-hooks.js.map +1 -1
- package/package.json +12 -6
- package/src/client/mockrtc-admin-request-builder.ts +232 -0
- package/src/client/mockrtc-client.ts +50 -28
- package/src/client/mockrtc-remote-peer.ts +9 -8
- package/src/handling/handler-builder.ts +22 -10
- package/src/handling/handler-step-definitions.ts +87 -27
- package/src/handling/handler-steps.ts +34 -20
- package/src/main-browser.ts +5 -0
- package/src/main.ts +23 -5
- package/src/matching/matcher-definitions.ts +109 -0
- package/src/matching/matchers.ts +118 -0
- package/src/mockrtc-base.ts +49 -0
- package/src/mockrtc-peer.ts +48 -6
- package/src/mockrtc.ts +235 -1
- package/src/rule-builder.ts +142 -0
- package/src/server/mockrtc-admin-plugin.ts +200 -9
- package/src/server/mockrtc-server-peer.ts +194 -7
- package/src/server/mockrtc-server.ts +136 -19
- package/src/webrtc/datachannel-stream.ts +31 -1
- package/src/webrtc/mediatrack-stream.ts +31 -4
- package/src/webrtc/mockrtc-connection.ts +22 -7
- package/src/webrtc/rtc-connection.ts +111 -29
- package/src/webrtc-hooks.ts +13 -4
- package/test/integration/events.spec.ts +538 -0
- package/test/integration/matching.spec.ts +257 -0
- package/test/integration/proxy.spec.ts +68 -1
- package/test/integration/send-steps.spec.ts +25 -0
- package/test/test-setup.ts +19 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { MockRTCEventData } from '../../src/mockrtc';
|
|
7
|
+
import {
|
|
8
|
+
MockRTC,
|
|
9
|
+
expect,
|
|
10
|
+
getDeferred,
|
|
11
|
+
waitForState,
|
|
12
|
+
delay
|
|
13
|
+
} from '../test-setup';
|
|
14
|
+
|
|
15
|
+
describe("MockRTC event subscriptions", function () {
|
|
16
|
+
|
|
17
|
+
const mockRTC = MockRTC.getRemote();
|
|
18
|
+
|
|
19
|
+
beforeEach(() => mockRTC.start());
|
|
20
|
+
afterEach(() => mockRTC.stop());
|
|
21
|
+
|
|
22
|
+
describe("for connection events", function () {
|
|
23
|
+
|
|
24
|
+
it("should fire an event when a mock peer connects", async () => {
|
|
25
|
+
const eventPromise = getDeferred<MockRTCEventData['peer-connected']>();
|
|
26
|
+
|
|
27
|
+
mockRTC.on('peer-connected', (peer) => eventPromise.resolve(peer));
|
|
28
|
+
|
|
29
|
+
const mockPeer = await mockRTC.buildPeer().waitForNextMessage().thenSend('Goodbye');
|
|
30
|
+
|
|
31
|
+
const localConnection = new RTCPeerConnection();
|
|
32
|
+
|
|
33
|
+
const { offer, setAnswer } = await mockPeer.createOffer({
|
|
34
|
+
connectionMetadata: {
|
|
35
|
+
userAgent: navigator.userAgent,
|
|
36
|
+
sourceURL: 'https://example.com/'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
await localConnection.setRemoteDescription(offer);
|
|
40
|
+
|
|
41
|
+
const localAnswer = await localConnection.createAnswer();
|
|
42
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
43
|
+
await setAnswer(localAnswer);
|
|
44
|
+
|
|
45
|
+
// Wait until the connection opens successfully:
|
|
46
|
+
await waitForState(localConnection, 'connected');
|
|
47
|
+
|
|
48
|
+
const connectionEvent = await eventPromise;
|
|
49
|
+
expect(connectionEvent.peerId).to.equal(mockPeer.peerId);
|
|
50
|
+
expect(connectionEvent.sessionId).not.to.equal(undefined);
|
|
51
|
+
expect(connectionEvent.localSessionDescription.type).to.equal('offer');
|
|
52
|
+
expect(connectionEvent.localSessionDescription.sdp!.length).to.be.greaterThan(10);
|
|
53
|
+
expect(connectionEvent.remoteSessionDescription.type).to.equal('answer');
|
|
54
|
+
expect(connectionEvent.remoteSessionDescription.sdp!.length).to.be.greaterThan(10);
|
|
55
|
+
|
|
56
|
+
expect(connectionEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
57
|
+
expect(connectionEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
58
|
+
expect(connectionEvent.timingEvents.externalAttachTimestamp).to.equal(undefined);
|
|
59
|
+
expect(connectionEvent.timingEvents.disconnectTimestamp).to.equal(undefined);
|
|
60
|
+
|
|
61
|
+
expect(connectionEvent.metadata.userAgent).to.equal(navigator.userAgent);
|
|
62
|
+
expect(connectionEvent.metadata.sourceURL).to.equal('https://example.com/');
|
|
63
|
+
|
|
64
|
+
const { selectedLocalCandidate, selectedRemoteCandidate } = connectionEvent;
|
|
65
|
+
[selectedLocalCandidate, selectedRemoteCandidate].forEach((candidate) => {
|
|
66
|
+
expect(candidate.address).to.match(/[:\w\.]+/); // IPv4 or 6
|
|
67
|
+
expect(candidate.port).to.be.greaterThan(0);
|
|
68
|
+
expect(candidate.protocol).to.equal('udp');
|
|
69
|
+
expect(candidate.type).not.to.equal(undefined);
|
|
70
|
+
});
|
|
71
|
+
expect(selectedLocalCandidate.port).to.not.equal(selectedRemoteCandidate.port);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should not fire an event when an external peer connects", async () => {
|
|
75
|
+
const eventPromise = getDeferred<MockRTCEventData['peer-connected']>();
|
|
76
|
+
|
|
77
|
+
mockRTC.on('peer-connected', (peer) => eventPromise.resolve(peer));
|
|
78
|
+
|
|
79
|
+
const mockPeer = await mockRTC.buildPeer().waitForNextMessage().thenSend('Goodbye');
|
|
80
|
+
|
|
81
|
+
const localConnection = new RTCPeerConnection();
|
|
82
|
+
|
|
83
|
+
const { offer, setAnswer } = await mockPeer.createExternalOffer();
|
|
84
|
+
await localConnection.setRemoteDescription(offer);
|
|
85
|
+
|
|
86
|
+
const localAnswer = await localConnection.createAnswer();
|
|
87
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
88
|
+
await setAnswer(localAnswer);
|
|
89
|
+
|
|
90
|
+
// Wait until the connection opens successfully:
|
|
91
|
+
await waitForState(localConnection, 'connected');
|
|
92
|
+
|
|
93
|
+
const result = await Promise.race([
|
|
94
|
+
delay(500).then(() => 'timeout'),
|
|
95
|
+
eventPromise
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// No event fires within 500ms
|
|
99
|
+
expect(result).to.equal('timeout');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should fire an event when an external peer is attached", async () => {
|
|
103
|
+
const eventPromise = getDeferred<MockRTCEventData['external-peer-attached']>();
|
|
104
|
+
|
|
105
|
+
mockRTC.on('external-peer-attached', (peer) => eventPromise.resolve(peer));
|
|
106
|
+
|
|
107
|
+
const mockPeer = await mockRTC.buildPeer().waitForNextMessage().thenSend('Goodbye');
|
|
108
|
+
|
|
109
|
+
// Hook the local connection (so traffic is redirected via an external peer)
|
|
110
|
+
const localConnection = new RTCPeerConnection();
|
|
111
|
+
MockRTC.hookWebRTCConnection(localConnection, mockPeer);
|
|
112
|
+
|
|
113
|
+
// Create and connect an unhooked remote connection:
|
|
114
|
+
const remoteConn = new RTCPeerConnection();
|
|
115
|
+
remoteConn.createDataChannel("test-channel");
|
|
116
|
+
const remoteOffer = await remoteConn.createOffer();
|
|
117
|
+
remoteConn.setLocalDescription(remoteOffer);
|
|
118
|
+
await localConnection.setRemoteDescription(remoteOffer);
|
|
119
|
+
const localAnswer = await localConnection.createAnswer();
|
|
120
|
+
localConnection.setLocalDescription(localAnswer);
|
|
121
|
+
remoteConn.setRemoteDescription(localAnswer);
|
|
122
|
+
|
|
123
|
+
// Wait until the connection opens successfully:
|
|
124
|
+
await waitForState(localConnection, 'connected');
|
|
125
|
+
|
|
126
|
+
const attachEvent = await eventPromise;
|
|
127
|
+
expect(attachEvent.peerId).to.equal(mockPeer.peerId);
|
|
128
|
+
expect(attachEvent.sessionId).not.to.equal(undefined);
|
|
129
|
+
|
|
130
|
+
const { externalConnection } = attachEvent;
|
|
131
|
+
expect(externalConnection.sessionId).not.to.equal(attachEvent.sessionId);
|
|
132
|
+
expect(externalConnection.localSessionDescription.type).to.equal('answer');
|
|
133
|
+
expect(externalConnection.localSessionDescription.sdp!.length).to.be.greaterThan(10);
|
|
134
|
+
expect(externalConnection.remoteSessionDescription.type).to.equal('offer');
|
|
135
|
+
expect(externalConnection.remoteSessionDescription.sdp!.length).to.be.greaterThan(10);
|
|
136
|
+
|
|
137
|
+
expect(attachEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
138
|
+
expect(attachEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
139
|
+
expect(attachEvent.timingEvents.externalAttachTimestamp).to.be.greaterThan(0);
|
|
140
|
+
expect(attachEvent.timingEvents.disconnectTimestamp).to.equal(undefined);
|
|
141
|
+
|
|
142
|
+
const { selectedLocalCandidate, selectedRemoteCandidate } = externalConnection;
|
|
143
|
+
[selectedLocalCandidate, selectedRemoteCandidate].forEach((candidate) => {
|
|
144
|
+
expect(candidate.address).to.match(/[:\w\.]+/); // IPv4 or 6
|
|
145
|
+
expect(candidate.port).to.be.greaterThan(0);
|
|
146
|
+
expect(candidate.protocol).to.equal('udp');
|
|
147
|
+
expect(candidate.type).not.to.equal(undefined);
|
|
148
|
+
});
|
|
149
|
+
expect(selectedLocalCandidate.port).to.not.equal(selectedRemoteCandidate.port);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should fire an event when a mock peer is disconnected by MockRTC", async () => {
|
|
153
|
+
const eventPromise = getDeferred<MockRTCEventData['peer-disconnected']>();
|
|
154
|
+
|
|
155
|
+
mockRTC.on('peer-disconnected', (peer) => eventPromise.resolve(peer));
|
|
156
|
+
|
|
157
|
+
const mockPeer = await mockRTC.buildPeer().thenClose(); // MockRTC closes immediately
|
|
158
|
+
|
|
159
|
+
const localConnection = new RTCPeerConnection();
|
|
160
|
+
|
|
161
|
+
const { offer, setAnswer } = await mockPeer.createOffer();
|
|
162
|
+
await localConnection.setRemoteDescription(offer);
|
|
163
|
+
|
|
164
|
+
const localAnswer = await localConnection.createAnswer();
|
|
165
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
166
|
+
await setAnswer(localAnswer);
|
|
167
|
+
|
|
168
|
+
// Wait until the connection opens successfully:
|
|
169
|
+
await waitForState(localConnection, 'connected');
|
|
170
|
+
|
|
171
|
+
const connectionEvent = await eventPromise;
|
|
172
|
+
expect(connectionEvent.peerId).to.equal(mockPeer.peerId);
|
|
173
|
+
expect(connectionEvent.sessionId).not.to.equal(undefined);
|
|
174
|
+
|
|
175
|
+
expect(connectionEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
176
|
+
expect(connectionEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
177
|
+
expect(connectionEvent.timingEvents.externalAttachTimestamp).to.equal(undefined);
|
|
178
|
+
expect(connectionEvent.timingEvents.disconnectTimestamp).to.be.greaterThan(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should fire an event when a mock peer is disconnected by the peer", async () => {
|
|
182
|
+
const connectEventPromise = getDeferred<MockRTCEventData['peer-connected']>();
|
|
183
|
+
const disconnectEventPromise = getDeferred<MockRTCEventData['peer-disconnected']>();
|
|
184
|
+
|
|
185
|
+
mockRTC.on('peer-connected', (peer) => connectEventPromise.resolve(peer));
|
|
186
|
+
mockRTC.on('peer-disconnected', (peer) => disconnectEventPromise.resolve(peer));
|
|
187
|
+
|
|
188
|
+
const mockPeer = await mockRTC.buildPeer().thenEcho(); // Stay open indefinitely
|
|
189
|
+
|
|
190
|
+
const localConnection = new RTCPeerConnection();
|
|
191
|
+
|
|
192
|
+
const { offer, setAnswer } = await mockPeer.createOffer();
|
|
193
|
+
await localConnection.setRemoteDescription(offer);
|
|
194
|
+
|
|
195
|
+
const localAnswer = await localConnection.createAnswer();
|
|
196
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
197
|
+
await setAnswer(localAnswer);
|
|
198
|
+
|
|
199
|
+
// Wait until the connection opens successfully. We need to wait until MockRTC is fully
|
|
200
|
+
// aware - if we disconnect before full connection, there are no events at all.
|
|
201
|
+
await connectEventPromise;
|
|
202
|
+
|
|
203
|
+
localConnection.close(); // Explicitly close the local connection
|
|
204
|
+
|
|
205
|
+
const connectionEvent = await disconnectEventPromise;
|
|
206
|
+
expect(connectionEvent.peerId).to.equal(mockPeer.peerId);
|
|
207
|
+
expect(connectionEvent.sessionId).not.to.equal(undefined);
|
|
208
|
+
|
|
209
|
+
expect(connectionEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
210
|
+
expect(connectionEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
211
|
+
expect(connectionEvent.timingEvents.externalAttachTimestamp).to.equal(undefined);
|
|
212
|
+
expect(connectionEvent.timingEvents.disconnectTimestamp).to.be.greaterThan(0);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should not fire an event when an external peer disconnects", async () => {
|
|
216
|
+
const eventPromise = getDeferred<MockRTCEventData['peer-disconnected']>();
|
|
217
|
+
|
|
218
|
+
mockRTC.on('peer-disconnected', (peer) => eventPromise.resolve(peer));
|
|
219
|
+
|
|
220
|
+
const mockPeer = await mockRTC.buildPeer().thenClose();
|
|
221
|
+
|
|
222
|
+
const localConnection = new RTCPeerConnection();
|
|
223
|
+
|
|
224
|
+
const { offer, setAnswer } = await mockPeer.createExternalOffer();
|
|
225
|
+
await localConnection.setRemoteDescription(offer);
|
|
226
|
+
|
|
227
|
+
const localAnswer = await localConnection.createAnswer();
|
|
228
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
229
|
+
await setAnswer(localAnswer);
|
|
230
|
+
|
|
231
|
+
// Wait until the connection opens successfully:
|
|
232
|
+
await waitForState(localConnection, 'connected');
|
|
233
|
+
|
|
234
|
+
const result = await Promise.race([
|
|
235
|
+
delay(500).then(() => 'timeout'),
|
|
236
|
+
eventPromise
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
// No event fires within 500ms
|
|
240
|
+
expect(result).to.equal('timeout');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("for data channels", function () {
|
|
246
|
+
|
|
247
|
+
it("fires an event when a data channel is created", async () => {
|
|
248
|
+
const eventPromise = getDeferred<MockRTCEventData['data-channel-opened']>();
|
|
249
|
+
|
|
250
|
+
mockRTC.on('data-channel-opened', (channel) => eventPromise.resolve(channel));
|
|
251
|
+
|
|
252
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
253
|
+
.waitForChannel()
|
|
254
|
+
.thenSend('Test message');
|
|
255
|
+
|
|
256
|
+
const localConnection = new RTCPeerConnection();
|
|
257
|
+
localConnection.createDataChannel("test-channel", {
|
|
258
|
+
protocol: "mockrtc-protocol"
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const localOffer = await localConnection.createOffer();
|
|
262
|
+
await localConnection.setLocalDescription(localOffer);
|
|
263
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
264
|
+
await localConnection.setRemoteDescription(answer);
|
|
265
|
+
|
|
266
|
+
const channelEvent = await eventPromise;
|
|
267
|
+
expect(channelEvent.peerId).to.equal(mockPeer.peerId);
|
|
268
|
+
expect(channelEvent.sessionId).not.to.equal(undefined);
|
|
269
|
+
expect(channelEvent.channelId).to.equal(1);
|
|
270
|
+
expect(channelEvent.channelLabel).to.equal('test-channel');
|
|
271
|
+
expect(channelEvent.channelProtocol).to.equal("mockrtc-protocol");
|
|
272
|
+
|
|
273
|
+
expect(channelEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
274
|
+
expect(channelEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
275
|
+
expect(channelEvent.eventTimestamp)
|
|
276
|
+
.to.be.greaterThan(channelEvent.timingEvents.connectTimestamp);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("fires an event when a data channel message is sent", async () => {
|
|
280
|
+
const eventPromise = getDeferred<MockRTCEventData['data-channel-message-sent']>();
|
|
281
|
+
|
|
282
|
+
mockRTC.on('data-channel-message-sent', (message) => eventPromise.resolve(message));
|
|
283
|
+
|
|
284
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
285
|
+
.waitForChannel()
|
|
286
|
+
.thenSend('Test message');
|
|
287
|
+
|
|
288
|
+
const localConnection = new RTCPeerConnection();
|
|
289
|
+
localConnection.createDataChannel("test-channel");
|
|
290
|
+
|
|
291
|
+
const localOffer = await localConnection.createOffer();
|
|
292
|
+
await localConnection.setLocalDescription(localOffer);
|
|
293
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
294
|
+
await localConnection.setRemoteDescription(answer);
|
|
295
|
+
|
|
296
|
+
const messageEvent = await eventPromise;
|
|
297
|
+
expect(messageEvent.peerId).to.equal(mockPeer.peerId);
|
|
298
|
+
expect(messageEvent.sessionId).not.to.equal(undefined);
|
|
299
|
+
expect(messageEvent.channelId).to.equal(1);
|
|
300
|
+
|
|
301
|
+
expect(messageEvent.direction).to.equal('sent');
|
|
302
|
+
expect(messageEvent.isBinary).to.equal(false);
|
|
303
|
+
expect(messageEvent.content.toString()).to.equal('Test message');
|
|
304
|
+
|
|
305
|
+
expect(messageEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
306
|
+
expect(messageEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
307
|
+
expect(messageEvent.eventTimestamp)
|
|
308
|
+
.to.be.greaterThan(messageEvent.timingEvents.connectTimestamp);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("fires an event when a data channel message is received", async () => {
|
|
312
|
+
const eventPromise = getDeferred<MockRTCEventData['data-channel-message-received']>();
|
|
313
|
+
|
|
314
|
+
mockRTC.on('data-channel-message-received', (message) => eventPromise.resolve(message));
|
|
315
|
+
|
|
316
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
317
|
+
.waitForChannel()
|
|
318
|
+
.send('Outgoing message')
|
|
319
|
+
.waitForNextMessage()
|
|
320
|
+
.thenClose();
|
|
321
|
+
|
|
322
|
+
const localConnection = new RTCPeerConnection();
|
|
323
|
+
const dataChannel = localConnection.createDataChannel("test-channel");
|
|
324
|
+
|
|
325
|
+
// Send a message to MockRTC once the connection opens:
|
|
326
|
+
dataChannel.addEventListener('open', () => {
|
|
327
|
+
dataChannel.send(
|
|
328
|
+
Buffer.from('Technically binary message from client')
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const localOffer = await localConnection.createOffer();
|
|
333
|
+
await localConnection.setLocalDescription(localOffer);
|
|
334
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
335
|
+
await localConnection.setRemoteDescription(answer);
|
|
336
|
+
|
|
337
|
+
const messageEvent = await eventPromise;
|
|
338
|
+
expect(messageEvent.peerId).to.equal(mockPeer.peerId);
|
|
339
|
+
expect(messageEvent.sessionId).not.to.equal(undefined);
|
|
340
|
+
expect(messageEvent.channelId).to.equal(1);
|
|
341
|
+
|
|
342
|
+
expect(messageEvent.direction).to.equal('received');
|
|
343
|
+
expect(messageEvent.isBinary).to.equal(true);
|
|
344
|
+
expect(messageEvent.content.toString()).to.equal('Technically binary message from client');
|
|
345
|
+
|
|
346
|
+
expect(messageEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
347
|
+
expect(messageEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
348
|
+
expect(messageEvent.eventTimestamp)
|
|
349
|
+
.to.be.greaterThan(messageEvent.timingEvents.connectTimestamp);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("fires an event when a data channel is closed", async () => {
|
|
353
|
+
const eventPromise = getDeferred<MockRTCEventData['data-channel-closed']>();
|
|
354
|
+
|
|
355
|
+
mockRTC.on('data-channel-closed', (channel) => eventPromise.resolve(channel));
|
|
356
|
+
|
|
357
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
358
|
+
.waitForChannel()
|
|
359
|
+
.thenClose();
|
|
360
|
+
|
|
361
|
+
const localConnection = new RTCPeerConnection();
|
|
362
|
+
const dataChannel = localConnection.createDataChannel("test-channel");
|
|
363
|
+
|
|
364
|
+
const localOffer = await localConnection.createOffer();
|
|
365
|
+
await localConnection.setLocalDescription(localOffer);
|
|
366
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
367
|
+
await localConnection.setRemoteDescription(answer);
|
|
368
|
+
|
|
369
|
+
dataChannel.addEventListener('open', () => dataChannel.close());
|
|
370
|
+
|
|
371
|
+
const channelEvent = await eventPromise;
|
|
372
|
+
expect(channelEvent.peerId).to.equal(mockPeer.peerId);
|
|
373
|
+
expect(channelEvent.sessionId).not.to.equal(undefined);
|
|
374
|
+
expect(channelEvent.channelId).to.equal(1);
|
|
375
|
+
|
|
376
|
+
expect(channelEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
377
|
+
expect(channelEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
378
|
+
expect(channelEvent.eventTimestamp)
|
|
379
|
+
.to.be.greaterThan(channelEvent.timingEvents.connectTimestamp);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it("does not fire any events for external connection data channels", async () => {
|
|
383
|
+
const eventPromises = ([
|
|
384
|
+
'data-channel-opened',
|
|
385
|
+
'data-channel-message-sent',
|
|
386
|
+
'data-channel-message-received',
|
|
387
|
+
'data-channel-closed'
|
|
388
|
+
] as const).map((eventName) => {
|
|
389
|
+
const eventPromise = getDeferred<MockRTCEventData[typeof eventName]>();
|
|
390
|
+
mockRTC.on(eventName, (message) => eventPromise.resolve(message));
|
|
391
|
+
return eventPromise;
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
395
|
+
.waitForChannel()
|
|
396
|
+
.send('Outgoing message')
|
|
397
|
+
.waitForNextMessage()
|
|
398
|
+
.thenClose();
|
|
399
|
+
|
|
400
|
+
const localConnection = new RTCPeerConnection();
|
|
401
|
+
const dataChannel = localConnection.createDataChannel("test-channel");
|
|
402
|
+
|
|
403
|
+
// Send a message to MockRTC once the connection opens:
|
|
404
|
+
dataChannel.addEventListener('open', () => {
|
|
405
|
+
dataChannel.send(
|
|
406
|
+
Buffer.from('Technically binary message from client')
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const localOffer = await localConnection.createOffer();
|
|
411
|
+
await localConnection.setLocalDescription(localOffer);
|
|
412
|
+
const { answer } = await mockPeer.answerExternalOffer(localOffer); // <-- External
|
|
413
|
+
await localConnection.setRemoteDescription(answer);
|
|
414
|
+
|
|
415
|
+
const result = await Promise.race([
|
|
416
|
+
delay(500).then(() => 'timeout'),
|
|
417
|
+
...eventPromises
|
|
418
|
+
]);
|
|
419
|
+
|
|
420
|
+
// No event fires within 500ms
|
|
421
|
+
expect(result).to.equal('timeout');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe("for media tracks", function () {
|
|
427
|
+
|
|
428
|
+
it("fires an event when a media track is created", async () => {
|
|
429
|
+
const eventPromise = getDeferred<MockRTCEventData['media-track-opened']>();
|
|
430
|
+
|
|
431
|
+
mockRTC.on('media-track-opened', (track) => eventPromise.resolve(track));
|
|
432
|
+
|
|
433
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
434
|
+
.waitForNextMedia()
|
|
435
|
+
.thenClose();
|
|
436
|
+
|
|
437
|
+
const localConnection = new RTCPeerConnection();
|
|
438
|
+
|
|
439
|
+
const localOffer = await localConnection.createOffer({
|
|
440
|
+
offerToReceiveAudio: true
|
|
441
|
+
});
|
|
442
|
+
await localConnection.setLocalDescription(localOffer);
|
|
443
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
444
|
+
await localConnection.setRemoteDescription(answer);
|
|
445
|
+
|
|
446
|
+
const trackEvent = await eventPromise;
|
|
447
|
+
expect(trackEvent.peerId).to.equal(mockPeer.peerId);
|
|
448
|
+
expect(trackEvent.sessionId).not.to.equal(undefined);
|
|
449
|
+
expect(trackEvent.trackMid).to.equal("0");
|
|
450
|
+
expect(trackEvent.trackDirection).to.equal("SendOnly");
|
|
451
|
+
expect(trackEvent.trackType).to.equal("audio");
|
|
452
|
+
|
|
453
|
+
expect(trackEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
454
|
+
expect(trackEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
455
|
+
expect(trackEvent.eventTimestamp)
|
|
456
|
+
.to.be.greaterThan(trackEvent.timingEvents.connectTimestamp);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("fires an event when a media track is closed", async () => {
|
|
460
|
+
const eventPromise = getDeferred<MockRTCEventData['media-track-closed']>();
|
|
461
|
+
|
|
462
|
+
mockRTC.on('media-track-closed', (track) => eventPromise.resolve(track));
|
|
463
|
+
|
|
464
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
465
|
+
.thenEcho();
|
|
466
|
+
|
|
467
|
+
const localConnection = new RTCPeerConnection();
|
|
468
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
469
|
+
stream.getTracks().forEach((track) => localConnection.addTrack(track, stream));
|
|
470
|
+
|
|
471
|
+
const localOffer = await localConnection.createOffer();
|
|
472
|
+
await localConnection.setLocalDescription(localOffer);
|
|
473
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
474
|
+
await localConnection.setRemoteDescription(answer);
|
|
475
|
+
|
|
476
|
+
await waitForState(localConnection, 'connected');
|
|
477
|
+
|
|
478
|
+
// Close the connection entirely, implicitly closing the media tracks:
|
|
479
|
+
localConnection.close();
|
|
480
|
+
// (renegotiating doesn't work - browsers keep the stream but updates direction to 'inactive')
|
|
481
|
+
|
|
482
|
+
const trackEvent = await eventPromise;
|
|
483
|
+
expect(trackEvent.peerId).to.equal(mockPeer.peerId);
|
|
484
|
+
expect(trackEvent.sessionId).not.to.equal(undefined);
|
|
485
|
+
expect(trackEvent.trackMid).to.equal("0");
|
|
486
|
+
|
|
487
|
+
expect(trackEvent.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
488
|
+
expect(trackEvent.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
489
|
+
expect(trackEvent.eventTimestamp)
|
|
490
|
+
.to.be.greaterThan(trackEvent.timingEvents.connectTimestamp);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("should fire media stats events whilst the connection is open", async function () {
|
|
494
|
+
this.timeout(5000);
|
|
495
|
+
|
|
496
|
+
const receivedStats: Array<MockRTCEventData['media-track-stats']> = [];
|
|
497
|
+
mockRTC.on('media-track-stats', (stats) => receivedStats.push(stats));
|
|
498
|
+
|
|
499
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
500
|
+
.sleep(10000)
|
|
501
|
+
.thenClose();
|
|
502
|
+
|
|
503
|
+
const localConnection = new RTCPeerConnection();
|
|
504
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
505
|
+
stream.getTracks().forEach((track) => localConnection.addTrack(track, stream));
|
|
506
|
+
|
|
507
|
+
const localOffer = await localConnection.createOffer();
|
|
508
|
+
await localConnection.setLocalDescription(localOffer);
|
|
509
|
+
const { answer, session } = await mockPeer.answerOffer(await localOffer);
|
|
510
|
+
await localConnection.setRemoteDescription(answer);
|
|
511
|
+
|
|
512
|
+
await waitForState(localConnection, 'connected');
|
|
513
|
+
await delay(2500); // Stats fires every 1s
|
|
514
|
+
|
|
515
|
+
expect(receivedStats.length).to.be.greaterThanOrEqual(2);
|
|
516
|
+
receivedStats.forEach((stats) => {
|
|
517
|
+
expect(stats.peerId).to.equal(mockPeer.peerId);
|
|
518
|
+
expect(stats.sessionId).to.equal(session.sessionId);
|
|
519
|
+
expect(stats.trackMid).to.equal("0");
|
|
520
|
+
|
|
521
|
+
expect(stats.timingEvents.startTime).to.be.lessThanOrEqual(Date.now());
|
|
522
|
+
expect(stats.timingEvents.connectTimestamp).to.be.greaterThan(0);
|
|
523
|
+
expect(stats.eventTimestamp)
|
|
524
|
+
.to.be.greaterThan(stats.timingEvents.connectTimestamp);
|
|
525
|
+
});
|
|
526
|
+
const [firstStats, secondStats] = receivedStats;
|
|
527
|
+
|
|
528
|
+
expect(firstStats.totalBytesReceived).to.be.greaterThan(1);
|
|
529
|
+
expect(firstStats.totalBytesSent).to.equal(0);
|
|
530
|
+
|
|
531
|
+
expect(secondStats.totalBytesReceived).to.be.greaterThan(firstStats.totalBytesReceived);
|
|
532
|
+
expect(secondStats.totalBytesSent).to.equal(0);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
});
|