@zimic/interceptor 1.2.7-canary.2 → 1.3.0-canary.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.
Files changed (37) hide show
  1. package/dist/{chunk-6GEP6R3L.mjs → chunk-4L2JH2L4.mjs} +80 -23
  2. package/dist/chunk-4L2JH2L4.mjs.map +1 -0
  3. package/dist/{chunk-PB4TJVK3.js → chunk-F5OGZSHS.js} +80 -23
  4. package/dist/chunk-F5OGZSHS.js.map +1 -0
  5. package/dist/cli.js +17 -19
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cli.mjs +2 -4
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/http.d.ts +95 -6
  10. package/dist/http.js +253 -186
  11. package/dist/http.js.map +1 -1
  12. package/dist/http.mjs +254 -187
  13. package/dist/http.mjs.map +1 -1
  14. package/dist/server.js +6 -6
  15. package/dist/server.mjs +1 -1
  16. package/package.json +2 -2
  17. package/src/cli/server/start.ts +7 -2
  18. package/src/http/interceptor/HttpInterceptorClient.ts +8 -10
  19. package/src/http/interceptor/types/options.ts +11 -5
  20. package/src/http/interceptorWorker/HttpInterceptorWorker.ts +73 -15
  21. package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +36 -11
  22. package/src/http/interceptorWorker/RemoteHttpInterceptorWorker.ts +34 -13
  23. package/src/http/interceptorWorker/types/http.ts +6 -1
  24. package/src/http/interceptorWorker/types/msw.ts +0 -9
  25. package/src/http/requestHandler/HttpRequestHandlerClient.ts +2 -4
  26. package/src/http/requestHandler/errors/TimesCheckError.ts +1 -1
  27. package/src/http/requestHandler/types/requests.ts +16 -2
  28. package/src/server/InterceptorServer.ts +13 -5
  29. package/src/server/constants.ts +1 -1
  30. package/src/server/errors/UnsupportedResponseBypassError.ts +11 -0
  31. package/src/utils/crypto.ts +1 -1
  32. package/src/utils/fetch.ts +25 -6
  33. package/src/utils/files.ts +1 -1
  34. package/src/utils/logging.ts +2 -2
  35. package/src/webSocket/WebSocketClient.ts +1 -1
  36. package/dist/chunk-6GEP6R3L.mjs.map +0 -1
  37. 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 chunkPB4TJVK3_js = require('./chunk-PB4TJVK3.js');
3
+ var chunkF5OGZSHS_js = require('./chunk-F5OGZSHS.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 chunkPB4TJVK3_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
10
+ get: function () { return chunkF5OGZSHS_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
11
11
  });
12
12
  Object.defineProperty(exports, "DEFAULT_PREFLIGHT_STATUS_CODE", {
13
13
  enumerable: true,
14
- get: function () { return chunkPB4TJVK3_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
14
+ get: function () { return chunkF5OGZSHS_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
15
15
  });
16
16
  Object.defineProperty(exports, "NotRunningInterceptorServerError", {
17
17
  enumerable: true,
18
- get: function () { return chunkPB4TJVK3_js.NotRunningInterceptorServerError_default; }
18
+ get: function () { return chunkF5OGZSHS_js.NotRunningInterceptorServerError_default; }
19
19
  });
20
20
  Object.defineProperty(exports, "RunningInterceptorServerError", {
21
21
  enumerable: true,
22
- get: function () { return chunkPB4TJVK3_js.RunningInterceptorServerError_default; }
22
+ get: function () { return chunkF5OGZSHS_js.RunningInterceptorServerError_default; }
23
23
  });
24
24
  Object.defineProperty(exports, "createInterceptorServer", {
25
25
  enumerable: true,
26
- get: function () { return chunkPB4TJVK3_js.createInterceptorServer; }
26
+ get: function () { return chunkF5OGZSHS_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-6GEP6R3L.mjs';
1
+ export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-4L2JH2L4.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.2.7-canary.2",
17
+ "version": "1.3.0-canary.1",
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 run --coverage",
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
  },
@@ -1,5 +1,10 @@
1
- import { ProcessExitEvent, PROCESS_EXIT_EVENTS, PROCESS_EXIT_CODE_BY_EXIT_EVENT } from '@zimic/utils/process/constants';
2
- import runCommand, { CommandError } from '@zimic/utils/process/runCommand';
1
+ import {
2
+ ProcessExitEvent,
3
+ PROCESS_EXIT_EVENTS,
4
+ PROCESS_EXIT_CODE_BY_EXIT_EVENT,
5
+ runCommand,
6
+ CommandError,
7
+ } from '@zimic/utils/process';
3
8
  import color from 'picocolors';
4
9
 
5
10
  import { InterceptorServer, createInterceptorServer } from '@/server';
@@ -8,9 +8,7 @@ import {
8
8
  HttpSchema,
9
9
  } from '@zimic/http';
10
10
  import { Default, PossiblePromise } from '@zimic/utils/types';
11
- import createRegexFromPath from '@zimic/utils/url/createRegexFromPath';
12
- import excludeNonPathParams from '@zimic/utils/url/excludeNonPathParams';
13
- import validateURLProtocol from '@zimic/utils/url/validateURLProtocol';
11
+ import { createRegexFromPath, excludeNonPathParams, validateURLProtocol } from '@zimic/utils/url';
14
12
 
15
13
  import { isServerSide } from '@/utils/environment';
16
14
 
@@ -267,7 +265,7 @@ class HttpInterceptorClient<
267
265
  }
268
266
  }
269
267
 
270
- private async handleInterceptedRequest<
268
+ async handleInterceptedRequest<
271
269
  Method extends HttpSchemaMethod<Schema>,
272
270
  Path extends HttpSchemaPath<Schema, Method>,
273
271
  Context extends HttpInterceptorRequestContext<Schema, Method, Path>,
@@ -289,15 +287,15 @@ class HttpInterceptorClient<
289
287
  return null;
290
288
  }
291
289
 
292
- const response = HttpInterceptorWorker.createResponseFromDeclaration(request, responseDeclaration);
290
+ const response = await this.workerOrThrow.createResponseFromDeclaration(request, responseDeclaration);
293
291
 
294
- if (this.requestSaving.enabled) {
292
+ const shouldSaveInterceptedRequest =
293
+ this.requestSaving.enabled && response && !HttpInterceptorWorker.isRejectedResponse(response);
294
+
295
+ if (shouldSaveInterceptedRequest) {
295
296
  const responseClone = response.clone();
296
297
 
297
- const parsedResponse = await HttpInterceptorWorker.parseRawResponse<
298
- Default<Schema[Path][Method]>,
299
- typeof responseDeclaration.status
300
- >(responseClone);
298
+ const parsedResponse = await HttpInterceptorWorker.parseRawResponse<Default<Schema[Path][Method]>>(responseClone);
301
299
 
302
300
  matchedHandler.saveInterceptedRequest(parsedRequest, parsedResponse);
303
301
  }
@@ -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 Action = 'bypass' | 'reject';
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<'reject'>;
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<'reject'>;
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,8 +10,9 @@ import {
10
10
  InferPathParams,
11
11
  parseHttpBody,
12
12
  HttpSearchParams,
13
+ HttpRequest,
13
14
  } from '@zimic/http';
14
- import isDefined from '@zimic/utils/data/isDefined';
15
+ import { isDefined } from '@zimic/utils/data';
15
16
  import { Default, PossiblePromise } from '@zimic/utils/types';
16
17
  import color from 'picocolors';
17
18
 
@@ -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 { MSWHttpResponseFactory } from './types/msw';
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: MSWHttpResponseFactory,
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 createResponseFromDeclaration(
193
- request: Request,
194
- declaration: { status: number; headers?: HttpHeadersInit; body?: HttpBody },
195
- ): HttpResponse {
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<MethodSchema extends HttpMethodSchema, StatusCode extends HttpStatusCode>(
309
- originalRawResponse: Response,
310
- ): Promise<HttpInterceptorResponse<MethodSchema, StatusCode>> {
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,6 @@
1
- import { HttpRequest, HttpResponse, HttpMethod, HttpSchema } from '@zimic/http';
2
- import createRegexFromPath from '@zimic/utils/url/createRegexFromPath';
3
- import excludeNonPathParams from '@zimic/utils/url/excludeNonPathParams';
4
- import validatePathParams from '@zimic/utils/url/validatePathParams';
5
- import { SharedOptions as MSWWorkerSharedOptions, http, passthrough } from 'msw';
1
+ import { HttpRequest, HttpResponse, HttpMethod, HttpSchema, HttpHeadersInit, HttpBody } from '@zimic/http';
2
+ import { createRegexFromPath, excludeNonPathParams, validatePathParams } from '@zimic/utils/url';
3
+ import { SharedOptions as MSWWorkerSharedOptions, bypass, http, passthrough } from 'msw';
6
4
  import * as mswBrowser from 'msw/browser';
7
5
  import * as mswNode from 'msw/node';
8
6
 
@@ -12,10 +10,11 @@ import { isClientSide, isServerSide } from '@/utils/environment';
12
10
  import NotRunningHttpInterceptorError from '../interceptor/errors/NotRunningHttpInterceptorError';
13
11
  import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError';
14
12
  import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient';
13
+ import { UnhandledRequestStrategy } from '../interceptor/types/options';
15
14
  import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError';
16
15
  import HttpInterceptorWorker from './HttpInterceptorWorker';
17
- import { HttpResponseFactoryContext } from './types/http';
18
- import { BrowserMSWWorker, MSWHttpResponseFactory, MSWWorker, NodeMSWWorker } from './types/msw';
16
+ import { HttpResponseFactory, HttpResponseFactoryContext } from './types/http';
17
+ import { BrowserMSWWorker, MSWWorker, NodeMSWWorker } from './types/msw';
19
18
  import { LocalHttpInterceptorWorkerOptions } from './types/options';
20
19
 
21
20
  interface HttpHandler {
@@ -163,7 +162,7 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
163
162
  interceptor: HttpInterceptorClient<Schema>,
164
163
  method: HttpMethod,
165
164
  path: string,
166
- createResponse: MSWHttpResponseFactory,
165
+ createResponse: HttpResponseFactory,
167
166
  ) {
168
167
  if (!this.isRunning) {
169
168
  throw new NotRunningHttpInterceptorError();
@@ -185,7 +184,7 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
185
184
  let response: HttpResponse | null = null;
186
185
 
187
186
  try {
188
- response = await createResponse({ ...context, request });
187
+ response = await createResponse({ request });
189
188
  } catch (error) {
190
189
  console.error(error);
191
190
  }
@@ -209,6 +208,32 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
209
208
  methodHandlers.push(handler);
210
209
  }
211
210
 
211
+ async createResponseFromDeclaration(
212
+ request: HttpRequest,
213
+ declaration:
214
+ | { status: number; headers?: HttpHeadersInit; body?: HttpBody }
215
+ | { action: UnhandledRequestStrategy.Action },
216
+ ) {
217
+ const requestClone = request.clone();
218
+ const response = await super.createResponseFromDeclaration(request, declaration);
219
+
220
+ if (response && HttpInterceptorWorker.isBypassedResponse(response)) {
221
+ try {
222
+ const response = (await fetch(bypass(requestClone))) as HttpResponse;
223
+ return response;
224
+ } catch (error) {
225
+ console.error(error);
226
+ return null;
227
+ }
228
+ }
229
+
230
+ if (response && HttpInterceptorWorker.isRejectedResponse(response)) {
231
+ return response;
232
+ }
233
+
234
+ return response;
235
+ }
236
+
212
237
  private async createResponseForRequest(request: HttpRequest) {
213
238
  const methodHandlers = this.httpHandlersByMethod[request.method as HttpMethod];
214
239
 
@@ -244,9 +269,9 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
244
269
  await super.logUnhandledRequestIfNecessary(requestClone, strategy);
245
270
 
246
271
  if (strategy?.action === 'reject') {
247
- return Response.error();
272
+ return Response.error() as HttpResponse;
248
273
  } else {
249
- return passthrough();
274
+ return passthrough() as HttpResponse;
250
275
  }
251
276
  }
252
277
 
@@ -1,10 +1,13 @@
1
- import { HttpMethod, HttpSchema } from '@zimic/http';
2
- import validatePathParams from '@zimic/utils/url/validatePathParams';
1
+ import { HttpBody, HttpHeadersInit, HttpMethod, HttpRequest, HttpSchema } from '@zimic/http';
2
+ import { PossiblePromise } from '@zimic/utils/types';
3
+ import { validatePathParams } from '@zimic/utils/url';
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) => Promise<Response | null>;
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 (response) {
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: MSWHttpResponseFactory,
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
- async createResponse(context) {
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>;
@@ -9,10 +9,8 @@ import {
9
9
  HttpRequestSearchParamsSchema,
10
10
  HttpRequestHeadersSchema,
11
11
  } from '@zimic/http';
12
- import blobEquals from '@zimic/utils/data/blobEquals';
13
- import jsonContains from '@zimic/utils/data/jsonContains';
14
- import jsonEquals from '@zimic/utils/data/jsonEquals';
15
- import waitForDelay from '@zimic/utils/time/waitForDelay';
12
+ import { blobEquals, jsonContains, jsonEquals } from '@zimic/utils/data';
13
+ import { waitForDelay } from '@zimic/utils/time';
16
14
  import { Default, Range } from '@zimic/utils/types';
17
15
 
18
16
  import { convertArrayBufferToBlob, convertReadableStreamToBlob } from '@/utils/data';
@@ -1,4 +1,4 @@
1
- import isNonEmpty from '@zimic/utils/data/isNonEmpty';
1
+ import { isNonEmpty } from '@zimic/utils/data';
2
2
  import { Range } from '@zimic/utils/types';
3
3
  import color from 'picocolors';
4
4
 
@@ -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 HttpRequestHandlerResponseDeclaration<
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,
@@ -1,8 +1,7 @@
1
1
  import { normalizeNodeRequest, sendNodeResponse } from '@whatwg-node/server';
2
2
  import { HttpRequest, HttpMethod } from '@zimic/http';
3
- import { startHttpServer, stopHttpServer, getHttpServerPort } from '@zimic/utils/server/lifecycle';
4
- import createRegexFromPath from '@zimic/utils/url/createRegexFromPath';
5
- import excludeNonPathParams from '@zimic/utils/url/excludeNonPathParams';
3
+ import { startHttpServer, stopHttpServer, getHttpServerPort } from '@zimic/utils/server';
4
+ import { createRegexFromPath, excludeNonPathParams } from '@zimic/utils/url';
6
5
  import { createServer, Server as HttpServer, IncomingMessage, ServerResponse } from 'http';
7
6
  import type { WebSocket as Socket } from 'isomorphic-ws';
8
7
 
@@ -297,8 +296,17 @@ class InterceptorServer implements PublicInterceptorServer {
297
296
  const { response, matchedSomeInterceptor } = await this.createResponseForRequest(serializedRequest);
298
297
 
299
298
  if (response) {
300
- this.setDefaultAccessControlHeaders(response, ['access-control-allow-origin', 'access-control-expose-headers']);
301
- await sendNodeResponse(response, nodeResponse, nodeRequest, true);
299
+ if (HttpInterceptorWorker.isRejectedResponse(response)) {
300
+ nodeResponse.destroy();
301
+ } else {
302
+ this.setDefaultAccessControlHeaders(response, [
303
+ 'access-control-allow-origin',
304
+ 'access-control-expose-headers',
305
+ ]);
306
+
307
+ await sendNodeResponse(response, nodeResponse, nodeRequest, true);
308
+ }
309
+
302
310
  return;
303
311
  }
304
312
 
@@ -1,5 +1,5 @@
1
1
  import { HTTP_METHODS, HttpSchema } from '@zimic/http';
2
- import { DEFAULT_HTTP_SERVER_LIFECYCLE_TIMEOUT } from '@zimic/utils/server/lifecycle';
2
+ import { DEFAULT_HTTP_SERVER_LIFECYCLE_TIMEOUT } from '@zimic/utils/server';
3
3
 
4
4
  import { DEFAULT_WEB_SOCKET_MESSAGE_TIMEOUT } from '@/utils/webSocket';
5
5
 
@@ -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;
@@ -1,4 +1,4 @@
1
- import createCachedDynamicImport from '@zimic/utils/import/createCachedDynamicImport';
1
+ import { createCachedDynamicImport } from '@zimic/utils/import';
2
2
 
3
3
  export type IsomorphicCrypto = Crypto | typeof import('crypto');
4
4