undici 7.12.0 → 7.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.
- package/README.md +16 -14
- package/docs/docs/api/DiagnosticsChannel.md +25 -1
- package/docs/docs/api/ProxyAgent.md +1 -1
- package/docs/docs/api/SnapshotAgent.md +616 -0
- package/index.js +2 -0
- package/lib/api/readable.js +48 -26
- package/lib/core/util.js +0 -1
- package/lib/dispatcher/proxy-agent.js +68 -73
- package/lib/handler/cache-handler.js +22 -4
- package/lib/handler/redirect-handler.js +10 -0
- package/lib/interceptor/cache.js +2 -2
- package/lib/interceptor/dump.js +2 -1
- package/lib/mock/mock-agent.js +10 -4
- package/lib/mock/snapshot-agent.js +347 -0
- package/lib/mock/snapshot-recorder.js +580 -0
- package/lib/mock/snapshot-utils.js +158 -0
- package/lib/util/cache.js +3 -3
- package/lib/web/cache/cache.js +4 -4
- package/lib/web/eventsource/eventsource.js +17 -2
- package/lib/web/fetch/body.js +0 -1
- package/lib/web/fetch/formdata-parser.js +0 -3
- package/lib/web/fetch/formdata.js +1 -5
- package/lib/web/fetch/response.js +8 -4
- package/lib/web/webidl/index.js +1 -1
- package/lib/web/websocket/stream/websocketstream.js +2 -2
- package/lib/web/websocket/websocket.js +11 -4
- package/package.json +5 -5
- package/types/agent.d.ts +0 -4
- package/types/client.d.ts +0 -2
- package/types/dispatcher.d.ts +0 -6
- package/types/eventsource.d.ts +6 -1
- package/types/h2c-client.d.ts +0 -2
- package/types/index.d.ts +6 -1
- package/types/mock-interceptor.d.ts +0 -1
- package/types/snapshot-agent.d.ts +107 -0
package/lib/api/readable.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Ported from https://github.com/nodejs/undici/pull/907
|
|
2
|
-
|
|
3
1
|
'use strict'
|
|
4
2
|
|
|
5
3
|
const assert = require('node:assert')
|
|
@@ -50,23 +48,32 @@ class BodyReadable extends Readable {
|
|
|
50
48
|
|
|
51
49
|
this[kAbort] = abort
|
|
52
50
|
|
|
53
|
-
/**
|
|
54
|
-
* @type {Consume | null}
|
|
55
|
-
*/
|
|
51
|
+
/** @type {Consume | null} */
|
|
56
52
|
this[kConsume] = null
|
|
53
|
+
|
|
54
|
+
/** @type {number} */
|
|
57
55
|
this[kBytesRead] = 0
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*/
|
|
56
|
+
|
|
57
|
+
/** @type {ReadableStream|null} */
|
|
61
58
|
this[kBody] = null
|
|
59
|
+
|
|
60
|
+
/** @type {boolean} */
|
|
62
61
|
this[kUsed] = false
|
|
62
|
+
|
|
63
|
+
/** @type {string} */
|
|
63
64
|
this[kContentType] = contentType
|
|
65
|
+
|
|
66
|
+
/** @type {number|null} */
|
|
64
67
|
this[kContentLength] = Number.isFinite(contentLength) ? contentLength : null
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Is stream being consumed through Readable API?
|
|
71
|
+
* This is an optimization so that we avoid checking
|
|
72
|
+
* for 'data' and 'readable' listeners in the hot path
|
|
73
|
+
* inside push().
|
|
74
|
+
*
|
|
75
|
+
* @type {boolean}
|
|
76
|
+
*/
|
|
70
77
|
this[kReading] = false
|
|
71
78
|
}
|
|
72
79
|
|
|
@@ -96,7 +103,7 @@ class BodyReadable extends Readable {
|
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
/**
|
|
99
|
-
* @param {string} event
|
|
106
|
+
* @param {string|symbol} event
|
|
100
107
|
* @param {(...args: any[]) => void} listener
|
|
101
108
|
* @returns {this}
|
|
102
109
|
*/
|
|
@@ -109,7 +116,7 @@ class BodyReadable extends Readable {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
/**
|
|
112
|
-
* @param {string} event
|
|
119
|
+
* @param {string|symbol} event
|
|
113
120
|
* @param {(...args: any[]) => void} listener
|
|
114
121
|
* @returns {this}
|
|
115
122
|
*/
|
|
@@ -147,12 +154,14 @@ class BodyReadable extends Readable {
|
|
|
147
154
|
* @returns {boolean}
|
|
148
155
|
*/
|
|
149
156
|
push (chunk) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
if (chunk) {
|
|
158
|
+
this[kBytesRead] += chunk.length
|
|
159
|
+
if (this[kConsume]) {
|
|
160
|
+
consumePush(this[kConsume], chunk)
|
|
161
|
+
return this[kReading] ? super.push(chunk) : true
|
|
162
|
+
}
|
|
155
163
|
}
|
|
164
|
+
|
|
156
165
|
return super.push(chunk)
|
|
157
166
|
}
|
|
158
167
|
|
|
@@ -338,9 +347,23 @@ function isUnusable (bodyReadable) {
|
|
|
338
347
|
return util.isDisturbed(bodyReadable) || isLocked(bodyReadable)
|
|
339
348
|
}
|
|
340
349
|
|
|
350
|
+
/**
|
|
351
|
+
* @typedef {'text' | 'json' | 'blob' | 'bytes' | 'arrayBuffer'} ConsumeType
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @template {ConsumeType} T
|
|
356
|
+
* @typedef {T extends 'text' ? string :
|
|
357
|
+
* T extends 'json' ? unknown :
|
|
358
|
+
* T extends 'blob' ? Blob :
|
|
359
|
+
* T extends 'arrayBuffer' ? ArrayBuffer :
|
|
360
|
+
* T extends 'bytes' ? Uint8Array :
|
|
361
|
+
* never
|
|
362
|
+
* } ConsumeReturnType
|
|
363
|
+
*/
|
|
341
364
|
/**
|
|
342
365
|
* @typedef {object} Consume
|
|
343
|
-
* @property {
|
|
366
|
+
* @property {ConsumeType} type
|
|
344
367
|
* @property {BodyReadable} stream
|
|
345
368
|
* @property {((value?: any) => void)} resolve
|
|
346
369
|
* @property {((err: Error) => void)} reject
|
|
@@ -349,9 +372,10 @@ function isUnusable (bodyReadable) {
|
|
|
349
372
|
*/
|
|
350
373
|
|
|
351
374
|
/**
|
|
375
|
+
* @template {ConsumeType} T
|
|
352
376
|
* @param {BodyReadable} stream
|
|
353
|
-
* @param {
|
|
354
|
-
* @returns {Promise<
|
|
377
|
+
* @param {T} type
|
|
378
|
+
* @returns {Promise<ConsumeReturnType<T>>}
|
|
355
379
|
*/
|
|
356
380
|
function consume (stream, type) {
|
|
357
381
|
assert(!stream[kConsume])
|
|
@@ -361,9 +385,7 @@ function consume (stream, type) {
|
|
|
361
385
|
const rState = stream._readableState
|
|
362
386
|
if (rState.destroyed && rState.closeEmitted === false) {
|
|
363
387
|
stream
|
|
364
|
-
.on('error',
|
|
365
|
-
reject(err)
|
|
366
|
-
})
|
|
388
|
+
.on('error', reject)
|
|
367
389
|
.on('close', () => {
|
|
368
390
|
reject(new TypeError('unusable'))
|
|
369
391
|
})
|
|
@@ -438,7 +460,7 @@ function consumeStart (consume) {
|
|
|
438
460
|
/**
|
|
439
461
|
* @param {Buffer[]} chunks
|
|
440
462
|
* @param {number} length
|
|
441
|
-
* @param {BufferEncoding} encoding
|
|
463
|
+
* @param {BufferEncoding} [encoding='utf8']
|
|
442
464
|
* @returns {string}
|
|
443
465
|
*/
|
|
444
466
|
function chunksDecode (chunks, length, encoding) {
|
package/lib/core/util.js
CHANGED
|
@@ -5,7 +5,6 @@ const { kDestroyed, kBodyUsed, kListeners, kBody } = require('./symbols')
|
|
|
5
5
|
const { IncomingMessage } = require('node:http')
|
|
6
6
|
const stream = require('node:stream')
|
|
7
7
|
const net = require('node:net')
|
|
8
|
-
const { Blob } = require('node:buffer')
|
|
9
8
|
const { stringify } = require('node:querystring')
|
|
10
9
|
const { EventEmitter: EE } = require('node:events')
|
|
11
10
|
const timers = require('../util/timers')
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { kProxy, kClose, kDestroy, kDispatch
|
|
4
|
-
const { URL } = require('node:url')
|
|
3
|
+
const { kProxy, kClose, kDestroy, kDispatch } = require('../core/symbols')
|
|
5
4
|
const Agent = require('./agent')
|
|
6
5
|
const Pool = require('./pool')
|
|
7
6
|
const DispatcherBase = require('./dispatcher-base')
|
|
@@ -27,61 +26,69 @@ function defaultFactory (origin, opts) {
|
|
|
27
26
|
|
|
28
27
|
const noop = () => {}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
function defaultAgentFactory (origin, opts) {
|
|
30
|
+
if (opts.connections === 1) {
|
|
31
|
+
return new Client(origin, opts)
|
|
32
|
+
}
|
|
33
|
+
return new Pool(origin, opts)
|
|
34
|
+
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
36
|
+
class Http1ProxyWrapper extends DispatcherBase {
|
|
37
|
+
#client
|
|
40
38
|
|
|
39
|
+
constructor (proxyUrl, { headers = {}, connect, factory }) {
|
|
41
40
|
super()
|
|
41
|
+
if (!proxyUrl) {
|
|
42
|
+
throw new InvalidArgumentError('Proxy URL is mandatory')
|
|
43
|
+
}
|
|
42
44
|
|
|
43
|
-
this
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
async [kDestroy] () {
|
|
51
|
-
await this.#client.destroy()
|
|
45
|
+
this[kProxyHeaders] = headers
|
|
46
|
+
if (factory) {
|
|
47
|
+
this.#client = factory(proxyUrl, { connect })
|
|
48
|
+
} else {
|
|
49
|
+
this.#client = new Client(proxyUrl, { connect })
|
|
50
|
+
}
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
path: opts.host,
|
|
61
|
-
signal: opts.signal,
|
|
62
|
-
headers: {
|
|
63
|
-
...this[kProxyHeaders],
|
|
64
|
-
host: opts.host
|
|
65
|
-
},
|
|
66
|
-
servername: this[kProxyTls]?.servername || opts.servername
|
|
67
|
-
},
|
|
68
|
-
(err, socket) => {
|
|
69
|
-
if (err) {
|
|
70
|
-
handler.callback(err)
|
|
71
|
-
} else {
|
|
72
|
-
handler.callback(null, { socket, statusCode: 200 })
|
|
53
|
+
[kDispatch] (opts, handler) {
|
|
54
|
+
const onHeaders = handler.onHeaders
|
|
55
|
+
handler.onHeaders = function (statusCode, data, resume) {
|
|
56
|
+
if (statusCode === 407) {
|
|
57
|
+
if (typeof handler.onError === 'function') {
|
|
58
|
+
handler.onError(new InvalidArgumentError('Proxy Authentication Required (407)'))
|
|
73
59
|
}
|
|
60
|
+
return
|
|
74
61
|
}
|
|
75
|
-
)
|
|
76
|
-
return
|
|
62
|
+
if (onHeaders) onHeaders.call(this, statusCode, data, resume)
|
|
77
63
|
}
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
|
|
65
|
+
// Rewrite request as an HTTP1 Proxy request, without tunneling.
|
|
66
|
+
const {
|
|
67
|
+
origin,
|
|
68
|
+
path = '/',
|
|
69
|
+
headers = {}
|
|
70
|
+
} = opts
|
|
71
|
+
|
|
72
|
+
opts.path = origin + path
|
|
73
|
+
|
|
74
|
+
if (!('host' in headers) && !('Host' in headers)) {
|
|
75
|
+
const { host } = new URL(origin)
|
|
76
|
+
headers.host = host
|
|
80
77
|
}
|
|
78
|
+
opts.headers = { ...this[kProxyHeaders], ...headers }
|
|
79
|
+
|
|
80
|
+
return this.#client[kDispatch](opts, handler)
|
|
81
|
+
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
async [kClose] () {
|
|
84
|
+
return this.#client.close()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async [kDestroy] (err) {
|
|
88
|
+
return this.#client.destroy(err)
|
|
83
89
|
}
|
|
84
90
|
}
|
|
91
|
+
|
|
85
92
|
class ProxyAgent extends DispatcherBase {
|
|
86
93
|
constructor (opts) {
|
|
87
94
|
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
|
|
@@ -104,6 +111,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
104
111
|
this[kRequestTls] = opts.requestTls
|
|
105
112
|
this[kProxyTls] = opts.proxyTls
|
|
106
113
|
this[kProxyHeaders] = opts.headers || {}
|
|
114
|
+
this[kTunnelProxy] = proxyTunnel
|
|
107
115
|
|
|
108
116
|
if (opts.auth && opts.token) {
|
|
109
117
|
throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token')
|
|
@@ -116,21 +124,25 @@ class ProxyAgent extends DispatcherBase {
|
|
|
116
124
|
this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
|
|
117
125
|
}
|
|
118
126
|
|
|
119
|
-
const factory = (!proxyTunnel && protocol === 'http:')
|
|
120
|
-
? (origin, options) => {
|
|
121
|
-
if (origin.protocol === 'http:') {
|
|
122
|
-
return new ProxyClient(origin, options)
|
|
123
|
-
}
|
|
124
|
-
return new Client(origin, options)
|
|
125
|
-
}
|
|
126
|
-
: undefined
|
|
127
|
-
|
|
128
127
|
const connect = buildConnector({ ...opts.proxyTls })
|
|
129
128
|
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
|
|
130
|
+
const agentFactory = opts.factory || defaultAgentFactory
|
|
131
|
+
const factory = (origin, options) => {
|
|
132
|
+
const { protocol } = new URL(origin)
|
|
133
|
+
if (!this[kTunnelProxy] && protocol === 'http:' && this[kProxy].protocol === 'http:') {
|
|
134
|
+
return new Http1ProxyWrapper(this[kProxy].uri, {
|
|
135
|
+
headers: this[kProxyHeaders],
|
|
136
|
+
connect,
|
|
137
|
+
factory: agentFactory
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
return agentFactory(origin, options)
|
|
141
|
+
}
|
|
142
|
+
this[kClient] = clientFactory(url, { connect })
|
|
132
143
|
this[kAgent] = new Agent({
|
|
133
144
|
...opts,
|
|
145
|
+
factory,
|
|
134
146
|
connect: async (opts, callback) => {
|
|
135
147
|
let requestedPath = opts.host
|
|
136
148
|
if (!opts.port) {
|
|
@@ -185,10 +197,6 @@ class ProxyAgent extends DispatcherBase {
|
|
|
185
197
|
headers.host = host
|
|
186
198
|
}
|
|
187
199
|
|
|
188
|
-
if (!this.#shouldConnect(new URL(opts.origin))) {
|
|
189
|
-
opts.path = opts.origin + opts.path
|
|
190
|
-
}
|
|
191
|
-
|
|
192
200
|
return this[kAgent].dispatch(
|
|
193
201
|
{
|
|
194
202
|
...opts,
|
|
@@ -199,7 +207,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
199
207
|
}
|
|
200
208
|
|
|
201
209
|
/**
|
|
202
|
-
* @param {import('
|
|
210
|
+
* @param {import('../../types/proxy-agent').ProxyAgent.Options | string | URL} opts
|
|
203
211
|
* @returns {URL}
|
|
204
212
|
*/
|
|
205
213
|
#getUrl (opts) {
|
|
@@ -221,19 +229,6 @@ class ProxyAgent extends DispatcherBase {
|
|
|
221
229
|
await this[kAgent].destroy()
|
|
222
230
|
await this[kClient].destroy()
|
|
223
231
|
}
|
|
224
|
-
|
|
225
|
-
#shouldConnect (uri) {
|
|
226
|
-
if (typeof uri === 'string') {
|
|
227
|
-
uri = new URL(uri)
|
|
228
|
-
}
|
|
229
|
-
if (this[kTunnelProxy]) {
|
|
230
|
-
return true
|
|
231
|
-
}
|
|
232
|
-
if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') {
|
|
233
|
-
return true
|
|
234
|
-
}
|
|
235
|
-
return false
|
|
236
|
-
}
|
|
237
232
|
}
|
|
238
233
|
|
|
239
234
|
/**
|
|
@@ -15,6 +15,15 @@ const HEURISTICALLY_CACHEABLE_STATUS_CODES = [
|
|
|
15
15
|
200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
+
// Status codes which semantic is not handled by the cache
|
|
19
|
+
// https://datatracker.ietf.org/doc/html/rfc9111#section-3
|
|
20
|
+
// This list should not grow beyond 206 and 304 unless the RFC is updated
|
|
21
|
+
// by a newer one including more. Please introduce another list if
|
|
22
|
+
// implementing caching of responses with the 'must-understand' directive.
|
|
23
|
+
const NOT_UNDERSTOOD_STATUS_CODES = [
|
|
24
|
+
206, 304
|
|
25
|
+
]
|
|
26
|
+
|
|
18
27
|
const MAX_RESPONSE_AGE = 2147483647000
|
|
19
28
|
|
|
20
29
|
/**
|
|
@@ -241,10 +250,19 @@ class CacheHandler {
|
|
|
241
250
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
242
251
|
*/
|
|
243
252
|
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) {
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
// Status code must be final and understood.
|
|
254
|
+
if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) {
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
// Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
|
|
258
|
+
// directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
|
|
259
|
+
if (!HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) && !resHeaders['expires'] &&
|
|
260
|
+
!cacheControlDirectives.public &&
|
|
261
|
+
cacheControlDirectives['max-age'] === undefined &&
|
|
262
|
+
// RFC 9111: a private response directive, if the cache is not shared
|
|
263
|
+
!(cacheControlDirectives.private && cacheType === 'private') &&
|
|
264
|
+
!(cacheControlDirectives['s-maxage'] !== undefined && cacheType === 'shared')
|
|
265
|
+
) {
|
|
248
266
|
return false
|
|
249
267
|
}
|
|
250
268
|
|
|
@@ -133,6 +133,16 @@ class RedirectHandler {
|
|
|
133
133
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))
|
|
134
134
|
const path = search ? `${pathname}${search}` : pathname
|
|
135
135
|
|
|
136
|
+
// Check for redirect loops by seeing if we've already visited this URL in our history
|
|
137
|
+
// This catches the case where Client/Pool try to handle cross-origin redirects but fail
|
|
138
|
+
// and keep redirecting to the same URL in an infinite loop
|
|
139
|
+
const redirectUrlString = `${origin}${path}`
|
|
140
|
+
for (const historyUrl of this.history) {
|
|
141
|
+
if (historyUrl.toString() === redirectUrlString) {
|
|
142
|
+
throw new InvalidArgumentError(`Redirect loop detected. Cannot redirect to ${origin}. This typically happens when using a Client or Pool with cross-origin redirects. Use an Agent for cross-origin redirects.`)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
// Remove headers referring to the original URL.
|
|
137
147
|
// By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers.
|
|
138
148
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -6,7 +6,7 @@ const util = require('../core/util')
|
|
|
6
6
|
const CacheHandler = require('../handler/cache-handler')
|
|
7
7
|
const MemoryCacheStore = require('../cache/memory-cache-store')
|
|
8
8
|
const CacheRevalidationHandler = require('../handler/cache-revalidation-handler')
|
|
9
|
-
const { assertCacheStore, assertCacheMethods, makeCacheKey,
|
|
9
|
+
const { assertCacheStore, assertCacheMethods, makeCacheKey, normalizeHeaders, parseCacheControlHeader } = require('../util/cache.js')
|
|
10
10
|
const { AbortError } = require('../core/errors.js')
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -326,7 +326,7 @@ module.exports = (opts = {}) => {
|
|
|
326
326
|
|
|
327
327
|
opts = {
|
|
328
328
|
...opts,
|
|
329
|
-
headers:
|
|
329
|
+
headers: normalizeHeaders(opts)
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
const reqCacheControl = opts.headers?.['cache-control']
|
package/lib/interceptor/dump.js
CHANGED
package/lib/mock/mock-agent.js
CHANGED
|
@@ -17,7 +17,8 @@ const {
|
|
|
17
17
|
kMockAgentAddCallHistoryLog,
|
|
18
18
|
kMockAgentMockCallHistoryInstance,
|
|
19
19
|
kMockAgentAcceptsNonStandardSearchParameters,
|
|
20
|
-
kMockCallHistoryAddLog
|
|
20
|
+
kMockCallHistoryAddLog,
|
|
21
|
+
kIgnoreTrailingSlash
|
|
21
22
|
} = require('./mock-symbols')
|
|
22
23
|
const MockClient = require('./mock-client')
|
|
23
24
|
const MockPool = require('./mock-pool')
|
|
@@ -37,6 +38,7 @@ class MockAgent extends Dispatcher {
|
|
|
37
38
|
this[kIsMockActive] = true
|
|
38
39
|
this[kMockAgentIsCallHistoryEnabled] = mockOptions?.enableCallHistory ?? false
|
|
39
40
|
this[kMockAgentAcceptsNonStandardSearchParameters] = mockOptions?.acceptNonStandardSearchParameters ?? false
|
|
41
|
+
this[kIgnoreTrailingSlash] = mockOptions?.ignoreTrailingSlash ?? false
|
|
40
42
|
|
|
41
43
|
// Instantiate Agent and encapsulate
|
|
42
44
|
if (opts?.agent && typeof opts.agent.dispatch !== 'function') {
|
|
@@ -54,11 +56,15 @@ class MockAgent extends Dispatcher {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
get (origin) {
|
|
57
|
-
|
|
59
|
+
const originKey = this[kIgnoreTrailingSlash]
|
|
60
|
+
? origin.replace(/\/$/, '')
|
|
61
|
+
: origin
|
|
62
|
+
|
|
63
|
+
let dispatcher = this[kMockAgentGet](originKey)
|
|
58
64
|
|
|
59
65
|
if (!dispatcher) {
|
|
60
|
-
dispatcher = this[kFactory](
|
|
61
|
-
this[kMockAgentSet](
|
|
66
|
+
dispatcher = this[kFactory](originKey)
|
|
67
|
+
this[kMockAgentSet](originKey, dispatcher)
|
|
62
68
|
}
|
|
63
69
|
return dispatcher
|
|
64
70
|
}
|