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.
Files changed (136) hide show
  1. package/.github/workflows/ci.yml +29 -0
  2. package/LICENSE +201 -0
  3. package/README.md +290 -0
  4. package/dist/admin-bin.d.ts +2 -0
  5. package/dist/admin-bin.js +67 -0
  6. package/dist/admin-bin.js.map +1 -0
  7. package/dist/client/mockrtc-client.d.ts +12 -0
  8. package/dist/client/mockrtc-client.js +67 -0
  9. package/dist/client/mockrtc-client.js.map +1 -0
  10. package/dist/client/mockrtc-remote-peer.d.ts +15 -0
  11. package/dist/client/mockrtc-remote-peer.js +246 -0
  12. package/dist/client/mockrtc-remote-peer.js.map +1 -0
  13. package/dist/control-channel.d.ts +8 -0
  14. package/dist/control-channel.js +11 -0
  15. package/dist/control-channel.js.map +1 -0
  16. package/dist/handling/handler-builder.d.ts +138 -0
  17. package/dist/handling/handler-builder.js +164 -0
  18. package/dist/handling/handler-builder.js.map +1 -0
  19. package/dist/handling/handler-step-definitions.d.ts +63 -0
  20. package/dist/handling/handler-step-definitions.js +123 -0
  21. package/dist/handling/handler-step-definitions.js.map +1 -0
  22. package/dist/handling/handler-steps.d.ts +48 -0
  23. package/dist/handling/handler-steps.js +218 -0
  24. package/dist/handling/handler-steps.js.map +1 -0
  25. package/dist/main-browser.d.ts +9 -0
  26. package/dist/main-browser.js +26 -0
  27. package/dist/main-browser.js.map +1 -0
  28. package/dist/main.d.ts +58 -0
  29. package/dist/main.js +67 -0
  30. package/dist/main.js.map +1 -0
  31. package/dist/mockrtc-admin-plugin.d.ts +56 -0
  32. package/dist/mockrtc-admin-plugin.js +151 -0
  33. package/dist/mockrtc-admin-plugin.js.map +1 -0
  34. package/dist/mockrtc-admin-server.d.ts +7 -0
  35. package/dist/mockrtc-admin-server.js +18 -0
  36. package/dist/mockrtc-admin-server.js.map +1 -0
  37. package/dist/mockrtc-client.d.ts +12 -0
  38. package/dist/mockrtc-client.js +64 -0
  39. package/dist/mockrtc-client.js.map +1 -0
  40. package/dist/mockrtc-handler-builder.d.ts +15 -0
  41. package/dist/mockrtc-handler-builder.js +24 -0
  42. package/dist/mockrtc-handler-builder.js.map +1 -0
  43. package/dist/mockrtc-peer.d.ts +147 -0
  44. package/dist/mockrtc-peer.js +7 -0
  45. package/dist/mockrtc-peer.js.map +1 -0
  46. package/dist/mockrtc-remote-peer.d.ts +15 -0
  47. package/dist/mockrtc-remote-peer.js +234 -0
  48. package/dist/mockrtc-remote-peer.js.map +1 -0
  49. package/dist/mockrtc-server-peer.d.ts +29 -0
  50. package/dist/mockrtc-server-peer.js +145 -0
  51. package/dist/mockrtc-server-peer.js.map +1 -0
  52. package/dist/mockrtc-server.d.ts +14 -0
  53. package/dist/mockrtc-server.js +53 -0
  54. package/dist/mockrtc-server.js.map +1 -0
  55. package/dist/mockrtc.d.ts +25 -0
  56. package/dist/mockrtc.js +7 -0
  57. package/dist/mockrtc.js.map +1 -0
  58. package/dist/package.json +52 -0
  59. package/dist/server/mockrtc-admin-plugin.d.ts +17 -0
  60. package/dist/server/mockrtc-admin-plugin.js +163 -0
  61. package/dist/server/mockrtc-admin-plugin.js.map +1 -0
  62. package/dist/server/mockrtc-admin-server.d.ts +7 -0
  63. package/dist/server/mockrtc-admin-server.js +18 -0
  64. package/dist/server/mockrtc-admin-server.js.map +1 -0
  65. package/dist/server/mockrtc-server-peer.d.ts +24 -0
  66. package/dist/server/mockrtc-server-peer.js +141 -0
  67. package/dist/server/mockrtc-server-peer.js.map +1 -0
  68. package/dist/server/mockrtc-server.d.ts +14 -0
  69. package/dist/server/mockrtc-server.js +53 -0
  70. package/dist/server/mockrtc-server.js.map +1 -0
  71. package/dist/src/main.d.ts +1 -0
  72. package/dist/src/main.js +24 -0
  73. package/dist/src/main.js.map +1 -0
  74. package/dist/src/mockrtc-peer.d.ts +0 -0
  75. package/dist/src/mockrtc-peer.js +2 -0
  76. package/dist/src/mockrtc-peer.js.map +1 -0
  77. package/dist/src/mockrtc.d.ts +0 -0
  78. package/dist/src/mockrtc.js +65 -0
  79. package/dist/src/mockrtc.js.map +1 -0
  80. package/dist/webrtc/control-channel.d.ts +8 -0
  81. package/dist/webrtc/control-channel.js +11 -0
  82. package/dist/webrtc/control-channel.js.map +1 -0
  83. package/dist/webrtc/datachannel-stream.d.ts +25 -0
  84. package/dist/webrtc/datachannel-stream.js +86 -0
  85. package/dist/webrtc/datachannel-stream.js.map +1 -0
  86. package/dist/webrtc/mediatrack-stream.d.ts +29 -0
  87. package/dist/webrtc/mediatrack-stream.js +109 -0
  88. package/dist/webrtc/mediatrack-stream.js.map +1 -0
  89. package/dist/webrtc/mockrtc-connection.d.ts +14 -0
  90. package/dist/webrtc/mockrtc-connection.js +147 -0
  91. package/dist/webrtc/mockrtc-connection.js.map +1 -0
  92. package/dist/webrtc/peer-connection.d.ts +16 -0
  93. package/dist/webrtc/peer-connection.js +81 -0
  94. package/dist/webrtc/peer-connection.js.map +1 -0
  95. package/dist/webrtc/rtc-connection.d.ts +47 -0
  96. package/dist/webrtc/rtc-connection.js +370 -0
  97. package/dist/webrtc/rtc-connection.js.map +1 -0
  98. package/dist/webrtc-hooks.d.ts +30 -0
  99. package/dist/webrtc-hooks.js +224 -0
  100. package/dist/webrtc-hooks.js.map +1 -0
  101. package/karma.conf.ts +89 -0
  102. package/ngi-eu-footer.png +0 -0
  103. package/package.json +86 -0
  104. package/src/admin-bin.ts +57 -0
  105. package/src/client/mockrtc-client.ts +79 -0
  106. package/src/client/mockrtc-remote-peer.ts +286 -0
  107. package/src/handling/handler-builder.ts +215 -0
  108. package/src/handling/handler-step-definitions.ts +142 -0
  109. package/src/handling/handler-steps.ts +254 -0
  110. package/src/main-browser.ts +44 -0
  111. package/src/main.ts +109 -0
  112. package/src/mockrtc-peer.ts +176 -0
  113. package/src/mockrtc.ts +36 -0
  114. package/src/server/mockrtc-admin-plugin.ts +196 -0
  115. package/src/server/mockrtc-admin-server.ts +17 -0
  116. package/src/server/mockrtc-server-peer.ts +159 -0
  117. package/src/server/mockrtc-server.ts +53 -0
  118. package/src/webrtc/control-channel.ts +13 -0
  119. package/src/webrtc/datachannel-stream.ts +102 -0
  120. package/src/webrtc/mediatrack-stream.ts +135 -0
  121. package/src/webrtc/mockrtc-connection.ts +164 -0
  122. package/src/webrtc/rtc-connection.ts +420 -0
  123. package/src/webrtc-hooks.ts +245 -0
  124. package/test/integration/close-steps.spec.ts +39 -0
  125. package/test/integration/connection-setup.spec.ts +230 -0
  126. package/test/integration/echo-steps.spec.ts +88 -0
  127. package/test/integration/proxy.spec.ts +526 -0
  128. package/test/integration/send-steps.spec.ts +76 -0
  129. package/test/integration/smoke-test.spec.ts +100 -0
  130. package/test/integration/wait-steps.spec.ts +225 -0
  131. package/test/start-test-admin-server.ts +12 -0
  132. package/test/test-setup.ts +136 -0
  133. package/test/tsconfig.json +11 -0
  134. package/tsconfig.json +14 -0
  135. package/typedoc.json +19 -0
  136. package/wallaby.js +41 -0
package/src/main.ts ADDED
@@ -0,0 +1,109 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import {
7
+ MockRTC,
8
+ MockRTCOptions,
9
+ MockRTCPeerBuilder
10
+ } from "./mockrtc";
11
+
12
+ import { MockRTCServer } from "./server/mockrtc-server";
13
+ import { MockRTCAdminServer } from "./server/mockrtc-admin-server";
14
+ export { MockRTCAdminPlugin } from "./server/mockrtc-admin-plugin";
15
+
16
+ import { MockRTCClient, MockRTCClientOptions } from "./client/mockrtc-client";
17
+
18
+ // Re-export lots of types are used in various APIs (mostly to make TypeDoc happy):
19
+ export type { HandlerStepDefinition } from "./handling/handler-step-definitions";
20
+ export type { HandlerStep } from "./handling/handler-steps";
21
+ export type { MockRTCHandlerBuilder } from "./handling/handler-builder";
22
+
23
+ export type { MockRTCServerPeer } from "./server/mockrtc-server-peer";
24
+ export type { SessionData } from "./server/mockrtc-admin-plugin";
25
+
26
+ export type { RTCConnection } from "./webrtc/rtc-connection";
27
+ export type { MockRTCConnection } from "./webrtc/mockrtc-connection";
28
+ export type { DataChannelStream } from "./webrtc/datachannel-stream";
29
+ export type { MediaTrackStream } from "./webrtc/mediatrack-stream";
30
+
31
+ export type { PluggableAdmin } from 'mockttp';
32
+
33
+ export type {
34
+ MockRTC,
35
+ MockRTCOptions,
36
+ MockRTCClientOptions,
37
+ MockRTCPeerBuilder,
38
+ MockRTCAdminServer
39
+ };
40
+
41
+ export type {
42
+ MockRTCPeer,
43
+ MockRTCPeerOptions,
44
+ MockRTCSession,
45
+ MockRTCOfferParams,
46
+ MockRTCAnswerParams,
47
+ MockRTCExternalOfferParams,
48
+ MockRTCExternalAnswerParams,
49
+ OfferOptions,
50
+ AnswerOptions
51
+ } from './mockrtc-peer';
52
+
53
+ export {
54
+ MOCKRTC_CONTROL_CHANNEL,
55
+ type MockRTCControlMessage
56
+ } from './webrtc/control-channel';
57
+ export {
58
+ hookWebRTCConnection,
59
+ hookAllWebRTC
60
+ } from "./webrtc-hooks";
61
+
62
+ /**
63
+ * Get a MockRTC instance on the local machine.
64
+ *
65
+ * In most simple environments, you can call this method directly and immediately
66
+ * get a MockRTC instance and start mocking peers.
67
+ *
68
+ * In node, the mocked peers will run in process and require no further setup.
69
+ *
70
+ * In browsers this is an alias for {@link getRemote}. You'll need to start a MockRTC
71
+ * admin server outside your tests before calling this, which will create and manage
72
+ * your fake peers outside the browser.
73
+ *
74
+ * @category API
75
+ */
76
+ export function getLocal(): MockRTC {
77
+ return new MockRTCServer();
78
+ }
79
+
80
+ /**
81
+ * Get a MockRTC instance, managed by a MockRTC admin server running elsewhere.
82
+ *
83
+ * This connects to a MockRTC server, and uses that to start
84
+ * and stop mock peers.
85
+ *
86
+ * @category API
87
+ */
88
+ export function getRemote(options: MockRTCClientOptions = {}): MockRTC {
89
+ return new MockRTCClient(options);
90
+ }
91
+
92
+ /**
93
+ * Get a MockRTC admin server, which can be used with a MockRTC remote client to create
94
+ * & manage mock peers either from remote machines or from local environments
95
+ * that lack necessary capabilities, e.g. to use MockRTC from inside a browser.
96
+ *
97
+ * This function exists so you can set up these servers programmatically, but for most
98
+ * usage you can just run your tests via the `mockrtc` binary, which will automatically
99
+ * start and stop an admin server for you:
100
+ *
101
+ * ```
102
+ * mockrtc -c <your test command>
103
+ * ```
104
+ *
105
+ * @category API
106
+ */
107
+ export function getAdminServer(): MockRTCAdminServer {
108
+ return new MockRTCAdminServer();
109
+ }
@@ -0,0 +1,176 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ export interface MockRTCPeerOptions {
7
+ recordMessages?: boolean;
8
+ }
9
+
10
+ /**
11
+ * A MockRTC peer represents a target you can connect to, and exposes an API to create
12
+ * offers or answers to create new connections.
13
+ *
14
+ * Peers have defined behaviour, and each connection will be handled accordingly and
15
+ * independently.
16
+ *
17
+ * Peers can also optionally track all the messages and metadata across all their
18
+ * connections.
19
+ */
20
+ export interface MockRTCPeer {
21
+ readonly peerId: string;
22
+
23
+ /**
24
+ * Creates an offer for a new connection to this mock peer.
25
+ *
26
+ * Returns a set of offer parameters: the offer itself, a session to renegotiate
27
+ * the connection in future, and a setAnswer callback to call with an answer
28
+ * once you have one.
29
+ */
30
+ createOffer(options?: OfferOptions): Promise<MockRTCOfferParams>;
31
+
32
+ /**
33
+ * Takes an offer for a WebRTC connection elsewhere, and creates an answer to
34
+ * connect that to this peer.
35
+ *
36
+ * Returns a set of answer parameters: the answer itself, and a session to renegotiate
37
+ * the connection in future.
38
+ */
39
+ answerOffer(
40
+ offer: RTCSessionDescriptionInit,
41
+ options?: AnswerOptions
42
+ ): Promise<MockRTCAnswerParams>;
43
+
44
+ /**
45
+ * Creates an offer for a new external connection to this mock peer.
46
+ *
47
+ * External connections are used for proxying traffic. They do not do anything
48
+ * by default (so they ignore this peer's configured steps) but a mock connection
49
+ * can be connected to an external connection using methods like
50
+ * {@link MockRTCHandlerBuilder.thenPassThrough thenPassThrough}.
51
+ *
52
+ * Returns a set of offer parameters: an external connection id, the offer itself,
53
+ * a session to renegotiate the connection in future, and a setAnswer callback to
54
+ * call with an answer once you have one.
55
+ */
56
+ createExternalOffer(options?: OfferOptions): Promise<MockRTCExternalOfferParams>;
57
+
58
+ /**
59
+ * Takes an offer for a WebRTC connection elsewhere, and creates an answer to create
60
+ * an external connection to this peer.
61
+ *
62
+ * External connections are used for proxying traffic. They do not do anything
63
+ * by default (so they ignore this peer's configured steps) but a mock connection
64
+ * can be connected to an external connection using methods like
65
+ * {@link MockRTCHandlerBuilder.thenPassThrough thenPassThrough}.
66
+ *
67
+ * Returns a set of answer parameters: an external connection id, the answer itself,
68
+ * and a session to renegotiate the connection in future.
69
+ */
70
+ answerExternalOffer(
71
+ offer: RTCSessionDescriptionInit,
72
+ options?: AnswerOptions
73
+ ): Promise<MockRTCExternalAnswerParams>;
74
+
75
+ /**
76
+ * Takes a connection id, and returns the associated session.
77
+ *
78
+ * This is useful for advanced use cases, where keeping the session returned by other
79
+ * setup methods is inconvenient, and it's easier to keep ids and look up sessions
80
+ * on demand instead.
81
+ */
82
+ getSession(id: string): MockRTCSession;
83
+
84
+ /**
85
+ * Retrieve an array of all data channel messages that this peer has received on
86
+ * all connections.
87
+ */
88
+ getAllMessages(): Promise<Array<string | Buffer>>;
89
+
90
+ /**
91
+ * Retrieve an array of all data channel messages on a specific channel that this
92
+ * peer has received on all connections.
93
+ */
94
+ getMessagesOnChannel(channelName: string): Promise<Array<string | Buffer>>;
95
+ }
96
+
97
+ /**
98
+ * Once a connection has been created, you can access its session API. This allows
99
+ * for renegotiation of an existing session, while persisting the same connection
100
+ * and ongoing handling process.
101
+ */
102
+ export interface MockRTCSession {
103
+ /**
104
+ * For most use cases explicitly using the session ID isn't necessary.
105
+ *
106
+ * For some advanced use cases though, it's more convenient to store session ids and use
107
+ * peer.getSession, rather than using the session property from the setup methods directly.
108
+ */
109
+ readonly sessionId: string;
110
+
111
+ /**
112
+ * Create a new offer for this session, to renegotiate the existing connection.
113
+ */
114
+ createOffer(options?: OfferOptions): Promise<RTCSessionDescriptionInit>;
115
+
116
+ /**
117
+ * Provide an answer to complete an offer for this session, to renegotiate the existing connection.
118
+ */
119
+ completeOffer(answer: RTCSessionDescriptionInit): Promise<void>;
120
+
121
+ /**
122
+ * Get an answer given an offer from elsewhere, to renegotiate the existing connection.
123
+ */
124
+ answerOffer(offer: RTCSessionDescriptionInit, options?: AnswerOptions): Promise<RTCSessionDescriptionInit>;
125
+ }
126
+
127
+ export interface MockRTCOfferParams {
128
+ offer: RTCSessionDescriptionInit;
129
+ setAnswer: (answer: RTCSessionDescriptionInit) => Promise<void>;
130
+ session: MockRTCSession;
131
+ }
132
+
133
+ export interface MockRTCAnswerParams {
134
+ answer: RTCSessionDescriptionInit;
135
+ session: MockRTCSession;
136
+ }
137
+
138
+ export interface OfferOptions {
139
+ /**
140
+ * A raw SDP string that should be mirrored (best efforts) where possible to
141
+ * create an equivalent offer, including the same media with the same params.
142
+ */
143
+ mirrorSDP?: string;
144
+
145
+ /**
146
+ * When using mirrorSDP, for SDP that only defines video/audio media we will
147
+ * receive an offer with no data stream attached. This can be a problem for
148
+ * proxied connections, which need a data stream to hook up the external
149
+ * connection later. If addDataStream is set to true, a data stream will always
150
+ * be created even if not present in the mirrored SDP.
151
+ *
152
+ * This option has no effect if mirrorSDP is not set.
153
+ */
154
+ addDataStream?: boolean;
155
+ }
156
+
157
+ export interface AnswerOptions {
158
+ /**
159
+ * A raw SDP string that should be mirrored (best efforts) where possible to
160
+ * create an equivalent answer, including the same media params.
161
+ */
162
+ mirrorSDP?: string;
163
+ }
164
+
165
+ export interface MockRTCExternalOfferParams {
166
+ id: string; // Used for external attach control messages
167
+ offer: RTCSessionDescriptionInit;
168
+ setAnswer: (answer: RTCSessionDescriptionInit) => Promise<void>;
169
+ session: MockRTCSession;
170
+ }
171
+
172
+ export interface MockRTCExternalAnswerParams {
173
+ id: string; // Used for external attach control messagesz
174
+ answer: RTCSessionDescriptionInit;
175
+ session: MockRTCSession;
176
+ }
package/src/mockrtc.ts ADDED
@@ -0,0 +1,36 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import type { MockRTCHandlerBuilder } from "./handling/handler-builder";
7
+ import type { MockRTCPeer } from "./mockrtc-peer";
8
+
9
+ export interface MockRTCPeerBuilder extends MockRTCHandlerBuilder<MockRTCPeer> {}
10
+
11
+ export interface MockRTCOptions {
12
+ /**
13
+ * Whether or not all DataChannel messages should be saved for later examination.
14
+ * This can be useful in quick testing, but may use large amounts of data if
15
+ * enabled when proxying lots of traffic.
16
+ *
17
+ * Defaults to false.
18
+ */
19
+ recordMessages?: boolean;
20
+ }
21
+
22
+ export interface MockRTC {
23
+
24
+ /**
25
+ * Start creating a mock WebRTC peer. This method returns a builder, who
26
+ * must be configured with the mock peer's settings. Once configured the
27
+ * peer can be created by calling any `.thenX()` method to define the
28
+ * peer's behaviour.
29
+ */
30
+ buildPeer(): MockRTCPeerBuilder;
31
+
32
+ start(): Promise<void>;
33
+
34
+ stop(): Promise<void>;
35
+
36
+ }
@@ -0,0 +1,196 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import * as stream from 'stream';
7
+ import { gql } from 'graphql-tag';
8
+ import { PluggableAdmin } from 'mockttp';
9
+ import type { IResolvers } from "@graphql-tools/utils";
10
+
11
+ import { HandlerStep, StepLookup } from '../handling/handler-steps';
12
+ import { MockRTCOptions } from '../mockrtc';
13
+ import { MockRTCServer } from './mockrtc-server';
14
+ import { AnswerOptions, OfferOptions } from '../mockrtc-peer';
15
+
16
+ const { deserialize } = PluggableAdmin.Serialization;
17
+ type SerializedValue<T> = PluggableAdmin.Serialization.SerializedValue<T>;
18
+
19
+ export interface SessionData {
20
+ id: string;
21
+ description: RTCSessionDescriptionInit
22
+ }
23
+
24
+ export class MockRTCAdminPlugin implements PluggableAdmin.AdminPlugin<MockRTCOptions, {}> {
25
+
26
+ private mockRTCServer!: MockRTCServer;
27
+
28
+ start(options: MockRTCOptions) {
29
+ this.mockRTCServer = new MockRTCServer(options);
30
+ return this.mockRTCServer.start();
31
+ }
32
+
33
+ reset() {}
34
+
35
+ stop() {
36
+ return this.mockRTCServer.stop();
37
+ }
38
+
39
+ schema = gql`
40
+ extend type Mutation {
41
+ createPeer(data: RTCHandlerData!): MockedPeer!
42
+
43
+ createOffer(peerId: ID!, sessionId: ID, options: Raw): Session!
44
+ createExternalOffer(peerId: ID!, options: Raw): Session!
45
+ completeOffer(peerId: ID!, sessionId: ID!, answer: SessionDescriptionInput!): Void
46
+
47
+ answerOffer(peerId: ID!, sessionId: ID, offer: SessionDescriptionInput!, options: Raw): Session!
48
+ answerExternalOffer(peerId: ID!, offer: SessionDescriptionInput!, options: Raw): Session!
49
+ }
50
+
51
+ input RTCHandlerData {
52
+ steps: [Raw!]!
53
+ }
54
+
55
+ type MockedPeer {
56
+ peerId: ID!
57
+ }
58
+
59
+ input SessionDescriptionInput {
60
+ type: String!
61
+ sdp: String!
62
+ }
63
+
64
+ type SessionDescriptionResult {
65
+ type: String!
66
+ sdp: String!
67
+ }
68
+
69
+ type Session {
70
+ id: ID!
71
+ description: SessionDescriptionResult
72
+ }
73
+
74
+ extend type Query {
75
+ getSeenMessages(peerId: ID!, channelName: String): [Raw!]
76
+ }
77
+
78
+ scalar HandlerStep
79
+ `;
80
+
81
+ buildResolvers(adminStream: stream.Duplex, ruleParams: {}): IResolvers {
82
+ return {
83
+ Mutation: {
84
+ createPeer: (__: any, { data: { steps } }: { data: {
85
+ steps: Array<SerializedValue<HandlerStep>>
86
+ } }) => {
87
+ return this.mockRTCServer.buildPeerFromData(
88
+ steps.map((stepData) =>
89
+ deserialize(stepData, adminStream, ruleParams, StepLookup)
90
+ )
91
+ );
92
+ },
93
+ createOffer: async (__: any, { peerId, sessionId, options }: {
94
+ peerId: string,
95
+ sessionId?: string,
96
+ options?: OfferOptions
97
+ }): Promise<SessionData> => {
98
+ const peer = this.mockRTCServer.getPeer(peerId);
99
+ if (!peer) throw new Error("Id matches no active peer");
100
+
101
+ if (sessionId) {
102
+ const session = peer.getSession(sessionId);
103
+ return {
104
+ id: sessionId,
105
+ description: await session.createOffer(options)
106
+ };
107
+ } else {
108
+ const offerParams = await peer.createOffer(options);
109
+ return {
110
+ id: offerParams.session.sessionId,
111
+ description: offerParams.offer
112
+ };
113
+ }
114
+ },
115
+ createExternalOffer: async (__: any, { peerId, options }: {
116
+ peerId: string,
117
+ options?: OfferOptions
118
+ }): Promise<SessionData> => {
119
+ const peer = this.mockRTCServer.getPeer(peerId);
120
+ if (!peer) throw new Error("Id matches no active peer");
121
+
122
+ const offerParams = await peer.createExternalOffer(options);
123
+ return {
124
+ id: offerParams.id,
125
+ description: offerParams.offer
126
+ };
127
+ },
128
+ completeOffer: async (__: any, { peerId, sessionId, answer } : {
129
+ peerId: string,
130
+ sessionId: string,
131
+ answer: RTCSessionDescriptionInit
132
+ }): Promise<void> => {
133
+ const session = this.mockRTCServer.getPeer(peerId).getSession(sessionId);
134
+ await session.completeOffer(answer);
135
+ },
136
+ answerOffer: async (__: any, { peerId, sessionId, offer, options } : {
137
+ peerId: string,
138
+ sessionId?: string,
139
+ offer: RTCSessionDescriptionInit,
140
+ options?: AnswerOptions
141
+ }): Promise<SessionData> => {
142
+ const peer = this.mockRTCServer.getPeer(peerId);
143
+ if (!peer) throw new Error("Id matches no active peer");
144
+
145
+ if (sessionId) {
146
+ const session = peer.getSession(sessionId);
147
+ const answer = await session.answerOffer(offer, options);
148
+ return { id: sessionId, description: answer };
149
+ } else {
150
+ const answerParams = await peer.answerOffer(offer, options);
151
+ return {
152
+ id: answerParams.session.sessionId,
153
+ description: answerParams.answer
154
+ };
155
+ }
156
+ },
157
+ answerExternalOffer: async (__: any, { peerId, offer, options } : {
158
+ peerId: string,
159
+ offer: RTCSessionDescriptionInit,
160
+ options?: AnswerOptions
161
+ }): Promise<SessionData> => {
162
+ const peer = this.mockRTCServer.getPeer(peerId);
163
+ if (!peer) throw new Error("Id matches no active peer");
164
+
165
+ const answerParams = await peer.answerExternalOffer(offer, options);
166
+ return {
167
+ id: answerParams.id,
168
+ description: answerParams.answer
169
+ };
170
+ }
171
+ },
172
+ Query: {
173
+ getSeenMessages: async (__: any, { peerId, channelName }: {
174
+ peerId: string,
175
+ channelName?: string
176
+ }) => {
177
+ const peer = this.mockRTCServer.getPeer(peerId);
178
+ if (!peer) throw new Error("Id matches no active peer");
179
+
180
+ const messages = await (channelName != undefined
181
+ ? peer.getMessagesOnChannel(channelName)
182
+ : peer.getAllMessages()
183
+ );
184
+
185
+ return messages.map((message) => {
186
+ if (Buffer.isBuffer(message)) {
187
+ return { type: 'buffer', value: message.toString('base64') };
188
+ } else {
189
+ return message;
190
+ }
191
+ });
192
+ }
193
+ }
194
+ };
195
+ }
196
+ }
@@ -0,0 +1,17 @@
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
+ import { MockRTCAdminPlugin } from './mockrtc-admin-plugin';
8
+
9
+ export class MockRTCAdminServer extends PluggableAdmin.AdminServer<{ webrtc: MockRTCAdminPlugin }> {
10
+
11
+ constructor() {
12
+ super({
13
+ adminPlugins: { webrtc: MockRTCAdminPlugin }
14
+ });
15
+ }
16
+
17
+ }
@@ -0,0 +1,159 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2022 Tim Perry <tim@httptoolkit.tech>
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { randomUUID } from 'crypto';
7
+
8
+ import {
9
+ MockRTCPeer,
10
+ MockRTCPeerOptions,
11
+ MockRTCSession,
12
+ MockRTCAnswerParams,
13
+ MockRTCOfferParams,
14
+ MockRTCExternalAnswerParams,
15
+ MockRTCExternalOfferParams,
16
+ OfferOptions,
17
+ AnswerOptions
18
+ } from "../mockrtc-peer";
19
+ import { HandlerStep } from '../handling/handler-steps';
20
+
21
+ import { RTCConnection } from '../webrtc/rtc-connection';
22
+ import { MockRTCConnection } from '../webrtc/mockrtc-connection';
23
+ import { DataChannelStream } from '../webrtc/datachannel-stream';
24
+
25
+ export class MockRTCServerPeer implements MockRTCPeer {
26
+
27
+ readonly peerId = randomUUID();
28
+
29
+ // A list of all currently open connections managed by this peer
30
+ private readonly connections: { [id: string]: RTCConnection } = {};
31
+
32
+ // A subset of the connections: external connections with no assigned internal connection
33
+ private readonly unassignedExternalConnections: { [id: string]: RTCConnection } = {};
34
+
35
+ constructor(
36
+ private handlerSteps: HandlerStep[],
37
+ private options: MockRTCPeerOptions = {}
38
+ ) {}
39
+
40
+ private trackConnection(conn: RTCConnection) {
41
+ this.connections[conn.id] = conn;
42
+ conn.once('connection-closed', () => {
43
+ delete this.connections[conn.id];
44
+ });
45
+ }
46
+
47
+ private getExternalConnection = (id: string) => {
48
+ const externalConn = this.unassignedExternalConnections[id];
49
+ if (!externalConn) throw new Error(`Attempted to connect unknown external conn ${id}`);
50
+ delete this.unassignedExternalConnections[id];
51
+ return externalConn;
52
+ }
53
+
54
+ async createExternalOffer(options: OfferOptions = {}): Promise<MockRTCExternalOfferParams> {
55
+ const externalConn = new RTCConnection();
56
+ this.unassignedExternalConnections[externalConn.id] = externalConn;
57
+ this.trackConnection(externalConn);
58
+
59
+ return {
60
+ id: externalConn.id,
61
+ offer: await externalConn.sessionApi.createOffer(options),
62
+ session: externalConn.sessionApi,
63
+ setAnswer: async (answer: RTCSessionDescriptionInit) => {
64
+ externalConn.sessionApi.completeOffer(answer);
65
+ }
66
+ };
67
+ }
68
+
69
+ async answerExternalOffer(
70
+ offer: RTCSessionDescriptionInit,
71
+ options?: AnswerOptions
72
+ ): Promise<MockRTCExternalAnswerParams> {
73
+ const externalConn = new RTCConnection();
74
+ this.unassignedExternalConnections[externalConn.id] = externalConn;
75
+ this.trackConnection(externalConn);
76
+
77
+ return {
78
+ id: externalConn.id,
79
+ answer: await externalConn.sessionApi.answerOffer(offer, options),
80
+ session: externalConn.sessionApi
81
+ };
82
+ }
83
+
84
+ private createConnection() {
85
+ const conn = new MockRTCConnection(this.getExternalConnection);
86
+ this.trackConnection(conn);
87
+
88
+ this.handleConnection(conn).catch((error) => {
89
+ console.error("Error handling WebRTC connection:", error);
90
+ conn.close().catch(() => {});
91
+ });
92
+
93
+ if (this.options.recordMessages) {
94
+ conn.on('channel-open', (channel: DataChannelStream) => {
95
+ const channelLabel = channel.label;
96
+ const messageLog = (this.messages[channelLabel] ??= []);
97
+
98
+ channel.on('data', d => {
99
+ messageLog.push(d);
100
+ });
101
+ });
102
+ }
103
+
104
+ return conn;
105
+ }
106
+
107
+ async createOffer(options: OfferOptions = {}): Promise<MockRTCOfferParams> {
108
+ const conn = this.createConnection();
109
+
110
+ return {
111
+ offer: await conn.sessionApi.createOffer(options),
112
+ session: conn.sessionApi,
113
+ setAnswer: async (answer) => {
114
+ conn.sessionApi.completeOffer(answer);
115
+ }
116
+ }
117
+ }
118
+
119
+ async answerOffer(offer: RTCSessionDescriptionInit, options: AnswerOptions = {}): Promise<MockRTCAnswerParams> {
120
+ const conn = this.createConnection();
121
+ return {
122
+ answer: await conn.sessionApi.answerOffer(offer, options),
123
+ session: conn.sessionApi
124
+ };
125
+ }
126
+
127
+ getSession(id: string): MockRTCSession {
128
+ return this.connections[id].sessionApi;
129
+ }
130
+
131
+ private async handleConnection(conn: MockRTCConnection) {
132
+ await conn.waitUntilConnected();
133
+
134
+ for (const step of this.handlerSteps) {
135
+ await step.handle(conn);
136
+ }
137
+
138
+ await conn.close();
139
+ }
140
+
141
+ async close() {
142
+ await Promise.all(
143
+ Object.values(this.connections).map(c =>
144
+ c.close()
145
+ )
146
+ );
147
+ }
148
+
149
+ private messages: { [channelName: string]: Array<string | Buffer> } = {};
150
+
151
+ async getAllMessages() {
152
+ return Object.values(this.messages).flat();
153
+ }
154
+
155
+ async getMessagesOnChannel(channelName: string) {
156
+ return this.messages[channelName].flat();
157
+ }
158
+
159
+ }