undici 5.1.0 → 5.3.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.
- package/README.md +16 -0
- package/docs/api/Dispatcher.md +3 -1
- package/docs/api/Errors.md +0 -1
- package/docs/best-practices/mocking-request.md +32 -0
- package/index-fetch.js +13 -0
- package/index.d.ts +1 -0
- package/index.js +2 -14
- package/lib/api/api-request.js +12 -3
- package/lib/client.js +1 -23
- package/lib/core/errors.js +14 -11
- package/lib/core/request.js +7 -3
- package/lib/core/util.js +47 -1
- package/lib/fetch/file.js +0 -8
- package/lib/fetch/formdata.js +3 -5
- package/lib/fetch/headers.js +75 -54
- package/lib/fetch/index.js +8 -15
- package/lib/fetch/request.js +4 -15
- package/lib/fetch/response.js +106 -63
- package/lib/fetch/util.js +20 -43
- package/lib/global.js +32 -0
- package/lib/mock/mock-agent.js +6 -0
- package/lib/mock/mock-utils.js +26 -7
- package/lib/proxy-agent.js +0 -1
- package/package.json +8 -5
- package/types/diagnostics-channel.d.ts +66 -0
- package/types/dispatcher.d.ts +4 -0
- package/types/fetch.d.ts +33 -17
package/README.md
CHANGED
|
@@ -194,6 +194,21 @@ Basic usage example:
|
|
|
194
194
|
}
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
You can pass an optional dispatcher to `fetch` as:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { fetch, Agent } from 'undici'
|
|
201
|
+
|
|
202
|
+
const res = await fetch('https://example.com', {
|
|
203
|
+
// Mocks are also supported
|
|
204
|
+
dispatcher: new Agent({
|
|
205
|
+
keepAliveTimeout: 10,
|
|
206
|
+
keepAliveMaxTimeout: 10
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
const json = await res.json()
|
|
210
|
+
console.log(json)
|
|
211
|
+
```
|
|
197
212
|
|
|
198
213
|
#### `request.body`
|
|
199
214
|
|
|
@@ -360,6 +375,7 @@ Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
|
|
|
360
375
|
* [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup>
|
|
361
376
|
* [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood>
|
|
362
377
|
* [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina>
|
|
378
|
+
* [__Matthew Aitken__](https://github.com/KhafraDev), <https://www.npmjs.com/~khaf>
|
|
363
379
|
* [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>
|
|
364
380
|
* [__Szymon Marczak__](https://github.com/szmarczak), <https://www.npmjs.com/~szmarczak>
|
|
365
381
|
* [__Tomas Della Vedova__](https://github.com/delvedor), <https://www.npmjs.com/~delvedor>
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -194,18 +194,20 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
|
|
|
194
194
|
* **method** `string`
|
|
195
195
|
* **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null`
|
|
196
196
|
* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`.
|
|
197
|
+
* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead.
|
|
197
198
|
* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed.
|
|
198
199
|
* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received.
|
|
199
200
|
* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`.
|
|
200
201
|
* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 30 seconds.
|
|
201
202
|
* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers. Defaults to 30 seconds.
|
|
203
|
+
* **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server.
|
|
202
204
|
|
|
203
205
|
#### Parameter: `DispatchHandler`
|
|
204
206
|
|
|
205
207
|
* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
|
|
206
208
|
* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
|
|
207
209
|
* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
|
|
208
|
-
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
|
|
210
|
+
* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
|
|
209
211
|
* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests.
|
|
210
212
|
* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
|
|
211
213
|
* **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent.
|
package/docs/api/Errors.md
CHANGED
|
@@ -19,7 +19,6 @@ import { errors } from 'undici'
|
|
|
19
19
|
| `RequestContentLengthMismatchError` | `UND_ERR_REQ_CONTENT_LENGTH_MISMATCH` | request body does not match content-length header |
|
|
20
20
|
| `ResponseContentLengthMismatchError` | `UND_ERR_RES_CONTENT_LENGTH_MISMATCH` | response body does not match content-length header |
|
|
21
21
|
| `InformationalError` | `UND_ERR_INFO` | expected error with reason |
|
|
22
|
-
| `TrailerMismatchError` | `UND_ERR_TRAILER_MISMATCH` | trailers did not match specification |
|
|
23
22
|
|
|
24
23
|
### `SocketError`
|
|
25
24
|
|
|
@@ -101,4 +101,36 @@ const badRequest = await bankTransfer('1234567890', '100')
|
|
|
101
101
|
// subsequent request to origin http://localhost:3000 was not allowed (net.connect disabled)
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
## Reply with data based on request
|
|
104
105
|
|
|
106
|
+
If the mocked response needs to be dynamically derived from the request parameters, you can provide a function instead of an object to `reply`
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
mockPool.intercept({
|
|
110
|
+
path: '/bank-transfer',
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers: {
|
|
113
|
+
'X-TOKEN-SECRET': 'SuperSecretToken',
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({
|
|
116
|
+
recepient: '1234567890',
|
|
117
|
+
amount: '100'
|
|
118
|
+
})
|
|
119
|
+
}).reply(200, (opts) => {
|
|
120
|
+
// do something with opts
|
|
121
|
+
|
|
122
|
+
return { message: 'transaction processed' }
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
in this case opts will be
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
{
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: { 'X-TOKEN-SECRET': 'SuperSecretToken' },
|
|
132
|
+
body: '{"recepient":"1234567890","amount":"100"}',
|
|
133
|
+
origin: 'http://localhost:3000',
|
|
134
|
+
path: '/bank-transfer'
|
|
135
|
+
}
|
|
136
|
+
```
|
package/index-fetch.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { getGlobalDispatcher } = require('./lib/global')
|
|
4
|
+
const fetchImpl = require('./lib/fetch')
|
|
5
|
+
|
|
6
|
+
module.exports.fetch = async function fetch (resource) {
|
|
7
|
+
const dispatcher = (arguments[1] && arguments[1].dispatcher) || getGlobalDispatcher()
|
|
8
|
+
return fetchImpl.apply(dispatcher, arguments)
|
|
9
|
+
}
|
|
10
|
+
module.exports.FormData = require('./lib/fetch/formdata').FormData
|
|
11
|
+
module.exports.Headers = require('./lib/fetch/headers').Headers
|
|
12
|
+
module.exports.Response = require('./lib/fetch/response').Response
|
|
13
|
+
module.exports.Request = require('./lib/fetch/request').Request
|
package/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { request, pipeline, stream, connect, upgrade } from './types/api'
|
|
|
16
16
|
export * from './types/fetch'
|
|
17
17
|
export * from './types/file'
|
|
18
18
|
export * from './types/formdata'
|
|
19
|
+
export * from './types/diagnostics-channel'
|
|
19
20
|
export { Interceptable } from './types/mock-interceptor'
|
|
20
21
|
|
|
21
22
|
export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
|
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const MockAgent = require('./lib/mock/mock-agent')
|
|
|
15
15
|
const MockPool = require('./lib/mock/mock-pool')
|
|
16
16
|
const mockErrors = require('./lib/mock/mock-errors')
|
|
17
17
|
const ProxyAgent = require('./lib/proxy-agent')
|
|
18
|
+
const { getGlobalDispatcher, setGlobalDispatcher } = require('./lib/global')
|
|
18
19
|
|
|
19
20
|
const nodeVersion = process.versions.node.split('.')
|
|
20
21
|
const nodeMajor = Number(nodeVersion[0])
|
|
@@ -32,19 +33,6 @@ module.exports.ProxyAgent = ProxyAgent
|
|
|
32
33
|
module.exports.buildConnector = buildConnector
|
|
33
34
|
module.exports.errors = errors
|
|
34
35
|
|
|
35
|
-
let globalDispatcher = new Agent()
|
|
36
|
-
|
|
37
|
-
function setGlobalDispatcher (agent) {
|
|
38
|
-
if (!agent || typeof agent.dispatch !== 'function') {
|
|
39
|
-
throw new InvalidArgumentError('Argument agent must implement Agent')
|
|
40
|
-
}
|
|
41
|
-
globalDispatcher = agent
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getGlobalDispatcher () {
|
|
45
|
-
return globalDispatcher
|
|
46
|
-
}
|
|
47
|
-
|
|
48
36
|
function makeDispatcher (fn) {
|
|
49
37
|
return (url, opts, handler) => {
|
|
50
38
|
if (typeof opts === 'function') {
|
|
@@ -98,7 +86,7 @@ if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5)) {
|
|
|
98
86
|
if (!fetchImpl) {
|
|
99
87
|
fetchImpl = require('./lib/fetch')
|
|
100
88
|
}
|
|
101
|
-
const dispatcher = getGlobalDispatcher()
|
|
89
|
+
const dispatcher = (arguments[1] && arguments[1].dispatcher) || getGlobalDispatcher()
|
|
102
90
|
return fetchImpl.apply(dispatcher, arguments)
|
|
103
91
|
}
|
|
104
92
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
package/lib/api/api-request.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const Readable = require('./readable')
|
|
4
4
|
const {
|
|
5
5
|
InvalidArgumentError,
|
|
6
|
-
RequestAbortedError
|
|
6
|
+
RequestAbortedError,
|
|
7
|
+
ResponseStatusCodeError
|
|
7
8
|
} = require('../core/errors')
|
|
8
9
|
const util = require('../core/util')
|
|
9
10
|
const { AsyncResource } = require('async_hooks')
|
|
@@ -15,7 +16,7 @@ class RequestHandler extends AsyncResource {
|
|
|
15
16
|
throw new InvalidArgumentError('invalid opts')
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
19
|
+
const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
22
|
if (typeof callback !== 'function') {
|
|
@@ -51,6 +52,7 @@ class RequestHandler extends AsyncResource {
|
|
|
51
52
|
this.trailers = {}
|
|
52
53
|
this.context = null
|
|
53
54
|
this.onInfo = onInfo || null
|
|
55
|
+
this.throwOnError = throwOnError
|
|
54
56
|
|
|
55
57
|
if (util.isStream(body)) {
|
|
56
58
|
body.on('error', (err) => {
|
|
@@ -70,7 +72,7 @@ class RequestHandler extends AsyncResource {
|
|
|
70
72
|
this.context = context
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
onHeaders (statusCode, rawHeaders, resume) {
|
|
75
|
+
onHeaders (statusCode, rawHeaders, resume, statusMessage) {
|
|
74
76
|
const { callback, opaque, abort, context } = this
|
|
75
77
|
|
|
76
78
|
if (statusCode < 200) {
|
|
@@ -89,6 +91,13 @@ class RequestHandler extends AsyncResource {
|
|
|
89
91
|
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
90
92
|
|
|
91
93
|
if (callback !== null) {
|
|
94
|
+
if (this.throwOnError && statusCode >= 400) {
|
|
95
|
+
this.runInAsyncScope(callback, null,
|
|
96
|
+
new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
92
101
|
this.runInAsyncScope(callback, null, null, {
|
|
93
102
|
statusCode,
|
|
94
103
|
headers,
|
package/lib/client.js
CHANGED
|
@@ -11,7 +11,6 @@ const RedirectHandler = require('./handler/redirect')
|
|
|
11
11
|
const {
|
|
12
12
|
RequestContentLengthMismatchError,
|
|
13
13
|
ResponseContentLengthMismatchError,
|
|
14
|
-
TrailerMismatchError,
|
|
15
14
|
InvalidArgumentError,
|
|
16
15
|
RequestAbortedError,
|
|
17
16
|
HeadersTimeoutError,
|
|
@@ -425,7 +424,6 @@ class Parser {
|
|
|
425
424
|
|
|
426
425
|
this.bytesRead = 0
|
|
427
426
|
|
|
428
|
-
this.trailer = ''
|
|
429
427
|
this.keepAlive = ''
|
|
430
428
|
this.contentLength = ''
|
|
431
429
|
}
|
|
@@ -615,8 +613,6 @@ class Parser {
|
|
|
615
613
|
const key = this.headers[len - 2]
|
|
616
614
|
if (key.length === 10 && key.toString().toLowerCase() === 'keep-alive') {
|
|
617
615
|
this.keepAlive += buf.toString()
|
|
618
|
-
} else if (key.length === 7 && key.toString().toLowerCase() === 'trailer') {
|
|
619
|
-
this.trailer += buf.toString()
|
|
620
616
|
} else if (key.length === 14 && key.toString().toLowerCase() === 'content-length') {
|
|
621
617
|
this.contentLength += buf.toString()
|
|
622
618
|
}
|
|
@@ -819,7 +815,7 @@ class Parser {
|
|
|
819
815
|
}
|
|
820
816
|
|
|
821
817
|
onMessageComplete () {
|
|
822
|
-
const { client, socket, statusCode, upgrade,
|
|
818
|
+
const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this
|
|
823
819
|
|
|
824
820
|
if (socket.destroyed && (!statusCode || shouldKeepAlive)) {
|
|
825
821
|
return -1
|
|
@@ -838,7 +834,6 @@ class Parser {
|
|
|
838
834
|
this.statusText = ''
|
|
839
835
|
this.bytesRead = 0
|
|
840
836
|
this.contentLength = ''
|
|
841
|
-
this.trailer = ''
|
|
842
837
|
this.keepAlive = ''
|
|
843
838
|
|
|
844
839
|
assert(this.headers.length % 2 === 0)
|
|
@@ -849,23 +844,6 @@ class Parser {
|
|
|
849
844
|
return
|
|
850
845
|
}
|
|
851
846
|
|
|
852
|
-
const trailers = trailer ? trailer.split(/,\s*/) : []
|
|
853
|
-
for (let i = 0; i < trailers.length; i++) {
|
|
854
|
-
const trailer = trailers[i]
|
|
855
|
-
let found = false
|
|
856
|
-
for (let n = 0; n < headers.length; n += 2) {
|
|
857
|
-
const key = headers[n]
|
|
858
|
-
if (key.length === trailer.length && key.toString().toLowerCase() === trailer.toLowerCase()) {
|
|
859
|
-
found = true
|
|
860
|
-
break
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
if (!found) {
|
|
864
|
-
util.destroy(socket, new TrailerMismatchError())
|
|
865
|
-
return -1
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
847
|
/* istanbul ignore next: should be handled by llhttp? */
|
|
870
848
|
if (request.method !== 'HEAD' && contentLength && bytesRead !== parseInt(contentLength, 10)) {
|
|
871
849
|
util.destroy(socket, new ResponseContentLengthMismatchError())
|
package/lib/core/errors.js
CHANGED
|
@@ -56,6 +56,19 @@ class BodyTimeoutError extends UndiciError {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
class ResponseStatusCodeError extends UndiciError {
|
|
60
|
+
constructor (message, statusCode, headers) {
|
|
61
|
+
super(message)
|
|
62
|
+
Error.captureStackTrace(this, ResponseStatusCodeError)
|
|
63
|
+
this.name = 'ResponseStatusCodeError'
|
|
64
|
+
this.message = message || 'Response Status Code Error'
|
|
65
|
+
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
|
66
|
+
this.status = statusCode
|
|
67
|
+
this.statusCode = statusCode
|
|
68
|
+
this.headers = headers
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
class InvalidArgumentError extends UndiciError {
|
|
60
73
|
constructor (message) {
|
|
61
74
|
super(message)
|
|
@@ -116,16 +129,6 @@ class ResponseContentLengthMismatchError extends UndiciError {
|
|
|
116
129
|
}
|
|
117
130
|
}
|
|
118
131
|
|
|
119
|
-
class TrailerMismatchError extends UndiciError {
|
|
120
|
-
constructor (message) {
|
|
121
|
-
super(message)
|
|
122
|
-
Error.captureStackTrace(this, TrailerMismatchError)
|
|
123
|
-
this.name = 'TrailerMismatchError'
|
|
124
|
-
this.message = message || 'Trailers does not match trailer header'
|
|
125
|
-
this.code = 'UND_ERR_TRAILER_MISMATCH'
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
132
|
class ClientDestroyedError extends UndiciError {
|
|
130
133
|
constructor (message) {
|
|
131
134
|
super(message)
|
|
@@ -196,7 +199,7 @@ module.exports = {
|
|
|
196
199
|
BodyTimeoutError,
|
|
197
200
|
RequestContentLengthMismatchError,
|
|
198
201
|
ConnectTimeoutError,
|
|
199
|
-
|
|
202
|
+
ResponseStatusCodeError,
|
|
200
203
|
InvalidArgumentError,
|
|
201
204
|
InvalidReturnValueError,
|
|
202
205
|
RequestAbortedError,
|
package/lib/core/request.js
CHANGED
|
@@ -4,8 +4,8 @@ const {
|
|
|
4
4
|
InvalidArgumentError,
|
|
5
5
|
NotSupportedError
|
|
6
6
|
} = require('./errors')
|
|
7
|
-
const util = require('./util')
|
|
8
7
|
const assert = require('assert')
|
|
8
|
+
const util = require('./util')
|
|
9
9
|
|
|
10
10
|
const kHandler = Symbol('handler')
|
|
11
11
|
|
|
@@ -38,11 +38,13 @@ class Request {
|
|
|
38
38
|
method,
|
|
39
39
|
body,
|
|
40
40
|
headers,
|
|
41
|
+
query,
|
|
41
42
|
idempotent,
|
|
42
43
|
blocking,
|
|
43
44
|
upgrade,
|
|
44
45
|
headersTimeout,
|
|
45
|
-
bodyTimeout
|
|
46
|
+
bodyTimeout,
|
|
47
|
+
throwOnError
|
|
46
48
|
}, handler) {
|
|
47
49
|
if (typeof path !== 'string') {
|
|
48
50
|
throw new InvalidArgumentError('path must be a string')
|
|
@@ -70,6 +72,8 @@ class Request {
|
|
|
70
72
|
|
|
71
73
|
this.bodyTimeout = bodyTimeout
|
|
72
74
|
|
|
75
|
+
this.throwOnError = throwOnError === true
|
|
76
|
+
|
|
73
77
|
this.method = method
|
|
74
78
|
|
|
75
79
|
if (body == null) {
|
|
@@ -97,7 +101,7 @@ class Request {
|
|
|
97
101
|
|
|
98
102
|
this.upgrade = upgrade || null
|
|
99
103
|
|
|
100
|
-
this.path = path
|
|
104
|
+
this.path = query ? util.buildURL(path, query) : path
|
|
101
105
|
|
|
102
106
|
this.origin = origin
|
|
103
107
|
|
package/lib/core/util.js
CHANGED
|
@@ -26,6 +26,51 @@ function isBlobLike (object) {
|
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function isObject (val) {
|
|
30
|
+
return val !== null && typeof val === 'object'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// this escapes all non-uri friendly characters
|
|
34
|
+
function encode (val) {
|
|
35
|
+
return encodeURIComponent(val)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// based on https://github.com/axios/axios/blob/63e559fa609c40a0a460ae5d5a18c3470ffc6c9e/lib/helpers/buildURL.js (MIT license)
|
|
39
|
+
function buildURL (url, queryParams) {
|
|
40
|
+
if (url.includes('?') || url.includes('#')) {
|
|
41
|
+
throw new Error('Query params cannot be passed when url already contains "?" or "#".')
|
|
42
|
+
}
|
|
43
|
+
if (!isObject(queryParams)) {
|
|
44
|
+
throw new Error('Query params must be an object')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const parts = []
|
|
48
|
+
for (let [key, val] of Object.entries(queryParams)) {
|
|
49
|
+
if (val === null || typeof val === 'undefined') {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!Array.isArray(val)) {
|
|
54
|
+
val = [val]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const v of val) {
|
|
58
|
+
if (isObject(v)) {
|
|
59
|
+
throw new Error('Passing object as a query param is not supported, please serialize to string up-front')
|
|
60
|
+
}
|
|
61
|
+
parts.push(encode(key) + '=' + encode(v))
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const serializedParams = parts.join('&')
|
|
66
|
+
|
|
67
|
+
if (serializedParams) {
|
|
68
|
+
url += '?' + serializedParams
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return url
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
function parseURL (url) {
|
|
30
75
|
if (typeof url === 'string') {
|
|
31
76
|
url = new URL(url)
|
|
@@ -357,5 +402,6 @@ module.exports = {
|
|
|
357
402
|
isBuffer,
|
|
358
403
|
validateHandler,
|
|
359
404
|
getSocketInfo,
|
|
360
|
-
isFormDataLike
|
|
405
|
+
isFormDataLike,
|
|
406
|
+
buildURL
|
|
361
407
|
}
|
package/lib/fetch/file.js
CHANGED
|
@@ -69,10 +69,6 @@ class File extends Blob {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
get [Symbol.toStringTag] () {
|
|
72
|
-
if (!(this instanceof File)) {
|
|
73
|
-
throw new TypeError('Illegal invocation')
|
|
74
|
-
}
|
|
75
|
-
|
|
76
72
|
return this.constructor.name
|
|
77
73
|
}
|
|
78
74
|
}
|
|
@@ -190,10 +186,6 @@ class FileLike {
|
|
|
190
186
|
}
|
|
191
187
|
|
|
192
188
|
get [Symbol.toStringTag] () {
|
|
193
|
-
if (!(this instanceof FileLike)) {
|
|
194
|
-
throw new TypeError('Illegal invocation')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
189
|
return 'File'
|
|
198
190
|
}
|
|
199
191
|
}
|
package/lib/fetch/formdata.js
CHANGED
|
@@ -6,6 +6,8 @@ const { File, FileLike } = require('./file')
|
|
|
6
6
|
const { Blob } = require('buffer')
|
|
7
7
|
|
|
8
8
|
class FormData {
|
|
9
|
+
static name = 'FormData'
|
|
10
|
+
|
|
9
11
|
constructor (...args) {
|
|
10
12
|
if (args.length > 0 && !(args[0]?.constructor?.name === 'HTMLFormElement')) {
|
|
11
13
|
throw new TypeError(
|
|
@@ -182,10 +184,6 @@ class FormData {
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
get [Symbol.toStringTag] () {
|
|
185
|
-
if (!(this instanceof FormData)) {
|
|
186
|
-
throw new TypeError('Illegal invocation')
|
|
187
|
-
}
|
|
188
|
-
|
|
189
187
|
return this.constructor.name
|
|
190
188
|
}
|
|
191
189
|
|
|
@@ -269,4 +267,4 @@ function makeEntry (name, value, filename) {
|
|
|
269
267
|
return entry
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
module.exports = { FormData
|
|
270
|
+
module.exports = { FormData }
|