@mswjs/interceptors 0.16.5 → 0.17.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 (50) hide show
  1. package/README.md +177 -56
  2. package/lib/InteractiveIsomorphicRequest.d.ts +7 -0
  3. package/lib/InteractiveIsomorphicRequest.js +37 -0
  4. package/lib/InteractiveIsomorphicRequest.js.map +1 -0
  5. package/lib/IsomorphicRequest.d.ts +24 -0
  6. package/lib/IsomorphicRequest.js +107 -0
  7. package/lib/IsomorphicRequest.js.map +1 -0
  8. package/lib/RemoteHttpInterceptor.d.ts +1 -1
  9. package/lib/RemoteHttpInterceptor.js +12 -8
  10. package/lib/RemoteHttpInterceptor.js.map +1 -1
  11. package/lib/glossary.d.ts +3 -17
  12. package/lib/glossary.js +2 -0
  13. package/lib/glossary.js.map +1 -1
  14. package/lib/index.d.ts +2 -0
  15. package/lib/index.js +2 -0
  16. package/lib/index.js.map +1 -1
  17. package/lib/interceptors/ClientRequest/NodeClientRequest.js +9 -27
  18. package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -1
  19. package/lib/interceptors/ClientRequest/index.d.ts +5 -2
  20. package/lib/interceptors/ClientRequest/index.js +11 -0
  21. package/lib/interceptors/ClientRequest/index.js.map +1 -1
  22. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
  23. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.d.ts +1 -2
  24. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +19 -27
  25. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
  26. package/lib/interceptors/XMLHttpRequest/index.d.ts +2 -1
  27. package/lib/interceptors/XMLHttpRequest/index.js +11 -0
  28. package/lib/interceptors/XMLHttpRequest/index.js.map +1 -1
  29. package/lib/interceptors/fetch/index.d.ts +1 -1
  30. package/lib/interceptors/fetch/index.js +33 -25
  31. package/lib/interceptors/fetch/index.js.map +1 -1
  32. package/lib/utils/bufferUtils.d.ts +3 -0
  33. package/lib/utils/bufferUtils.js +20 -0
  34. package/lib/utils/bufferUtils.js.map +1 -0
  35. package/package.json +3 -2
  36. package/src/InteractiveIsomorphicRequest.ts +24 -0
  37. package/src/IsomorphicRequest.test.ts +106 -0
  38. package/src/IsomorphicRequest.ts +86 -0
  39. package/src/RemoteHttpInterceptor.ts +18 -18
  40. package/src/glossary.ts +4 -19
  41. package/src/index.ts +2 -0
  42. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +2 -8
  43. package/src/interceptors/ClientRequest/NodeClientRequest.ts +14 -35
  44. package/src/interceptors/ClientRequest/index.ts +26 -2
  45. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +4 -4
  46. package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +21 -23
  47. package/src/interceptors/XMLHttpRequest/index.ts +18 -4
  48. package/src/interceptors/fetch/index.ts +47 -20
  49. package/src/utils/bufferUtils.test.ts +20 -0
  50. package/src/utils/bufferUtils.ts +19 -0
@@ -11,14 +11,14 @@ import {
11
11
  headersToString,
12
12
  } from 'headers-polyfill'
13
13
  import { DOMParser } from '@xmldom/xmldom'
14
- import { InteractiveIsomorphicRequest, IsomorphicRequest } from '../../glossary'
15
14
  import { parseJson } from '../../utils/parseJson'
16
15
  import { toIsoResponse } from '../../utils/toIsoResponse'
17
- import { uuidv4 } from '../../utils/uuid'
18
16
  import { bufferFrom } from './utils/bufferFrom'
19
17
  import { createEvent } from './utils/createEvent'
20
18
  import type { XMLHttpRequestEmitter } from '.'
21
- import { createLazyCallback } from '../../utils/createLazyCallback'
19
+ import { IsomorphicRequest } from '../../IsomorphicRequest'
20
+ import { encodeBuffer } from '../../utils/bufferUtils'
21
+ import { InteractiveIsomorphicRequest } from '../../InteractiveIsomorphicRequest'
22
22
 
23
23
  type XMLHttpRequestEventHandler = (
24
24
  this: XMLHttpRequest,
@@ -84,7 +84,6 @@ export const createXMLHttpRequestOverride = (
84
84
  public statusText: string
85
85
  public user?: string
86
86
  public password?: string
87
- public data: string
88
87
  public async?: boolean
89
88
  public response: any
90
89
  public responseText: string
@@ -132,7 +131,6 @@ export const createXMLHttpRequestOverride = (
132
131
  this.withCredentials = false
133
132
  this.status = 200
134
133
  this.statusText = 'OK'
135
- this.data = ''
136
134
  this.response = ''
137
135
  this.responseType = 'text'
138
136
  this.responseText = ''
@@ -154,7 +152,7 @@ export const createXMLHttpRequestOverride = (
154
152
  this.readyState = nextState
155
153
 
156
154
  if (nextState !== this.UNSENT) {
157
- this.log('triggerring readystate change...')
155
+ this.log('triggering readystate change...')
158
156
  this.trigger('readystatechange')
159
157
  }
160
158
  }
@@ -195,7 +193,6 @@ export const createXMLHttpRequestOverride = (
195
193
  this.setReadyState(this.UNSENT)
196
194
  this.status = 200
197
195
  this.statusText = 'OK'
198
- this.data = ''
199
196
  this.response = null as any
200
197
  this.responseText = null as any
201
198
  this.responseXML = null as any
@@ -229,10 +226,14 @@ export const createXMLHttpRequestOverride = (
229
226
  }
230
227
  }
231
228
 
232
- public send(data?: string) {
229
+ public send(data?: string | ArrayBuffer) {
233
230
  this.log('send %s %s', this.method, this.url)
234
-
235
- this.data = data || ''
231
+ let buffer: ArrayBuffer
232
+ if (typeof data === 'string') {
233
+ buffer = encodeBuffer(data)
234
+ } else {
235
+ buffer = data || new ArrayBuffer(0)
236
+ }
236
237
 
237
238
  let url: URL
238
239
 
@@ -248,19 +249,16 @@ export const createXMLHttpRequestOverride = (
248
249
  this.log('request headers', this._requestHeaders)
249
250
 
250
251
  // Create an intercepted request instance exposed to the request intercepting middleware.
251
- const isomorphicRequest: IsomorphicRequest = {
252
- id: uuidv4(),
253
- url,
252
+ const isomorphicRequest = new IsomorphicRequest(url, {
253
+ body: buffer,
254
254
  method: this.method,
255
255
  headers: this._requestHeaders,
256
256
  credentials: this.withCredentials ? 'include' : 'omit',
257
- body: this.data,
258
- }
257
+ })
259
258
 
260
- const interactiveIsomorphicRequest: InteractiveIsomorphicRequest = {
261
- ...isomorphicRequest,
262
- respondWith: createLazyCallback(),
263
- }
259
+ const interactiveIsomorphicRequest = new InteractiveIsomorphicRequest(
260
+ isomorphicRequest
261
+ )
264
262
 
265
263
  this.log(
266
264
  'emitting the "request" event for %d listener(s)...',
@@ -331,8 +329,8 @@ export const createXMLHttpRequestOverride = (
331
329
  if (mockedResponse.body && this.response) {
332
330
  this.setReadyState(this.LOADING)
333
331
 
334
- // Presense of the mocked response implies a response body (not null).
335
- // Presense of the coerced `this.response` implies the mocked body is valid.
332
+ // Presence of the mocked response implies a response body (not null).
333
+ // Presence of the coerced `this.response` implies the mocked body is valid.
336
334
  const bodyBuffer = bufferFrom(mockedResponse.body)
337
335
 
338
336
  // Trigger a progress event based on the mocked response body.
@@ -428,8 +426,8 @@ export const createXMLHttpRequestOverride = (
428
426
  originalRequest.timeout = this.timeout
429
427
  }
430
428
 
431
- this.log('send', this.data)
432
- originalRequest.send(this.data)
429
+ this.log('send', data)
430
+ originalRequest.send(data)
433
431
  }
434
432
  })
435
433
  }
@@ -1,7 +1,6 @@
1
- import type {
2
- HttpRequestEventMap,
3
- InteractiveIsomorphicRequest,
4
- } from '../../glossary'
1
+ import { invariant } from 'outvariant'
2
+ import { HttpRequestEventMap, IS_PATCHED_MODULE } from '../../glossary'
3
+ import { InteractiveIsomorphicRequest } from '../../InteractiveIsomorphicRequest'
5
4
  import { Interceptor } from '../../Interceptor'
6
5
  import { AsyncEventEmitter } from '../../utils/AsyncEventEmitter'
7
6
  import { createXMLHttpRequestOverride } from './XMLHttpRequestOverride'
@@ -33,6 +32,11 @@ export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap>
33
32
 
34
33
  const PureXMLHttpRequest = window.XMLHttpRequest
35
34
 
35
+ invariant(
36
+ !(PureXMLHttpRequest as any)[IS_PATCHED_MODULE],
37
+ 'Failed to patch the "XMLHttpRequest" module: already patched.'
38
+ )
39
+
36
40
  window.XMLHttpRequest = createXMLHttpRequestOverride({
37
41
  XMLHttpRequest: PureXMLHttpRequest,
38
42
  emitter: this.emitter,
@@ -41,7 +45,17 @@ export class XMLHttpRequestInterceptor extends Interceptor<HttpRequestEventMap>
41
45
 
42
46
  log('native "XMLHttpRequest" module patched!', window.XMLHttpRequest.name)
43
47
 
48
+ Object.defineProperty(window.XMLHttpRequest, IS_PATCHED_MODULE, {
49
+ enumerable: true,
50
+ configurable: true,
51
+ value: true,
52
+ })
53
+
44
54
  this.subscriptions.push(() => {
55
+ Object.defineProperty(window.XMLHttpRequest, IS_PATCHED_MODULE, {
56
+ value: undefined,
57
+ })
58
+
45
59
  window.XMLHttpRequest = PureXMLHttpRequest
46
60
  log(
47
61
  'native "XMLHttpRequest" module restored!',
@@ -4,15 +4,16 @@ import {
4
4
  objectToHeaders,
5
5
  headersToObject,
6
6
  } from 'headers-polyfill'
7
- import type {
7
+ import { invariant } from 'outvariant'
8
+ import { IsomorphicRequest } from '../../IsomorphicRequest'
9
+ import {
8
10
  HttpRequestEventMap,
9
- InteractiveIsomorphicRequest,
10
11
  IsomorphicResponse,
12
+ IS_PATCHED_MODULE,
11
13
  } from '../../glossary'
12
14
  import { Interceptor } from '../../Interceptor'
13
- import { createLazyCallback } from '../../utils/createLazyCallback'
14
15
  import { toIsoResponse } from '../../utils/toIsoResponse'
15
- import { uuidv4 } from '../../utils/uuid'
16
+ import { InteractiveIsomorphicRequest } from '../../InteractiveIsomorphicRequest'
16
17
 
17
18
  export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
18
19
  static symbol = Symbol('fetch')
@@ -31,6 +32,11 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
31
32
  protected setup() {
32
33
  const pureFetch = globalThis.fetch
33
34
 
35
+ invariant(
36
+ !(pureFetch as any)[IS_PATCHED_MODULE],
37
+ 'Failed to patch the "fetch" module: already patched.'
38
+ )
39
+
34
40
  globalThis.fetch = async (input, init) => {
35
41
  const request = new Request(input, init)
36
42
 
@@ -39,32 +45,38 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
39
45
 
40
46
  this.log('[%s] %s', method, url)
41
47
 
42
- const isomorphicRequest: InteractiveIsomorphicRequest = {
43
- id: uuidv4(),
44
- url: new URL(url, location.origin),
45
- method: method,
46
- headers: new Headers(request.headers),
47
- credentials: request.credentials,
48
- body: await request.clone().text(),
49
- respondWith: createLazyCallback(),
50
- }
48
+ const body = await request.clone().arrayBuffer()
49
+ const isomorphicRequest = new IsomorphicRequest(
50
+ new URL(url, location.origin),
51
+ {
52
+ body,
53
+ method,
54
+ headers: new Headers(request.headers),
55
+ credentials: request.credentials,
56
+ }
57
+ )
58
+
59
+ const interactiveIsomorphicRequest = new InteractiveIsomorphicRequest(
60
+ isomorphicRequest
61
+ )
51
62
 
52
- this.log('isomorphic request', isomorphicRequest)
63
+ this.log('isomorphic request', interactiveIsomorphicRequest)
53
64
 
54
65
  this.log(
55
66
  'emitting the "request" event for %d listener(s)...',
56
67
  this.emitter.listenerCount('request')
57
68
  )
58
- this.emitter.emit('request', isomorphicRequest)
69
+ this.emitter.emit('request', interactiveIsomorphicRequest)
59
70
 
60
71
  this.log('awaiting for the mocked response...')
61
72
 
62
73
  await this.emitter.untilIdle('request', ({ args: [request] }) => {
63
- return request.id === isomorphicRequest.id
74
+ return request.id === interactiveIsomorphicRequest.id
64
75
  })
65
76
  this.log('all request listeners have been resolved!')
66
77
 
67
- const [mockedResponse] = await isomorphicRequest.respondWith.invoked()
78
+ const [mockedResponse] =
79
+ await interactiveIsomorphicRequest.respondWith.invoked()
68
80
  this.log('event.respondWith called with:', mockedResponse)
69
81
 
70
82
  if (mockedResponse) {
@@ -73,7 +85,11 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
73
85
  const isomorphicResponse = toIsoResponse(mockedResponse)
74
86
  this.log('derived isomorphic response:', isomorphicResponse)
75
87
 
76
- this.emitter.emit('response', isomorphicRequest, isomorphicResponse)
88
+ this.emitter.emit(
89
+ 'response',
90
+ interactiveIsomorphicRequest,
91
+ isomorphicResponse
92
+ )
77
93
 
78
94
  const response = new Response(mockedResponse.body, {
79
95
  ...isomorphicResponse,
@@ -88,7 +104,7 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
88
104
  writable: false,
89
105
  enumerable: true,
90
106
  configurable: false,
91
- value: isomorphicRequest.url.href,
107
+ value: interactiveIsomorphicRequest.url.href,
92
108
  })
93
109
 
94
110
  return response
@@ -102,15 +118,26 @@ export class FetchInterceptor extends Interceptor<HttpRequestEventMap> {
102
118
 
103
119
  this.emitter.emit(
104
120
  'response',
105
- isomorphicRequest,
121
+ interactiveIsomorphicRequest,
106
122
  await normalizeFetchResponse(cloneResponse)
107
123
  )
108
124
  return response
109
125
  })
110
126
  }
111
127
 
128
+ Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
129
+ enumerable: true,
130
+ configurable: true,
131
+ value: true,
132
+ })
133
+
112
134
  this.subscriptions.push(() => {
135
+ Object.defineProperty(globalThis.fetch, IS_PATCHED_MODULE, {
136
+ value: undefined,
137
+ })
138
+
113
139
  globalThis.fetch = pureFetch
140
+
114
141
  this.log('restored native "globalThis.fetch"!', globalThis.fetch.name)
115
142
  })
116
143
  }
@@ -0,0 +1,20 @@
1
+ import { decodeBuffer, encodeBuffer } from './bufferUtils'
2
+
3
+ it('encodes utf-8 string', () => {
4
+ const encoded = encodeBuffer('😁')
5
+ expect(new Uint8Array(encoded)).toEqual(new Uint8Array([240, 159, 152, 129]))
6
+ })
7
+
8
+ it('decodes utf-8 string', () => {
9
+ const array = new Uint8Array([240, 159, 152, 129])
10
+ const decoded = decodeBuffer(array.buffer)
11
+ expect(decoded).toEqual('😁')
12
+ })
13
+
14
+ it('decodes string with custom encoding', () => {
15
+ const array = new Uint8Array([
16
+ 207, 240, 232, 226, 229, 242, 44, 32, 236, 232, 240, 33,
17
+ ])
18
+ const decoded = decodeBuffer(array.buffer, 'windows-1251')
19
+ expect(decoded).toEqual('Привет, мир!')
20
+ })
@@ -0,0 +1,19 @@
1
+ import { TextDecoder, TextEncoder } from 'web-encoding'
2
+
3
+ export function encodeBuffer(text: string): ArrayBuffer {
4
+ const encoder = new TextEncoder()
5
+ const encoded = encoder.encode(text)
6
+ return getArrayBuffer(encoded)
7
+ }
8
+
9
+ export function decodeBuffer(buffer: ArrayBuffer, encoding?: string): string {
10
+ const decoder = new TextDecoder(encoding)
11
+ return decoder.decode(buffer)
12
+ }
13
+
14
+ export function getArrayBuffer(array: Uint8Array): ArrayBuffer {
15
+ return array.buffer.slice(
16
+ array.byteOffset,
17
+ array.byteOffset + array.byteLength
18
+ )
19
+ }