@zimic/interceptor 1.5.0 → 1.5.1
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.
- package/LICENSE.md +3 -10
- package/README.md +6 -18
- package/dist/{chunk-3W3A4TJ5.js → chunk-3WCEMV5U.js} +39 -3
- package/dist/chunk-3WCEMV5U.js.map +1 -0
- package/dist/{chunk-F3VMDYHM.mjs → chunk-YQNDAJTN.mjs} +39 -3
- package/dist/chunk-YQNDAJTN.mjs.map +1 -0
- package/dist/cli.js +17 -27
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +3 -13
- package/dist/cli.mjs.map +1 -1
- package/dist/http.js.map +1 -1
- package/dist/http.mjs.map +1 -1
- package/dist/server.js +6 -6
- package/dist/server.mjs +1 -1
- package/package.json +7 -7
- package/src/cli/cli.ts +3 -3
- package/src/cli/server/start.ts +1 -12
- package/src/http/interceptor/HttpInterceptorImplementation.ts +1 -2
- package/src/http/interceptorWorker/HttpInterceptorWorker.ts +1 -2
- package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +1 -2
- package/src/http/interceptorWorker/RemoteHttpInterceptorWorker.ts +1 -2
- package/src/server/InterceptorServer.ts +51 -1
- package/src/utils/http.ts +6 -0
- package/src/utils/webSocket/types.ts +1 -2
- package/dist/chunk-3W3A4TJ5.js.map +0 -1
- package/dist/chunk-F3VMDYHM.mjs.map +0 -1
package/dist/server.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk3WCEMV5U_js = require('./chunk-3WCEMV5U.js');
|
|
4
4
|
require('./chunk-DGUM43GV.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "DEFAULT_ACCESS_CONTROL_HEADERS", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunk3WCEMV5U_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "DEFAULT_PREFLIGHT_STATUS_CODE", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunk3WCEMV5U_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
|
|
15
15
|
});
|
|
16
16
|
Object.defineProperty(exports, "NotRunningInterceptorServerError", {
|
|
17
17
|
enumerable: true,
|
|
18
|
-
get: function () { return
|
|
18
|
+
get: function () { return chunk3WCEMV5U_js.NotRunningInterceptorServerError_default; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "RunningInterceptorServerError", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunk3WCEMV5U_js.RunningInterceptorServerError_default; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "createInterceptorServer", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunk3WCEMV5U_js.createInterceptorServer; }
|
|
27
27
|
});
|
|
28
28
|
//# sourceMappingURL=server.js.map
|
|
29
29
|
//# sourceMappingURL=server.js.map
|
package/dist/server.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-
|
|
1
|
+
export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-YQNDAJTN.mjs';
|
|
2
2
|
import './chunk-BJTO5JO5.mjs';
|
|
3
3
|
//# sourceMappingURL=server.mjs.map
|
|
4
4
|
//# sourceMappingURL=server.mjs.map
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"api",
|
|
15
15
|
"static"
|
|
16
16
|
],
|
|
17
|
-
"version": "1.5.
|
|
17
|
+
"version": "1.5.1",
|
|
18
18
|
"homepage": "https://zimic.dev/docs/interceptor",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -100,12 +100,12 @@
|
|
|
100
100
|
"isomorphic-ws": "5.0.0",
|
|
101
101
|
"msw": "2.15.0",
|
|
102
102
|
"picocolors": "^1.1.1",
|
|
103
|
-
"ws": "8.21.
|
|
104
|
-
"yargs": "18.
|
|
103
|
+
"ws": "8.21.1",
|
|
104
|
+
"yargs": "18.1.0",
|
|
105
105
|
"zod": "^4.4.3"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@types/node": "^26.1.
|
|
108
|
+
"@types/node": "^26.1.2",
|
|
109
109
|
"@types/ws": "^8.18.1",
|
|
110
110
|
"@types/yargs": "^17.0.35",
|
|
111
111
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
"@zimic/lint-staged-config": "workspace:*",
|
|
117
117
|
"@zimic/tsconfig": "workspace:*",
|
|
118
118
|
"@zimic/utils": "workspace:*",
|
|
119
|
-
"concurrently": "^10.0.
|
|
119
|
+
"concurrently": "^10.0.4",
|
|
120
120
|
"dotenv-cli": "^11.0.0",
|
|
121
|
-
"eslint": "^9.39.
|
|
122
|
-
"playwright": "^1.
|
|
121
|
+
"eslint": "^9.39.5",
|
|
122
|
+
"playwright": "^1.62.0",
|
|
123
123
|
"tsup": "^8.5.1",
|
|
124
124
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
125
125
|
"vitest": "^4.1.10"
|
package/src/cli/cli.ts
CHANGED
|
@@ -79,9 +79,9 @@ async function runCLI() {
|
|
|
79
79
|
type: 'string',
|
|
80
80
|
description:
|
|
81
81
|
'The directory where the authorized interceptor authentication tokens are saved. If provided, only ' +
|
|
82
|
-
'remote interceptors bearing a valid token will be accepted.
|
|
83
|
-
'
|
|
84
|
-
'
|
|
82
|
+
'remote interceptors bearing a valid token will be accepted. While authentication is optional for ' +
|
|
83
|
+
'private development servers, it is strongly recommended when the server is not bound to localhost, ' +
|
|
84
|
+
'127.0.0.1, or ::1, or when it is shared over a network.',
|
|
85
85
|
alias: 't',
|
|
86
86
|
}),
|
|
87
87
|
async (cliArguments) => {
|
package/src/cli/server/start.ts
CHANGED
|
@@ -42,6 +42,7 @@ async function startInterceptorServer({
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const exitCode = exitEvent ? PROCESS_EXIT_CODE_BY_EXIT_EVENT[exitEvent] : undefined;
|
|
45
|
+
|
|
45
46
|
if (exitCode !== undefined) {
|
|
46
47
|
process.exit(exitCode);
|
|
47
48
|
}
|
|
@@ -64,18 +65,6 @@ async function startInterceptorServer({
|
|
|
64
65
|
`${ephemeral ? 'Ephemeral s' : 'S'}erver is running on ${color.yellow(`${server.hostname}:${server.port}`)}`,
|
|
65
66
|
);
|
|
66
67
|
|
|
67
|
-
const isDangerouslyUnprotected = !tokensDirectory && process.env.NODE_ENV === 'production';
|
|
68
|
-
|
|
69
|
-
if (isDangerouslyUnprotected) {
|
|
70
|
-
logger.warn(
|
|
71
|
-
[
|
|
72
|
-
`Attention: this interceptor server is ${color.bold(color.red('unprotected'))}. Do not expose it publicly without authentication.`,
|
|
73
|
-
'',
|
|
74
|
-
'Learn more: https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication',
|
|
75
|
-
].join('\n'),
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
68
|
if (onReady) {
|
|
80
69
|
try {
|
|
81
70
|
await runCommand(onReady.command, onReady.arguments);
|
|
@@ -90,8 +90,7 @@ class HttpInterceptorImplementation<
|
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
this.onUnhandledRequest = options.onUnhandledRequest satisfies
|
|
93
|
-
|
|
|
94
|
-
| undefined as this['onUnhandledRequest'];
|
|
93
|
+
UnhandledRequestStrategy | undefined as this['onUnhandledRequest'];
|
|
95
94
|
|
|
96
95
|
this.Handler = options.Handler;
|
|
97
96
|
}
|
|
@@ -249,8 +249,7 @@ abstract class HttpInterceptorWorker {
|
|
|
249
249
|
createResponseFromDeclaration(
|
|
250
250
|
request: HttpRequest,
|
|
251
251
|
declaration:
|
|
252
|
-
|
|
253
|
-
| { action: UnhandledRequestStrategy.Action },
|
|
252
|
+
{ status: number; headers?: HttpHeadersInit; body?: HttpBody } | { action: UnhandledRequestStrategy.Action },
|
|
254
253
|
): PossiblePromise<HttpResponse | null> {
|
|
255
254
|
if ('action' in declaration) {
|
|
256
255
|
if (declaration.action === 'bypass') {
|
|
@@ -252,8 +252,7 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
252
252
|
async createResponseFromDeclaration(
|
|
253
253
|
request: HttpRequest,
|
|
254
254
|
declaration:
|
|
255
|
-
|
|
256
|
-
| { action: UnhandledRequestStrategy.Action },
|
|
255
|
+
{ status: number; headers?: HttpHeadersInit; body?: HttpBody } | { action: UnhandledRequestStrategy.Action },
|
|
257
256
|
) {
|
|
258
257
|
const requestClone = request.clone();
|
|
259
258
|
const response = await super.createResponseFromDeclaration(request, declaration);
|
|
@@ -171,8 +171,7 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
171
171
|
async createResponseFromDeclaration(
|
|
172
172
|
request: HttpRequest,
|
|
173
173
|
declaration:
|
|
174
|
-
|
|
175
|
-
| { action: UnhandledRequestStrategy.Action },
|
|
174
|
+
{ status: number; headers?: HttpHeadersInit; body?: HttpBody } | { action: UnhandledRequestStrategy.Action },
|
|
176
175
|
) {
|
|
177
176
|
const response = await super.createResponseFromDeclaration(request, declaration);
|
|
178
177
|
|
|
@@ -4,10 +4,13 @@ import { startHttpServer, stopHttpServer, getHttpServerPort } from '@zimic/utils
|
|
|
4
4
|
import { createRegexFromPath, excludeNonPathParams } from '@zimic/utils/url';
|
|
5
5
|
import { createServer, Server as HttpServer, IncomingMessage, ServerResponse } from 'http';
|
|
6
6
|
import type { WebSocket as Socket } from 'isomorphic-ws';
|
|
7
|
+
import color from 'picocolors';
|
|
7
8
|
|
|
8
9
|
import HttpInterceptorWorker from '@/http/interceptorWorker/HttpInterceptorWorker';
|
|
9
10
|
import { removeArrayIndex } from '@/utils/arrays';
|
|
10
11
|
import { deserializeResponse, SerializedHttpRequest, serializeRequest } from '@/utils/fetch';
|
|
12
|
+
import { isLoopbackHostname } from '@/utils/http';
|
|
13
|
+
import { logger } from '@/utils/logging';
|
|
11
14
|
import { WebSocketMessageAbortError } from '@/utils/webSocket';
|
|
12
15
|
import { WebSocketEventMessage } from '@/utils/webSocket/types';
|
|
13
16
|
import WebSocketServer, { WebSocketServerAuthenticate } from '@/utils/webSocket/WebSocketServer';
|
|
@@ -124,11 +127,58 @@ class InterceptorServer implements PublicInterceptorServer {
|
|
|
124
127
|
});
|
|
125
128
|
|
|
126
129
|
this.startWebSocketServer();
|
|
130
|
+
|
|
131
|
+
const isDangerouslyUnprotected =
|
|
132
|
+
!this.tokensDirectory && (process.env.NODE_ENV === 'production' || !isLoopbackHostname(this.hostname));
|
|
133
|
+
|
|
134
|
+
if (isDangerouslyUnprotected) {
|
|
135
|
+
logger.warn(
|
|
136
|
+
[
|
|
137
|
+
`Attention: this interceptor server is ${color.bold(
|
|
138
|
+
color.red('unprotected'),
|
|
139
|
+
)}. Do not expose it publicly without authentication.`,
|
|
140
|
+
'',
|
|
141
|
+
'For your safety, this server will reject remote browser interceptors until authentication is configured.',
|
|
142
|
+
'',
|
|
143
|
+
'In @zimic/interceptor v2, interceptor servers running on non-loopback hostnames will require ' +
|
|
144
|
+
'authentication and refuse to start without a tokens directory.',
|
|
145
|
+
'',
|
|
146
|
+
'Learn more: https://zimic.dev/docs/interceptor/guides/http/remote-interceptors#interceptor-server-authentication',
|
|
147
|
+
].join('\n'),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
127
150
|
}
|
|
128
151
|
|
|
129
152
|
private authenticateWebSocketConnection: WebSocketServerAuthenticate = async (_socket, request) => {
|
|
130
153
|
if (!this.tokensDirectory) {
|
|
131
|
-
|
|
154
|
+
// Requests without an origin header are allowed when the interceptor server is not configured to require token
|
|
155
|
+
// authentication. They are typically made by non-browser clients.
|
|
156
|
+
if (request.headers.origin === undefined) {
|
|
157
|
+
return { isValid: true };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const originURL = new URL(request.headers.origin);
|
|
162
|
+
const originHostname = originURL.hostname === '[::1]' ? '::1' : originURL.hostname;
|
|
163
|
+
|
|
164
|
+
const originMatchesServerLoopback =
|
|
165
|
+
originURL.origin === request.headers.origin &&
|
|
166
|
+
isLoopbackHostname(this.hostname) &&
|
|
167
|
+
isLoopbackHostname(originHostname);
|
|
168
|
+
|
|
169
|
+
if (originMatchesServerLoopback) {
|
|
170
|
+
return { isValid: true };
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
isValid: false,
|
|
178
|
+
message:
|
|
179
|
+
'Unauthenticated browser connections are only allowed from loopback origins. ' +
|
|
180
|
+
'Configure token authentication.',
|
|
181
|
+
};
|
|
132
182
|
}
|
|
133
183
|
|
|
134
184
|
const tokenValue = this.getWebSocketRequestTokenValue(request);
|
package/src/utils/http.ts
CHANGED
|
@@ -14,3 +14,9 @@ export const HTTP_METHODS_WITH_RESPONSE_BODY = new Set<HttpMethod>([
|
|
|
14
14
|
export function methodCanHaveResponseBody(method: HttpMethod) {
|
|
15
15
|
return HTTP_METHODS_WITH_RESPONSE_BODY.has(method);
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
export const LOOPBACK_HOSTNAMES = Object.freeze(['localhost', '127.0.0.1', '::1']);
|
|
19
|
+
|
|
20
|
+
export function isLoopbackHostname(hostname: string) {
|
|
21
|
+
return LOOPBACK_HOSTNAMES.includes(hostname.toLowerCase());
|
|
22
|
+
}
|
|
@@ -58,5 +58,4 @@ export type WebSocketReplyMessageListener<Schema extends WebSocketSchema, Channe
|
|
|
58
58
|
) => PossiblePromise<void>;
|
|
59
59
|
|
|
60
60
|
export type WebSocketMessageListener<Schema extends WebSocketSchema, Channel extends WebSocketChannel<Schema>> =
|
|
61
|
-
|
|
|
62
|
-
| WebSocketReplyMessageListener<Schema, Channel>;
|
|
61
|
+
WebSocketEventMessageListener<Schema, Channel> | WebSocketReplyMessageListener<Schema, Channel>;
|