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
package/src/types.ts
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import stream = require('stream');
|
|
2
|
+
import http = require('http');
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ADMIN_SERVER_PORT = 45454;
|
|
6
|
+
|
|
7
|
+
export enum Method {
|
|
8
|
+
GET,
|
|
9
|
+
POST,
|
|
10
|
+
PUT,
|
|
11
|
+
DELETE,
|
|
12
|
+
PATCH,
|
|
13
|
+
HEAD,
|
|
14
|
+
OPTIONS
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export enum RulePriority {
|
|
18
|
+
FALLBACK = 0,
|
|
19
|
+
DEFAULT = 1
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Headers {
|
|
23
|
+
// An arbitrary set of headers that are known to
|
|
24
|
+
// only ever appear once (for valid requests).
|
|
25
|
+
host?: string;
|
|
26
|
+
'content-length'?: string;
|
|
27
|
+
'content-type'?: string;
|
|
28
|
+
'user-agent'?: string;
|
|
29
|
+
cookie?: string;
|
|
30
|
+
':method'?: string;
|
|
31
|
+
':scheme'?: string;
|
|
32
|
+
':authority'?: string;
|
|
33
|
+
':path'?: string;
|
|
34
|
+
|
|
35
|
+
// In general there may be 0+ of any header
|
|
36
|
+
[key: string]: undefined | string | string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Trailers {
|
|
40
|
+
// 0+ of any trailer
|
|
41
|
+
[key: string]: undefined | string | string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type RawHeaders = Array<[key: string, value: string]>;
|
|
45
|
+
export type RawTrailers = RawHeaders; // Just a convenient alias
|
|
46
|
+
|
|
47
|
+
export interface Request {
|
|
48
|
+
id: string;
|
|
49
|
+
matchedRuleId?: string;
|
|
50
|
+
|
|
51
|
+
protocol: string;
|
|
52
|
+
httpVersion?: string; // Like timingEvents - not set remotely with older servers
|
|
53
|
+
method: string;
|
|
54
|
+
url: string;
|
|
55
|
+
path: string;
|
|
56
|
+
|
|
57
|
+
remoteIpAddress?: string; // Not set remotely with older servers
|
|
58
|
+
remotePort?: number; // Not set remotely with older servers
|
|
59
|
+
|
|
60
|
+
// Exists only if a host header is sent. A strong candidate for deprecation
|
|
61
|
+
// in future, since it's not clear that this comes from headers not the URL, and
|
|
62
|
+
// either way it duplicates existing data.
|
|
63
|
+
hostname?: string;
|
|
64
|
+
|
|
65
|
+
headers: Headers;
|
|
66
|
+
rawHeaders: RawHeaders;
|
|
67
|
+
|
|
68
|
+
timingEvents: TimingEvents;
|
|
69
|
+
tags: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface TlsConnectionEvent {
|
|
73
|
+
hostname?: string;
|
|
74
|
+
remoteIpAddress: string;
|
|
75
|
+
remotePort: number;
|
|
76
|
+
tags: string[];
|
|
77
|
+
timingEvents: TlsTimingEvents;
|
|
78
|
+
tlsMetadata: TlsSocketMetadata;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TlsSocketMetadata {
|
|
82
|
+
sniHostname?: string;
|
|
83
|
+
connectHostname?: string;
|
|
84
|
+
connectPort?: string;
|
|
85
|
+
clientAlpn?: string[];
|
|
86
|
+
ja3Fingerprint?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TlsPassthroughEvent extends TlsConnectionEvent {
|
|
90
|
+
id: string;
|
|
91
|
+
upstreamPort: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface TlsHandshakeFailure extends TlsConnectionEvent {
|
|
95
|
+
failureCause:
|
|
96
|
+
| 'closed'
|
|
97
|
+
| 'reset'
|
|
98
|
+
| 'cert-rejected'
|
|
99
|
+
| 'no-shared-cipher'
|
|
100
|
+
| 'handshake-timeout'
|
|
101
|
+
| 'unknown';
|
|
102
|
+
timingEvents: TlsFailureTimingEvents;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface TlsTimingEvents {
|
|
106
|
+
/**
|
|
107
|
+
* When the socket initially connected, in MS since the unix
|
|
108
|
+
* epoch.
|
|
109
|
+
*/
|
|
110
|
+
startTime: number;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* When the socket initially connected, equivalent to startTime.
|
|
114
|
+
*
|
|
115
|
+
* High-precision floating-point monotonically increasing timestamps.
|
|
116
|
+
* Comparable and precise, but not related to specific current time.
|
|
117
|
+
*/
|
|
118
|
+
connectTimestamp: number;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* When Mockttp's handshake for this connection was completed (if there
|
|
122
|
+
* was one). This is not set for passed through connections.
|
|
123
|
+
*/
|
|
124
|
+
handshakeTimestamp?: number;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* When the outer tunnel (e.g. a preceeding CONNECT request) was created,
|
|
128
|
+
* if there was one.
|
|
129
|
+
*/
|
|
130
|
+
tunnelTimestamp?: number;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* When the connection was closed, if it has been closed.
|
|
134
|
+
*/
|
|
135
|
+
disconnectTimestamp?: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface TlsFailureTimingEvents extends TlsTimingEvents {
|
|
139
|
+
/**
|
|
140
|
+
* When the TLS connection failed. This may be due to a failed handshake
|
|
141
|
+
* (in which case `handshakeTimestamp` will be undefined) or due to a
|
|
142
|
+
* subsequent error which means the TLS connection was not usable (like
|
|
143
|
+
* an immediate closure due to an async certificate rejection).
|
|
144
|
+
*/
|
|
145
|
+
failureTimestamp: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Internal representation of an ongoing HTTP request whilst it's being processed
|
|
149
|
+
export interface OngoingRequest extends Request, EventEmitter {
|
|
150
|
+
body: OngoingBody;
|
|
151
|
+
rawTrailers?: RawHeaders;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface OngoingBody {
|
|
155
|
+
asStream: () => stream.Readable;
|
|
156
|
+
asBuffer: () => Promise<Buffer>;
|
|
157
|
+
asDecodedBuffer: () => Promise<Buffer>;
|
|
158
|
+
asText: () => Promise<string>;
|
|
159
|
+
asJson: () => Promise<object>;
|
|
160
|
+
asFormData: () => Promise<{ [key: string]: string | string[] | undefined }>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface CompletedBody {
|
|
164
|
+
/**
|
|
165
|
+
* The raw bytes of the response. If a content encoding was used, this is
|
|
166
|
+
* the raw encoded data.
|
|
167
|
+
*/
|
|
168
|
+
buffer: Buffer;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The decoded bytes of the response. If no encoding was used, this is the
|
|
172
|
+
* same as `.buffer`. The response is decoded and returned asynchronously
|
|
173
|
+
* as a Promise.
|
|
174
|
+
*/
|
|
175
|
+
getDecodedBuffer(): Promise<Buffer | undefined>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The contents of the response, decoded and parsed as a UTF-8 string.
|
|
179
|
+
* The response is decoded and returned asynchronously as a Promise.
|
|
180
|
+
*/
|
|
181
|
+
getText(): Promise<string | undefined>;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The contents of the response, decoded, parsed as UTF-8 string, and
|
|
185
|
+
* then parsed a JSON. The response is decoded and returned asynchronously
|
|
186
|
+
* as a Promise.
|
|
187
|
+
*/
|
|
188
|
+
getJson(): Promise<object | undefined>;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The contents of the response, decoded, and then parsed automatically as
|
|
192
|
+
* either one of the form encoding types (either URL-encoded or multipart),
|
|
193
|
+
* determined automatically from the message content-type header.
|
|
194
|
+
*
|
|
195
|
+
* This method is convenient and offers a single mechanism to parse both
|
|
196
|
+
* formats, but you may want to consider parsing on format explicitly with
|
|
197
|
+
* the `getUrlEncodedFormData()` or `getMultipartFormData()` methods instead.
|
|
198
|
+
*
|
|
199
|
+
* After parsing & decoding, the result is returned asynchronously as a
|
|
200
|
+
* Promise for a key-value(s) object.
|
|
201
|
+
*/
|
|
202
|
+
getFormData(): Promise<{ [key: string]: string | string[] | undefined } | undefined>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The contents of the response, decoded, parsed as UTF-8 string, and then
|
|
206
|
+
* parsed as URL-encoded form data. After parsing & decoding, the result is
|
|
207
|
+
* returned asynchronously as a Promise for a key-value(s) object.
|
|
208
|
+
*/
|
|
209
|
+
getUrlEncodedFormData(): Promise<{ [key: string]: string | string[] | undefined } | undefined>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* The contents of the response, decoded, and then parsed as multi-part
|
|
213
|
+
* form data. The response is result is returned asynchronously as a
|
|
214
|
+
* Promise for an array of parts with their names, data and metadata.
|
|
215
|
+
*/
|
|
216
|
+
getMultipartFormData(): Promise<Array<{ name?: string, filename?: string, type?: string, data: Buffer }> | undefined>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Internal & external representation of an initiated (no body yet received) HTTP request.
|
|
220
|
+
export type InitiatedRequest = Request;
|
|
221
|
+
|
|
222
|
+
export interface AbortedRequest extends InitiatedRequest {
|
|
223
|
+
error?: {
|
|
224
|
+
name?: string;
|
|
225
|
+
code?: string;
|
|
226
|
+
message?: string;
|
|
227
|
+
stack?: string;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Internal & external representation of a fully completed HTTP request
|
|
232
|
+
export interface CompletedRequest extends Request {
|
|
233
|
+
body: CompletedBody;
|
|
234
|
+
rawTrailers: RawTrailers;
|
|
235
|
+
trailers: Trailers;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface TimingEvents {
|
|
239
|
+
// Milliseconds since unix epoch
|
|
240
|
+
startTime: number;
|
|
241
|
+
|
|
242
|
+
// High-precision floating-point monotonically increasing timestamps.
|
|
243
|
+
// Comparable and precise, but not related to specific current time.
|
|
244
|
+
startTimestamp: number; // When the request was initially received
|
|
245
|
+
bodyReceivedTimestamp?: number; // When the request body was fully received
|
|
246
|
+
headersSentTimestamp?: number; // When the response headers were sent
|
|
247
|
+
responseSentTimestamp?: number; // When the response was fully completed
|
|
248
|
+
|
|
249
|
+
wsAcceptedTimestamp?: number; // When the websocket was accepted
|
|
250
|
+
wsClosedTimestamp?: number; // When the websocket was closed
|
|
251
|
+
|
|
252
|
+
abortedTimestamp?: number; // When the connected was aborted
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface OngoingResponse extends http.ServerResponse {
|
|
256
|
+
id: string;
|
|
257
|
+
getHeaders(): Headers;
|
|
258
|
+
getRawHeaders(): RawHeaders;
|
|
259
|
+
body: OngoingBody;
|
|
260
|
+
getRawTrailers(): RawTrailers;
|
|
261
|
+
timingEvents: TimingEvents;
|
|
262
|
+
tags: string[];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface CompletedResponse {
|
|
266
|
+
id: string;
|
|
267
|
+
statusCode: number;
|
|
268
|
+
statusMessage: string;
|
|
269
|
+
headers: Headers;
|
|
270
|
+
rawHeaders: RawHeaders;
|
|
271
|
+
body: CompletedBody;
|
|
272
|
+
rawTrailers: RawTrailers;
|
|
273
|
+
trailers: Trailers;
|
|
274
|
+
timingEvents: TimingEvents;
|
|
275
|
+
tags: string[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export interface WebSocketMessage {
|
|
279
|
+
/**
|
|
280
|
+
* The id of this websocket stream. This will match the id of the request,
|
|
281
|
+
* the initial connection response, and any other WebSocket events for the
|
|
282
|
+
* same connection stream.
|
|
283
|
+
*/
|
|
284
|
+
streamId: string;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Whether the message was sent by Mockttp, or received from a Mockttp client.
|
|
288
|
+
*/
|
|
289
|
+
direction: 'sent' | 'received';
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The contents of the message as a raw buffer. This is already decompressed,
|
|
293
|
+
* if the WebSocket uses compression.
|
|
294
|
+
*/
|
|
295
|
+
content: Uint8Array;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Whether this is a string message or a raw binary data message.
|
|
299
|
+
*/
|
|
300
|
+
isBinary: boolean;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* A high-precision floating-point monotonically increasing timestamp.
|
|
304
|
+
* Comparable and precise, but not related to specific current time.
|
|
305
|
+
*
|
|
306
|
+
* To link this to the current time, compare it to `timingEvents.startTime`.
|
|
307
|
+
*/
|
|
308
|
+
eventTimestamp: number;
|
|
309
|
+
|
|
310
|
+
timingEvents: TimingEvents;
|
|
311
|
+
tags: string[];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface WebSocketClose {
|
|
315
|
+
/**
|
|
316
|
+
* The id of this websocket stream. This will match the id of the request,
|
|
317
|
+
* the initial connection response, and any other WebSocket events for the
|
|
318
|
+
* same connection stream.
|
|
319
|
+
*/
|
|
320
|
+
streamId: string;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The close code of the shutdown. This is the close code that was received
|
|
324
|
+
* from the remote client (either initiated remotely, or echoing our own sent
|
|
325
|
+
* close frame).
|
|
326
|
+
*
|
|
327
|
+
* This may be undefined only if a close frame was received but did not contain
|
|
328
|
+
* any close code. If no close frame was received before the connection was
|
|
329
|
+
* lost (i.e. the connection was not cleanly closed) this event will not
|
|
330
|
+
* fire at all, and an 'abort' event will fire instead.
|
|
331
|
+
*/
|
|
332
|
+
closeCode: number | undefined;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The close reason of the shutdown.
|
|
336
|
+
*/
|
|
337
|
+
closeReason: string;
|
|
338
|
+
|
|
339
|
+
timingEvents: TimingEvents;
|
|
340
|
+
tags: string[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* A client error event describes a request (or our best guess at parsing it),
|
|
345
|
+
* that wasn't correctly completed, and the error response it received, or
|
|
346
|
+
* 'aborted' if the connection was disconnected before we could respond.
|
|
347
|
+
*/
|
|
348
|
+
export interface ClientError {
|
|
349
|
+
errorCode?: string;
|
|
350
|
+
request: {
|
|
351
|
+
id: string;
|
|
352
|
+
timingEvents: TimingEvents;
|
|
353
|
+
tags: string[];
|
|
354
|
+
|
|
355
|
+
// All of these are best guess, depending on what's parseable:
|
|
356
|
+
protocol?: string;
|
|
357
|
+
httpVersion?: string;
|
|
358
|
+
method?: string;
|
|
359
|
+
url?: string;
|
|
360
|
+
path?: string;
|
|
361
|
+
|
|
362
|
+
headers: Headers;
|
|
363
|
+
rawHeaders: RawHeaders;
|
|
364
|
+
|
|
365
|
+
remoteIpAddress?: string;
|
|
366
|
+
remotePort?: number;
|
|
367
|
+
};
|
|
368
|
+
response: CompletedResponse | 'aborted';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* An event fired from an individual rule during request processing.
|
|
373
|
+
*/
|
|
374
|
+
export interface RuleEvent<T = unknown> {
|
|
375
|
+
requestId: string;
|
|
376
|
+
ruleId: string;
|
|
377
|
+
eventType: string;
|
|
378
|
+
eventData: T;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* A mocked endpoint provides methods to see the current state of
|
|
383
|
+
* a mock rule.
|
|
384
|
+
*/
|
|
385
|
+
export interface MockedEndpoint {
|
|
386
|
+
id: string;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get the requests that this endpoint has seen so far.
|
|
390
|
+
*
|
|
391
|
+
* This method returns a promise, which resolves with the requests seen
|
|
392
|
+
* up until now, once all ongoing requests have terminated. The returned
|
|
393
|
+
* lists are immutable, so won't change if more requests arrive in future.
|
|
394
|
+
* Call `getSeenRequests` again later to get an updated list.
|
|
395
|
+
*
|
|
396
|
+
* Requests are included here once the response is completed, even if the request
|
|
397
|
+
* itself failed, the responses failed or exceptions are thrown elsewhere. To
|
|
398
|
+
* watch for errors or detailed response info, look at the various server.on(event)
|
|
399
|
+
* methods.
|
|
400
|
+
*/
|
|
401
|
+
getSeenRequests(): Promise<CompletedRequest[]>;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Reports whether this endpoint is still pending: if it either hasn't seen the
|
|
405
|
+
* specified number of requests (if one was specified e.g. with .twice())
|
|
406
|
+
* or if it hasn't seen at least one request, by default.
|
|
407
|
+
*
|
|
408
|
+
* This method returns a promise, which resolves with the result once all
|
|
409
|
+
* ongoing requests have terminated.
|
|
410
|
+
*/
|
|
411
|
+
isPending(): Promise<boolean>;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export interface MockedEndpointData {
|
|
415
|
+
id: string;
|
|
416
|
+
explanation?: string;
|
|
417
|
+
seenRequests: CompletedRequest[];
|
|
418
|
+
isPending: boolean;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface Explainable {
|
|
422
|
+
explain(): string;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export interface ProxyEnvConfig {
|
|
426
|
+
HTTP_PROXY: string;
|
|
427
|
+
HTTPS_PROXY: string;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// A slightly weird one: this is necessary because we export types that inherit from EventEmitter,
|
|
431
|
+
// so the docs include EventEmitter's methods, which @link to this type, that's otherwise not
|
|
432
|
+
// defined in this module. Reexporting the values avoids warnings for that.
|
|
433
|
+
export type defaultMaxListeners = typeof EventEmitter.defaultMaxListeners;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import * as stream from 'stream';
|
|
4
|
+
|
|
5
|
+
import { isNode } from './util';
|
|
6
|
+
|
|
7
|
+
const MAX_BUFFER_SIZE = isNode
|
|
8
|
+
? require('buffer').constants.MAX_LENGTH
|
|
9
|
+
: Infinity;
|
|
10
|
+
|
|
11
|
+
export const asBuffer = (input: Buffer | Uint8Array | string) =>
|
|
12
|
+
Buffer.isBuffer(input)
|
|
13
|
+
? input
|
|
14
|
+
: typeof input === "string"
|
|
15
|
+
? Buffer.from(input, 'utf8')
|
|
16
|
+
// Is Array:
|
|
17
|
+
: Buffer.from(input);
|
|
18
|
+
|
|
19
|
+
export type BufferInProgress = Promise<Buffer> & {
|
|
20
|
+
currentChunks: Buffer[]; // Stores the body chunks as they arrive
|
|
21
|
+
failedWith?: Error; // Stores the error that killed the stream, if one did
|
|
22
|
+
events: EventEmitter; // Emits events - notably 'truncate' if data is truncated
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Takes a buffer and a stream, returns a simple stream that outputs the buffer then the stream. The stream
|
|
26
|
+
// is lazy, so doesn't read data in from the buffer or input until something here starts reading.
|
|
27
|
+
export const bufferThenStream = (buffer: BufferInProgress, inputStream: stream.Readable): stream.Readable => {
|
|
28
|
+
let active = false;
|
|
29
|
+
|
|
30
|
+
const outputStream = new stream.PassThrough({
|
|
31
|
+
// Note we use the default highWaterMark, which means this applies backpressure, pushing buffering
|
|
32
|
+
// onto the OS & backpressure on network instead of accepting data before we're ready to stream it.
|
|
33
|
+
|
|
34
|
+
// Without changing behaviour, we listen for read() events, and don't start streaming until we get one.
|
|
35
|
+
read(size) {
|
|
36
|
+
// On the first actual read of this stream, we pull from the buffer
|
|
37
|
+
// and then hook it up to the input.
|
|
38
|
+
if (!active) {
|
|
39
|
+
if (buffer.failedWith) {
|
|
40
|
+
outputStream.destroy(buffer.failedWith);
|
|
41
|
+
} else {
|
|
42
|
+
// First stream everything that's been buffered so far:
|
|
43
|
+
outputStream.write(Buffer.concat(buffer.currentChunks));
|
|
44
|
+
|
|
45
|
+
// Then start streaming all future incoming data:
|
|
46
|
+
inputStream.pipe(outputStream);
|
|
47
|
+
|
|
48
|
+
if (inputStream.readableEnded) outputStream.end();
|
|
49
|
+
if (inputStream.readableAborted) outputStream.destroy();
|
|
50
|
+
|
|
51
|
+
// Forward any future errors from the input stream:
|
|
52
|
+
inputStream.on('error', (e) => {
|
|
53
|
+
outputStream.emit('error', e)
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Silence 'unhandled rejection' warnings here, since we'll handle
|
|
57
|
+
// them on the stream instead
|
|
58
|
+
buffer.catch(() => {});
|
|
59
|
+
}
|
|
60
|
+
active = true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Except for the first activation logic (above) do the default transform read() steps just
|
|
64
|
+
// like a normal PassThrough stream.
|
|
65
|
+
return stream.Transform.prototype._read.call(this, size);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
buffer.events.on('truncate', (chunks) => {
|
|
70
|
+
// If the stream hasn't started yet, start it now, so it grabs the buffer
|
|
71
|
+
// data before it gets truncated:
|
|
72
|
+
if (!active) outputStream.read(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return outputStream;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const bufferToStream = (buffer: Buffer): stream.Readable => {
|
|
79
|
+
const outputStream = new stream.PassThrough();
|
|
80
|
+
outputStream.end(buffer);
|
|
81
|
+
return outputStream;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const streamToBuffer = (input: stream.Readable, maxSize = MAX_BUFFER_SIZE) => {
|
|
85
|
+
let chunks: Buffer[] = [];
|
|
86
|
+
|
|
87
|
+
const bufferPromise = <BufferInProgress> new Promise(
|
|
88
|
+
(resolve, reject) => {
|
|
89
|
+
function failWithAbortError() {
|
|
90
|
+
bufferPromise.failedWith = new Error('Aborted');
|
|
91
|
+
reject(bufferPromise.failedWith);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If stream has already finished/aborted, resolve accordingly immediately:
|
|
95
|
+
if (input.readableEnded) return resolve(Buffer.from([]));
|
|
96
|
+
if (input.readableAborted) return failWithAbortError();
|
|
97
|
+
|
|
98
|
+
let currentSize = 0;
|
|
99
|
+
const onData = (d: Buffer) => {
|
|
100
|
+
currentSize += d.length;
|
|
101
|
+
chunks.push(d);
|
|
102
|
+
|
|
103
|
+
// If we go over maxSize, drop the whole stream, so the buffer
|
|
104
|
+
// resolves empty. MaxSize should be large, so this is rare,
|
|
105
|
+
// and only happens as an alternative to crashing the process.
|
|
106
|
+
if (currentSize > maxSize) {
|
|
107
|
+
// Announce truncation, so that other mechanisms (asStream) can
|
|
108
|
+
// capture this data if they're interested in it.
|
|
109
|
+
bufferPromise.events.emit('truncate', chunks);
|
|
110
|
+
|
|
111
|
+
// Drop all the data so far & stop reading
|
|
112
|
+
bufferPromise.currentChunks = chunks = [];
|
|
113
|
+
input.removeListener('data', onData);
|
|
114
|
+
|
|
115
|
+
// We then resolve immediately - the buffer is done, even if the body
|
|
116
|
+
// might still be streaming in we're not listening to it. This means
|
|
117
|
+
// that requests can 'complete' for event/callback purposes while
|
|
118
|
+
// they're actually still streaming, but only in this scenario where
|
|
119
|
+
// the data is too large to really be used by the events/callbacks.
|
|
120
|
+
|
|
121
|
+
// If we don't resolve, then cases which intentionally don't consume
|
|
122
|
+
// the raw stream but do consume the buffer (beforeRequest) would
|
|
123
|
+
// deadlock: beforeRequest must complete to begin streaming the
|
|
124
|
+
// full body to the target clients.
|
|
125
|
+
|
|
126
|
+
resolve(Buffer.from([]));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
input.on('data', onData);
|
|
130
|
+
|
|
131
|
+
input.once('end', () => {
|
|
132
|
+
resolve(Buffer.concat(chunks));
|
|
133
|
+
});
|
|
134
|
+
input.once('aborted', failWithAbortError);
|
|
135
|
+
input.on('error', (e) => {
|
|
136
|
+
bufferPromise.failedWith = bufferPromise.failedWith || e;
|
|
137
|
+
reject(e);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
bufferPromise.currentChunks = chunks;
|
|
142
|
+
bufferPromise.events = new EventEmitter();
|
|
143
|
+
return bufferPromise;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export function splitBuffer(input: Buffer, splitter: string, maxParts = Infinity) {
|
|
147
|
+
const parts: Buffer[] = [];
|
|
148
|
+
|
|
149
|
+
let remainingBuffer = input;
|
|
150
|
+
while (remainingBuffer.length) {
|
|
151
|
+
let endOfPart = remainingBuffer.indexOf(splitter);
|
|
152
|
+
if (endOfPart === -1) endOfPart = remainingBuffer.length;
|
|
153
|
+
|
|
154
|
+
parts.push(remainingBuffer.slice(0, endOfPart));
|
|
155
|
+
remainingBuffer = remainingBuffer.slice(endOfPart + splitter.length);
|
|
156
|
+
|
|
157
|
+
if (parts.length === maxParts - 1) {
|
|
158
|
+
parts.push(remainingBuffer);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parts;
|
|
164
|
+
}
|
package/src/util/dns.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as dns from 'dns';
|
|
2
|
+
|
|
3
|
+
// We exclude the __promisify__ type hack from the official DNS type, to give us a type
|
|
4
|
+
// that otherwise enforces correctness. We still have to cast past the missing 'field'
|
|
5
|
+
// at the end of the day, but this allows us to avoid that until the last minute without
|
|
6
|
+
// sacrificing any strictness the rest of the time.
|
|
7
|
+
export type DnsLookupFunction = Omit<typeof dns.lookup, '__promisify__'>;
|
|
8
|
+
|
|
9
|
+
// A drop-in alternative to dns.lookup, but where results are briefly cached to avoid completely
|
|
10
|
+
// unnecessary lookups, while remaining fairly reactive to actual host file changes etc.
|
|
11
|
+
export class CachedDns {
|
|
12
|
+
|
|
13
|
+
private cache = new Map<string, [address: string | dns.LookupAddress[], family: number]>();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private cacheDurationMs: number
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
private cacheKey(hostname: Parameters<typeof dns.lookup>[0], options?: dns.LookupAllOptions | dns.LookupOneOptions) {
|
|
20
|
+
return `${hostname}-${options?.all}-${options?.family}-${options?.hints}-${options?.verbatim}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
lookup: DnsLookupFunction = (...args: Parameters<typeof dns.lookup>) => {
|
|
24
|
+
const [hostname, options] = args.slice(0, -1) as [string, dns.LookupOptions | undefined];
|
|
25
|
+
const cb = args[args.length - 1] as (err: NodeJS.ErrnoException | null, address: string | dns.LookupAddress[], family: number) => void;
|
|
26
|
+
|
|
27
|
+
const key = this.cacheKey(hostname, options);
|
|
28
|
+
const cachedResult = this.cache.get(key);
|
|
29
|
+
if (cachedResult) {
|
|
30
|
+
setImmediate(() => cb(null, ...cachedResult));
|
|
31
|
+
} else {
|
|
32
|
+
dns.lookup(hostname, options ?? {}, (err, ...cbArgs) => {
|
|
33
|
+
if (!err) {
|
|
34
|
+
this.cache.set(key, cbArgs);
|
|
35
|
+
// Always refresh Xms after initially inserted - no LRU or similar
|
|
36
|
+
setTimeout(() => this.cache.delete(key), this.cacheDurationMs).unref();
|
|
37
|
+
}
|
|
38
|
+
return cb(err, ...cbArgs);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function dnsLookup(lookupFn: typeof dns.lookup | DnsLookupFunction, hostname: string) {
|
|
46
|
+
return new Promise<string>((resolve, reject) => {
|
|
47
|
+
(lookupFn as typeof dns.lookup)(hostname!, (err, address) => {
|
|
48
|
+
if (err) reject(err);
|
|
49
|
+
else resolve(address);
|
|
50
|
+
});
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ErrorLike = Partial<Error> & {
|
|
2
|
+
// Various properties we might want to look for on errors:
|
|
3
|
+
code?: string;
|
|
4
|
+
cmd?: string;
|
|
5
|
+
signal?: string;
|
|
6
|
+
statusCode?: number;
|
|
7
|
+
statusMessage?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Useful to easily cast and then examine errors that are otherwise 'unknown':
|
|
11
|
+
export function isErrorLike(error: any): error is ErrorLike {
|
|
12
|
+
return typeof error === 'object' && (
|
|
13
|
+
error instanceof Error ||
|
|
14
|
+
error.message ||
|
|
15
|
+
error.code ||
|
|
16
|
+
error.stack
|
|
17
|
+
)
|
|
18
|
+
}
|