msw 2.13.6 → 2.14.0

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 (108) hide show
  1. package/lib/core/{HttpResponse-BMMzfpjG.d.mts → HttpResponse-CxHR1nNN.d.mts} +5 -1
  2. package/lib/core/{HttpResponse-DPDqE4Pb.d.ts → HttpResponse-aGiIzO91.d.ts} +5 -1
  3. package/lib/core/HttpResponse.d.mts +1 -1
  4. package/lib/core/HttpResponse.d.ts +1 -1
  5. package/lib/core/experimental/compat.d.mts +1 -1
  6. package/lib/core/experimental/compat.d.ts +1 -1
  7. package/lib/core/experimental/define-network.d.mts +1 -1
  8. package/lib/core/experimental/define-network.d.ts +1 -1
  9. package/lib/core/experimental/frames/http-frame.d.mts +1 -1
  10. package/lib/core/experimental/frames/http-frame.d.ts +1 -1
  11. package/lib/core/experimental/frames/http-frame.js +3 -2
  12. package/lib/core/experimental/frames/http-frame.js.map +1 -1
  13. package/lib/core/experimental/frames/http-frame.mjs +3 -2
  14. package/lib/core/experimental/frames/http-frame.mjs.map +1 -1
  15. package/lib/core/experimental/frames/network-frame.d.mts +1 -1
  16. package/lib/core/experimental/frames/network-frame.d.ts +1 -1
  17. package/lib/core/experimental/frames/websocket-frame.d.mts +1 -1
  18. package/lib/core/experimental/frames/websocket-frame.d.ts +1 -1
  19. package/lib/core/experimental/handlers-controller.d.mts +1 -1
  20. package/lib/core/experimental/handlers-controller.d.ts +1 -1
  21. package/lib/core/experimental/handlers-controller.js +16 -5
  22. package/lib/core/experimental/handlers-controller.js.map +1 -1
  23. package/lib/core/experimental/handlers-controller.mjs +16 -5
  24. package/lib/core/experimental/handlers-controller.mjs.map +1 -1
  25. package/lib/core/experimental/index.d.mts +1 -1
  26. package/lib/core/experimental/index.d.ts +1 -1
  27. package/lib/core/experimental/on-unhandled-frame.d.mts +1 -1
  28. package/lib/core/experimental/on-unhandled-frame.d.ts +1 -1
  29. package/lib/core/experimental/setup-api.d.mts +1 -1
  30. package/lib/core/experimental/setup-api.d.ts +1 -1
  31. package/lib/core/experimental/sources/interceptor-source.d.mts +1 -1
  32. package/lib/core/experimental/sources/interceptor-source.d.ts +1 -1
  33. package/lib/core/experimental/sources/network-source.d.mts +1 -1
  34. package/lib/core/experimental/sources/network-source.d.ts +1 -1
  35. package/lib/core/getResponse.d.mts +1 -1
  36. package/lib/core/getResponse.d.ts +1 -1
  37. package/lib/core/graphql.d.mts +1 -1
  38. package/lib/core/graphql.d.ts +1 -1
  39. package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
  40. package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
  41. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  42. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  43. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  44. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  45. package/lib/core/handlers/RequestHandler.js +22 -1
  46. package/lib/core/handlers/RequestHandler.js.map +1 -1
  47. package/lib/core/handlers/RequestHandler.mjs +22 -1
  48. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  49. package/lib/core/handlers/WebSocketHandler.js +1 -1
  50. package/lib/core/handlers/WebSocketHandler.js.map +1 -1
  51. package/lib/core/handlers/WebSocketHandler.mjs +1 -1
  52. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -1
  53. package/lib/core/http.d.mts +1 -1
  54. package/lib/core/http.d.ts +1 -1
  55. package/lib/core/index.d.mts +1 -1
  56. package/lib/core/index.d.ts +1 -1
  57. package/lib/core/passthrough.d.mts +1 -1
  58. package/lib/core/passthrough.d.ts +1 -1
  59. package/lib/core/sse.d.mts +1 -1
  60. package/lib/core/sse.d.ts +1 -1
  61. package/lib/core/utils/HttpResponse/decorators.d.mts +3 -3
  62. package/lib/core/utils/HttpResponse/decorators.d.ts +3 -3
  63. package/lib/core/utils/HttpResponse/decorators.js +4 -10
  64. package/lib/core/utils/HttpResponse/decorators.js.map +1 -1
  65. package/lib/core/utils/HttpResponse/decorators.mjs +4 -10
  66. package/lib/core/utils/HttpResponse/decorators.mjs.map +1 -1
  67. package/lib/core/utils/executeHandlers.d.mts +1 -1
  68. package/lib/core/utils/executeHandlers.d.ts +1 -1
  69. package/lib/core/utils/handleRequest.d.mts +1 -1
  70. package/lib/core/utils/handleRequest.d.ts +1 -1
  71. package/lib/core/utils/internal/attachSiblingHandlers.d.mts +15 -0
  72. package/lib/core/utils/internal/attachSiblingHandlers.d.ts +15 -0
  73. package/lib/core/utils/internal/attachSiblingHandlers.js +44 -0
  74. package/lib/core/utils/internal/attachSiblingHandlers.js.map +1 -0
  75. package/lib/core/utils/internal/attachSiblingHandlers.mjs +24 -0
  76. package/lib/core/utils/internal/attachSiblingHandlers.mjs.map +1 -0
  77. package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
  78. package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
  79. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
  80. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
  81. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  82. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  83. package/lib/core/utils/request/storeResponseCookies.js +1 -1
  84. package/lib/core/utils/request/storeResponseCookies.js.map +1 -1
  85. package/lib/core/utils/request/storeResponseCookies.mjs +2 -2
  86. package/lib/core/utils/request/storeResponseCookies.mjs.map +1 -1
  87. package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
  88. package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
  89. package/lib/core/ws.d.mts +17 -4
  90. package/lib/core/ws.d.ts +17 -4
  91. package/lib/core/ws.js +30 -5
  92. package/lib/core/ws.js.map +1 -1
  93. package/lib/core/ws.mjs +34 -6
  94. package/lib/core/ws.mjs.map +1 -1
  95. package/lib/iife/index.js +1208 -1142
  96. package/lib/iife/index.js.map +1 -1
  97. package/lib/mockServiceWorker.js +1 -1
  98. package/package.json +2 -2
  99. package/src/core/experimental/frames/http-frame.test.ts +6 -1
  100. package/src/core/experimental/frames/http-frame.ts +6 -2
  101. package/src/core/experimental/handlers-controller.test.ts +139 -5
  102. package/src/core/experimental/handlers-controller.ts +24 -9
  103. package/src/core/handlers/RequestHandler.ts +36 -1
  104. package/src/core/handlers/WebSocketHandler.ts +1 -1
  105. package/src/core/utils/HttpResponse/decorators.ts +6 -21
  106. package/src/core/utils/internal/attachSiblingHandlers.ts +28 -0
  107. package/src/core/utils/request/storeResponseCookies.ts +2 -4
  108. package/src/core/ws.ts +65 -6
@@ -7,7 +7,7 @@
7
7
  * - Please do NOT modify this file.
8
8
  */
9
9
 
10
- const PACKAGE_VERSION = '2.13.6'
10
+ const PACKAGE_VERSION = '2.14.0'
11
11
  const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
12
12
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
13
  const activeClientIds = new Set()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw",
3
- "version": "2.13.6",
3
+ "version": "2.14.0",
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",
@@ -286,7 +286,7 @@
286
286
  "vitest-environment-miniflare": "^2.14.4",
287
287
  "webpack": "^5.106.2",
288
288
  "webpack-http-server": "^0.5.0",
289
- "msw": "^2.13.6"
289
+ "msw": "^2.14.0"
290
290
  },
291
291
  "peerDependencies": {
292
292
  "typescript": ">= 4.8.x"
@@ -4,7 +4,8 @@ import { ws } from '../../ws'
4
4
  import { bypass } from '../../bypass'
5
5
  import type { HttpNetworkFrameEventMap } from './http-frame'
6
6
  import { HttpNetworkFrame } from './http-frame'
7
- import { InMemoryHandlersController } from '#core/experimental/handlers-controller'
7
+ import { InMemoryHandlersController } from '../../experimental/handlers-controller'
8
+ import { getSiblingHandlers } from '../../utils/internal/attachSiblingHandlers'
8
9
 
9
10
  beforeAll(() => {
10
11
  vi.spyOn(console, 'error').mockImplementation(() => {})
@@ -46,6 +47,9 @@ it('filters only request type handlers', async () => {
46
47
  const webSocketHandlers = [
47
48
  ws.link('ws://localhost').addEventListener('connection', () => {}),
48
49
  ]
50
+ const webSocketSiblingHandlers = webSocketHandlers.flatMap((handler) =>
51
+ getSiblingHandlers(handler),
52
+ )
49
53
 
50
54
  const controller = new InMemoryHandlersController([
51
55
  ...httpHandlers,
@@ -55,6 +59,7 @@ it('filters only request type handlers', async () => {
55
59
 
56
60
  expect(frame.getHandlers(controller)).toEqual([
57
61
  ...httpHandlers,
62
+ ...webSocketSiblingHandlers,
58
63
  ...graphqlHandlers,
59
64
  ])
60
65
  expect(frame.getHandlers(new InMemoryHandlersController([]))).toEqual([])
@@ -251,9 +251,13 @@ export abstract class HttpNetworkFrame extends NetworkFrame<
251
251
  return null
252
252
  }
253
253
 
254
+ const responseCloneForLogs = resolutionContext?.quiet
255
+ ? null
256
+ : response.clone()
257
+
254
258
  await storeResponseCookies(request, response)
255
259
 
256
- this.respondWith(response.clone())
260
+ this.respondWith(response)
257
261
 
258
262
  this.events.emit(
259
263
  new RequestEvent('request:end', {
@@ -265,7 +269,7 @@ export abstract class HttpNetworkFrame extends NetworkFrame<
265
269
  if (!resolutionContext?.quiet) {
266
270
  handler.log({
267
271
  request: requestCloneForLogs!,
268
- response,
272
+ response: responseCloneForLogs!,
269
273
  parsedResult,
270
274
  })
271
275
  }
@@ -1,8 +1,68 @@
1
1
  import { http } from '../http'
2
2
  import { graphql } from '../graphql'
3
3
  import { ws } from '../ws'
4
+ import { getSiblingHandlers } from '../utils/internal/attachSiblingHandlers'
4
5
  import { InMemoryHandlersController } from './handlers-controller'
5
6
 
7
+ describe('constructor', () => {
8
+ it('places the sibling in its own kind bucket', () => {
9
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
10
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
11
+
12
+ const controller = new InMemoryHandlersController([wsHandler])
13
+
14
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
15
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
16
+ })
17
+
18
+ it('interleaves the sibling at the owner position when grouping by kind', () => {
19
+ const httpOne = http.get('/', () => {})
20
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
21
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
22
+ const httpTwo = http.get('/', () => {})
23
+
24
+ const controller = new InMemoryHandlersController([
25
+ httpOne,
26
+ wsHandler,
27
+ httpTwo,
28
+ ])
29
+
30
+ expect(controller.getHandlersByKind('request')).toEqual([
31
+ httpOne,
32
+ upgradeHandler,
33
+ httpTwo,
34
+ ])
35
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
36
+ })
37
+
38
+ it('extracts siblings from every owner in the input list', () => {
39
+ const wsOne = ws.link('*').addEventListener('connection', () => {})
40
+ const wsTwo = ws.link('*').addEventListener('connection', () => {})
41
+ const [upgradeOne] = getSiblingHandlers(wsOne)
42
+ const [upgradeTwo] = getSiblingHandlers(wsTwo)
43
+
44
+ const controller = new InMemoryHandlersController([wsOne, wsTwo])
45
+
46
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
47
+ expect(controller.getHandlersByKind('request')).toEqual([
48
+ upgradeOne,
49
+ upgradeTwo,
50
+ ])
51
+ })
52
+
53
+ it('dedupes the shared upgrade sibling across multiple handlers from the same link', () => {
54
+ const chat = ws.link('*')
55
+ const wsOne = chat.addEventListener('connection', () => {})
56
+ const wsTwo = chat.addEventListener('connection', () => {})
57
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
58
+
59
+ const controller = new InMemoryHandlersController([wsOne, wsTwo])
60
+
61
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
62
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
63
+ })
64
+ })
65
+
6
66
  describe(InMemoryHandlersController.prototype.use, () => {
7
67
  it('prepends a handler to an empty controller', () => {
8
68
  const controller = new InMemoryHandlersController([])
@@ -51,6 +111,44 @@ describe(InMemoryHandlersController.prototype.use, () => {
51
111
 
52
112
  expect(controller.currentHandlers()).toEqual([graphqlOne, httpTwo, httpOne])
53
113
  })
114
+
115
+ it('propagates siblings to their kind buckets at runtime', () => {
116
+ const controller = new InMemoryHandlersController([])
117
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
118
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
119
+
120
+ controller.use([wsHandler])
121
+
122
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
123
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
124
+ })
125
+
126
+ it('prepends incoming siblings before existing handlers of the same kind', () => {
127
+ const existingHttp = http.get('/existing', () => {})
128
+ const controller = new InMemoryHandlersController([existingHttp])
129
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
130
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
131
+
132
+ controller.use([wsHandler])
133
+
134
+ expect(controller.getHandlersByKind('request')).toEqual([
135
+ upgradeHandler,
136
+ existingHttp,
137
+ ])
138
+ })
139
+
140
+ it('dedupes the shared upgrade sibling when called with multiple handlers from the same link', () => {
141
+ const chat = ws.link('*')
142
+ const wsOne = chat.addEventListener('connection', () => {})
143
+ const wsTwo = chat.addEventListener('connection', () => {})
144
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
145
+
146
+ const controller = new InMemoryHandlersController([])
147
+ controller.use([wsOne, wsTwo])
148
+
149
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
150
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
151
+ })
54
152
  })
55
153
 
56
154
  describe(InMemoryHandlersController.prototype.reset, () => {
@@ -96,6 +194,42 @@ describe(InMemoryHandlersController.prototype.reset, () => {
96
194
  */
97
195
  expect(controller.currentHandlers()).toEqual([httpTwo])
98
196
  })
197
+
198
+ it('places siblings into their kind buckets when resetting to next handlers', () => {
199
+ const controller = new InMemoryHandlersController([])
200
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
201
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
202
+
203
+ controller.reset([wsHandler])
204
+
205
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
206
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
207
+ })
208
+
209
+ it('restores siblings when resetting to the initial handlers', () => {
210
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
211
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
212
+ const controller = new InMemoryHandlersController([wsHandler])
213
+
214
+ controller.use([http.get('/runtime', () => {})])
215
+ controller.reset([])
216
+
217
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
218
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
219
+ })
220
+
221
+ it('dedupes the shared upgrade sibling when reset with multiple handlers from the same link', () => {
222
+ const chat = ws.link('*')
223
+ const wsOne = chat.addEventListener('connection', () => {})
224
+ const wsTwo = chat.addEventListener('connection', () => {})
225
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
226
+
227
+ const controller = new InMemoryHandlersController([])
228
+ controller.reset([wsOne, wsTwo])
229
+
230
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
231
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
232
+ })
99
233
  })
100
234
 
101
235
  describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
@@ -112,11 +246,10 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
112
246
  ]).getHandlersByKind('websocket'),
113
247
  ).toEqual([])
114
248
 
249
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
115
250
  expect(
116
- new InMemoryHandlersController([
117
- ws.link('*').addEventListener('connection', () => {}),
118
- ]).getHandlersByKind('request'),
119
- ).toEqual([])
251
+ new InMemoryHandlersController([wsHandler]).getHandlersByKind('request'),
252
+ ).toEqual(getSiblingHandlers(wsHandler))
120
253
  })
121
254
 
122
255
  it('returns all handlers if they all match', () => {
@@ -142,6 +275,7 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
142
275
  const httpHandler = http.get('/', () => {})
143
276
  const graphqlHandler = graphql.query('', () => {})
144
277
  const wsHandler = ws.link('*').addEventListener('connection', () => {})
278
+ const wsHandlerSiblings = getSiblingHandlers(wsHandler)
145
279
 
146
280
  expect(
147
281
  new InMemoryHandlersController([
@@ -149,7 +283,7 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
149
283
  graphqlHandler,
150
284
  wsHandler,
151
285
  ]).getHandlersByKind('request'),
152
- ).toEqual([httpHandler, graphqlHandler])
286
+ ).toEqual([httpHandler, graphqlHandler, ...wsHandlerSiblings])
153
287
 
154
288
  expect(
155
289
  new InMemoryHandlersController([
@@ -2,6 +2,7 @@ import { invariant } from 'outvariant'
2
2
  import { type RequestHandler } from '../handlers/RequestHandler'
3
3
  import { type WebSocketHandler } from '../handlers/WebSocketHandler'
4
4
  import { devUtils } from '../utils/internal/devUtils'
5
+ import { getSiblingHandlers } from '../utils/internal/attachSiblingHandlers'
5
6
 
6
7
  export type AnyHandler = RequestHandler | WebSocketHandler
7
8
  export type HandlersMap = Partial<Record<AnyHandler['kind'], Array<AnyHandler>>>
@@ -9,11 +10,23 @@ export type HandlersMap = Partial<Record<AnyHandler['kind'], Array<AnyHandler>>>
9
10
  export function groupHandlersByKind(handlers: Array<AnyHandler>): HandlersMap {
10
11
  const groups: HandlersMap = {}
11
12
 
13
+ const pushUnique = (kind: AnyHandler['kind'], handler: AnyHandler) => {
14
+ const bucket = (groups[kind] ||= [])
15
+
16
+ if (!bucket.includes(handler)) {
17
+ bucket.push(handler)
18
+ }
19
+ }
20
+
12
21
  /**
13
22
  * @note `Object.groupBy` is not implemented in Node.js v20.
14
23
  */
15
24
  for (const handler of handlers) {
16
- ;(groups[handler.kind] ||= []).push(handler)
25
+ pushUnique(handler.kind, handler)
26
+
27
+ for (const sibling of getSiblingHandlers(handler)) {
28
+ pushUnique(sibling.kind, sibling)
29
+ }
17
30
  }
18
31
 
19
32
  return groups
@@ -69,14 +82,16 @@ export abstract class HandlersController {
69
82
  }
70
83
 
71
84
  const { handlers } = this.getState()
72
-
73
- // Iterate over next handlers and prepend them to their respective lists.
74
- // Iterate in a reverse order to the keep the order of the runtime handlers as provided.
75
- for (let i = nextHandlers.length - 1; i >= 0; i--) {
76
- const handler = nextHandlers[i]
77
- handlers[handler.kind] = handlers[handler.kind]
78
- ? [handler, ...handlers[handler.kind]!]
79
- : [handler]
85
+ const overrides = groupHandlersByKind(nextHandlers)
86
+
87
+ // Prepend overrides to their respective kind buckets so they take
88
+ // priority over existing handlers while preserving input order.
89
+ for (const kind in overrides) {
90
+ const overridesForKind = overrides[kind as AnyHandler['kind']]!
91
+ const existingForKind = handlers[kind as AnyHandler['kind']]
92
+ handlers[kind as AnyHandler['kind']] = existingForKind
93
+ ? [...overridesForKind, ...existingForKind]
94
+ : overridesForKind
80
95
  }
81
96
 
82
97
  this.setState({ handlers })
@@ -1,3 +1,4 @@
1
+ import { Headers as HeadersPolyfill } from 'headers-polyfill'
1
2
  import { getCallFrame } from '../utils/internal/getCallFrame'
2
3
  import {
3
4
  isIterable,
@@ -12,6 +13,7 @@ import {
12
13
  type DefaultUnsafeFetchResponse,
13
14
  } from '../HttpResponse'
14
15
  import type { GraphQLRequestBody } from './GraphQLHandler'
16
+ import { getRawSetCookie } from '../utils/HttpResponse/decorators'
15
17
 
16
18
  export type DefaultRequestMultipartBody = Record<
17
19
  string,
@@ -335,7 +337,7 @@ export abstract class RequestHandler<
335
337
  ...resolverExtras,
336
338
  requestId: args.requestId,
337
339
  request: args.request,
338
- }) as Promise<Response>
340
+ }) as Promise<Response | undefined>
339
341
  ).catch((errorOrResponse) => {
340
342
  // Allow throwing a Response instance in a response resolver.
341
343
  if (errorOrResponse instanceof Response) {
@@ -348,6 +350,10 @@ export abstract class RequestHandler<
348
350
 
349
351
  const mockedResponse = await mockedResponsePromise
350
352
 
353
+ if (mockedResponse) {
354
+ forwardResponseCookies(mockedResponse)
355
+ }
356
+
351
357
  const executionResult = this.createExecutionResult({
352
358
  // Pass the cloned request to the result so that logging
353
359
  // and other consumers could read its body once more.
@@ -416,3 +422,32 @@ export abstract class RequestHandler<
416
422
  }
417
423
  }
418
424
  }
425
+
426
+ /**
427
+ * Forwards the cookies from the given response to `document.cookie`.
428
+ */
429
+ export function forwardResponseCookies(response: Response): void {
430
+ // Cookie forwarding is only relevant in the browser.
431
+ if (typeof document === 'undefined') {
432
+ return
433
+ }
434
+
435
+ const responseCookies = getRawSetCookie(response)
436
+
437
+ if (!responseCookies) {
438
+ return
439
+ }
440
+
441
+ // Write the mocked response cookies to the document.
442
+ // Use `headers-polyfill` to get the Set-Cookie header value correctly.
443
+ // This is an alternative until TypeScript 5.2
444
+ // and Node.js v20 become the minimum supported versions
445
+ // and "Headers.prototype.getSetCookie" can be used directly.
446
+ const allResponseCookies = HeadersPolyfill.prototype.getSetCookie.call(
447
+ new Headers([['set-cookie', responseCookies]]),
448
+ )
449
+
450
+ for (const cookieString of allResponseCookies) {
451
+ document.cookie = cookieString
452
+ }
453
+ }
@@ -118,7 +118,7 @@ export class WebSocketHandler {
118
118
  params: parsedResult.match.params || {},
119
119
  }
120
120
 
121
- if (resolutionContext?.[kAutoConnect]) {
121
+ if (resolutionContext?.[kAutoConnect] ?? true) {
122
122
  if (this[kConnect](resolvedConnection)) {
123
123
  return resolvedConnection
124
124
  }
@@ -1,10 +1,9 @@
1
1
  import statuses from '../../../shims/statuses'
2
- import { Headers as HeadersPolyfill } from 'headers-polyfill'
3
2
  import type { HttpResponseInit } from '../../HttpResponse'
4
3
 
5
4
  const { message } = statuses
6
5
 
7
- export const kSetCookie = Symbol('kSetCookie')
6
+ const kSetCookie = Symbol('kSetCookie')
8
7
 
9
8
  export interface HttpResponseDecoratedInit extends HttpResponseInit {
10
9
  status: number
@@ -31,7 +30,7 @@ export function decorateResponse(
31
30
  response: Response,
32
31
  init: HttpResponseDecoratedInit,
33
32
  ): Response {
34
- // Allow to mock the response type.
33
+ // Allow mocking the response type.
35
34
  if (init.type) {
36
35
  Object.defineProperty(response, 'type', {
37
36
  value: init.type,
@@ -52,25 +51,11 @@ export function decorateResponse(
52
51
  enumerable: false,
53
52
  writable: false,
54
53
  })
55
-
56
- // Cookie forwarding is only relevant in the browser.
57
- if (typeof document !== 'undefined') {
58
- // Write the mocked response cookies to the document.
59
- // Use `headers-polyfill` to get the Set-Cookie header value correctly.
60
- // This is an alternative until TypeScript 5.2
61
- // and Node.js v20 become the minimum supported version
62
- // and getSetCookie in Headers can be used directly.
63
- const responseCookiePairs = HeadersPolyfill.prototype.getSetCookie.call(
64
- init.headers,
65
- )
66
-
67
- for (const cookieString of responseCookiePairs) {
68
- // No need to parse the cookie headers because it's defined
69
- // as the valid cookie string to begin with.
70
- document.cookie = cookieString
71
- }
72
- }
73
54
  }
74
55
 
75
56
  return response
76
57
  }
58
+
59
+ export function getRawSetCookie(response: Response): string | undefined {
60
+ return Reflect.get(response, kSetCookie)
61
+ }
@@ -0,0 +1,28 @@
1
+ import { invariant } from 'outvariant'
2
+ import type { AnyHandler } from '../../experimental/handlers-controller'
3
+
4
+ const kSiblingHandlers = Symbol('kSiblingHandlers')
5
+
6
+ export function attachSiblingHandlers<T extends AnyHandler>(
7
+ owner: T,
8
+ siblings: Array<AnyHandler>,
9
+ ): T {
10
+ invariant(
11
+ getSiblingHandlers(owner).length === 0,
12
+ 'Failed to merge handlers: the owner "%s" handler is already merged',
13
+ owner.kind,
14
+ )
15
+
16
+ Object.defineProperty(owner, kSiblingHandlers, {
17
+ value: siblings,
18
+ enumerable: false,
19
+ writable: false,
20
+ configurable: false,
21
+ })
22
+
23
+ return owner
24
+ }
25
+
26
+ export function getSiblingHandlers(owner: AnyHandler): Array<AnyHandler> {
27
+ return Reflect.get(owner, kSiblingHandlers) || []
28
+ }
@@ -1,5 +1,5 @@
1
1
  import { cookieStore } from '../cookieStore'
2
- import { kSetCookie } from '../HttpResponse/decorators'
2
+ import { getRawSetCookie } from '../HttpResponse/decorators'
3
3
 
4
4
  export async function storeResponseCookies(
5
5
  request: Request,
@@ -7,9 +7,7 @@ export async function storeResponseCookies(
7
7
  ): Promise<void> {
8
8
  // Grab the raw "Set-Cookie" response header provided
9
9
  // in the HeadersInit for this mocked response.
10
- const responseCookies = Reflect.get(response, kSetCookie) as
11
- | string
12
- | undefined
10
+ const responseCookies = getRawSetCookie(response)
13
11
 
14
12
  if (responseCookies) {
15
13
  await cookieStore.setCookie(responseCookies, request.url)
package/src/core/ws.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { invariant } from 'outvariant'
2
+ import { FetchResponse, resolveWebSocketUrl } from '@mswjs/interceptors'
2
3
  import type {
3
4
  WebSocketData,
4
5
  WebSocketClientConnectionProtocol,
@@ -9,8 +10,15 @@ import {
9
10
  type WebSocketHandlerEventMap,
10
11
  } from './handlers/WebSocketHandler'
11
12
  import { hasRefCounted } from './utils/internal/hasRefCounted'
12
- import { type Path, isPath } from './utils/matching/matchRequestUrl'
13
+ import {
14
+ type Path,
15
+ type PathParams,
16
+ isPath,
17
+ matchRequestUrl,
18
+ } from './utils/matching/matchRequestUrl'
13
19
  import { WebSocketClientManager } from './ws/WebSocketClientManager'
20
+ import { http } from './http'
21
+ import { attachSiblingHandlers } from './utils/internal/attachSiblingHandlers'
14
22
 
15
23
  const webSocketChannel = new BroadcastChannel('msw:websocket-client-manager')
16
24
 
@@ -100,18 +108,29 @@ function createWebSocketLinkHandler(url: Path): WebSocketLink {
100
108
 
101
109
  const clientManager = new WebSocketClientManager(webSocketChannel)
102
110
 
111
+ // The same upgrade handler instance is attached as a sibling to every
112
+ // WebSocketHandler returned by this link. `groupHandlersByKind` dedupes
113
+ // by reference, so it lands in the `request` bucket exactly once regardless
114
+ // of which subset of WS handlers the user ends up registering.
115
+ const upgradeHandler = http.get(({ request }) => {
116
+ return (
117
+ request.headers.get('upgrade')?.toLowerCase() === 'websocket' &&
118
+ matchRequestUrl(new URL(resolveWebSocketUrl(request.url)), url).matches
119
+ )
120
+ }, ws.onUpgrade)
121
+
103
122
  return {
104
123
  get clients() {
105
124
  return clientManager.clients
106
125
  },
107
126
  addEventListener(event, listener) {
108
- const handler = new WebSocketHandler(url)
127
+ const webSocketHandler = new WebSocketHandler(url)
109
128
 
110
129
  // Add the connection event listener for when the
111
130
  // handler matches and emits a connection event.
112
131
  // When that happens, store that connection in the
113
132
  // set of all connections for reference.
114
- handler[kEmitter].on('connection', async ({ client }) => {
133
+ webSocketHandler[kEmitter].on('connection', async ({ client }) => {
115
134
  await clientManager.addConnection(client)
116
135
  })
117
136
 
@@ -119,9 +138,9 @@ function createWebSocketLinkHandler(url: Path): WebSocketLink {
119
138
  // the "run()" method on the WebSocketHandler.
120
139
  // If the handler matches, it will emit the "connection"
121
140
  // event. Attach the user-defined listener to that event.
122
- handler[kEmitter].on(event, listener)
141
+ webSocketHandler[kEmitter].on(event, listener)
123
142
 
124
- return handler
143
+ return attachSiblingHandlers(webSocketHandler, [upgradeHandler])
125
144
  },
126
145
 
127
146
  broadcast(data) {
@@ -145,6 +164,24 @@ function createWebSocketLinkHandler(url: Path): WebSocketLink {
145
164
  }
146
165
  }
147
166
 
167
+ const WEBSOCKET_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
168
+
169
+ interface WebSocketNamespace {
170
+ link: typeof createWebSocketLinkHandler
171
+ /**
172
+ * Request handler for the `upgrade` requests to the WebSocket protocol.
173
+ * This requires a WebSocket handler to be present to fire.
174
+ * @note This only affects Node.js as the `upgrade` request header is
175
+ * forbidden and cannot be read in the browser. Consider using the
176
+ * `WebSocket` API for establishing WebSocket connections in the browser.
177
+ */
178
+ onUpgrade: (info: {
179
+ requestId: string
180
+ request: Request
181
+ params: PathParams
182
+ }) => Promise<Response | undefined> | Response | undefined
183
+ }
184
+
148
185
  /**
149
186
  * A namespace to intercept and mock WebSocket connections.
150
187
  *
@@ -154,8 +191,30 @@ function createWebSocketLinkHandler(url: Path): WebSocketLink {
154
191
  * @see {@link https://mswjs.io/docs/api/ws `ws` API reference}
155
192
  * @see {@link https://mswjs.io/docs/basics/handling-websocket-events Handling WebSocket events}
156
193
  */
157
- export const ws = {
194
+ export const ws: WebSocketNamespace = {
158
195
  link: createWebSocketLinkHandler,
196
+ async onUpgrade({ request }) {
197
+ const key = request.headers.get('sec-websocket-key')
198
+
199
+ if (!key) {
200
+ return
201
+ }
202
+
203
+ const keyBytes = new TextEncoder().encode(key + WEBSOCKET_GUID)
204
+ const digest = await crypto.subtle.digest('SHA-1', keyBytes)
205
+ const acceptValue = btoa(String.fromCharCode(...new Uint8Array(digest)))
206
+
207
+ new WebSocket(resolveWebSocketUrl(request.url))
208
+
209
+ return new FetchResponse(null, {
210
+ status: 101,
211
+ headers: {
212
+ upgrade: 'websocket',
213
+ connection: 'upgrade',
214
+ 'sec-websocket-accept': acceptValue,
215
+ },
216
+ })
217
+ },
159
218
  }
160
219
 
161
220
  export { type WebSocketData }