msw 2.2.13 → 2.3.0-ws.rc-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 (168) hide show
  1. package/cli/init.js +0 -0
  2. package/config/scripts/postinstall.js +0 -0
  3. package/lib/browser/index.d.mts +7 -6
  4. package/lib/browser/index.d.ts +7 -6
  5. package/lib/browser/index.js +26 -7
  6. package/lib/browser/index.js.map +1 -1
  7. package/lib/browser/index.mjs +26 -7
  8. package/lib/browser/index.mjs.map +1 -1
  9. package/lib/core/{GraphQLHandler-bom2Dn82.d.ts → GraphQLHandler-3gvpA65n.d.ts} +1 -1
  10. package/lib/core/{GraphQLHandler-yJ_I6j54.d.mts → GraphQLHandler-4DPdxG0R.d.mts} +1 -1
  11. package/lib/core/{HttpResponse-vQNlixkj.d.ts → HttpResponse-aJY-D0oG.d.ts} +2 -2
  12. package/lib/core/{HttpResponse-3-NyzyF7.d.mts → HttpResponse-xuSipbNt.d.mts} +2 -2
  13. package/lib/core/HttpResponse.d.mts +1 -1
  14. package/lib/core/HttpResponse.d.ts +1 -1
  15. package/lib/core/SetupApi.d.mts +15 -12
  16. package/lib/core/SetupApi.d.ts +15 -12
  17. package/lib/core/SetupApi.js +3 -1
  18. package/lib/core/SetupApi.js.map +1 -1
  19. package/lib/core/SetupApi.mjs +3 -1
  20. package/lib/core/SetupApi.mjs.map +1 -1
  21. package/lib/core/getResponse.d.mts +1 -1
  22. package/lib/core/getResponse.d.ts +1 -1
  23. package/lib/core/graphql.d.mts +2 -2
  24. package/lib/core/graphql.d.ts +2 -2
  25. package/lib/core/handlers/GraphQLHandler.d.mts +2 -2
  26. package/lib/core/handlers/GraphQLHandler.d.ts +2 -2
  27. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  28. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  29. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  30. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  31. package/lib/core/handlers/WebSocketHandler.d.mts +32 -0
  32. package/lib/core/handlers/WebSocketHandler.d.ts +32 -0
  33. package/lib/core/handlers/WebSocketHandler.js +62 -0
  34. package/lib/core/handlers/WebSocketHandler.js.map +1 -0
  35. package/lib/core/handlers/WebSocketHandler.mjs +44 -0
  36. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -0
  37. package/lib/core/http.d.mts +1 -1
  38. package/lib/core/http.d.ts +1 -1
  39. package/lib/core/index.d.mts +5 -2
  40. package/lib/core/index.d.ts +5 -2
  41. package/lib/core/index.js +5 -1
  42. package/lib/core/index.js.map +1 -1
  43. package/lib/core/index.mjs +7 -1
  44. package/lib/core/index.mjs.map +1 -1
  45. package/lib/core/passthrough.d.mts +1 -1
  46. package/lib/core/passthrough.d.ts +1 -1
  47. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  48. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  49. package/lib/core/utils/executeHandlers.d.mts +1 -1
  50. package/lib/core/utils/executeHandlers.d.ts +1 -1
  51. package/lib/core/utils/executeHandlers.js +4 -0
  52. package/lib/core/utils/executeHandlers.js.map +1 -1
  53. package/lib/core/utils/executeHandlers.mjs +6 -0
  54. package/lib/core/utils/executeHandlers.mjs.map +1 -1
  55. package/lib/core/utils/handleRequest.d.mts +2 -2
  56. package/lib/core/utils/handleRequest.d.ts +2 -2
  57. package/lib/core/utils/handleRequest.js.map +1 -1
  58. package/lib/core/utils/handleRequest.mjs.map +1 -1
  59. package/lib/core/utils/handleWebSocketEvent.d.mts +16 -0
  60. package/lib/core/utils/handleWebSocketEvent.d.ts +16 -0
  61. package/lib/core/utils/handleWebSocketEvent.js +59 -0
  62. package/lib/core/utils/handleWebSocketEvent.js.map +1 -0
  63. package/lib/core/utils/handleWebSocketEvent.mjs +39 -0
  64. package/lib/core/utils/handleWebSocketEvent.mjs.map +1 -0
  65. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +2 -2
  66. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +2 -2
  67. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  68. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  69. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  70. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  71. package/lib/core/utils/logging/getTimestamp.d.mts +4 -1
  72. package/lib/core/utils/logging/getTimestamp.d.ts +4 -1
  73. package/lib/core/utils/logging/getTimestamp.js +6 -2
  74. package/lib/core/utils/logging/getTimestamp.js.map +1 -1
  75. package/lib/core/utils/logging/getTimestamp.mjs +6 -2
  76. package/lib/core/utils/logging/getTimestamp.mjs.map +1 -1
  77. package/lib/core/utils/matching/matchRequestUrl.d.mts +2 -1
  78. package/lib/core/utils/matching/matchRequestUrl.d.ts +2 -1
  79. package/lib/core/utils/matching/matchRequestUrl.js +4 -0
  80. package/lib/core/utils/matching/matchRequestUrl.js.map +1 -1
  81. package/lib/core/utils/matching/matchRequestUrl.mjs +4 -0
  82. package/lib/core/utils/matching/matchRequestUrl.mjs.map +1 -1
  83. package/lib/core/ws/WebSocketClientManager.d.mts +64 -0
  84. package/lib/core/ws/WebSocketClientManager.d.ts +64 -0
  85. package/lib/core/ws/WebSocketClientManager.js +123 -0
  86. package/lib/core/ws/WebSocketClientManager.js.map +1 -0
  87. package/lib/core/ws/WebSocketClientManager.mjs +103 -0
  88. package/lib/core/ws/WebSocketClientManager.mjs.map +1 -0
  89. package/lib/core/ws/utils/attachWebSocketLogger.d.mts +34 -0
  90. package/lib/core/ws/utils/attachWebSocketLogger.d.ts +34 -0
  91. package/lib/core/ws/utils/attachWebSocketLogger.js +211 -0
  92. package/lib/core/ws/utils/attachWebSocketLogger.js.map +1 -0
  93. package/lib/core/ws/utils/attachWebSocketLogger.mjs +191 -0
  94. package/lib/core/ws/utils/attachWebSocketLogger.mjs.map +1 -0
  95. package/lib/core/ws/utils/getMessageLength.d.mts +11 -0
  96. package/lib/core/ws/utils/getMessageLength.d.ts +11 -0
  97. package/lib/core/ws/utils/getMessageLength.js +33 -0
  98. package/lib/core/ws/utils/getMessageLength.js.map +1 -0
  99. package/lib/core/ws/utils/getMessageLength.mjs +13 -0
  100. package/lib/core/ws/utils/getMessageLength.mjs.map +1 -0
  101. package/lib/core/ws/utils/getPublicData.d.mts +5 -0
  102. package/lib/core/ws/utils/getPublicData.d.ts +5 -0
  103. package/lib/core/ws/utils/getPublicData.js +36 -0
  104. package/lib/core/ws/utils/getPublicData.js.map +1 -0
  105. package/lib/core/ws/utils/getPublicData.mjs +16 -0
  106. package/lib/core/ws/utils/getPublicData.mjs.map +1 -0
  107. package/lib/core/ws/utils/truncateMessage.d.mts +3 -0
  108. package/lib/core/ws/utils/truncateMessage.d.ts +3 -0
  109. package/lib/core/ws/utils/truncateMessage.js +31 -0
  110. package/lib/core/ws/utils/truncateMessage.js.map +1 -0
  111. package/lib/core/ws/utils/truncateMessage.mjs +11 -0
  112. package/lib/core/ws/utils/truncateMessage.mjs.map +1 -0
  113. package/lib/core/ws/webSocketInterceptor.d.mts +5 -0
  114. package/lib/core/ws/webSocketInterceptor.d.ts +5 -0
  115. package/lib/core/ws/webSocketInterceptor.js +26 -0
  116. package/lib/core/ws/webSocketInterceptor.js.map +1 -0
  117. package/lib/core/ws/webSocketInterceptor.mjs +6 -0
  118. package/lib/core/ws/webSocketInterceptor.mjs.map +1 -0
  119. package/lib/core/ws/ws.d.mts +44 -0
  120. package/lib/core/ws/ws.d.ts +44 -0
  121. package/lib/core/ws/ws.js +82 -0
  122. package/lib/core/ws/ws.js.map +1 -0
  123. package/lib/core/ws/ws.mjs +65 -0
  124. package/lib/core/ws/ws.mjs.map +1 -0
  125. package/lib/iife/index.js +967 -11
  126. package/lib/iife/index.js.map +1 -1
  127. package/lib/mockServiceWorker.js +1 -1
  128. package/lib/native/index.d.mts +6 -5
  129. package/lib/native/index.d.ts +6 -5
  130. package/lib/native/index.js +13 -0
  131. package/lib/native/index.js.map +1 -1
  132. package/lib/native/index.mjs +13 -0
  133. package/lib/native/index.mjs.map +1 -1
  134. package/lib/node/index.d.mts +8 -7
  135. package/lib/node/index.d.ts +8 -7
  136. package/lib/node/index.js +13 -0
  137. package/lib/node/index.js.map +1 -1
  138. package/lib/node/index.mjs +13 -0
  139. package/lib/node/index.mjs.map +1 -1
  140. package/package.json +26 -21
  141. package/src/browser/setupWorker/glossary.ts +10 -10
  142. package/src/browser/setupWorker/setupWorker.ts +34 -3
  143. package/src/core/SetupApi.ts +28 -20
  144. package/src/core/handlers/WebSocketHandler.ts +71 -0
  145. package/src/core/index.ts +8 -1
  146. package/src/core/utils/executeHandlers.ts +6 -2
  147. package/src/core/utils/handleRequest.ts +1 -2
  148. package/src/core/utils/handleWebSocketEvent.ts +59 -0
  149. package/src/core/utils/logging/getTimestamp.test.ts +20 -6
  150. package/src/core/utils/logging/getTimestamp.ts +11 -6
  151. package/src/core/utils/matching/matchRequestUrl.test.ts +44 -0
  152. package/src/core/utils/matching/matchRequestUrl.ts +4 -0
  153. package/src/core/ws/WebSocketClientManager.test.ts +159 -0
  154. package/src/core/ws/WebSocketClientManager.ts +170 -0
  155. package/src/core/ws/utils/attachWebSocketLogger.ts +262 -0
  156. package/src/core/ws/utils/getMessageLength.test.ts +16 -0
  157. package/src/core/ws/utils/getMessageLength.ts +19 -0
  158. package/src/core/ws/utils/getPublicData.test.ts +38 -0
  159. package/src/core/ws/utils/getPublicData.ts +17 -0
  160. package/src/core/ws/utils/truncateMessage.test.ts +12 -0
  161. package/src/core/ws/utils/truncateMessage.ts +9 -0
  162. package/src/core/ws/webSocketInterceptor.ts +3 -0
  163. package/src/core/ws/ws.test.ts +23 -0
  164. package/src/core/ws/ws.ts +108 -0
  165. package/src/node/SetupServerApi.ts +8 -7
  166. package/src/node/SetupServerCommonApi.ts +14 -1
  167. package/src/node/glossary.ts +5 -7
  168. package/src/node/setupServer.ts +2 -1
@@ -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
 
@@ -87,7 +85,7 @@ export interface SetupWorkerInternalContext {
87
85
  startOptions: RequiredDeep<StartOptions>
88
86
  worker: ServiceWorker | null
89
87
  registration: ServiceWorkerRegistration | null
90
- getRequestHandlers(): Array<RequestHandler>
88
+ getRequestHandlers(): Array<RequestHandler | WebSocketHandler>
91
89
  requests: Map<string, Request>
92
90
  emitter: Emitter<LifeCycleEventsMap>
93
91
  keepAliveInterval?: number
@@ -211,7 +209,7 @@ export interface SetupWorker {
211
209
  *
212
210
  * @see {@link https://mswjs.io/docs/api/setup-worker/use `worker.use()` API reference}
213
211
  */
214
- use: (...handlers: RequestHandler[]) => void
212
+ use: (...handlers: Array<RequestHandler | WebSocketHandler>) => void
215
213
 
216
214
  /**
217
215
  * Marks all request handlers that respond using `res.once()` as unused.
@@ -226,14 +224,16 @@ export interface SetupWorker {
226
224
  *
227
225
  * @see {@link https://mswjs.io/docs/api/setup-worker/reset-handlers `worker.resetHandlers()` API reference}
228
226
  */
229
- resetHandlers: (...nextHandlers: RequestHandler[]) => void
227
+ resetHandlers: (
228
+ ...nextHandlers: Array<RequestHandler | WebSocketHandler>
229
+ ) => void
230
230
 
231
231
  /**
232
232
  * Returns a readonly list of currently active request handlers.
233
233
  *
234
234
  * @see {@link https://mswjs.io/docs/api/setup-worker/list-handlers `worker.listHandlers()` API reference}
235
235
  */
236
- listHandlers(): ReadonlyArray<RequestHandler<RequestHandlerDefaultInfo, any>>
236
+ listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>
237
237
 
238
238
  /**
239
239
  * 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/utils/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,31 @@ export class SetupWorkerApi
176
180
  options,
177
181
  ) as SetupWorkerInternalContext['startOptions']
178
182
 
183
+ // Enable WebSocket interception.
184
+ handleWebSocketEvent({
185
+ getHandlers: () => {
186
+ return this.handlersController.currentHandlers()
187
+ },
188
+ onMockedConnection: (connection) => {
189
+ if (!this.context.startOptions.quiet) {
190
+ // Attach the logger for mocked connections since
191
+ // those won't be visible in the browser's devtools.
192
+ attachWebSocketLogger(connection)
193
+ }
194
+ },
195
+ onPassthroughConnection() {
196
+ /**
197
+ * @fixme Call some "onUnhandledConnection".
198
+ */
199
+ },
200
+ })
201
+
202
+ webSocketInterceptor.apply()
203
+
204
+ this.subscriptions.push(() => {
205
+ webSocketInterceptor.dispose()
206
+ })
207
+
179
208
  return await this.startHandler(this.context.startOptions, options)
180
209
  }
181
210
 
@@ -193,6 +222,8 @@ export class SetupWorkerApi
193
222
  *
194
223
  * @see {@link https://mswjs.io/docs/api/setup-worker `setupWorker()` API reference}
195
224
  */
196
- export function setupWorker(...handlers: Array<RequestHandler>): SetupWorker {
225
+ export function setupWorker(
226
+ ...handlers: Array<RequestHandler | WebSocketHandler>
227
+ ): SetupWorker {
197
228
  return new SetupWorkerApi(...handlers)
198
229
  }
@@ -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
 
@@ -0,0 +1,71 @@
1
+ import { Emitter } from 'strict-event-emitter'
2
+ import type { WebSocketConnectionData } from '@mswjs/interceptors/WebSocket'
3
+ import {
4
+ type Match,
5
+ type Path,
6
+ type PathParams,
7
+ matchRequestUrl,
8
+ } from '../utils/matching/matchRequestUrl'
9
+ import { getCallFrame } from '../utils/internal/getCallFrame'
10
+
11
+ type WebSocketHandlerParsedResult = {
12
+ match: Match
13
+ }
14
+
15
+ export type WebSocketHandlerEventMap = {
16
+ connection: [args: WebSocketHandlerConnection]
17
+ }
18
+
19
+ interface WebSocketHandlerConnection extends WebSocketConnectionData {
20
+ params: PathParams
21
+ }
22
+
23
+ export const kEmitter = Symbol('kEmitter')
24
+ export const kDispatchEvent = Symbol('kDispatchEvent')
25
+ export const kSender = Symbol('kSender')
26
+
27
+ export class WebSocketHandler {
28
+ public callFrame?: string
29
+
30
+ protected [kEmitter]: Emitter<WebSocketHandlerEventMap>
31
+
32
+ constructor(private readonly url: Path) {
33
+ this[kEmitter] = new Emitter()
34
+ this.callFrame = getCallFrame(new Error())
35
+ }
36
+
37
+ public parse(args: {
38
+ event: MessageEvent<WebSocketConnectionData>
39
+ }): WebSocketHandlerParsedResult {
40
+ const connection = args.event.data
41
+ const match = matchRequestUrl(connection.client.url, this.url)
42
+
43
+ return {
44
+ match,
45
+ }
46
+ }
47
+
48
+ public predicate(args: {
49
+ event: MessageEvent<WebSocketConnectionData>
50
+ parsedResult: WebSocketHandlerParsedResult
51
+ }): boolean {
52
+ return args.parsedResult.match.matches
53
+ }
54
+
55
+ async [kDispatchEvent](
56
+ event: MessageEvent<WebSocketConnectionData>,
57
+ ): Promise<void> {
58
+ const parsedResult = this.parse({ event })
59
+ const connection = event.data
60
+
61
+ const resolvedConnection: WebSocketHandlerConnection = {
62
+ client: connection.client,
63
+ server: connection.server,
64
+ params: parsedResult.match.params || {},
65
+ }
66
+
67
+ // Emit the connection event on the handler.
68
+ // This is what the developer adds listeners for.
69
+ this[kEmitter].emit('connection', resolvedConnection)
70
+ }
71
+ }
package/src/core/index.ts CHANGED
@@ -2,13 +2,20 @@ 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 } from './ws/ws'
14
+ export {
15
+ WebSocketHandler,
16
+ type WebSocketHandlerEventMap,
17
+ } from './handlers/WebSocketHandler'
18
+
12
19
  /* Utils */
13
20
  export { matchRequestUrl } from './utils/matching/matchRequestUrl'
14
21
  export * from './utils/handleRequest'
@@ -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 {
@@ -18,7 +18,7 @@ export interface ResponseResolutionContext {
18
18
  * Returns the execution result object containing any matching request
19
19
  * handler and any mocked response it returned.
20
20
  */
21
- export const executeHandlers = async <Handlers extends Array<RequestHandler>>({
21
+ export const executeHandlers = async <Handlers extends Array<unknown>>({
22
22
  request,
23
23
  requestId,
24
24
  handlers,
@@ -33,6 +33,10 @@ export const executeHandlers = async <Handlers extends Array<RequestHandler>>({
33
33
  let result: RequestHandlerExecutionResult<any> | null = null
34
34
 
35
35
  for (const handler of handlers) {
36
+ if (!(handler instanceof RequestHandler)) {
37
+ continue
38
+ }
39
+
36
40
  result = await handler.run({ request, requestId, resolutionContext })
37
41
 
38
42
  // If the handler produces some result for this request,
@@ -1,6 +1,5 @@
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'
6
5
  import { HandlersExecutionResult, executeHandlers } from './executeHandlers'
@@ -45,7 +44,7 @@ export interface HandleRequestOptions {
45
44
  export async function handleRequest(
46
45
  request: Request,
47
46
  requestId: string,
48
- handlers: Array<RequestHandler>,
47
+ handlers: Array<unknown>,
49
48
  options: RequiredDeep<SharedOptions>,
50
49
  emitter: Emitter<LifeCycleEventsMap>,
51
50
  handleRequestOptions?: HandleRequestOptions,
@@ -0,0 +1,59 @@
1
+ import type { WebSocketConnectionData } from '@mswjs/interceptors/lib/browser/interceptors/WebSocket'
2
+ import { RequestHandler } from '../handlers/RequestHandler'
3
+ import { WebSocketHandler, kDispatchEvent } from '../handlers/WebSocketHandler'
4
+ import { webSocketInterceptor } from '../ws/webSocketInterceptor'
5
+
6
+ interface HandleWebSocketEventOptions {
7
+ getHandlers: () => Array<RequestHandler | WebSocketHandler>
8
+ onMockedConnection: (connection: WebSocketConnectionData) => void
9
+ onPassthroughConnection: (onnection: WebSocketConnectionData) => void
10
+ }
11
+
12
+ export function handleWebSocketEvent(options: HandleWebSocketEventOptions) {
13
+ webSocketInterceptor.on('connection', (connection) => {
14
+ const handlers = options.getHandlers()
15
+
16
+ const connectionEvent = new MessageEvent('connection', {
17
+ data: connection,
18
+ })
19
+
20
+ // First, filter only those WebSocket handlers that
21
+ // match the "ws.link()" endpoint predicate. Don't dispatch
22
+ // anything yet so the logger can be attached to the connection
23
+ // before it potentially sends events.
24
+ const matchingHandlers = handlers.filter<WebSocketHandler>(
25
+ (handler): handler is WebSocketHandler => {
26
+ if (handler instanceof WebSocketHandler) {
27
+ return handler.predicate({
28
+ event: connectionEvent,
29
+ parsedResult: handler.parse({
30
+ event: connectionEvent,
31
+ }),
32
+ })
33
+ }
34
+
35
+ return false
36
+ },
37
+ )
38
+
39
+ if (matchingHandlers.length > 0) {
40
+ options?.onMockedConnection(connection)
41
+
42
+ // Iterate over the handlers and forward the connection
43
+ // event to WebSocket event handlers. This is equivalent
44
+ // to dispatching that event onto multiple listeners.
45
+ for (const handler of matchingHandlers) {
46
+ handler[kDispatchEvent](connectionEvent)
47
+ }
48
+ } else {
49
+ options?.onPassthroughConnection(connection)
50
+
51
+ // If none of the "ws" handlers matched,
52
+ // establish the WebSocket connection as-is.
53
+ connection.server.connect()
54
+ connection.client.addEventListener('message', (event) => {
55
+ connection.server.send(event.data)
56
+ })
57
+ }
58
+ })
59
+ }
@@ -1,18 +1,32 @@
1
1
  import { getTimestamp } from './getTimestamp'
2
2
 
3
3
  beforeAll(() => {
4
- // Stub native `Date` prototype methods used in the tested module,
5
- // to always produce a predictable value for testing purposes.
6
- vi.spyOn(global.Date.prototype, 'getHours').mockImplementation(() => 12)
7
- vi.spyOn(global.Date.prototype, 'getMinutes').mockImplementation(() => 4)
8
- vi.spyOn(global.Date.prototype, 'getSeconds').mockImplementation(() => 8)
4
+ vi.useFakeTimers()
9
5
  })
10
6
 
11
7
  afterAll(() => {
12
- vi.restoreAllMocks()
8
+ vi.useRealTimers()
13
9
  })
14
10
 
15
11
  test('returns a timestamp string of the invocation time', () => {
12
+ vi.setSystemTime(new Date('2024-01-01 12:4:8'))
16
13
  const timestamp = getTimestamp()
17
14
  expect(timestamp).toBe('12:04:08')
18
15
  })
16
+
17
+ test('returns a timestamp with milliseconds', () => {
18
+ vi.setSystemTime(new Date('2024-01-01 12:4:8'))
19
+ expect(getTimestamp({ milliseconds: true })).toBe('12:04:08.000')
20
+
21
+ vi.setSystemTime(new Date('2024-01-01 12:4:8.000'))
22
+ expect(getTimestamp({ milliseconds: true })).toBe('12:04:08.000')
23
+
24
+ vi.setSystemTime(new Date('2024-01-01 12:4:8.4'))
25
+ expect(getTimestamp({ milliseconds: true })).toBe('12:04:08.400')
26
+
27
+ vi.setSystemTime(new Date('2024-01-01 12:4:8.123'))
28
+ expect(getTimestamp({ milliseconds: true })).toBe('12:04:08.123')
29
+
30
+ vi.setSystemTime(new Date('2024-01-01 12:00:00'))
31
+ expect(getTimestamp({ milliseconds: true })).toBe('12:00:00.000')
32
+ })
@@ -1,12 +1,17 @@
1
+ interface GetTimestampOptions {
2
+ milliseconds?: boolean
3
+ }
4
+
1
5
  /**
2
6
  * Returns a timestamp string in a "HH:MM:SS" format.
3
7
  */
4
- export function getTimestamp(): string {
8
+ export function getTimestamp(options?: GetTimestampOptions): string {
5
9
  const now = new Date()
10
+ const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`
11
+
12
+ if (options?.milliseconds) {
13
+ return `${timestamp}.${now.getMilliseconds().toString().padStart(3, '0')}`
14
+ }
6
15
 
7
- return [now.getHours(), now.getMinutes(), now.getSeconds()]
8
- .map(String)
9
- .map((chunk) => chunk.slice(0, 2))
10
- .map((chunk) => chunk.padStart(2, '0'))
11
- .join(':')
16
+ return timestamp
12
17
  }
@@ -61,6 +61,50 @@ describe('matchRequestUrl', () => {
61
61
  expect(match).toHaveProperty('matches', false)
62
62
  expect(match).toHaveProperty('params', {})
63
63
  })
64
+
65
+ test('returns true for matching WebSocket URL', () => {
66
+ expect(
67
+ matchRequestUrl(new URL('ws://test.mswjs.io'), 'ws://test.mswjs.io'),
68
+ ).toEqual({
69
+ matches: true,
70
+ params: {},
71
+ })
72
+ expect(
73
+ matchRequestUrl(new URL('wss://test.mswjs.io'), 'wss://test.mswjs.io'),
74
+ ).toEqual({
75
+ matches: true,
76
+ params: {},
77
+ })
78
+ })
79
+
80
+ test('returns false for non-matching WebSocket URL', () => {
81
+ expect(
82
+ matchRequestUrl(new URL('ws://test.mswjs.io'), 'ws://foo.mswjs.io'),
83
+ ).toEqual({
84
+ matches: false,
85
+ params: {},
86
+ })
87
+ expect(
88
+ matchRequestUrl(new URL('wss://test.mswjs.io'), 'wss://completely.diff'),
89
+ ).toEqual({
90
+ matches: false,
91
+ params: {},
92
+ })
93
+ })
94
+
95
+ test('returns path parameters when matched a WebSocket URL', () => {
96
+ expect(
97
+ matchRequestUrl(
98
+ new URL('wss://test.mswjs.io'),
99
+ 'wss://:service.mswjs.io',
100
+ ),
101
+ ).toEqual({
102
+ matches: true,
103
+ params: {
104
+ service: 'test',
105
+ },
106
+ })
107
+ })
64
108
  })
65
109
 
66
110
  describe('coercePath', () => {
@@ -71,3 +71,7 @@ export function matchRequestUrl(url: URL, path: Path, baseUrl?: string): Match {
71
71
  params,
72
72
  }
73
73
  }
74
+
75
+ export function isPath(value: unknown): value is Path {
76
+ return typeof value === 'string' || value instanceof RegExp
77
+ }