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,215 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type HandlerStepDefinition,
|
|
8
|
+
PeerProxyStepDefinition,
|
|
9
|
+
SendStepDefinition,
|
|
10
|
+
DynamicProxyStepDefinition,
|
|
11
|
+
WaitForChannelStepDefinition,
|
|
12
|
+
WaitForMessageStepDefinition,
|
|
13
|
+
WaitForDurationStepDefinition,
|
|
14
|
+
CloseStepDefinition,
|
|
15
|
+
EchoStepDefinition,
|
|
16
|
+
WaitForTrackStepDefinition,
|
|
17
|
+
WaitForMediaStepDefinition
|
|
18
|
+
} from "./handler-step-definitions";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The builder logic for composing RTC handling behaviour for both mock peers and rules,
|
|
22
|
+
* by internally queuing defined actions until a `.thenX()` method is called to compile
|
|
23
|
+
* the actions into either a peer or a rule (handled by an constructor callback param).
|
|
24
|
+
*/
|
|
25
|
+
export class MockRTCHandlerBuilder<R> {
|
|
26
|
+
|
|
27
|
+
private handlerSteps: HandlerStepDefinition[] = [];
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private buildCallback: (handlerSteps: HandlerStepDefinition[]) => Promise<R>
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wait for a given duration, in milliseconds
|
|
35
|
+
*
|
|
36
|
+
* @category Steps
|
|
37
|
+
*/
|
|
38
|
+
sleep(duration: number): this {
|
|
39
|
+
this.handlerSteps.push(new WaitForDurationStepDefinition(duration));
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wait until the remote client has created at least one DataChannel.
|
|
45
|
+
*
|
|
46
|
+
* @category Steps
|
|
47
|
+
*/
|
|
48
|
+
waitForChannel(channelLabel?: string): this {
|
|
49
|
+
this.handlerSteps.push(new WaitForChannelStepDefinition(channelLabel));
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Wait until the remote client has created at least one media track
|
|
55
|
+
*
|
|
56
|
+
* @category Steps
|
|
57
|
+
*/
|
|
58
|
+
waitForTrack(): this {
|
|
59
|
+
this.handlerSteps.push(new WaitForTrackStepDefinition());
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wait until the remote client next sends a message to us on any DataChannel.
|
|
65
|
+
*
|
|
66
|
+
* This looks for new messages, ignoring any messages already consumed by
|
|
67
|
+
* previous steps.
|
|
68
|
+
*
|
|
69
|
+
* @category Steps
|
|
70
|
+
*/
|
|
71
|
+
waitForNextMessage(): this {
|
|
72
|
+
this.handlerSteps.push(new WaitForMessageStepDefinition());
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Wait until the remote client next sends media data on a media track.
|
|
78
|
+
*
|
|
79
|
+
* This waits for new media, ignoring any media already consumed by previous steps.
|
|
80
|
+
*
|
|
81
|
+
* @category Steps
|
|
82
|
+
*/
|
|
83
|
+
waitForNextMedia(): this {
|
|
84
|
+
this.handlerSteps.push(new WaitForMediaStepDefinition());
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wait until the remote client sends a message to us on a specific DataChannel.
|
|
90
|
+
*
|
|
91
|
+
* This looks for new messages, ignoring any messages already consumed by
|
|
92
|
+
* previous steps.
|
|
93
|
+
*
|
|
94
|
+
* @category Steps
|
|
95
|
+
*/
|
|
96
|
+
waitForNextMessageOnChannel(channelLabel: string): this {
|
|
97
|
+
this.handlerSteps.push(new WaitForMessageStepDefinition(channelLabel));
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Send a message or buffer on the connection's data channels.
|
|
103
|
+
*
|
|
104
|
+
* This can take one or two arguments. If only one is provided, it is used
|
|
105
|
+
* as a message that's sent on all open data channels. If two arguments are
|
|
106
|
+
* provided, the first must be the data channel label, and the message (the
|
|
107
|
+
* second) will be sent only to data channel(s) with that label.
|
|
108
|
+
*
|
|
109
|
+
* If no matching channels are open, this is a no-op. Use `waitForChannel()`
|
|
110
|
+
* to ensure the channels you're expecting are open first if necessary.
|
|
111
|
+
*
|
|
112
|
+
* @category Steps
|
|
113
|
+
*/
|
|
114
|
+
send(message: string | Buffer): this;
|
|
115
|
+
send(channel: string | undefined, message: string | Buffer): this;
|
|
116
|
+
send(...args: [string | undefined, string | Buffer] | [string | Buffer]): this {
|
|
117
|
+
if (args[1] !== undefined) {
|
|
118
|
+
const [channel, message] = args as [string, string | Buffer];
|
|
119
|
+
this.handlerSteps.push(new SendStepDefinition(channel, message));
|
|
120
|
+
} else {
|
|
121
|
+
const [message] = args as [string | Buffer];
|
|
122
|
+
this.handlerSteps.push(new SendStepDefinition(undefined, message));
|
|
123
|
+
}
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Immediately close the connection.
|
|
129
|
+
*
|
|
130
|
+
* This defines a final step, and will then create a mock peer from the full
|
|
131
|
+
* set of steps you've defined, and return it wrapped in a promise. As soon
|
|
132
|
+
* as the promise resolves the peer is ready to use.
|
|
133
|
+
*
|
|
134
|
+
* @category Final Steps
|
|
135
|
+
*/
|
|
136
|
+
thenClose(): Promise<R> {
|
|
137
|
+
this.handlerSteps.push(new CloseStepDefinition());
|
|
138
|
+
return this.buildCallback(this.handlerSteps);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Send a message or buffer on the connection's data channels, then close the
|
|
143
|
+
* connection. This is equivalent to {@link send `.send()`} then
|
|
144
|
+
* {@link thenClose `.thenClose()`}.
|
|
145
|
+
*
|
|
146
|
+
* This defines a final step, and will then create a mock peer from the full
|
|
147
|
+
* set of steps you've defined, and return it wrapped in a promise. As soon
|
|
148
|
+
* as the promise resolves the peer is ready to use.
|
|
149
|
+
*
|
|
150
|
+
* @category Final Steps
|
|
151
|
+
*/
|
|
152
|
+
thenSend(message: string | Buffer): Promise<R>;
|
|
153
|
+
thenSend(channel: string, message: string | Buffer): Promise<R>;
|
|
154
|
+
thenSend(...args: [string, string | Buffer] | [string | Buffer]): Promise<R> {
|
|
155
|
+
return this.send(...args as [string | undefined, string | Buffer])
|
|
156
|
+
.buildCallback(this.handlerSteps);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Echo all incoming data channel messages until the other peer closes the
|
|
161
|
+
* connection.
|
|
162
|
+
*
|
|
163
|
+
* This defines a final step, and will then create a mock peer from the full
|
|
164
|
+
* set of steps you've defined, and return it wrapped in a promise. As soon
|
|
165
|
+
* as the promise resolves the peer is ready to use.
|
|
166
|
+
*
|
|
167
|
+
* @category Final Steps
|
|
168
|
+
*/
|
|
169
|
+
thenEcho(): Promise<R> {
|
|
170
|
+
this.handlerSteps.push(new EchoStepDefinition());
|
|
171
|
+
return this.buildCallback(this.handlerSteps);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Creates a new external connection to the given remote peer connection,
|
|
176
|
+
* matching the existing mocked connection, and then proxies all traffic
|
|
177
|
+
* through to that peer.
|
|
178
|
+
*
|
|
179
|
+
* This defines a final step, and will then create a mock peer from the full
|
|
180
|
+
* set of steps you've defined, and return it wrapped in a promise. As soon
|
|
181
|
+
* as the promise resolves the peer is ready to use.
|
|
182
|
+
*
|
|
183
|
+
* @category Final Steps
|
|
184
|
+
*/
|
|
185
|
+
thenForwardTo(peer: RTCPeerConnection): Promise<R> {
|
|
186
|
+
this.handlerSteps.push(new PeerProxyStepDefinition(peer));
|
|
187
|
+
return this.buildCallback(this.handlerSteps);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Proxy this connection dynamically to the 'real' target peer, whoever
|
|
192
|
+
* that may be.
|
|
193
|
+
*
|
|
194
|
+
* This assumes that you have an existing external connection already
|
|
195
|
+
* set up and attached to this mock connection.
|
|
196
|
+
*
|
|
197
|
+
* You can do that either by using {@link hookWebRTCConnection} or
|
|
198
|
+
* {@link hookAllWebRTC} to hook your connection during normal setup to
|
|
199
|
+
* automatically create an external offer to the real remote peer, or you can
|
|
200
|
+
* do so manually using {@link MockRTCPeer.createExternalOffer} or
|
|
201
|
+
* {@link MockRTCPeer.answerExternalOffer} and then passing the connection
|
|
202
|
+
* id as {@link https://github.com/httptoolkit/mockrtc/blob/d0604f3111e0438c52aa514d00cf04ac0718dfeb/src/webrtc-hooks.ts#L83-L93 here}.
|
|
203
|
+
*
|
|
204
|
+
* This defines a final step, and will then create a mock peer from the full
|
|
205
|
+
* set of steps you've defined, and return it wrapped in a promise. As soon
|
|
206
|
+
* as the promise resolves the peer is ready to use.
|
|
207
|
+
*
|
|
208
|
+
* @category Final Steps
|
|
209
|
+
*/
|
|
210
|
+
thenPassThrough(): Promise<R> {
|
|
211
|
+
this.handlerSteps.push(new DynamicProxyStepDefinition());
|
|
212
|
+
return this.buildCallback(this.handlerSteps);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as PluggableAdmin from 'mockttp/dist/pluggable-admin-api/pluggable-admin.browser';
|
|
7
|
+
|
|
8
|
+
export type Serializable = PluggableAdmin.Serialization.Serializable;
|
|
9
|
+
export const { Serializable } = PluggableAdmin.Serialization;
|
|
10
|
+
type ClientServerChannel = PluggableAdmin.Serialization.ClientServerChannel;
|
|
11
|
+
|
|
12
|
+
export interface HandlerStepDefinition extends Serializable {
|
|
13
|
+
readonly type: keyof typeof StepDefinitionLookup;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class WaitForDurationStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
17
|
+
|
|
18
|
+
readonly type = 'wait-for-duration';
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
protected durationMs: number
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class WaitForChannelStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
29
|
+
|
|
30
|
+
readonly type = 'wait-for-channel';
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
protected channelLabel?: string
|
|
34
|
+
) {
|
|
35
|
+
super();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class WaitForMessageStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
40
|
+
|
|
41
|
+
readonly type = 'wait-for-message';
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
protected channelLabel?: string
|
|
45
|
+
) {
|
|
46
|
+
super();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class WaitForTrackStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
52
|
+
|
|
53
|
+
readonly type = 'wait-for-track';
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class WaitForMediaStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
58
|
+
|
|
59
|
+
readonly type = 'wait-for-media';
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class SendStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
64
|
+
|
|
65
|
+
readonly type = 'send-message';
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
protected channelLabel: string | undefined,
|
|
69
|
+
protected message: string | Buffer
|
|
70
|
+
) {
|
|
71
|
+
super();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class CloseStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
77
|
+
|
|
78
|
+
readonly type = 'close-connection';
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class EchoStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
83
|
+
|
|
84
|
+
readonly type = 'echo-channels';
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class PeerProxyStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
89
|
+
|
|
90
|
+
readonly type = 'peer-proxy';
|
|
91
|
+
|
|
92
|
+
protected getAnswer: (offer: RTCSessionDescriptionInit) => Promise<RTCSessionDescriptionInit>;
|
|
93
|
+
|
|
94
|
+
constructor(
|
|
95
|
+
connectionTarget:
|
|
96
|
+
| RTCPeerConnection
|
|
97
|
+
| ((offer: RTCSessionDescriptionInit) => Promise<RTCSessionDescriptionInit>)
|
|
98
|
+
) {
|
|
99
|
+
super();
|
|
100
|
+
if (connectionTarget instanceof Function) {
|
|
101
|
+
this.getAnswer = connectionTarget;
|
|
102
|
+
} else {
|
|
103
|
+
this.getAnswer = async (offer: RTCSessionDescriptionInit) => {
|
|
104
|
+
await connectionTarget.setRemoteDescription(offer);
|
|
105
|
+
const answer = await connectionTarget.createAnswer();
|
|
106
|
+
await connectionTarget.setLocalDescription(answer);
|
|
107
|
+
return answer;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
serialize(channel: ClientServerChannel): {} {
|
|
113
|
+
channel.onRequest<
|
|
114
|
+
{ offer: RTCSessionDescriptionInit },
|
|
115
|
+
{ answer: RTCSessionDescriptionInit }
|
|
116
|
+
>(async (msg) => {
|
|
117
|
+
return { answer: await this.getAnswer(msg.offer) };
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return { type: this.type };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class DynamicProxyStepDefinition extends Serializable implements HandlerStepDefinition {
|
|
126
|
+
|
|
127
|
+
readonly type = 'dynamic-proxy';
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const StepDefinitionLookup = {
|
|
132
|
+
'wait-for-duration': WaitForDurationStepDefinition,
|
|
133
|
+
'wait-for-channel': WaitForChannelStepDefinition,
|
|
134
|
+
'wait-for-track': WaitForTrackStepDefinition,
|
|
135
|
+
'wait-for-media': WaitForMediaStepDefinition,
|
|
136
|
+
'wait-for-message': WaitForMessageStepDefinition,
|
|
137
|
+
'send-message': SendStepDefinition,
|
|
138
|
+
'close-connection': CloseStepDefinition,
|
|
139
|
+
'echo-channels': EchoStepDefinition,
|
|
140
|
+
'peer-proxy': PeerProxyStepDefinition,
|
|
141
|
+
'dynamic-proxy': DynamicProxyStepDefinition
|
|
142
|
+
};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PluggableAdmin } from 'mockttp';
|
|
7
|
+
|
|
8
|
+
import type { DataChannelStream } from '../webrtc/datachannel-stream';
|
|
9
|
+
import type { MediaTrackStream } from '../webrtc/mediatrack-stream';
|
|
10
|
+
import type { MockRTCConnection } from '../webrtc/mockrtc-connection';
|
|
11
|
+
import { RTCConnection } from '../webrtc/rtc-connection';
|
|
12
|
+
import {
|
|
13
|
+
StepDefinitionLookup,
|
|
14
|
+
CloseStepDefinition,
|
|
15
|
+
DynamicProxyStepDefinition,
|
|
16
|
+
EchoStepDefinition,
|
|
17
|
+
HandlerStepDefinition,
|
|
18
|
+
PeerProxyStepDefinition,
|
|
19
|
+
SendStepDefinition,
|
|
20
|
+
WaitForChannelStepDefinition,
|
|
21
|
+
WaitForDurationStepDefinition,
|
|
22
|
+
WaitForMediaStepDefinition,
|
|
23
|
+
WaitForMessageStepDefinition,
|
|
24
|
+
WaitForTrackStepDefinition
|
|
25
|
+
} from './handler-step-definitions';
|
|
26
|
+
|
|
27
|
+
type ClientServerChannel = PluggableAdmin.Serialization.ClientServerChannel;
|
|
28
|
+
|
|
29
|
+
export interface HandlerStep extends HandlerStepDefinition {
|
|
30
|
+
handle(connection: MockRTCConnection): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class WaitForDurationStep extends WaitForDurationStepDefinition {
|
|
34
|
+
|
|
35
|
+
async handle(): Promise<void> {
|
|
36
|
+
return new Promise<void>((resolve) => setTimeout(resolve, this.durationMs));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class WaitForChannelStep extends WaitForChannelStepDefinition {
|
|
42
|
+
|
|
43
|
+
private matchesChannel(channel: DataChannelStream) {
|
|
44
|
+
return this.channelLabel === undefined || this.channelLabel === channel.label;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
48
|
+
return new Promise<void>((resolve) => {
|
|
49
|
+
const channelOpened = (channel: DataChannelStream) => {
|
|
50
|
+
if (this.matchesChannel(channel)) {
|
|
51
|
+
connection.removeListener('remote-channel-open', channelOpened);
|
|
52
|
+
resolve();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
connection.on('remote-channel-open', channelOpened);
|
|
57
|
+
connection.remoteChannels.forEach(channelOpened);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class WaitForMessageStep extends WaitForMessageStepDefinition {
|
|
64
|
+
|
|
65
|
+
private matchesChannel(channel: DataChannelStream) {
|
|
66
|
+
return this.channelLabel === undefined || this.channelLabel === channel.label;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
70
|
+
return new Promise<void>((resolve) => {
|
|
71
|
+
const messageReceived = () => {
|
|
72
|
+
connection.removeListener('channel-open', listenForMessage);
|
|
73
|
+
connection.channels.forEach((channel) => {
|
|
74
|
+
channel.removeListener('data', messageReceived);
|
|
75
|
+
channel.pause();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
resolve();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const listenForMessage = (channel: DataChannelStream) => {
|
|
82
|
+
if (this.matchesChannel(channel)) {
|
|
83
|
+
channel.once('data', messageReceived);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
connection.on('channel-open', listenForMessage);
|
|
88
|
+
connection.channels.forEach(listenForMessage);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class WaitForTrackStep extends WaitForTrackStepDefinition {
|
|
95
|
+
|
|
96
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
97
|
+
await new Promise<void>((resolve) => {
|
|
98
|
+
if (connection.remoteMediaTracks.length) resolve();
|
|
99
|
+
else connection.once('remote-track-open', () => resolve());
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class WaitForMediaStep extends WaitForMediaStepDefinition {
|
|
106
|
+
|
|
107
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
108
|
+
return new Promise<void>((resolve) => {
|
|
109
|
+
const messageReceived = () => {
|
|
110
|
+
connection.removeListener('track-open', listenForData);
|
|
111
|
+
connection.mediaTracks.forEach((track) => {
|
|
112
|
+
track.removeListener('data', messageReceived);
|
|
113
|
+
track.pause();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
resolve();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const listenForData = (track: MediaTrackStream) => {
|
|
120
|
+
track.once('data', messageReceived);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
connection.on('track-open', listenForData);
|
|
124
|
+
connection.mediaTracks.forEach(listenForData);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class SendStep extends SendStepDefinition {
|
|
131
|
+
|
|
132
|
+
private matchesChannel(channel: DataChannelStream) {
|
|
133
|
+
return this.channelLabel === undefined || this.channelLabel === channel.label;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async handle({ channels }: MockRTCConnection): Promise<void> {
|
|
137
|
+
await Promise.all(
|
|
138
|
+
channels
|
|
139
|
+
.filter((channel) => this.matchesChannel(channel))
|
|
140
|
+
.map((channel) => {
|
|
141
|
+
return new Promise<void>((resolve, reject) => {
|
|
142
|
+
channel.write(this.message, (error: Error | null | undefined) => {
|
|
143
|
+
if (error) reject(error);
|
|
144
|
+
else resolve();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class CloseStep extends CloseStepDefinition {
|
|
154
|
+
|
|
155
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
156
|
+
await connection.close();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class EchoStep extends EchoStepDefinition {
|
|
162
|
+
|
|
163
|
+
async handle(connection: MockRTCConnection): Promise<void> {
|
|
164
|
+
const echoContent = (stream: DataChannelStream | MediaTrackStream) => {
|
|
165
|
+
stream.pipe(stream);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
connection.on('channel-open', echoContent);
|
|
169
|
+
connection.on('track-open', echoContent);
|
|
170
|
+
connection.channels.forEach(echoContent);
|
|
171
|
+
connection.mediaTracks.forEach(echoContent);
|
|
172
|
+
|
|
173
|
+
// This step keeps running indefinitely, until the connection closes
|
|
174
|
+
return new Promise<void>((resolve) => connection.on('connection-closed', resolve));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export class PeerProxyStep extends PeerProxyStepDefinition {
|
|
180
|
+
|
|
181
|
+
private externalConnections: RTCConnection[] = [];
|
|
182
|
+
|
|
183
|
+
async handle(connection: MockRTCConnection) {
|
|
184
|
+
const externalConn = new RTCConnection();
|
|
185
|
+
this.externalConnections.push(externalConn);
|
|
186
|
+
|
|
187
|
+
// We mirror the internal peer's SDP as an offer to the given connection:
|
|
188
|
+
const externalOffer = await externalConn.getMirroredLocalOffer(
|
|
189
|
+
connection.getRemoteDescription()!.sdp!
|
|
190
|
+
);
|
|
191
|
+
externalConn.setRemoteDescription(await this.getAnswer(externalOffer));
|
|
192
|
+
|
|
193
|
+
connection.proxyTrafficTo(externalConn);
|
|
194
|
+
|
|
195
|
+
// This step keeps running indefinitely, until the connection closes
|
|
196
|
+
return new Promise<void>((resolve) => connection.on('connection-closed', resolve));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
serialize(channel: ClientServerChannel): {} {
|
|
200
|
+
channel.onRequest<
|
|
201
|
+
{ offer: RTCSessionDescriptionInit },
|
|
202
|
+
{ answer: RTCSessionDescriptionInit }
|
|
203
|
+
>(async (msg) => {
|
|
204
|
+
return { answer: await this.getAnswer(msg.offer) };
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return { type: this.type };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static deserialize(_data: {}, channel: ClientServerChannel): PeerProxyStep {
|
|
211
|
+
return new PeerProxyStep(async (offer: RTCSessionDescriptionInit) => {
|
|
212
|
+
const response = await channel.request<
|
|
213
|
+
{ offer: RTCSessionDescriptionInit },
|
|
214
|
+
{ answer: RTCSessionDescriptionInit }
|
|
215
|
+
>({ offer });
|
|
216
|
+
return response.answer;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
dispose(): void {
|
|
221
|
+
this.externalConnections.forEach(conn => conn.close());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class DynamicProxyStep extends DynamicProxyStepDefinition {
|
|
227
|
+
|
|
228
|
+
private externalConnections: RTCConnection[] = [];
|
|
229
|
+
|
|
230
|
+
async handle(connection: MockRTCConnection) {
|
|
231
|
+
await connection.proxyTrafficToExternalConnection();
|
|
232
|
+
|
|
233
|
+
// This step keeps running indefinitely, until the connection closes
|
|
234
|
+
return new Promise<void>((resolve) => connection.on('connection-closed', resolve));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
dispose(): void {
|
|
238
|
+
this.externalConnections.forEach(conn => conn.close());
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const StepLookup: typeof StepDefinitionLookup = {
|
|
244
|
+
'wait-for-duration': WaitForDurationStep,
|
|
245
|
+
'wait-for-channel': WaitForChannelStep,
|
|
246
|
+
'wait-for-track': WaitForTrackStep,
|
|
247
|
+
'wait-for-media': WaitForMediaStep,
|
|
248
|
+
'wait-for-message': WaitForMessageStep,
|
|
249
|
+
'send-message': SendStep,
|
|
250
|
+
'close-connection': CloseStep,
|
|
251
|
+
'echo-channels': EchoStep,
|
|
252
|
+
'peer-proxy': PeerProxyStep,
|
|
253
|
+
'dynamic-proxy': DynamicProxyStep
|
|
254
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
MockRTC,
|
|
8
|
+
MockRTCOptions,
|
|
9
|
+
} from "./mockrtc";
|
|
10
|
+
import { MockRTCClient, MockRTCClientOptions } from "./client/mockrtc-client";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
MockRTC,
|
|
14
|
+
MockRTCOptions
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
MockRTCPeer,
|
|
19
|
+
MockRTCSession,
|
|
20
|
+
MockRTCOfferParams,
|
|
21
|
+
MockRTCAnswerParams,
|
|
22
|
+
MockRTCExternalOfferParams,
|
|
23
|
+
MockRTCExternalAnswerParams,
|
|
24
|
+
OfferOptions,
|
|
25
|
+
AnswerOptions
|
|
26
|
+
} from './mockrtc-peer';
|
|
27
|
+
|
|
28
|
+
export { MOCKRTC_CONTROL_CHANNEL } from './webrtc/control-channel';
|
|
29
|
+
export {
|
|
30
|
+
hookWebRTCConnection,
|
|
31
|
+
hookAllWebRTC
|
|
32
|
+
} from "./webrtc-hooks";
|
|
33
|
+
|
|
34
|
+
export function getLocal(): MockRTC {
|
|
35
|
+
return new MockRTCClient();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getRemote(options: MockRTCClientOptions = {}): MockRTC {
|
|
39
|
+
return new MockRTCClient(options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getAdminServer(): never {
|
|
43
|
+
throw new Error("Can't use MockRTC.getLocal() in a browser");
|
|
44
|
+
}
|