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.
- package/LICENSE +201 -0
- package/README.md +123 -3
- package/custom-typings/Function.d.ts +4 -0
- package/custom-typings/cors-gate.d.ts +13 -0
- package/custom-typings/http-proxy-agent.d.ts +9 -0
- package/custom-typings/node-type-extensions.d.ts +115 -0
- package/custom-typings/proxy-agent-modules.d.ts +5 -0
- package/custom-typings/request-promise-native.d.ts +28 -0
- package/custom-typings/zstd-codec.d.ts +20 -0
- package/dist/admin/admin-bin.d.ts +3 -0
- package/dist/admin/admin-bin.d.ts.map +1 -0
- package/dist/admin/admin-bin.js +61 -0
- package/dist/admin/admin-bin.js.map +1 -0
- package/dist/admin/admin-plugin-types.d.ts +29 -0
- package/dist/admin/admin-plugin-types.d.ts.map +1 -0
- package/dist/admin/admin-plugin-types.js +3 -0
- package/dist/admin/admin-plugin-types.js.map +1 -0
- package/dist/admin/admin-server.d.ts +98 -0
- package/dist/admin/admin-server.d.ts.map +1 -0
- package/dist/admin/admin-server.js +426 -0
- package/dist/admin/admin-server.js.map +1 -0
- package/dist/admin/graphql-utils.d.ts +4 -0
- package/dist/admin/graphql-utils.d.ts.map +1 -0
- package/dist/admin/graphql-utils.js +28 -0
- package/dist/admin/graphql-utils.js.map +1 -0
- package/dist/admin/mockttp-admin-model.d.ts +7 -0
- package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-model.js +214 -0
- package/dist/admin/mockttp-admin-model.js.map +1 -0
- package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
- package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-plugin.js +37 -0
- package/dist/admin/mockttp-admin-plugin.js.map +1 -0
- package/dist/admin/mockttp-admin-server.d.ts +16 -0
- package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-server.js +17 -0
- package/dist/admin/mockttp-admin-server.js.map +1 -0
- package/dist/admin/mockttp-schema.d.ts +2 -0
- package/dist/admin/mockttp-schema.d.ts.map +1 -0
- package/dist/admin/mockttp-schema.js +225 -0
- package/dist/admin/mockttp-schema.js.map +1 -0
- package/dist/client/admin-client.d.ts +112 -0
- package/dist/client/admin-client.d.ts.map +1 -0
- package/dist/client/admin-client.js +511 -0
- package/dist/client/admin-client.js.map +1 -0
- package/dist/client/admin-query.d.ts +13 -0
- package/dist/client/admin-query.d.ts.map +1 -0
- package/dist/client/admin-query.js +26 -0
- package/dist/client/admin-query.js.map +1 -0
- package/dist/client/mocked-endpoint-client.d.ts +12 -0
- package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
- package/dist/client/mocked-endpoint-client.js +33 -0
- package/dist/client/mocked-endpoint-client.js.map +1 -0
- package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
- package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
- package/dist/client/mockttp-admin-request-builder.js +462 -0
- package/dist/client/mockttp-admin-request-builder.js.map +1 -0
- package/dist/client/mockttp-client.d.ts +56 -0
- package/dist/client/mockttp-client.d.ts.map +1 -0
- package/dist/client/mockttp-client.js +112 -0
- package/dist/client/mockttp-client.js.map +1 -0
- package/dist/client/schema-introspection.d.ts +11 -0
- package/dist/client/schema-introspection.d.ts.map +1 -0
- package/dist/client/schema-introspection.js +128 -0
- package/dist/client/schema-introspection.js.map +1 -0
- package/dist/main.browser.d.ts +49 -0
- package/dist/main.browser.d.ts.map +1 -0
- package/dist/main.browser.js +57 -0
- package/dist/main.browser.js.map +1 -0
- package/dist/main.d.ts +86 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +108 -0
- package/dist/main.js.map +1 -0
- package/dist/mockttp.d.ts +774 -0
- package/dist/mockttp.d.ts.map +1 -0
- package/dist/mockttp.js +81 -0
- package/dist/mockttp.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
- package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
- package/dist/rules/base-rule-builder.d.ts +185 -0
- package/dist/rules/base-rule-builder.d.ts.map +1 -0
- package/dist/rules/base-rule-builder.js +251 -0
- package/dist/rules/base-rule-builder.js.map +1 -0
- package/dist/rules/completion-checkers.d.ts +41 -0
- package/dist/rules/completion-checkers.d.ts.map +1 -0
- package/dist/rules/completion-checkers.js +87 -0
- package/dist/rules/completion-checkers.js.map +1 -0
- package/dist/rules/http-agents.d.ts +11 -0
- package/dist/rules/http-agents.d.ts.map +1 -0
- package/dist/rules/http-agents.js +91 -0
- package/dist/rules/http-agents.js.map +1 -0
- package/dist/rules/matchers.d.ts +214 -0
- package/dist/rules/matchers.d.ts.map +1 -0
- package/dist/rules/matchers.js +515 -0
- package/dist/rules/matchers.js.map +1 -0
- package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
- package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
- package/dist/rules/passthrough-handling-definitions.js +3 -0
- package/dist/rules/passthrough-handling-definitions.js.map +1 -0
- package/dist/rules/passthrough-handling.d.ts +33 -0
- package/dist/rules/passthrough-handling.d.ts.map +1 -0
- package/dist/rules/passthrough-handling.js +294 -0
- package/dist/rules/passthrough-handling.js.map +1 -0
- package/dist/rules/proxy-config.d.ts +76 -0
- package/dist/rules/proxy-config.d.ts.map +1 -0
- package/dist/rules/proxy-config.js +48 -0
- package/dist/rules/proxy-config.js.map +1 -0
- package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
- package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
- package/dist/rules/requests/request-handler-definitions.js +423 -0
- package/dist/rules/requests/request-handler-definitions.js.map +1 -0
- package/dist/rules/requests/request-handlers.d.ts +65 -0
- package/dist/rules/requests/request-handlers.d.ts.map +1 -0
- package/dist/rules/requests/request-handlers.js +1014 -0
- package/dist/rules/requests/request-handlers.js.map +1 -0
- package/dist/rules/requests/request-rule-builder.d.ts +255 -0
- package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
- package/dist/rules/requests/request-rule-builder.js +340 -0
- package/dist/rules/requests/request-rule-builder.js.map +1 -0
- package/dist/rules/requests/request-rule.d.ts +36 -0
- package/dist/rules/requests/request-rule.d.ts.map +1 -0
- package/dist/rules/requests/request-rule.js +100 -0
- package/dist/rules/requests/request-rule.js.map +1 -0
- package/dist/rules/rule-deserialization.d.ts +8 -0
- package/dist/rules/rule-deserialization.d.ts.map +1 -0
- package/dist/rules/rule-deserialization.js +27 -0
- package/dist/rules/rule-deserialization.js.map +1 -0
- package/dist/rules/rule-parameters.d.ts +21 -0
- package/dist/rules/rule-parameters.d.ts.map +1 -0
- package/dist/rules/rule-parameters.js +31 -0
- package/dist/rules/rule-parameters.js.map +1 -0
- package/dist/rules/rule-serialization.d.ts +7 -0
- package/dist/rules/rule-serialization.d.ts.map +1 -0
- package/dist/rules/rule-serialization.js +25 -0
- package/dist/rules/rule-serialization.js.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
- package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
- package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
- package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handlers.js +356 -0
- package/dist/rules/websockets/websocket-handlers.js.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.js +232 -0
- package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
- package/dist/rules/websockets/websocket-rule.d.ts +34 -0
- package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule.js +87 -0
- package/dist/rules/websockets/websocket-rule.js.map +1 -0
- package/dist/serialization/body-serialization.d.ts +43 -0
- package/dist/serialization/body-serialization.d.ts.map +1 -0
- package/dist/serialization/body-serialization.js +70 -0
- package/dist/serialization/body-serialization.js.map +1 -0
- package/dist/serialization/serialization.d.ts +63 -0
- package/dist/serialization/serialization.d.ts.map +1 -0
- package/dist/serialization/serialization.js +263 -0
- package/dist/serialization/serialization.js.map +1 -0
- package/dist/server/http-combo-server.d.ts +13 -0
- package/dist/server/http-combo-server.d.ts.map +1 -0
- package/dist/server/http-combo-server.js +330 -0
- package/dist/server/http-combo-server.js.map +1 -0
- package/dist/server/mocked-endpoint.d.ts +14 -0
- package/dist/server/mocked-endpoint.d.ts.map +1 -0
- package/dist/server/mocked-endpoint.js +40 -0
- package/dist/server/mocked-endpoint.js.map +1 -0
- package/dist/server/mockttp-server.d.ts +87 -0
- package/dist/server/mockttp-server.d.ts.map +1 -0
- package/dist/server/mockttp-server.js +859 -0
- package/dist/server/mockttp-server.js.map +1 -0
- package/dist/types.d.ts +359 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/util/buffer-utils.d.ts +13 -0
- package/dist/util/buffer-utils.d.ts.map +1 -0
- package/dist/util/buffer-utils.js +141 -0
- package/dist/util/buffer-utils.js.map +1 -0
- package/dist/util/dns.d.ts +11 -0
- package/dist/util/dns.d.ts.map +1 -0
- package/dist/util/dns.js +47 -0
- package/dist/util/dns.js.map +1 -0
- package/dist/util/error.d.ts +9 -0
- package/dist/util/error.d.ts.map +1 -0
- package/dist/util/error.js +11 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/header-utils.d.ts +35 -0
- package/dist/util/header-utils.d.ts.map +1 -0
- package/dist/util/header-utils.js +200 -0
- package/dist/util/header-utils.js.map +1 -0
- package/dist/util/openssl-compat.d.ts +2 -0
- package/dist/util/openssl-compat.d.ts.map +1 -0
- package/dist/util/openssl-compat.js +26 -0
- package/dist/util/openssl-compat.js.map +1 -0
- package/dist/util/promise.d.ts +10 -0
- package/dist/util/promise.d.ts.map +1 -0
- package/dist/util/promise.js +25 -0
- package/dist/util/promise.js.map +1 -0
- package/dist/util/request-utils.d.ts +46 -0
- package/dist/util/request-utils.d.ts.map +1 -0
- package/dist/util/request-utils.js +462 -0
- package/dist/util/request-utils.js.map +1 -0
- package/dist/util/server-utils.d.ts +2 -0
- package/dist/util/server-utils.d.ts.map +1 -0
- package/dist/util/server-utils.js +14 -0
- package/dist/util/server-utils.js.map +1 -0
- package/dist/util/socket-util.d.ts +28 -0
- package/dist/util/socket-util.d.ts.map +1 -0
- package/dist/util/socket-util.js +174 -0
- package/dist/util/socket-util.js.map +1 -0
- package/dist/util/tls.d.ts +68 -0
- package/dist/util/tls.d.ts.map +1 -0
- package/dist/util/tls.js +220 -0
- package/dist/util/tls.js.map +1 -0
- package/dist/util/type-utils.d.ts +14 -0
- package/dist/util/type-utils.d.ts.map +1 -0
- package/dist/util/type-utils.js +3 -0
- package/dist/util/type-utils.js.map +1 -0
- package/dist/util/url.d.ts +17 -0
- package/dist/util/url.d.ts.map +1 -0
- package/dist/util/url.js +96 -0
- package/dist/util/url.js.map +1 -0
- package/dist/util/util.d.ts +8 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +41 -0
- package/dist/util/util.js.map +1 -0
- package/docs/api-docs-landing-page.md +11 -0
- package/docs/runkitExample.js +16 -0
- package/docs/setup.md +136 -0
- package/nfyb8qx5.cjs +1 -0
- package/package.json +194 -4
- package/src/admin/admin-bin.ts +62 -0
- package/src/admin/admin-plugin-types.ts +29 -0
- package/src/admin/admin-server.ts +619 -0
- package/src/admin/graphql-utils.ts +28 -0
- package/src/admin/mockttp-admin-model.ts +264 -0
- package/src/admin/mockttp-admin-plugin.ts +59 -0
- package/src/admin/mockttp-admin-server.ts +27 -0
- package/src/admin/mockttp-schema.ts +222 -0
- package/src/client/admin-client.ts +652 -0
- package/src/client/admin-query.ts +52 -0
- package/src/client/mocked-endpoint-client.ts +32 -0
- package/src/client/mockttp-admin-request-builder.ts +540 -0
- package/src/client/mockttp-client.ts +178 -0
- package/src/client/schema-introspection.ts +131 -0
- package/src/main.browser.ts +60 -0
- package/src/main.ts +160 -0
- package/src/mockttp.ts +926 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
- package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
- package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
- package/src/rules/base-rule-builder.ts +312 -0
- package/src/rules/completion-checkers.ts +90 -0
- package/src/rules/http-agents.ts +119 -0
- package/src/rules/matchers.ts +665 -0
- package/src/rules/passthrough-handling-definitions.ts +111 -0
- package/src/rules/passthrough-handling.ts +376 -0
- package/src/rules/proxy-config.ts +136 -0
- package/src/rules/requests/request-handler-definitions.ts +1089 -0
- package/src/rules/requests/request-handlers.ts +1369 -0
- package/src/rules/requests/request-rule-builder.ts +481 -0
- package/src/rules/requests/request-rule.ts +148 -0
- package/src/rules/rule-deserialization.ts +55 -0
- package/src/rules/rule-parameters.ts +41 -0
- package/src/rules/rule-serialization.ts +29 -0
- package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
- package/src/rules/websockets/websocket-handlers.ts +509 -0
- package/src/rules/websockets/websocket-rule-builder.ts +275 -0
- package/src/rules/websockets/websocket-rule.ts +136 -0
- package/src/serialization/body-serialization.ts +84 -0
- package/src/serialization/serialization.ts +373 -0
- package/src/server/http-combo-server.ts +424 -0
- package/src/server/mocked-endpoint.ts +44 -0
- package/src/server/mockttp-server.ts +1110 -0
- package/src/types.ts +433 -0
- package/src/util/buffer-utils.ts +164 -0
- package/src/util/dns.ts +52 -0
- package/src/util/error.ts +18 -0
- package/src/util/header-utils.ts +220 -0
- package/src/util/openssl-compat.ts +26 -0
- package/src/util/promise.ts +31 -0
- package/src/util/request-utils.ts +607 -0
- package/src/util/server-utils.ts +18 -0
- package/src/util/socket-util.ts +193 -0
- package/src/util/tls.ts +348 -0
- package/src/util/type-utils.ts +15 -0
- package/src/util/url.ts +113 -0
- 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
|
+
}
|