@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/server.js CHANGED
@@ -1,29 +1,29 @@
1
1
  'use strict';
2
2
 
3
- var chunkPB4TJVK3_js = require('./chunk-PB4TJVK3.js');
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 chunkPB4TJVK3_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
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 chunkPB4TJVK3_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
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 chunkPB4TJVK3_js.NotRunningInterceptorServerError_default; }
18
+ get: function () { return chunkKDM6Y6GO_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 chunkKDM6Y6GO_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 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-6GEP6R3L.mjs';
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.2.7-canary.2",
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 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
  },
@@ -267,7 +267,7 @@ class HttpInterceptorClient<
267
267
  }
268
268
  }
269
269
 
270
- private async handleInterceptedRequest<
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 = HttpInterceptorWorker.createResponseFromDeclaration(request, responseDeclaration);
292
+ const response = await this.workerOrThrow.createResponseFromDeclaration(request, responseDeclaration);
293
293
 
294
- if (this.requestSaving.enabled) {
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 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,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 { 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,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, MSWHttpResponseFactory, MSWWorker, NodeMSWWorker } from './types/msw';
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: MSWHttpResponseFactory,
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({ ...context, request });
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) => 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>;
@@ -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,
@@ -297,8 +297,17 @@ class InterceptorServer implements PublicInterceptorServer {
297
297
  const { response, matchedSomeInterceptor } = await this.createResponseForRequest(serializedRequest);
298
298
 
299
299
  if (response) {
300
- this.setDefaultAccessControlHeaders(response, ['access-control-allow-origin', 'access-control-expose-headers']);
301
- await sendNodeResponse(response, nodeResponse, nodeRequest, true);
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;
@@ -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
- const deserializedBody = serializedResponse.body ? convertBase64ToArrayBuffer(serializedResponse.body) : null;
85
+ let response: Response;
79
86
 
80
- return new Response(deserializedBody, {
81
- status: serializedResponse.status,
82
- statusText: serializedResponse.statusText,
83
- headers: new Headers(serializedResponse.headers),
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
  }