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,619 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import * as express from 'express';
|
|
3
|
+
import * as cors from 'cors';
|
|
4
|
+
import corsGate = require('cors-gate');
|
|
5
|
+
import * as http from 'http';
|
|
6
|
+
import * as net from 'net';
|
|
7
|
+
import * as bodyParser from 'body-parser';
|
|
8
|
+
import * as Ws from 'ws';
|
|
9
|
+
import { v4 as uuid } from "uuid";
|
|
10
|
+
|
|
11
|
+
import { createHandler as createGraphQLHandler } from 'graphql-http/lib/use/express';
|
|
12
|
+
import { execute, formatError, GraphQLScalarType, subscribe } from 'graphql';
|
|
13
|
+
import gql from 'graphql-tag';
|
|
14
|
+
import { makeExecutableSchema } from '@graphql-tools/schema';
|
|
15
|
+
import { SubscriptionServer } from '@httptoolkit/subscriptions-transport-ws';
|
|
16
|
+
import { EventEmitter } from 'stream';
|
|
17
|
+
import DuplexPair = require('native-duplexpair');
|
|
18
|
+
|
|
19
|
+
import { makeDestroyable, DestroyableServer } from "destroyable-server";
|
|
20
|
+
import { isErrorLike } from '../util/error';
|
|
21
|
+
import { objectAllPromise } from '../util/promise';
|
|
22
|
+
|
|
23
|
+
import { DEFAULT_ADMIN_SERVER_PORT } from '../types';
|
|
24
|
+
|
|
25
|
+
import { RuleParameters } from '../rules/rule-parameters';
|
|
26
|
+
import { AdminPlugin, PluginConstructorMap, PluginStartParamsMap } from './admin-plugin-types';
|
|
27
|
+
import { parseAnyAst } from './graphql-utils';
|
|
28
|
+
import { MockttpAdminPlugin } from './mockttp-admin-plugin';
|
|
29
|
+
|
|
30
|
+
export interface AdminServerOptions<Plugins extends { [key: string]: AdminPlugin<any, any> }> {
|
|
31
|
+
/**
|
|
32
|
+
* Should the admin server print extra debug information? This enables admin server debugging
|
|
33
|
+
* only - individual mock session debugging must be enabled separately.
|
|
34
|
+
*/
|
|
35
|
+
debug?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Set CORS options to limit the sites which can send requests to manage this admin server.
|
|
39
|
+
*/
|
|
40
|
+
corsOptions?: cors.CorsOptions & {
|
|
41
|
+
strict?: boolean,
|
|
42
|
+
allowPrivateNetworkAccess?: boolean
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set a keep alive frequency in milliseconds for the subscription & stream websockets of each
|
|
47
|
+
* session, to ensure they remain connected in long-lived connections, especially in browsers which
|
|
48
|
+
* often close quiet background connections automatically.
|
|
49
|
+
*/
|
|
50
|
+
webSocketKeepAlive?: number;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Override the default parameters for sessions started from this admin server. These values will be
|
|
54
|
+
* used for each setting that is not explicitly specified by the client when creating a mock session.
|
|
55
|
+
*/
|
|
56
|
+
pluginDefaults?: Partial<PluginStartParamsMap<Plugins>>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Some rule options can't easily be specified in remote clients, since they need to access
|
|
60
|
+
* server-side state or Node APIs directly. To handle this, referenceable parameters can
|
|
61
|
+
* be provided here, and referenced with a `{ [MOCKTTP_PARAM_REF]: <value> }` value in place
|
|
62
|
+
* of the real parameter in the remote client.
|
|
63
|
+
*/
|
|
64
|
+
ruleParameters?: {
|
|
65
|
+
[key: string]: any
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @internal
|
|
70
|
+
*
|
|
71
|
+
* This API is not yet stable, and is intended for internal use only. It may change in future
|
|
72
|
+
* in minor versions without warning.
|
|
73
|
+
*
|
|
74
|
+
* This defines admin plugin modules: remote-controlled types of mocks that should be attached to this
|
|
75
|
+
* admin server, to allow configuring other mocking services through the same HTTP infrastructure.
|
|
76
|
+
*
|
|
77
|
+
* This can be useful when mocking non-HTTP protocols like WebRTC.
|
|
78
|
+
*/
|
|
79
|
+
adminPlugins?: PluginConstructorMap<Plugins>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function strictOriginMatch(
|
|
83
|
+
origin: string | undefined,
|
|
84
|
+
expectedOrigin: cors.CorsOptions['origin']
|
|
85
|
+
): Promise<boolean> {
|
|
86
|
+
if (!origin) return false;
|
|
87
|
+
|
|
88
|
+
if (typeof expectedOrigin === 'string') {
|
|
89
|
+
return expectedOrigin === origin;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (_.isRegExp(expectedOrigin)) {
|
|
93
|
+
return !!origin.match(expectedOrigin);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (_.isArray(expectedOrigin)) {
|
|
97
|
+
return _.some(expectedOrigin, (exp) =>
|
|
98
|
+
strictOriginMatch(origin, exp)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (_.isFunction(expectedOrigin)) {
|
|
103
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
104
|
+
expectedOrigin(origin, (error, result) => {
|
|
105
|
+
if (error) reject(error);
|
|
106
|
+
else resolve(strictOriginMatch(origin, result));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// We don't allow boolean or undefined matches
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class AdminServer<Plugins extends { [key: string]: AdminPlugin<any, any> }> {
|
|
116
|
+
|
|
117
|
+
private debug: boolean;
|
|
118
|
+
private requiredOrigin: cors.CorsOptions['origin'] | false;
|
|
119
|
+
private webSocketKeepAlive: number | undefined;
|
|
120
|
+
private ruleParams: RuleParameters;
|
|
121
|
+
|
|
122
|
+
private app = express();
|
|
123
|
+
private server: DestroyableServer<http.Server> | null = null;
|
|
124
|
+
private eventEmitter = new EventEmitter();
|
|
125
|
+
|
|
126
|
+
private adminPlugins: PluginConstructorMap<Plugins>;
|
|
127
|
+
|
|
128
|
+
private sessions: { [id: string]: {
|
|
129
|
+
router: express.Router,
|
|
130
|
+
stop: () => Promise<void>,
|
|
131
|
+
|
|
132
|
+
subscriptionServer: SubscriptionServer,
|
|
133
|
+
streamServer: Ws.Server,
|
|
134
|
+
|
|
135
|
+
sessionPlugins: Plugins
|
|
136
|
+
} } = { };
|
|
137
|
+
|
|
138
|
+
constructor(options: AdminServerOptions<Plugins> = {}) {
|
|
139
|
+
this.debug = options.debug || false;
|
|
140
|
+
if (this.debug) console.log('Admin server started in debug mode');
|
|
141
|
+
|
|
142
|
+
this.webSocketKeepAlive = options.webSocketKeepAlive || undefined;
|
|
143
|
+
this.ruleParams = options.ruleParameters || {};
|
|
144
|
+
this.adminPlugins = options.adminPlugins || {} as PluginConstructorMap<Plugins>;
|
|
145
|
+
|
|
146
|
+
if (options.corsOptions?.allowPrivateNetworkAccess) {
|
|
147
|
+
// Allow web pages on non-local URLs (testsite.example.com, not localhost) to
|
|
148
|
+
// send requests to this admin server too. Without this, those requests will
|
|
149
|
+
// fail after rejected preflights in recent Chrome (from ~v102, ish? Unclear).
|
|
150
|
+
// This is combined with the origin restrictions that may be set, so only
|
|
151
|
+
// accepted origins will be allowed to make these requests.
|
|
152
|
+
this.app.use((req, res, next) => {
|
|
153
|
+
if (req.headers["access-control-request-private-network"]) {
|
|
154
|
+
res.setHeader("access-control-allow-private-network", "true");
|
|
155
|
+
}
|
|
156
|
+
next(null);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.app.use(cors(options.corsOptions));
|
|
161
|
+
|
|
162
|
+
// If you use strict CORS, and set a specific origin, we'll enforce it:
|
|
163
|
+
this.requiredOrigin = !!options.corsOptions &&
|
|
164
|
+
!!options.corsOptions.strict &&
|
|
165
|
+
!!options.corsOptions.origin &&
|
|
166
|
+
typeof options.corsOptions.origin !== 'boolean' &&
|
|
167
|
+
options.corsOptions.origin;
|
|
168
|
+
|
|
169
|
+
if (this.requiredOrigin) {
|
|
170
|
+
this.app.use(corsGate({
|
|
171
|
+
strict: true, // MUST send an allowed origin
|
|
172
|
+
allowSafe: false, // Even for HEAD/GET requests (should be none anyway)
|
|
173
|
+
origin: '' // No base origin - we accept *no* same-origin requests
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.app.use(bodyParser.json({ limit: '50mb' }));
|
|
178
|
+
|
|
179
|
+
const defaultPluginStartParams: Partial<PluginStartParamsMap<Plugins>> = options.pluginDefaults ?? {};
|
|
180
|
+
|
|
181
|
+
this.app.post('/start', async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const rawConfig = req.body;
|
|
184
|
+
|
|
185
|
+
// New clients send: "{ plugins: { http: {...}, webrtc: {...} } }" etc. Old clients just send
|
|
186
|
+
// the HTTP options bare with no wrapper, so we wrap them for backward compat.
|
|
187
|
+
const isPluginAwareClient = ('plugins' in rawConfig);
|
|
188
|
+
|
|
189
|
+
const providedPluginStartParams = (!isPluginAwareClient
|
|
190
|
+
? { // Backward compat: this means the client is not plugin-aware, and so all options are Mockttp options
|
|
191
|
+
http: {
|
|
192
|
+
options: _.cloneDeep(rawConfig),
|
|
193
|
+
port: (typeof req.query.port === 'string')
|
|
194
|
+
? JSON.parse(req.query.port)
|
|
195
|
+
: undefined
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
: rawConfig.plugins
|
|
199
|
+
) as PluginStartParamsMap<Plugins>;
|
|
200
|
+
|
|
201
|
+
// For each plugin that was specified, we pull default params into their start params.
|
|
202
|
+
const pluginStartParams = _.mapValues((providedPluginStartParams), (params, pluginId) => {
|
|
203
|
+
return _.merge({}, defaultPluginStartParams[pluginId], params);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (this.debug) console.log('Admin server starting mock session with config', pluginStartParams);
|
|
207
|
+
|
|
208
|
+
// Backward compat: do an explicit check for HTTP port conflicts
|
|
209
|
+
const httpPort = (pluginStartParams as { http?: { port: number } }).http?.port;
|
|
210
|
+
if (_.isNumber(httpPort) && this.sessions[httpPort] != null) {
|
|
211
|
+
res.status(409).json({
|
|
212
|
+
error: `Cannot start: mock server is already running on port ${httpPort}`
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const missingPluginId = Object.keys(pluginStartParams).find(pluginId => !(pluginId in this.adminPlugins));
|
|
218
|
+
if (missingPluginId) {
|
|
219
|
+
res.status(400).json({
|
|
220
|
+
error: `Request to mock using unrecognized plugin: ${missingPluginId}`
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sessionPlugins = _.mapValues(pluginStartParams, (__, pluginId: keyof Plugins) => {
|
|
226
|
+
const PluginType = this.adminPlugins[pluginId];
|
|
227
|
+
return new PluginType();
|
|
228
|
+
}) as Plugins;
|
|
229
|
+
|
|
230
|
+
const pluginStartResults = await objectAllPromise(
|
|
231
|
+
_.mapValues(sessionPlugins, (plugin, pluginId: keyof Plugins) =>
|
|
232
|
+
plugin.start(pluginStartParams[pluginId])
|
|
233
|
+
)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// More backward compat: old clients assume that the port is also the management id.
|
|
237
|
+
const sessionId = isPluginAwareClient
|
|
238
|
+
? uuid()
|
|
239
|
+
: (sessionPlugins as any as {
|
|
240
|
+
'http': MockttpAdminPlugin
|
|
241
|
+
}).http.getMockServer().port.toString();
|
|
242
|
+
|
|
243
|
+
await this.startSessionManagementAPI(sessionId, sessionPlugins);
|
|
244
|
+
|
|
245
|
+
if (isPluginAwareClient) {
|
|
246
|
+
res.json({
|
|
247
|
+
id: sessionId,
|
|
248
|
+
pluginData: _.mapValues(pluginStartResults, (r: unknown) =>
|
|
249
|
+
r ?? {} // Always return _something_, even if the plugin returns null/undefined.
|
|
250
|
+
)
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
res.json({
|
|
254
|
+
id: sessionId,
|
|
255
|
+
...(pluginStartResults['http']!)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
res.status(500).json({ error: `Failed to start mock session: ${
|
|
260
|
+
(isErrorLike(e) && e.message) || e
|
|
261
|
+
}` });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this.app.post('/reset', async (req, res) => {
|
|
266
|
+
try {
|
|
267
|
+
await this.resetAdminServer();
|
|
268
|
+
res.json({ success: true });
|
|
269
|
+
} catch (e) {
|
|
270
|
+
res.status(500).json({
|
|
271
|
+
error: (isErrorLike(e) && e.message) || 'Unknown error'
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
// Dynamically route to mock sessions ourselves, so we can easily add/remove
|
|
278
|
+
// sessions as we see fit later on.
|
|
279
|
+
const sessionRequest = (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
280
|
+
const sessionId = req.params.id;
|
|
281
|
+
const sessionRouter = this.sessions[sessionId]?.router;
|
|
282
|
+
|
|
283
|
+
if (!sessionRouter) {
|
|
284
|
+
res.status(404).send('Unknown mock session');
|
|
285
|
+
console.error(`Request for unknown mock session with id: ${sessionId}`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
sessionRouter(req, res, next);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.app.use('/session/:id/', sessionRequest);
|
|
293
|
+
this.app.use('/server/:id/', sessionRequest); // Old URL for backward compat
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async resetAdminServer() {
|
|
297
|
+
if (this.debug) console.log('Resetting admin server');
|
|
298
|
+
await Promise.all(
|
|
299
|
+
Object.values(this.sessions).map(({ stop }) => stop())
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Subscribe to hear when each mock ession is started. The listener is provided the
|
|
305
|
+
* session plugin data, which can be used to log session startup, add side-effects that
|
|
306
|
+
* run elsewhere at startup, or preconfigure every started plugin in addition ways.
|
|
307
|
+
*
|
|
308
|
+
* This is run synchronously when a session is created, after it has fully started
|
|
309
|
+
* but before its been returned to remote clients.
|
|
310
|
+
*/
|
|
311
|
+
on(event: 'mock-session-started', listener: (plugins: Plugins, sessionId: string) => void): void;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Subscribe to hear when a mock session is stopped. The listener is provided with
|
|
315
|
+
* the state of all plugins that are about to be stopped. This can be used to log
|
|
316
|
+
* mock session shutdown, add side-effects that run elsewhere at shutdown, or clean
|
|
317
|
+
* up after sessions in other ways.
|
|
318
|
+
*
|
|
319
|
+
* This is run synchronously immediately before the session is shutdown, whilst all
|
|
320
|
+
* its state is still available, and before remote clients have had any response to
|
|
321
|
+
* their request. This is also run before shutdown when the admin server itself is
|
|
322
|
+
* cleanly shutdown with `adminServer.stop()`.
|
|
323
|
+
*/
|
|
324
|
+
on(event: 'mock-session-stopping', listener: (plugins: Plugins, sessionId: string) => void): void;
|
|
325
|
+
on(event: string, listener: (...args: any) => void): void {
|
|
326
|
+
this.eventEmitter.on(event, listener);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async start(
|
|
330
|
+
listenOptions: number | {
|
|
331
|
+
port: number,
|
|
332
|
+
host: string
|
|
333
|
+
} = DEFAULT_ADMIN_SERVER_PORT
|
|
334
|
+
) {
|
|
335
|
+
if (this.server) throw new Error('Admin server already running');
|
|
336
|
+
|
|
337
|
+
await new Promise<void>((resolve, reject) => {
|
|
338
|
+
this.server = makeDestroyable(this.app.listen(listenOptions, resolve));
|
|
339
|
+
|
|
340
|
+
this.server.on('error', reject);
|
|
341
|
+
|
|
342
|
+
this.server.on('upgrade', async (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
|
343
|
+
const reqOrigin = req.headers['origin'] as string | undefined;
|
|
344
|
+
if (this.requiredOrigin && !await strictOriginMatch(reqOrigin, this.requiredOrigin)) {
|
|
345
|
+
console.warn(`Websocket request from invalid origin: ${req.headers['origin']}`);
|
|
346
|
+
socket.destroy();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const isSubscriptionRequest = req.url!.match(/^\/(?:server|session)\/([\w\d\-]+)\/subscription$/);
|
|
351
|
+
const isStreamRequest = req.url!.match(/^\/(?:server|session)\/([\w\d\-]+)\/stream$/);
|
|
352
|
+
const isMatch = isSubscriptionRequest || isStreamRequest;
|
|
353
|
+
|
|
354
|
+
if (isMatch) {
|
|
355
|
+
const sessionId = isMatch[1];
|
|
356
|
+
|
|
357
|
+
let wsServer: Ws.Server = isSubscriptionRequest
|
|
358
|
+
? this.sessions[sessionId]?.subscriptionServer.server
|
|
359
|
+
: this.sessions[sessionId]?.streamServer;
|
|
360
|
+
|
|
361
|
+
if (wsServer) {
|
|
362
|
+
wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
363
|
+
wsServer.emit('connection', ws, req);
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
console.warn(`Websocket request for unrecognized mock session: ${sessionId}`);
|
|
367
|
+
socket.destroy();
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
console.warn(`Unrecognized websocket request for ${req.url}`);
|
|
371
|
+
socket.destroy();
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private async startSessionManagementAPI(sessionId: string, plugins: Plugins): Promise<void> {
|
|
378
|
+
const mockSessionRouter = express.Router();
|
|
379
|
+
|
|
380
|
+
let running = true;
|
|
381
|
+
const stopSession = async () => {
|
|
382
|
+
if (!running) return;
|
|
383
|
+
running = false;
|
|
384
|
+
|
|
385
|
+
this.eventEmitter.emit('mock-session-stopping', plugins);
|
|
386
|
+
|
|
387
|
+
const session = this.sessions[sessionId];
|
|
388
|
+
delete this.sessions[sessionId];
|
|
389
|
+
|
|
390
|
+
await Promise.all(Object.values(plugins).map(plugin => plugin.stop()));
|
|
391
|
+
|
|
392
|
+
session.subscriptionServer.close();
|
|
393
|
+
|
|
394
|
+
// Close with code 1000 (purpose is complete - no more streaming happening)
|
|
395
|
+
session.streamServer.clients.forEach((client) => {
|
|
396
|
+
client.close(1000);
|
|
397
|
+
});
|
|
398
|
+
session.streamServer.close();
|
|
399
|
+
session.streamServer.emit('close');
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
mockSessionRouter.post('/stop', async (req, res) => {
|
|
403
|
+
await stopSession();
|
|
404
|
+
res.json({ success: true });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// A pair of sockets, representing the 2-way connection between the session & WSs.
|
|
408
|
+
// All websocket messages are written to wsSocket, and then read from sessionSocket
|
|
409
|
+
// All session messages are written to sessionSocket, and then read from wsSocket and sent
|
|
410
|
+
const { socket1: wsSocket, socket2: sessionSocket } = new DuplexPair();
|
|
411
|
+
|
|
412
|
+
// This receives a lot of listeners! One channel per matcher, handler & completion checker,
|
|
413
|
+
// and each adds listeners for data/error/finish/etc. That's OK, it's not generally a leak,
|
|
414
|
+
// but maybe 100 would be a bit suspicious (unless you have 30+ active rules).
|
|
415
|
+
sessionSocket.setMaxListeners(100);
|
|
416
|
+
|
|
417
|
+
if (this.debug) {
|
|
418
|
+
sessionSocket.on('data', (d: any) => {
|
|
419
|
+
console.log('Streaming data from WS clients:', d.toString());
|
|
420
|
+
});
|
|
421
|
+
wsSocket.on('data', (d: any) => {
|
|
422
|
+
console.log('Streaming data to WS clients:', d.toString());
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const streamServer = new Ws.Server({ noServer: true });
|
|
427
|
+
streamServer.on('connection', (ws) => {
|
|
428
|
+
let newClientStream = Ws.createWebSocketStream(ws, {});
|
|
429
|
+
wsSocket.pipe(newClientStream).pipe(wsSocket, { end: false });
|
|
430
|
+
|
|
431
|
+
const unpipe = () => {
|
|
432
|
+
wsSocket.unpipe(newClientStream);
|
|
433
|
+
newClientStream.unpipe(wsSocket);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
newClientStream.on('error', unpipe);
|
|
437
|
+
wsSocket.on('end', unpipe);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
streamServer.on('close', () => {
|
|
441
|
+
wsSocket.end();
|
|
442
|
+
sessionSocket.end();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Handle errors by logging & stopping this session
|
|
446
|
+
const onStreamError = (e: Error) => {
|
|
447
|
+
if (!running) return; // We don't care about connection issues during shutdown
|
|
448
|
+
console.error("Error in admin server stream, shutting down mock session");
|
|
449
|
+
console.error(e);
|
|
450
|
+
stopSession();
|
|
451
|
+
};
|
|
452
|
+
wsSocket.on('error', onStreamError);
|
|
453
|
+
sessionSocket.on('error', onStreamError);
|
|
454
|
+
|
|
455
|
+
const schema = makeExecutableSchema({
|
|
456
|
+
typeDefs: [
|
|
457
|
+
AdminServer.baseSchema,
|
|
458
|
+
...Object.values(plugins).map(plugin => plugin.schema)
|
|
459
|
+
],
|
|
460
|
+
resolvers: [
|
|
461
|
+
this.buildBaseResolvers(sessionId),
|
|
462
|
+
...Object.values(plugins).map(plugin =>
|
|
463
|
+
plugin.buildResolvers(sessionSocket, this.ruleParams)
|
|
464
|
+
)
|
|
465
|
+
]
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const subscriptionServer = SubscriptionServer.create({
|
|
469
|
+
schema,
|
|
470
|
+
execute,
|
|
471
|
+
subscribe,
|
|
472
|
+
keepAlive: this.webSocketKeepAlive
|
|
473
|
+
}, {
|
|
474
|
+
noServer: true
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
mockSessionRouter.use(
|
|
478
|
+
createGraphQLHandler({
|
|
479
|
+
schema,
|
|
480
|
+
|
|
481
|
+
// Add console logging of all GQL errors:
|
|
482
|
+
formatError: (error) => {
|
|
483
|
+
console.error(error.stack);
|
|
484
|
+
return error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
));
|
|
488
|
+
|
|
489
|
+
if (this.webSocketKeepAlive) {
|
|
490
|
+
// If we have a keep-alive set, send the client a ping frame every Xms to
|
|
491
|
+
// try and stop closes (especially by browsers) due to inactivity.
|
|
492
|
+
const webSocketKeepAlive = setInterval(() => {
|
|
493
|
+
[
|
|
494
|
+
...streamServer.clients,
|
|
495
|
+
...subscriptionServer.server.clients
|
|
496
|
+
].forEach((client) => {
|
|
497
|
+
if (client.readyState !== Ws.OPEN) return;
|
|
498
|
+
client.ping();
|
|
499
|
+
});
|
|
500
|
+
}, this.webSocketKeepAlive);
|
|
501
|
+
|
|
502
|
+
// We use the stream server's shutdown as an easy proxy event for full shutdown:
|
|
503
|
+
streamServer.on('close', () => clearInterval(webSocketKeepAlive));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
this.sessions[sessionId] = {
|
|
507
|
+
sessionPlugins: plugins,
|
|
508
|
+
router: mockSessionRouter,
|
|
509
|
+
streamServer,
|
|
510
|
+
subscriptionServer,
|
|
511
|
+
stop: stopSession
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
this.eventEmitter.emit('mock-session-started', plugins, sessionId);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
stop(): Promise<void> {
|
|
518
|
+
if (!this.server) return Promise.resolve();
|
|
519
|
+
|
|
520
|
+
return Promise.all([
|
|
521
|
+
this.server.destroy(),
|
|
522
|
+
].concat(
|
|
523
|
+
Object.values(this.sessions).map((s) => s.stop())
|
|
524
|
+
)).then(() => {
|
|
525
|
+
this.server = null;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private static baseSchema = gql`
|
|
530
|
+
type Mutation {
|
|
531
|
+
reset: Void
|
|
532
|
+
enableDebug: Void
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
type Query {
|
|
536
|
+
ruleParameterKeys: [String!]!
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
type Subscription {
|
|
540
|
+
_empty_placeholder_: Void # A placeholder so we can define an empty extendable type
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
scalar Void
|
|
544
|
+
scalar Raw
|
|
545
|
+
scalar Json
|
|
546
|
+
scalar Buffer
|
|
547
|
+
`;
|
|
548
|
+
|
|
549
|
+
private buildBaseResolvers(sessionId: string) {
|
|
550
|
+
return {
|
|
551
|
+
Query: {
|
|
552
|
+
ruleParameterKeys: () => this.ruleParameterKeys
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
Mutation: {
|
|
556
|
+
reset: () => this.resetPluginsForSession(sessionId),
|
|
557
|
+
enableDebug: () => this.enableDebugForSession(sessionId)
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
Raw: new GraphQLScalarType({
|
|
561
|
+
name: 'Raw',
|
|
562
|
+
description: 'A raw entity, serialized directly (must be JSON-compatible)',
|
|
563
|
+
serialize: (value: any) => value,
|
|
564
|
+
parseValue: (input: string): any => input,
|
|
565
|
+
parseLiteral: parseAnyAst
|
|
566
|
+
}),
|
|
567
|
+
|
|
568
|
+
// Json exists just for API backward compatibility - all new data should be Raw.
|
|
569
|
+
// Converting to JSON is pointless, since bodies all contain JSON anyway.
|
|
570
|
+
Json: new GraphQLScalarType({
|
|
571
|
+
name: 'Json',
|
|
572
|
+
description: 'A JSON entity, serialized as a simple JSON string',
|
|
573
|
+
serialize: (value: any) => JSON.stringify(value),
|
|
574
|
+
parseValue: (input: string): any => JSON.parse(input),
|
|
575
|
+
parseLiteral: parseAnyAst
|
|
576
|
+
}),
|
|
577
|
+
|
|
578
|
+
Void: new GraphQLScalarType({
|
|
579
|
+
name: 'Void',
|
|
580
|
+
description: 'Nothing at all',
|
|
581
|
+
serialize: (value: any) => null,
|
|
582
|
+
parseValue: (input: string): any => null,
|
|
583
|
+
parseLiteral: (): any => { throw new Error('Void literals are not supported') }
|
|
584
|
+
}),
|
|
585
|
+
|
|
586
|
+
Buffer: new GraphQLScalarType({
|
|
587
|
+
name: 'Buffer',
|
|
588
|
+
description: 'A buffer',
|
|
589
|
+
serialize: (value: Buffer) => {
|
|
590
|
+
return value.toString('base64');
|
|
591
|
+
},
|
|
592
|
+
parseValue: (input: string) => {
|
|
593
|
+
return Buffer.from(input, 'base64');
|
|
594
|
+
},
|
|
595
|
+
parseLiteral: parseAnyAst
|
|
596
|
+
})
|
|
597
|
+
};
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
private resetPluginsForSession(sessionId: string) {
|
|
601
|
+
return Promise.all(
|
|
602
|
+
Object.values(this.sessions[sessionId].sessionPlugins).map(plugin =>
|
|
603
|
+
plugin.reset?.()
|
|
604
|
+
)
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
private enableDebugForSession(sessionId: string) {
|
|
609
|
+
return Promise.all(
|
|
610
|
+
Object.values(this.sessions[sessionId].sessionPlugins).map(plugin =>
|
|
611
|
+
plugin.enableDebug?.()
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
get ruleParameterKeys() {
|
|
617
|
+
return Object.keys(this.ruleParams);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import { Kind, ObjectValueNode, ValueNode } from "graphql";
|
|
3
|
+
|
|
4
|
+
export function astToObject<T>(ast: ObjectValueNode): T {
|
|
5
|
+
return <T> _.zipObject(
|
|
6
|
+
ast.fields.map((f) => f.name.value),
|
|
7
|
+
ast.fields.map((f) => parseAnyAst(f.value))
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseAnyAst(ast: ValueNode): any {
|
|
12
|
+
switch (ast.kind) {
|
|
13
|
+
case Kind.OBJECT:
|
|
14
|
+
return astToObject<any>(ast);
|
|
15
|
+
case Kind.LIST:
|
|
16
|
+
return ast.values.map(parseAnyAst);
|
|
17
|
+
case Kind.BOOLEAN:
|
|
18
|
+
case Kind.ENUM:
|
|
19
|
+
case Kind.FLOAT:
|
|
20
|
+
case Kind.INT:
|
|
21
|
+
case Kind.STRING:
|
|
22
|
+
return ast.value;
|
|
23
|
+
case Kind.NULL:
|
|
24
|
+
return null;
|
|
25
|
+
case Kind.VARIABLE:
|
|
26
|
+
throw new Error("No idea what parsing a 'variable' means");
|
|
27
|
+
}
|
|
28
|
+
}
|