mockrtc 0.2.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/dist/client/mockrtc-admin-request-builder.d.ts +7 -1
- package/dist/client/mockrtc-admin-request-builder.js +30 -0
- package/dist/client/mockrtc-admin-request-builder.js.map +1 -1
- package/dist/client/mockrtc-client.d.ts +8 -4
- package/dist/client/mockrtc-client.js +21 -9
- package/dist/client/mockrtc-client.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 +40 -23
- 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 +26 -16
- package/dist/handling/handler-steps.js.map +1 -1
- package/dist/main-browser.d.ts +1 -0
- package/dist/main-browser.js +2 -1
- package/dist/main-browser.js.map +1 -1
- package/dist/main.d.ts +5 -3
- package/dist/main.js +2 -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 +9 -2
- package/dist/mockrtc.d.ts +62 -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.js +18 -1
- package/dist/server/mockrtc-admin-plugin.js.map +1 -1
- package/dist/server/mockrtc-server-peer.d.ts +1 -1
- package/dist/server/mockrtc-server-peer.js +19 -7
- package/dist/server/mockrtc-server-peer.js.map +1 -1
- package/dist/server/mockrtc-server.d.ts +12 -5
- package/dist/server/mockrtc-server.js +53 -18
- package/dist/server/mockrtc-server.js.map +1 -1
- package/dist/webrtc/datachannel-stream.d.ts +2 -0
- package/dist/webrtc/datachannel-stream.js +15 -1
- package/dist/webrtc/datachannel-stream.js.map +1 -1
- package/dist/webrtc/mediatrack-stream.d.ts +2 -0
- package/dist/webrtc/mediatrack-stream.js +15 -1
- package/dist/webrtc/mediatrack-stream.js.map +1 -1
- package/dist/webrtc/mockrtc-connection.js +1 -1
- package/dist/webrtc/mockrtc-connection.js.map +1 -1
- package/dist/webrtc/rtc-connection.d.ts +6 -2
- package/dist/webrtc/rtc-connection.js +20 -20
- package/dist/webrtc/rtc-connection.js.map +1 -1
- package/dist/webrtc-hooks.js +8 -2
- package/dist/webrtc-hooks.js.map +1 -1
- package/package.json +1 -1
- package/src/client/mockrtc-admin-request-builder.ts +49 -1
- package/src/client/mockrtc-client.ts +28 -10
- package/src/handling/handler-builder.ts +22 -10
- package/src/handling/handler-step-definitions.ts +81 -23
- package/src/handling/handler-steps.ts +28 -15
- package/src/main-browser.ts +1 -0
- package/src/main.ts +5 -1
- 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 +9 -2
- package/src/mockrtc.ts +72 -0
- package/src/rule-builder.ts +142 -0
- package/src/server/mockrtc-admin-plugin.ts +41 -3
- package/src/server/mockrtc-server-peer.ts +28 -18
- package/src/server/mockrtc-server.ts +71 -15
- package/src/webrtc/datachannel-stream.ts +15 -1
- package/src/webrtc/mediatrack-stream.ts +15 -1
- package/src/webrtc/mockrtc-connection.ts +1 -1
- package/src/webrtc/rtc-connection.ts +36 -19
- package/src/webrtc-hooks.ts +8 -2
- package/test/integration/events.spec.ts +3 -1
- package/test/integration/matching.spec.ts +189 -0
- package/test/integration/proxy.spec.ts +4 -2
- package/test/integration/send-steps.spec.ts +25 -0
package/src/mockrtc.ts
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { MockRTCHandlerBuilder } from "./handling/handler-builder";
|
|
7
|
+
import { HandlerStepDefinition } from "./handling/handler-step-definitions";
|
|
8
|
+
import { MatcherDefinition } from "./matching/matcher-definitions";
|
|
7
9
|
import type { ConnectionMetadata, MockRTCPeer } from "./mockrtc-peer";
|
|
10
|
+
import { MockRTCRuleBuilder } from "./rule-builder";
|
|
8
11
|
|
|
9
12
|
export interface MockRTCPeerBuilder extends MockRTCHandlerBuilder<MockRTCPeer> {}
|
|
10
13
|
|
|
@@ -155,6 +158,11 @@ export type MockRTCEventData = {
|
|
|
155
158
|
|
|
156
159
|
export type MockRTCEvent = keyof MockRTCEventData;
|
|
157
160
|
|
|
161
|
+
export type MockRTCRuleDefinition = {
|
|
162
|
+
matchers: MatcherDefinition[];
|
|
163
|
+
steps: HandlerStepDefinition[];
|
|
164
|
+
};
|
|
165
|
+
|
|
158
166
|
export interface MockRTC {
|
|
159
167
|
|
|
160
168
|
/**
|
|
@@ -172,6 +180,23 @@ export interface MockRTC {
|
|
|
172
180
|
*/
|
|
173
181
|
buildPeer(): MockRTCPeerBuilder;
|
|
174
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Starting defining a mock WebRTC rule. This methods returns a rule builder,
|
|
185
|
+
* which can be configured to define which incoming connections should be
|
|
186
|
+
* matched, with methods like `.fromPageHostname(hostname)`.
|
|
187
|
+
*
|
|
188
|
+
* Once the matching is configured, start calling handler methods like
|
|
189
|
+
* `.send()` to define a series of steps to run for matching connections,
|
|
190
|
+
* and then call a `.thenX()` method to complete the definition and
|
|
191
|
+
* define the rule.
|
|
192
|
+
*
|
|
193
|
+
* The rule definition is not complete until the returned promise resolves.
|
|
194
|
+
* Once it has resolved successfully, any future connections to the peer
|
|
195
|
+
* returned by `getMatchingPeer()` will be matched against these rules,
|
|
196
|
+
* and will run the steps for the first matching rule found.
|
|
197
|
+
*/
|
|
198
|
+
forConnections(): MockRTCRuleBuilder;
|
|
199
|
+
|
|
175
200
|
/**
|
|
176
201
|
* Get the rule-matching peer.
|
|
177
202
|
*
|
|
@@ -193,6 +218,53 @@ export interface MockRTC {
|
|
|
193
218
|
|
|
194
219
|
stop(): Promise<void>;
|
|
195
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Subscribe to events to monitor WebRTC interactions across all peers managed by
|
|
223
|
+
* this MockRTC session. The events available include:
|
|
224
|
+
*
|
|
225
|
+
* - `peer-connected`
|
|
226
|
+
* - `peer-disconnected`
|
|
227
|
+
* - `external-peer-attached`
|
|
228
|
+
* - `data-channel-opened`
|
|
229
|
+
* - `data-channel-message-sent`
|
|
230
|
+
* - `data-channel-message-received`
|
|
231
|
+
* - `data-channel-closed`
|
|
232
|
+
* - `media-track-opened`
|
|
233
|
+
* - `media-track-stats`
|
|
234
|
+
* - `media-track-closed`
|
|
235
|
+
*/
|
|
196
236
|
on<E extends MockRTCEvent>(event: E, callback: (param: MockRTCEventData[E]) => void): Promise<void>;
|
|
197
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Create a peer from a set of step definitions.
|
|
240
|
+
*
|
|
241
|
+
* This API is only useful if you're building peers from data programmatically,
|
|
242
|
+
* rather than using `buildPeer()` and `MockRTCPeerBuilder`, which are generally
|
|
243
|
+
* preferable otherwise.
|
|
244
|
+
*/
|
|
245
|
+
buildPeerFromDefinition(
|
|
246
|
+
handlerStepDefinitions: HandlerStepDefinition[]
|
|
247
|
+
): Promise<MockRTCPeer>;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create a connection-matching rule from a set of matchers and step definitions.
|
|
251
|
+
*
|
|
252
|
+
* This API is only useful if you're building rule from data programmatically,
|
|
253
|
+
* rather than using `forX()` and `MockRTCHandlerBuilder`, which are generally
|
|
254
|
+
* preferable otherwise.
|
|
255
|
+
*/
|
|
256
|
+
addRuleFromDefinition(
|
|
257
|
+
matcherDefinitions: MatcherDefinition[],
|
|
258
|
+
handlerStepDefinitions: HandlerStepDefinition[]
|
|
259
|
+
): Promise<void>;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create a connection-matching rule from a set of matchers and step definitions.
|
|
263
|
+
*
|
|
264
|
+
* This API is only useful if you're building rule from data programmatically,
|
|
265
|
+
* rather than using `forX()` and `MockRTCHandlerBuilder`, which are generally
|
|
266
|
+
* preferable otherwise.
|
|
267
|
+
*/
|
|
268
|
+
setRulesFromDefinitions(rules: Array<MockRTCRuleDefinition>): Promise<void>;
|
|
269
|
+
|
|
198
270
|
}
|
|
@@ -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 { MockRTCHandlerBuilder } from "./handling/handler-builder";
|
|
7
|
+
import { HandlerStepDefinition } from "./handling/handler-step-definitions";
|
|
8
|
+
import {
|
|
9
|
+
MatcherDefinition,
|
|
10
|
+
HostnameMatcherDefinition,
|
|
11
|
+
UrlRegexMatcherDefinition,
|
|
12
|
+
UserAgentRegexMatcherDefinition,
|
|
13
|
+
HasAudioTrackMatcherDefinition,
|
|
14
|
+
HasVideoTrackMatcherDefinition,
|
|
15
|
+
HasMediaTrackMatcherDefinition,
|
|
16
|
+
HasDataChannelMatcherDefinition
|
|
17
|
+
} from "./matching/matcher-definitions";
|
|
18
|
+
|
|
19
|
+
export type RuleHandlerBuilder = MockRTCHandlerBuilder<void>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Rule builders allow you to combine sets of matchers, progressively
|
|
23
|
+
* building a set of matching conditions, before defining the resulting
|
|
24
|
+
* behaviour that will be applied to matching traffic.
|
|
25
|
+
*/
|
|
26
|
+
export class MockRTCRuleBuilder implements Omit<RuleHandlerBuilder, 'handlerSteps' | 'buildCallback'> {
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
private addRuleCallback: (
|
|
30
|
+
matcherDefinitions: MatcherDefinition[],
|
|
31
|
+
handlerStepDefinitions: HandlerStepDefinition[]
|
|
32
|
+
) => Promise<void>
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
private matchers: MatcherDefinition[] = [];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Match RTC connections whose initial negotiation includes a data channel.
|
|
39
|
+
*/
|
|
40
|
+
withDataChannels() {
|
|
41
|
+
this.matchers.push(new HasDataChannelMatcherDefinition());
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Match RTC connections whose initial negotiation includes either an audio or video
|
|
47
|
+
* media track.
|
|
48
|
+
*/
|
|
49
|
+
withMedia() {
|
|
50
|
+
this.matchers.push(new HasMediaTrackMatcherDefinition());
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Match RTC connections whose initial negotiation includes a video media track
|
|
56
|
+
*/
|
|
57
|
+
withVideo() {
|
|
58
|
+
this.matchers.push(new HasVideoTrackMatcherDefinition());
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Match RTC connections whose initial negotiation includes an audio media track
|
|
64
|
+
*/
|
|
65
|
+
withAudio() {
|
|
66
|
+
this.matchers.push(new HasAudioTrackMatcherDefinition());
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Match RTC connections made from hooked JavaScript running on a given hostname.
|
|
72
|
+
*
|
|
73
|
+
* This only matches connections with explicit `sourceURL` metadata, which must be
|
|
74
|
+
* either added automatically (by using the `hookAllWebRTC` or `hookWebRTCConnection`
|
|
75
|
+
* methods) or manually (by providing `metadata: { sourceURL: '...' }` options
|
|
76
|
+
* during mock connection setup).
|
|
77
|
+
*
|
|
78
|
+
* @category Matcher
|
|
79
|
+
*/
|
|
80
|
+
fromPageHostname(hostname: string): this {
|
|
81
|
+
this.matchers.push(new HostnameMatcherDefinition(hostname));
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Match RTC connections made from hooked JavaScript running on a matching URL.
|
|
87
|
+
*
|
|
88
|
+
* This only matches connections with explicit `sourceURL` metadata, which must be
|
|
89
|
+
* either added automatically (by using the `hookAllWebRTC` or `hookWebRTCConnection`
|
|
90
|
+
* methods) or manually (by providing `metadata: { sourceURL: '...' }` options
|
|
91
|
+
* during mock connection setup).
|
|
92
|
+
*
|
|
93
|
+
* @category Matcher
|
|
94
|
+
*/
|
|
95
|
+
fromPageUrlMatching(urlRegex: RegExp): this {
|
|
96
|
+
this.matchers.push(new UrlRegexMatcherDefinition(urlRegex));
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Match RTC connections made by hooked JavaScript running within a browser with a
|
|
102
|
+
* matching user agent string.
|
|
103
|
+
*
|
|
104
|
+
* This only matches connections with explicit `userAgent` metadata, which must be
|
|
105
|
+
* either added automatically (by using the `hookAllWebRTC` or `hookWebRTCConnection`
|
|
106
|
+
* methods) or manually (by providing `metadata: { userAgent: '...' }` options
|
|
107
|
+
* during mock connection setup).
|
|
108
|
+
*
|
|
109
|
+
* @category Matcher
|
|
110
|
+
*/
|
|
111
|
+
fromUserAgentMatching(userAgentRegEx: RegExp): this {
|
|
112
|
+
this.matchers.push(new UserAgentRegexMatcherDefinition(userAgentRegEx));
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// For all handler methods, return a handler builder - i.e. once you start calling
|
|
117
|
+
// any of these step-definition methods, you can't keep calling matcher methods:
|
|
118
|
+
private buildDefinitionMethod = <K extends keyof RuleHandlerBuilder>(
|
|
119
|
+
methodName: K
|
|
120
|
+
) => ((...args: any[]) => {
|
|
121
|
+
const handlerBuilder = new MockRTCHandlerBuilder(
|
|
122
|
+
(steps) => this.addRuleCallback(this.matchers, steps)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return (handlerBuilder as any)[methodName](...args);
|
|
126
|
+
}) as RuleHandlerBuilder[K];
|
|
127
|
+
|
|
128
|
+
sleep = this.buildDefinitionMethod('sleep');
|
|
129
|
+
waitForChannel = this.buildDefinitionMethod('waitForChannel');
|
|
130
|
+
waitForTrack = this.buildDefinitionMethod('waitForTrack');
|
|
131
|
+
waitForNextMessage = this.buildDefinitionMethod('waitForNextMessage');
|
|
132
|
+
waitForNextMedia = this.buildDefinitionMethod('waitForNextMedia');
|
|
133
|
+
waitForNextMessageOnChannel = this.buildDefinitionMethod('waitForNextMessageOnChannel');
|
|
134
|
+
createDataChannel = this.buildDefinitionMethod('createDataChannel');
|
|
135
|
+
send = this.buildDefinitionMethod('send');
|
|
136
|
+
thenClose = this.buildDefinitionMethod('thenClose');
|
|
137
|
+
thenSend = this.buildDefinitionMethod('thenSend');
|
|
138
|
+
thenEcho = this.buildDefinitionMethod('thenEcho');
|
|
139
|
+
thenForwardTo = this.buildDefinitionMethod('thenForwardTo');
|
|
140
|
+
thenPassThrough = this.buildDefinitionMethod('thenPassThrough');
|
|
141
|
+
|
|
142
|
+
}
|
|
@@ -10,10 +10,13 @@ import { PluggableAdmin } from 'mockttp';
|
|
|
10
10
|
import type { IResolvers } from "@graphql-tools/utils";
|
|
11
11
|
import { PubSub } from "graphql-subscriptions";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { StepLookup } from '../handling/handler-steps';
|
|
14
14
|
import { MockRTCOptions, MockRTCSessionDescription } from '../mockrtc';
|
|
15
15
|
import { MockRTCServer } from './mockrtc-server';
|
|
16
16
|
import { AnswerOptions, OfferOptions } from '../mockrtc-peer';
|
|
17
|
+
import { MatcherDefinition } from '../matching/matcher-definitions';
|
|
18
|
+
import { MatcherLookup } from '../matching/matchers';
|
|
19
|
+
import { HandlerStepDefinition } from '../handling/handler-step-definitions';
|
|
17
20
|
|
|
18
21
|
const { deserialize } = PluggableAdmin.Serialization;
|
|
19
22
|
type SerializedValue<T> = PluggableAdmin.Serialization.SerializedValue<T>;
|
|
@@ -56,6 +59,8 @@ export class MockRTCAdminPlugin implements PluggableAdmin.AdminPlugin<MockRTCOpt
|
|
|
56
59
|
schema = gql`
|
|
57
60
|
extend type Mutation {
|
|
58
61
|
createPeer(data: RTCHandlerData!): MockedPeer!
|
|
62
|
+
addRTCRule(data: RTCRuleData!): Void
|
|
63
|
+
setRTCRules(data: [RTCRuleData!]!): Void
|
|
59
64
|
|
|
60
65
|
createOffer(peerId: ID!, sessionId: ID, options: Raw): Session!
|
|
61
66
|
createExternalOffer(peerId: ID!, options: Raw): Session!
|
|
@@ -69,6 +74,11 @@ export class MockRTCAdminPlugin implements PluggableAdmin.AdminPlugin<MockRTCOpt
|
|
|
69
74
|
steps: [Raw!]!
|
|
70
75
|
}
|
|
71
76
|
|
|
77
|
+
input RTCRuleData {
|
|
78
|
+
matchers: [Raw!]!
|
|
79
|
+
steps: [Raw!]!
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
type MockedPeer {
|
|
73
83
|
peerId: ID!
|
|
74
84
|
}
|
|
@@ -227,14 +237,42 @@ export class MockRTCAdminPlugin implements PluggableAdmin.AdminPlugin<MockRTCOpt
|
|
|
227
237
|
return {
|
|
228
238
|
Mutation: {
|
|
229
239
|
createPeer: (__: any, { data: { steps } }: { data: {
|
|
230
|
-
steps: Array<SerializedValue<
|
|
240
|
+
steps: Array<SerializedValue<HandlerStepDefinition>>
|
|
231
241
|
} }) => {
|
|
232
|
-
return this.mockRTCServer.
|
|
242
|
+
return this.mockRTCServer.buildPeerFromDefinition(
|
|
233
243
|
steps.map((stepData) =>
|
|
234
244
|
deserialize(stepData, adminStream, ruleParams, StepLookup)
|
|
235
245
|
)
|
|
236
246
|
);
|
|
237
247
|
},
|
|
248
|
+
addRTCRule: (__: any, { data: { steps, matchers } }: { data: {
|
|
249
|
+
matchers: Array<SerializedValue<MatcherDefinition>>
|
|
250
|
+
steps: Array<SerializedValue<HandlerStepDefinition>>
|
|
251
|
+
} }) => {
|
|
252
|
+
return this.mockRTCServer.addRuleFromDefinition(
|
|
253
|
+
matchers.map((matcherData) =>
|
|
254
|
+
deserialize(matcherData, adminStream, ruleParams, MatcherLookup)
|
|
255
|
+
),
|
|
256
|
+
steps.map((stepData) =>
|
|
257
|
+
deserialize(stepData, adminStream, ruleParams, StepLookup)
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
},
|
|
261
|
+
setRTCRules: (__: any, { data: rules }: { data: Array<{
|
|
262
|
+
matchers: Array<SerializedValue<MatcherDefinition>>
|
|
263
|
+
steps: Array<SerializedValue<HandlerStepDefinition>>
|
|
264
|
+
}> }) => {
|
|
265
|
+
return this.mockRTCServer.setRulesFromDefinitions(
|
|
266
|
+
rules.map(({ matchers, steps }) => ({
|
|
267
|
+
matchers: matchers.map((matcherData) =>
|
|
268
|
+
deserialize(matcherData, adminStream, ruleParams, MatcherLookup)
|
|
269
|
+
),
|
|
270
|
+
steps: steps.map((stepData) =>
|
|
271
|
+
deserialize(stepData, adminStream, ruleParams, StepLookup)
|
|
272
|
+
)
|
|
273
|
+
}))
|
|
274
|
+
);
|
|
275
|
+
},
|
|
238
276
|
createOffer: async (__: any, { peerId, sessionId, options }: {
|
|
239
277
|
peerId: string,
|
|
240
278
|
sessionId?: string,
|
|
@@ -39,7 +39,8 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
39
39
|
private readonly unassignedExternalConnections: { [id: string]: RTCConnection } = {};
|
|
40
40
|
|
|
41
41
|
constructor(
|
|
42
|
-
private getHandlerSteps: (conn: RTCConnection) =>
|
|
42
|
+
private getHandlerSteps: (conn: RTCConnection) =>
|
|
43
|
+
(HandlerStep[] | Promise<HandlerStep[]>),
|
|
43
44
|
private options: MockRTCPeerOptions & { peerId?: string } = {},
|
|
44
45
|
private eventEmitter: EventEmitter
|
|
45
46
|
) {
|
|
@@ -105,12 +106,16 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
105
106
|
channelId: channelStream.id,
|
|
106
107
|
};
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
const announceOpen = () => {
|
|
110
|
+
this.eventEmitter.emit('data-channel-opened', {
|
|
111
|
+
...channelEventParams,
|
|
112
|
+
channelLabel: channelStream.label,
|
|
113
|
+
channelProtocol: channelStream.protocol,
|
|
114
|
+
eventTimestamp: now()
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
if (channelStream.isOpen) announceOpen();
|
|
118
|
+
else channelStream.on('channel-open', announceOpen);
|
|
114
119
|
|
|
115
120
|
const emitMessage = (direction: 'sent' | 'received') => (data: Buffer | string) => {
|
|
116
121
|
const isBinary = Buffer.isBuffer(data);
|
|
@@ -137,7 +142,7 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
137
142
|
}));
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
conn.on('channel-
|
|
145
|
+
conn.on('channel-created', emitChannelEvents);
|
|
141
146
|
// Due to race conditions somewhere (?) presumably in node-datachannel, channels can
|
|
142
147
|
// be created before the 'connected' event fires, so we need to handle already
|
|
143
148
|
// existing channels here too:
|
|
@@ -149,12 +154,17 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
149
154
|
trackMid: mediaTrack.mid
|
|
150
155
|
};
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
const announceOpen = () => {
|
|
158
|
+
this.eventEmitter.emit('media-track-opened', {
|
|
159
|
+
...trackEventParams,
|
|
160
|
+
trackType: mediaTrack.type,
|
|
161
|
+
trackDirection: mediaTrack.direction,
|
|
162
|
+
eventTimestamp: now()
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (mediaTrack.isOpen) announceOpen();
|
|
167
|
+
else mediaTrack.on('track-open', announceOpen);
|
|
158
168
|
|
|
159
169
|
let previousBytesSent = 0;
|
|
160
170
|
let previousBytesReceived = 0;
|
|
@@ -185,7 +195,7 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
185
195
|
});
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
conn.on('track-
|
|
198
|
+
conn.on('track-created', emitTrackEvents);
|
|
189
199
|
// Due to race conditions somewhere (?) presumably in node-datachannel, tracks can
|
|
190
200
|
// be created before the 'connected' event fires, so we need to handle already
|
|
191
201
|
// existing tracks here too:
|
|
@@ -255,13 +265,13 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
255
265
|
const channelLabel = channel.label;
|
|
256
266
|
const messageLog = (this.messages[channelLabel] ??= []);
|
|
257
267
|
|
|
258
|
-
channel.on('data', d => {
|
|
268
|
+
channel.on('read-data', d => {
|
|
259
269
|
messageLog.push(d);
|
|
260
270
|
});
|
|
261
271
|
};
|
|
262
272
|
|
|
263
273
|
conn.channels.forEach(logChannelMessages);
|
|
264
|
-
conn.on('channel-
|
|
274
|
+
conn.on('channel-created', logChannelMessages);
|
|
265
275
|
}
|
|
266
276
|
|
|
267
277
|
return conn;
|
|
@@ -298,7 +308,7 @@ export class MockRTCServerPeer implements MockRTCPeer {
|
|
|
298
308
|
private async handleConnection(conn: MockRTCConnection) {
|
|
299
309
|
await conn.waitUntilConnected();
|
|
300
310
|
|
|
301
|
-
const handlerSteps = this.getHandlerSteps(conn);
|
|
311
|
+
const handlerSteps = await this.getHandlerSteps(conn);
|
|
302
312
|
|
|
303
313
|
for (const step of handlerSteps) {
|
|
304
314
|
await step.handle(conn);
|
|
@@ -5,23 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
|
|
8
|
-
import { MockRTC, MockRTCEvent, MockRTCOptions
|
|
8
|
+
import { MockRTC, MockRTCEvent, MockRTCOptions } from "../mockrtc";
|
|
9
|
+
import { MockRTCBase } from "../mockrtc-base";
|
|
9
10
|
import { MockRTCServerPeer } from "./mockrtc-server-peer";
|
|
10
|
-
import { MockRTCHandlerBuilder } from "../handling/handler-builder";
|
|
11
|
-
import { HandlerStepDefinition } from "../handling/handler-step-definitions";
|
|
12
|
-
import { DynamicProxyStep, StepLookup } from "../handling/handler-steps";
|
|
13
|
-
import { RTCConnection } from "../main";
|
|
14
11
|
import { MockRTCPeer } from "../mockrtc-peer";
|
|
12
|
+
import { RTCConnection } from "../webrtc/rtc-connection";
|
|
13
|
+
|
|
14
|
+
import type { MatcherDefinition } from "../matching/matcher-definitions";
|
|
15
|
+
import { Matcher, MatcherLookup } from "../matching/matchers";
|
|
16
|
+
import type { HandlerStepDefinition } from "../handling/handler-step-definitions";
|
|
17
|
+
import { DynamicProxyStep, HandlerStep, StepLookup } from "../handling/handler-steps";
|
|
15
18
|
|
|
16
19
|
const MATCHING_PEER_ID = 'matching-peer';
|
|
17
20
|
|
|
18
|
-
export class MockRTCServer implements MockRTC {
|
|
21
|
+
export class MockRTCServer extends MockRTCBase implements MockRTC {
|
|
19
22
|
|
|
20
23
|
private debug: boolean = false;
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
23
26
|
private options: MockRTCOptions = {}
|
|
24
27
|
) {
|
|
28
|
+
super();
|
|
25
29
|
this.debug = !!options.debug;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -51,6 +55,7 @@ export class MockRTCServer implements MockRTC {
|
|
|
51
55
|
|
|
52
56
|
this._activePeers = {};
|
|
53
57
|
this.matchingPeer = undefined;
|
|
58
|
+
this.rules = [];
|
|
54
59
|
|
|
55
60
|
this.eventEmitter.removeAllListeners();
|
|
56
61
|
}
|
|
@@ -80,20 +85,71 @@ export class MockRTCServer implements MockRTC {
|
|
|
80
85
|
return this.matchingPeer;
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
private rules: Array<{
|
|
89
|
+
matchers: Matcher[],
|
|
90
|
+
handlerSteps: HandlerStep[]
|
|
91
|
+
}> = [];
|
|
92
|
+
|
|
93
|
+
async setRulesFromDefinitions(
|
|
94
|
+
rules: Array<{
|
|
95
|
+
matchers: MatcherDefinition[],
|
|
96
|
+
steps: HandlerStepDefinition[]
|
|
97
|
+
}>
|
|
98
|
+
) {
|
|
99
|
+
this.rules = [];
|
|
100
|
+
await Promise.all(rules.map(({ matchers, steps }) =>
|
|
101
|
+
this.addRuleFromDefinition(matchers, steps)
|
|
102
|
+
));
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
|
|
105
|
+
async addRuleFromDefinition(
|
|
106
|
+
matcherDefinitions: MatcherDefinition[],
|
|
107
|
+
handlerStepDefinitions: HandlerStepDefinition[]
|
|
108
|
+
) {
|
|
109
|
+
const matchers = matcherDefinitions.map((definition): Matcher => {
|
|
110
|
+
return Object.assign(
|
|
111
|
+
Object.create(MatcherLookup[definition.type].prototype),
|
|
112
|
+
definition
|
|
113
|
+
);
|
|
114
|
+
});
|
|
90
115
|
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
const handlerSteps = handlerStepDefinitions.map((definition): HandlerStep => {
|
|
117
|
+
return Object.assign(
|
|
118
|
+
Object.create(StepLookup[definition.type].prototype),
|
|
119
|
+
definition
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.rules.push({ matchers, handlerSteps });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async matchConnection(connection: RTCConnection) {
|
|
127
|
+
if (this.debug) console.log('Matching incoming RTC connection...');
|
|
128
|
+
await connection.waitUntilConnected();
|
|
129
|
+
|
|
130
|
+
for (const rule of this.rules) {
|
|
131
|
+
const matches = rule.matchers.every(matcher => matcher.matches(connection));
|
|
132
|
+
|
|
133
|
+
if (matches) {
|
|
134
|
+
if (this.debug) console.log(`Matched incoming RTC connection, running steps: ${
|
|
135
|
+
rule.handlerSteps.map(s => s.type).join(', ')
|
|
136
|
+
}`);
|
|
137
|
+
|
|
138
|
+
return rule.handlerSteps;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.debug) console.log('RTC connection did not match any rules');
|
|
143
|
+
|
|
144
|
+
// Unmatched connections are proxied dynamically. In practice, that means they're accepted
|
|
145
|
+
// and ignored initially, unless an external peer also connects and is attached:
|
|
146
|
+
return [new DynamicProxyStep()];
|
|
93
147
|
}
|
|
94
148
|
|
|
95
|
-
|
|
96
|
-
|
|
149
|
+
// Peer definition API:
|
|
150
|
+
|
|
151
|
+
async buildPeerFromDefinition(handlerStepDefinitions: HandlerStepDefinition[]): Promise<MockRTCServerPeer> {
|
|
152
|
+
const handlerSteps = handlerStepDefinitions.map((definition): HandlerStep => {
|
|
97
153
|
return Object.assign(
|
|
98
154
|
Object.create(StepLookup[definition.type].prototype),
|
|
99
155
|
definition
|
|
@@ -57,10 +57,24 @@ export class DataChannelStream extends stream.Duplex {
|
|
|
57
57
|
// Buffer all writes until the DataChannel opens
|
|
58
58
|
if (!rawChannel.isOpen()) {
|
|
59
59
|
this.cork();
|
|
60
|
-
rawChannel.onOpen(() =>
|
|
60
|
+
rawChannel.onOpen(() => {
|
|
61
|
+
this.uncork();
|
|
62
|
+
this._isOpen = true;
|
|
63
|
+
this.emit('channel-open');
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
setImmediate(() => {
|
|
67
|
+
this._isOpen = true;
|
|
68
|
+
this.emit('channel-open');
|
|
69
|
+
});
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
73
|
+
private _isOpen = false;
|
|
74
|
+
get isOpen() {
|
|
75
|
+
return this._isOpen;
|
|
76
|
+
}
|
|
77
|
+
|
|
64
78
|
private _readActive = true;
|
|
65
79
|
_read() {
|
|
66
80
|
// Stop dropping messages, if the buffer filling up meant we were doing so before.
|
|
@@ -48,10 +48,24 @@ export class MediaTrackStream extends stream.Duplex {
|
|
|
48
48
|
// Buffer all writes until the DataChannel opens
|
|
49
49
|
if (!rawTrack.isOpen()) {
|
|
50
50
|
this.cork();
|
|
51
|
-
rawTrack.onOpen(() =>
|
|
51
|
+
rawTrack.onOpen(() => {
|
|
52
|
+
this.uncork();
|
|
53
|
+
this._isOpen = true;
|
|
54
|
+
this.emit('track-open');
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
setImmediate(() => {
|
|
58
|
+
this._isOpen = true;
|
|
59
|
+
this.emit('track-open');
|
|
60
|
+
});
|
|
52
61
|
}
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
private _isOpen = false;
|
|
65
|
+
get isOpen() {
|
|
66
|
+
return this._isOpen;
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
private _totalBytesSent = 0;
|
|
56
70
|
get totalBytesSent() {
|
|
57
71
|
return this._totalBytesSent;
|
|
@@ -143,7 +143,7 @@ export class MockRTCConnection extends RTCConnection {
|
|
|
143
143
|
|
|
144
144
|
// If any new channels open in future, mirror them to the other peer:
|
|
145
145
|
[[this, externalConnection], [externalConnection, this]].forEach(([connA, connB]) => {
|
|
146
|
-
connA.on('remote-channel-
|
|
146
|
+
connA.on('remote-channel-created', (incomingChannel: DataChannelStream) => {
|
|
147
147
|
const mirrorChannelStream = connB.createDataChannel(incomingChannel.label);
|
|
148
148
|
incomingChannel.pipe(mirrorChannelStream).pipe(incomingChannel);
|
|
149
149
|
});
|