@mswjs/interceptors 0.12.6 → 0.13.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 (100) hide show
  1. package/README.md +64 -15
  2. package/lib/createInterceptor.d.ts +7 -0
  3. package/lib/createInterceptor.js.map +1 -1
  4. package/lib/interceptors/ClientRequest/NodeClientRequest.d.ts +38 -0
  5. package/lib/interceptors/ClientRequest/NodeClientRequest.js +420 -0
  6. package/lib/interceptors/ClientRequest/NodeClientRequest.js.map +1 -0
  7. package/lib/interceptors/ClientRequest/http.get.d.ts +5 -0
  8. package/lib/interceptors/ClientRequest/http.get.js +47 -0
  9. package/lib/interceptors/ClientRequest/http.get.js.map +1 -0
  10. package/lib/interceptors/ClientRequest/http.request.d.ts +5 -0
  11. package/lib/interceptors/ClientRequest/http.request.js +44 -0
  12. package/lib/interceptors/ClientRequest/http.request.js.map +1 -0
  13. package/lib/interceptors/ClientRequest/index.d.ts +5 -1
  14. package/lib/interceptors/ClientRequest/index.js +50 -84
  15. package/lib/interceptors/ClientRequest/index.js.map +1 -1
  16. package/lib/interceptors/ClientRequest/utils/cloneIncomingMessage.d.ts +7 -0
  17. package/lib/interceptors/ClientRequest/utils/cloneIncomingMessage.js +77 -0
  18. package/lib/interceptors/ClientRequest/utils/cloneIncomingMessage.js.map +1 -0
  19. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.d.ts +1 -1
  20. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js +21 -12
  21. package/lib/interceptors/ClientRequest/utils/getIncomingMessageBody.js.map +1 -1
  22. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs.d.ts +17 -0
  23. package/lib/interceptors/ClientRequest/utils/{normalizeHttpRequestParams.js → normalizeClientRequestArgs.js} +65 -39
  24. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestArgs.js.map +1 -0
  25. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.d.ts +15 -0
  26. package/lib/interceptors/ClientRequest/utils/{normalizeHttpRequestEndParams.js → normalizeClientRequestEndArgs.js} +5 -6
  27. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.js.map +1 -0
  28. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.d.ts +13 -0
  29. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.js +20 -0
  30. package/lib/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.js.map +1 -0
  31. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js +3 -2
  32. package/lib/interceptors/XMLHttpRequest/XMLHttpRequestOverride.js.map +1 -1
  33. package/lib/interceptors/fetch/index.js +6 -4
  34. package/lib/interceptors/fetch/index.js.map +1 -1
  35. package/lib/utils/getUrlByRequestOptions.d.ts +5 -4
  36. package/lib/utils/getUrlByRequestOptions.js.map +1 -1
  37. package/package.json +33 -18
  38. package/src/createInterceptor.ts +100 -0
  39. package/src/index.ts +5 -0
  40. package/src/interceptors/ClientRequest/NodeClientRequest.test.ts +283 -0
  41. package/src/interceptors/ClientRequest/NodeClientRequest.ts +377 -0
  42. package/src/interceptors/ClientRequest/http.get.ts +32 -0
  43. package/src/interceptors/ClientRequest/http.request.ts +29 -0
  44. package/src/interceptors/ClientRequest/index.ts +61 -0
  45. package/src/interceptors/ClientRequest/utils/bodyBufferToString.test.ts +16 -0
  46. package/src/interceptors/ClientRequest/utils/bodyBufferToString.ts +7 -0
  47. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.test.ts +20 -0
  48. package/src/interceptors/ClientRequest/utils/cloneIncomingMessage.ts +41 -0
  49. package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.test.ts +13 -0
  50. package/src/interceptors/ClientRequest/utils/concatChunkToBuffer.ts +10 -0
  51. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.test.ts +44 -0
  52. package/src/interceptors/ClientRequest/utils/getIncomingMessageBody.ts +38 -0
  53. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.test.ts +336 -0
  54. package/src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts +205 -0
  55. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.test.ts +40 -0
  56. package/src/interceptors/ClientRequest/utils/normalizeClientRequestEndArgs.ts +51 -0
  57. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.test.ts +35 -0
  58. package/src/interceptors/ClientRequest/utils/normalizeClientRequestWriteArgs.ts +36 -0
  59. package/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts +565 -0
  60. package/src/interceptors/XMLHttpRequest/index.ts +34 -0
  61. package/src/interceptors/XMLHttpRequest/polyfills/EventPolyfill.ts +51 -0
  62. package/src/interceptors/XMLHttpRequest/polyfills/ProgressEventPolyfill.ts +17 -0
  63. package/src/interceptors/XMLHttpRequest/utils/bufferFrom.test.ts +11 -0
  64. package/src/interceptors/XMLHttpRequest/utils/bufferFrom.ts +16 -0
  65. package/src/interceptors/XMLHttpRequest/utils/createEvent.test.ts +27 -0
  66. package/src/interceptors/XMLHttpRequest/utils/createEvent.ts +41 -0
  67. package/src/interceptors/fetch/index.ts +89 -0
  68. package/src/presets/browser.ts +8 -0
  69. package/src/presets/node.ts +8 -0
  70. package/src/remote.ts +176 -0
  71. package/src/utils/cloneObject.test.ts +93 -0
  72. package/src/utils/cloneObject.ts +34 -0
  73. package/src/utils/getCleanUrl.test.ts +31 -0
  74. package/src/utils/getCleanUrl.ts +6 -0
  75. package/src/utils/getRequestOptionsByUrl.ts +29 -0
  76. package/src/utils/getUrlByRequestOptions.test.ts +140 -0
  77. package/src/utils/getUrlByRequestOptions.ts +108 -0
  78. package/src/utils/isObject.test.ts +19 -0
  79. package/src/utils/isObject.ts +6 -0
  80. package/src/utils/parseJson.test.ts +9 -0
  81. package/src/utils/parseJson.ts +12 -0
  82. package/src/utils/toIsoResponse.test.ts +39 -0
  83. package/src/utils/toIsoResponse.ts +14 -0
  84. package/src/utils/uuid.ts +7 -0
  85. package/lib/interceptors/ClientRequest/ClientRequest.glossary.d.ts +0 -6
  86. package/lib/interceptors/ClientRequest/ClientRequest.glossary.js +0 -3
  87. package/lib/interceptors/ClientRequest/ClientRequest.glossary.js.map +0 -1
  88. package/lib/interceptors/ClientRequest/createClientRequestOverride.d.ts +0 -12
  89. package/lib/interceptors/ClientRequest/createClientRequestOverride.js +0 -340
  90. package/lib/interceptors/ClientRequest/createClientRequestOverride.js.map +0 -1
  91. package/lib/interceptors/ClientRequest/polyfills/SocketPolyfill.d.ts +0 -49
  92. package/lib/interceptors/ClientRequest/polyfills/SocketPolyfill.js +0 -118
  93. package/lib/interceptors/ClientRequest/polyfills/SocketPolyfill.js.map +0 -1
  94. package/lib/interceptors/ClientRequest/utils/inheritRequestHeaders.d.ts +0 -3
  95. package/lib/interceptors/ClientRequest/utils/inheritRequestHeaders.js +0 -34
  96. package/lib/interceptors/ClientRequest/utils/inheritRequestHeaders.js.map +0 -1
  97. package/lib/interceptors/ClientRequest/utils/normalizeHttpRequestEndParams.d.ts +0 -17
  98. package/lib/interceptors/ClientRequest/utils/normalizeHttpRequestEndParams.js.map +0 -1
  99. package/lib/interceptors/ClientRequest/utils/normalizeHttpRequestParams.d.ts +0 -11
  100. package/lib/interceptors/ClientRequest/utils/normalizeHttpRequestParams.js.map +0 -1
@@ -0,0 +1,38 @@
1
+ import { debug } from 'debug'
2
+ import { IncomingMessage } from 'http'
3
+ import { PassThrough } from 'stream'
4
+ import * as zlib from 'zlib'
5
+
6
+ const log = debug('http getIncomingMessageBody')
7
+
8
+ export function getIncomingMessageBody(
9
+ response: IncomingMessage
10
+ ): Promise<string> {
11
+ return new Promise((resolve, reject) => {
12
+ log('cloning the original response...')
13
+
14
+ // Pipe the original response to support non-clone
15
+ // "response" input. No need to clone the response,
16
+ // as we always have access to the full "response" input,
17
+ // either a clone or an original one (in tests).
18
+ const responseClone = response.pipe(new PassThrough())
19
+ const stream =
20
+ response.headers['content-encoding'] === 'gzip'
21
+ ? responseClone.pipe(zlib.createGunzip())
22
+ : responseClone
23
+
24
+ const encoding = response.readableEncoding || 'utf8'
25
+ stream.setEncoding(encoding)
26
+ log('using encoding:', encoding)
27
+
28
+ stream.once('data', (responseBody) => {
29
+ log('response body read:', responseBody)
30
+ resolve(responseBody)
31
+ })
32
+
33
+ stream.once('error', (error) => {
34
+ log('error while reading response body:', error)
35
+ reject(error)
36
+ })
37
+ })
38
+ }
@@ -0,0 +1,336 @@
1
+ import { parse } from 'url'
2
+ import { Agent as HttpsAgent } from 'https'
3
+ import { getUrlByRequestOptions } from '../../../utils/getUrlByRequestOptions'
4
+ import { normalizeClientRequestArgs } from './normalizeClientRequestArgs'
5
+
6
+ test('handles [string, callback] input', () => {
7
+ const [url, options, callback] = normalizeClientRequestArgs(
8
+ 'https:',
9
+ 'https://mswjs.io/resource',
10
+ function cb() {}
11
+ )
12
+
13
+ // URL string must be converted to a URL instance.
14
+ expect(url.href).toEqual('https://mswjs.io/resource')
15
+
16
+ // Request options must be derived from the URL instance.
17
+ expect(options).toHaveProperty('method', 'GET')
18
+ expect(options).toHaveProperty('protocol', 'https:')
19
+ expect(options).toHaveProperty('hostname', 'mswjs.io')
20
+ expect(options).toHaveProperty('path', '/resource')
21
+
22
+ // Callback must be preserved.
23
+ expect(callback?.name).toEqual('cb')
24
+ })
25
+
26
+ test('handles [string, RequestOptions, callback] input', () => {
27
+ const initialOptions = {
28
+ headers: {
29
+ 'Content-Type': 'text/plain',
30
+ },
31
+ }
32
+ const [url, options, callback] = normalizeClientRequestArgs(
33
+ 'https:',
34
+ 'https://mswjs.io/resource',
35
+ initialOptions,
36
+ function cb() {}
37
+ )
38
+
39
+ // URL must be created from the string.
40
+ expect(url.href).toEqual('https://mswjs.io/resource')
41
+
42
+ // Request options must be preserved.
43
+ expect(options).toHaveProperty('headers', initialOptions.headers)
44
+
45
+ // Callback must be preserved.
46
+ expect(callback?.name).toEqual('cb')
47
+ })
48
+
49
+ test('handles [URL, callback] input', () => {
50
+ const [url, options, callback] = normalizeClientRequestArgs(
51
+ 'https:',
52
+ new URL('https://mswjs.io/resource'),
53
+ function cb() {}
54
+ )
55
+
56
+ // URL must be preserved.
57
+ expect(url.href).toEqual('https://mswjs.io/resource')
58
+
59
+ // Request options must be derived from the URL instance.
60
+ expect(options.method).toEqual('GET')
61
+ expect(options.protocol).toEqual('https:')
62
+ expect(options.hostname).toEqual('mswjs.io')
63
+ expect(options.path).toEqual('/resource')
64
+
65
+ // Callback must be preserved.
66
+ expect(callback?.name).toEqual('cb')
67
+ })
68
+
69
+ test('handles [Absolute Legacy URL, callback] input', () => {
70
+ const [url, options, callback] = normalizeClientRequestArgs(
71
+ 'https:',
72
+ parse('https://cherry:durian@mswjs.io:12345/resource?apple=banana'),
73
+ function cb() {}
74
+ )
75
+
76
+ // URL must be preserved.
77
+ expect(url.toJSON()).toEqual(
78
+ new URL(
79
+ 'https://cherry:durian@mswjs.io:12345/resource?apple=banana'
80
+ ).toJSON()
81
+ )
82
+
83
+ // Request options must be derived from the URL instance.
84
+ expect(options.method).toEqual('GET')
85
+ expect(options.protocol).toEqual('https:')
86
+ expect(options.hostname).toEqual('mswjs.io')
87
+ expect(options.path).toEqual('/resource?apple=banana')
88
+ expect(options.port).toEqual(12345)
89
+ expect(options.auth).toEqual('cherry:durian')
90
+
91
+ // Callback must be preserved.
92
+ expect(callback?.name).toEqual('cb')
93
+ })
94
+
95
+ test('handles [Relative Legacy URL, RequestOptions without path set, callback] input', () => {
96
+ const [url, options, callback] = normalizeClientRequestArgs(
97
+ 'http:',
98
+ parse('/resource?apple=banana'),
99
+ { host: 'mswjs.io' },
100
+ function cb() {}
101
+ )
102
+
103
+ // Correct WHATWG URL generated.
104
+ expect(url.toJSON()).toEqual(
105
+ new URL('http://mswjs.io/resource?apple=banana').toJSON()
106
+ )
107
+
108
+ // No path in request options, so legacy url path is copied-in.
109
+ expect(options.protocol).toEqual('http:')
110
+ expect(options.host).toEqual('mswjs.io')
111
+ expect(options.path).toEqual('/resource?apple=banana')
112
+
113
+ // Callback must be preserved.
114
+ expect(callback?.name).toEqual('cb')
115
+ })
116
+
117
+ test('handles [Relative Legacy URL, RequestOptions with path set, callback] input', () => {
118
+ const [url, options, callback] = normalizeClientRequestArgs(
119
+ 'http:',
120
+ parse('/resource?apple=banana'),
121
+ { host: 'mswjs.io', path: '/other?cherry=durian' },
122
+ function cb() {}
123
+ )
124
+
125
+ // Correct WHATWG URL generated.
126
+ expect(url.toJSON()).toEqual(
127
+ new URL('http://mswjs.io/other?cherry=durian').toJSON()
128
+ )
129
+
130
+ // Path in request options, so that path is preferred.
131
+ expect(options.protocol).toEqual('http:')
132
+ expect(options.host).toEqual('mswjs.io')
133
+ expect(options.path).toEqual('/other?cherry=durian')
134
+
135
+ // Callback must be preserved.
136
+ expect(callback?.name).toEqual('cb')
137
+ })
138
+
139
+ test('handles [Relative Legacy URL, callback] input', () => {
140
+ const [url, options, callback] = normalizeClientRequestArgs(
141
+ 'http:',
142
+ parse('/resource?apple=banana'),
143
+ function cb() {}
144
+ )
145
+
146
+ // Correct WHATWG URL generated.
147
+ expect(url.toJSON()).toMatch(
148
+ getUrlByRequestOptions({ path: '/resource?apple=banana' }).toJSON()
149
+ )
150
+
151
+ // Check path is in options.
152
+ expect(options.protocol).toEqual('http:')
153
+ expect(options.path).toEqual('/resource?apple=banana')
154
+
155
+ // Callback must be preserved.
156
+ expect(callback?.name).toEqual('cb')
157
+ })
158
+
159
+ test('handles [Relative Legacy URL] input', () => {
160
+ const [url, options, callback] = normalizeClientRequestArgs(
161
+ 'http:',
162
+ parse('/resource?apple=banana')
163
+ )
164
+
165
+ // Correct WHATWG URL generated.
166
+ expect(url.toJSON()).toMatch(
167
+ getUrlByRequestOptions({ path: '/resource?apple=banana' }).toJSON()
168
+ )
169
+
170
+ // Check path is in options.
171
+ expect(options.protocol).toEqual('http:')
172
+ expect(options.path).toEqual('/resource?apple=banana')
173
+
174
+ // Callback must be preserved.
175
+ expect(callback).toBeUndefined()
176
+ })
177
+
178
+ test('handles [URL, RequestOptions, callback] input', () => {
179
+ const [url, options, callback] = normalizeClientRequestArgs(
180
+ 'https:',
181
+ new URL('https://mswjs.io/resource'),
182
+ {
183
+ agent: false,
184
+ headers: {
185
+ 'Content-Type': 'text/plain',
186
+ },
187
+ },
188
+ function cb() {}
189
+ )
190
+
191
+ // URL must be preserved.
192
+ expect(url.href).toEqual('https://mswjs.io/resource')
193
+
194
+ // Options must be preserved.
195
+ expect(options).toEqual({
196
+ agent: false,
197
+ protocol: url.protocol,
198
+ method: 'GET',
199
+ headers: {
200
+ 'Content-Type': 'text/plain',
201
+ },
202
+
203
+ host: url.host,
204
+ hostname: url.hostname,
205
+ path: url.pathname,
206
+ })
207
+
208
+ // Callback must be preserved.
209
+ expect(callback?.name).toEqual('cb')
210
+ })
211
+
212
+ test('handles [RequestOptions, callback] input', () => {
213
+ const initialOptions = {
214
+ method: 'POST',
215
+ protocol: 'https:',
216
+ host: 'mswjs.io',
217
+ /**
218
+ * @see https://github.com/mswjs/msw/issues/705
219
+ */
220
+ origin: 'https://mswjs.io',
221
+ path: '/resource',
222
+ headers: {
223
+ 'Content-Type': 'text/plain',
224
+ },
225
+ }
226
+ const [url, options, callback] = normalizeClientRequestArgs(
227
+ 'https:',
228
+ initialOptions,
229
+ function cb() {}
230
+ )
231
+
232
+ // URL must be derived from request options.
233
+ expect(url.href).toEqual('https://mswjs.io/resource')
234
+
235
+ // Request options must be preserved.
236
+ expect(options).toEqual(initialOptions)
237
+
238
+ // Callback must be preserved.
239
+ expect(callback?.name).toEqual('cb')
240
+ })
241
+
242
+ test('handles [Empty RequestOptions, callback] input', () => {
243
+ const [_, options, callback] = normalizeClientRequestArgs(
244
+ 'https:',
245
+ {},
246
+ function cb() {}
247
+ )
248
+
249
+ expect(options.protocol).toEqual('https:')
250
+
251
+ // Callback must be preserved
252
+ expect(callback?.name).toEqual('cb')
253
+ })
254
+
255
+ /**
256
+ * @see https://github.com/mswjs/interceptors/issues/19
257
+ */
258
+ test('handles [PartialRequestOptions, callback] input', () => {
259
+ const initialOptions = {
260
+ method: 'GET',
261
+ port: '50176',
262
+ path: '/resource',
263
+ host: '127.0.0.1',
264
+ ca: undefined,
265
+ key: undefined,
266
+ pfx: undefined,
267
+ cert: undefined,
268
+ passphrase: undefined,
269
+ agent: false,
270
+ }
271
+ const [url, options, callback] = normalizeClientRequestArgs(
272
+ 'https:',
273
+ initialOptions,
274
+ function cb() {}
275
+ )
276
+
277
+ // URL must be derived from request options.
278
+ expect(url.toJSON()).toEqual(
279
+ new URL('https://127.0.0.1:50176/resource').toJSON()
280
+ )
281
+
282
+ // Request options must be preserved.
283
+ expect(options).toEqual(initialOptions)
284
+
285
+ // Options protocol must be inferred from the request issuing module.
286
+ expect(options.protocol).toEqual('https:')
287
+
288
+ // Callback must be preserved.
289
+ expect(callback?.name).toEqual('cb')
290
+ })
291
+
292
+ test('sets fallback Agent based on the URL protocol', () => {
293
+ const [url, options] = normalizeClientRequestArgs(
294
+ 'https:',
295
+ 'https://github.com'
296
+ )
297
+
298
+ expect(options.agent).toBeInstanceOf(HttpsAgent)
299
+ expect(
300
+ // @ts-expect-error Protocol is an internal property.
301
+ options.agent.protocol
302
+ ).toEqual(url.protocol)
303
+ })
304
+
305
+ test('does not set any fallback Agent given "agent: false" option', () => {
306
+ const [, options] = normalizeClientRequestArgs(
307
+ 'https:',
308
+ 'https://github.com',
309
+ { agent: false }
310
+ )
311
+
312
+ expect(options.agent).toEqual(false)
313
+ })
314
+
315
+ test('merges URL-based RequestOptions with the custom RequestOptions', () => {
316
+ const [url, options] = normalizeClientRequestArgs(
317
+ 'https:',
318
+ 'https://github.com/graphql',
319
+ {
320
+ method: 'GET',
321
+ pfx: 'PFX_KEY',
322
+ }
323
+ )
324
+
325
+ expect(url.href).toEqual('https://github.com/graphql')
326
+
327
+ // Original request options must be preserved.
328
+ expect(options.method).toEqual('GET')
329
+ expect(options.pfx).toEqual('PFX_KEY')
330
+
331
+ // Other options must be inferred from the URL.
332
+ expect(options.protocol).toEqual(url.protocol)
333
+ expect(options.host).toEqual(url.host)
334
+ expect(options.hostname).toEqual(url.hostname)
335
+ expect(options.path).toEqual(url.pathname)
336
+ })
@@ -0,0 +1,205 @@
1
+ import { debug } from 'debug'
2
+ import { Agent as HttpAgent, IncomingMessage } from 'http'
3
+ import { RequestOptions, Agent as HttpsAgent } from 'https'
4
+ import { Url as LegacyURL } from 'url'
5
+ import { getRequestOptionsByUrl } from '../../../utils/getRequestOptionsByUrl'
6
+ import {
7
+ ResolvedRequestOptions,
8
+ getUrlByRequestOptions,
9
+ } from '../../../utils/getUrlByRequestOptions'
10
+ import { cloneObject } from '../../../utils/cloneObject'
11
+ import { isObject } from '../../../utils/isObject'
12
+
13
+ const log = debug('http normalizeClientRequestArgs')
14
+
15
+ export type HttpRequestCallback = (response: IncomingMessage) => void
16
+
17
+ export type ClientRequestArgs =
18
+ | [string | URL | LegacyURL, HttpRequestCallback?]
19
+ | [string | URL | LegacyURL, RequestOptions, HttpRequestCallback?]
20
+ | [RequestOptions, HttpRequestCallback?]
21
+
22
+ function resolveRequestOptions(
23
+ args: ClientRequestArgs,
24
+ url: URL
25
+ ): RequestOptions {
26
+ // Calling `fetch` provides only URL to `ClientRequest`
27
+ // without any `RequestOptions` or callback.
28
+ if (typeof args[1] === 'undefined' || typeof args[1] === 'function') {
29
+ log('request options not provided, deriving from the url', url)
30
+ return getRequestOptionsByUrl(url)
31
+ }
32
+
33
+ if (args[1]) {
34
+ log('has custom RequestOptions!', args[1])
35
+ const requestOptionsFromUrl = getRequestOptionsByUrl(url)
36
+
37
+ log('derived RequestOptions from the URL:', requestOptionsFromUrl)
38
+
39
+ /**
40
+ * Clone the request options to lock their state
41
+ * at the moment they are provided to `ClientRequest`.
42
+ * @see https://github.com/mswjs/interceptors/issues/86
43
+ */
44
+ log('cloning RequestOptions...')
45
+ const clonedRequestOptions = cloneObject(args[1])
46
+ log('successfully cloned RequestOptions!', clonedRequestOptions)
47
+
48
+ return {
49
+ ...requestOptionsFromUrl,
50
+ ...clonedRequestOptions,
51
+ }
52
+ }
53
+
54
+ log('using an empty object as request options')
55
+ return {} as RequestOptions
56
+ }
57
+
58
+ function resolveCallback(
59
+ args: ClientRequestArgs
60
+ ): HttpRequestCallback | undefined {
61
+ return typeof args[1] === 'function' ? args[1] : args[2]
62
+ }
63
+
64
+ export type NormalizedClientRequestArgs = [
65
+ url: URL,
66
+ options: ResolvedRequestOptions,
67
+ callback?: HttpRequestCallback
68
+ ]
69
+
70
+ /**
71
+ * Normalizes parameters given to a `http.request` call
72
+ * so it always has a `URL` and `RequestOptions`.
73
+ */
74
+ export function normalizeClientRequestArgs(
75
+ defaultProtocol: string,
76
+ ...args: ClientRequestArgs
77
+ ): NormalizedClientRequestArgs {
78
+ let url: URL
79
+ let options: ResolvedRequestOptions
80
+ let callback: HttpRequestCallback | undefined
81
+
82
+ log('arguments', args)
83
+ log('using default protocol:', defaultProtocol)
84
+
85
+ // Convert a url string into a URL instance
86
+ // and derive request options from it.
87
+ if (typeof args[0] === 'string') {
88
+ log('first argument is a location string:', args[0])
89
+
90
+ url = new URL(args[0])
91
+ log('created a url:', url)
92
+
93
+ const requestOptionsFromUrl = getRequestOptionsByUrl(url)
94
+ log('request options from url:', requestOptionsFromUrl)
95
+
96
+ options = resolveRequestOptions(args, url)
97
+ log('resolved request options:', options)
98
+
99
+ callback = resolveCallback(args)
100
+ }
101
+ // Handle a given URL instance as-is
102
+ // and derive request options from it.
103
+ else if (args[0] instanceof URL) {
104
+ url = args[0]
105
+ log('first argument is a URL:', url)
106
+
107
+ options = resolveRequestOptions(args, url)
108
+ log('derived request options:', options)
109
+
110
+ callback = resolveCallback(args)
111
+ }
112
+ // Handle a legacy URL instance and re-normalize from either a RequestOptions object
113
+ // or a WHATWG URL.
114
+ else if ('hash' in args[0] && !('method' in args[0])) {
115
+ const [legacyUrl] = args
116
+ log('first argument is a legacy URL:', legacyUrl)
117
+
118
+ if (legacyUrl.hostname === null) {
119
+ /**
120
+ * We are dealing with a relative url, so use the path as an "option" and
121
+ * merge in any existing options, giving priority to exising options -- i.e. a path in any
122
+ * existing options will take precedence over the one contained in the url. This is consistent
123
+ * with the behaviour in ClientRequest.
124
+ * @see https://github.com/nodejs/node/blob/d84f1312915fe45fe0febe888db692c74894c382/lib/_http_client.js#L122
125
+ */
126
+ log('given legacy URL is relative (no hostname)')
127
+
128
+ return isObject(args[1])
129
+ ? normalizeClientRequestArgs(
130
+ defaultProtocol,
131
+ { path: legacyUrl.path, ...args[1] },
132
+ args[2]
133
+ )
134
+ : normalizeClientRequestArgs(
135
+ defaultProtocol,
136
+ { path: legacyUrl.path },
137
+ args[1] as HttpRequestCallback
138
+ )
139
+ }
140
+
141
+ log('given legacy url is absolute')
142
+
143
+ // We are dealing with an absolute URL, so convert to WHATWG and try again.
144
+ const resolvedUrl = new URL(legacyUrl.href)
145
+
146
+ return args[1] === undefined
147
+ ? normalizeClientRequestArgs(defaultProtocol, resolvedUrl)
148
+ : typeof args[1] === 'function'
149
+ ? normalizeClientRequestArgs(defaultProtocol, resolvedUrl, args[1])
150
+ : normalizeClientRequestArgs(
151
+ defaultProtocol,
152
+ resolvedUrl,
153
+ args[1],
154
+ args[2]
155
+ )
156
+ }
157
+ // Handle a given "RequestOptions" object as-is
158
+ // and derive the URL instance from it.
159
+ else if (isObject(args[0])) {
160
+ options = args[0]
161
+ log('first argument is RequestOptions:', options)
162
+
163
+ // When handling a "RequestOptions" object without an explicit "protocol",
164
+ // infer the protocol from the request issuing module (http/https).
165
+ options.protocol = options.protocol || defaultProtocol
166
+ log('normalized request options:', options)
167
+
168
+ url = getUrlByRequestOptions(options)
169
+ log('created a URL from RequestOptions:', url.href)
170
+
171
+ callback = resolveCallback(args)
172
+ } else {
173
+ throw new Error(
174
+ `Failed to construct ClientRequest with these parameters: ${args}`
175
+ )
176
+ }
177
+
178
+ options.protocol = options.protocol || url.protocol
179
+ options.method = options.method || 'GET'
180
+
181
+ /**
182
+ * Infer a fallback agent from the URL protocol.
183
+ * The interception is done on the "ClientRequest" level ("NodeClientRequest")
184
+ * and it may miss the correct agent. Always align the agent
185
+ * with the URL protocol, if not provided.
186
+ *
187
+ * @note Respect the "agent: false" value.
188
+ */
189
+ if (typeof options.agent === 'undefined') {
190
+ const agent =
191
+ options.protocol === 'https:'
192
+ ? new HttpsAgent({
193
+ rejectUnauthorized: options.rejectUnauthorized,
194
+ })
195
+ : new HttpAgent()
196
+
197
+ options.agent = agent
198
+ log('resolved fallback agent:', agent)
199
+ }
200
+
201
+ log('successfully resolved url:', url.href)
202
+ log('successfully resolved options:', options)
203
+
204
+ return [url, options, callback]
205
+ }
@@ -0,0 +1,40 @@
1
+ import { normalizeClientRequestEndArgs } from './normalizeClientRequestEndArgs'
2
+
3
+ test('returns [null, null, cb] given only the callback', () => {
4
+ const callback = () => {}
5
+ expect(normalizeClientRequestEndArgs(callback)).toEqual([
6
+ null,
7
+ null,
8
+ callback,
9
+ ])
10
+ })
11
+
12
+ test('returns [chunk, null, null] given only the chunk', () => {
13
+ expect(normalizeClientRequestEndArgs('chunk')).toEqual(['chunk', null, null])
14
+ })
15
+
16
+ test('returns [chunk, cb] given the chunk and the callback', () => {
17
+ const callback = () => {}
18
+ expect(normalizeClientRequestEndArgs('chunk', callback)).toEqual([
19
+ 'chunk',
20
+ null,
21
+ callback,
22
+ ])
23
+ })
24
+
25
+ test('returns [chunk, encoding] given the chunk with the encoding', () => {
26
+ expect(normalizeClientRequestEndArgs('chunk', 'utf8')).toEqual([
27
+ 'chunk',
28
+ 'utf8',
29
+ null,
30
+ ])
31
+ })
32
+
33
+ test('returns [chunk, encoding, cb] given all three arguments', () => {
34
+ const callback = () => {}
35
+ expect(normalizeClientRequestEndArgs('chunk', 'utf8', callback)).toEqual([
36
+ 'chunk',
37
+ 'utf8',
38
+ callback,
39
+ ])
40
+ })
@@ -0,0 +1,51 @@
1
+ const debug = require('debug')('http normalizeClientRequestEndArgs')
2
+
3
+ export type ClientRequestEndChunk = string | Buffer
4
+ type ClientRequestEndCallback = () => void
5
+
6
+ type HttpRequestEndArgs =
7
+ | []
8
+ | [ClientRequestEndCallback]
9
+ | [ClientRequestEndChunk, ClientRequestEndCallback?]
10
+ | [ClientRequestEndChunk, BufferEncoding, ClientRequestEndCallback?]
11
+
12
+ type NormalizedHttpRequestEndParams = [
13
+ ClientRequestEndChunk | null,
14
+ BufferEncoding | null,
15
+ ClientRequestEndCallback | null
16
+ ]
17
+
18
+ /**
19
+ * Normalizes a list of arguments given to the `ClientRequest.end()`
20
+ * method to always include `chunk`, `encoding`, and `callback`.
21
+ */
22
+ export function normalizeClientRequestEndArgs(
23
+ ...args: HttpRequestEndArgs
24
+ ): NormalizedHttpRequestEndParams {
25
+ debug('arguments', args)
26
+ const normalizedArgs = new Array(3)
27
+ .fill(null)
28
+ .map((value, index) => args[index] || value)
29
+
30
+ normalizedArgs.sort((a, b) => {
31
+ // If first element is a function, move it rightwards.
32
+ if (typeof a === 'function') {
33
+ return 1
34
+ }
35
+
36
+ // If second element is a function, move the first leftwards.
37
+ if (typeof b === 'function') {
38
+ return -1
39
+ }
40
+
41
+ // If both elements are strings, preserve their original index.
42
+ if (typeof a === 'string' && typeof b === 'string') {
43
+ return normalizedArgs.indexOf(a) - normalizedArgs.indexOf(b)
44
+ }
45
+
46
+ return 0
47
+ })
48
+
49
+ debug('normalized args', normalizedArgs)
50
+ return normalizedArgs as NormalizedHttpRequestEndParams
51
+ }
@@ -0,0 +1,35 @@
1
+ import { normalizeClientRequestWriteArgs } from './normalizeClientRequestWriteArgs'
2
+
3
+ test('returns a triplet of null given no chunk, encoding, or callback', () => {
4
+ expect(
5
+ normalizeClientRequestWriteArgs([
6
+ // @ts-ignore
7
+ undefined,
8
+ undefined,
9
+ undefined,
10
+ ])
11
+ ).toEqual([undefined, undefined, undefined])
12
+ })
13
+
14
+ test('returns [chunk, null, null] given only a chunk', () => {
15
+ expect(normalizeClientRequestWriteArgs(['chunk', undefined])).toEqual([
16
+ 'chunk',
17
+ undefined,
18
+ undefined,
19
+ ])
20
+ })
21
+
22
+ test('returns [chunk, encoding] given only chunk and encoding', () => {
23
+ expect(normalizeClientRequestWriteArgs(['chunk', 'utf8'])).toEqual([
24
+ 'chunk',
25
+ 'utf8',
26
+ undefined,
27
+ ])
28
+ })
29
+
30
+ test('returns [chunk, encoding, cb] given all three arguments', () => {
31
+ const callbackFn = () => {}
32
+ expect(
33
+ normalizeClientRequestWriteArgs(['chunk', 'utf8', callbackFn])
34
+ ).toEqual(['chunk', 'utf8', callbackFn])
35
+ })