@zimic/interceptor 1.5.1 → 1.5.2-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 (42) hide show
  1. package/dist/{chunk-3WCEMV5U.js → chunk-6YD4TBYJ.js} +13 -4
  2. package/dist/chunk-6YD4TBYJ.js.map +1 -0
  3. package/dist/{chunk-YQNDAJTN.mjs → chunk-MCLPYYCY.mjs} +13 -4
  4. package/dist/chunk-MCLPYYCY.mjs.map +1 -0
  5. package/dist/cli.js +16 -16
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cli.mjs +2 -2
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/http.d.ts +24 -8
  10. package/dist/http.js +368 -289
  11. package/dist/http.js.map +1 -1
  12. package/dist/http.mjs +367 -288
  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 +10 -10
  17. package/src/http/errors/HttpTimesCheckError.ts +9 -9
  18. package/src/http/index.ts +6 -1
  19. package/src/http/interceptor/HttpInterceptorImplementation.ts +74 -21
  20. package/src/http/interceptor/HttpInterceptorStore.ts +11 -13
  21. package/src/http/interceptor/LocalHttpInterceptor.ts +1 -10
  22. package/src/http/interceptor/RemoteHttpInterceptor.ts +16 -22
  23. package/src/http/interceptor/errors/UnknownHttpInterceptorPlatformError.ts +3 -1
  24. package/src/http/interceptor/factory.ts +3 -0
  25. package/src/http/interceptor/types/options.ts +1 -1
  26. package/src/http/interceptorWorker/HttpInterceptorWorker.ts +14 -3
  27. package/src/http/interceptorWorker/LocalHttpInterceptorWorker.ts +25 -93
  28. package/src/http/requestHandler/HttpRequestHandlerImplementation.ts +5 -9
  29. package/src/http/requestHandler/LocalHttpRequestHandler.ts +2 -5
  30. package/src/http/requestHandler/RemoteHttpRequestHandler.ts +17 -20
  31. package/src/http/requestHandler/types/public.ts +3 -8
  32. package/src/http/requestHandler/types/requests.ts +31 -3
  33. package/src/http/utils/logging.ts +54 -0
  34. package/src/interceptor/LocalMSWWorkerStore.ts +123 -0
  35. package/src/{http/interceptorWorker → interceptor}/types/msw.ts +1 -2
  36. package/src/server/utils/auth.ts +1 -1
  37. package/src/utils/fetch.ts +2 -2
  38. package/src/utils/logging.ts +0 -54
  39. package/src/utils/webSocket/WebSocketHandler.ts +1 -1
  40. package/dist/chunk-3WCEMV5U.js.map +0 -1
  41. package/dist/chunk-YQNDAJTN.mjs.map +0 -1
  42. /package/src/{http/interceptorWorker → interceptor}/errors/UnregisteredBrowserServiceWorkerError.ts +0 -0
@@ -1,10 +1,10 @@
1
1
  import { HttpRequest, HttpResponse, HttpMethod, HttpSchema, HttpHeadersInit, HttpBody } from '@zimic/http';
2
- import { createCachedDynamicImport } from '@zimic/utils/import';
3
2
  import { createRegexFromPath, excludeNonPathParams, validatePathParams } from '@zimic/utils/url';
4
- import { SharedOptions as MSWWorkerSharedOptions, bypass, http, passthrough } from 'msw';
3
+ import { SharedOptions as MSWWorkerSharedOptions, HttpHandler as MSWHttpHandler, bypass, http, passthrough } from 'msw';
5
4
 
5
+ import LocalMSWWorkerStore from '@/interceptor/LocalMSWWorkerStore';
6
+ import { MSWWorker } from '@/interceptor/types/msw';
6
7
  import { removeArrayIndex } from '@/utils/arrays';
7
- import { isClientSide, isServerSide } from '@/utils/environment';
8
8
 
9
9
  import NotRunningHttpInterceptorError from '../interceptor/errors/NotRunningHttpInterceptorError';
10
10
  import UnknownHttpInterceptorPlatformError from '../interceptor/errors/UnknownHttpInterceptorPlatformError';
@@ -12,15 +12,10 @@ import HttpInterceptorImplementation, {
12
12
  AnyHttpInterceptorImplementation,
13
13
  } from '../interceptor/HttpInterceptorImplementation';
14
14
  import { UnhandledRequestStrategy } from '../interceptor/types/options';
15
- import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError';
16
15
  import HttpInterceptorWorker from './HttpInterceptorWorker';
17
16
  import { HttpResponseFactory, HttpResponseFactoryContext } from './types/http';
18
- import { BrowserMSWWorker, MSWHttpHandler, MSWWorker, NodeMSWWorker } from './types/msw';
19
17
  import { LocalHttpInterceptorWorkerOptions } from './types/options';
20
18
 
21
- const importMSWNode = createCachedDynamicImport(() => import('msw/node'));
22
- const importMSWBrowser = createCachedDynamicImport(() => import('msw/browser'));
23
-
24
19
  interface HttpHandler {
25
20
  baseURL: string;
26
21
  method: HttpMethod;
@@ -30,11 +25,6 @@ interface HttpHandler {
30
25
  }
31
26
 
32
27
  class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
33
- // Re-creating MSW workers may cause issues, so we should keep a single worker instance, even if all interceptor
34
- // workers are stopped. See https://github.com/mswjs/msw/issues/2597.
35
- private static mswWorker?: MSWWorker;
36
- static isMSWWorkerRunning = false;
37
-
38
28
  private mswHttpHandler?: MSWHttpHandler;
39
29
 
40
30
  private httpHandlersByMethod: {
@@ -49,14 +39,12 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
49
39
  OPTIONS: [],
50
40
  };
51
41
 
42
+ private store = new LocalMSWWorkerStore();
43
+
52
44
  constructor(_options: LocalHttpInterceptorWorkerOptions) {
53
45
  super();
54
46
  }
55
47
 
56
- get class() {
57
- return LocalHttpInterceptorWorker;
58
- }
59
-
60
48
  get type() {
61
49
  return 'local' as const;
62
50
  }
@@ -64,44 +52,20 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
64
52
  get mswWorkerOrThrow() {
65
53
  /* istanbul ignore if -- @preserve
66
54
  * Trying to access the internal worker when it does not exist should not happen. */
67
- if (!this.class.mswWorker) {
55
+ if (!this.store.mswWorker) {
68
56
  throw new NotRunningHttpInterceptorError();
69
57
  }
70
- return this.class.mswWorker;
58
+ return this.store.mswWorker;
71
59
  }
72
60
 
73
61
  async getMSWWorkerOrCreate() {
74
- this.class.mswWorker ??= await this.createMSWWorker();
75
- return this.class.mswWorker;
76
- }
77
-
78
- private async createMSWWorker() {
79
- if (isServerSide()) {
80
- const mswNode = await importMSWNode();
81
-
82
- /* istanbul ignore else -- @preserve
83
- * We still check if we actually imported the server module in case our `isServerSide()` check returns true, but
84
- * the environment actually resolves the browser module. */
85
- if ('setupServer' in mswNode) {
86
- return mswNode.setupServer();
87
- }
88
- }
89
-
90
- /* istanbul ignore else -- @preserve */
91
- if (isClientSide()) {
92
- const mswBrowser = await importMSWBrowser();
93
-
94
- /* istanbul ignore else -- @preserve
95
- * We still check if we actually imported the browser module in case our `isClientSide()` check returns true, but
96
- * the environment actually resolves the server module. */
97
- if ('setupWorker' in mswBrowser) {
98
- return mswBrowser.setupWorker();
99
- }
100
- }
101
-
102
- /* istanbul ignore next -- @preserve
103
- * Ignoring because checking unknown platforms is not configured in our test setup. */
104
- throw new UnknownHttpInterceptorPlatformError();
62
+ return this.store.getMSWWorkerOrCreate({
63
+ createUnknownPlatformError:
64
+ // The unknown platform error is created here to prevent the store from relying on HTTP-specific resources.
65
+ /* istanbul ignore next -- @preserve
66
+ * Unknown runtime platforms are not part of the package test matrix. */
67
+ () => new UnknownHttpInterceptorPlatformError(),
68
+ });
105
69
  }
106
70
 
107
71
  async start() {
@@ -122,45 +86,23 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
122
86
 
123
87
  if (this.isInternalBrowserWorker(mswWorker)) {
124
88
  this.platform = 'browser';
125
-
126
- // Even after https://github.com/mswjs/msw/issues/2714 was fixed, there are collateral effects preventing us
127
- // from restarting the global browser worker even if unused. Restarting it causes interception issues in MSW.
128
- if (!this.class.isMSWWorkerRunning) {
129
- await this.startInBrowser(mswWorker, sharedOptions);
130
- this.class.isMSWWorkerRunning = true;
131
- }
132
89
  } else {
133
90
  this.platform = 'node';
134
- this.startInNode(mswWorker, sharedOptions);
135
- this.class.isMSWWorkerRunning = true;
91
+ }
92
+
93
+ try {
94
+ await this.store.startMSWWorker(mswWorker, sharedOptions);
95
+ } catch (error) {
96
+ const newMSWHandlers = mswWorker.listHandlers().filter((handler) => handler !== this.mswHttpHandler);
97
+ mswWorker.resetHandlers(...newMSWHandlers);
98
+ this.mswHttpHandler = undefined;
99
+ throw error;
136
100
  }
137
101
 
138
102
  this.isRunning = true;
139
103
  });
140
104
  }
141
105
 
142
- private async startInBrowser(mswWorker: BrowserMSWWorker, sharedOptions: MSWWorkerSharedOptions) {
143
- try {
144
- await mswWorker.start({ ...sharedOptions, quiet: true });
145
- } catch (error) {
146
- this.handleBrowserWorkerStartError(error);
147
- }
148
- }
149
-
150
- private handleBrowserWorkerStartError(error: unknown) {
151
- /* istanbul ignore else -- @preserve
152
- * Since we start the internal worker once and do not stop it, tests may not be able exercise this branch. */
153
- if (UnregisteredBrowserServiceWorkerError.matchesRawError(error)) {
154
- throw new UnregisteredBrowserServiceWorkerError();
155
- } else {
156
- throw error;
157
- }
158
- }
159
-
160
- private startInNode(mswWorker: NodeMSWWorker, sharedOptions: MSWWorkerSharedOptions) {
161
- mswWorker.listen(sharedOptions);
162
- }
163
-
164
106
  async stop() {
165
107
  await super.sharedStop(async () => {
166
108
  const mswWorker = await this.getMSWWorkerOrCreate();
@@ -170,25 +112,15 @@ class LocalHttpInterceptorWorker extends HttpInterceptorWorker {
170
112
  const newMSWHandlers = mswWorker.listHandlers().filter((handler) => handler !== this.mswHttpHandler);
171
113
  mswWorker.resetHandlers(...newMSWHandlers);
172
114
 
173
- if (this.isInternalBrowserWorker(mswWorker)) {
174
- // Even after https://github.com/mswjs/msw/issues/2714 was fixed, restarting browser workers causes interception
175
- // issues, so we keep it running and just remove our handlers.
176
- } else {
177
- this.stopInNode(mswWorker);
178
- this.class.isMSWWorkerRunning = false;
179
- }
115
+ this.store.stopMSWWorker(mswWorker);
180
116
 
181
117
  this.mswHttpHandler = undefined;
182
118
  this.isRunning = false;
183
119
  });
184
120
  }
185
121
 
186
- private stopInNode(mswWorker: NodeMSWWorker) {
187
- mswWorker.close();
188
- }
189
-
190
122
  private isInternalBrowserWorker(worker: MSWWorker) {
191
- return 'start' in worker && 'stop' in worker;
123
+ return this.store.isInternalBrowserWorker(worker);
192
124
  }
193
125
 
194
126
  hasInternalBrowserWorker() {
@@ -24,10 +24,10 @@ import { InternalHttpRequestHandler } from './types/public';
24
24
  import {
25
25
  HttpInterceptorRequest,
26
26
  HttpInterceptorResponse,
27
- HttpRequestHandlerResponseDeclaration,
28
- HttpRequestHandlerResponseDeclarationFactory,
27
+ HttpRequestHandlerResponseComputedDeclaration,
29
28
  HttpRequestHandlerResponseDelayFactory,
30
29
  InterceptedHttpInterceptorRequest,
30
+ HttpRequestHandlerResponseDeclarationInput,
31
31
  } from './types/requests';
32
32
  import {
33
33
  HttpRequestHandlerRestriction,
@@ -70,7 +70,7 @@ class HttpRequestHandlerImplementation<
70
70
  StatusCode
71
71
  >[] = [];
72
72
 
73
- private createResponseDeclaration?: HttpRequestHandlerResponseDeclarationFactory<
73
+ private createResponseDeclaration?: HttpRequestHandlerResponseComputedDeclaration<
74
74
  Path,
75
75
  Default<Schema[Path][Method]>,
76
76
  StatusCode
@@ -113,9 +113,7 @@ class HttpRequestHandlerImplementation<
113
113
  }
114
114
 
115
115
  respond<NewStatusCode extends HttpStatusCode>(
116
- declaration:
117
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>, NewStatusCode>
118
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>, NewStatusCode>,
116
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>, NewStatusCode>,
119
117
  ): HttpRequestHandlerImplementation<Schema, Method, Path, NewStatusCode> {
120
118
  const newThis = this as unknown as HttpRequestHandlerImplementation<Schema, Method, Path, NewStatusCode>;
121
119
 
@@ -134,9 +132,7 @@ class HttpRequestHandlerImplementation<
134
132
  }
135
133
 
136
134
  private isResponseDeclarationFactory(
137
- declaration:
138
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>>
139
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>>,
135
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>>,
140
136
  ) {
141
137
  return typeof declaration === 'function';
142
138
  }
@@ -7,8 +7,7 @@ import { InternalHttpRequestHandler } from './types/public';
7
7
  import {
8
8
  HttpInterceptorRequest,
9
9
  HttpInterceptorResponse,
10
- HttpRequestHandlerResponseDeclaration,
11
- HttpRequestHandlerResponseDeclarationFactory,
10
+ HttpRequestHandlerResponseDeclarationInput,
12
11
  HttpRequestHandlerResponseDelayFactory,
13
12
  InterceptedHttpInterceptorRequest,
14
13
  } from './types/requests';
@@ -50,9 +49,7 @@ class LocalHttpRequestHandler<
50
49
  }
51
50
 
52
51
  respond<NewStatusCode extends HttpStatusCode>(
53
- declaration:
54
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>, NewStatusCode>
55
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>, NewStatusCode>,
52
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>, NewStatusCode>,
56
53
  ): LocalHttpRequestHandler<Schema, Method, Path, NewStatusCode> {
57
54
  this.implementation.respond(declaration);
58
55
 
@@ -10,14 +10,13 @@ import {
10
10
  import {
11
11
  HttpInterceptorRequest,
12
12
  HttpInterceptorResponse,
13
- HttpRequestHandlerResponseDeclaration,
14
- HttpRequestHandlerResponseDeclarationFactory,
13
+ HttpRequestHandlerResponseDeclarationInput,
15
14
  HttpRequestHandlerResponseDelayFactory,
16
15
  InterceptedHttpInterceptorRequest,
17
16
  } from './types/requests';
18
17
  import { HttpRequestHandlerRestriction } from './types/restrictions';
19
18
 
20
- const UNSYNCED_PROPERTIES = new Set<string | symbol>(['then'] satisfies (keyof Promise<unknown>)[]);
19
+ const PENDING_PROPERTIES = new Set<string | symbol>(['then'] satisfies (keyof Promise<unknown>)[]);
21
20
 
22
21
  class RemoteHttpRequestHandler<
23
22
  Schema extends HttpSchema,
@@ -31,7 +30,7 @@ class RemoteHttpRequestHandler<
31
30
 
32
31
  private syncPromises: Promise<unknown>[] = [];
33
32
 
34
- private unsynced: this;
33
+ private pending: this;
35
34
  private synced: this;
36
35
 
37
36
  constructor(
@@ -40,7 +39,7 @@ class RemoteHttpRequestHandler<
40
39
  path: Path,
41
40
  ) {
42
41
  this.implementation = new HttpRequestHandlerImplementation(interceptor, method, path, this);
43
- this.unsynced = this;
42
+ this.pending = this;
44
43
  this.synced = this.createSyncedProxy();
45
44
  }
46
45
 
@@ -63,7 +62,7 @@ class RemoteHttpRequestHandler<
63
62
  }
64
63
 
65
64
  private shouldBeHiddenPropertyWhenSynced(property: string | symbol) {
66
- return UNSYNCED_PROPERTIES.has(property);
65
+ return PENDING_PROPERTIES.has(property);
67
66
  }
68
67
 
69
68
  get method() {
@@ -74,30 +73,28 @@ class RemoteHttpRequestHandler<
74
73
  return this.implementation.path;
75
74
  }
76
75
 
77
- with(restriction: HttpRequestHandlerRestriction<Schema, Method, Path>): this {
76
+ with(restriction: HttpRequestHandlerRestriction<Schema, Method, Path>) {
78
77
  this.implementation.with(restriction);
79
- return this.unsynced;
78
+ return this.pending;
80
79
  }
81
80
 
82
81
  delay(
83
82
  minMilliseconds: number | HttpRequestHandlerResponseDelayFactory<Path, Default<Schema[Path][Method]>>,
84
83
  maxMilliseconds?: number,
85
- ): this {
84
+ ) {
86
85
  this.implementation.delay(minMilliseconds, maxMilliseconds);
87
- return this.unsynced;
86
+ return this.pending;
88
87
  }
89
88
 
90
89
  respond<NewStatusCode extends HttpStatusCode>(
91
- declaration:
92
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>, NewStatusCode>
93
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>, NewStatusCode>,
90
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>, NewStatusCode>,
94
91
  ): RemoteHttpRequestHandler<Schema, Method, Path, NewStatusCode> {
95
- const newUnsyncedThis = this.unsynced as unknown as RemoteHttpRequestHandler<Schema, Method, Path, NewStatusCode>;
96
- newUnsyncedThis.implementation.respond(declaration);
97
- return newUnsyncedThis;
92
+ const pending = this.pending as unknown as RemoteHttpRequestHandler<Schema, Method, Path, NewStatusCode>;
93
+ pending.implementation.respond(declaration);
94
+ return pending;
98
95
  }
99
96
 
100
- times(minNumberOfRequests: number, maxNumberOfRequests?: number): this {
97
+ times(minNumberOfRequests: number, maxNumberOfRequests?: number) {
101
98
  this.implementation.times(minNumberOfRequests, maxNumberOfRequests);
102
99
  return this;
103
100
  }
@@ -113,9 +110,9 @@ class RemoteHttpRequestHandler<
113
110
  });
114
111
  }
115
112
 
116
- clear(): this {
113
+ clear() {
117
114
  this.implementation.clear();
118
- return this.unsynced;
115
+ return this.pending;
119
116
  }
120
117
 
121
118
  get requests(): readonly InterceptedHttpInterceptorRequest<Path, Default<Schema[Path][Method]>, StatusCode>[] {
@@ -172,7 +169,7 @@ class RemoteHttpRequestHandler<
172
169
  .then(() => {
173
170
  this.syncPromises = this.syncPromises.filter((promise) => !promisesToWait.has(promise));
174
171
 
175
- return this.isSynced ? this.synced : this.unsynced;
172
+ return this.isSynced ? this.synced : this.pending;
176
173
  })
177
174
  .then(onFulfilled, onRejected);
178
175
  }
@@ -9,8 +9,7 @@ import { Default, PossiblePromise } from '@zimic/utils/types';
9
9
 
10
10
  import HttpRequestHandlerImplementation from '../HttpRequestHandlerImplementation';
11
11
  import {
12
- HttpRequestHandlerResponseDeclaration,
13
- HttpRequestHandlerResponseDeclarationFactory,
12
+ HttpRequestHandlerResponseDeclarationInput,
14
13
  HttpRequestHandlerResponseDelayFactory,
15
14
  InterceptedHttpInterceptorRequest,
16
15
  } from './requests';
@@ -65,9 +64,7 @@ export interface LocalHttpRequestHandler<
65
64
 
66
65
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
67
66
  respond: <NewStatusCode extends HttpResponseSchemaStatusCode<Default<Default<Schema[Path][Method]>['response']>>>(
68
- declaration:
69
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>, NewStatusCode>
70
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>, NewStatusCode>,
67
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>, NewStatusCode>,
71
68
  ) => LocalHttpRequestHandler<Schema, Method, Path, NewStatusCode>;
72
69
 
73
70
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlertimes `handler.times()` API reference} */
@@ -117,9 +114,7 @@ export interface SyncedRemoteHttpRequestHandler<
117
114
 
118
115
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
119
116
  respond: <NewStatusCode extends HttpResponseSchemaStatusCode<Default<Default<Schema[Path][Method]>['response']>>>(
120
- declaration:
121
- | HttpRequestHandlerResponseDeclaration<Default<Schema[Path][Method]>, NewStatusCode>
122
- | HttpRequestHandlerResponseDeclarationFactory<Path, Default<Schema[Path][Method]>, NewStatusCode>,
117
+ declaration: HttpRequestHandlerResponseDeclarationInput<Path, Default<Schema[Path][Method]>, NewStatusCode>,
123
118
  ) => PendingRemoteHttpRequestHandler<Schema, Method, Path, NewStatusCode>;
124
119
 
125
120
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlertimes `handler.times()` API reference} */
@@ -62,19 +62,47 @@ export interface HttpRequestHandlerActionResponseDeclaration {
62
62
  }
63
63
 
64
64
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
65
- export type HttpRequestHandlerResponseDeclaration<
65
+ export type HttpRequestHandlerResponseStaticDeclaration<
66
66
  MethodSchema extends HttpMethodSchema = HttpMethodSchema,
67
67
  StatusCode extends HttpStatusCode = HttpStatusCode,
68
68
  > = HttpRequestHandlerStatusResponseDeclaration<MethodSchema, StatusCode> | HttpRequestHandlerActionResponseDeclaration;
69
69
 
70
+ /**
71
+ * @deprecated Use `HttpRequestHandlerResponseStaticDeclaration` instead.
72
+ * @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference}
73
+ */
74
+ export type HttpRequestHandlerResponseDeclaration<
75
+ MethodSchema extends HttpMethodSchema = HttpMethodSchema,
76
+ StatusCode extends HttpStatusCode = HttpStatusCode,
77
+ > = HttpRequestHandlerResponseStaticDeclaration<MethodSchema, StatusCode>;
78
+
70
79
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
71
- export type HttpRequestHandlerResponseDeclarationFactory<
80
+ export type HttpRequestHandlerResponseComputedDeclaration<
72
81
  Path extends string,
73
82
  MethodSchema extends HttpMethodSchema,
74
83
  StatusCode extends HttpStatusCode = HttpStatusCode,
75
84
  > = (
76
85
  request: Omit<HttpInterceptorRequest<Path, MethodSchema>, 'response'>,
77
- ) => PossiblePromise<HttpRequestHandlerResponseDeclaration<MethodSchema, StatusCode>>;
86
+ ) => PossiblePromise<HttpRequestHandlerResponseStaticDeclaration<MethodSchema, StatusCode>>;
87
+
88
+ /**
89
+ * @deprecated Use `HttpRequestHandlerResponseComputedDeclaration` instead.
90
+ * @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference}
91
+ */
92
+ export type HttpRequestHandlerResponseDeclarationFactory<
93
+ Path extends string,
94
+ MethodSchema extends HttpMethodSchema,
95
+ StatusCode extends HttpStatusCode = HttpStatusCode,
96
+ > = HttpRequestHandlerResponseComputedDeclaration<Path, MethodSchema, StatusCode>;
97
+
98
+ /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerrespond `handler.respond()` API reference} */
99
+ export type HttpRequestHandlerResponseDeclarationInput<
100
+ Path extends string,
101
+ MethodSchema extends HttpMethodSchema,
102
+ StatusCode extends HttpStatusCode = HttpStatusCode,
103
+ > =
104
+ | HttpRequestHandlerResponseStaticDeclaration<MethodSchema, StatusCode>
105
+ | HttpRequestHandlerResponseComputedDeclaration<Path, MethodSchema, StatusCode>;
78
106
 
79
107
  /** @see {@link https://zimic.dev/docs/interceptor/api/http-request-handler#handlerdelay `handler.delay()` API reference} */
80
108
  export type HttpRequestHandlerResponseDelayFactory<Path extends string, MethodSchema extends HttpMethodSchema> = (
@@ -0,0 +1,54 @@
1
+ import { HttpFormData, HttpHeaders, HttpSearchParams } from '@zimic/http';
2
+
3
+ function stringifyHttpJSONToLog(value: unknown): string {
4
+ return JSON.stringify(
5
+ value,
6
+ (_key, value) => {
7
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
8
+ return stringifyHttpValueToLog(value, {
9
+ fallback: (value) => value as string,
10
+ });
11
+ },
12
+ 2,
13
+ )
14
+ .replace(/\n\s*/g, ' ')
15
+ .replace(/"(File { name: '.*?', type: '.*?', size: \d*? })"/g, '$1')
16
+ .replace(/"(Blob { type: '.*?', size: \d*? })"/g, '$1');
17
+ }
18
+
19
+ export function stringifyHttpValueToLog(
20
+ value: unknown,
21
+ options: {
22
+ fallback?: (value: unknown) => string;
23
+ includeClassName?: { searchParams?: boolean };
24
+ } = {},
25
+ ): string {
26
+ const { fallback = stringifyHttpJSONToLog, includeClassName } = options;
27
+
28
+ if (value === null || value === undefined || typeof value !== 'object') {
29
+ return String(value);
30
+ }
31
+
32
+ if (value instanceof HttpHeaders) {
33
+ return stringifyHttpValueToLog(value.toObject());
34
+ }
35
+
36
+ if (value instanceof HttpSearchParams) {
37
+ const prefix = (includeClassName?.searchParams ?? false) ? 'URLSearchParams ' : '';
38
+ return `${prefix}${stringifyHttpValueToLog(value.toObject())}`;
39
+ }
40
+
41
+ if (value instanceof HttpFormData) {
42
+ return `FormData ${stringifyHttpValueToLog(value.toObject())}`;
43
+ }
44
+
45
+ if (value instanceof File) {
46
+ return `File { name: '${value.name}', type: '${value.type}', size: ${value.size} }`;
47
+ }
48
+
49
+ if (value instanceof Blob) {
50
+ return `Blob { type: '${value.type}', size: ${value.size} }`;
51
+ }
52
+
53
+ return fallback(value);
54
+ }
@@ -0,0 +1,123 @@
1
+ import { createCachedDynamicImport } from '@zimic/utils/import';
2
+ import type { SharedOptions as MSWWorkerSharedOptions } from 'msw';
3
+
4
+ import { isClientSide, isServerSide } from '@/utils/environment';
5
+
6
+ import UnregisteredBrowserServiceWorkerError from './errors/UnregisteredBrowserServiceWorkerError';
7
+ import type { BrowserMSWWorker, MSWWorker } from './types/msw';
8
+
9
+ const importMSWNode = createCachedDynamicImport(() => import('msw/node'));
10
+ const importMSWBrowser = createCachedDynamicImport(() => import('msw/browser'));
11
+
12
+ class LocalMSWWorkerStore {
13
+ private static mswWorker?: MSWWorker;
14
+
15
+ private static creatingMSWWorkerPromise?: Promise<MSWWorker>;
16
+ private static startingMSWWorkerPromise?: Promise<void>;
17
+
18
+ private static numberOfRunningWorkers = 0;
19
+
20
+ static isMSWWorkerRunning = false;
21
+
22
+ private class = LocalMSWWorkerStore;
23
+
24
+ get mswWorker() {
25
+ return this.class.mswWorker;
26
+ }
27
+
28
+ async getMSWWorkerOrCreate(options: { createUnknownPlatformError: () => Error }) {
29
+ if (this.class.mswWorker) {
30
+ return this.class.mswWorker;
31
+ }
32
+
33
+ this.class.creatingMSWWorkerPromise ??= this.createMSWWorker(options)
34
+ .then((mswWorker) => {
35
+ this.class.mswWorker = mswWorker;
36
+ return mswWorker;
37
+ })
38
+ .finally(() => {
39
+ this.class.creatingMSWWorkerPromise = undefined;
40
+ });
41
+
42
+ return this.class.creatingMSWWorkerPromise;
43
+ }
44
+
45
+ private async createMSWWorker(options: { createUnknownPlatformError: () => Error }) {
46
+ if (isServerSide()) {
47
+ const mswNode = await importMSWNode();
48
+
49
+ /* istanbul ignore else -- @preserve */
50
+ if ('setupServer' in mswNode) {
51
+ return mswNode.setupServer();
52
+ }
53
+ }
54
+
55
+ /* istanbul ignore else -- @preserve */
56
+ if (isClientSide()) {
57
+ const mswBrowser = await importMSWBrowser();
58
+
59
+ /* istanbul ignore else -- @preserve */
60
+ if ('setupWorker' in mswBrowser) {
61
+ return mswBrowser.setupWorker();
62
+ }
63
+ }
64
+
65
+ /* istanbul ignore next -- @preserve */
66
+ throw options.createUnknownPlatformError();
67
+ }
68
+
69
+ async startMSWWorker(mswWorker: MSWWorker, sharedOptions: MSWWorkerSharedOptions) {
70
+ if (!this.class.isMSWWorkerRunning) {
71
+ this.class.startingMSWWorkerPromise ??= this.startMSWWorkerOnce(mswWorker, sharedOptions)
72
+ .then(() => {
73
+ this.class.isMSWWorkerRunning = true;
74
+ })
75
+ .finally(() => {
76
+ this.class.startingMSWWorkerPromise = undefined;
77
+ });
78
+
79
+ await this.class.startingMSWWorkerPromise;
80
+ }
81
+
82
+ this.class.numberOfRunningWorkers++;
83
+ }
84
+
85
+ private async startMSWWorkerOnce(mswWorker: MSWWorker, sharedOptions: MSWWorkerSharedOptions) {
86
+ if (this.isInternalBrowserWorker(mswWorker)) {
87
+ try {
88
+ await mswWorker.start({ ...sharedOptions, quiet: true });
89
+ } catch (error) {
90
+ /* istanbul ignore else -- @preserve
91
+ * Browser worker start errors other than unregistered service workers come from MSW internals. */
92
+ if (UnregisteredBrowserServiceWorkerError.matchesRawError(error)) {
93
+ throw new UnregisteredBrowserServiceWorkerError();
94
+ } else {
95
+ throw error;
96
+ }
97
+ }
98
+ } else {
99
+ mswWorker.listen(sharedOptions);
100
+ }
101
+ }
102
+
103
+ stopMSWWorker(mswWorker: MSWWorker) {
104
+ this.class.numberOfRunningWorkers = Math.max(this.class.numberOfRunningWorkers - 1, 0);
105
+
106
+ if (this.class.numberOfRunningWorkers > 0) {
107
+ return;
108
+ }
109
+
110
+ if (this.isInternalBrowserWorker(mswWorker)) {
111
+ // Browser workers are kept running because restarting them can cause interception issues in MSW.
112
+ } else {
113
+ mswWorker.close();
114
+ this.class.isMSWWorkerRunning = false;
115
+ }
116
+ }
117
+
118
+ isInternalBrowserWorker(worker: MSWWorker): worker is BrowserMSWWorker {
119
+ return 'start' in worker && 'stop' in worker;
120
+ }
121
+ }
122
+
123
+ export default LocalMSWWorkerStore;
@@ -1,7 +1,6 @@
1
- import type { HttpHandler as MSWHttpHandler } from 'msw';
2
1
  import type { SetupWorker as BrowserMSWWorker } from 'msw/browser';
3
2
  import type { SetupServer as NodeMSWWorker } from 'msw/node';
4
3
 
5
- export type { MSWHttpHandler, NodeMSWWorker, BrowserMSWWorker };
4
+ export type { NodeMSWWorker, BrowserMSWWorker };
6
5
 
7
6
  export type MSWWorker = NodeMSWWorker | BrowserMSWWorker;
@@ -162,7 +162,7 @@ export async function readInterceptorTokenFromFile(
162
162
 
163
163
  const tokenFileContentAsString = await fs.promises.readFile(tokenFilePath, { encoding: 'utf-8' });
164
164
 
165
- const validation = interceptorTokenFileContentSchema.safeParse(JSON.parse(tokenFileContentAsString) as unknown);
165
+ const validation = interceptorTokenFileContentSchema.safeParse(JSON.parse(tokenFileContentAsString));
166
166
 
167
167
  if (!validation.success) {
168
168
  throw new InvalidInterceptorTokenFileError(tokenFilePath, validation.error.message);
@@ -1,6 +1,6 @@
1
- import { JSONValue } from '@zimic/http';
1
+ import type { JSONValue } from '@zimic/utils/types';
2
2
 
3
- import { UnhandledRequestStrategy } from '@/http';
3
+ import type { UnhandledRequestStrategy } from '@/http';
4
4
  import HttpInterceptorWorker from '@/http/interceptorWorker/HttpInterceptorWorker';
5
5
 
6
6
  import { convertArrayBufferToBase64, convertBase64ToArrayBuffer } from './data';