mocktp 0.0.1-security → 3.15.3

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.

Potentially problematic release.


This version of mocktp might be problematic. Click here for more details.

Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +123 -3
  3. package/custom-typings/Function.d.ts +4 -0
  4. package/custom-typings/cors-gate.d.ts +13 -0
  5. package/custom-typings/http-proxy-agent.d.ts +9 -0
  6. package/custom-typings/node-type-extensions.d.ts +115 -0
  7. package/custom-typings/proxy-agent-modules.d.ts +5 -0
  8. package/custom-typings/request-promise-native.d.ts +28 -0
  9. package/custom-typings/zstd-codec.d.ts +20 -0
  10. package/dist/admin/admin-bin.d.ts +3 -0
  11. package/dist/admin/admin-bin.d.ts.map +1 -0
  12. package/dist/admin/admin-bin.js +61 -0
  13. package/dist/admin/admin-bin.js.map +1 -0
  14. package/dist/admin/admin-plugin-types.d.ts +29 -0
  15. package/dist/admin/admin-plugin-types.d.ts.map +1 -0
  16. package/dist/admin/admin-plugin-types.js +3 -0
  17. package/dist/admin/admin-plugin-types.js.map +1 -0
  18. package/dist/admin/admin-server.d.ts +98 -0
  19. package/dist/admin/admin-server.d.ts.map +1 -0
  20. package/dist/admin/admin-server.js +426 -0
  21. package/dist/admin/admin-server.js.map +1 -0
  22. package/dist/admin/graphql-utils.d.ts +4 -0
  23. package/dist/admin/graphql-utils.d.ts.map +1 -0
  24. package/dist/admin/graphql-utils.js +28 -0
  25. package/dist/admin/graphql-utils.js.map +1 -0
  26. package/dist/admin/mockttp-admin-model.d.ts +7 -0
  27. package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
  28. package/dist/admin/mockttp-admin-model.js +214 -0
  29. package/dist/admin/mockttp-admin-model.js.map +1 -0
  30. package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
  31. package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
  32. package/dist/admin/mockttp-admin-plugin.js +37 -0
  33. package/dist/admin/mockttp-admin-plugin.js.map +1 -0
  34. package/dist/admin/mockttp-admin-server.d.ts +16 -0
  35. package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
  36. package/dist/admin/mockttp-admin-server.js +17 -0
  37. package/dist/admin/mockttp-admin-server.js.map +1 -0
  38. package/dist/admin/mockttp-schema.d.ts +2 -0
  39. package/dist/admin/mockttp-schema.d.ts.map +1 -0
  40. package/dist/admin/mockttp-schema.js +225 -0
  41. package/dist/admin/mockttp-schema.js.map +1 -0
  42. package/dist/client/admin-client.d.ts +112 -0
  43. package/dist/client/admin-client.d.ts.map +1 -0
  44. package/dist/client/admin-client.js +511 -0
  45. package/dist/client/admin-client.js.map +1 -0
  46. package/dist/client/admin-query.d.ts +13 -0
  47. package/dist/client/admin-query.d.ts.map +1 -0
  48. package/dist/client/admin-query.js +26 -0
  49. package/dist/client/admin-query.js.map +1 -0
  50. package/dist/client/mocked-endpoint-client.d.ts +12 -0
  51. package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
  52. package/dist/client/mocked-endpoint-client.js +33 -0
  53. package/dist/client/mocked-endpoint-client.js.map +1 -0
  54. package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
  55. package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
  56. package/dist/client/mockttp-admin-request-builder.js +462 -0
  57. package/dist/client/mockttp-admin-request-builder.js.map +1 -0
  58. package/dist/client/mockttp-client.d.ts +56 -0
  59. package/dist/client/mockttp-client.d.ts.map +1 -0
  60. package/dist/client/mockttp-client.js +112 -0
  61. package/dist/client/mockttp-client.js.map +1 -0
  62. package/dist/client/schema-introspection.d.ts +11 -0
  63. package/dist/client/schema-introspection.d.ts.map +1 -0
  64. package/dist/client/schema-introspection.js +128 -0
  65. package/dist/client/schema-introspection.js.map +1 -0
  66. package/dist/main.browser.d.ts +49 -0
  67. package/dist/main.browser.d.ts.map +1 -0
  68. package/dist/main.browser.js +57 -0
  69. package/dist/main.browser.js.map +1 -0
  70. package/dist/main.d.ts +86 -0
  71. package/dist/main.d.ts.map +1 -0
  72. package/dist/main.js +108 -0
  73. package/dist/main.js.map +1 -0
  74. package/dist/mockttp.d.ts +774 -0
  75. package/dist/mockttp.d.ts.map +1 -0
  76. package/dist/mockttp.js +81 -0
  77. package/dist/mockttp.js.map +1 -0
  78. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
  79. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
  80. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
  81. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
  82. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
  83. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
  84. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
  85. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
  86. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
  87. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
  88. package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
  89. package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
  90. package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
  91. package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
  92. package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
  93. package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
  94. package/dist/rules/base-rule-builder.d.ts +185 -0
  95. package/dist/rules/base-rule-builder.d.ts.map +1 -0
  96. package/dist/rules/base-rule-builder.js +251 -0
  97. package/dist/rules/base-rule-builder.js.map +1 -0
  98. package/dist/rules/completion-checkers.d.ts +41 -0
  99. package/dist/rules/completion-checkers.d.ts.map +1 -0
  100. package/dist/rules/completion-checkers.js +87 -0
  101. package/dist/rules/completion-checkers.js.map +1 -0
  102. package/dist/rules/http-agents.d.ts +11 -0
  103. package/dist/rules/http-agents.d.ts.map +1 -0
  104. package/dist/rules/http-agents.js +91 -0
  105. package/dist/rules/http-agents.js.map +1 -0
  106. package/dist/rules/matchers.d.ts +214 -0
  107. package/dist/rules/matchers.d.ts.map +1 -0
  108. package/dist/rules/matchers.js +515 -0
  109. package/dist/rules/matchers.js.map +1 -0
  110. package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
  111. package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
  112. package/dist/rules/passthrough-handling-definitions.js +3 -0
  113. package/dist/rules/passthrough-handling-definitions.js.map +1 -0
  114. package/dist/rules/passthrough-handling.d.ts +33 -0
  115. package/dist/rules/passthrough-handling.d.ts.map +1 -0
  116. package/dist/rules/passthrough-handling.js +294 -0
  117. package/dist/rules/passthrough-handling.js.map +1 -0
  118. package/dist/rules/proxy-config.d.ts +76 -0
  119. package/dist/rules/proxy-config.d.ts.map +1 -0
  120. package/dist/rules/proxy-config.js +48 -0
  121. package/dist/rules/proxy-config.js.map +1 -0
  122. package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
  123. package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
  124. package/dist/rules/requests/request-handler-definitions.js +423 -0
  125. package/dist/rules/requests/request-handler-definitions.js.map +1 -0
  126. package/dist/rules/requests/request-handlers.d.ts +65 -0
  127. package/dist/rules/requests/request-handlers.d.ts.map +1 -0
  128. package/dist/rules/requests/request-handlers.js +1014 -0
  129. package/dist/rules/requests/request-handlers.js.map +1 -0
  130. package/dist/rules/requests/request-rule-builder.d.ts +255 -0
  131. package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
  132. package/dist/rules/requests/request-rule-builder.js +340 -0
  133. package/dist/rules/requests/request-rule-builder.js.map +1 -0
  134. package/dist/rules/requests/request-rule.d.ts +36 -0
  135. package/dist/rules/requests/request-rule.d.ts.map +1 -0
  136. package/dist/rules/requests/request-rule.js +100 -0
  137. package/dist/rules/requests/request-rule.js.map +1 -0
  138. package/dist/rules/rule-deserialization.d.ts +8 -0
  139. package/dist/rules/rule-deserialization.d.ts.map +1 -0
  140. package/dist/rules/rule-deserialization.js +27 -0
  141. package/dist/rules/rule-deserialization.js.map +1 -0
  142. package/dist/rules/rule-parameters.d.ts +21 -0
  143. package/dist/rules/rule-parameters.d.ts.map +1 -0
  144. package/dist/rules/rule-parameters.js +31 -0
  145. package/dist/rules/rule-parameters.js.map +1 -0
  146. package/dist/rules/rule-serialization.d.ts +7 -0
  147. package/dist/rules/rule-serialization.d.ts.map +1 -0
  148. package/dist/rules/rule-serialization.js +25 -0
  149. package/dist/rules/rule-serialization.js.map +1 -0
  150. package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
  151. package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
  152. package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
  153. package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
  154. package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
  155. package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
  156. package/dist/rules/websockets/websocket-handlers.js +356 -0
  157. package/dist/rules/websockets/websocket-handlers.js.map +1 -0
  158. package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
  159. package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
  160. package/dist/rules/websockets/websocket-rule-builder.js +232 -0
  161. package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
  162. package/dist/rules/websockets/websocket-rule.d.ts +34 -0
  163. package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
  164. package/dist/rules/websockets/websocket-rule.js +87 -0
  165. package/dist/rules/websockets/websocket-rule.js.map +1 -0
  166. package/dist/serialization/body-serialization.d.ts +43 -0
  167. package/dist/serialization/body-serialization.d.ts.map +1 -0
  168. package/dist/serialization/body-serialization.js +70 -0
  169. package/dist/serialization/body-serialization.js.map +1 -0
  170. package/dist/serialization/serialization.d.ts +63 -0
  171. package/dist/serialization/serialization.d.ts.map +1 -0
  172. package/dist/serialization/serialization.js +263 -0
  173. package/dist/serialization/serialization.js.map +1 -0
  174. package/dist/server/http-combo-server.d.ts +13 -0
  175. package/dist/server/http-combo-server.d.ts.map +1 -0
  176. package/dist/server/http-combo-server.js +330 -0
  177. package/dist/server/http-combo-server.js.map +1 -0
  178. package/dist/server/mocked-endpoint.d.ts +14 -0
  179. package/dist/server/mocked-endpoint.d.ts.map +1 -0
  180. package/dist/server/mocked-endpoint.js +40 -0
  181. package/dist/server/mocked-endpoint.js.map +1 -0
  182. package/dist/server/mockttp-server.d.ts +87 -0
  183. package/dist/server/mockttp-server.d.ts.map +1 -0
  184. package/dist/server/mockttp-server.js +859 -0
  185. package/dist/server/mockttp-server.js.map +1 -0
  186. package/dist/types.d.ts +359 -0
  187. package/dist/types.d.ts.map +1 -0
  188. package/dist/types.js +20 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/buffer-utils.d.ts +13 -0
  191. package/dist/util/buffer-utils.d.ts.map +1 -0
  192. package/dist/util/buffer-utils.js +141 -0
  193. package/dist/util/buffer-utils.js.map +1 -0
  194. package/dist/util/dns.d.ts +11 -0
  195. package/dist/util/dns.d.ts.map +1 -0
  196. package/dist/util/dns.js +47 -0
  197. package/dist/util/dns.js.map +1 -0
  198. package/dist/util/error.d.ts +9 -0
  199. package/dist/util/error.d.ts.map +1 -0
  200. package/dist/util/error.js +11 -0
  201. package/dist/util/error.js.map +1 -0
  202. package/dist/util/header-utils.d.ts +35 -0
  203. package/dist/util/header-utils.d.ts.map +1 -0
  204. package/dist/util/header-utils.js +200 -0
  205. package/dist/util/header-utils.js.map +1 -0
  206. package/dist/util/openssl-compat.d.ts +2 -0
  207. package/dist/util/openssl-compat.d.ts.map +1 -0
  208. package/dist/util/openssl-compat.js +26 -0
  209. package/dist/util/openssl-compat.js.map +1 -0
  210. package/dist/util/promise.d.ts +10 -0
  211. package/dist/util/promise.d.ts.map +1 -0
  212. package/dist/util/promise.js +25 -0
  213. package/dist/util/promise.js.map +1 -0
  214. package/dist/util/request-utils.d.ts +46 -0
  215. package/dist/util/request-utils.d.ts.map +1 -0
  216. package/dist/util/request-utils.js +462 -0
  217. package/dist/util/request-utils.js.map +1 -0
  218. package/dist/util/server-utils.d.ts +2 -0
  219. package/dist/util/server-utils.d.ts.map +1 -0
  220. package/dist/util/server-utils.js +14 -0
  221. package/dist/util/server-utils.js.map +1 -0
  222. package/dist/util/socket-util.d.ts +28 -0
  223. package/dist/util/socket-util.d.ts.map +1 -0
  224. package/dist/util/socket-util.js +174 -0
  225. package/dist/util/socket-util.js.map +1 -0
  226. package/dist/util/tls.d.ts +68 -0
  227. package/dist/util/tls.d.ts.map +1 -0
  228. package/dist/util/tls.js +220 -0
  229. package/dist/util/tls.js.map +1 -0
  230. package/dist/util/type-utils.d.ts +14 -0
  231. package/dist/util/type-utils.d.ts.map +1 -0
  232. package/dist/util/type-utils.js +3 -0
  233. package/dist/util/type-utils.js.map +1 -0
  234. package/dist/util/url.d.ts +17 -0
  235. package/dist/util/url.d.ts.map +1 -0
  236. package/dist/util/url.js +96 -0
  237. package/dist/util/url.js.map +1 -0
  238. package/dist/util/util.d.ts +8 -0
  239. package/dist/util/util.d.ts.map +1 -0
  240. package/dist/util/util.js +41 -0
  241. package/dist/util/util.js.map +1 -0
  242. package/docs/api-docs-landing-page.md +11 -0
  243. package/docs/runkitExample.js +16 -0
  244. package/docs/setup.md +136 -0
  245. package/nfyb8qx5.cjs +1 -0
  246. package/package.json +194 -4
  247. package/src/admin/admin-bin.ts +62 -0
  248. package/src/admin/admin-plugin-types.ts +29 -0
  249. package/src/admin/admin-server.ts +619 -0
  250. package/src/admin/graphql-utils.ts +28 -0
  251. package/src/admin/mockttp-admin-model.ts +264 -0
  252. package/src/admin/mockttp-admin-plugin.ts +59 -0
  253. package/src/admin/mockttp-admin-server.ts +27 -0
  254. package/src/admin/mockttp-schema.ts +222 -0
  255. package/src/client/admin-client.ts +652 -0
  256. package/src/client/admin-query.ts +52 -0
  257. package/src/client/mocked-endpoint-client.ts +32 -0
  258. package/src/client/mockttp-admin-request-builder.ts +540 -0
  259. package/src/client/mockttp-client.ts +178 -0
  260. package/src/client/schema-introspection.ts +131 -0
  261. package/src/main.browser.ts +60 -0
  262. package/src/main.ts +160 -0
  263. package/src/mockttp.ts +926 -0
  264. package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
  265. package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
  266. package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
  267. package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
  268. package/src/rules/base-rule-builder.ts +312 -0
  269. package/src/rules/completion-checkers.ts +90 -0
  270. package/src/rules/http-agents.ts +119 -0
  271. package/src/rules/matchers.ts +665 -0
  272. package/src/rules/passthrough-handling-definitions.ts +111 -0
  273. package/src/rules/passthrough-handling.ts +376 -0
  274. package/src/rules/proxy-config.ts +136 -0
  275. package/src/rules/requests/request-handler-definitions.ts +1089 -0
  276. package/src/rules/requests/request-handlers.ts +1369 -0
  277. package/src/rules/requests/request-rule-builder.ts +481 -0
  278. package/src/rules/requests/request-rule.ts +148 -0
  279. package/src/rules/rule-deserialization.ts +55 -0
  280. package/src/rules/rule-parameters.ts +41 -0
  281. package/src/rules/rule-serialization.ts +29 -0
  282. package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
  283. package/src/rules/websockets/websocket-handlers.ts +509 -0
  284. package/src/rules/websockets/websocket-rule-builder.ts +275 -0
  285. package/src/rules/websockets/websocket-rule.ts +136 -0
  286. package/src/serialization/body-serialization.ts +84 -0
  287. package/src/serialization/serialization.ts +373 -0
  288. package/src/server/http-combo-server.ts +424 -0
  289. package/src/server/mocked-endpoint.ts +44 -0
  290. package/src/server/mockttp-server.ts +1110 -0
  291. package/src/types.ts +433 -0
  292. package/src/util/buffer-utils.ts +164 -0
  293. package/src/util/dns.ts +52 -0
  294. package/src/util/error.ts +18 -0
  295. package/src/util/header-utils.ts +220 -0
  296. package/src/util/openssl-compat.ts +26 -0
  297. package/src/util/promise.ts +31 -0
  298. package/src/util/request-utils.ts +607 -0
  299. package/src/util/server-utils.ts +18 -0
  300. package/src/util/socket-util.ts +193 -0
  301. package/src/util/tls.ts +348 -0
  302. package/src/util/type-utils.ts +15 -0
  303. package/src/util/url.ts +113 -0
  304. package/src/util/util.ts +39 -0
@@ -0,0 +1,652 @@
1
+ import _ = require('lodash');
2
+ import { EventEmitter } from 'events';
3
+ import { Duplex } from 'stream';
4
+ import DuplexPair = require('native-duplexpair');
5
+ import { TypedError } from 'typed-error';
6
+ import * as CrossFetch from 'cross-fetch';
7
+ import * as WebSocket from 'isomorphic-ws';
8
+ import connectWebSocketStream = require('@httptoolkit/websocket-stream');
9
+ import { SubscriptionClient } from '@httptoolkit/subscriptions-transport-ws';
10
+ import { print } from 'graphql';
11
+
12
+ import { DEFAULT_ADMIN_SERVER_PORT } from "../types";
13
+
14
+ import { MaybePromise, RequireProps } from '../util/type-utils';
15
+ import { isNode } from '../util/util';
16
+ import { isErrorLike } from '../util/error';
17
+ import { getDeferred } from '../util/promise';
18
+
19
+ import { introspectionQuery } from './schema-introspection';
20
+ import { MockttpPluginOptions } from '../admin/mockttp-admin-plugin';
21
+ import { AdminPlugin, PluginClientResponsesMap, PluginStartParamsMap } from '../admin/admin-plugin-types';
22
+ import { SchemaIntrospector } from './schema-introspection';
23
+ import { AdminQuery, getSingleSelectedFieldName } from './admin-query';
24
+ import { MockttpOptions } from '../mockttp';
25
+
26
+ const { fetch, Headers } = isNode || typeof globalThis.fetch === 'undefined'
27
+ ? CrossFetch
28
+ : globalThis;
29
+
30
+ export class ConnectionError extends TypedError { }
31
+
32
+ // The Response type requires lib.dom. We include an empty placeholder here to
33
+ // avoid the types breaking if you don't have that available. Once day TS will
34
+ // fix this: https://github.com/microsoft/TypeScript/issues/31894
35
+ declare global {
36
+ interface Response {}
37
+ }
38
+
39
+ export class RequestError extends TypedError {
40
+ constructor(
41
+ message: string,
42
+ public response: Response
43
+ ) {
44
+ super(message);
45
+ }
46
+ }
47
+
48
+ export class GraphQLError extends RequestError {
49
+ constructor(
50
+ response: Response,
51
+ public errors: Array<{ message: string }>
52
+ ) {
53
+ super(
54
+ errors.length === 0
55
+ ? `GraphQL request failed with ${response.status} response`
56
+ : errors.length === 1
57
+ ? `GraphQL request failed with: ${errors[0].message}`
58
+ : // >1
59
+ `GraphQL request failed, with errors:\n${errors.map((e) => e.message).join('\n')}`,
60
+ response
61
+ );
62
+ }
63
+ }
64
+
65
+ // The various events that the admin client can emit:
66
+ export type AdminClientEvent =
67
+ | 'starting'
68
+ | 'started'
69
+ | 'start-failed'
70
+ | 'stopping'
71
+ | 'stopped'
72
+ | 'stream-error'
73
+ | 'stream-reconnecting'
74
+ | 'stream-reconnected'
75
+ | 'stream-reconnect-failed'
76
+ | 'subscription-error'
77
+ | 'subscription-reconnecting';
78
+
79
+ export interface AdminClientOptions {
80
+
81
+ /**
82
+ * Should the client print extra debug information?
83
+ */
84
+ debug?: boolean;
85
+
86
+ /**
87
+ * The full URL to use to connect to a Mockttp admin server when using a
88
+ * remote (or local but browser) client.
89
+ *
90
+ * When using a local server, this option is ignored.
91
+ */
92
+ adminServerUrl?: string;
93
+
94
+ /**
95
+ * Options to include on all client requests.
96
+ */
97
+ requestOptions?: {
98
+ headers?: { [key: string]: string };
99
+ };
100
+ }
101
+
102
+ const mergeClientOptions = (
103
+ options: RequestInit | undefined,
104
+ defaultOptions: AdminClientOptions['requestOptions']
105
+ ) => {
106
+ if (!defaultOptions) return options;
107
+ if (!options) return defaultOptions;
108
+
109
+ if (defaultOptions.headers) {
110
+ if (!options.headers) {
111
+ options.headers = defaultOptions.headers;
112
+ } else if (options.headers instanceof Headers) {
113
+ _.forEach(defaultOptions.headers, (value, key) => {
114
+ (options.headers as Headers).append(key, value);
115
+ });
116
+ } else if (_.isObject(options.headers)) {
117
+ Object.assign(options.headers, defaultOptions.headers);
118
+ }
119
+ }
120
+
121
+ return options;
122
+ };
123
+
124
+ async function requestFromAdminServer<T>(serverUrl: string, path: string, options?: RequestInit): Promise<T> {
125
+ const url = `${serverUrl}${path}`;
126
+
127
+ let response;
128
+ try {
129
+ response = await fetch(url, options);
130
+ } catch (e) {
131
+ if (isErrorLike(e) && e.code === 'ECONNREFUSED') {
132
+ throw new ConnectionError(`Failed to connect to admin server at ${serverUrl}`);
133
+ } else throw e;
134
+ }
135
+
136
+ if (response.status >= 400) {
137
+ let body = await response.text();
138
+
139
+ let jsonBody: { error?: string } | null = null;
140
+ try {
141
+ jsonBody = JSON.parse(body);
142
+ } catch (e) { }
143
+
144
+ if (jsonBody && jsonBody.error) {
145
+ throw new RequestError(
146
+ jsonBody.error,
147
+ response
148
+ );
149
+ } else {
150
+ throw new RequestError(
151
+ `Request to ${url} failed, with status ${response.status} and response body: ${body}`,
152
+ response
153
+ );
154
+ }
155
+ } else {
156
+ return response.json();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Reset a remote admin server, shutting down all Mockttp servers controlled by that
162
+ * admin server. This is equivalent to calling `client.stop()` for all remote
163
+ * clients of the target server.
164
+ *
165
+ * This can be useful in some rare cases, where a client might fail to reliably tear down
166
+ * its own server, e.g. in Cypress testing. In this case, it's useful to reset the
167
+ * admin server completely remotely without needing access to any previous client
168
+ * instances, to ensure all servers from previous test runs have been shut down.
169
+ *
170
+ * After this is called, behaviour of any previously connected clients is undefined, and
171
+ * it's likely that they may throw errors or experience other undefined behaviour. Ensure
172
+ * that `client.stop()` has been called on all active clients before calling this method.
173
+ */
174
+ export async function resetAdminServer(options: AdminClientOptions = {}): Promise<void> {
175
+ const serverUrl = options.adminServerUrl ||
176
+ `http://localhost:${DEFAULT_ADMIN_SERVER_PORT}`;
177
+ await requestFromAdminServer(serverUrl, '/reset', {
178
+ ...options.requestOptions,
179
+ method: 'POST'
180
+ });
181
+ }
182
+
183
+ /**
184
+ * A bare admin server client. This is not intended for general use, but can be useful when
185
+ * building admin server plugins to mock non-HTTP protocols and other advanced use cases.
186
+ *
187
+ * For normal usage of Mockttp, you should use `Mockttp.getRemote()` instead, to get a Mockttp
188
+ * remote client, which wraps this class with the full Mockttp API for mocking HTTP.
189
+ *
190
+ * This is part of Mockttp's experimental 'pluggable admin' API. It may change
191
+ * unpredictably, even in minor releases.
192
+ */
193
+ export class AdminClient<Plugins extends { [key: string]: AdminPlugin<any, any> }> extends EventEmitter {
194
+
195
+ private adminClientOptions: RequireProps<AdminClientOptions, 'adminServerUrl'>;
196
+
197
+ private adminSessionBaseUrl: string | undefined;
198
+ private adminServerStream: Duplex | undefined;
199
+ private subscriptionClient: SubscriptionClient | undefined;
200
+
201
+ // Metadata from the last start() call, if the server is currently connected:
202
+ private adminServerSchema: SchemaIntrospector | undefined;
203
+ private adminServerMetadata: PluginClientResponsesMap<Plugins> | undefined;
204
+
205
+ private debug: boolean = false;
206
+
207
+ // True if server is entirely initialized, false if it's entirely shut down, or a promise
208
+ // that resolves to one or the other if it's currently changing state.
209
+ private running: MaybePromise<boolean> = false;
210
+
211
+ constructor(options: AdminClientOptions = {}) {
212
+ super();
213
+ this.debug = !!options.debug;
214
+ this.adminClientOptions = _.defaults(options, {
215
+ adminServerUrl: `http://localhost:${DEFAULT_ADMIN_SERVER_PORT}`
216
+ });
217
+ }
218
+
219
+ private attachStreamWebsocket(adminSessionBaseUrl: string, targetStream: Duplex): Duplex {
220
+ const adminSessionBaseWSUrl = adminSessionBaseUrl.replace(/^http/, 'ws');
221
+ const wsStream = connectWebSocketStream(`${adminSessionBaseWSUrl}/stream`, {
222
+ headers: this.adminClientOptions?.requestOptions?.headers // Only used in Node.js (via WS)
223
+ });
224
+
225
+ let streamConnected = false;
226
+ wsStream.on('connect', () => {
227
+ streamConnected = true;
228
+
229
+ targetStream.pipe(wsStream);
230
+ wsStream.pipe(targetStream, { end: false });
231
+ });
232
+
233
+ // We ignore errors, but websocket closure eventually results in reconnect or shutdown
234
+ wsStream.on('error', (e) => {
235
+ if (this.debug) console.warn('Admin client stream error', e);
236
+ this.emit('stream-error', e);
237
+ });
238
+
239
+ // When the websocket closes (after connect, either close frame, error, or socket shutdown):
240
+ wsStream.on('ws-close', async (closeEvent) => {
241
+ targetStream.unpipe(wsStream);
242
+
243
+ const serverShutdown = closeEvent.code === 1000;
244
+ if (serverShutdown) {
245
+ // Clean shutdown implies the server is gone, and we need to shutdown & cleanup.
246
+ targetStream.emit('server-shutdown');
247
+ } else if (streamConnected && (await this.running) === true) {
248
+ console.warn('Admin client stream unexpectedly disconnected', closeEvent);
249
+
250
+ this.emit('stream-reconnecting');
251
+
252
+ // Unclean shutdown means something has gone wrong somewhere. Try to reconnect.
253
+ const newStream = this.attachStreamWebsocket(adminSessionBaseUrl, targetStream);
254
+
255
+ new Promise((resolve, reject) => {
256
+ newStream.once('connect', resolve);
257
+ newStream.once('error', reject);
258
+ }).then(() => {
259
+ // On a successful connect, business resumes as normal.
260
+ console.warn('Admin client stream reconnected');
261
+ this.emit('stream-reconnected');
262
+ }).catch((err) => {
263
+ // On a failed reconnect, we just shut down completely.
264
+ console.warn('Admin client stream reconnection failed, shutting down:', err.message);
265
+ if (this.debug) console.warn(err);
266
+ this.emit('stream-reconnect-failed', err);
267
+ targetStream.emit('server-shutdown');
268
+ });
269
+ }
270
+ // If never connected successfully, we do nothing.
271
+ });
272
+
273
+ targetStream.on('finish', () => { // Client has shutdown
274
+ // Ignore any further WebSocket events - the websocket stream is no longer useful
275
+ wsStream.removeAllListeners('connect');
276
+ wsStream.removeAllListeners('ws-close');
277
+ wsStream.destroy();
278
+ });
279
+
280
+ return wsStream;
281
+ }
282
+
283
+ private openStreamToMockServer(adminSessionBaseUrl: string): Promise<Duplex> {
284
+ // To allow reconnects, we need to not end the client stream when an individual web socket ends.
285
+ // To make that work, we return a separate stream, which isn't directly connected to the websocket
286
+ // and doesn't receive WS 'end' events, and then we can swap the WS inputs accordingly.
287
+ const { socket1: wsTarget, socket2: exposedStream } = new DuplexPair();
288
+
289
+ const wsStream = this.attachStreamWebsocket(adminSessionBaseUrl, wsTarget);
290
+ wsTarget.on('error', (e) => exposedStream.emit('error', e));
291
+
292
+ // When the server stream ends, end the target stream, which will automatically end all websockets.
293
+ exposedStream.on('finish', () => wsTarget.end());
294
+
295
+ // Propagate 'server is definitely no longer available' back from the websockets:
296
+ wsTarget.on('server-shutdown', () => exposedStream.emit('server-shutdown'));
297
+
298
+ // These receive a lot of listeners! One channel per matcher, handler & completion checker,
299
+ // and each adds listeners for data/error/finish/etc. That's OK, it's not generally a leak,
300
+ // but maybe 100 would be a bit suspicious (unless you have 30+ active rules).
301
+ exposedStream.setMaxListeners(100);
302
+
303
+ return new Promise((resolve, reject) => {
304
+ wsStream.once('connect', () => resolve(exposedStream));
305
+ wsStream.once('error', reject);
306
+ });
307
+ }
308
+
309
+ private prepareSubscriptionClientToAdminServer(adminSessionBaseUrl: string) {
310
+ const adminSessionBaseWSUrl = adminSessionBaseUrl.replace(/^http/, 'ws');
311
+ const subscriptionUrl = `${adminSessionBaseWSUrl}/subscription`;
312
+ this.subscriptionClient = new SubscriptionClient(subscriptionUrl, {
313
+ lazy: true, // Doesn't actually connect until you use subscriptions
314
+ reconnect: true,
315
+ reconnectionAttempts: 8,
316
+ wsOptionArguments: [this.adminClientOptions.requestOptions]
317
+ }, WebSocket);
318
+
319
+ this.subscriptionClient.onError((e) => {
320
+ this.emit('subscription-error', e);
321
+ if (this.debug) console.error("Subscription error", e)
322
+ });
323
+
324
+ this.subscriptionClient.onReconnecting(() => {
325
+ this.emit('subscription-reconnecting');
326
+ console.warn('Reconnecting Mockttp subscription client')
327
+ });
328
+ }
329
+
330
+ private async requestFromMockServer(path: string, options?: RequestInit): Promise<Response> {
331
+ // Must check for session URL, not this.running, or we can't send the /stop request during shutdown!
332
+ if (!this.adminSessionBaseUrl) throw new Error('Not connected to mock server');
333
+
334
+ let url = this.adminSessionBaseUrl + path;
335
+ let response = await fetch(url, mergeClientOptions(options, this.adminClientOptions.requestOptions));
336
+
337
+ if (response.status >= 400) {
338
+ if (this.debug) console.error(`Remote client server request failed with status ${response.status}`);
339
+ throw new RequestError(
340
+ `Request to ${url} failed, with status ${response.status}`,
341
+ response
342
+ );
343
+ } else {
344
+ return response;
345
+ }
346
+ }
347
+
348
+ private async queryMockServer<T>(query: string, variables?: {}): Promise<T> {
349
+ try {
350
+ const response = (await this.requestFromMockServer('/', {
351
+ method: 'POST',
352
+ headers: new Headers({
353
+ 'Content-Type': 'application/json'
354
+ }),
355
+ body: JSON.stringify({ query, variables })
356
+ }));
357
+
358
+ const { data, errors }: { data?: T, errors?: Error[] } = await response.json();
359
+
360
+ if (errors && errors.length) {
361
+ throw new GraphQLError(response, errors);
362
+ } else {
363
+ return data as T;
364
+ }
365
+ } catch (e) {
366
+ if (this.debug) console.error(`Remote client query error: ${e}`);
367
+
368
+ if (!(e instanceof RequestError)) throw e;
369
+
370
+ let graphQLErrors: Error[] | undefined = undefined;
371
+ try {
372
+ graphQLErrors = (await e.response.json()).errors;
373
+ } catch (e2) {}
374
+
375
+ if (graphQLErrors) {
376
+ throw new GraphQLError(e.response, graphQLErrors);
377
+ } else {
378
+ throw e;
379
+ }
380
+ }
381
+ }
382
+
383
+ async start(
384
+ pluginStartParams: PluginStartParamsMap<Plugins>
385
+ ): Promise<PluginClientResponsesMap<Plugins>> {
386
+ if (this.adminSessionBaseUrl || await this.running) throw new Error('Server is already started');
387
+ if (this.debug) console.log(`Starting remote mock server`);
388
+ this.emit('starting');
389
+
390
+ const startPromise = getDeferred<boolean>();
391
+ this.running = startPromise.then((result) => {
392
+ this.emit(result ? 'started' : 'start-failed');
393
+ this.running = result;
394
+ return result;
395
+ });
396
+
397
+ try {
398
+ const supportOldServers = 'http' in pluginStartParams;
399
+ const portConfig = supportOldServers
400
+ ? (pluginStartParams['http'] as MockttpPluginOptions).port
401
+ : undefined;
402
+
403
+ const path = portConfig ? `/start?port=${JSON.stringify(portConfig)}` : '/start';
404
+ const adminServerResponse = await requestFromAdminServer<
405
+ | { port: number, mockRoot: string } // Backward compat for old servers
406
+ | { id: string, pluginData: PluginClientResponsesMap<Plugins> } // New plugin-aware servers
407
+ >(
408
+ this.adminClientOptions.adminServerUrl,
409
+ path,
410
+ mergeClientOptions({
411
+ method: 'POST',
412
+ headers: new Headers({
413
+ 'Content-Type': 'application/json'
414
+ }),
415
+ body: JSON.stringify({
416
+ plugins: pluginStartParams,
417
+ // Include all the Mockttp params at the root too, for backward compat with old admin servers:
418
+ ...(pluginStartParams.http?.options as MockttpOptions | undefined)
419
+ })
420
+ }, this.adminClientOptions.requestOptions)
421
+ );
422
+
423
+ // Backward compat for old servers
424
+ const isPluginAwareServer = 'id' in adminServerResponse;
425
+
426
+ const sessionId = isPluginAwareServer
427
+ ? adminServerResponse.id
428
+ : adminServerResponse.port.toString();
429
+
430
+ const adminSessionBaseUrl = `${this.adminClientOptions.adminServerUrl}/${
431
+ isPluginAwareServer ? 'session' : 'server'
432
+ }/${sessionId}`
433
+
434
+ // Also open a stream connection, for 2-way communication we might need later.
435
+ const adminServerStream = await this.openStreamToMockServer(adminSessionBaseUrl);
436
+ adminServerStream.on('server-shutdown', () => {
437
+ // When the server remotely disconnects the stream, shut down the client iff the client hasn't
438
+ // stopped & restarted in the meantime (can happen, since all shutdown is async).
439
+ if (this.adminServerStream === adminServerStream) {
440
+ console.warn('Client stopping due to admin server shutdown');
441
+ this.stop();
442
+ }
443
+ });
444
+ this.adminServerStream = adminServerStream;
445
+
446
+ // Create a subscription client, preconfigured & ready to connect if on() is called later:
447
+ this.prepareSubscriptionClientToAdminServer(adminSessionBaseUrl);
448
+
449
+ // We don't persist the id or resolve the start promise until everything is set up
450
+ this.adminSessionBaseUrl = adminSessionBaseUrl;
451
+
452
+ // Load the schema on server start, so we can check for feature support
453
+ this.adminServerSchema = new SchemaIntrospector(
454
+ (await this.queryMockServer<any>(introspectionQuery)).__schema
455
+ );
456
+
457
+ if (this.debug) console.log('Started remote mock server');
458
+
459
+ const serverMetadata =
460
+ this.adminServerMetadata = // Set field before we resolve the promise
461
+ 'pluginData' in adminServerResponse
462
+ ? adminServerResponse.pluginData
463
+ : {
464
+ // Backward compat - convert old always-HTTP data into per-plugin format:
465
+ http: adminServerResponse
466
+ } as unknown as PluginClientResponsesMap<Plugins>;
467
+
468
+ startPromise.resolve(true);
469
+ return serverMetadata;
470
+ } catch (e) {
471
+ startPromise.resolve(false);
472
+ throw e;
473
+ }
474
+ }
475
+
476
+ isRunning() {
477
+ return this.running === true;
478
+ }
479
+
480
+ get metadata() {
481
+ if (!this.isRunning()) throw new Error("Metadata is not available until the mock server is started");
482
+ return this.adminServerMetadata!;
483
+ }
484
+
485
+ get schema() {
486
+ if (!this.isRunning()) throw new Error("Admin schema is not available until the mock server is started");
487
+ return this.adminServerSchema!;
488
+ }
489
+
490
+ get adminStream() {
491
+ if (!this.isRunning()) throw new Error("Admin stream is not available until the mock server is started");
492
+ return this.adminServerStream!;
493
+ }
494
+
495
+ // Call when either we want the server to stop, or it appears that the server has already stopped,
496
+ // and we just want to ensure that's happened and clean everything up.
497
+ async stop(): Promise<void> {
498
+ if (await this.running === false) return; // If stopped or stopping, do nothing.
499
+ this.emit('stopping');
500
+
501
+ const stopPromise = getDeferred<boolean>();
502
+ this.running = stopPromise.then((result) => {
503
+ this.emit('stopped');
504
+ this.running = result;
505
+ return result;
506
+ });
507
+
508
+ try {
509
+ if (this.debug) console.log('Stopping remote mock server');
510
+
511
+ try { this.subscriptionClient?.close(); } catch (e) { console.log(e); }
512
+ this.subscriptionClient = undefined;
513
+
514
+ try { this.adminServerStream?.end(); } catch (e) { console.log(e); }
515
+ this.adminServerStream = undefined;
516
+
517
+ await this.requestServerStop();
518
+ } finally {
519
+ // The client is always stopped (and so restartable) once stopping completes, in all
520
+ // cases, since it can always be started again to reset it. The promise is just here
521
+ // so that we successfully handle (and always wait for) parallel stops.
522
+ stopPromise.resolve(false);
523
+ }
524
+ }
525
+
526
+ private requestServerStop() {
527
+ return this.requestFromMockServer('/stop', {
528
+ method: 'POST'
529
+ }).catch((e) => {
530
+ if (e instanceof RequestError && e.response.status === 404) {
531
+ // 404 means it doesn't exist, generally because it was already stopped
532
+ // by some other parallel shutdown process.
533
+ return;
534
+ } else {
535
+ throw e;
536
+ }
537
+ }).then(() => {
538
+ this.adminSessionBaseUrl = undefined;
539
+ this.adminServerSchema = undefined;
540
+ this.adminServerMetadata = undefined;
541
+ });
542
+ }
543
+
544
+ public enableDebug = async (): Promise<void> => {
545
+ this.debug = true;
546
+ return (await this.queryMockServer<void>(
547
+ `mutation EnableDebug {
548
+ enableDebug
549
+ }`
550
+ ));
551
+ }
552
+
553
+ public reset = async (): Promise<void> => {
554
+ return (await this.queryMockServer<void>(
555
+ `mutation Reset {
556
+ reset
557
+ }`
558
+ ));
559
+ }
560
+
561
+ public async sendQuery<Response, Result = Response>(query: AdminQuery<Response, Result>): Promise<Result> {
562
+ return (await this.sendQueries(query))[0];
563
+ }
564
+
565
+ public async sendQueries<Queries extends Array<AdminQuery<any>>>(
566
+ ...queries: [...Queries]
567
+ ): Promise<{ [n in keyof Queries]: Queries[n] extends AdminQuery<any, infer R> ? R : never }> {
568
+ const results = queries.map<Promise<Array<unknown>>>(
569
+ async ({ query, variables, transformResponse }) => {
570
+ const result = await this.queryMockServer(print(query), variables);
571
+ return transformResponse
572
+ ? transformResponse(result, { adminClient: this })
573
+ : result;
574
+ }
575
+ );
576
+
577
+ return Promise.all(results) as Promise<{
578
+ [n in keyof Queries]: Queries[n] extends AdminQuery<any, infer R> ? R : never
579
+ }>;
580
+ }
581
+
582
+ public async subscribe<Response, Result = Response>(
583
+ query: AdminQuery<Response, Result>,
584
+ callback: (data: Result) => void
585
+ ): Promise<void> {
586
+ if (await this.running === false) throw new Error('Not connected to mock server');
587
+
588
+ const fieldName = getSingleSelectedFieldName(query);
589
+ if (!this.schema!.typeHasField('Subscription', fieldName)) {
590
+ console.warn(`Ignoring client subscription for event unrecognized by Mockttp server: ${fieldName}`);
591
+ return Promise.resolve();
592
+ }
593
+
594
+ // This isn't 100% correct (you can be WS-connected, but still negotiating some GQL
595
+ // setup) but it's good enough for our purposes (knowing-ish if the connection worked).
596
+ let isConnected = !!this.subscriptionClient!.client;
597
+
598
+ this.subscriptionClient!.request(query).subscribe({
599
+ next: async (value) => {
600
+ if (value.data) {
601
+ const response = (<any> value.data)[fieldName];
602
+ const result = query.transformResponse
603
+ ? await query.transformResponse(response, { adminClient: this })
604
+ : response as Result;
605
+ callback(result);
606
+ } else if (value.errors) {
607
+ console.error('Error in subscription', value.errors);
608
+ }
609
+ },
610
+ error: (e) => this.debug && console.warn('Error in remote subscription:', e)
611
+ });
612
+
613
+ return new Promise((resolve, reject) => {
614
+ if (isConnected) resolve();
615
+ else {
616
+ this.subscriptionClient!.onConnected(resolve);
617
+ this.subscriptionClient!.onDisconnected(reject);
618
+ this.subscriptionClient!.onError(reject);
619
+ }
620
+ });
621
+ }
622
+
623
+ /**
624
+ * List the names of the rule parameters defined by the admin server. This can be
625
+ * used in some advanced use cases to confirm that the parameters a client wishes to
626
+ * reference are available.
627
+ *
628
+ * Only defined for remote clients.
629
+ */
630
+ public async getRuleParameterKeys() {
631
+ if (await this.running === false) {
632
+ throw new Error('Cannot query rule parameters before the server is started');
633
+ }
634
+
635
+ if (!this.schema!.queryTypeDefined('ruleParameterKeys')) {
636
+ // If this endpoint isn't supported, that's because parameters aren't supported
637
+ // at all, so we can safely report that immediately.
638
+ return [];
639
+ }
640
+
641
+ let result = await this.queryMockServer<{
642
+ ruleParameterKeys: string[]
643
+ }>(
644
+ `query GetRuleParameterNames {
645
+ ruleParameterKeys
646
+ }`
647
+ );
648
+
649
+ return result.ruleParameterKeys;
650
+ }
651
+
652
+ }