@zimic/interceptor 1.1.0-canary.1 → 1.1.0-canary.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,29 +1,29 @@
1
1
  'use strict';
2
2
 
3
- var chunkLWUFSWRA_js = require('./chunk-LWUFSWRA.js');
3
+ var chunkQDNKVSEG_js = require('./chunk-QDNKVSEG.js');
4
4
  require('./chunk-WCQVDF3K.js');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "DEFAULT_ACCESS_CONTROL_HEADERS", {
9
9
  enumerable: true,
10
- get: function () { return chunkLWUFSWRA_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
10
+ get: function () { return chunkQDNKVSEG_js.DEFAULT_ACCESS_CONTROL_HEADERS; }
11
11
  });
12
12
  Object.defineProperty(exports, "DEFAULT_PREFLIGHT_STATUS_CODE", {
13
13
  enumerable: true,
14
- get: function () { return chunkLWUFSWRA_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
14
+ get: function () { return chunkQDNKVSEG_js.DEFAULT_PREFLIGHT_STATUS_CODE; }
15
15
  });
16
16
  Object.defineProperty(exports, "NotRunningInterceptorServerError", {
17
17
  enumerable: true,
18
- get: function () { return chunkLWUFSWRA_js.NotRunningInterceptorServerError_default; }
18
+ get: function () { return chunkQDNKVSEG_js.NotRunningInterceptorServerError_default; }
19
19
  });
20
20
  Object.defineProperty(exports, "RunningInterceptorServerError", {
21
21
  enumerable: true,
22
- get: function () { return chunkLWUFSWRA_js.RunningInterceptorServerError_default; }
22
+ get: function () { return chunkQDNKVSEG_js.RunningInterceptorServerError_default; }
23
23
  });
24
24
  Object.defineProperty(exports, "createInterceptorServer", {
25
25
  enumerable: true,
26
- get: function () { return chunkLWUFSWRA_js.createInterceptorServer; }
26
+ get: function () { return chunkQDNKVSEG_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-7DRPXOOJ.mjs';
1
+ export { DEFAULT_ACCESS_CONTROL_HEADERS, DEFAULT_PREFLIGHT_STATUS_CODE, NotRunningInterceptorServerError_default as NotRunningInterceptorServerError, RunningInterceptorServerError_default as RunningInterceptorServerError, createInterceptorServer } from './chunk-RDMXECNU.mjs';
2
2
  import './chunk-CGILA3WO.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.1.0-canary.1",
17
+ "version": "1.1.0-canary.3",
18
18
  "homepage": "https://zimic.dev/docs/interceptor",
19
19
  "repository": {
20
20
  "type": "git",
@@ -105,13 +105,13 @@
105
105
  "typescript": "^5.9.2",
106
106
  "vitest": "^3.2.4",
107
107
  "@zimic/eslint-config-node": "0.0.0",
108
- "@zimic/tsconfig": "0.0.0",
109
108
  "@zimic/lint-staged-config": "0.0.0",
110
- "@zimic/utils": "0.0.0"
109
+ "@zimic/utils": "0.0.0",
110
+ "@zimic/tsconfig": "0.0.0"
111
111
  },
112
112
  "peerDependencies": {
113
113
  "typescript": ">=5.0.0",
114
- "@zimic/http": "^1.0.0 || 1.1.0-canary.1"
114
+ "@zimic/http": "^1.0.0 || 1.1.0-canary.4"
115
115
  },
116
116
  "peerDependenciesMeta": {
117
117
  "typescript": {
@@ -8,9 +8,8 @@ import {
8
8
  HttpSchema,
9
9
  } from '@zimic/http';
10
10
  import { Default, PossiblePromise } from '@zimic/utils/types';
11
- import createRegExpFromURL from '@zimic/utils/url/createRegExpFromURL';
11
+ import createParametrizedPathPattern from '@zimic/utils/url/createParametrizedPathPattern';
12
12
  import excludeURLParams from '@zimic/utils/url/excludeURLParams';
13
- import joinURL from '@zimic/utils/url/joinURL';
14
13
  import validateURLProtocol from '@zimic/utils/url/validateURLProtocol';
15
14
 
16
15
  import { isServerSide } from '@/utils/environment';
@@ -246,14 +245,13 @@ class HttpInterceptorClient<
246
245
 
247
246
  this.handlerClientsByMethod[handler.method].set(handler.path, handlerClients);
248
247
 
249
- const url = joinURL(this.baseURLAsString, handler.path);
250
- const urlRegex = createRegExpFromURL(url);
248
+ const pathPattern = createParametrizedPathPattern(handler.path);
251
249
 
252
- const registrationResult = this.workerOrThrow.use(this, handler.method, url, async (context) => {
250
+ const registrationResult = this.workerOrThrow.use(this, handler.method, handler.path, async (context) => {
253
251
  const response = await this.handleInterceptedRequest(
254
- urlRegex,
255
252
  handler.method,
256
253
  handler.path,
254
+ pathPattern,
257
255
  context as HttpInterceptorRequestContext<Schema, Method, Path>,
258
256
  );
259
257
  return response;
@@ -268,9 +266,10 @@ class HttpInterceptorClient<
268
266
  Method extends HttpSchemaMethod<Schema>,
269
267
  Path extends HttpSchemaPath<Schema, Method>,
270
268
  Context extends HttpInterceptorRequestContext<Schema, Method, Path>,
271
- >(matchedURLRegex: RegExp, method: Method, path: Path, { request }: Context): Promise<HttpResponse | null> {
269
+ >(method: Method, path: Path, pathPattern: RegExp, { request }: Context): Promise<HttpResponse | null> {
272
270
  const parsedRequest = await HttpInterceptorWorker.parseRawRequest<Path, Default<Schema[Path][Method]>>(request, {
273
- urlRegex: matchedURLRegex,
271
+ baseURL: this.baseURLAsString,
272
+ pathPattern,
274
273
  });
275
274
 
276
275
  const matchedHandler = await this.findMatchedHandler(method, path, parsedRequest);
@@ -1,14 +1,15 @@
1
1
  import { HttpBody, HttpSchema, HttpSchemaMethod, HttpSchemaPath } from '@zimic/http';
2
2
  import { Default } from '@zimic/utils/types';
3
3
 
4
- import { MSWHttpResponseFactoryContext } from '../../interceptorWorker/types/msw';
4
+ import { HttpResponseFactoryContext } from '@/http/interceptorWorker/types/http';
5
+
5
6
  import { HttpInterceptorRequest } from '../../requestHandler/types/requests';
6
7
 
7
8
  export type HttpInterceptorRequestContext<
8
9
  Schema extends HttpSchema,
9
10
  Method extends HttpSchemaMethod<Schema>,
10
11
  Path extends HttpSchemaPath<Schema, Method>,
11
- > = MSWHttpResponseFactoryContext<Default<Default<Schema[Path][Method]>['request']>['body']>;
12
+ > = HttpResponseFactoryContext<Default<Default<Schema[Path][Method]>['request']>['body']>;
12
13
 
13
14
  export type UnhandledHttpInterceptorRequestPath = string;
14
15
 
@@ -101,7 +101,7 @@ abstract class HttpInterceptorWorker {
101
101
  abstract use<Schema extends HttpSchema>(
102
102
  interceptor: HttpInterceptorClient<Schema>,
103
103
  method: HttpMethod,
104
- url: string,
104
+ path: string,
105
105
  createResponse: MSWHttpResponseFactory,
106
106
  ): PossiblePromise<void>;
107
107
 
@@ -229,7 +229,7 @@ abstract class HttpInterceptorWorker {
229
229
 
230
230
  static async parseRawRequest<Path extends string, MethodSchema extends HttpMethodSchema>(
231
231
  originalRawRequest: Request,
232
- options: { urlRegex?: RegExp } = {},
232
+ options?: { baseURL: string; pathPattern: RegExp },
233
233
  ): Promise<HttpInterceptorRequest<Path, MethodSchema>> {
234
234
  const rawRequest = originalRawRequest.clone();
235
235
  const rawRequestClone = rawRequest.clone();
@@ -240,7 +240,7 @@ abstract class HttpInterceptorWorker {
240
240
  type HeadersSchema = Default<Default<MethodSchema['request']>['headers']>;
241
241
  const headers = new HttpHeaders<HeadersSchema>(rawRequest.headers);
242
242
 
243
- const pathParams = options.urlRegex ? this.parseRawPathParams<Path>(options.urlRegex, rawRequest) : {};
243
+ const pathParams = this.parseRawPathParams<Path>(rawRequest, options);
244
244
 
245
245
  const parsedURL = new URL(rawRequest.url);
246
246
  type SearchParamsSchema = Default<Default<MethodSchema['request']>['searchParams']>;
@@ -360,10 +360,12 @@ abstract class HttpInterceptorWorker {
360
360
  return HTTP_INTERCEPTOR_RESPONSE_HIDDEN_PROPERTIES.has(property as never);
361
361
  }
362
362
 
363
- static parseRawPathParams<Path extends string>(matchedURLRegex: RegExp, request: Request): InferPathParams<Path> {
364
- const match = request.url.match(matchedURLRegex);
365
- const pathParams = { ...match?.groups };
366
- return pathParams as InferPathParams<Path>;
363
+ static parseRawPathParams<Path extends string>(
364
+ request: Request,
365
+ options?: { baseURL: string; pathPattern: RegExp },
366
+ ): InferPathParams<Path> {
367
+ const match = options?.pathPattern.exec(request.url.replace(options.baseURL, ''));
368
+ return { ...match?.groups } as InferPathParams<Path>;
367
369
  }
368
370
 
369
371
  static async parseRawBody<Body extends HttpBody>(resource: Request | Response) {
@@ -1,6 +1,7 @@
1
1
  import { HttpRequest, HttpResponse, HttpMethod, HttpSchema } from '@zimic/http';
2
+ import createParametrizedPathPattern from '@zimic/utils/url/createParametrizedPathPattern';
2
3
  import excludeURLParams from '@zimic/utils/url/excludeURLParams';
3
- import validateURLPathParams from '@zimic/utils/url/validateURLPathParams';
4
+ import validatePathParams from '@zimic/utils/url/validatePathParams';
4
5
  import { SharedOptions as MSWWorkerSharedOptions, http, passthrough } from 'msw';
5
6
  import * as mswBrowser from 'msw/browser';
6
7
  import * as mswNode from 'msw/node';
@@ -10,29 +11,38 @@ import { isClientSide, isServerSide } from '@/utils/environment';
10
11
 
11
12
  import NotRunningHttpInterceptorError from '../interceptor/errors/NotRunningHttpInterceptorError';
12
13
  import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError';
13
- import HttpInterceptorClient from '../interceptor/HttpInterceptorClient';
14
+ import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient';
14
15
  import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError';
15
16
  import HttpInterceptorWorker from './HttpInterceptorWorker';
16
- import { BrowserMSWWorker, MSWHandler, MSWHttpResponseFactory, MSWWorker, NodeMSWWorker } from './types/msw';
17
+ import { HttpResponseFactoryContext } from './types/http';
18
+ import { BrowserMSWWorker, MSWHttpResponseFactory, MSWWorker, NodeMSWWorker } from './types/msw';
17
19
  import { LocalHttpInterceptorWorkerOptions } from './types/options';
18
20
 
21
+ interface HttpHandler {
22
+ baseURL: string;
23
+ method: HttpMethod;
24
+ pathPattern: RegExp;
25
+ interceptor: AnyHttpInterceptorClient;
26
+ createResponse: (context: HttpResponseFactoryContext) => Promise<Response>;
27
+ }
28
+
19
29
  class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
20
30
  private internalWorker?: MSWWorker;
21
31
 
22
- private defaultHttpHandler: MSWHandler;
23
-
24
- private httpHandlerGroups: {
25
- interceptor: HttpInterceptorClient<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
26
- httpHandler: MSWHandler;
27
- }[] = [];
32
+ private httpHandlersByMethod: {
33
+ [Method in HttpMethod]: HttpHandler[];
34
+ } = {
35
+ GET: [],
36
+ POST: [],
37
+ PATCH: [],
38
+ PUT: [],
39
+ DELETE: [],
40
+ HEAD: [],
41
+ OPTIONS: [],
42
+ };
28
43
 
29
44
  constructor(_options: LocalHttpInterceptorWorkerOptions) {
30
45
  super();
31
-
32
- this.defaultHttpHandler = http.all('*', async (context) => {
33
- const request = context.request satisfies Request as HttpRequest;
34
- return this.bypassOrRejectUnhandledRequest(request);
35
- });
36
46
  }
37
47
 
38
48
  get type() {
@@ -52,13 +62,21 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
52
62
  }
53
63
 
54
64
  private createInternalWorker() {
65
+ const mswHttpHandler = http.all('*', async (context) => {
66
+ const request = context.request satisfies Request as HttpRequest;
67
+ const response = await this.createResponseForRequest(request);
68
+ return response;
69
+ });
70
+
55
71
  if (isServerSide() && 'setupServer' in mswNode) {
56
- return mswNode.setupServer(this.defaultHttpHandler);
72
+ return mswNode.setupServer(mswHttpHandler);
57
73
  }
74
+
58
75
  /* istanbul ignore else -- @preserve */
59
76
  if (isClientSide() && 'setupWorker' in mswBrowser) {
60
- return mswBrowser.setupWorker(this.defaultHttpHandler);
77
+ return mswBrowser.setupWorker(mswHttpHandler);
61
78
  }
79
+
62
80
  /* istanbul ignore next -- @preserve
63
81
  * Ignoring because checking unknown platforms is not configured in our test setup. */
64
82
  throw new UnknownHttpInterceptorPlatformError();
@@ -142,45 +160,79 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
142
160
  use<Schema extends HttpSchema>(
143
161
  interceptor: HttpInterceptorClient<Schema>,
144
162
  method: HttpMethod,
145
- rawURL: string | URL,
163
+ path: string,
146
164
  createResponse: MSWHttpResponseFactory,
147
165
  ) {
148
- const lowercaseMethod = method.toLowerCase<typeof method>();
166
+ if (!this.isRunning) {
167
+ throw new NotRunningHttpInterceptorError();
168
+ }
149
169
 
150
- const url = new URL(rawURL);
151
- excludeURLParams(url);
152
- validateURLPathParams(url);
170
+ validatePathParams(path);
153
171
 
154
- const httpHandler = http[lowercaseMethod](url.toString(), async (context) => {
155
- const request = context.request as HttpRequest;
156
- const requestClone = request.clone();
172
+ const methodHandlers = this.httpHandlersByMethod[method];
157
173
 
158
- let response: HttpResponse | null = null;
174
+ const handler: HttpHandler = {
175
+ baseURL: interceptor.baseURLAsString,
176
+ method,
177
+ pathPattern: createParametrizedPathPattern(path),
178
+ interceptor,
179
+ createResponse: async (context) => {
180
+ const request = context.request as HttpRequest;
181
+ const requestClone = request.clone();
159
182
 
160
- try {
161
- response = await createResponse({ ...context, request });
162
- } catch (error) {
163
- console.error(error);
164
- }
183
+ let response: HttpResponse | null = null;
184
+
185
+ try {
186
+ response = await createResponse({ ...context, request });
187
+ } catch (error) {
188
+ console.error(error);
189
+ }
190
+
191
+ if (!response) {
192
+ return this.bypassOrRejectUnhandledRequest(requestClone);
193
+ }
194
+
195
+ if (context.request.method === 'HEAD') {
196
+ return new Response(null, {
197
+ status: response.status,
198
+ statusText: response.statusText,
199
+ headers: response.headers,
200
+ });
201
+ }
202
+
203
+ return response;
204
+ },
205
+ };
165
206
 
166
- if (!response) {
167
- return this.bypassOrRejectUnhandledRequest(requestClone);
207
+ methodHandlers.push(handler);
208
+ }
209
+
210
+ private async createResponseForRequest(request: HttpRequest) {
211
+ const methodHandlers = this.httpHandlersByMethod[request.method as HttpMethod];
212
+
213
+ const requestURL = excludeURLParams(new URL(request.url));
214
+ const requestURLAsString = requestURL.href === `${requestURL.origin}/` ? requestURL.origin : requestURL.href;
215
+
216
+ for (let handlerIndex = methodHandlers.length - 1; handlerIndex >= 0; handlerIndex--) {
217
+ const handler = methodHandlers[handlerIndex];
218
+ const matchesBaseURL = requestURLAsString.startsWith(handler.baseURL);
219
+
220
+ if (!matchesBaseURL) {
221
+ continue;
168
222
  }
169
223
 
170
- if (context.request.method === 'HEAD') {
171
- return new Response(null, {
172
- status: response.status,
173
- statusText: response.statusText,
174
- headers: response.headers,
175
- });
224
+ const requestPath = requestURLAsString.replace(handler.baseURL, '');
225
+ const matchesPath = handler.pathPattern.test(requestPath);
226
+
227
+ if (!matchesPath) {
228
+ continue;
176
229
  }
177
230
 
231
+ const response = await handler.createResponse({ request });
178
232
  return response;
179
- });
180
-
181
- this.internalWorkerOrThrow.use(httpHandler);
233
+ }
182
234
 
183
- this.httpHandlerGroups.push({ interceptor, httpHandler });
235
+ return this.bypassOrRejectUnhandledRequest(request);
184
236
  }
185
237
 
186
238
  private async bypassOrRejectUnhandledRequest(request: HttpRequest) {
@@ -198,22 +250,33 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
198
250
 
199
251
  clearHandlers() {
200
252
  this.internalWorkerOrThrow.resetHandlers();
201
- this.httpHandlerGroups = [];
253
+
254
+ for (const handlers of Object.values(this.httpHandlersByMethod)) {
255
+ handlers.length = 0;
256
+ }
202
257
  }
203
258
 
204
259
  clearInterceptorHandlers<Schema extends HttpSchema>(interceptor: HttpInterceptorClient<Schema>) {
205
- const groupToRemoveIndex = this.httpHandlerGroups.findIndex((group) => group.interceptor === interceptor);
206
- removeArrayIndex(this.httpHandlerGroups, groupToRemoveIndex);
207
-
208
- this.internalWorkerOrThrow.resetHandlers();
260
+ if (!this.isRunning) {
261
+ throw new NotRunningHttpInterceptorError();
262
+ }
209
263
 
210
- for (const { httpHandler } of this.httpHandlerGroups) {
211
- this.internalWorkerOrThrow.use(httpHandler);
264
+ for (const methodHandlers of Object.values(this.httpHandlersByMethod)) {
265
+ const groupToRemoveIndex = methodHandlers.findIndex((group) => group.interceptor === interceptor);
266
+ removeArrayIndex(methodHandlers, groupToRemoveIndex);
212
267
  }
213
268
  }
214
269
 
215
270
  get interceptorsWithHandlers() {
216
- return this.httpHandlerGroups.map((group) => group.interceptor);
271
+ const interceptors = new Set<AnyHttpInterceptorClient>();
272
+
273
+ for (const handlers of Object.values(this.httpHandlersByMethod)) {
274
+ for (const handler of handlers) {
275
+ interceptors.add(handler.interceptor);
276
+ }
277
+ }
278
+
279
+ return Array.from(interceptors);
217
280
  }
218
281
  }
219
282
 
@@ -1,6 +1,5 @@
1
- import { HttpResponse, HttpMethod, HttpSchema } from '@zimic/http';
2
- import excludeURLParams from '@zimic/utils/url/excludeURLParams';
3
- import validateURLPathParams from '@zimic/utils/url/validateURLPathParams';
1
+ import { HttpMethod, HttpSchema } from '@zimic/http';
2
+ import validatePathParams from '@zimic/utils/url/validatePathParams';
4
3
 
5
4
  import { HttpHandlerCommit, InterceptorServerWebSocketSchema } from '@/server/types/schema';
6
5
  import { importCrypto } from '@/utils/crypto';
@@ -14,15 +13,17 @@ import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHt
14
13
  import HttpInterceptorClient, { AnyHttpInterceptorClient } from '../interceptor/HttpInterceptorClient';
15
14
  import { HttpInterceptorPlatform } from '../interceptor/types/options';
16
15
  import HttpInterceptorWorker from './HttpInterceptorWorker';
17
- import { MSWHttpResponseFactory, MSWHttpResponseFactoryContext } from './types/msw';
16
+ import { HttpResponseFactoryContext } from './types/http';
17
+ import { MSWHttpResponseFactory } from './types/msw';
18
18
  import { RemoteHttpInterceptorWorkerOptions } from './types/options';
19
19
 
20
20
  interface HttpHandler {
21
21
  id: string;
22
- url: { base: string; full: string };
22
+ baseURL: string;
23
23
  method: HttpMethod;
24
+ path: string;
24
25
  interceptor: AnyHttpInterceptorClient;
25
- createResponse: (context: MSWHttpResponseFactoryContext) => Promise<HttpResponse | null>;
26
+ createResponse: (context: HttpResponseFactoryContext) => Promise<Response | null>;
26
27
  }
27
28
 
28
29
  class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
@@ -132,26 +133,22 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
132
133
  async use<Schema extends HttpSchema>(
133
134
  interceptor: HttpInterceptorClient<Schema>,
134
135
  method: HttpMethod,
135
- rawURL: string | URL,
136
+ path: string,
136
137
  createResponse: MSWHttpResponseFactory,
137
138
  ) {
138
139
  if (!this.isRunning) {
139
140
  throw new NotRunningHttpInterceptorError();
140
141
  }
141
142
 
142
- const crypto = await importCrypto();
143
+ validatePathParams(path);
143
144
 
144
- const url = new URL(rawURL);
145
- excludeURLParams(url);
146
- validateURLPathParams(url);
145
+ const crypto = await importCrypto();
147
146
 
148
147
  const handler: HttpHandler = {
149
148
  id: crypto.randomUUID(),
150
- url: {
151
- base: interceptor.baseURLAsString,
152
- full: url.toString(),
153
- },
149
+ baseURL: interceptor.baseURLAsString,
154
150
  method,
151
+ path,
155
152
  interceptor,
156
153
  async createResponse(context) {
157
154
  const response = await createResponse(context);
@@ -163,8 +160,9 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
163
160
 
164
161
  await this.webSocketClient.request('interceptors/workers/commit', {
165
162
  id: handler.id,
166
- url: handler.url,
167
- method,
163
+ baseURL: handler.baseURL,
164
+ method: handler.method,
165
+ path: handler.path,
168
166
  });
169
167
  }
170
168
 
@@ -194,8 +192,9 @@ class RemoteHttpInterceptorWorker extends HttpInterceptorWorker {
194
192
  if (this.webSocketClient.isRunning) {
195
193
  const groupsToRecommit = Array.from<HttpHandler, HttpHandlerCommit>(this.httpHandlers.values(), (handler) => ({
196
194
  id: handler.id,
197
- url: handler.url,
195
+ baseURL: handler.baseURL,
198
196
  method: handler.method,
197
+ path: handler.path,
199
198
  }));
200
199
 
201
200
  await this.webSocketClient.request('interceptors/workers/reset', groupsToRecommit);
@@ -0,0 +1,5 @@
1
+ import { HttpBody, HttpRequest } from '@zimic/http';
2
+
3
+ export interface HttpResponseFactoryContext<Body extends HttpBody = HttpBody> {
4
+ request: HttpRequest<Body>;
5
+ }
@@ -1,18 +1,16 @@
1
- import { HttpResponse, HttpBody, HttpRequest } from '@zimic/http';
1
+ import { HttpResponse, HttpBody } from '@zimic/http';
2
2
  import { PossiblePromise } from '@zimic/utils/types';
3
3
  import type { HttpHandler as MSWHandler } from 'msw';
4
4
  import type { SetupWorker as BrowserMSWWorker } from 'msw/browser';
5
5
  import type { SetupServer as NodeMSWWorker } from 'msw/node';
6
6
 
7
+ import { HttpResponseFactoryContext } from './http';
8
+
7
9
  export type { MSWHandler, NodeMSWWorker, BrowserMSWWorker };
8
10
 
9
11
  export type MSWWorker = NodeMSWWorker | BrowserMSWWorker;
10
12
 
11
- export interface MSWHttpResponseFactoryContext<Body extends HttpBody = HttpBody> {
12
- request: HttpRequest<Body>;
13
- }
14
-
15
13
  export type MSWHttpResponseFactory<
16
14
  RequestBody extends HttpBody = HttpBody,
17
15
  ResponseBody extends HttpBody = HttpBody,
18
- > = (context: MSWHttpResponseFactoryContext<RequestBody>) => PossiblePromise<HttpResponse<ResponseBody> | null>;
16
+ > = (context: HttpResponseFactoryContext<RequestBody>) => PossiblePromise<HttpResponse<ResponseBody> | null>;
@@ -1,6 +1,6 @@
1
1
  import { normalizeNodeRequest, sendNodeResponse } from '@whatwg-node/server';
2
2
  import { HttpRequest, HttpMethod } from '@zimic/http';
3
- import createRegExpFromURL from '@zimic/utils/url/createRegExpFromURL';
3
+ import createParametrizedPathPattern from '@zimic/utils/url/createParametrizedPathPattern';
4
4
  import excludeURLParams from '@zimic/utils/url/excludeURLParams';
5
5
  import { createServer, Server as HttpServer, IncomingMessage, ServerResponse } from 'http';
6
6
  import type { WebSocket as Socket } from 'isomorphic-ws';
@@ -29,10 +29,8 @@ import { getFetchAPI } from './utils/fetch';
29
29
 
30
30
  interface HttpHandler {
31
31
  id: string;
32
- url: {
33
- base: string;
34
- fullRegex: RegExp;
35
- };
32
+ baseURL: string;
33
+ pathPattern: RegExp;
36
34
  socket: Socket;
37
35
  }
38
36
 
@@ -45,7 +43,7 @@ class InterceptorServer implements PublicInterceptorServer {
45
43
  logUnhandledRequests: boolean;
46
44
  tokensDirectory?: string;
47
45
 
48
- private httpHandlerGroups: {
46
+ private httpHandlersByMethod: {
49
47
  [Method in HttpMethod]: HttpHandler[];
50
48
  } = {
51
49
  GET: [],
@@ -218,18 +216,13 @@ class InterceptorServer implements PublicInterceptorServer {
218
216
  return {};
219
217
  };
220
218
 
221
- private registerHttpHandler({ id, url, method }: HttpHandlerCommit, socket: Socket) {
222
- const handlerGroups = this.httpHandlerGroups[method];
223
-
224
- const fullURL = new URL(url.full);
225
- excludeURLParams(fullURL);
219
+ private registerHttpHandler({ id, baseURL, method, path }: HttpHandlerCommit, socket: Socket) {
220
+ const handlerGroups = this.httpHandlersByMethod[method];
226
221
 
227
222
  handlerGroups.push({
228
223
  id,
229
- url: {
230
- base: url.base,
231
- fullRegex: createRegExpFromURL(fullURL.toString()),
232
- },
224
+ baseURL,
225
+ pathPattern: createParametrizedPathPattern(path),
233
226
  socket,
234
227
  });
235
228
  }
@@ -248,7 +241,7 @@ class InterceptorServer implements PublicInterceptorServer {
248
241
  }
249
242
 
250
243
  private removeHttpHandlersBySocket(socket: Socket) {
251
- for (const handlerGroups of Object.values(this.httpHandlerGroups)) {
244
+ for (const handlerGroups of Object.values(this.httpHandlersByMethod)) {
252
245
  const socketIndex = handlerGroups.findIndex((handlerGroup) => handlerGroup.socket === socket);
253
246
  removeArrayIndex(handlerGroups, socketIndex);
254
247
  }
@@ -319,17 +312,25 @@ class InterceptorServer implements PublicInterceptorServer {
319
312
  };
320
313
 
321
314
  private async createResponseForRequest(request: SerializedHttpRequest) {
322
- const methodHandlers = this.httpHandlerGroups[request.method as HttpMethod];
315
+ const methodHandlers = this.httpHandlersByMethod[request.method as HttpMethod];
323
316
 
324
- const requestURL = excludeURLParams(new URL(request.url)).toString();
317
+ const requestURL = excludeURLParams(new URL(request.url));
318
+ const requestURLAsString = requestURL.href === `${requestURL.origin}/` ? requestURL.origin : requestURL.href;
325
319
 
326
320
  let matchedSomeInterceptor = false;
327
321
 
328
- for (let index = methodHandlers.length - 1; index >= 0; index--) {
329
- const handler = methodHandlers[index];
322
+ for (let handlerIndex = methodHandlers.length - 1; handlerIndex >= 0; handlerIndex--) {
323
+ const handler = methodHandlers[handlerIndex];
324
+ const matchesBaseURL = requestURLAsString.startsWith(handler.baseURL);
325
+
326
+ if (!matchesBaseURL) {
327
+ continue;
328
+ }
329
+
330
+ const requestPath = requestURLAsString.replace(handler.baseURL, '');
331
+ const matchesPath = handler.pathPattern.test(requestPath);
330
332
 
331
- const matchesHandlerURL = handler.url.fullRegex.test(requestURL);
332
- if (!matchesHandlerURL) {
333
+ if (!matchesPath) {
333
334
  continue;
334
335
  }
335
336
 
@@ -403,12 +404,9 @@ class InterceptorServer implements PublicInterceptorServer {
403
404
  }
404
405
 
405
406
  private findHttpHandlerByRequestBaseURL(request: HttpRequest) {
406
- const methodHandlers = this.httpHandlerGroups[request.method as HttpMethod];
407
-
408
- const handler = methodHandlers.findLast((handler) => {
409
- return request.url.startsWith(handler.url.base);
410
- });
407
+ const methodHandlers = this.httpHandlersByMethod[request.method as HttpMethod];
411
408
 
409
+ const handler = methodHandlers.findLast((handler) => request.url.startsWith(handler.baseURL));
412
410
  return handler;
413
411
  }
414
412
  }
@@ -5,8 +5,9 @@ import { WebSocketSchema } from '@/webSocket/types';
5
5
 
6
6
  export interface HttpHandlerCommit {
7
7
  id: string;
8
- url: { base: string; full: string };
8
+ baseURL: string;
9
9
  method: HttpMethod;
10
+ path: string;
10
11
  }
11
12
 
12
13
  export type InterceptorServerWebSocketSchema = WebSocketSchema<{