undici 7.0.0-alpha.2 → 7.0.0-alpha.4
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 +3 -2
- package/docs/docs/api/BalancedPool.md +1 -1
- package/docs/docs/api/CacheStore.md +100 -0
- package/docs/docs/api/Dispatcher.md +32 -2
- package/docs/docs/api/MockClient.md +1 -1
- package/docs/docs/api/Pool.md +1 -1
- package/docs/docs/api/api-lifecycle.md +2 -2
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +8 -2
- package/lib/api/api-request.js +2 -2
- package/lib/api/readable.js +6 -6
- package/lib/cache/memory-cache-store.js +325 -0
- package/lib/core/connect.js +5 -0
- package/lib/core/constants.js +24 -1
- package/lib/core/request.js +2 -2
- package/lib/core/util.js +13 -1
- package/lib/dispatcher/client-h1.js +100 -87
- package/lib/dispatcher/client-h2.js +168 -96
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/handler/cache-handler.js +389 -0
- package/lib/handler/cache-revalidation-handler.js +151 -0
- package/lib/handler/redirect-handler.js +5 -3
- package/lib/handler/retry-handler.js +3 -3
- package/lib/interceptor/cache.js +192 -0
- package/lib/interceptor/dns.js +71 -48
- package/lib/util/cache.js +249 -0
- package/lib/web/cache/cache.js +1 -0
- package/lib/web/cache/cachestorage.js +2 -0
- package/lib/web/cookies/index.js +12 -1
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +1 -5
- package/lib/web/fetch/constants.js +12 -5
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +70 -43
- package/lib/web/fetch/formdata.js +3 -1
- package/lib/web/fetch/headers.js +3 -1
- package/lib/web/fetch/index.js +4 -6
- package/lib/web/fetch/request.js +3 -1
- package/lib/web/fetch/response.js +3 -1
- package/lib/web/fetch/util.js +171 -47
- package/lib/web/fetch/webidl.js +28 -16
- package/lib/web/websocket/constants.js +67 -6
- package/lib/web/websocket/events.js +4 -0
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/websocket.js +2 -0
- package/package.json +8 -5
- package/types/cache-interceptor.d.ts +101 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +4 -1
- package/types/webidl.d.ts +7 -1
package/lib/web/cookies/parse.js
CHANGED
|
@@ -4,6 +4,7 @@ const { maxNameValuePairSize, maxAttributeValueSize } = require('./constants')
|
|
|
4
4
|
const { isCTLExcludingHtab } = require('./util')
|
|
5
5
|
const { collectASequenceOfCodePointsFast } = require('../fetch/data-url')
|
|
6
6
|
const assert = require('node:assert')
|
|
7
|
+
const { unescape } = require('node:querystring')
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @description Parses the field-value attributes of a set-cookie header string.
|
|
@@ -76,8 +77,12 @@ function parseSetCookie (header) {
|
|
|
76
77
|
|
|
77
78
|
// 6. The cookie-name is the name string, and the cookie-value is the
|
|
78
79
|
// value string.
|
|
80
|
+
// https://datatracker.ietf.org/doc/html/rfc6265
|
|
81
|
+
// To maximize compatibility with user agents, servers that wish to
|
|
82
|
+
// store arbitrary data in a cookie-value SHOULD encode that data, for
|
|
83
|
+
// example, using Base64 [RFC4648].
|
|
79
84
|
return {
|
|
80
|
-
name, value, ...parseUnparsedAttributes(unparsedAttributes)
|
|
85
|
+
name, value: unescape(value), ...parseUnparsedAttributes(unparsedAttributes)
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
|
package/lib/web/fetch/body.js
CHANGED
|
@@ -364,12 +364,8 @@ function bodyMixinMethods (instance, getInternalState) {
|
|
|
364
364
|
switch (mimeType.essence) {
|
|
365
365
|
case 'multipart/form-data': {
|
|
366
366
|
// 1. ... [long step]
|
|
367
|
-
const parsed = multipartFormDataParser(value, mimeType)
|
|
368
|
-
|
|
369
367
|
// 2. If that fails for some reason, then throw a TypeError.
|
|
370
|
-
|
|
371
|
-
throw new TypeError('Failed to parse body as FormData.')
|
|
372
|
-
}
|
|
368
|
+
const parsed = multipartFormDataParser(value, mimeType)
|
|
373
369
|
|
|
374
370
|
// 3. Return a new FormData object, appending each entry,
|
|
375
371
|
// resulting from the parsing operation, to its entry list.
|
|
@@ -22,10 +22,9 @@ const badPorts = /** @type {const} */ ([
|
|
|
22
22
|
const badPortsSet = new Set(badPorts)
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-
|
|
25
|
+
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-header
|
|
26
26
|
*/
|
|
27
|
-
const
|
|
28
|
-
'',
|
|
27
|
+
const referrerPolicyTokens = /** @type {const} */ ([
|
|
29
28
|
'no-referrer',
|
|
30
29
|
'no-referrer-when-downgrade',
|
|
31
30
|
'same-origin',
|
|
@@ -35,7 +34,15 @@ const referrerPolicy = /** @type {const} */ ([
|
|
|
35
34
|
'strict-origin-when-cross-origin',
|
|
36
35
|
'unsafe-url'
|
|
37
36
|
])
|
|
38
|
-
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @see https://w3c.github.io/webappsec-referrer-policy/#referrer-policies
|
|
40
|
+
*/
|
|
41
|
+
const referrerPolicy = /** @type {const} */ ([
|
|
42
|
+
'',
|
|
43
|
+
...referrerPolicyTokens
|
|
44
|
+
])
|
|
45
|
+
const referrerPolicyTokensSet = new Set(referrerPolicyTokens)
|
|
39
46
|
|
|
40
47
|
const requestRedirect = /** @type {const} */ (['follow', 'manual', 'error'])
|
|
41
48
|
|
|
@@ -120,5 +127,5 @@ module.exports = {
|
|
|
120
127
|
corsSafeListedMethodsSet,
|
|
121
128
|
safeMethodsSet,
|
|
122
129
|
forbiddenMethodsSet,
|
|
123
|
-
|
|
130
|
+
referrerPolicyTokens: referrerPolicyTokensSet
|
|
124
131
|
}
|
|
@@ -471,9 +471,9 @@ function forgivingBase64 (data) {
|
|
|
471
471
|
/**
|
|
472
472
|
* @param {string} input
|
|
473
473
|
* @param {{ position: number }} position
|
|
474
|
-
* @param {boolean
|
|
474
|
+
* @param {boolean} [extractValue=false]
|
|
475
475
|
*/
|
|
476
|
-
function collectAnHTTPQuotedString (input, position, extractValue) {
|
|
476
|
+
function collectAnHTTPQuotedString (input, position, extractValue = false) {
|
|
477
477
|
// 1. Let positionStart be position.
|
|
478
478
|
const positionStart = position.position
|
|
479
479
|
|
|
@@ -11,7 +11,7 @@ const { File: NodeFile } = require('node:buffer')
|
|
|
11
11
|
const File = globalThis.File ?? NodeFile
|
|
12
12
|
|
|
13
13
|
const formDataNameBuffer = Buffer.from('form-data; name="')
|
|
14
|
-
const filenameBuffer = Buffer.from('
|
|
14
|
+
const filenameBuffer = Buffer.from('filename')
|
|
15
15
|
const dd = Buffer.from('--')
|
|
16
16
|
const ddcrlf = Buffer.from('--\r\n')
|
|
17
17
|
|
|
@@ -75,7 +75,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
75
75
|
// Otherwise, let boundary be the result of UTF-8 decoding mimeType’s
|
|
76
76
|
// parameters["boundary"].
|
|
77
77
|
if (boundaryString === undefined) {
|
|
78
|
-
|
|
78
|
+
throw parsingError('missing boundary in content-type header')
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
const boundary = Buffer.from(`--${boundaryString}`, 'utf8')
|
|
@@ -111,7 +111,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
111
111
|
if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) {
|
|
112
112
|
position.position += boundary.length
|
|
113
113
|
} else {
|
|
114
|
-
|
|
114
|
+
throw parsingError('expected a value starting with -- and the boundary')
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// 5.2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A
|
|
@@ -127,7 +127,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
127
127
|
// 5.3. If position does not point to a sequence of bytes starting with 0x0D
|
|
128
128
|
// 0x0A (CR LF), return failure.
|
|
129
129
|
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
|
130
|
-
|
|
130
|
+
throw parsingError('expected CRLF')
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// 5.4. Advance position by 2. (This skips past the newline.)
|
|
@@ -138,10 +138,6 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
138
138
|
// is not failure. Otherwise, return failure.
|
|
139
139
|
const result = parseMultipartFormDataHeaders(input, position)
|
|
140
140
|
|
|
141
|
-
if (result === 'failure') {
|
|
142
|
-
return 'failure'
|
|
143
|
-
}
|
|
144
|
-
|
|
145
141
|
let { name, filename, contentType, encoding } = result
|
|
146
142
|
|
|
147
143
|
// 5.6. Advance position by 2. (This skips past the empty line that marks
|
|
@@ -157,7 +153,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
157
153
|
const boundaryIndex = input.indexOf(boundary.subarray(2), position.position)
|
|
158
154
|
|
|
159
155
|
if (boundaryIndex === -1) {
|
|
160
|
-
|
|
156
|
+
throw parsingError('expected boundary after body')
|
|
161
157
|
}
|
|
162
158
|
|
|
163
159
|
body = input.subarray(position.position, boundaryIndex - 4)
|
|
@@ -174,7 +170,7 @@ function multipartFormDataParser (input, mimeType) {
|
|
|
174
170
|
// 5.9. If position does not point to a sequence of bytes starting with
|
|
175
171
|
// 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2.
|
|
176
172
|
if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
|
|
177
|
-
|
|
173
|
+
throw parsingError('expected CRLF')
|
|
178
174
|
} else {
|
|
179
175
|
position.position += 2
|
|
180
176
|
}
|
|
@@ -230,7 +226,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
230
226
|
if (input[position.position] === 0x0d && input[position.position + 1] === 0x0a) {
|
|
231
227
|
// 2.1.1. If name is null, return failure.
|
|
232
228
|
if (name === null) {
|
|
233
|
-
|
|
229
|
+
throw parsingError('header name is null')
|
|
234
230
|
}
|
|
235
231
|
|
|
236
232
|
// 2.1.2. Return name, filename and contentType.
|
|
@@ -250,12 +246,12 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
250
246
|
|
|
251
247
|
// 2.4. If header name does not match the field-name token production, return failure.
|
|
252
248
|
if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) {
|
|
253
|
-
|
|
249
|
+
throw parsingError('header name does not match the field-name token production')
|
|
254
250
|
}
|
|
255
251
|
|
|
256
252
|
// 2.5. If the byte at position is not 0x3A (:), return failure.
|
|
257
253
|
if (input[position.position] !== 0x3a) {
|
|
258
|
-
|
|
254
|
+
throw parsingError('expected :')
|
|
259
255
|
}
|
|
260
256
|
|
|
261
257
|
// 2.6. Advance position by 1.
|
|
@@ -278,7 +274,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
278
274
|
// 2. If position does not point to a sequence of bytes starting with
|
|
279
275
|
// `form-data; name="`, return failure.
|
|
280
276
|
if (!bufferStartsWith(input, formDataNameBuffer, position)) {
|
|
281
|
-
|
|
277
|
+
throw parsingError('expected form-data; name=" for content-disposition header')
|
|
282
278
|
}
|
|
283
279
|
|
|
284
280
|
// 3. Advance position so it points at the byte after the next 0x22 (")
|
|
@@ -290,34 +286,61 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
290
286
|
// failure.
|
|
291
287
|
name = parseMultipartFormDataName(input, position)
|
|
292
288
|
|
|
293
|
-
if (name === null) {
|
|
294
|
-
return 'failure'
|
|
295
|
-
}
|
|
296
|
-
|
|
297
289
|
// 5. If position points to a sequence of bytes starting with `; filename="`:
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
290
|
+
if (input[position.position] === 0x3b /* ; */ && input[position.position + 1] === 0x20 /* ' ' */) {
|
|
291
|
+
const at = { position: position.position + 2 }
|
|
292
|
+
|
|
293
|
+
if (bufferStartsWith(input, filenameBuffer, at)) {
|
|
294
|
+
if (input[at.position + 8] === 0x2a /* '*' */) {
|
|
295
|
+
at.position += 10 // skip past filename*=
|
|
296
|
+
|
|
297
|
+
// Remove leading http tab and spaces. See RFC for examples.
|
|
298
|
+
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
|
299
|
+
collectASequenceOfBytes(
|
|
300
|
+
(char) => char === 0x20 || char === 0x09,
|
|
301
|
+
input,
|
|
302
|
+
at
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
const headerValue = collectASequenceOfBytes(
|
|
306
|
+
(char) => char !== 0x20 && char !== 0x0d && char !== 0x0a, // ' ' or CRLF
|
|
307
|
+
input,
|
|
308
|
+
at
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if (
|
|
312
|
+
(headerValue[0] !== 0x75 && headerValue[0] !== 0x55) || // u or U
|
|
313
|
+
(headerValue[1] !== 0x74 && headerValue[1] !== 0x54) || // t or T
|
|
314
|
+
(headerValue[2] !== 0x66 && headerValue[2] !== 0x46) || // f or F
|
|
315
|
+
headerValue[3] !== 0x2d || // -
|
|
316
|
+
headerValue[4] !== 0x38 // 8
|
|
317
|
+
) {
|
|
318
|
+
throw parsingError('unknown encoding, expected utf-8\'\'')
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// skip utf-8''
|
|
322
|
+
filename = decodeURIComponent(new TextDecoder().decode(headerValue.subarray(7)))
|
|
323
|
+
|
|
324
|
+
position.position = at.position
|
|
325
|
+
} else {
|
|
326
|
+
// 1. Advance position so it points at the byte after the next 0x22 (") byte
|
|
327
|
+
// (the one in the sequence of bytes matched above).
|
|
328
|
+
position.position += 11
|
|
329
|
+
|
|
330
|
+
// Remove leading http tab and spaces. See RFC for examples.
|
|
331
|
+
// https://datatracker.ietf.org/doc/html/rfc6266#section-5
|
|
332
|
+
collectASequenceOfBytes(
|
|
333
|
+
(char) => char === 0x20 || char === 0x09,
|
|
334
|
+
input,
|
|
335
|
+
position
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
position.position++ // skip past " after removing whitespace
|
|
339
|
+
|
|
340
|
+
// 2. Set filename to the result of parsing a multipart/form-data name given
|
|
341
|
+
// input and position, if the result is not failure. Otherwise, return failure.
|
|
342
|
+
filename = parseMultipartFormDataName(input, position)
|
|
343
|
+
}
|
|
321
344
|
}
|
|
322
345
|
}
|
|
323
346
|
|
|
@@ -367,7 +390,7 @@ function parseMultipartFormDataHeaders (input, position) {
|
|
|
367
390
|
// 2.9. If position does not point to a sequence of bytes starting with 0x0D 0x0A
|
|
368
391
|
// (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
|
|
369
392
|
if (input[position.position] !== 0x0d && input[position.position + 1] !== 0x0a) {
|
|
370
|
-
|
|
393
|
+
throw parsingError('expected CRLF')
|
|
371
394
|
} else {
|
|
372
395
|
position.position += 2
|
|
373
396
|
}
|
|
@@ -393,7 +416,7 @@ function parseMultipartFormDataName (input, position) {
|
|
|
393
416
|
|
|
394
417
|
// 3. If the byte at position is not 0x22 ("), return failure. Otherwise, advance position by 1.
|
|
395
418
|
if (input[position.position] !== 0x22) {
|
|
396
|
-
|
|
419
|
+
throw parsingError('expected "')
|
|
397
420
|
} else {
|
|
398
421
|
position.position++
|
|
399
422
|
}
|
|
@@ -468,6 +491,10 @@ function bufferStartsWith (buffer, start, position) {
|
|
|
468
491
|
return true
|
|
469
492
|
}
|
|
470
493
|
|
|
494
|
+
function parsingError (cause) {
|
|
495
|
+
return new TypeError('Failed to parse body as FormData.', { cause: new TypeError(cause) })
|
|
496
|
+
}
|
|
497
|
+
|
|
471
498
|
module.exports = {
|
|
472
499
|
multipartFormDataParser,
|
|
473
500
|
validateBoundary
|
|
@@ -14,6 +14,8 @@ class FormData {
|
|
|
14
14
|
#state = []
|
|
15
15
|
|
|
16
16
|
constructor (form) {
|
|
17
|
+
webidl.util.markAsUncloneable(this)
|
|
18
|
+
|
|
17
19
|
if (form !== undefined) {
|
|
18
20
|
throw webidl.errors.conversionFailed({
|
|
19
21
|
prefix: 'FormData constructor',
|
|
@@ -256,6 +258,6 @@ function makeEntry (name, value, filename) {
|
|
|
256
258
|
return { name, value }
|
|
257
259
|
}
|
|
258
260
|
|
|
259
|
-
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData
|
|
261
|
+
webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
|
|
260
262
|
|
|
261
263
|
module.exports = { FormData, makeEntry, setFormDataState }
|
package/lib/web/fetch/headers.js
CHANGED
|
@@ -436,6 +436,8 @@ class Headers {
|
|
|
436
436
|
* @returns
|
|
437
437
|
*/
|
|
438
438
|
constructor (init = undefined) {
|
|
439
|
+
webidl.util.markAsUncloneable(this)
|
|
440
|
+
|
|
439
441
|
if (init === kConstruct) {
|
|
440
442
|
return
|
|
441
443
|
}
|
|
@@ -449,7 +451,7 @@ class Headers {
|
|
|
449
451
|
|
|
450
452
|
// 2. If init is given, then fill this with init.
|
|
451
453
|
if (init !== undefined) {
|
|
452
|
-
init = webidl.converters.HeadersInit(init, 'Headers
|
|
454
|
+
init = webidl.converters.HeadersInit(init, 'Headers constructor', 'init')
|
|
453
455
|
fill(this, init)
|
|
454
456
|
}
|
|
455
457
|
}
|
package/lib/web/fetch/index.js
CHANGED
|
@@ -1943,8 +1943,10 @@ async function httpNetworkFetch (
|
|
|
1943
1943
|
// 19. Run these steps in parallel:
|
|
1944
1944
|
|
|
1945
1945
|
// 1. Run these steps, but abort when fetchParams is canceled:
|
|
1946
|
-
fetchParams.controller.
|
|
1947
|
-
|
|
1946
|
+
if (!fetchParams.controller.resume) {
|
|
1947
|
+
fetchParams.controller.on('terminated', onAborted)
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1948
1950
|
fetchParams.controller.resume = async () => {
|
|
1949
1951
|
// 1. While true
|
|
1950
1952
|
while (true) {
|
|
@@ -2205,10 +2207,6 @@ async function httpNetworkFetch (
|
|
|
2205
2207
|
fetchParams.controller.off('terminated', this.abort)
|
|
2206
2208
|
}
|
|
2207
2209
|
|
|
2208
|
-
if (fetchParams.controller.onAborted) {
|
|
2209
|
-
fetchParams.controller.off('terminated', fetchParams.controller.onAborted)
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
2210
|
fetchParams.controller.ended = true
|
|
2213
2211
|
|
|
2214
2212
|
this.body.push(null)
|
package/lib/web/fetch/request.js
CHANGED
|
@@ -92,6 +92,8 @@ class Request {
|
|
|
92
92
|
|
|
93
93
|
// https://fetch.spec.whatwg.org/#dom-request
|
|
94
94
|
constructor (input, init = undefined) {
|
|
95
|
+
webidl.util.markAsUncloneable(this)
|
|
96
|
+
|
|
95
97
|
if (input === kConstruct) {
|
|
96
98
|
return
|
|
97
99
|
}
|
|
@@ -986,7 +988,7 @@ Object.defineProperties(Request.prototype, {
|
|
|
986
988
|
}
|
|
987
989
|
})
|
|
988
990
|
|
|
989
|
-
webidl.is.Request = webidl.util.MakeTypeAssertion(Request
|
|
991
|
+
webidl.is.Request = webidl.util.MakeTypeAssertion(Request)
|
|
990
992
|
|
|
991
993
|
// https://fetch.spec.whatwg.org/#requestinfo
|
|
992
994
|
webidl.converters.RequestInfo = function (V, prefix, argument) {
|
|
@@ -112,6 +112,8 @@ class Response {
|
|
|
112
112
|
|
|
113
113
|
// https://fetch.spec.whatwg.org/#dom-response
|
|
114
114
|
constructor (body = null, init = undefined) {
|
|
115
|
+
webidl.util.markAsUncloneable(this)
|
|
116
|
+
|
|
115
117
|
if (body === kConstruct) {
|
|
116
118
|
return
|
|
117
119
|
}
|
|
@@ -619,7 +621,7 @@ webidl.converters.ResponseInit = webidl.dictionaryConverter([
|
|
|
619
621
|
}
|
|
620
622
|
])
|
|
621
623
|
|
|
622
|
-
webidl.is.Response = webidl.util.MakeTypeAssertion(Response
|
|
624
|
+
webidl.is.Response = webidl.util.MakeTypeAssertion(Response)
|
|
623
625
|
|
|
624
626
|
module.exports = {
|
|
625
627
|
isNetworkError,
|