Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
response.js611 linesDownload Raw Back to fetch
1'use strict'
2
3const { Headers, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require('./headers')
4const { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require('./body')
5const util = require('../../core/util')
6const nodeUtil = require('node:util')
7const { kEnumerableProperty } = util
8const {
9  isValidReasonPhrase,
10  isCancelled,
11  isAborted,
12  isBlobLike,
13  serializeJavascriptValueToJSONString,
14  isErrorLike,
15  isomorphicEncode,
16  environmentSettingsObject: relevantRealm
17} = require('./util')
18const {
19  redirectStatusSet,
20  nullBodyStatus
21} = require('./constants')
22const { kState, kHeaders } = require('./symbols')
23const { webidl } = require('./webidl')
24const { FormData } = require('./formdata')
25const { URLSerializer } = require('./data-url')
26const { kConstruct } = require('../../core/symbols')
27const assert = require('node:assert')
28const { types } = require('node:util')
29
30const textEncoder = new TextEncoder('utf-8')
31
32// https://fetch.spec.whatwg.org/#response-class
33class Response {
34  // Creates network error Response.
35  static error () {
36    // The static error() method steps are to return the result of creating a
37    // Response object, given a new network error, "immutable", and this’s
38    // relevant Realm.
39    const responseObject = fromInnerResponse(makeNetworkError(), 'immutable')
40
41    return responseObject
42  }
43
44  // https://fetch.spec.whatwg.org/#dom-response-json
45  static json (data, init = {}) {
46    webidl.argumentLengthCheck(arguments, 1, 'Response.json')
47
48    if (init !== null) {
49      init = webidl.converters.ResponseInit(init)
50    }
51
52    // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
53    const bytes = textEncoder.encode(
54      serializeJavascriptValueToJSONString(data)
55    )
56
57    // 2. Let body be the result of extracting bytes.
58    const body = extractBody(bytes)
59
60    // 3. Let responseObject be the result of creating a Response object, given a new response,
61    //    "response", and this’s relevant Realm.
62    const responseObject = fromInnerResponse(makeResponse({}), 'response')
63
64    // 4. Perform initialize a response given responseObject, init, and (body, "application/json").
65    initializeResponse(responseObject, init, { body: body[0], type: 'application/json' })
66
67    // 5. Return responseObject.
68    return responseObject
69  }
70
71  // Creates a redirect Response that redirects to url with status status.
72  static redirect (url, status = 302) {
73    webidl.argumentLengthCheck(arguments, 1, 'Response.redirect')
74
75    url = webidl.converters.USVString(url)
76    status = webidl.converters['unsigned short'](status)
77
78    // 1. Let parsedURL be the result of parsing url with current settings
79    // object’s API base URL.
80    // 2. If parsedURL is failure, then throw a TypeError.
81    // TODO: base-URL?
82    let parsedURL
83    try {
84      parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl)
85    } catch (err) {
86      throw new TypeError(`Failed to parse URL from ${url}`, { cause: err })
87    }
88
89    // 3. If status is not a redirect status, then throw a RangeError.
90    if (!redirectStatusSet.has(status)) {
91      throw new RangeError(`Invalid status code ${status}`)
92    }
93
94    // 4. Let responseObject be the result of creating a Response object,
95    // given a new response, "immutable", and this’s relevant Realm.
96    const responseObject = fromInnerResponse(makeResponse({}), 'immutable')
97
98    // 5. Set responseObject’s response’s status to status.
99    responseObject[kState].status = status
100
101    // 6. Let value be parsedURL, serialized and isomorphic encoded.
102    const value = isomorphicEncode(URLSerializer(parsedURL))
103
104    // 7. Append `Location`/value to responseObject’s response’s header list.
105    responseObject[kState].headersList.append('location', value, true)
106
107    // 8. Return responseObject.
108    return responseObject
109  }
110
111  // https://fetch.spec.whatwg.org/#dom-response
112  constructor (body = null, init = {}) {
113    webidl.util.markAsUncloneable(this)
114    if (body === kConstruct) {
115      return
116    }
117
118    if (body !== null) {
119      body = webidl.converters.BodyInit(body)
120    }
121
122    init = webidl.converters.ResponseInit(init)
123
124    // 1. Set this’s response to a new response.
125    this[kState] = makeResponse({})
126
127    // 2. Set this’s headers to a new Headers object with this’s relevant
128    // Realm, whose header list is this’s response’s header list and guard
129    // is "response".
130    this[kHeaders] = new Headers(kConstruct)
131    setHeadersGuard(this[kHeaders], 'response')
132    setHeadersList(this[kHeaders], this[kState].headersList)
133
134    // 3. Let bodyWithType be null.
135    let bodyWithType = null
136
137    // 4. If body is non-null, then set bodyWithType to the result of extracting body.
138    if (body != null) {
139      const [extractedBody, type] = extractBody(body)
140      bodyWithType = { body: extractedBody, type }
141    }
142
143    // 5. Perform initialize a response given this, init, and bodyWithType.
144    initializeResponse(this, init, bodyWithType)
145  }
146
147  // Returns response’s type, e.g., "cors".
148  get type () {
149    webidl.brandCheck(this, Response)
150
151    // The type getter steps are to return this’s response’s type.
152    return this[kState].type
153  }
154
155  // Returns response’s URL, if it has one; otherwise the empty string.
156  get url () {
157    webidl.brandCheck(this, Response)
158
159    const urlList = this[kState].urlList
160
161    // The url getter steps are to return the empty string if this’s
162    // response’s URL is null; otherwise this’s response’s URL,
163    // serialized with exclude fragment set to true.
164    const url = urlList[urlList.length - 1] ?? null
165
166    if (url === null) {
167      return ''
168    }
169
170    return URLSerializer(url, true)
171  }
172
173  // Returns whether response was obtained through a redirect.
174  get redirected () {
175    webidl.brandCheck(this, Response)
176
177    // The redirected getter steps are to return true if this’s response’s URL
178    // list has more than one item; otherwise false.
179    return this[kState].urlList.length > 1
180  }
181
182  // Returns response’s status.
183  get status () {
184    webidl.brandCheck(this, Response)
185
186    // The status getter steps are to return this’s response’s status.
187    return this[kState].status
188  }
189
190  // Returns whether response’s status is an ok status.
191  get ok () {
192    webidl.brandCheck(this, Response)
193
194    // The ok getter steps are to return true if this’s response’s status is an
195    // ok status; otherwise false.
196    return this[kState].status >= 200 && this[kState].status <= 299
197  }
198
199  // Returns response’s status message.
200  get statusText () {
201    webidl.brandCheck(this, Response)
202
203    // The statusText getter steps are to return this’s response’s status
204    // message.
205    return this[kState].statusText
206  }
207
208  // Returns response’s headers as Headers.
209  get headers () {
210    webidl.brandCheck(this, Response)
211
212    // The headers getter steps are to return this’s headers.
213    return this[kHeaders]
214  }
215
216  get body () {
217    webidl.brandCheck(this, Response)
218
219    return this[kState].body ? this[kState].body.stream : null
220  }
221
222  get bodyUsed () {
223    webidl.brandCheck(this, Response)
224
225    return !!this[kState].body && util.isDisturbed(this[kState].body.stream)
226  }
227
228  // Returns a clone of response.
229  clone () {
230    webidl.brandCheck(this, Response)
231
232    // 1. If this is unusable, then throw a TypeError.
233    if (bodyUnusable(this)) {
234      throw webidl.errors.exception({
235        header: 'Response.clone',
236        message: 'Body has already been consumed.'
237      })
238    }
239
240    // 2. Let clonedResponse be the result of cloning this’s response.
241    const clonedResponse = cloneResponse(this[kState])
242
243    // Note: To re-register because of a new stream.
244    if (hasFinalizationRegistry && this[kState].body?.stream) {
245      streamRegistry.register(this, new WeakRef(this[kState].body.stream))
246    }
247
248    // 3. Return the result of creating a Response object, given
249    // clonedResponse, this’s headers’s guard, and this’s relevant Realm.
250    return fromInnerResponse(clonedResponse, getHeadersGuard(this[kHeaders]))
251  }
252
253  [nodeUtil.inspect.custom] (depth, options) {
254    if (options.depth === null) {
255      options.depth = 2
256    }
257
258    options.colors ??= true
259
260    const properties = {
261      status: this.status,
262      statusText: this.statusText,
263      headers: this.headers,
264      body: this.body,
265      bodyUsed: this.bodyUsed,
266      ok: this.ok,
267      redirected: this.redirected,
268      type: this.type,
269      url: this.url
270    }
271
272    return `Response ${nodeUtil.formatWithOptions(options, properties)}`
273  }
274}
275
276mixinBody(Response)
277
278Object.defineProperties(Response.prototype, {
279  type: kEnumerableProperty,
280  url: kEnumerableProperty,
281  status: kEnumerableProperty,
282  ok: kEnumerableProperty,
283  redirected: kEnumerableProperty,
284  statusText: kEnumerableProperty,
285  headers: kEnumerableProperty,
286  clone: kEnumerableProperty,
287  body: kEnumerableProperty,
288  bodyUsed: kEnumerableProperty,
289  [Symbol.toStringTag]: {
290    value: 'Response',
291    configurable: true
292  }
293})
294
295Object.defineProperties(Response, {
296  json: kEnumerableProperty,
297  redirect: kEnumerableProperty,
298  error: kEnumerableProperty
299})
300
301// https://fetch.spec.whatwg.org/#concept-response-clone
302function cloneResponse (response) {
303  // To clone a response response, run these steps:
304
305  // 1. If response is a filtered response, then return a new identical
306  // filtered response whose internal response is a clone of response’s
307  // internal response.
308  if (response.internalResponse) {
309    return filterResponse(
310      cloneResponse(response.internalResponse),
311      response.type
312    )
313  }
314
315  // 2. Let newResponse be a copy of response, except for its body.
316  const newResponse = makeResponse({ ...response, body: null })
317
318  // 3. If response’s body is non-null, then set newResponse’s body to the
319  // result of cloning response’s body.
320  if (response.body != null) {
321    newResponse.body = cloneBody(newResponse, response.body)
322  }
323
324  // 4. Return newResponse.
325  return newResponse
326}
327
328function makeResponse (init) {
329  return {
330    aborted: false,
331    rangeRequested: false,
332    timingAllowPassed: false,
333    requestIncludesCredentials: false,
334    type: 'default',
335    status: 200,
336    timingInfo: null,
337    cacheState: '',
338    statusText: '',
339    ...init,
340    headersList: init?.headersList
341      ? new HeadersList(init?.headersList)
342      : new HeadersList(),
343    urlList: init?.urlList ? [...init.urlList] : []
344  }
345}
346
347function makeNetworkError (reason) {
348  const isError = isErrorLike(reason)
349  return makeResponse({
350    type: 'error',
351    status: 0,
352    error: isError
353      ? reason
354      : new Error(reason ? String(reason) : reason),
355    aborted: reason && reason.name === 'AbortError'
356  })
357}
358
359// @see https://fetch.spec.whatwg.org/#concept-network-error
360function isNetworkError (response) {
361  return (
362    // A network error is a response whose type is "error",
363    response.type === 'error' &&
364    // status is 0
365    response.status === 0
366  )
367}
368
369function makeFilteredResponse (response, state) {
370  state = {
371    internalResponse: response,
372    ...state
373  }
374
375  return new Proxy(response, {
376    get (target, p) {
377      return p in state ? state[p] : target[p]
378    },
379    set (target, p, value) {
380      assert(!(p in state))
381      target[p] = value
382      return true
383    }
384  })
385}
386
387// https://fetch.spec.whatwg.org/#concept-filtered-response
388function filterResponse (response, type) {
389  // Set response to the following filtered response with response as its
390  // internal response, depending on request’s response tainting:
391  if (type === 'basic') {
392    // A basic filtered response is a filtered response whose type is "basic"
393    // and header list excludes any headers in internal response’s header list
394    // whose name is a forbidden response-header name.
395
396    // Note: undici does not implement forbidden response-header names
397    return makeFilteredResponse(response, {
398      type: 'basic',
399      headersList: response.headersList
400    })
401  } else if (type === 'cors') {
402    // A CORS filtered response is a filtered response whose type is "cors"
403    // and header list excludes any headers in internal response’s header
404    // list whose name is not a CORS-safelisted response-header name, given
405    // internal response’s CORS-exposed header-name list.
406
407    // Note: undici does not implement CORS-safelisted response-header names
408    return makeFilteredResponse(response, {
409      type: 'cors',
410      headersList: response.headersList
411    })
412  } else if (type === 'opaque') {
413    // An opaque filtered response is a filtered response whose type is
414    // "opaque", URL list is the empty list, status is 0, status message
415    // is the empty byte sequence, header list is empty, and body is null.
416
417    return makeFilteredResponse(response, {
418      type: 'opaque',
419      urlList: Object.freeze([]),
420      status: 0,
421      statusText: '',
422      body: null
423    })
424  } else if (type === 'opaqueredirect') {
425    // An opaque-redirect filtered response is a filtered response whose type
426    // is "opaqueredirect", status is 0, status message is the empty byte
427    // sequence, header list is empty, and body is null.
428
429    return makeFilteredResponse(response, {
430      type: 'opaqueredirect',
431      status: 0,
432      statusText: '',
433      headersList: [],
434      body: null
435    })
436  } else {
437    assert(false)
438  }
439}
440
441// https://fetch.spec.whatwg.org/#appropriate-network-error
442function makeAppropriateNetworkError (fetchParams, err = null) {
443  // 1. Assert: fetchParams is canceled.
444  assert(isCancelled(fetchParams))
445
446  // 2. Return an aborted network error if fetchParams is aborted;
447  // otherwise return a network error.
448  return isAborted(fetchParams)
449    ? makeNetworkError(Object.assign(new DOMException('The operation was aborted.', 'AbortError'), { cause: err }))
450    : makeNetworkError(Object.assign(new DOMException('Request was cancelled.'), { cause: err }))
451}
452
453// https://whatpr.org/fetch/1392.html#initialize-a-response
454function initializeResponse (response, init, body) {
455  // 1. If init["status"] is not in the range 200 to 599, inclusive, then
456  //    throw a RangeError.
457  if (init.status !== null && (init.status < 200 || init.status > 599)) {
458    throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.')
459  }
460
461  // 2. If init["statusText"] does not match the reason-phrase token production,
462  //    then throw a TypeError.
463  if ('statusText' in init && init.statusText != null) {
464    // See, https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2:
465    //   reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
466    if (!isValidReasonPhrase(String(init.statusText))) {
467      throw new TypeError('Invalid statusText')
468    }
469  }
470
471  // 3. Set response’s response’s status to init["status"].
472  if ('status' in init && init.status != null) {
473    response[kState].status = init.status
474  }
475
476  // 4. Set response’s response’s status message to init["statusText"].
477  if ('statusText' in init && init.statusText != null) {
478    response[kState].statusText = init.statusText
479  }
480
481  // 5. If init["headers"] exists, then fill response’s headers with init["headers"].
482  if ('headers' in init && init.headers != null) {
483    fill(response[kHeaders], init.headers)
484  }
485
486  // 6. If body was given, then:
487  if (body) {
488    // 1. If response's status is a null body status, then throw a TypeError.
489    if (nullBodyStatus.includes(response.status)) {
490      throw webidl.errors.exception({
491        header: 'Response constructor',
492        message: `Invalid response status code ${response.status}`
493      })
494    }
495
496    // 2. Set response's body to body's body.
497    response[kState].body = body.body
498
499    // 3. If body's type is non-null and response's header list does not contain
500    //    `Content-Type`, then append (`Content-Type`, body's type) to response's header list.
501    if (body.type != null && !response[kState].headersList.contains('content-type', true)) {
502      response[kState].headersList.append('content-type', body.type, true)
503    }
504  }
505}
506
507/**
508 * @see https://fetch.spec.whatwg.org/#response-create
509 * @param {any} innerResponse
510 * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard
511 * @returns {Response}
512 */
513function fromInnerResponse (innerResponse, guard) {
514  const response = new Response(kConstruct)
515  response[kState] = innerResponse
516  response[kHeaders] = new Headers(kConstruct)
517  setHeadersList(response[kHeaders], innerResponse.headersList)
518  setHeadersGuard(response[kHeaders], guard)
519
520  if (hasFinalizationRegistry && innerResponse.body?.stream) {
521    // If the target (response) is reclaimed, the cleanup callback may be called at some point with
522    // the held value provided for it (innerResponse.body.stream). The held value can be any value:
523    // a primitive or an object, even undefined. If the held value is an object, the registry keeps
524    // a strong reference to it (so it can pass it to the cleanup callback later). Reworded from
525    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry
526    streamRegistry.register(response, new WeakRef(innerResponse.body.stream))
527  }
528
529  return response
530}
531
532webidl.converters.ReadableStream = webidl.interfaceConverter(
533  ReadableStream
534)
535
536webidl.converters.FormData = webidl.interfaceConverter(
537  FormData
538)
539
540webidl.converters.URLSearchParams = webidl.interfaceConverter(
541  URLSearchParams
542)
543
544// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
545webidl.converters.XMLHttpRequestBodyInit = function (V, prefix, name) {
546  if (typeof V === 'string') {
547    return webidl.converters.USVString(V, prefix, name)
548  }
549
550  if (isBlobLike(V)) {
551    return webidl.converters.Blob(V, prefix, name, { strict: false })
552  }
553
554  if (ArrayBuffer.isView(V) || types.isArrayBuffer(V)) {
555    return webidl.converters.BufferSource(V, prefix, name)
556  }
557
558  if (util.isFormDataLike(V)) {
559    return webidl.converters.FormData(V, prefix, name, { strict: false })
560  }
561
562  if (V instanceof URLSearchParams) {
563    return webidl.converters.URLSearchParams(V, prefix, name)
564  }
565
566  return webidl.converters.DOMString(V, prefix, name)
567}
568
569// https://fetch.spec.whatwg.org/#bodyinit
570webidl.converters.BodyInit = function (V, prefix, argument) {
571  if (V instanceof ReadableStream) {
572    return webidl.converters.ReadableStream(V, prefix, argument)
573  }
574
575  // Note: the spec doesn't include async iterables,
576  // this is an undici extension.
577  if (V?.[Symbol.asyncIterator]) {
578    return V
579  }
580
581  return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument)
582}
583
584webidl.converters.ResponseInit = webidl.dictionaryConverter([
585  {
586    key: 'status',
587    converter: webidl.converters['unsigned short'],
588    defaultValue: () => 200
589  },
590  {
591    key: 'statusText',
592    converter: webidl.converters.ByteString,
593    defaultValue: () => ''
594  },
595  {
596    key: 'headers',
597    converter: webidl.converters.HeadersInit
598  }
599])
600
601module.exports = {
602  isNetworkError,
603  makeNetworkError,
604  makeResponse,
605  makeAppropriateNetworkError,
606  filterResponse,
607  Response,
608  cloneResponse,
609  fromInnerResponse
610}
611 
codekingpro/portable-devtools · Team Ai