@zimic/interceptor 1.2.7-canary.2 → 1.3.0-canary.0
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/dist/{chunk-6GEP6R3L.mjs → chunk-5CI37HTA.mjs} +75 -16
- package/dist/chunk-5CI37HTA.mjs.map +1 -0
- package/dist/{chunk-PB4TJVK3.js → chunk-KDM6Y6GO.js} +75 -16
- package/dist/chunk-KDM6Y6GO.js.map +1 -0
- package/dist/cli.js +17 -17
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/http.d.ts +18 -6
- package/dist/http.js +96 -15
- package/dist/http.js.map +1 -1
- package/dist/http.mjs +97 -16
- package/dist/http.mjs.map +1 -1
- package/dist/server.js +6 -6
- package/dist/server.mjs +1 -1
- package/package.json +2 -2
- package/src/http/interceptor/HttpInterceptorClient.ts +7 -7
- package/src/http/interceptor/types/options.ts +11 -5
- package/src/http/interceptorWorker/HttpInterceptorWorker.ts +72 -14
- package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +35 -8
- package/src/http/interceptorWorker/RemoteHttpInterceptorWorker.ts +33 -12
- package/src/http/interceptorWorker/types/http.ts +6 -1
- package/src/http/interceptorWorker/types/msw.ts +0 -9
- package/src/http/requestHandler/types/requests.ts +16 -2
- package/src/server/InterceptorServer.ts +11 -2
- package/src/server/errors/UnsupportedResponseBypassError.ts +11 -0
- package/src/utils/fetch.ts +25 -6
- package/dist/chunk-6GEP6R3L.mjs.map +0 -1
- package/dist/chunk-PB4TJVK3.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkKDM6Y6GO_js = require('./chunk-KDM6Y6GO.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 chunkKDM6Y6GO_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 chunkKDM6Y6GO_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 chunkKDM6Y6GO_js.NotRunningInterceptorServerError_default; }
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(exports, "RunningInterceptorServerError", {
|
|
21
21
|
enumerable: true,
|
|
22
|
-
get: function () { return
|
|
22
|
+
get: function () { return chunkKDM6Y6GO_js.RunningInterceptorServerError_default; }
|
|
23
23
|
});
|
|
24
24
|
Object.defineProperty(exports, "createInterceptorServer", {
|
|
25
25
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
26
|
+
get: function () { return chunkKDM6Y6GO_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-5CI37HTA.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.
|
|
17
|
+
"version": "1.3.0-canary.0",
|
|
18
18
|
"homepage": "https://zimic.dev/docs/interceptor",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"style:check": "pnpm style --check",
|
|
91
91
|
"style:format": "pnpm style --write",
|
|
92
92
|
"test": "dotenv -v NODE_ENV=test -v FORCE_COLOR=1 -- vitest",
|
|
93
|
-
"test:turbo": "dotenv -v CI=true -- pnpm run test
|
|
93
|
+
"test:turbo": "dotenv -v CI=true -- pnpm run test --coverage --reporter tree",
|
|
94
94
|
"types:check": "tsc --noEmit",
|
|
95
95
|
"postinstall": "node --enable-source-maps -e \"try{require('./dist/scripts/postinstall')}catch(error){console.error(error)}\""
|
|
96
96
|
},
|
|
@@ -267,7 +267,7 @@ class HttpInterceptorClient<
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
|
|
270
|
+
async handleInterceptedRequest<
|
|
271
271
|
Method extends HttpSchemaMethod<Schema>,
|
|
272
272
|
Path extends HttpSchemaPath<Schema, Method>,
|
|
273
273
|
Context extends HttpInterceptorRequestContext<Schema, Method, Path>,
|
|
@@ -289,15 +289,15 @@ class HttpInterceptorClient<
|
|
|
289
289
|
return null;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
const response =
|
|
292
|
+
const response = await this.workerOrThrow.createResponseFromDeclaration(request, responseDeclaration);
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
const shouldSaveInterceptedRequest =
|
|
295
|
+
this.requestSaving.enabled && response && !HttpInterceptorWorker.isRejectedResponse(response);
|
|
296
|
+
|
|
297
|
+
if (shouldSaveInterceptedRequest) {
|
|
295
298
|
const responseClone = response.clone();
|
|
296
299
|
|
|
297
|
-
const parsedResponse = await HttpInterceptorWorker.parseRawResponse<
|
|
298
|
-
Default<Schema[Path][Method]>,
|
|
299
|
-
typeof responseDeclaration.status
|
|
300
|
-
>(responseClone);
|
|
300
|
+
const parsedResponse = await HttpInterceptorWorker.parseRawResponse<Default<Schema[Path][Method]>>(responseClone);
|
|
301
301
|
|
|
302
302
|
matchedHandler.saveInterceptedRequest(parsedRequest, parsedResponse);
|
|
303
303
|
}
|
|
@@ -12,7 +12,13 @@ export type HttpInterceptorPlatform = 'node' | 'browser';
|
|
|
12
12
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
13
13
|
export namespace UnhandledRequestStrategy {
|
|
14
14
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
15
|
-
export type
|
|
15
|
+
export type LocalAction = 'bypass' | 'reject';
|
|
16
|
+
|
|
17
|
+
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
18
|
+
export type RemoteAction = 'reject';
|
|
19
|
+
|
|
20
|
+
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
21
|
+
export type Action = LocalAction | RemoteAction;
|
|
16
22
|
|
|
17
23
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
18
24
|
export interface Declaration<DeclarationAction extends Action = Action> {
|
|
@@ -29,16 +35,16 @@ export namespace UnhandledRequestStrategy {
|
|
|
29
35
|
) => PossiblePromise<Declaration<DeclarationAction>>;
|
|
30
36
|
|
|
31
37
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
32
|
-
export type LocalDeclaration = Declaration
|
|
38
|
+
export type LocalDeclaration = Declaration<LocalAction>;
|
|
33
39
|
|
|
34
40
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
35
|
-
export type LocalDeclarationFactory = DeclarationFactory
|
|
41
|
+
export type LocalDeclarationFactory = DeclarationFactory<LocalAction>;
|
|
36
42
|
|
|
37
43
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
38
|
-
export type RemoteDeclaration = Declaration<
|
|
44
|
+
export type RemoteDeclaration = Declaration<RemoteAction>;
|
|
39
45
|
|
|
40
46
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
41
|
-
export type RemoteDeclarationFactory = DeclarationFactory<
|
|
47
|
+
export type RemoteDeclarationFactory = DeclarationFactory<RemoteAction>;
|
|
42
48
|
|
|
43
49
|
/** @see {@link https://zimic.dev/docs/interceptor/guides/http/unhandled-requests Unhandled requests} */
|
|
44
50
|
export type Local = LocalDeclaration | LocalDeclarationFactory;
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
InferPathParams,
|
|
11
11
|
parseHttpBody,
|
|
12
12
|
HttpSearchParams,
|
|
13
|
+
HttpRequest,
|
|
13
14
|
} from '@zimic/http';
|
|
14
15
|
import isDefined from '@zimic/utils/data/isDefined';
|
|
15
16
|
import { Default, PossiblePromise } from '@zimic/utils/types';
|
|
@@ -33,9 +34,11 @@ import {
|
|
|
33
34
|
HttpInterceptorResponse,
|
|
34
35
|
} from '../requestHandler/types/requests';
|
|
35
36
|
import { DEFAULT_UNHANDLED_REQUEST_STRATEGY } from './constants';
|
|
36
|
-
import {
|
|
37
|
+
import { HttpResponseFactory } from './types/http';
|
|
37
38
|
import { HttpInterceptorWorkerType } from './types/options';
|
|
38
39
|
|
|
40
|
+
const RESPONSE_ACTION_SYMBOL = Symbol.for('HttpResponse.action');
|
|
41
|
+
|
|
39
42
|
abstract class HttpInterceptorWorker {
|
|
40
43
|
abstract get type(): HttpInterceptorWorkerType;
|
|
41
44
|
|
|
@@ -100,7 +103,7 @@ abstract class HttpInterceptorWorker {
|
|
|
100
103
|
interceptor: HttpInterceptorClient<Schema>,
|
|
101
104
|
method: HttpMethod,
|
|
102
105
|
path: string,
|
|
103
|
-
createResponse:
|
|
106
|
+
createResponse: HttpResponseFactory,
|
|
104
107
|
): PossiblePromise<void>;
|
|
105
108
|
|
|
106
109
|
protected async logUnhandledRequestIfNecessary(
|
|
@@ -189,17 +192,71 @@ abstract class HttpInterceptorWorker {
|
|
|
189
192
|
|
|
190
193
|
abstract get interceptorsWithHandlers(): AnyHttpInterceptorClient[];
|
|
191
194
|
|
|
192
|
-
static
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
static setResponseAction(response: Response, action: UnhandledRequestStrategy.Action) {
|
|
196
|
+
Object.defineProperty(response, RESPONSE_ACTION_SYMBOL, {
|
|
197
|
+
value: action,
|
|
198
|
+
enumerable: false,
|
|
199
|
+
configurable: false,
|
|
200
|
+
writable: false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static getResponseAction(response: Response): UnhandledRequestStrategy.Action | undefined {
|
|
205
|
+
if (!(RESPONSE_ACTION_SYMBOL in response)) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const action = response[RESPONSE_ACTION_SYMBOL];
|
|
210
|
+
|
|
211
|
+
/* istanbul ignore if -- @preserve
|
|
212
|
+
* This is just a type guard to ensure the value is valid. In practice, this condition should never be true. */
|
|
213
|
+
if (action !== 'bypass' && action !== 'reject') {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return action;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private createBypassedResponse() {
|
|
221
|
+
const response = Response.redirect('about:blank', 302) as HttpResponse;
|
|
222
|
+
HttpInterceptorWorker.setResponseAction(response, 'bypass');
|
|
223
|
+
return response;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static isBypassedResponse(response: Response) {
|
|
227
|
+
return this.getResponseAction(response) === 'bypass';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private createRejectedResponse() {
|
|
231
|
+
const response = Response.error() as HttpResponse;
|
|
232
|
+
HttpInterceptorWorker.setResponseAction(response, 'reject');
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static isRejectedResponse(response: Response) {
|
|
237
|
+
return this.getResponseAction(response) === 'reject';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
createResponseFromDeclaration(
|
|
241
|
+
request: HttpRequest,
|
|
242
|
+
declaration:
|
|
243
|
+
| { status: number; headers?: HttpHeadersInit; body?: HttpBody }
|
|
244
|
+
| { action: UnhandledRequestStrategy.Action },
|
|
245
|
+
): PossiblePromise<HttpResponse | null> {
|
|
246
|
+
if ('action' in declaration) {
|
|
247
|
+
if (declaration.action === 'bypass') {
|
|
248
|
+
return this.createBypassedResponse();
|
|
249
|
+
} else {
|
|
250
|
+
return this.createRejectedResponse();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
196
254
|
const headers = new HttpHeaders(declaration.headers);
|
|
197
|
-
const status = declaration.status;
|
|
198
255
|
|
|
199
|
-
const canHaveBody = methodCanHaveResponseBody(request.method as HttpMethod) && status !== 204;
|
|
256
|
+
const canHaveBody = methodCanHaveResponseBody(request.method as HttpMethod) && declaration.status !== 204;
|
|
200
257
|
|
|
201
258
|
if (!canHaveBody) {
|
|
202
|
-
return new Response(null, { headers, status }) as HttpResponse;
|
|
259
|
+
return new Response(null, { headers, status: declaration.status }) as HttpResponse;
|
|
203
260
|
}
|
|
204
261
|
|
|
205
262
|
if (
|
|
@@ -212,10 +269,10 @@ abstract class HttpInterceptorWorker {
|
|
|
212
269
|
declaration.body instanceof ArrayBuffer ||
|
|
213
270
|
declaration.body instanceof ReadableStream
|
|
214
271
|
) {
|
|
215
|
-
return new Response(declaration.body ?? null, { headers, status }) as HttpResponse;
|
|
272
|
+
return new Response(declaration.body ?? null, { headers, status: declaration.status }) as HttpResponse;
|
|
216
273
|
}
|
|
217
274
|
|
|
218
|
-
return Response.json(declaration.body, { headers, status }) as HttpResponse;
|
|
275
|
+
return Response.json(declaration.body, { headers, status: declaration.status }) as HttpResponse;
|
|
219
276
|
}
|
|
220
277
|
|
|
221
278
|
static async parseRawUnhandledRequest(request: Request) {
|
|
@@ -305,9 +362,10 @@ abstract class HttpInterceptorWorker {
|
|
|
305
362
|
return HTTP_INTERCEPTOR_REQUEST_HIDDEN_PROPERTIES.has(property as never);
|
|
306
363
|
}
|
|
307
364
|
|
|
308
|
-
static async parseRawResponse<
|
|
309
|
-
|
|
310
|
-
|
|
365
|
+
static async parseRawResponse<
|
|
366
|
+
MethodSchema extends HttpMethodSchema,
|
|
367
|
+
StatusCode extends HttpStatusCode = HttpStatusCode,
|
|
368
|
+
>(originalRawResponse: Response): Promise<HttpInterceptorResponse<MethodSchema, StatusCode>> {
|
|
311
369
|
const rawResponse = originalRawResponse.clone();
|
|
312
370
|
const rawResponseClone = rawResponse.clone();
|
|
313
371
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { HttpRequest, HttpResponse, HttpMethod, HttpSchema } from '@zimic/http';
|
|
1
|
+
import { HttpRequest, HttpResponse, HttpMethod, HttpSchema, HttpHeadersInit, HttpBody } from '@zimic/http';
|
|
2
2
|
import createRegexFromPath from '@zimic/utils/url/createRegexFromPath';
|
|
3
3
|
import excludeNonPathParams from '@zimic/utils/url/excludeNonPathParams';
|
|
4
4
|
import validatePathParams from '@zimic/utils/url/validatePathParams';
|
|
5
|
-
import { SharedOptions as MSWWorkerSharedOptions, http, passthrough } from 'msw';
|
|
5
|
+
import { SharedOptions as MSWWorkerSharedOptions, bypass, http, passthrough } from 'msw';
|
|
6
6
|
import * as mswBrowser from 'msw/browser';
|
|
7
7
|
import * as mswNode from 'msw/node';
|
|
8
8
|
|
|
@@ -12,10 +12,11 @@ import { isClientSide, isServerSide } from '@/utils/environment';
|
|
|
12
12
|
import NotRunningHttpInterceptorError from '../interceptor/errors/NotRunningHttpInterceptorError';
|
|
13
13
|
import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError';
|
|
14
14
|
import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient';
|
|
15
|
+
import { UnhandledRequestStrategy } from '../interceptor/types/options';
|
|
15
16
|
import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError';
|
|
16
17
|
import HttpInterceptorWorker from './HttpInterceptorWorker';
|
|
17
|
-
import { HttpResponseFactoryContext } from './types/http';
|
|
18
|
-
import { BrowserMSWWorker,
|
|
18
|
+
import { HttpResponseFactory, HttpResponseFactoryContext } from './types/http';
|
|
19
|
+
import { BrowserMSWWorker, MSWWorker, NodeMSWWorker } from './types/msw';
|
|
19
20
|
import { LocalHttpInterceptorWorkerOptions } from './types/options';
|
|
20
21
|
|
|
21
22
|
interface HttpHandler {
|
|
@@ -163,7 +164,7 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
163
164
|
interceptor: HttpInterceptorClient<Schema>,
|
|
164
165
|
method: HttpMethod,
|
|
165
166
|
path: string,
|
|
166
|
-
createResponse:
|
|
167
|
+
createResponse: HttpResponseFactory,
|
|
167
168
|
) {
|
|
168
169
|
if (!this.isRunning) {
|
|
169
170
|
throw new NotRunningHttpInterceptorError();
|
|
@@ -185,7 +186,7 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
185
186
|
let response: HttpResponse | null = null;
|
|
186
187
|
|
|
187
188
|
try {
|
|
188
|
-
response = await createResponse({
|
|
189
|
+
response = await createResponse({ request });
|
|
189
190
|
} catch (error) {
|
|
190
191
|
console.error(error);
|
|
191
192
|
}
|
|
@@ -209,6 +210,32 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
209
210
|
methodHandlers.push(handler);
|
|
210
211
|
}
|
|
211
212
|
|
|
213
|
+
async createResponseFromDeclaration(
|
|
214
|
+
request: HttpRequest,
|
|
215
|
+
declaration:
|
|
216
|
+
| { status: number; headers?: HttpHeadersInit; body?: HttpBody }
|
|
217
|
+
| { action: UnhandledRequestStrategy.Action },
|
|
218
|
+
) {
|
|
219
|
+
const requestClone = request.clone();
|
|
220
|
+
const response = await super.createResponseFromDeclaration(request, declaration);
|
|
221
|
+
|
|
222
|
+
if (response && HttpInterceptorWorker.isBypassedResponse(response)) {
|
|
223
|
+
try {
|
|
224
|
+
const response = (await fetch(bypass(requestClone))) as HttpResponse;
|
|
225
|
+
return response;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error(error);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (response && HttpInterceptorWorker.isRejectedResponse(response)) {
|
|
233
|
+
return response;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return response;
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
private async createResponseForRequest(request: HttpRequest) {
|
|
213
240
|
const methodHandlers = this.httpHandlersByMethod[request.method as HttpMethod];
|
|
214
241
|
|
|
@@ -244,9 +271,9 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
244
271
|
await super.logUnhandledRequestIfNecessary(requestClone, strategy);
|
|
245
272
|
|
|
246
273
|
if (strategy?.action === 'reject') {
|
|
247
|
-
return Response.error();
|
|
274
|
+
return Response.error() as HttpResponse;
|
|
248
275
|
} else {
|
|
249
|
-
return passthrough();
|
|
276
|
+
return passthrough() as HttpResponse;
|
|
250
277
|
}
|
|
251
278
|
}
|
|
252
279
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { HttpMethod, HttpSchema } from '@zimic/http';
|
|
1
|
+
import { HttpBody, HttpHeadersInit, HttpMethod, HttpRequest, HttpSchema } from '@zimic/http';
|
|
2
|
+
import { PossiblePromise } from '@zimic/utils/types';
|
|
2
3
|
import validatePathParams from '@zimic/utils/url/validatePathParams';
|
|
3
4
|
|
|
5
|
+
import UnsupportedResponseBypassError from '@/server/errors/UnsupportedResponseBypassError';
|
|
4
6
|
import { HttpHandlerCommit, InterceptorServerWebSocketSchema } from '@/server/types/schema';
|
|
5
7
|
import { importCrypto } from '@/utils/crypto';
|
|
6
8
|
import { isClientSide, isServerSide } from '@/utils/environment';
|
|
7
9
|
import { deserializeRequest, serializeResponse } from '@/utils/fetch';
|
|
10
|
+
import { methodCanHaveResponseBody } from '@/utils/http';
|
|
8
11
|
import { WebSocketMessageAbortError } from '@/utils/webSocket';
|
|
9
12
|
import { WebSocketEventMessage } from '@/webSocket/types';
|
|
10
13
|
import WebSocketClient from '@/webSocket/WebSocketClient';
|
|
@@ -12,10 +15,9 @@ import WebSocketClient from '@/webSocket/WebSocketClient';
|
|
|
12
15
|
import NotRunningHttpInterceptorError from '../interceptor/errors/NotRunningHttpInterceptorError';
|
|
13
16
|
import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError';
|
|
14
17
|
import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient';
|
|
15
|
-
import { HttpInterceptorPlatform } from '../interceptor/types/options';
|
|
18
|
+
import { HttpInterceptorPlatform, UnhandledRequestStrategy } from '../interceptor/types/options';
|
|
16
19
|
import HttpInterceptorWorker from './HttpInterceptorWorker';
|
|
17
|
-
import { HttpResponseFactoryContext } from './types/http';
|
|
18
|
-
import { MSWHttpResponseFactory } from './types/msw';
|
|
20
|
+
import { HttpResponseFactory, HttpResponseFactoryContext } from './types/http';
|
|
19
21
|
import { RemoteHttpInterceptorWorkerOptions } from './types/options';
|
|
20
22
|
|
|
21
23
|
interface HttpHandler {
|
|
@@ -24,7 +26,7 @@ interface HttpHandler {
|
|
|
24
26
|
method: HttpMethod;
|
|
25
27
|
path: string;
|
|
26
28
|
interceptor: AnyHttpInterceptorClient;
|
|
27
|
-
createResponse: (context: HttpResponseFactoryContext) =>
|
|
29
|
+
createResponse: (context: HttpResponseFactoryContext) => PossiblePromise<Response | null>;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
@@ -78,9 +80,12 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
78
80
|
|
|
79
81
|
try {
|
|
80
82
|
const rawResponse = (await handler?.createResponse({ request })) ?? null;
|
|
81
|
-
const response = rawResponse && request.method === 'HEAD' ? new Response(null, rawResponse) : rawResponse;
|
|
82
83
|
|
|
83
|
-
if (
|
|
84
|
+
if (rawResponse) {
|
|
85
|
+
const response = methodCanHaveResponseBody(request.method as HttpMethod)
|
|
86
|
+
? rawResponse
|
|
87
|
+
: new Response(null, rawResponse);
|
|
88
|
+
|
|
84
89
|
return { response: await serializeResponse(response) };
|
|
85
90
|
}
|
|
86
91
|
} catch (error) {
|
|
@@ -135,7 +140,7 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
135
140
|
interceptor: HttpInterceptorClient<Schema>,
|
|
136
141
|
method: HttpMethod,
|
|
137
142
|
path: string,
|
|
138
|
-
createResponse:
|
|
143
|
+
createResponse: HttpResponseFactory,
|
|
139
144
|
) {
|
|
140
145
|
if (!this.isRunning) {
|
|
141
146
|
throw new NotRunningHttpInterceptorError();
|
|
@@ -151,10 +156,7 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
151
156
|
method,
|
|
152
157
|
path,
|
|
153
158
|
interceptor,
|
|
154
|
-
|
|
155
|
-
const response = await createResponse(context);
|
|
156
|
-
return response;
|
|
157
|
-
},
|
|
159
|
+
createResponse,
|
|
158
160
|
};
|
|
159
161
|
|
|
160
162
|
this.httpHandlers.set(handler.id, handler);
|
|
@@ -167,6 +169,25 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
|
|
|
167
169
|
});
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
async createResponseFromDeclaration(
|
|
173
|
+
request: HttpRequest,
|
|
174
|
+
declaration:
|
|
175
|
+
| { status: number; headers?: HttpHeadersInit; body?: HttpBody }
|
|
176
|
+
| { action: UnhandledRequestStrategy.Action },
|
|
177
|
+
) {
|
|
178
|
+
const response = await super.createResponseFromDeclaration(request, declaration);
|
|
179
|
+
|
|
180
|
+
if (response && HttpInterceptorWorker.isBypassedResponse(response)) {
|
|
181
|
+
throw new UnsupportedResponseBypassError();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (response && HttpInterceptorWorker.isRejectedResponse(response)) {
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return response;
|
|
189
|
+
}
|
|
190
|
+
|
|
170
191
|
async clearHandlers<Schema extends HttpSchema>(
|
|
171
192
|
options: {
|
|
172
193
|
interceptor?: HttpInterceptorClient<Schema>;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { HttpBody, HttpRequest } from '@zimic/http';
|
|
1
|
+
import { HttpBody, HttpRequest, HttpResponse } from '@zimic/http';
|
|
2
|
+
import { PossiblePromise } from '@zimic/utils/types';
|
|
2
3
|
|
|
3
4
|
export interface HttpResponseFactoryContext<Body extends HttpBody = HttpBody> {
|
|
4
5
|
request: HttpRequest<Body>;
|
|
5
6
|
}
|
|
7
|
+
|
|
8
|
+
export type HttpResponseFactory<RequestBody extends HttpBody = HttpBody, ResponseBody extends HttpBody = HttpBody> = (
|
|
9
|
+
context: HttpResponseFactoryContext<RequestBody>,
|
|
10
|
+
) => PossiblePromise<HttpResponse<ResponseBody> | null>;
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import { HttpResponse, HttpBody } from '@zimic/http';
|
|
2
|
-
import { PossiblePromise } from '@zimic/utils/types';
|
|
3
1
|
import type { HttpHandler as MSWHandler } from 'msw';
|
|
4
2
|
import type { SetupWorker as BrowserMSWWorker } from 'msw/browser';
|
|
5
3
|
import type { SetupServer as NodeMSWWorker } from 'msw/node';
|
|
6
4
|
|
|
7
|
-
import { HttpResponseFactoryContext } from './http';
|
|
8
|
-
|
|
9
5
|
export type { MSWHandler, NodeMSWWorker, BrowserMSWWorker };
|
|
10
6
|
|
|
11
7
|
export type MSWWorker = NodeMSWWorker | BrowserMSWWorker;
|
|
12
|
-
|
|
13
|
-
export type MSWHttpResponseFactory<
|
|
14
|
-
RequestBody extends HttpBody = HttpBody,
|
|
15
|
-
ResponseBody extends HttpBody = HttpBody,
|
|
16
|
-
> = (context: HttpResponseFactoryContext<RequestBody>) => PossiblePromise<HttpResponse<ResponseBody> | null>;
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
} from '@zimic/http';
|
|
17
17
|
import { Default, PartialByKey, PossiblePromise, Replace } from '@zimic/utils/types';
|
|
18
18
|
|
|
19
|
+
import { UnhandledRequestStrategy } from '@/http/interceptor/types/options';
|
|
20
|
+
|
|
19
21
|
type HttpRequestHandlerResponseBody<
|
|
20
22
|
ResponseSchema extends HttpResponseSchema,
|
|
21
23
|
StatusCode extends HttpStatusCode,
|
|
@@ -42,17 +44,29 @@ export type HttpRequestHandlerResponseDeclarationWithHeaders<ResponseSchema exte
|
|
|
42
44
|
: { headers: HttpRequestHandlerResponseDeclarationHeaders<ResponseSchema> };
|
|
43
45
|
|
|
44
46
|
/** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
|
|
45
|
-
export type
|
|
47
|
+
export type HttpRequestHandlerStatusResponseDeclaration<
|
|
46
48
|
MethodSchema extends HttpMethodSchema = HttpMethodSchema,
|
|
47
49
|
StatusCode extends HttpStatusCode = HttpStatusCode,
|
|
48
50
|
> = StatusCode extends StatusCode
|
|
49
|
-
? { status: StatusCode } & HttpRequestHandlerResponseWithBody<
|
|
51
|
+
? { status: StatusCode; action?: never } & HttpRequestHandlerResponseWithBody<
|
|
50
52
|
Default<Default<MethodSchema['response']>[StatusCode]>,
|
|
51
53
|
StatusCode
|
|
52
54
|
> &
|
|
53
55
|
HttpRequestHandlerResponseDeclarationWithHeaders<Default<Default<MethodSchema['response']>[StatusCode]>>
|
|
54
56
|
: never;
|
|
55
57
|
|
|
58
|
+
/** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
|
|
59
|
+
export interface HttpRequestHandlerActionResponseDeclaration {
|
|
60
|
+
status?: never;
|
|
61
|
+
action: UnhandledRequestStrategy.Action;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
|
|
65
|
+
export type HttpRequestHandlerResponseDeclaration<
|
|
66
|
+
MethodSchema extends HttpMethodSchema = HttpMethodSchema,
|
|
67
|
+
StatusCode extends HttpStatusCode = HttpStatusCode,
|
|
68
|
+
> = HttpRequestHandlerStatusResponseDeclaration<MethodSchema, StatusCode> | HttpRequestHandlerActionResponseDeclaration;
|
|
69
|
+
|
|
56
70
|
/** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
|
|
57
71
|
export type HttpRequestHandlerResponseDeclarationFactory<
|
|
58
72
|
Path extends string,
|
|
@@ -297,8 +297,17 @@ class InterceptorServer implements PublicInterceptorServer {
|
|
|
297
297
|
const { response, matchedSomeInterceptor } = await this.createResponseForRequest(serializedRequest);
|
|
298
298
|
|
|
299
299
|
if (response) {
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
if (HttpInterceptorWorker.isRejectedResponse(response)) {
|
|
301
|
+
nodeResponse.destroy();
|
|
302
|
+
} else {
|
|
303
|
+
this.setDefaultAccessControlHeaders(response, [
|
|
304
|
+
'access-control-allow-origin',
|
|
305
|
+
'access-control-expose-headers',
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
await sendNodeResponse(response, nodeResponse, nodeRequest, true);
|
|
309
|
+
}
|
|
310
|
+
|
|
302
311
|
return;
|
|
303
312
|
}
|
|
304
313
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class UnsupportedResponseBypassError extends Error {
|
|
2
|
+
constructor() {
|
|
3
|
+
super(
|
|
4
|
+
"Remote interceptors cannot bypass responses. Use `{ action: 'reject' }` instead.\n\n" +
|
|
5
|
+
'Learn more: https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond',
|
|
6
|
+
);
|
|
7
|
+
this.name = 'UnsupportedResponseBypassError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default UnsupportedResponseBypassError;
|
package/src/utils/fetch.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { JSONValue } from '@zimic/http';
|
|
2
2
|
|
|
3
|
+
import { UnhandledRequestStrategy } from '@/http';
|
|
4
|
+
import HttpInterceptorWorker from '@/http/interceptorWorker/HttpInterceptorWorker';
|
|
5
|
+
|
|
3
6
|
import { convertArrayBufferToBase64, convertBase64ToArrayBuffer } from './data';
|
|
4
7
|
|
|
5
8
|
export type SerializedHttpRequest = JSONValue<{
|
|
@@ -56,6 +59,8 @@ export function deserializeRequest(serializedRequest: SerializedHttpRequest): Re
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export type SerializedResponse = JSONValue<{
|
|
62
|
+
type: Response['type'];
|
|
63
|
+
action?: UnhandledRequestStrategy.Action;
|
|
59
64
|
status: number;
|
|
60
65
|
statusText: string;
|
|
61
66
|
headers: Record<string, string>;
|
|
@@ -67,6 +72,8 @@ export async function serializeResponse(response: Response): Promise<SerializedR
|
|
|
67
72
|
const serializedBody = responseClone.body ? convertArrayBufferToBase64(await responseClone.arrayBuffer()) : null;
|
|
68
73
|
|
|
69
74
|
return {
|
|
75
|
+
type: response.type,
|
|
76
|
+
action: HttpInterceptorWorker.getResponseAction(response),
|
|
70
77
|
status: response.status,
|
|
71
78
|
statusText: response.statusText,
|
|
72
79
|
headers: Object.fromEntries(response.headers),
|
|
@@ -75,11 +82,23 @@ export async function serializeResponse(response: Response): Promise<SerializedR
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
export function deserializeResponse(serializedResponse: SerializedResponse): Response {
|
|
78
|
-
|
|
85
|
+
let response: Response;
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
if (serializedResponse.type === 'error') {
|
|
88
|
+
response = Response.error();
|
|
89
|
+
} else {
|
|
90
|
+
const deserializedBody = serializedResponse.body ? convertBase64ToArrayBuffer(serializedResponse.body) : null;
|
|
91
|
+
|
|
92
|
+
response = new Response(deserializedBody, {
|
|
93
|
+
status: serializedResponse.status,
|
|
94
|
+
statusText: serializedResponse.statusText,
|
|
95
|
+
headers: new Headers(serializedResponse.headers),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (serializedResponse.action) {
|
|
100
|
+
HttpInterceptorWorker.setResponseAction(response, serializedResponse.action);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return response;
|
|
85
104
|
}
|