mockrtc 0.1.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 +29 -0
- package/LICENSE +201 -0
- package/README.md +290 -0
- package/dist/admin-bin.d.ts +2 -0
- package/dist/admin-bin.js +67 -0
- package/dist/admin-bin.js.map +1 -0
- package/dist/client/mockrtc-client.d.ts +12 -0
- package/dist/client/mockrtc-client.js +67 -0
- package/dist/client/mockrtc-client.js.map +1 -0
- package/dist/client/mockrtc-remote-peer.d.ts +15 -0
- package/dist/client/mockrtc-remote-peer.js +246 -0
- package/dist/client/mockrtc-remote-peer.js.map +1 -0
- package/dist/control-channel.d.ts +8 -0
- package/dist/control-channel.js +11 -0
- package/dist/control-channel.js.map +1 -0
- package/dist/handling/handler-builder.d.ts +138 -0
- package/dist/handling/handler-builder.js +164 -0
- package/dist/handling/handler-builder.js.map +1 -0
- package/dist/handling/handler-step-definitions.d.ts +63 -0
- package/dist/handling/handler-step-definitions.js +123 -0
- package/dist/handling/handler-step-definitions.js.map +1 -0
- package/dist/handling/handler-steps.d.ts +48 -0
- package/dist/handling/handler-steps.js +218 -0
- package/dist/handling/handler-steps.js.map +1 -0
- package/dist/main-browser.d.ts +9 -0
- package/dist/main-browser.js +26 -0
- package/dist/main-browser.js.map +1 -0
- package/dist/main.d.ts +58 -0
- package/dist/main.js +67 -0
- package/dist/main.js.map +1 -0
- package/dist/mockrtc-admin-plugin.d.ts +56 -0
- package/dist/mockrtc-admin-plugin.js +151 -0
- package/dist/mockrtc-admin-plugin.js.map +1 -0
- package/dist/mockrtc-admin-server.d.ts +7 -0
- package/dist/mockrtc-admin-server.js +18 -0
- package/dist/mockrtc-admin-server.js.map +1 -0
- package/dist/mockrtc-client.d.ts +12 -0
- package/dist/mockrtc-client.js +64 -0
- package/dist/mockrtc-client.js.map +1 -0
- package/dist/mockrtc-handler-builder.d.ts +15 -0
- package/dist/mockrtc-handler-builder.js +24 -0
- package/dist/mockrtc-handler-builder.js.map +1 -0
- package/dist/mockrtc-peer.d.ts +147 -0
- package/dist/mockrtc-peer.js +7 -0
- package/dist/mockrtc-peer.js.map +1 -0
- package/dist/mockrtc-remote-peer.d.ts +15 -0
- package/dist/mockrtc-remote-peer.js +234 -0
- package/dist/mockrtc-remote-peer.js.map +1 -0
- package/dist/mockrtc-server-peer.d.ts +29 -0
- package/dist/mockrtc-server-peer.js +145 -0
- package/dist/mockrtc-server-peer.js.map +1 -0
- package/dist/mockrtc-server.d.ts +14 -0
- package/dist/mockrtc-server.js +53 -0
- package/dist/mockrtc-server.js.map +1 -0
- package/dist/mockrtc.d.ts +25 -0
- package/dist/mockrtc.js +7 -0
- package/dist/mockrtc.js.map +1 -0
- package/dist/package.json +52 -0
- package/dist/server/mockrtc-admin-plugin.d.ts +17 -0
- package/dist/server/mockrtc-admin-plugin.js +163 -0
- package/dist/server/mockrtc-admin-plugin.js.map +1 -0
- package/dist/server/mockrtc-admin-server.d.ts +7 -0
- package/dist/server/mockrtc-admin-server.js +18 -0
- package/dist/server/mockrtc-admin-server.js.map +1 -0
- package/dist/server/mockrtc-server-peer.d.ts +24 -0
- package/dist/server/mockrtc-server-peer.js +141 -0
- package/dist/server/mockrtc-server-peer.js.map +1 -0
- package/dist/server/mockrtc-server.d.ts +14 -0
- package/dist/server/mockrtc-server.js +53 -0
- package/dist/server/mockrtc-server.js.map +1 -0
- package/dist/src/main.d.ts +1 -0
- package/dist/src/main.js +24 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/mockrtc-peer.d.ts +0 -0
- package/dist/src/mockrtc-peer.js +2 -0
- package/dist/src/mockrtc-peer.js.map +1 -0
- package/dist/src/mockrtc.d.ts +0 -0
- package/dist/src/mockrtc.js +65 -0
- package/dist/src/mockrtc.js.map +1 -0
- package/dist/webrtc/control-channel.d.ts +8 -0
- package/dist/webrtc/control-channel.js +11 -0
- package/dist/webrtc/control-channel.js.map +1 -0
- package/dist/webrtc/datachannel-stream.d.ts +25 -0
- package/dist/webrtc/datachannel-stream.js +86 -0
- package/dist/webrtc/datachannel-stream.js.map +1 -0
- package/dist/webrtc/mediatrack-stream.d.ts +29 -0
- package/dist/webrtc/mediatrack-stream.js +109 -0
- package/dist/webrtc/mediatrack-stream.js.map +1 -0
- package/dist/webrtc/mockrtc-connection.d.ts +14 -0
- package/dist/webrtc/mockrtc-connection.js +147 -0
- package/dist/webrtc/mockrtc-connection.js.map +1 -0
- package/dist/webrtc/peer-connection.d.ts +16 -0
- package/dist/webrtc/peer-connection.js +81 -0
- package/dist/webrtc/peer-connection.js.map +1 -0
- package/dist/webrtc/rtc-connection.d.ts +47 -0
- package/dist/webrtc/rtc-connection.js +370 -0
- package/dist/webrtc/rtc-connection.js.map +1 -0
- package/dist/webrtc-hooks.d.ts +30 -0
- package/dist/webrtc-hooks.js +224 -0
- package/dist/webrtc-hooks.js.map +1 -0
- package/karma.conf.ts +89 -0
- package/ngi-eu-footer.png +0 -0
- package/package.json +86 -0
- package/src/admin-bin.ts +57 -0
- package/src/client/mockrtc-client.ts +79 -0
- package/src/client/mockrtc-remote-peer.ts +286 -0
- package/src/handling/handler-builder.ts +215 -0
- package/src/handling/handler-step-definitions.ts +142 -0
- package/src/handling/handler-steps.ts +254 -0
- package/src/main-browser.ts +44 -0
- package/src/main.ts +109 -0
- package/src/mockrtc-peer.ts +176 -0
- package/src/mockrtc.ts +36 -0
- package/src/server/mockrtc-admin-plugin.ts +196 -0
- package/src/server/mockrtc-admin-server.ts +17 -0
- package/src/server/mockrtc-server-peer.ts +159 -0
- package/src/server/mockrtc-server.ts +53 -0
- package/src/webrtc/control-channel.ts +13 -0
- package/src/webrtc/datachannel-stream.ts +102 -0
- package/src/webrtc/mediatrack-stream.ts +135 -0
- package/src/webrtc/mockrtc-connection.ts +164 -0
- package/src/webrtc/rtc-connection.ts +420 -0
- package/src/webrtc-hooks.ts +245 -0
- package/test/integration/close-steps.spec.ts +39 -0
- package/test/integration/connection-setup.spec.ts +230 -0
- package/test/integration/echo-steps.spec.ts +88 -0
- package/test/integration/proxy.spec.ts +526 -0
- package/test/integration/send-steps.spec.ts +76 -0
- package/test/integration/smoke-test.spec.ts +100 -0
- package/test/integration/wait-steps.spec.ts +225 -0
- package/test/start-test-admin-server.ts +12 -0
- package/test/test-setup.ts +136 -0
- package/test/tsconfig.json +11 -0
- package/tsconfig.json +14 -0
- package/typedoc.json +19 -0
- package/wallaby.js +41 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MockRTC,
|
|
8
|
+
expect,
|
|
9
|
+
delay
|
|
10
|
+
} from '../test-setup';
|
|
11
|
+
|
|
12
|
+
describe("MockRTC smoke test:", function () {
|
|
13
|
+
|
|
14
|
+
const mockRTC = MockRTC.getRemote({ recordMessages: true });
|
|
15
|
+
|
|
16
|
+
beforeEach(() => mockRTC.start());
|
|
17
|
+
afterEach(() => mockRTC.stop());
|
|
18
|
+
|
|
19
|
+
it("should pass the README example test", async () => {
|
|
20
|
+
// Create a mock peer who sends 'Goodbye' after receiving its first message.
|
|
21
|
+
const mockPeer = await mockRTC
|
|
22
|
+
.buildPeer()
|
|
23
|
+
.waitForNextMessage()
|
|
24
|
+
.thenSend('Goodbye');
|
|
25
|
+
|
|
26
|
+
// Create a data connection:
|
|
27
|
+
const localConnection = new RTCPeerConnection();
|
|
28
|
+
const dataChannel = localConnection.createDataChannel("dataChannel");
|
|
29
|
+
|
|
30
|
+
const localOffer = await localConnection.createOffer();
|
|
31
|
+
await localConnection.setLocalDescription(localOffer);
|
|
32
|
+
|
|
33
|
+
// Get the remote details for the mock peer:
|
|
34
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
35
|
+
await localConnection.setRemoteDescription(answer);
|
|
36
|
+
|
|
37
|
+
// Once the connection is open, message the peer
|
|
38
|
+
dataChannel.onopen = () => {
|
|
39
|
+
dataChannel.send('Hello');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Wait for a response:
|
|
43
|
+
const message = await new Promise((resolve) => {
|
|
44
|
+
dataChannel.addEventListener('message', (event) => resolve(event.data));
|
|
45
|
+
});
|
|
46
|
+
expect(message).to.equal('Goodbye'); // <-- We get our mock response!
|
|
47
|
+
|
|
48
|
+
// Assert on the messages the mock peer received:
|
|
49
|
+
expect(await mockPeer.getAllMessages()).to.deep.equal(['Hello']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should pass the README proxy example", async () => {
|
|
53
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
54
|
+
.waitForNextMessage() // Wait for and drop the first datachannel message
|
|
55
|
+
.send('MockRTC injected message') // Send a message on every data channel
|
|
56
|
+
.thenPassThrough(); // Then proxy everything else
|
|
57
|
+
|
|
58
|
+
const localConn = new RTCPeerConnection();
|
|
59
|
+
|
|
60
|
+
// The magic:
|
|
61
|
+
MockRTC.hookWebRTCConnection(localConn, mockPeer);
|
|
62
|
+
// ^ This redirects all connA's traffic via the mock peer, no matter who it connects to.
|
|
63
|
+
|
|
64
|
+
// Normal WebRTC setup using real browser connections:
|
|
65
|
+
const localOffer = await localConn.createOffer();
|
|
66
|
+
const localDataChannel = localConn.createDataChannel("dataChannel");
|
|
67
|
+
localConn.setLocalDescription(localOffer);
|
|
68
|
+
|
|
69
|
+
const remoteConn = new RTCPeerConnection();
|
|
70
|
+
remoteConn.setRemoteDescription(localOffer);
|
|
71
|
+
const remoteAnswer = await remoteConn.createAnswer();
|
|
72
|
+
remoteConn.setLocalDescription(remoteAnswer);
|
|
73
|
+
localConn.setRemoteDescription(remoteAnswer);
|
|
74
|
+
|
|
75
|
+
const log: string[] = [];
|
|
76
|
+
|
|
77
|
+
localDataChannel.onopen = () => {
|
|
78
|
+
localDataChannel.addEventListener('message', ({ data }) => log.push(`LOCAL: ${data}`));
|
|
79
|
+
localDataChannel.send('local message 1');
|
|
80
|
+
localDataChannel.send('local message 2');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
remoteConn.addEventListener('datachannel', async ({ channel }) => {
|
|
84
|
+
channel.addEventListener('message', ({ data }) => log.push(`REMOTE: ${data}`));
|
|
85
|
+
await delay(10); // Delay to guarantee ordering - skipped in README but that's OK
|
|
86
|
+
channel.send("remote message 1");
|
|
87
|
+
channel.send("remote message 2");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await delay(200);
|
|
91
|
+
|
|
92
|
+
expect(log).to.deep.equal([
|
|
93
|
+
'LOCAL: MockRTC injected message',
|
|
94
|
+
'REMOTE: local message 2',
|
|
95
|
+
'LOCAL: remote message 1',
|
|
96
|
+
'LOCAL: remote message 2'
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
MockRTC,
|
|
8
|
+
expect,
|
|
9
|
+
waitForState,
|
|
10
|
+
delay,
|
|
11
|
+
waitForChannelOpen
|
|
12
|
+
} from '../test-setup';
|
|
13
|
+
|
|
14
|
+
describe("Wait steps", function () {
|
|
15
|
+
|
|
16
|
+
const mockRTC = MockRTC.getRemote();
|
|
17
|
+
|
|
18
|
+
beforeEach(() => mockRTC.start());
|
|
19
|
+
afterEach(() => mockRTC.stop());
|
|
20
|
+
|
|
21
|
+
it("should be able to wait for a duration before a step", async () => {
|
|
22
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
23
|
+
.sleep(400)
|
|
24
|
+
.thenSend('delayed message');
|
|
25
|
+
|
|
26
|
+
const localConnection = new RTCPeerConnection();
|
|
27
|
+
|
|
28
|
+
const receivedMessages: string[] = [];
|
|
29
|
+
const testChannel = localConnection.createDataChannel('data-channel');
|
|
30
|
+
testChannel.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
31
|
+
|
|
32
|
+
const localOffer = await localConnection.createOffer();
|
|
33
|
+
await localConnection.setLocalDescription(localOffer);
|
|
34
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
35
|
+
await localConnection.setRemoteDescription(answer);
|
|
36
|
+
|
|
37
|
+
await waitForState(localConnection, 'connected');
|
|
38
|
+
|
|
39
|
+
await delay(200);
|
|
40
|
+
expect(receivedMessages).to.deep.equal([]);
|
|
41
|
+
|
|
42
|
+
await delay(300);
|
|
43
|
+
expect(receivedMessages).to.deep.equal(['delayed message']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should be able to wait the existence of a channel", async () => {
|
|
47
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
48
|
+
.waitForChannel()
|
|
49
|
+
.thenSend('delayed message');
|
|
50
|
+
|
|
51
|
+
const localConnection = new RTCPeerConnection();
|
|
52
|
+
|
|
53
|
+
const { offer, setAnswer } = await mockPeer.createOffer();
|
|
54
|
+
await localConnection.setRemoteDescription(offer);
|
|
55
|
+
const localAnswer = await localConnection.createAnswer();
|
|
56
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
57
|
+
await setAnswer(localAnswer);
|
|
58
|
+
|
|
59
|
+
await waitForState(localConnection, 'connected');
|
|
60
|
+
await delay(100);
|
|
61
|
+
|
|
62
|
+
const receivedMessages: string[] = [];
|
|
63
|
+
const testChannel = localConnection.createDataChannel('data-channel');
|
|
64
|
+
testChannel.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
65
|
+
|
|
66
|
+
await delay(100);
|
|
67
|
+
expect(receivedMessages).to.deep.equal(['delayed message']);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should be able to wait for the existence of a specific named channel", async () => {
|
|
71
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
72
|
+
.waitForChannel('message-channel')
|
|
73
|
+
.thenSend('delayed message');
|
|
74
|
+
|
|
75
|
+
const localConnection = new RTCPeerConnection();
|
|
76
|
+
|
|
77
|
+
const { offer, setAnswer } = await mockPeer.createOffer();
|
|
78
|
+
await localConnection.setRemoteDescription(offer);
|
|
79
|
+
const localAnswer = await localConnection.createAnswer();
|
|
80
|
+
await localConnection.setLocalDescription(localAnswer);
|
|
81
|
+
await setAnswer(localAnswer);
|
|
82
|
+
|
|
83
|
+
await waitForState(localConnection, 'connected');
|
|
84
|
+
await delay(100);
|
|
85
|
+
|
|
86
|
+
const receivedIgnoredChannelMessages: string[] = [];
|
|
87
|
+
const ignoredChannel = localConnection.createDataChannel('ignored-channel');
|
|
88
|
+
ignoredChannel.addEventListener('message', ({ data }) => { receivedIgnoredChannelMessages.push(data) });
|
|
89
|
+
|
|
90
|
+
await delay(100);
|
|
91
|
+
expect(receivedIgnoredChannelMessages).to.deep.equal([]);
|
|
92
|
+
|
|
93
|
+
const receivedRealChannelMessages: string[] = [];
|
|
94
|
+
const testChannel = localConnection.createDataChannel('message-channel');
|
|
95
|
+
testChannel.addEventListener('message', ({ data }) => { receivedRealChannelMessages.push(data) });
|
|
96
|
+
|
|
97
|
+
await delay(100);
|
|
98
|
+
expect(receivedRealChannelMessages).to.deep.equal(['delayed message']);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should be able to wait for the addition of a media track", async () => {
|
|
102
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
103
|
+
.waitForTrack()
|
|
104
|
+
.thenSend('after-track message');
|
|
105
|
+
|
|
106
|
+
const localConnection = new RTCPeerConnection();
|
|
107
|
+
|
|
108
|
+
const receivedMessages: string[] = [];
|
|
109
|
+
localConnection.createDataChannel('message-channel')
|
|
110
|
+
.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
111
|
+
|
|
112
|
+
const localOffer = await localConnection.createOffer();
|
|
113
|
+
await localConnection.setLocalDescription(localOffer);
|
|
114
|
+
const { answer, session } = await mockPeer.answerOffer(await localOffer);
|
|
115
|
+
await localConnection.setRemoteDescription(answer);
|
|
116
|
+
|
|
117
|
+
await waitForState(localConnection, 'connected');
|
|
118
|
+
await delay(100);
|
|
119
|
+
expect(receivedMessages).to.deep.equal([]);
|
|
120
|
+
|
|
121
|
+
// Add (listen only) media tracks and renegotiate:
|
|
122
|
+
const updatedOffer = await localConnection.createOffer({
|
|
123
|
+
offerToReceiveAudio: true,
|
|
124
|
+
offerToReceiveVideo: true
|
|
125
|
+
});
|
|
126
|
+
localConnection.setLocalDescription(updatedOffer);
|
|
127
|
+
const updatedAnswer = await session.answerOffer(updatedOffer);
|
|
128
|
+
await localConnection.setRemoteDescription(updatedAnswer);
|
|
129
|
+
|
|
130
|
+
await delay(100);
|
|
131
|
+
expect(receivedMessages).to.deep.equal(['after-track message']);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should be able to wait for media data", async () => {
|
|
135
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
136
|
+
.waitForNextMedia()
|
|
137
|
+
.thenSend('after-track message');
|
|
138
|
+
|
|
139
|
+
const localConnection = new RTCPeerConnection();
|
|
140
|
+
|
|
141
|
+
const receivedMessages: string[] = [];
|
|
142
|
+
localConnection.createDataChannel('message-channel')
|
|
143
|
+
.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
144
|
+
|
|
145
|
+
const localOffer = await localConnection.createOffer({
|
|
146
|
+
offerToReceiveAudio: true,
|
|
147
|
+
offerToReceiveVideo: true
|
|
148
|
+
});
|
|
149
|
+
await localConnection.setLocalDescription(localOffer);
|
|
150
|
+
const { answer, session } = await mockPeer.answerOffer(await localOffer);
|
|
151
|
+
await localConnection.setRemoteDescription(answer);
|
|
152
|
+
|
|
153
|
+
await waitForState(localConnection, 'connected');
|
|
154
|
+
await delay(100);
|
|
155
|
+
expect(receivedMessages).to.deep.equal([]);
|
|
156
|
+
|
|
157
|
+
// Add media tracks and renegotiate:
|
|
158
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
|
|
159
|
+
stream.getTracks().forEach((track) => localConnection.addTrack(track, stream));
|
|
160
|
+
|
|
161
|
+
const updatedOffer = await localConnection.createOffer();
|
|
162
|
+
localConnection.setLocalDescription(updatedOffer);
|
|
163
|
+
const updatedAnswer = await session.answerOffer(updatedOffer);
|
|
164
|
+
await localConnection.setRemoteDescription(updatedAnswer);
|
|
165
|
+
|
|
166
|
+
await delay(1000);
|
|
167
|
+
expect(receivedMessages).to.deep.equal(['after-track message']);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should be able to wait for a message on any channel", async () => {
|
|
171
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
172
|
+
.waitForNextMessage()
|
|
173
|
+
.thenSend('delayed message');
|
|
174
|
+
|
|
175
|
+
const localConnection = new RTCPeerConnection();
|
|
176
|
+
|
|
177
|
+
const receivedMessages: string[] = [];
|
|
178
|
+
const testChannel = localConnection.createDataChannel('data-channel');
|
|
179
|
+
testChannel.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
180
|
+
|
|
181
|
+
const localOffer = await localConnection.createOffer();
|
|
182
|
+
await localConnection.setLocalDescription(localOffer);
|
|
183
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
184
|
+
await localConnection.setRemoteDescription(answer);
|
|
185
|
+
|
|
186
|
+
await waitForState(localConnection, 'connected');
|
|
187
|
+
|
|
188
|
+
await delay(100);
|
|
189
|
+
expect(receivedMessages).to.deep.equal([]);
|
|
190
|
+
|
|
191
|
+
testChannel.send('test message');
|
|
192
|
+
await delay(100);
|
|
193
|
+
expect(receivedMessages).to.deep.equal(['delayed message']);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should be able to wait for a message on a specific named channel", async () => {
|
|
197
|
+
const mockPeer = await mockRTC.buildPeer()
|
|
198
|
+
.waitForNextMessageOnChannel("message-channel")
|
|
199
|
+
.thenSend('delayed message');
|
|
200
|
+
|
|
201
|
+
const localConnection = new RTCPeerConnection();
|
|
202
|
+
|
|
203
|
+
const receivedMessages: string[] = [];
|
|
204
|
+
const ignoredChannel = localConnection.createDataChannel('ignored-channel');
|
|
205
|
+
const messageChannel = localConnection.createDataChannel('message-channel');
|
|
206
|
+
messageChannel.addEventListener('message', ({ data }) => { receivedMessages.push(data) });
|
|
207
|
+
|
|
208
|
+
const localOffer = await localConnection.createOffer();
|
|
209
|
+
await localConnection.setLocalDescription(localOffer);
|
|
210
|
+
const { answer } = await mockPeer.answerOffer(localOffer);
|
|
211
|
+
await localConnection.setRemoteDescription(answer);
|
|
212
|
+
|
|
213
|
+
await waitForState(localConnection, 'connected');
|
|
214
|
+
|
|
215
|
+
await waitForChannelOpen(ignoredChannel);
|
|
216
|
+
ignoredChannel.send('test message');
|
|
217
|
+
await delay(100);
|
|
218
|
+
expect(receivedMessages).to.deep.equal([]);
|
|
219
|
+
|
|
220
|
+
messageChannel.send('test message');
|
|
221
|
+
await delay(100);
|
|
222
|
+
expect(receivedMessages).to.deep.equal(['delayed message']);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const mockrtc = require('../src/main');
|
|
7
|
+
mockrtc.getAdminServer().start().then(() => {
|
|
8
|
+
console.log("Test admin server started");
|
|
9
|
+
}).catch((error: any) => {
|
|
10
|
+
console.error(error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { expect } from "chai";
|
|
7
|
+
|
|
8
|
+
import * as MockRTC from "../src/main-browser";
|
|
9
|
+
|
|
10
|
+
export { MockRTC };
|
|
11
|
+
|
|
12
|
+
// When running continuous tests, where the admin server restarts, we have a race condition
|
|
13
|
+
// between server restart & test run. To fix that, here we wait 10s for the admin server to
|
|
14
|
+
// become accessible before we run any tests.
|
|
15
|
+
before(async function () {
|
|
16
|
+
this.timeout(10000);
|
|
17
|
+
let shownMessage = false;
|
|
18
|
+
|
|
19
|
+
while (true) {
|
|
20
|
+
try {
|
|
21
|
+
const server = MockRTC.getRemote();
|
|
22
|
+
await server.start();
|
|
23
|
+
await server.stop();
|
|
24
|
+
break;
|
|
25
|
+
} catch (e: any) {
|
|
26
|
+
if (!shownMessage) {
|
|
27
|
+
shownMessage = true;
|
|
28
|
+
if (!(e instanceof TypeError)) {
|
|
29
|
+
console.log("Could not connect to admin server");
|
|
30
|
+
throw e;
|
|
31
|
+
} else {
|
|
32
|
+
console.log("Waiting for admin server to start...");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export async function waitForState(connection: RTCPeerConnection, state: RTCPeerConnectionState) {
|
|
40
|
+
await new Promise<void>((resolve) => {
|
|
41
|
+
if (connection.connectionState === state) resolve();
|
|
42
|
+
else {
|
|
43
|
+
connection.addEventListener('connectionstatechange', () => {
|
|
44
|
+
if (connection.connectionState === state) resolve();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function waitForChannelOpen(channel: RTCDataChannel) {
|
|
51
|
+
await new Promise<void>((resolve) => {
|
|
52
|
+
if (channel.readyState === 'open') resolve();
|
|
53
|
+
else {
|
|
54
|
+
channel.addEventListener('open', () => resolve());
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function waitForChannelClose(channel: RTCDataChannel) {
|
|
60
|
+
await new Promise<void>((resolve) => {
|
|
61
|
+
if (channel.readyState === 'closed') resolve();
|
|
62
|
+
else {
|
|
63
|
+
channel.addEventListener('close', () => resolve());
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function delay(durationMs: number) {
|
|
69
|
+
return new Promise<void>((resolve) => {
|
|
70
|
+
setTimeout(resolve, durationMs);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Connect a peer & signalling channel to connect with no specific direction specified, using
|
|
75
|
+
// the official example code from the spec:
|
|
76
|
+
export function setupPerfectNegotiation(
|
|
77
|
+
peer: RTCPeerConnection,
|
|
78
|
+
polite: boolean,
|
|
79
|
+
signaler: { send: (msg: any) => void, onmessage: (msg: any) => void }
|
|
80
|
+
) {
|
|
81
|
+
// Example almost verbatim from https://w3c.github.io/webrtc-pc/#perfect-negotiation-example
|
|
82
|
+
|
|
83
|
+
// keep track of some negotiation state to prevent races and errors
|
|
84
|
+
let makingOffer = false;
|
|
85
|
+
let ignoreOffer = false;
|
|
86
|
+
let isSettingRemoteAnswerPending = false;
|
|
87
|
+
|
|
88
|
+
// send any ice candidates to the other peer
|
|
89
|
+
peer.onicecandidate = ({candidate}) => signaler.send({candidate});
|
|
90
|
+
|
|
91
|
+
// let the "negotiationneeded" event trigger offer generation
|
|
92
|
+
peer.onnegotiationneeded = async () => {
|
|
93
|
+
try {
|
|
94
|
+
makingOffer = true;
|
|
95
|
+
await peer.setLocalDescription();
|
|
96
|
+
signaler.send({description: peer.localDescription});
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error('onnegotiationneeded', err);
|
|
99
|
+
} finally {
|
|
100
|
+
makingOffer = false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
signaler.onmessage = async ({description, candidate}) => {
|
|
105
|
+
try {
|
|
106
|
+
if (description) {
|
|
107
|
+
// An offer may come in while we are busy processing SRD(answer).
|
|
108
|
+
// In this case, we will be in "stable" by the time the offer is processed
|
|
109
|
+
// so it is safe to chain it on our Operations Chain now.
|
|
110
|
+
const readyForOffer = !makingOffer &&
|
|
111
|
+
(peer.signalingState == "stable" || isSettingRemoteAnswerPending);
|
|
112
|
+
const offerCollision = description.type == "offer" && !readyForOffer;
|
|
113
|
+
|
|
114
|
+
ignoreOffer = !polite && offerCollision;
|
|
115
|
+
if (ignoreOffer) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
isSettingRemoteAnswerPending = description.type == "answer";
|
|
119
|
+
await peer.setRemoteDescription(description); // SRD rolls back as needed
|
|
120
|
+
isSettingRemoteAnswerPending = false;
|
|
121
|
+
if (description.type == "offer") {
|
|
122
|
+
await peer.setLocalDescription();
|
|
123
|
+
signaler.send({description: peer.localDescription});
|
|
124
|
+
}
|
|
125
|
+
} else if (candidate) {
|
|
126
|
+
try {
|
|
127
|
+
await peer.addIceCandidate(candidate);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
if (!ignoreOffer) throw err; // Suppress ignored offer's candidates
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error('onmessage error', err);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
package/tsconfig.json
ADDED
package/typedoc.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "MockRTC API Reference",
|
|
3
|
+
"categorizeByGroup": false,
|
|
4
|
+
"categoryOrder": [
|
|
5
|
+
"API",
|
|
6
|
+
"Steps",
|
|
7
|
+
"Final Steps",
|
|
8
|
+
"*"
|
|
9
|
+
],
|
|
10
|
+
"readme": "none",
|
|
11
|
+
"validation": {
|
|
12
|
+
"invalidLink": true
|
|
13
|
+
},
|
|
14
|
+
"treatWarningsAsErrors": true,
|
|
15
|
+
"excludePrivate": true,
|
|
16
|
+
"excludeProtected": true,
|
|
17
|
+
"excludeInternal": true,
|
|
18
|
+
"out": "typedoc/"
|
|
19
|
+
}
|
package/wallaby.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = (wallaby) => {
|
|
7
|
+
return {
|
|
8
|
+
files: [
|
|
9
|
+
'package.json',
|
|
10
|
+
'src/**/*.ts',
|
|
11
|
+
'test/**/*.ts',
|
|
12
|
+
'!test/**/*.spec.ts'
|
|
13
|
+
],
|
|
14
|
+
tests: [
|
|
15
|
+
'test/**/*.spec.ts'
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
preprocessors: {
|
|
19
|
+
// Package.json points `main` to the built output. We use this a lot in the integration tests, but we
|
|
20
|
+
// want wallaby to run on raw source. This is a simple remap of paths to lets us do that.
|
|
21
|
+
'test/integration/**/*.ts': file => {
|
|
22
|
+
return file.content.replace(
|
|
23
|
+
/("|')..((\/..)+)("|')/g,
|
|
24
|
+
'"..$2/src/main"'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
workers: {
|
|
30
|
+
initial: 1,
|
|
31
|
+
regular: 1,
|
|
32
|
+
restart: true
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
testFramework: 'mocha',
|
|
36
|
+
env: {
|
|
37
|
+
type: 'node'
|
|
38
|
+
},
|
|
39
|
+
debug: true
|
|
40
|
+
};
|
|
41
|
+
};
|