msw 2.5.2 → 2.6.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 (209) hide show
  1. package/lib/browser/index.d.mts +7 -6
  2. package/lib/browser/index.d.ts +7 -6
  3. package/lib/browser/index.js +28 -2
  4. package/lib/browser/index.js.map +1 -1
  5. package/lib/browser/index.mjs +28 -2
  6. package/lib/browser/index.mjs.map +1 -1
  7. package/lib/core/{GraphQLHandler-ClMB0BOy.d.mts → GraphQLHandler-B6uni-E_.d.mts} +1 -1
  8. package/lib/core/{GraphQLHandler-D6mLMXGZ.d.ts → GraphQLHandler-Cjm7JNGi.d.ts} +1 -1
  9. package/lib/core/{HttpResponse-vn-Pb4Bi.d.mts → HttpResponse-63H9vVoL.d.mts} +1 -0
  10. package/lib/core/{HttpResponse-DaYkf3ml.d.ts → HttpResponse-DzhqZzTK.d.ts} +1 -0
  11. package/lib/core/HttpResponse.d.mts +1 -1
  12. package/lib/core/HttpResponse.d.ts +1 -1
  13. package/lib/core/SetupApi.d.mts +15 -12
  14. package/lib/core/SetupApi.d.ts +15 -12
  15. package/lib/core/SetupApi.js +3 -1
  16. package/lib/core/SetupApi.js.map +1 -1
  17. package/lib/core/SetupApi.mjs +3 -1
  18. package/lib/core/SetupApi.mjs.map +1 -1
  19. package/lib/core/getResponse.d.mts +1 -1
  20. package/lib/core/getResponse.d.ts +1 -1
  21. package/lib/core/graphql.d.mts +2 -2
  22. package/lib/core/graphql.d.ts +2 -2
  23. package/lib/core/handlers/GraphQLHandler.d.mts +2 -2
  24. package/lib/core/handlers/GraphQLHandler.d.ts +2 -2
  25. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  26. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  27. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  28. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  29. package/lib/core/handlers/RequestHandler.js +2 -0
  30. package/lib/core/handlers/RequestHandler.js.map +1 -1
  31. package/lib/core/handlers/RequestHandler.mjs +2 -0
  32. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  33. package/lib/core/handlers/WebSocketHandler.d.mts +34 -0
  34. package/lib/core/handlers/WebSocketHandler.d.ts +34 -0
  35. package/lib/core/handlers/WebSocketHandler.js +122 -0
  36. package/lib/core/handlers/WebSocketHandler.js.map +1 -0
  37. package/lib/core/handlers/WebSocketHandler.mjs +104 -0
  38. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -0
  39. package/lib/core/handlers/common.d.mts +3 -0
  40. package/lib/core/handlers/common.d.ts +3 -0
  41. package/lib/core/handlers/common.js +17 -0
  42. package/lib/core/handlers/common.js.map +1 -0
  43. package/lib/core/handlers/common.mjs +1 -0
  44. package/lib/core/handlers/common.mjs.map +1 -0
  45. package/lib/core/http.d.mts +1 -1
  46. package/lib/core/http.d.ts +1 -1
  47. package/lib/core/index.d.mts +5 -2
  48. package/lib/core/index.d.ts +5 -2
  49. package/lib/core/index.js +5 -1
  50. package/lib/core/index.js.map +1 -1
  51. package/lib/core/index.mjs +7 -1
  52. package/lib/core/index.mjs.map +1 -1
  53. package/lib/core/passthrough.d.mts +1 -1
  54. package/lib/core/passthrough.d.ts +1 -1
  55. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  56. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  57. package/lib/core/utils/executeHandlers.d.mts +1 -1
  58. package/lib/core/utils/executeHandlers.d.ts +1 -1
  59. package/lib/core/utils/executeHandlers.js.map +1 -1
  60. package/lib/core/utils/executeHandlers.mjs.map +1 -1
  61. package/lib/core/utils/handleRequest.d.mts +2 -2
  62. package/lib/core/utils/handleRequest.d.ts +2 -2
  63. package/lib/core/utils/handleRequest.js.map +1 -1
  64. package/lib/core/utils/handleRequest.mjs.map +1 -1
  65. package/lib/core/utils/internal/isHandlerKind.d.mts +17 -0
  66. package/lib/core/utils/internal/isHandlerKind.d.ts +17 -0
  67. package/lib/core/utils/internal/isHandlerKind.js +29 -0
  68. package/lib/core/utils/internal/isHandlerKind.js.map +1 -0
  69. package/lib/core/utils/internal/isHandlerKind.mjs +9 -0
  70. package/lib/core/utils/internal/isHandlerKind.mjs.map +1 -0
  71. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +2 -2
  72. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +2 -2
  73. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  74. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  75. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  76. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  77. package/lib/core/utils/logging/getTimestamp.d.mts +4 -1
  78. package/lib/core/utils/logging/getTimestamp.d.ts +4 -1
  79. package/lib/core/utils/logging/getTimestamp.js +6 -2
  80. package/lib/core/utils/logging/getTimestamp.js.map +1 -1
  81. package/lib/core/utils/logging/getTimestamp.mjs +6 -2
  82. package/lib/core/utils/logging/getTimestamp.mjs.map +1 -1
  83. package/lib/core/utils/matching/matchRequestUrl.d.mts +2 -1
  84. package/lib/core/utils/matching/matchRequestUrl.d.ts +2 -1
  85. package/lib/core/utils/matching/matchRequestUrl.js +4 -0
  86. package/lib/core/utils/matching/matchRequestUrl.js.map +1 -1
  87. package/lib/core/utils/matching/matchRequestUrl.mjs +4 -0
  88. package/lib/core/utils/matching/matchRequestUrl.mjs.map +1 -1
  89. package/lib/core/ws/WebSocketClientManager.d.mts +63 -0
  90. package/lib/core/ws/WebSocketClientManager.d.ts +63 -0
  91. package/lib/core/ws/WebSocketClientManager.js +149 -0
  92. package/lib/core/ws/WebSocketClientManager.js.map +1 -0
  93. package/lib/core/ws/WebSocketClientManager.mjs +129 -0
  94. package/lib/core/ws/WebSocketClientManager.mjs.map +1 -0
  95. package/lib/core/ws/WebSocketClientStore.d.mts +13 -0
  96. package/lib/core/ws/WebSocketClientStore.d.ts +13 -0
  97. package/lib/core/ws/WebSocketClientStore.js +26 -0
  98. package/lib/core/ws/WebSocketClientStore.js.map +1 -0
  99. package/lib/core/ws/WebSocketClientStore.mjs +6 -0
  100. package/lib/core/ws/WebSocketClientStore.mjs.map +1 -0
  101. package/lib/core/ws/WebSocketIndexedDBClientStore.d.mts +15 -0
  102. package/lib/core/ws/WebSocketIndexedDBClientStore.d.ts +15 -0
  103. package/lib/core/ws/WebSocketIndexedDBClientStore.js +130 -0
  104. package/lib/core/ws/WebSocketIndexedDBClientStore.js.map +1 -0
  105. package/lib/core/ws/WebSocketIndexedDBClientStore.mjs +110 -0
  106. package/lib/core/ws/WebSocketIndexedDBClientStore.mjs.map +1 -0
  107. package/lib/core/ws/WebSocketMemoryClientStore.d.mts +13 -0
  108. package/lib/core/ws/WebSocketMemoryClientStore.d.ts +13 -0
  109. package/lib/core/ws/WebSocketMemoryClientStore.js +41 -0
  110. package/lib/core/ws/WebSocketMemoryClientStore.js.map +1 -0
  111. package/lib/core/ws/WebSocketMemoryClientStore.mjs +21 -0
  112. package/lib/core/ws/WebSocketMemoryClientStore.mjs.map +1 -0
  113. package/lib/core/ws/handleWebSocketEvent.d.mts +19 -0
  114. package/lib/core/ws/handleWebSocketEvent.d.ts +19 -0
  115. package/lib/core/ws/handleWebSocketEvent.js +74 -0
  116. package/lib/core/ws/handleWebSocketEvent.js.map +1 -0
  117. package/lib/core/ws/handleWebSocketEvent.mjs +56 -0
  118. package/lib/core/ws/handleWebSocketEvent.mjs.map +1 -0
  119. package/lib/core/ws/utils/attachWebSocketLogger.d.mts +12 -0
  120. package/lib/core/ws/utils/attachWebSocketLogger.d.ts +12 -0
  121. package/lib/core/ws/utils/attachWebSocketLogger.js +198 -0
  122. package/lib/core/ws/utils/attachWebSocketLogger.js.map +1 -0
  123. package/lib/core/ws/utils/attachWebSocketLogger.mjs +178 -0
  124. package/lib/core/ws/utils/attachWebSocketLogger.mjs.map +1 -0
  125. package/lib/core/ws/utils/getMessageLength.d.mts +11 -0
  126. package/lib/core/ws/utils/getMessageLength.d.ts +11 -0
  127. package/lib/core/ws/utils/getMessageLength.js +33 -0
  128. package/lib/core/ws/utils/getMessageLength.js.map +1 -0
  129. package/lib/core/ws/utils/getMessageLength.mjs +13 -0
  130. package/lib/core/ws/utils/getMessageLength.mjs.map +1 -0
  131. package/lib/core/ws/utils/getPublicData.d.mts +5 -0
  132. package/lib/core/ws/utils/getPublicData.d.ts +5 -0
  133. package/lib/core/ws/utils/getPublicData.js +36 -0
  134. package/lib/core/ws/utils/getPublicData.js.map +1 -0
  135. package/lib/core/ws/utils/getPublicData.mjs +16 -0
  136. package/lib/core/ws/utils/getPublicData.mjs.map +1 -0
  137. package/lib/core/ws/utils/truncateMessage.d.mts +3 -0
  138. package/lib/core/ws/utils/truncateMessage.d.ts +3 -0
  139. package/lib/core/ws/utils/truncateMessage.js +31 -0
  140. package/lib/core/ws/utils/truncateMessage.js.map +1 -0
  141. package/lib/core/ws/utils/truncateMessage.mjs +11 -0
  142. package/lib/core/ws/utils/truncateMessage.mjs.map +1 -0
  143. package/lib/core/ws/webSocketInterceptor.d.mts +5 -0
  144. package/lib/core/ws/webSocketInterceptor.d.ts +5 -0
  145. package/lib/core/ws/webSocketInterceptor.js +26 -0
  146. package/lib/core/ws/webSocketInterceptor.js.map +1 -0
  147. package/lib/core/ws/webSocketInterceptor.mjs +6 -0
  148. package/lib/core/ws/webSocketInterceptor.mjs.map +1 -0
  149. package/lib/core/ws.d.mts +75 -0
  150. package/lib/core/ws.d.ts +75 -0
  151. package/lib/core/ws.js +71 -0
  152. package/lib/core/ws.js.map +1 -0
  153. package/lib/core/ws.mjs +54 -0
  154. package/lib/core/ws.mjs.map +1 -0
  155. package/lib/iife/index.js +1420 -86
  156. package/lib/iife/index.js.map +1 -1
  157. package/lib/mockServiceWorker.js +1 -1
  158. package/lib/native/index.d.mts +6 -5
  159. package/lib/native/index.d.ts +6 -5
  160. package/lib/native/index.js +19 -4
  161. package/lib/native/index.js.map +1 -1
  162. package/lib/native/index.mjs +19 -4
  163. package/lib/native/index.mjs.map +1 -1
  164. package/lib/node/index.d.mts +8 -7
  165. package/lib/node/index.d.ts +8 -7
  166. package/lib/node/index.js +19 -4
  167. package/lib/node/index.js.map +1 -1
  168. package/lib/node/index.mjs +19 -4
  169. package/lib/node/index.mjs.map +1 -1
  170. package/package.json +10 -1
  171. package/src/browser/setupWorker/glossary.ts +10 -10
  172. package/src/browser/setupWorker/setupWorker.ts +32 -3
  173. package/src/browser/setupWorker/start/createFallbackRequestListener.ts +2 -1
  174. package/src/browser/setupWorker/start/createRequestListener.ts +2 -1
  175. package/src/browser/setupWorker/start/createStartHandler.ts +5 -0
  176. package/src/browser/setupWorker/stop/createStop.ts +6 -0
  177. package/src/core/SetupApi.ts +28 -20
  178. package/src/core/handlers/RequestHandler.ts +4 -0
  179. package/src/core/handlers/WebSocketHandler.ts +146 -0
  180. package/src/core/handlers/common.ts +1 -0
  181. package/src/core/index.ts +11 -1
  182. package/src/core/utils/executeHandlers.ts +1 -1
  183. package/src/core/utils/handleRequest.ts +1 -1
  184. package/src/core/utils/internal/isHandlerKind.test.ts +64 -0
  185. package/src/core/utils/internal/isHandlerKind.ts +21 -0
  186. package/src/core/utils/logging/getTimestamp.test.ts +20 -6
  187. package/src/core/utils/logging/getTimestamp.ts +11 -6
  188. package/src/core/utils/matching/matchRequestUrl.test.ts +44 -0
  189. package/src/core/utils/matching/matchRequestUrl.ts +4 -0
  190. package/src/core/ws/WebSocketClientManager.test.ts +164 -0
  191. package/src/core/ws/WebSocketClientManager.ts +211 -0
  192. package/src/core/ws/WebSocketClientStore.ts +14 -0
  193. package/src/core/ws/WebSocketIndexedDBClientStore.ts +145 -0
  194. package/src/core/ws/WebSocketMemoryClientStore.ts +27 -0
  195. package/src/core/ws/handleWebSocketEvent.ts +83 -0
  196. package/src/core/ws/utils/attachWebSocketLogger.ts +259 -0
  197. package/src/core/ws/utils/getMessageLength.test.ts +16 -0
  198. package/src/core/ws/utils/getMessageLength.ts +19 -0
  199. package/src/core/ws/utils/getPublicData.test.ts +38 -0
  200. package/src/core/ws/utils/getPublicData.ts +17 -0
  201. package/src/core/ws/utils/truncateMessage.test.ts +12 -0
  202. package/src/core/ws/utils/truncateMessage.ts +9 -0
  203. package/src/core/ws/webSocketInterceptor.ts +3 -0
  204. package/src/core/ws.test.ts +23 -0
  205. package/src/core/ws.ts +166 -0
  206. package/src/node/SetupServerApi.ts +8 -7
  207. package/src/node/SetupServerCommonApi.ts +25 -5
  208. package/src/node/glossary.ts +5 -7
  209. package/src/node/setupServer.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw",
3
- "version": "2.5.2",
3
+ "version": "2.6.1",
4
4
  "description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
5
5
  "main": "./lib/core/index.js",
6
6
  "module": "./lib/core/index.mjs",
@@ -58,6 +58,12 @@
58
58
  "import": "./lib/core/graphql.mjs",
59
59
  "default": "./lib/core/graphql.js"
60
60
  },
61
+ "./core/ws": {
62
+ "types": "./lib/core/ws.d.ts",
63
+ "require": "./lib/core/ws.js",
64
+ "import": "./lib/core/ws.mjs",
65
+ "default": "./lib/core/ws.js"
66
+ },
61
67
  "./mockServiceWorker.js": "./lib/mockServiceWorker.js",
62
68
  "./package.json": "./package.json"
63
69
  },
@@ -118,6 +124,7 @@
118
124
  "@bundled-es-modules/tough-cookie": "^0.1.6",
119
125
  "@inquirer/confirm": "^5.0.0",
120
126
  "@mswjs/interceptors": "^0.36.5",
127
+ "@open-draft/deferred-promise": "^2.2.0",
121
128
  "@open-draft/until": "^2.1.0",
122
129
  "@types/cookie": "^0.6.0",
123
130
  "@types/statuses": "^2.0.4",
@@ -134,6 +141,7 @@
134
141
  "devDependencies": {
135
142
  "@commitlint/cli": "^18.4.4",
136
143
  "@commitlint/config-conventional": "^18.4.4",
144
+ "@fastify/websocket": "^8.3.1",
137
145
  "@open-draft/test-server": "^0.4.2",
138
146
  "@ossjs/release": "^0.8.1",
139
147
  "@playwright/test": "^1.48.0",
@@ -157,6 +165,7 @@
157
165
  "eslint-config-prettier": "^9.1.0",
158
166
  "eslint-plugin-prettier": "^5.2.1",
159
167
  "express": "^5.0.0",
168
+ "fastify": "^4.26.0",
160
169
  "fs-extra": "^11.2.0",
161
170
  "fs-teardown": "^0.3.0",
162
171
  "glob": "^11.0.0",
@@ -5,13 +5,11 @@ import {
5
5
  SharedOptions,
6
6
  } from '~/core/sharedOptions'
7
7
  import { ServiceWorkerMessage } from './start/utils/createMessageChannel'
8
- import {
9
- RequestHandler,
10
- RequestHandlerDefaultInfo,
11
- } from '~/core/handlers/RequestHandler'
8
+ import { RequestHandler } from '~/core/handlers/RequestHandler'
12
9
  import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors'
13
- import { Path } from '~/core/utils/matching/matchRequestUrl'
14
- import { RequiredDeep } from '~/core/typeUtils'
10
+ import type { Path } from '~/core/utils/matching/matchRequestUrl'
11
+ import type { RequiredDeep } from '~/core/typeUtils'
12
+ import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
15
13
 
16
14
  export type ResolvedPath = Path | URL
17
15
 
@@ -92,7 +90,7 @@ export interface SetupWorkerInternalContext {
92
90
  startOptions: RequiredDeep<StartOptions>
93
91
  worker: ServiceWorker | null
94
92
  registration: ServiceWorkerRegistration | null
95
- getRequestHandlers(): Array<RequestHandler>
93
+ getRequestHandlers(): Array<RequestHandler | WebSocketHandler>
96
94
  requests: Map<string, Request>
97
95
  emitter: Emitter<LifeCycleEventsMap>
98
96
  keepAliveInterval?: number
@@ -216,7 +214,7 @@ export interface SetupWorker {
216
214
  *
217
215
  * @see {@link https://mswjs.io/docs/api/setup-worker/use `worker.use()` API reference}
218
216
  */
219
- use: (...handlers: RequestHandler[]) => void
217
+ use: (...handlers: Array<RequestHandler | WebSocketHandler>) => void
220
218
 
221
219
  /**
222
220
  * Marks all request handlers that respond using `res.once()` as unused.
@@ -231,14 +229,16 @@ export interface SetupWorker {
231
229
  *
232
230
  * @see {@link https://mswjs.io/docs/api/setup-worker/reset-handlers `worker.resetHandlers()` API reference}
233
231
  */
234
- resetHandlers: (...nextHandlers: RequestHandler[]) => void
232
+ resetHandlers: (
233
+ ...nextHandlers: Array<RequestHandler | WebSocketHandler>
234
+ ) => void
235
235
 
236
236
  /**
237
237
  * Returns a readonly list of currently active request handlers.
238
238
  *
239
239
  * @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()` API reference}
240
240
  */
241
- listHandlers(): ReadonlyArray<RequestHandler<RequestHandlerDefaultInfo, any>>
241
+ listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>
242
242
 
243
243
  /**
244
244
  * Life-cycle events.
@@ -18,9 +18,13 @@ import { createFallbackStop } from './stop/createFallbackStop'
18
18
  import { devUtils } from '~/core/utils/internal/devUtils'
19
19
  import { SetupApi } from '~/core/SetupApi'
20
20
  import { mergeRight } from '~/core/utils/internal/mergeRight'
21
- import { LifeCycleEventsMap } from '~/core/sharedOptions'
21
+ import type { LifeCycleEventsMap } from '~/core/sharedOptions'
22
+ import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
22
23
  import { SetupWorker } from './glossary'
23
24
  import { supportsReadableStreamTransfer } from '../utils/supportsReadableStreamTransfer'
25
+ import { webSocketInterceptor } from '~/core/ws/webSocketInterceptor'
26
+ import { handleWebSocketEvent } from '~/core/ws/handleWebSocketEvent'
27
+ import { attachWebSocketLogger } from '~/core/ws/utils/attachWebSocketLogger'
24
28
 
25
29
  interface Listener {
26
30
  target: EventTarget
@@ -37,7 +41,7 @@ export class SetupWorkerApi
37
41
  private stopHandler: StopHandler = null as any
38
42
  private listeners: Array<Listener>
39
43
 
40
- constructor(...handlers: Array<RequestHandler>) {
44
+ constructor(...handlers: Array<RequestHandler | WebSocketHandler>) {
41
45
  super(...handlers)
42
46
 
43
47
  invariant(
@@ -176,6 +180,29 @@ export class SetupWorkerApi
176
180
  options,
177
181
  ) as SetupWorkerInternalContext['startOptions']
178
182
 
183
+ // Enable the WebSocket interception.
184
+ handleWebSocketEvent({
185
+ getUnhandledRequestStrategy: () => {
186
+ return this.context.startOptions.onUnhandledRequest
187
+ },
188
+ getHandlers: () => {
189
+ return this.handlersController.currentHandlers()
190
+ },
191
+ onMockedConnection: (connection) => {
192
+ if (!this.context.startOptions.quiet) {
193
+ // Attach the logger for mocked connections since
194
+ // those won't be visible in the browser's devtools.
195
+ attachWebSocketLogger(connection)
196
+ }
197
+ },
198
+ onPassthroughConnection() {},
199
+ })
200
+ webSocketInterceptor.apply()
201
+
202
+ this.subscriptions.push(() => {
203
+ webSocketInterceptor.dispose()
204
+ })
205
+
179
206
  return await this.startHandler(this.context.startOptions, options)
180
207
  }
181
208
 
@@ -193,6 +220,8 @@ export class SetupWorkerApi
193
220
  *
194
221
  * @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker()` API reference}
195
222
  */
196
- export function setupWorker(...handlers: Array<RequestHandler>): SetupWorker {
223
+ export function setupWorker(
224
+ ...handlers: Array<RequestHandler | WebSocketHandler>
225
+ ): SetupWorker {
197
226
  return new SetupWorkerApi(...handlers)
198
227
  }
@@ -8,6 +8,7 @@ import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
8
8
  import { SetupWorkerInternalContext, StartOptions } from '../glossary'
9
9
  import type { RequiredDeep } from '~/core/typeUtils'
10
10
  import { handleRequest } from '~/core/utils/handleRequest'
11
+ import { isHandlerKind } from '~/core/utils/internal/isHandlerKind'
11
12
 
12
13
  export function createFallbackRequestListener(
13
14
  context: SetupWorkerInternalContext,
@@ -24,7 +25,7 @@ export function createFallbackRequestListener(
24
25
  const response = await handleRequest(
25
26
  request,
26
27
  requestId,
27
- context.getRequestHandlers(),
28
+ context.getRequestHandlers().filter(isHandlerKind('RequestHandler')),
28
29
  options,
29
30
  context.emitter,
30
31
  {
@@ -13,6 +13,7 @@ import { handleRequest } from '~/core/utils/handleRequest'
13
13
  import { RequiredDeep } from '~/core/typeUtils'
14
14
  import { devUtils } from '~/core/utils/internal/devUtils'
15
15
  import { toResponseInit } from '~/core/utils/toResponseInit'
16
+ import { isHandlerKind } from '~/core/utils/internal/isHandlerKind'
16
17
 
17
18
  export const createRequestListener = (
18
19
  context: SetupWorkerInternalContext,
@@ -43,7 +44,7 @@ export const createRequestListener = (
43
44
  await handleRequest(
44
45
  request,
45
46
  requestId,
46
- context.getRequestHandlers(),
47
+ context.getRequestHandlers().filter(isHandlerKind('RequestHandler')),
47
48
  options,
48
49
  context.emitter,
49
50
  {
@@ -71,6 +71,11 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
71
71
  // Make sure we're always clearing the interval - there are reports that not doing this can
72
72
  // cause memory leaks in headless browser environments.
73
73
  window.clearInterval(context.keepAliveInterval)
74
+
75
+ // Notify others about this client disconnecting.
76
+ // E.g. this will purge the in-memory WebSocket clients since
77
+ // starting the worker again will assign them new IDs.
78
+ window.postMessage({ type: 'msw/worker:stop' })
74
79
  })
75
80
 
76
81
  // Check if the active Service Worker has been generated
@@ -24,6 +24,12 @@ export const createStop = (
24
24
  context.isMockingEnabled = false
25
25
  window.clearInterval(context.keepAliveInterval)
26
26
 
27
+ // Post the internal stop message on the window
28
+ // to let any logic know when the worker has stopped.
29
+ // E.g. the WebSocket client manager needs this to know
30
+ // when to clear its in-memory clients list.
31
+ window.postMessage({ type: 'msw/worker:stop' })
32
+
27
33
  printStopMessage({ quiet: context.startOptions?.quiet })
28
34
  }
29
35
  }
@@ -1,38 +1,42 @@
1
1
  import { invariant } from 'outvariant'
2
2
  import { EventMap, Emitter } from 'strict-event-emitter'
3
- import {
4
- RequestHandler,
5
- RequestHandlerDefaultInfo,
6
- } from './handlers/RequestHandler'
3
+ import { RequestHandler } from './handlers/RequestHandler'
7
4
  import { LifeCycleEventEmitter } from './sharedOptions'
8
5
  import { devUtils } from './utils/internal/devUtils'
9
6
  import { pipeEvents } from './utils/internal/pipeEvents'
10
7
  import { toReadonlyArray } from './utils/internal/toReadonlyArray'
11
8
  import { Disposable } from './utils/internal/Disposable'
9
+ import type { WebSocketHandler } from './handlers/WebSocketHandler'
12
10
 
13
11
  export abstract class HandlersController {
14
- abstract prepend(runtimeHandlers: Array<RequestHandler>): void
15
- abstract reset(nextHandles: Array<RequestHandler>): void
16
- abstract currentHandlers(): Array<RequestHandler>
12
+ abstract prepend(
13
+ runtimeHandlers: Array<RequestHandler | WebSocketHandler>,
14
+ ): void
15
+ abstract reset(nextHandles: Array<RequestHandler | WebSocketHandler>): void
16
+ abstract currentHandlers(): Array<RequestHandler | WebSocketHandler>
17
17
  }
18
18
 
19
19
  export class InMemoryHandlersController implements HandlersController {
20
- private handlers: Array<RequestHandler>
20
+ private handlers: Array<RequestHandler | WebSocketHandler>
21
21
 
22
- constructor(private initialHandlers: Array<RequestHandler>) {
22
+ constructor(
23
+ private initialHandlers: Array<RequestHandler | WebSocketHandler>,
24
+ ) {
23
25
  this.handlers = [...initialHandlers]
24
26
  }
25
27
 
26
- public prepend(runtimeHandles: Array<RequestHandler>): void {
28
+ public prepend(
29
+ runtimeHandles: Array<RequestHandler | WebSocketHandler>,
30
+ ): void {
27
31
  this.handlers.unshift(...runtimeHandles)
28
32
  }
29
33
 
30
- public reset(nextHandlers: Array<RequestHandler>): void {
34
+ public reset(nextHandlers: Array<RequestHandler | WebSocketHandler>): void {
31
35
  this.handlers =
32
36
  nextHandlers.length > 0 ? [...nextHandlers] : [...this.initialHandlers]
33
37
  }
34
38
 
35
- public currentHandlers(): Array<RequestHandler> {
39
+ public currentHandlers(): Array<RequestHandler | WebSocketHandler> {
36
40
  return this.handlers
37
41
  }
38
42
  }
@@ -47,7 +51,7 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
47
51
 
48
52
  public readonly events: LifeCycleEventEmitter<EventsMap>
49
53
 
50
- constructor(...initialHandlers: Array<RequestHandler>) {
54
+ constructor(...initialHandlers: Array<RequestHandler | WebSocketHandler>) {
51
55
  super()
52
56
 
53
57
  invariant(
@@ -71,12 +75,14 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
71
75
  })
72
76
  }
73
77
 
74
- private validateHandlers(handlers: ReadonlyArray<RequestHandler>): boolean {
78
+ private validateHandlers(handlers: ReadonlyArray<unknown>): boolean {
75
79
  // Guard against incorrect call signature of the setup API.
76
80
  return handlers.every((handler) => !Array.isArray(handler))
77
81
  }
78
82
 
79
- public use(...runtimeHandlers: Array<RequestHandler>): void {
83
+ public use(
84
+ ...runtimeHandlers: Array<RequestHandler | WebSocketHandler>
85
+ ): void {
80
86
  invariant(
81
87
  this.validateHandlers(runtimeHandlers),
82
88
  devUtils.formatMessage(
@@ -89,17 +95,19 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
89
95
 
90
96
  public restoreHandlers(): void {
91
97
  this.handlersController.currentHandlers().forEach((handler) => {
92
- handler.isUsed = false
98
+ if ('isUsed' in handler) {
99
+ handler.isUsed = false
100
+ }
93
101
  })
94
102
  }
95
103
 
96
- public resetHandlers(...nextHandlers: Array<RequestHandler>): void {
104
+ public resetHandlers(
105
+ ...nextHandlers: Array<RequestHandler | WebSocketHandler>
106
+ ): void {
97
107
  this.handlersController.reset(nextHandlers)
98
108
  }
99
109
 
100
- public listHandlers(): ReadonlyArray<
101
- RequestHandler<RequestHandlerDefaultInfo, any, any>
102
- > {
110
+ public listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler> {
103
111
  return toReadonlyArray(this.handlersController.currentHandlers())
104
112
  }
105
113
 
@@ -7,6 +7,7 @@ import {
7
7
  import type { ResponseResolutionContext } from '../utils/executeHandlers'
8
8
  import type { MaybePromise } from '../typeUtils'
9
9
  import { StrictRequest, StrictResponse } from '..//HttpResponse'
10
+ import type { HandlerKind } from './common'
10
11
 
11
12
  export type DefaultRequestMultipartBody = Record<
12
13
  string,
@@ -117,6 +118,8 @@ export abstract class RequestHandler<
117
118
  StrictRequest<DefaultBodyType>
118
119
  >()
119
120
 
121
+ private readonly __kind: HandlerKind
122
+
120
123
  public info: HandlerInfo & RequestHandlerInternalInfo
121
124
  /**
122
125
  * Indicates whether this request handler has been used
@@ -151,6 +154,7 @@ export abstract class RequestHandler<
151
154
  }
152
155
 
153
156
  this.isUsed = false
157
+ this.__kind = 'RequestHandler'
154
158
  }
155
159
 
156
160
  /**
@@ -0,0 +1,146 @@
1
+ import { Emitter } from 'strict-event-emitter'
2
+ import { createRequestId } from '@mswjs/interceptors'
3
+ import type { WebSocketConnectionData } from '@mswjs/interceptors/WebSocket'
4
+ import {
5
+ type Match,
6
+ type Path,
7
+ type PathParams,
8
+ matchRequestUrl,
9
+ } from '../utils/matching/matchRequestUrl'
10
+ import { getCallFrame } from '../utils/internal/getCallFrame'
11
+ import type { HandlerKind } from './common'
12
+
13
+ type WebSocketHandlerParsedResult = {
14
+ match: Match
15
+ }
16
+
17
+ export type WebSocketHandlerEventMap = {
18
+ connection: [args: WebSocketHandlerConnection]
19
+ }
20
+
21
+ export interface WebSocketHandlerConnection extends WebSocketConnectionData {
22
+ params: PathParams
23
+ }
24
+
25
+ export const kEmitter = Symbol('kEmitter')
26
+ export const kDispatchEvent = Symbol('kDispatchEvent')
27
+ export const kSender = Symbol('kSender')
28
+ const kStopPropagationPatched = Symbol('kStopPropagationPatched')
29
+ const KOnStopPropagation = Symbol('KOnStopPropagation')
30
+
31
+ export class WebSocketHandler {
32
+ private readonly __kind: HandlerKind
33
+
34
+ public id: string
35
+ public callFrame?: string
36
+
37
+ protected [kEmitter]: Emitter<WebSocketHandlerEventMap>
38
+
39
+ constructor(private readonly url: Path) {
40
+ this.id = createRequestId()
41
+
42
+ this[kEmitter] = new Emitter()
43
+ this.callFrame = getCallFrame(new Error())
44
+ this.__kind = 'EventHandler'
45
+ }
46
+
47
+ public parse(args: {
48
+ event: MessageEvent<WebSocketConnectionData>
49
+ }): WebSocketHandlerParsedResult {
50
+ const connection = args.event.data
51
+ const match = matchRequestUrl(connection.client.url, this.url)
52
+
53
+ return {
54
+ match,
55
+ }
56
+ }
57
+
58
+ public predicate(args: {
59
+ event: MessageEvent<WebSocketConnectionData>
60
+ parsedResult: WebSocketHandlerParsedResult
61
+ }): boolean {
62
+ return args.parsedResult.match.matches
63
+ }
64
+
65
+ async [kDispatchEvent](
66
+ event: MessageEvent<WebSocketConnectionData>,
67
+ ): Promise<void> {
68
+ const parsedResult = this.parse({ event })
69
+ const connection = event.data
70
+
71
+ const resolvedConnection: WebSocketHandlerConnection = {
72
+ ...connection,
73
+ params: parsedResult.match.params || {},
74
+ }
75
+
76
+ // Support `event.stopPropagation()` for various client/server events.
77
+ connection.client.addEventListener(
78
+ 'message',
79
+ createStopPropagationListener(this),
80
+ )
81
+ connection.client.addEventListener(
82
+ 'close',
83
+ createStopPropagationListener(this),
84
+ )
85
+
86
+ connection.server.addEventListener(
87
+ 'open',
88
+ createStopPropagationListener(this),
89
+ )
90
+ connection.server.addEventListener(
91
+ 'message',
92
+ createStopPropagationListener(this),
93
+ )
94
+ connection.server.addEventListener(
95
+ 'error',
96
+ createStopPropagationListener(this),
97
+ )
98
+ connection.server.addEventListener(
99
+ 'close',
100
+ createStopPropagationListener(this),
101
+ )
102
+
103
+ // Emit the connection event on the handler.
104
+ // This is what the developer adds listeners for.
105
+ this[kEmitter].emit('connection', resolvedConnection)
106
+ }
107
+ }
108
+
109
+ function createStopPropagationListener(handler: WebSocketHandler) {
110
+ return function stopPropagationListener(event: Event) {
111
+ const propagationStoppedAt = Reflect.get(event, 'kPropagationStoppedAt') as
112
+ | string
113
+ | undefined
114
+
115
+ if (propagationStoppedAt && handler.id !== propagationStoppedAt) {
116
+ event.stopImmediatePropagation()
117
+ return
118
+ }
119
+
120
+ Object.defineProperty(event, KOnStopPropagation, {
121
+ value(this: WebSocketHandler) {
122
+ Object.defineProperty(event, 'kPropagationStoppedAt', {
123
+ value: handler.id,
124
+ })
125
+ },
126
+ configurable: true,
127
+ })
128
+
129
+ // Since the same event instance is shared between all client/server objects,
130
+ // make sure to patch its `stopPropagation` method only once.
131
+ if (!Reflect.get(event, kStopPropagationPatched)) {
132
+ event.stopPropagation = new Proxy(event.stopPropagation, {
133
+ apply: (target, thisArg, args) => {
134
+ Reflect.get(event, KOnStopPropagation)?.call(handler)
135
+ return Reflect.apply(target, thisArg, args)
136
+ },
137
+ })
138
+
139
+ Object.defineProperty(event, kStopPropagationPatched, {
140
+ value: true,
141
+ // If something else attempts to redefine this, throw.
142
+ configurable: false,
143
+ })
144
+ }
145
+ }
146
+ }
@@ -0,0 +1 @@
1
+ export type HandlerKind = 'RequestHandler' | 'EventHandler'
package/src/core/index.ts CHANGED
@@ -2,13 +2,21 @@ import { checkGlobals } from './utils/internal/checkGlobals'
2
2
 
3
3
  export { SetupApi } from './SetupApi'
4
4
 
5
- /* Request handlers */
5
+ /* HTTP handlers */
6
6
  export { RequestHandler } from './handlers/RequestHandler'
7
7
  export { http } from './http'
8
8
  export { HttpHandler, HttpMethods } from './handlers/HttpHandler'
9
9
  export { graphql } from './graphql'
10
10
  export { GraphQLHandler } from './handlers/GraphQLHandler'
11
11
 
12
+ /* WebSocket handler */
13
+ export { ws, type WebSocketLink } from './ws'
14
+ export {
15
+ WebSocketHandler,
16
+ type WebSocketHandlerEventMap,
17
+ type WebSocketHandlerConnection,
18
+ } from './handlers/WebSocketHandler'
19
+
12
20
  /* Utils */
13
21
  export { matchRequestUrl } from './utils/matching/matchRequestUrl'
14
22
  export * from './utils/handleRequest'
@@ -45,6 +53,8 @@ export type {
45
53
  } from './handlers/GraphQLHandler'
46
54
  export type { GraphQLRequestHandler, GraphQLResponseResolver } from './graphql'
47
55
 
56
+ export type { WebSocketData, WebSocketEventListener } from './ws'
57
+
48
58
  export type { Path, PathParams, Match } from './utils/matching/matchRequestUrl'
49
59
  export type { ParsedGraphQLRequest } from './utils/internal/parseGraphQLRequest'
50
60
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RequestHandler,
3
- RequestHandlerExecutionResult,
3
+ type RequestHandlerExecutionResult,
4
4
  } from '../handlers/RequestHandler'
5
5
 
6
6
  export interface HandlersExecutionResult {
@@ -1,8 +1,8 @@
1
1
  import { until } from '@open-draft/until'
2
2
  import { Emitter } from 'strict-event-emitter'
3
- import { RequestHandler } from '../handlers/RequestHandler'
4
3
  import { LifeCycleEventsMap, SharedOptions } from '../sharedOptions'
5
4
  import { RequiredDeep } from '../typeUtils'
5
+ import type { RequestHandler } from '../handlers/RequestHandler'
6
6
  import { HandlersExecutionResult, executeHandlers } from './executeHandlers'
7
7
  import { onUnhandledRequest } from './request/onUnhandledRequest'
8
8
  import { storeResponseCookies } from './request/storeResponseCookies'
@@ -0,0 +1,64 @@
1
+ import { GraphQLHandler } from '../../handlers/GraphQLHandler'
2
+ import { HttpHandler } from '../../handlers/HttpHandler'
3
+ import { RequestHandler } from '../../handlers/RequestHandler'
4
+ import { WebSocketHandler } from '../../handlers/WebSocketHandler'
5
+ import { isHandlerKind } from './isHandlerKind'
6
+
7
+ it('returns true if expected a request handler and given a request handler', () => {
8
+ expect(
9
+ isHandlerKind('RequestHandler')(new HttpHandler('*', '*', () => {})),
10
+ ).toBe(true)
11
+
12
+ expect(
13
+ isHandlerKind('RequestHandler')(
14
+ new GraphQLHandler('all', '*', '*', () => {}),
15
+ ),
16
+ ).toBe(true)
17
+ })
18
+
19
+ it('returns true if expected a request handler and given a custom request handler', () => {
20
+ class MyHandler extends RequestHandler {
21
+ constructor() {
22
+ super({ info: { header: '*' }, resolver: () => {} })
23
+ }
24
+ predicate = () => false
25
+ log() {}
26
+ }
27
+
28
+ expect(isHandlerKind('RequestHandler')(new MyHandler())).toBe(true)
29
+ })
30
+
31
+ it('returns false if expected a request handler but given event handler', () => {
32
+ expect(isHandlerKind('RequestHandler')(new WebSocketHandler('*'))).toBe(false)
33
+ })
34
+
35
+ it('returns false if expected a request handler but given arbitrary object', () => {
36
+ expect(isHandlerKind('RequestHandler')(undefined)).toBe(false)
37
+ expect(isHandlerKind('RequestHandler')(null)).toBe(false)
38
+ expect(isHandlerKind('RequestHandler')({})).toBe(false)
39
+ expect(isHandlerKind('RequestHandler')([])).toBe(false)
40
+ expect(isHandlerKind('RequestHandler')(123)).toBe(false)
41
+ expect(isHandlerKind('RequestHandler')('hello')).toBe(false)
42
+ })
43
+
44
+ it('returns true if expected an event handler and given an event handler', () => {
45
+ expect(isHandlerKind('EventHandler')(new WebSocketHandler('*'))).toBe(true)
46
+ })
47
+
48
+ it('returns true if expected an event handler and given a custom event handler', () => {
49
+ class MyEventHandler extends WebSocketHandler {
50
+ constructor() {
51
+ super('*')
52
+ }
53
+ }
54
+ expect(isHandlerKind('EventHandler')(new MyEventHandler())).toBe(true)
55
+ })
56
+
57
+ it('returns false if expected an event handler but given arbitrary object', () => {
58
+ expect(isHandlerKind('EventHandler')(undefined)).toBe(false)
59
+ expect(isHandlerKind('EventHandler')(null)).toBe(false)
60
+ expect(isHandlerKind('EventHandler')({})).toBe(false)
61
+ expect(isHandlerKind('EventHandler')([])).toBe(false)
62
+ expect(isHandlerKind('EventHandler')(123)).toBe(false)
63
+ expect(isHandlerKind('EventHandler')('hello')).toBe(false)
64
+ })
@@ -0,0 +1,21 @@
1
+ import type { HandlerKind } from '../../handlers/common'
2
+ import type { RequestHandler } from '../../handlers/RequestHandler'
3
+ import type { WebSocketHandler } from '../../handlers/WebSocketHandler'
4
+
5
+ /**
6
+ * A filter function that ensures that the provided argument
7
+ * is a handler of the given kind. This helps differentiate
8
+ * between different kinds of handlers, e.g. request and event handlers.
9
+ */
10
+ export function isHandlerKind<K extends HandlerKind>(kind: K) {
11
+ return (
12
+ input: unknown,
13
+ ): input is K extends 'EventHandler' ? WebSocketHandler : RequestHandler => {
14
+ return (
15
+ input != null &&
16
+ typeof input === 'object' &&
17
+ '__kind' in input &&
18
+ input.__kind === kind
19
+ )
20
+ }
21
+ }