msw 2.11.1 → 2.11.2

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 (65) hide show
  1. package/lib/browser/index.d.mts +0 -3
  2. package/lib/browser/index.d.ts +0 -3
  3. package/lib/browser/index.js +503 -294
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/index.mjs +503 -294
  6. package/lib/browser/index.mjs.map +1 -1
  7. package/lib/core/handlers/GraphQLHandler.js.map +1 -1
  8. package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
  9. package/lib/core/handlers/HttpHandler.js.map +1 -1
  10. package/lib/core/handlers/HttpHandler.mjs.map +1 -1
  11. package/lib/core/utils/internal/devUtils.js.map +1 -1
  12. package/lib/core/utils/internal/devUtils.mjs.map +1 -1
  13. package/lib/core/utils/internal/getCallFrame.js +2 -2
  14. package/lib/core/utils/internal/getCallFrame.js.map +1 -1
  15. package/lib/core/utils/internal/getCallFrame.mjs +2 -2
  16. package/lib/core/utils/internal/getCallFrame.mjs.map +1 -1
  17. package/lib/core/utils/internal/parseGraphQLRequest.js +1 -0
  18. package/lib/core/utils/internal/parseGraphQLRequest.js.map +1 -1
  19. package/lib/core/utils/internal/parseGraphQLRequest.mjs +1 -0
  20. package/lib/core/utils/internal/parseGraphQLRequest.mjs.map +1 -1
  21. package/lib/core/utils/matching/matchRequestUrl.js +1 -1
  22. package/lib/core/utils/matching/matchRequestUrl.js.map +1 -1
  23. package/lib/core/utils/matching/matchRequestUrl.mjs +1 -1
  24. package/lib/core/utils/matching/matchRequestUrl.mjs.map +1 -1
  25. package/lib/core/utils/url/cleanUrl.js +1 -1
  26. package/lib/core/utils/url/cleanUrl.js.map +1 -1
  27. package/lib/core/utils/url/cleanUrl.mjs +1 -1
  28. package/lib/core/utils/url/cleanUrl.mjs.map +1 -1
  29. package/lib/core/utils/url/isAbsoluteUrl.js +1 -1
  30. package/lib/core/utils/url/isAbsoluteUrl.js.map +1 -1
  31. package/lib/core/utils/url/isAbsoluteUrl.mjs +1 -1
  32. package/lib/core/utils/url/isAbsoluteUrl.mjs.map +1 -1
  33. package/lib/core/ws/WebSocketIndexedDBClientStore.js.map +1 -1
  34. package/lib/core/ws/WebSocketIndexedDBClientStore.mjs.map +1 -1
  35. package/lib/core/ws/utils/attachWebSocketLogger.js.map +1 -1
  36. package/lib/core/ws/utils/attachWebSocketLogger.mjs.map +1 -1
  37. package/lib/iife/index.js +2494 -2281
  38. package/lib/iife/index.js.map +1 -1
  39. package/lib/mockServiceWorker.js +16 -12
  40. package/package.json +20 -13
  41. package/src/browser/setupWorker/glossary.ts +9 -114
  42. package/src/browser/setupWorker/setupWorker.ts +82 -119
  43. package/src/browser/setupWorker/start/createRequestListener.ts +20 -24
  44. package/src/browser/setupWorker/start/createResponseListener.ts +15 -22
  45. package/src/browser/setupWorker/start/createStartHandler.ts +24 -18
  46. package/src/browser/setupWorker/start/utils/enableMocking.ts +18 -21
  47. package/src/browser/setupWorker/start/utils/printStartMessage.ts +0 -2
  48. package/src/browser/utils/checkWorkerIntegrity.ts +22 -14
  49. package/src/browser/utils/workerChannel.ts +146 -0
  50. package/src/core/handlers/GraphQLHandler.ts +0 -3
  51. package/src/core/handlers/HttpHandler.ts +0 -2
  52. package/src/core/utils/internal/devUtils.ts +0 -2
  53. package/src/core/utils/internal/getCallFrame.ts +2 -2
  54. package/src/core/utils/internal/parseGraphQLRequest.ts +1 -0
  55. package/src/core/utils/matching/matchRequestUrl.ts +2 -2
  56. package/src/core/utils/request/onUnhandledRequest.test.ts +1 -1
  57. package/src/core/utils/url/cleanUrl.ts +1 -1
  58. package/src/core/utils/url/isAbsoluteUrl.ts +1 -1
  59. package/src/core/ws/WebSocketIndexedDBClientStore.ts +0 -4
  60. package/src/core/ws/utils/attachWebSocketLogger.ts +0 -14
  61. package/src/mockServiceWorker.js +14 -10
  62. package/src/browser/setupWorker/start/createFallbackStart.ts +0 -21
  63. package/src/browser/setupWorker/start/utils/createMessageChannel.ts +0 -32
  64. package/src/browser/setupWorker/stop/createFallbackStop.ts +0 -11
  65. package/src/browser/setupWorker/stop/createStop.ts +0 -35
@@ -7,8 +7,8 @@
7
7
  * - Please do NOT modify this file.
8
8
  */
9
9
 
10
- const PACKAGE_VERSION = '2.11.1'
11
- const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
10
+ const PACKAGE_VERSION = '2.11.2'
11
+ const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
12
12
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
13
  const activeClientIds = new Set()
14
14
 
@@ -71,11 +71,6 @@ addEventListener('message', async function (event) {
71
71
  break
72
72
  }
73
73
 
74
- case 'MOCK_DEACTIVATE': {
75
- activeClientIds.delete(clientId)
76
- break
77
- }
78
-
79
74
  case 'CLIENT_CLOSED': {
80
75
  activeClientIds.delete(clientId)
81
76
 
@@ -94,6 +89,8 @@ addEventListener('message', async function (event) {
94
89
  })
95
90
 
96
91
  addEventListener('fetch', function (event) {
92
+ const requestInterceptedAt = Date.now()
93
+
97
94
  // Bypass navigation requests.
98
95
  if (event.request.mode === 'navigate') {
99
96
  return
@@ -110,23 +107,29 @@ addEventListener('fetch', function (event) {
110
107
 
111
108
  // Bypass all requests when there are no active clients.
112
109
  // Prevents the self-unregistered worked from handling requests
113
- // after it's been deleted (still remains active until the next reload).
110
+ // after it's been terminated (still remains active until the next reload).
114
111
  if (activeClientIds.size === 0) {
115
112
  return
116
113
  }
117
114
 
118
115
  const requestId = crypto.randomUUID()
119
- event.respondWith(handleRequest(event, requestId))
116
+ event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
120
117
  })
121
118
 
122
119
  /**
123
120
  * @param {FetchEvent} event
124
121
  * @param {string} requestId
122
+ * @param {number} requestInterceptedAt
125
123
  */
126
- async function handleRequest(event, requestId) {
124
+ async function handleRequest(event, requestId, requestInterceptedAt) {
127
125
  const client = await resolveMainClient(event)
128
126
  const requestCloneForEvents = event.request.clone()
129
- const response = await getResponse(event, client, requestId)
127
+ const response = await getResponse(
128
+ event,
129
+ client,
130
+ requestId,
131
+ requestInterceptedAt,
132
+ )
130
133
 
131
134
  // Send back the response clone for the "response:*" life-cycle events.
132
135
  // Ensure MSW is active and ready to handle the message, otherwise
@@ -204,7 +207,7 @@ async function resolveMainClient(event) {
204
207
  * @param {string} requestId
205
208
  * @returns {Promise<Response>}
206
209
  */
207
- async function getResponse(event, client, requestId) {
210
+ async function getResponse(event, client, requestId, requestInterceptedAt) {
208
211
  // Clone the request because it might've been already used
209
212
  // (i.e. its body has been read and sent to the client).
210
213
  const requestClone = event.request.clone()
@@ -255,6 +258,7 @@ async function getResponse(event, client, requestId) {
255
258
  type: 'REQUEST',
256
259
  payload: {
257
260
  id: requestId,
261
+ interceptedAt: requestInterceptedAt,
258
262
  ...serializedRequest,
259
263
  },
260
264
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw",
3
- "version": "2.11.1",
3
+ "version": "2.11.2",
4
4
  "description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
5
5
  "type": "commonjs",
6
6
  "main": "./lib/core/index.js",
@@ -228,6 +228,7 @@
228
228
  "outvariant": "^1.4.3",
229
229
  "path-to-regexp": "^6.3.0",
230
230
  "picocolors": "^1.1.1",
231
+ "rettime": "^0.7.0",
231
232
  "strict-event-emitter": "^0.5.1",
232
233
  "tough-cookie": "^6.0.0",
233
234
  "type-fest": "^4.26.1",
@@ -236,18 +237,22 @@
236
237
  "devDependencies": {
237
238
  "@commitlint/cli": "^18.4.4",
238
239
  "@commitlint/config-conventional": "^18.4.4",
240
+ "@eslint/eslintrc": "^3.3.1",
241
+ "@eslint/js": "^9.34.0",
239
242
  "@fastify/websocket": "^8.3.1",
243
+ "@graphql-typed-document-node/core": "^3.2.0",
240
244
  "@open-draft/test-server": "^0.4.2",
241
245
  "@ossjs/release": "^0.8.1",
242
246
  "@playwright/test": "^1.48.0",
247
+ "@swc/core": "^1.13.5",
248
+ "@types/eslint__js": "^8.42.3",
243
249
  "@types/express": "^4.17.21",
244
250
  "@types/json-bigint": "^1.0.4",
245
- "@types/node": "~18.19.28",
246
- "@types/serviceworker": "^0.0.136",
247
- "@typescript-eslint/eslint-plugin": "^8.8.1",
248
- "@typescript-eslint/parser": "^8.8.1",
249
- "@web/dev-server": "^0.4.6",
250
- "axios": "^1.7.7",
251
+ "@types/node": "18.x",
252
+ "@typescript-eslint/eslint-plugin": "^8.42.0",
253
+ "@typescript-eslint/parser": "^8.42.0",
254
+ "@web/dev-server": "^0.1.38",
255
+ "axios": "^1.6.5",
251
256
  "babel-minify": "^0.5.1",
252
257
  "commitizen": "^4.3.1",
253
258
  "cross-env": "^7.0.3",
@@ -255,11 +260,12 @@
255
260
  "cz-conventional-changelog": "3.3.0",
256
261
  "esbuild": "^0.25.3",
257
262
  "esbuild-loader": "^4.2.2",
258
- "eslint": "^8.57.0",
259
- "eslint-config-prettier": "^9.1.0",
260
- "eslint-plugin-prettier": "^5.2.1",
263
+ "eslint": "^9.34.0",
264
+ "eslint-config-prettier": "^10.1.8",
265
+ "eslint-plugin-prettier": "^5.5.4",
261
266
  "express": "^5.0.0",
262
267
  "fastify": "^4.26.0",
268
+ "fs-extra": "^11.2.0",
263
269
  "fs-teardown": "^0.3.0",
264
270
  "glob": "^11.0.0",
265
271
  "jsdom": "^25.0.1",
@@ -267,20 +273,21 @@
267
273
  "knip": "^5.51.1",
268
274
  "lint-staged": "^15.2.10",
269
275
  "page-with": "^0.6.1",
270
- "prettier": "^3.4.2",
276
+ "prettier": "^3.6.2",
271
277
  "publint": "^0.3.12",
272
278
  "regenerator-runtime": "^0.14.1",
273
279
  "rimraf": "^6.0.1",
274
280
  "simple-git-hooks": "^2.9.0",
275
281
  "tsup": "^8.4.0",
276
282
  "typescript": "^5.9.2",
283
+ "typescript-eslint": "^8.42.0",
277
284
  "undici": "^6.20.0",
278
285
  "url-loader": "^4.1.1",
279
286
  "vitest": "^3.1.4",
280
287
  "vitest-environment-miniflare": "^2.14.4",
281
288
  "webpack": "^5.95.0",
282
289
  "webpack-http-server": "^0.5.0",
283
- "msw": "2.11.1"
290
+ "msw": "2.11.2"
284
291
  },
285
292
  "peerDependencies": {
286
293
  "typescript": ">= 4.8.x"
@@ -303,7 +310,7 @@
303
310
  "scripts": {
304
311
  "start": "tsup --watch",
305
312
  "clean": "rimraf ./lib",
306
- "lint": "eslint \"{cli,src}/**/*.ts\"",
313
+ "lint": "eslint \"{cli,src,test}/**/*.ts\"",
307
314
  "build": "pnpm clean && cross-env NODE_ENV=production tsup && pnpm patch:dts",
308
315
  "patch:dts": "node \"./config/scripts/patch-ts.js\"",
309
316
  "publint": "publint",
@@ -1,137 +1,30 @@
1
1
  import { Emitter } from 'strict-event-emitter'
2
+ import type { DeferredPromise } from '@open-draft/deferred-promise'
2
3
  import {
3
4
  LifeCycleEventEmitter,
4
5
  LifeCycleEventsMap,
5
6
  SharedOptions,
6
7
  } from '~/core/sharedOptions'
7
- import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
8
8
  import { RequestHandler } from '~/core/handlers/RequestHandler'
9
9
  import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors'
10
10
  import type { RequiredDeep } from '~/core/typeUtils'
11
11
  import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
12
-
13
- type RequestWithoutMethods = Omit<
14
- Request,
15
- | 'text'
16
- | 'body'
17
- | 'json'
18
- | 'blob'
19
- | 'arrayBuffer'
20
- | 'formData'
21
- | 'clone'
22
- | 'signal'
23
- | 'isHistoryNavigation'
24
- | 'isReloadNavigation'
25
- >
26
-
27
- /**
28
- * Request representation received from the worker message event.
29
- */
30
- export interface ServiceWorkerIncomingRequest extends RequestWithoutMethods {
31
- /**
32
- * Unique ID of the request generated once the request is
33
- * intercepted by the "fetch" event in the Service Worker.
34
- */
35
- id: string
36
- body?: ArrayBuffer | null
37
- }
38
-
39
- type ServiceWorkerIncomingResponse = {
40
- isMockedResponse: boolean
41
- request: ServiceWorkerIncomingRequest
42
- response: Pick<
43
- Response,
44
- 'type' | 'ok' | 'status' | 'statusText' | 'body' | 'headers' | 'redirected'
45
- >
46
- }
47
-
48
- /**
49
- * Map of the events that can be received from the Service Worker.
50
- */
51
- export interface ServiceWorkerIncomingEventsMap {
52
- MOCKING_ENABLED: {
53
- client: {
54
- id: string
55
- frameType: string
56
- }
57
- }
58
- INTEGRITY_CHECK_RESPONSE: {
59
- packageVersion: string
60
- checksum: string
61
- }
62
- KEEPALIVE_RESPONSE: never
63
- REQUEST: ServiceWorkerIncomingRequest
64
- RESPONSE: ServiceWorkerIncomingResponse
65
- }
66
-
67
- /**
68
- * Map of the events that can be sent to the Service Worker
69
- * from any execution context.
70
- */
71
- type ServiceWorkerOutgoingEventTypes =
72
- | 'MOCK_ACTIVATE'
73
- | 'MOCK_DEACTIVATE'
74
- | 'INTEGRITY_CHECK_REQUEST'
75
- | 'KEEPALIVE_REQUEST'
76
- | 'CLIENT_CLOSED'
12
+ import type { WorkerChannel } from '../utils/workerChannel'
77
13
 
78
14
  export interface StringifiedResponse extends ResponseInit {
79
15
  body: string | ArrayBuffer | ReadableStream<Uint8Array> | null
80
16
  }
81
17
 
82
- interface StrictEventListener<EventType extends Event> {
83
- (event: EventType): void
84
- }
85
-
86
- export interface SetupWorkerInternalContext {
18
+ export type SetupWorkerInternalContext = {
87
19
  isMockingEnabled: boolean
20
+ workerStoppedAt?: number
88
21
  startOptions: RequiredDeep<StartOptions>
89
- worker: ServiceWorker | null
90
- registration: ServiceWorkerRegistration | null
22
+ workerPromise: DeferredPromise<ServiceWorker>
23
+ registration: ServiceWorkerRegistration | undefined
91
24
  getRequestHandlers: () => Array<RequestHandler | WebSocketHandler>
92
25
  emitter: Emitter<LifeCycleEventsMap>
93
26
  keepAliveInterval?: number
94
- workerChannel: {
95
- /**
96
- * Adds a Service Worker event listener.
97
- */
98
- on<EventType extends keyof ServiceWorkerIncomingEventsMap>(
99
- eventType: EventType,
100
- callback: (
101
- event: MessageEvent,
102
- message: ServiceWorkerMessage<
103
- EventType,
104
- ServiceWorkerIncomingEventsMap[EventType]
105
- >,
106
- ) => void,
107
- ): void
108
- send<EventType extends ServiceWorkerOutgoingEventTypes>(
109
- eventType: EventType,
110
- ): void
111
- }
112
- events: {
113
- /**
114
- * Adds an event listener on the given target.
115
- * Returns a clean-up function that removes that listener.
116
- */
117
- addListener<EventType extends Event>(
118
- target: EventTarget,
119
- eventType: string,
120
- callback: StrictEventListener<EventType>,
121
- ): () => void
122
- /**
123
- * Removes all currently attached listeners.
124
- */
125
- removeAllListeners(): void
126
- /**
127
- * Awaits a given message type from the Service Worker.
128
- */
129
- once<EventType extends keyof ServiceWorkerIncomingEventsMap>(
130
- eventType: EventType,
131
- ): Promise<
132
- ServiceWorkerMessage<EventType, ServiceWorkerIncomingEventsMap[EventType]>
133
- >
134
- }
27
+ workerChannel: WorkerChannel
135
28
  supports: {
136
29
  serviceWorkerApi: boolean
137
30
  readableStreamTransfer: boolean
@@ -184,10 +77,12 @@ export interface StartOptions extends SharedOptions {
184
77
  }
185
78
 
186
79
  export type StartReturnType = Promise<ServiceWorkerRegistration | undefined>
80
+
187
81
  export type StartHandler = (
188
82
  options: RequiredDeep<StartOptions>,
189
83
  initialOptions: StartOptions,
190
84
  ) => StartReturnType
85
+
191
86
  export type StopHandler = () => void
192
87
 
193
88
  export interface SetupWorker {
@@ -1,21 +1,14 @@
1
1
  import { invariant } from 'outvariant'
2
2
  import { isNodeProcess } from 'is-node-process'
3
- import {
3
+ import type {
4
4
  SetupWorkerInternalContext,
5
- ServiceWorkerIncomingEventsMap,
6
5
  StartReturnType,
7
- StopHandler,
8
- StartHandler,
9
6
  StartOptions,
10
7
  SetupWorker,
11
8
  } from './glossary'
12
- import { createStartHandler } from './start/createStartHandler'
13
- import { createStop } from './stop/createStop'
14
- import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
15
9
  import { RequestHandler } from '~/core/handlers/RequestHandler'
16
10
  import { DEFAULT_START_OPTIONS } from './start/utils/prepareStartHandler'
17
- import { createFallbackStart } from './start/createFallbackStart'
18
- import { createFallbackStop } from './stop/createFallbackStop'
11
+ import { createStartHandler } from './start/createStartHandler'
19
12
  import { devUtils } from '~/core/utils/internal/devUtils'
20
13
  import { SetupApi } from '~/core/SetupApi'
21
14
  import { mergeRight } from '~/core/utils/internal/mergeRight'
@@ -25,21 +18,17 @@ import { supportsReadableStreamTransfer } from '../utils/supportsReadableStreamT
25
18
  import { webSocketInterceptor } from '~/core/ws/webSocketInterceptor'
26
19
  import { handleWebSocketEvent } from '~/core/ws/handleWebSocketEvent'
27
20
  import { attachWebSocketLogger } from '~/core/ws/utils/attachWebSocketLogger'
28
-
29
- interface Listener {
30
- target: EventTarget
31
- eventType: string
32
- callback: EventListenerOrEventListenerObject
33
- }
21
+ import { WorkerChannel } from '../utils/workerChannel'
22
+ import { DeferredPromise } from '@open-draft/deferred-promise'
23
+ import { createFallbackRequestListener } from './start/createFallbackRequestListener'
24
+ import { printStartMessage } from './start/utils/printStartMessage'
25
+ import { printStopMessage } from './stop/utils/printStopMessage'
34
26
 
35
27
  export class SetupWorkerApi
36
28
  extends SetupApi<LifeCycleEventsMap>
37
29
  implements SetupWorker
38
30
  {
39
31
  private context: SetupWorkerInternalContext
40
- private startHandler: StartHandler = null as any
41
- private stopHandler: StopHandler = null as any
42
- private listeners: Array<Listener>
43
32
 
44
33
  constructor(...handlers: Array<RequestHandler | WebSocketHandler>) {
45
34
  super(...handlers)
@@ -51,129 +40,53 @@ export class SetupWorkerApi
51
40
  ),
52
41
  )
53
42
 
54
- this.listeners = []
55
43
  this.context = this.createWorkerContext()
56
44
  }
57
45
 
58
46
  private createWorkerContext(): SetupWorkerInternalContext {
59
- const context: SetupWorkerInternalContext = {
47
+ const workerPromise = new DeferredPromise<ServiceWorker>()
48
+
49
+ return {
60
50
  // Mocking is not considered enabled until the worker
61
51
  // signals back the successful activation event.
62
52
  isMockingEnabled: false,
63
53
  startOptions: null as any,
64
- worker: null,
54
+ workerPromise,
55
+ registration: undefined,
65
56
  getRequestHandlers: () => {
66
57
  return this.handlersController.currentHandlers()
67
58
  },
68
- registration: null,
69
59
  emitter: this.emitter,
70
- workerChannel: {
71
- on: (eventType, callback) => {
72
- this.context.events.addListener<
73
- MessageEvent<ServiceWorkerMessage<typeof eventType, any>>
74
- >(navigator.serviceWorker, 'message', (event) => {
75
- // Avoid messages broadcasted from unrelated workers.
76
- if (event.source !== this.context.worker) {
77
- return
78
- }
79
-
80
- const message = event.data
81
-
82
- if (!message) {
83
- return
84
- }
85
-
86
- if (message.type === eventType) {
87
- callback(event, message)
88
- }
89
- })
90
- },
91
- send: (type) => {
92
- this.context.worker?.postMessage(type)
93
- },
94
- },
95
- events: {
96
- addListener: (target, eventType, callback) => {
97
- target.addEventListener(eventType, callback as EventListener)
98
- this.listeners.push({
99
- eventType,
100
- target,
101
- callback: callback as EventListener,
102
- })
103
-
104
- return () => {
105
- target.removeEventListener(eventType, callback as EventListener)
106
- }
107
- },
108
- removeAllListeners: () => {
109
- for (const { target, eventType, callback } of this.listeners) {
110
- target.removeEventListener(eventType, callback)
111
- }
112
- this.listeners = []
113
- },
114
- once: (eventType) => {
115
- const bindings: Array<() => void> = []
116
-
117
- return new Promise<
118
- ServiceWorkerMessage<
119
- typeof eventType,
120
- ServiceWorkerIncomingEventsMap[typeof eventType]
121
- >
122
- >((resolve, reject) => {
123
- const handleIncomingMessage = (event: MessageEvent) => {
124
- try {
125
- const message = event.data
126
-
127
- if (message.type === eventType) {
128
- resolve(message)
129
- }
130
- } catch (error) {
131
- reject(error)
132
- }
133
- }
134
-
135
- bindings.push(
136
- this.context.events.addListener(
137
- navigator.serviceWorker,
138
- 'message',
139
- handleIncomingMessage,
140
- ),
141
- this.context.events.addListener(
142
- navigator.serviceWorker,
143
- 'messageerror',
144
- reject,
145
- ),
146
- )
147
- }).finally(() => {
148
- bindings.forEach((unbind) => unbind())
149
- })
150
- },
151
- },
60
+ workerChannel: new WorkerChannel({
61
+ worker: workerPromise,
62
+ }),
152
63
  supports: {
153
64
  serviceWorkerApi:
154
- !('serviceWorker' in navigator) || location.protocol === 'file:',
65
+ 'serviceWorker' in navigator && location.protocol !== 'file:',
155
66
  readableStreamTransfer: supportsReadableStreamTransfer(),
156
67
  },
157
68
  }
158
-
159
- this.startHandler = context.supports.serviceWorkerApi
160
- ? createFallbackStart(context)
161
- : createStartHandler(context)
162
-
163
- this.stopHandler = context.supports.serviceWorkerApi
164
- ? createFallbackStop(context)
165
- : createStop(context)
166
-
167
- return context
168
69
  }
169
70
 
170
71
  public async start(options: StartOptions = {}): StartReturnType {
171
- if (options.waitUntilReady === true) {
72
+ if ('waitUntilReady' in options) {
172
73
  devUtils.warn(
173
74
  'The "waitUntilReady" option has been deprecated. Please remove it from this "worker.start()" call. Follow the recommended Browser integration (https://mswjs.io/docs/integrations/browser) to eliminate any race conditions between the Service Worker registration and any requests made by your application on initial render.',
174
75
  )
175
76
  }
176
77
 
78
+ // Warn the developer on multiple "worker.start()" calls.
79
+ // While this will not affect the worker in any way,
80
+ // it likely indicates an issue with the developer's code.
81
+ if (this.context.isMockingEnabled) {
82
+ devUtils.warn(
83
+ `Found a redundant "worker.start()" call. Note that starting the worker while mocking is already enabled will have no effect. Consider removing this "worker.start()" call.`,
84
+ )
85
+ return this.context.registration
86
+ }
87
+
88
+ this.context.workerStoppedAt = undefined
89
+
177
90
  this.context.startOptions = mergeRight(
178
91
  DEFAULT_START_OPTIONS,
179
92
  options,
@@ -202,14 +115,64 @@ export class SetupWorkerApi
202
115
  webSocketInterceptor.dispose()
203
116
  })
204
117
 
205
- return await this.startHandler(this.context.startOptions, options)
118
+ // Use a fallback interception algorithm in the environments
119
+ // where the Service Worker API isn't supported.
120
+ if (!this.context.supports.serviceWorkerApi) {
121
+ const fallbackInterceptor = createFallbackRequestListener(
122
+ this.context,
123
+ this.context.startOptions,
124
+ )
125
+
126
+ this.subscriptions.push(() => {
127
+ fallbackInterceptor.dispose()
128
+ })
129
+
130
+ this.context.isMockingEnabled = true
131
+
132
+ printStartMessage({
133
+ message: 'Mocking enabled (fallback mode).',
134
+ quiet: this.context.startOptions.quiet,
135
+ })
136
+
137
+ return undefined
138
+ }
139
+
140
+ const startHandler = createStartHandler(this.context)
141
+ const registration = await startHandler(this.context.startOptions, options)
142
+
143
+ this.context.isMockingEnabled = true
144
+
145
+ return registration
206
146
  }
207
147
 
208
148
  public stop(): void {
209
149
  super.dispose()
210
- this.context.events.removeAllListeners()
150
+
151
+ if (!this.context.isMockingEnabled) {
152
+ devUtils.warn(
153
+ 'Found a redundant "worker.stop()" call. Notice that stopping the worker after it has already been stopped has no effect. Consider removing this "worker.stop()" call.',
154
+ )
155
+ return
156
+ }
157
+
158
+ this.context.isMockingEnabled = false
159
+ this.context.workerStoppedAt = Date.now()
211
160
  this.context.emitter.removeAllListeners()
212
- this.stopHandler()
161
+
162
+ if (this.context.supports.serviceWorkerApi) {
163
+ this.context.workerChannel.removeAllListeners('RESPONSE')
164
+ window.clearInterval(this.context.keepAliveInterval)
165
+ }
166
+
167
+ // Post the internal stop message on the window
168
+ // to let any logic know when the worker has stopped.
169
+ // E.g. the WebSocket client manager needs this to know
170
+ // when to clear its in-memory clients list.
171
+ window.postMessage({ type: 'msw/worker:stop' })
172
+
173
+ printStopMessage({
174
+ quiet: this.context.startOptions?.quiet,
175
+ })
213
176
  }
214
177
  }
215
178
 
@@ -1,12 +1,5 @@
1
- import {
2
- StartOptions,
3
- SetupWorkerInternalContext,
4
- ServiceWorkerIncomingEventsMap,
5
- } from '../glossary'
6
- import {
7
- ServiceWorkerMessage,
8
- WorkerChannel,
9
- } from './utils/createMessageChannel'
1
+ import { Emitter } from 'rettime'
2
+ import { StartOptions, SetupWorkerInternalContext } from '../glossary'
10
3
  import { deserializeRequest } from '../../utils/deserializeRequest'
11
4
  import { RequestHandler } from '~/core/handlers/RequestHandler'
12
5
  import { handleRequest } from '~/core/utils/handleRequest'
@@ -18,18 +11,21 @@ import { isHandlerKind } from '~/core/utils/internal/isHandlerKind'
18
11
  export const createRequestListener = (
19
12
  context: SetupWorkerInternalContext,
20
13
  options: RequiredDeep<StartOptions>,
21
- ) => {
22
- return async (
23
- event: MessageEvent,
24
- message: ServiceWorkerMessage<
25
- 'REQUEST',
26
- ServiceWorkerIncomingEventsMap['REQUEST']
27
- >,
28
- ) => {
29
- const messageChannel = new WorkerChannel(event.ports[0])
14
+ ): Emitter.ListenerType<typeof context.workerChannel, 'REQUEST'> => {
15
+ return async (event) => {
16
+ // Treat any incoming requests from the worker as passthrough
17
+ // if `worker.stop()` has been called for this client.
18
+ if (
19
+ !context.isMockingEnabled &&
20
+ context.workerStoppedAt &&
21
+ event.data.interceptedAt > context.workerStoppedAt
22
+ ) {
23
+ event.postMessage('PASSTHROUGH')
24
+ return
25
+ }
30
26
 
31
- const requestId = message.payload.id
32
- const request = deserializeRequest(message.payload)
27
+ const requestId = event.data.id
28
+ const request = deserializeRequest(event.data)
33
29
  const requestCloneForLogs = request.clone()
34
30
 
35
31
  // Make this the first request clone before the
@@ -48,7 +44,7 @@ export const createRequestListener = (
48
44
  context.emitter,
49
45
  {
50
46
  onPassthroughResponse() {
51
- messageChannel.postMessage('PASSTHROUGH')
47
+ event.postMessage('PASSTHROUGH')
52
48
  },
53
49
  async onMockedResponse(response, { handler, parsedResult }) {
54
50
  // Clone the mocked response so its body could be read
@@ -65,7 +61,7 @@ export const createRequestListener = (
65
61
  if (context.supports.readableStreamTransfer) {
66
62
  const responseStreamOrNull = response.body
67
63
 
68
- messageChannel.postMessage(
64
+ event.postMessage(
69
65
  'MOCK_RESPONSE',
70
66
  {
71
67
  ...responseInit,
@@ -85,7 +81,7 @@ export const createRequestListener = (
85
81
  ? null
86
82
  : await responseClone.arrayBuffer()
87
83
 
88
- messageChannel.postMessage('MOCK_RESPONSE', {
84
+ event.postMessage('MOCK_RESPONSE', {
89
85
  ...responseInit,
90
86
  body: responseBufferOrNull,
91
87
  })
@@ -118,7 +114,7 @@ This exception has been gracefully handled as a 500 response, however, it's stro
118
114
 
119
115
  // Treat all other exceptions in a request handler as unintended,
120
116
  // alerting that there is a problem that needs fixing.
121
- messageChannel.postMessage('MOCK_RESPONSE', {
117
+ event.postMessage('MOCK_RESPONSE', {
122
118
  status: 500,
123
119
  statusText: 'Request Handler Error',
124
120
  headers: {