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.
Files changed (84) hide show
  1. package/.github/workflows/ci.yml +2 -2
  2. package/dist/client/mockrtc-admin-request-builder.d.ts +7 -1
  3. package/dist/client/mockrtc-admin-request-builder.js +30 -0
  4. package/dist/client/mockrtc-admin-request-builder.js.map +1 -1
  5. package/dist/client/mockrtc-client.d.ts +8 -4
  6. package/dist/client/mockrtc-client.js +21 -9
  7. package/dist/client/mockrtc-client.js.map +1 -1
  8. package/dist/handling/handler-builder.d.ts +16 -9
  9. package/dist/handling/handler-builder.js +11 -1
  10. package/dist/handling/handler-builder.js.map +1 -1
  11. package/dist/handling/handler-step-definitions.d.ts +40 -23
  12. package/dist/handling/handler-step-definitions.js +61 -19
  13. package/dist/handling/handler-step-definitions.js.map +1 -1
  14. package/dist/handling/handler-steps.d.ts +4 -1
  15. package/dist/handling/handler-steps.js +26 -16
  16. package/dist/handling/handler-steps.js.map +1 -1
  17. package/dist/main-browser.d.ts +1 -0
  18. package/dist/main-browser.js +2 -1
  19. package/dist/main-browser.js.map +1 -1
  20. package/dist/main.d.ts +5 -3
  21. package/dist/main.js +2 -1
  22. package/dist/main.js.map +1 -1
  23. package/dist/matching/matcher-definitions.d.ts +51 -0
  24. package/dist/matching/matcher-definitions.js +94 -0
  25. package/dist/matching/matcher-definitions.js.map +1 -0
  26. package/dist/matching/matchers.d.ts +27 -0
  27. package/dist/matching/matchers.js +87 -0
  28. package/dist/matching/matchers.js.map +1 -0
  29. package/dist/mockrtc-base.d.ts +16 -0
  30. package/dist/mockrtc-base.js +19 -0
  31. package/dist/mockrtc-base.js.map +1 -0
  32. package/dist/mockrtc-peer.d.ts +9 -2
  33. package/dist/mockrtc.d.ts +62 -0
  34. package/dist/mockrtc.js.map +1 -1
  35. package/dist/rule-builder.d.ts +86 -0
  36. package/dist/rule-builder.js +113 -0
  37. package/dist/rule-builder.js.map +1 -0
  38. package/dist/server/mockrtc-admin-plugin.js +18 -1
  39. package/dist/server/mockrtc-admin-plugin.js.map +1 -1
  40. package/dist/server/mockrtc-server-peer.d.ts +1 -1
  41. package/dist/server/mockrtc-server-peer.js +19 -7
  42. package/dist/server/mockrtc-server-peer.js.map +1 -1
  43. package/dist/server/mockrtc-server.d.ts +12 -5
  44. package/dist/server/mockrtc-server.js +53 -18
  45. package/dist/server/mockrtc-server.js.map +1 -1
  46. package/dist/webrtc/datachannel-stream.d.ts +2 -0
  47. package/dist/webrtc/datachannel-stream.js +15 -1
  48. package/dist/webrtc/datachannel-stream.js.map +1 -1
  49. package/dist/webrtc/mediatrack-stream.d.ts +2 -0
  50. package/dist/webrtc/mediatrack-stream.js +15 -1
  51. package/dist/webrtc/mediatrack-stream.js.map +1 -1
  52. package/dist/webrtc/mockrtc-connection.js +1 -1
  53. package/dist/webrtc/mockrtc-connection.js.map +1 -1
  54. package/dist/webrtc/rtc-connection.d.ts +6 -2
  55. package/dist/webrtc/rtc-connection.js +20 -20
  56. package/dist/webrtc/rtc-connection.js.map +1 -1
  57. package/dist/webrtc-hooks.js +8 -2
  58. package/dist/webrtc-hooks.js.map +1 -1
  59. package/package.json +1 -1
  60. package/src/client/mockrtc-admin-request-builder.ts +49 -1
  61. package/src/client/mockrtc-client.ts +28 -10
  62. package/src/handling/handler-builder.ts +22 -10
  63. package/src/handling/handler-step-definitions.ts +81 -23
  64. package/src/handling/handler-steps.ts +28 -15
  65. package/src/main-browser.ts +1 -0
  66. package/src/main.ts +5 -1
  67. package/src/matching/matcher-definitions.ts +109 -0
  68. package/src/matching/matchers.ts +118 -0
  69. package/src/mockrtc-base.ts +49 -0
  70. package/src/mockrtc-peer.ts +9 -2
  71. package/src/mockrtc.ts +72 -0
  72. package/src/rule-builder.ts +142 -0
  73. package/src/server/mockrtc-admin-plugin.ts +41 -3
  74. package/src/server/mockrtc-server-peer.ts +28 -18
  75. package/src/server/mockrtc-server.ts +71 -15
  76. package/src/webrtc/datachannel-stream.ts +15 -1
  77. package/src/webrtc/mediatrack-stream.ts +15 -1
  78. package/src/webrtc/mockrtc-connection.ts +1 -1
  79. package/src/webrtc/rtc-connection.ts +36 -19
  80. package/src/webrtc-hooks.ts +8 -2
  81. package/test/integration/events.spec.ts +3 -1
  82. package/test/integration/matching.spec.ts +189 -0
  83. package/test/integration/proxy.spec.ts +4 -2
  84. 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 { HandlerStep, StepLookup } from '../handling/handler-steps';
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<HandlerStep>>
240
+ steps: Array<SerializedValue<HandlerStepDefinition>>
231
241
  } }) => {
232
- return this.mockRTCServer.buildPeerFromData(
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) => HandlerStep[],
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
- this.eventEmitter.emit('data-channel-opened', {
109
- ...channelEventParams,
110
- channelLabel: channelStream.label,
111
- channelProtocol: channelStream.protocol,
112
- eventTimestamp: now()
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-open', emitChannelEvents);
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
- this.eventEmitter.emit('media-track-opened', {
153
- ...trackEventParams,
154
- trackType: mediaTrack.type,
155
- trackDirection: mediaTrack.direction,
156
- eventTimestamp: now()
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-open', emitTrackEvents);
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-open', logChannelMessages);
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, MockRTCPeerBuilder } from "../mockrtc";
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 matchConnection(connection: RTCConnection) {
84
- return [
85
- new DynamicProxyStep()
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
- // Peer definition API:
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
- buildPeer(): MockRTCPeerBuilder {
92
- return new MockRTCHandlerBuilder(this.buildPeerFromData);
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
- buildPeerFromData = async (handlerStepDefinitions: HandlerStepDefinition[]): Promise<MockRTCServerPeer> => {
96
- const handlerSteps = handlerStepDefinitions.map((definition) => {
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(() => this.uncork());
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(() => this.uncork());
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-open', (incomingChannel: DataChannelStream) => {
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
  });