Spaces:
Paused
Paused
| /* globals AbortController */ | |
| const { extractBody, mixinBody, cloneBody, bodyUnusable } = require('./body') | |
| const { Headers, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require('./headers') | |
| const util = require('../../core/util') | |
| const nodeUtil = require('node:util') | |
| const { | |
| isValidHTTPToken, | |
| sameOrigin, | |
| environmentSettingsObject | |
| } = require('./util') | |
| const { | |
| forbiddenMethodsSet, | |
| corsSafeListedMethodsSet, | |
| referrerPolicy, | |
| requestRedirect, | |
| requestMode, | |
| requestCredentials, | |
| requestCache, | |
| requestDuplex | |
| } = require('./constants') | |
| const { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util | |
| const { webidl } = require('../webidl') | |
| const { URLSerializer } = require('./data-url') | |
| const { kConstruct } = require('../../core/symbols') | |
| const assert = require('node:assert') | |
| const { getMaxListeners, setMaxListeners, defaultMaxListeners } = require('node:events') | |
| const kAbortController = Symbol('abortController') | |
| const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { | |
| signal.removeEventListener('abort', abort) | |
| }) | |
| const dependentControllerMap = new WeakMap() | |
| let abortSignalHasEventHandlerLeakWarning | |
| try { | |
| abortSignalHasEventHandlerLeakWarning = getMaxListeners(new AbortController().signal) > 0 | |
| } catch { | |
| abortSignalHasEventHandlerLeakWarning = false | |
| } | |
| function buildAbort (acRef) { | |
| return abort | |
| function abort () { | |
| const ac = acRef.deref() | |
| if (ac !== undefined) { | |
| // Currently, there is a problem with FinalizationRegistry. | |
| // https://github.com/nodejs/node/issues/49344 | |
| // https://github.com/nodejs/node/issues/47748 | |
| // In the case of abort, the first step is to unregister from it. | |
| // If the controller can refer to it, it is still registered. | |
| // It will be removed in the future. | |
| requestFinalizer.unregister(abort) | |
| // Unsubscribe a listener. | |
| // FinalizationRegistry will no longer be called, so this must be done. | |
| this.removeEventListener('abort', abort) | |
| ac.abort(this.reason) | |
| const controllerList = dependentControllerMap.get(ac.signal) | |
| if (controllerList !== undefined) { | |
| if (controllerList.size !== 0) { | |
| for (const ref of controllerList) { | |
| const ctrl = ref.deref() | |
| if (ctrl !== undefined) { | |
| ctrl.abort(this.reason) | |
| } | |
| } | |
| controllerList.clear() | |
| } | |
| dependentControllerMap.delete(ac.signal) | |
| } | |
| } | |
| } | |
| } | |
| let patchMethodWarning = false | |
| // https://fetch.spec.whatwg.org/#request-class | |
| class Request { | |
| /** @type {AbortSignal} */ | |
| #signal | |
| /** @type {import('../../dispatcher/dispatcher')} */ | |
| #dispatcher | |
| /** @type {Headers} */ | |
| #headers | |
| #state | |
| // https://fetch.spec.whatwg.org/#dom-request | |
| constructor (input, init = undefined) { | |
| webidl.util.markAsUncloneable(this) | |
| if (input === kConstruct) { | |
| return | |
| } | |
| const prefix = 'Request constructor' | |
| webidl.argumentLengthCheck(arguments, 1, prefix) | |
| input = webidl.converters.RequestInfo(input) | |
| init = webidl.converters.RequestInit(init) | |
| // 1. Let request be null. | |
| let request = null | |
| // 2. Let fallbackMode be null. | |
| let fallbackMode = null | |
| // 3. Let baseURL be this’s relevant settings object’s API base URL. | |
| const baseUrl = environmentSettingsObject.settingsObject.baseUrl | |
| // 4. Let signal be null. | |
| let signal = null | |
| // 5. If input is a string, then: | |
| if (typeof input === 'string') { | |
| this.#dispatcher = init.dispatcher | |
| // 1. Let parsedURL be the result of parsing input with baseURL. | |
| // 2. If parsedURL is failure, then throw a TypeError. | |
| let parsedURL | |
| try { | |
| parsedURL = new URL(input, baseUrl) | |
| } catch (err) { | |
| throw new TypeError('Failed to parse URL from ' + input, { cause: err }) | |
| } | |
| // 3. If parsedURL includes credentials, then throw a TypeError. | |
| if (parsedURL.username || parsedURL.password) { | |
| throw new TypeError( | |
| 'Request cannot be constructed from a URL that includes credentials: ' + | |
| input | |
| ) | |
| } | |
| // 4. Set request to a new request whose URL is parsedURL. | |
| request = makeRequest({ urlList: [parsedURL] }) | |
| // 5. Set fallbackMode to "cors". | |
| fallbackMode = 'cors' | |
| } else { | |
| // 6. Otherwise: | |
| // 7. Assert: input is a Request object. | |
| assert(webidl.is.Request(input)) | |
| // 8. Set request to input’s request. | |
| request = input.#state | |
| // 9. Set signal to input’s signal. | |
| signal = input.#signal | |
| this.#dispatcher = init.dispatcher || input.#dispatcher | |
| } | |
| // 7. Let origin be this’s relevant settings object’s origin. | |
| const origin = environmentSettingsObject.settingsObject.origin | |
| // 8. Let window be "client". | |
| let window = 'client' | |
| // 9. If request’s window is an environment settings object and its origin | |
| // is same origin with origin, then set window to request’s window. | |
| if ( | |
| request.window?.constructor?.name === 'EnvironmentSettingsObject' && | |
| sameOrigin(request.window, origin) | |
| ) { | |
| window = request.window | |
| } | |
| // 10. If init["window"] exists and is non-null, then throw a TypeError. | |
| if (init.window != null) { | |
| throw new TypeError(`'window' option '${window}' must be null`) | |
| } | |
| // 11. If init["window"] exists, then set window to "no-window". | |
| if ('window' in init) { | |
| window = 'no-window' | |
| } | |
| // 12. Set request to a new request with the following properties: | |
| request = makeRequest({ | |
| // URL request’s URL. | |
| // undici implementation note: this is set as the first item in request's urlList in makeRequest | |
| // method request’s method. | |
| method: request.method, | |
| // header list A copy of request’s header list. | |
| // undici implementation note: headersList is cloned in makeRequest | |
| headersList: request.headersList, | |
| // unsafe-request flag Set. | |
| unsafeRequest: request.unsafeRequest, | |
| // client This’s relevant settings object. | |
| client: environmentSettingsObject.settingsObject, | |
| // window window. | |
| window, | |
| // priority request’s priority. | |
| priority: request.priority, | |
| // origin request’s origin. The propagation of the origin is only significant for navigation requests | |
| // being handled by a service worker. In this scenario a request can have an origin that is different | |
| // from the current client. | |
| origin: request.origin, | |
| // referrer request’s referrer. | |
| referrer: request.referrer, | |
| // referrer policy request’s referrer policy. | |
| referrerPolicy: request.referrerPolicy, | |
| // mode request’s mode. | |
| mode: request.mode, | |
| // credentials mode request’s credentials mode. | |
| credentials: request.credentials, | |
| // cache mode request’s cache mode. | |
| cache: request.cache, | |
| // redirect mode request’s redirect mode. | |
| redirect: request.redirect, | |
| // integrity metadata request’s integrity metadata. | |
| integrity: request.integrity, | |
| // keepalive request’s keepalive. | |
| keepalive: request.keepalive, | |
| // reload-navigation flag request’s reload-navigation flag. | |
| reloadNavigation: request.reloadNavigation, | |
| // history-navigation flag request’s history-navigation flag. | |
| historyNavigation: request.historyNavigation, | |
| // URL list A clone of request’s URL list. | |
| urlList: [...request.urlList] | |
| }) | |
| const initHasKey = Object.keys(init).length !== 0 | |
| // 13. If init is not empty, then: | |
| if (initHasKey) { | |
| // 1. If request’s mode is "navigate", then set it to "same-origin". | |
| if (request.mode === 'navigate') { | |
| request.mode = 'same-origin' | |
| } | |
| // 2. Unset request’s reload-navigation flag. | |
| request.reloadNavigation = false | |
| // 3. Unset request’s history-navigation flag. | |
| request.historyNavigation = false | |
| // 4. Set request’s origin to "client". | |
| request.origin = 'client' | |
| // 5. Set request’s referrer to "client" | |
| request.referrer = 'client' | |
| // 6. Set request’s referrer policy to the empty string. | |
| request.referrerPolicy = '' | |
| // 7. Set request’s URL to request’s current URL. | |
| request.url = request.urlList[request.urlList.length - 1] | |
| // 8. Set request’s URL list to « request’s URL ». | |
| request.urlList = [request.url] | |
| } | |
| // 14. If init["referrer"] exists, then: | |
| if (init.referrer !== undefined) { | |
| // 1. Let referrer be init["referrer"]. | |
| const referrer = init.referrer | |
| // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". | |
| if (referrer === '') { | |
| request.referrer = 'no-referrer' | |
| } else { | |
| // 1. Let parsedReferrer be the result of parsing referrer with | |
| // baseURL. | |
| // 2. If parsedReferrer is failure, then throw a TypeError. | |
| let parsedReferrer | |
| try { | |
| parsedReferrer = new URL(referrer, baseUrl) | |
| } catch (err) { | |
| throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }) | |
| } | |
| // 3. If one of the following is true | |
| // - parsedReferrer’s scheme is "about" and path is the string "client" | |
| // - parsedReferrer’s origin is not same origin with origin | |
| // then set request’s referrer to "client". | |
| if ( | |
| (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') || | |
| (origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl)) | |
| ) { | |
| request.referrer = 'client' | |
| } else { | |
| // 4. Otherwise, set request’s referrer to parsedReferrer. | |
| request.referrer = parsedReferrer | |
| } | |
| } | |
| } | |
| // 15. If init["referrerPolicy"] exists, then set request’s referrer policy | |
| // to it. | |
| if (init.referrerPolicy !== undefined) { | |
| request.referrerPolicy = init.referrerPolicy | |
| } | |
| // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. | |
| let mode | |
| if (init.mode !== undefined) { | |
| mode = init.mode | |
| } else { | |
| mode = fallbackMode | |
| } | |
| // 17. If mode is "navigate", then throw a TypeError. | |
| if (mode === 'navigate') { | |
| throw webidl.errors.exception({ | |
| header: 'Request constructor', | |
| message: 'invalid request mode navigate.' | |
| }) | |
| } | |
| // 18. If mode is non-null, set request’s mode to mode. | |
| if (mode != null) { | |
| request.mode = mode | |
| } | |
| // 19. If init["credentials"] exists, then set request’s credentials mode | |
| // to it. | |
| if (init.credentials !== undefined) { | |
| request.credentials = init.credentials | |
| } | |
| // 18. If init["cache"] exists, then set request’s cache mode to it. | |
| if (init.cache !== undefined) { | |
| request.cache = init.cache | |
| } | |
| // 21. If request’s cache mode is "only-if-cached" and request’s mode is | |
| // not "same-origin", then throw a TypeError. | |
| if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { | |
| throw new TypeError( | |
| "'only-if-cached' can be set only with 'same-origin' mode" | |
| ) | |
| } | |
| // 22. If init["redirect"] exists, then set request’s redirect mode to it. | |
| if (init.redirect !== undefined) { | |
| request.redirect = init.redirect | |
| } | |
| // 23. If init["integrity"] exists, then set request’s integrity metadata to it. | |
| if (init.integrity != null) { | |
| request.integrity = String(init.integrity) | |
| } | |
| // 24. If init["keepalive"] exists, then set request’s keepalive to it. | |
| if (init.keepalive !== undefined) { | |
| request.keepalive = Boolean(init.keepalive) | |
| } | |
| // 25. If init["method"] exists, then: | |
| if (init.method !== undefined) { | |
| // 1. Let method be init["method"]. | |
| let method = init.method | |
| const mayBeNormalized = normalizedMethodRecords[method] | |
| if (mayBeNormalized !== undefined) { | |
| // Note: Bypass validation DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH and these lowercase ones | |
| request.method = mayBeNormalized | |
| } else { | |
| // 2. If method is not a method or method is a forbidden method, then | |
| // throw a TypeError. | |
| if (!isValidHTTPToken(method)) { | |
| throw new TypeError(`'${method}' is not a valid HTTP method.`) | |
| } | |
| const upperCase = method.toUpperCase() | |
| if (forbiddenMethodsSet.has(upperCase)) { | |
| throw new TypeError(`'${method}' HTTP method is unsupported.`) | |
| } | |
| // 3. Normalize method. | |
| // https://fetch.spec.whatwg.org/#concept-method-normalize | |
| // Note: must be in uppercase | |
| method = normalizedMethodRecordsBase[upperCase] ?? method | |
| // 4. Set request’s method to method. | |
| request.method = method | |
| } | |
| if (!patchMethodWarning && request.method === 'patch') { | |
| process.emitWarning('Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.', { | |
| code: 'UNDICI-FETCH-patch' | |
| }) | |
| patchMethodWarning = true | |
| } | |
| } | |
| // 26. If init["signal"] exists, then set signal to it. | |
| if (init.signal !== undefined) { | |
| signal = init.signal | |
| } | |
| // 27. Set this’s request to request. | |
| this.#state = request | |
| // 28. Set this’s signal to a new AbortSignal object with this’s relevant | |
| // Realm. | |
| // TODO: could this be simplified with AbortSignal.any | |
| // (https://dom.spec.whatwg.org/#dom-abortsignal-any) | |
| const ac = new AbortController() | |
| this.#signal = ac.signal | |
| // 29. If signal is not null, then make this’s signal follow signal. | |
| if (signal != null) { | |
| if (signal.aborted) { | |
| ac.abort(signal.reason) | |
| } else { | |
| // Keep a strong ref to ac while request object | |
| // is alive. This is needed to prevent AbortController | |
| // from being prematurely garbage collected. | |
| // See, https://github.com/nodejs/undici/issues/1926. | |
| this[kAbortController] = ac | |
| const acRef = new WeakRef(ac) | |
| const abort = buildAbort(acRef) | |
| // If the max amount of listeners is equal to the default, increase it | |
| if (abortSignalHasEventHandlerLeakWarning && getMaxListeners(signal) === defaultMaxListeners) { | |
| setMaxListeners(1500, signal) | |
| } | |
| util.addAbortListener(signal, abort) | |
| // The third argument must be a registry key to be unregistered. | |
| // Without it, you cannot unregister. | |
| // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry | |
| // abort is used as the unregister key. (because it is unique) | |
| requestFinalizer.register(ac, { signal, abort }, abort) | |
| } | |
| } | |
| // 30. Set this’s headers to a new Headers object with this’s relevant | |
| // Realm, whose header list is request’s header list and guard is | |
| // "request". | |
| this.#headers = new Headers(kConstruct) | |
| setHeadersList(this.#headers, request.headersList) | |
| setHeadersGuard(this.#headers, 'request') | |
| // 31. If this’s request’s mode is "no-cors", then: | |
| if (mode === 'no-cors') { | |
| // 1. If this’s request’s method is not a CORS-safelisted method, | |
| // then throw a TypeError. | |
| if (!corsSafeListedMethodsSet.has(request.method)) { | |
| throw new TypeError( | |
| `'${request.method} is unsupported in no-cors mode.` | |
| ) | |
| } | |
| // 2. Set this’s headers’s guard to "request-no-cors". | |
| setHeadersGuard(this.#headers, 'request-no-cors') | |
| } | |
| // 32. If init is not empty, then: | |
| if (initHasKey) { | |
| /** @type {HeadersList} */ | |
| const headersList = getHeadersList(this.#headers) | |
| // 1. Let headers be a copy of this’s headers and its associated header | |
| // list. | |
| // 2. If init["headers"] exists, then set headers to init["headers"]. | |
| const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) | |
| // 3. Empty this’s headers’s header list. | |
| headersList.clear() | |
| // 4. If headers is a Headers object, then for each header in its header | |
| // list, append header’s name/header’s value to this’s headers. | |
| if (headers instanceof HeadersList) { | |
| for (const { name, value } of headers.rawValues()) { | |
| headersList.append(name, value, false) | |
| } | |
| // Note: Copy the `set-cookie` meta-data. | |
| headersList.cookies = headers.cookies | |
| } else { | |
| // 5. Otherwise, fill this’s headers with headers. | |
| fillHeaders(this.#headers, headers) | |
| } | |
| } | |
| // 33. Let inputBody be input’s request’s body if input is a Request | |
| // object; otherwise null. | |
| const inputBody = webidl.is.Request(input) ? input.#state.body : null | |
| // 34. If either init["body"] exists and is non-null or inputBody is | |
| // non-null, and request’s method is `GET` or `HEAD`, then throw a | |
| // TypeError. | |
| if ( | |
| (init.body != null || inputBody != null) && | |
| (request.method === 'GET' || request.method === 'HEAD') | |
| ) { | |
| throw new TypeError('Request with GET/HEAD method cannot have body.') | |
| } | |
| // 35. Let initBody be null. | |
| let initBody = null | |
| // 36. If init["body"] exists and is non-null, then: | |
| if (init.body != null) { | |
| // 1. Let Content-Type be null. | |
| // 2. Set initBody and Content-Type to the result of extracting | |
| // init["body"], with keepalive set to request’s keepalive. | |
| const [extractedBody, contentType] = extractBody( | |
| init.body, | |
| request.keepalive | |
| ) | |
| initBody = extractedBody | |
| // 3, If Content-Type is non-null and this’s headers’s header list does | |
| // not contain `Content-Type`, then append `Content-Type`/Content-Type to | |
| // this’s headers. | |
| if (contentType && !getHeadersList(this.#headers).contains('content-type', true)) { | |
| this.#headers.append('content-type', contentType, true) | |
| } | |
| } | |
| // 37. Let inputOrInitBody be initBody if it is non-null; otherwise | |
| // inputBody. | |
| const inputOrInitBody = initBody ?? inputBody | |
| // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is | |
| // null, then: | |
| if (inputOrInitBody != null && inputOrInitBody.source == null) { | |
| // 1. If initBody is non-null and init["duplex"] does not exist, | |
| // then throw a TypeError. | |
| if (initBody != null && init.duplex == null) { | |
| throw new TypeError('RequestInit: duplex option is required when sending a body.') | |
| } | |
| // 2. If this’s request’s mode is neither "same-origin" nor "cors", | |
| // then throw a TypeError. | |
| if (request.mode !== 'same-origin' && request.mode !== 'cors') { | |
| throw new TypeError( | |
| 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' | |
| ) | |
| } | |
| // 3. Set this’s request’s use-CORS-preflight flag. | |
| request.useCORSPreflightFlag = true | |
| } | |
| // 39. Let finalBody be inputOrInitBody. | |
| let finalBody = inputOrInitBody | |
| // 40. If initBody is null and inputBody is non-null, then: | |
| if (initBody == null && inputBody != null) { | |
| // 1. If input is unusable, then throw a TypeError. | |
| if (bodyUnusable(input.#state)) { | |
| throw new TypeError( | |
| 'Cannot construct a Request with a Request object that has already been used.' | |
| ) | |
| } | |
| // 2. Set finalBody to the result of creating a proxy for inputBody. | |
| // https://streams.spec.whatwg.org/#readablestream-create-a-proxy | |
| const identityTransform = new TransformStream() | |
| inputBody.stream.pipeThrough(identityTransform) | |
| finalBody = { | |
| source: inputBody.source, | |
| length: inputBody.length, | |
| stream: identityTransform.readable | |
| } | |
| } | |
| // 41. Set this’s request’s body to finalBody. | |
| this.#state.body = finalBody | |
| } | |
| // Returns request’s HTTP method, which is "GET" by default. | |
| get method () { | |
| webidl.brandCheck(this, Request) | |
| // The method getter steps are to return this’s request’s method. | |
| return this.#state.method | |
| } | |
| // Returns the URL of request as a string. | |
| get url () { | |
| webidl.brandCheck(this, Request) | |
| // The url getter steps are to return this’s request’s URL, serialized. | |
| return URLSerializer(this.#state.url) | |
| } | |
| // Returns a Headers object consisting of the headers associated with request. | |
| // Note that headers added in the network layer by the user agent will not | |
| // be accounted for in this object, e.g., the "Host" header. | |
| get headers () { | |
| webidl.brandCheck(this, Request) | |
| // The headers getter steps are to return this’s headers. | |
| return this.#headers | |
| } | |
| // Returns the kind of resource requested by request, e.g., "document" | |
| // or "script". | |
| get destination () { | |
| webidl.brandCheck(this, Request) | |
| // The destination getter are to return this’s request’s destination. | |
| return this.#state.destination | |
| } | |
| // Returns the referrer of request. Its value can be a same-origin URL if | |
| // explicitly set in init, the empty string to indicate no referrer, and | |
| // "about:client" when defaulting to the global’s default. This is used | |
| // during fetching to determine the value of the `Referer` header of the | |
| // request being made. | |
| get referrer () { | |
| webidl.brandCheck(this, Request) | |
| // 1. If this’s request’s referrer is "no-referrer", then return the | |
| // empty string. | |
| if (this.#state.referrer === 'no-referrer') { | |
| return '' | |
| } | |
| // 2. If this’s request’s referrer is "client", then return | |
| // "about:client". | |
| if (this.#state.referrer === 'client') { | |
| return 'about:client' | |
| } | |
| // Return this’s request’s referrer, serialized. | |
| return this.#state.referrer.toString() | |
| } | |
| // Returns the referrer policy associated with request. | |
| // This is used during fetching to compute the value of the request’s | |
| // referrer. | |
| get referrerPolicy () { | |
| webidl.brandCheck(this, Request) | |
| // The referrerPolicy getter steps are to return this’s request’s referrer policy. | |
| return this.#state.referrerPolicy | |
| } | |
| // Returns the mode associated with request, which is a string indicating | |
| // whether the request will use CORS, or will be restricted to same-origin | |
| // URLs. | |
| get mode () { | |
| webidl.brandCheck(this, Request) | |
| // The mode getter steps are to return this’s request’s mode. | |
| return this.#state.mode | |
| } | |
| // Returns the credentials mode associated with request, | |
| // which is a string indicating whether credentials will be sent with the | |
| // request always, never, or only when sent to a same-origin URL. | |
| get credentials () { | |
| webidl.brandCheck(this, Request) | |
| // The credentials getter steps are to return this’s request’s credentials mode. | |
| return this.#state.credentials | |
| } | |
| // Returns the cache mode associated with request, | |
| // which is a string indicating how the request will | |
| // interact with the browser’s cache when fetching. | |
| get cache () { | |
| webidl.brandCheck(this, Request) | |
| // The cache getter steps are to return this’s request’s cache mode. | |
| return this.#state.cache | |
| } | |
| // Returns the redirect mode associated with request, | |
| // which is a string indicating how redirects for the | |
| // request will be handled during fetching. A request | |
| // will follow redirects by default. | |
| get redirect () { | |
| webidl.brandCheck(this, Request) | |
| // The redirect getter steps are to return this’s request’s redirect mode. | |
| return this.#state.redirect | |
| } | |
| // Returns request’s subresource integrity metadata, which is a | |
| // cryptographic hash of the resource being fetched. Its value | |
| // consists of multiple hashes separated by whitespace. [SRI] | |
| get integrity () { | |
| webidl.brandCheck(this, Request) | |
| // The integrity getter steps are to return this’s request’s integrity | |
| // metadata. | |
| return this.#state.integrity | |
| } | |
| // Returns a boolean indicating whether or not request can outlive the | |
| // global in which it was created. | |
| get keepalive () { | |
| webidl.brandCheck(this, Request) | |
| // The keepalive getter steps are to return this’s request’s keepalive. | |
| return this.#state.keepalive | |
| } | |
| // Returns a boolean indicating whether or not request is for a reload | |
| // navigation. | |
| get isReloadNavigation () { | |
| webidl.brandCheck(this, Request) | |
| // The isReloadNavigation getter steps are to return true if this’s | |
| // request’s reload-navigation flag is set; otherwise false. | |
| return this.#state.reloadNavigation | |
| } | |
| // Returns a boolean indicating whether or not request is for a history | |
| // navigation (a.k.a. back-forward navigation). | |
| get isHistoryNavigation () { | |
| webidl.brandCheck(this, Request) | |
| // The isHistoryNavigation getter steps are to return true if this’s request’s | |
| // history-navigation flag is set; otherwise false. | |
| return this.#state.historyNavigation | |
| } | |
| // Returns the signal associated with request, which is an AbortSignal | |
| // object indicating whether or not request has been aborted, and its | |
| // abort event handler. | |
| get signal () { | |
| webidl.brandCheck(this, Request) | |
| // The signal getter steps are to return this’s signal. | |
| return this.#signal | |
| } | |
| get body () { | |
| webidl.brandCheck(this, Request) | |
| return this.#state.body ? this.#state.body.stream : null | |
| } | |
| get bodyUsed () { | |
| webidl.brandCheck(this, Request) | |
| return !!this.#state.body && util.isDisturbed(this.#state.body.stream) | |
| } | |
| get duplex () { | |
| webidl.brandCheck(this, Request) | |
| return 'half' | |
| } | |
| // Returns a clone of request. | |
| clone () { | |
| webidl.brandCheck(this, Request) | |
| // 1. If this is unusable, then throw a TypeError. | |
| if (bodyUnusable(this.#state)) { | |
| throw new TypeError('unusable') | |
| } | |
| // 2. Let clonedRequest be the result of cloning this’s request. | |
| const clonedRequest = cloneRequest(this.#state) | |
| // 3. Let clonedRequestObject be the result of creating a Request object, | |
| // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. | |
| // 4. Make clonedRequestObject’s signal follow this’s signal. | |
| const ac = new AbortController() | |
| if (this.signal.aborted) { | |
| ac.abort(this.signal.reason) | |
| } else { | |
| let list = dependentControllerMap.get(this.signal) | |
| if (list === undefined) { | |
| list = new Set() | |
| dependentControllerMap.set(this.signal, list) | |
| } | |
| const acRef = new WeakRef(ac) | |
| list.add(acRef) | |
| util.addAbortListener( | |
| ac.signal, | |
| buildAbort(acRef) | |
| ) | |
| } | |
| // 4. Return clonedRequestObject. | |
| return fromInnerRequest(clonedRequest, this.#dispatcher, ac.signal, getHeadersGuard(this.#headers)) | |
| } | |
| [nodeUtil.inspect.custom] (depth, options) { | |
| if (options.depth === null) { | |
| options.depth = 2 | |
| } | |
| options.colors ??= true | |
| const properties = { | |
| method: this.method, | |
| url: this.url, | |
| headers: this.headers, | |
| destination: this.destination, | |
| referrer: this.referrer, | |
| referrerPolicy: this.referrerPolicy, | |
| mode: this.mode, | |
| credentials: this.credentials, | |
| cache: this.cache, | |
| redirect: this.redirect, | |
| integrity: this.integrity, | |
| keepalive: this.keepalive, | |
| isReloadNavigation: this.isReloadNavigation, | |
| isHistoryNavigation: this.isHistoryNavigation, | |
| signal: this.signal | |
| } | |
| return `Request ${nodeUtil.formatWithOptions(options, properties)}` | |
| } | |
| /** | |
| * @param {Request} request | |
| * @param {AbortSignal} newSignal | |
| */ | |
| static setRequestSignal (request, newSignal) { | |
| request.#signal = newSignal | |
| return request | |
| } | |
| /** | |
| * @param {Request} request | |
| */ | |
| static getRequestDispatcher (request) { | |
| return request.#dispatcher | |
| } | |
| /** | |
| * @param {Request} request | |
| * @param {import('../../dispatcher/dispatcher')} newDispatcher | |
| */ | |
| static setRequestDispatcher (request, newDispatcher) { | |
| request.#dispatcher = newDispatcher | |
| } | |
| /** | |
| * @param {Request} request | |
| * @param {Headers} newHeaders | |
| */ | |
| static setRequestHeaders (request, newHeaders) { | |
| request.#headers = newHeaders | |
| } | |
| /** | |
| * @param {Request} request | |
| */ | |
| static getRequestState (request) { | |
| return request.#state | |
| } | |
| /** | |
| * @param {Request} request | |
| * @param {any} newState | |
| */ | |
| static setRequestState (request, newState) { | |
| request.#state = newState | |
| } | |
| } | |
| const { setRequestSignal, getRequestDispatcher, setRequestDispatcher, setRequestHeaders, getRequestState, setRequestState } = Request | |
| Reflect.deleteProperty(Request, 'setRequestSignal') | |
| Reflect.deleteProperty(Request, 'getRequestDispatcher') | |
| Reflect.deleteProperty(Request, 'setRequestDispatcher') | |
| Reflect.deleteProperty(Request, 'setRequestHeaders') | |
| Reflect.deleteProperty(Request, 'getRequestState') | |
| Reflect.deleteProperty(Request, 'setRequestState') | |
| mixinBody(Request, getRequestState) | |
| // https://fetch.spec.whatwg.org/#requests | |
| function makeRequest (init) { | |
| return { | |
| method: init.method ?? 'GET', | |
| localURLsOnly: init.localURLsOnly ?? false, | |
| unsafeRequest: init.unsafeRequest ?? false, | |
| body: init.body ?? null, | |
| client: init.client ?? null, | |
| reservedClient: init.reservedClient ?? null, | |
| replacesClientId: init.replacesClientId ?? '', | |
| window: init.window ?? 'client', | |
| keepalive: init.keepalive ?? false, | |
| serviceWorkers: init.serviceWorkers ?? 'all', | |
| initiator: init.initiator ?? '', | |
| destination: init.destination ?? '', | |
| priority: init.priority ?? null, | |
| origin: init.origin ?? 'client', | |
| policyContainer: init.policyContainer ?? 'client', | |
| referrer: init.referrer ?? 'client', | |
| referrerPolicy: init.referrerPolicy ?? '', | |
| mode: init.mode ?? 'no-cors', | |
| useCORSPreflightFlag: init.useCORSPreflightFlag ?? false, | |
| credentials: init.credentials ?? 'same-origin', | |
| useCredentials: init.useCredentials ?? false, | |
| cache: init.cache ?? 'default', | |
| redirect: init.redirect ?? 'follow', | |
| integrity: init.integrity ?? '', | |
| cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? '', | |
| parserMetadata: init.parserMetadata ?? '', | |
| reloadNavigation: init.reloadNavigation ?? false, | |
| historyNavigation: init.historyNavigation ?? false, | |
| userActivation: init.userActivation ?? false, | |
| taintedOrigin: init.taintedOrigin ?? false, | |
| redirectCount: init.redirectCount ?? 0, | |
| responseTainting: init.responseTainting ?? 'basic', | |
| preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false, | |
| done: init.done ?? false, | |
| timingAllowFailed: init.timingAllowFailed ?? false, | |
| useURLCredentials: init.useURLCredentials ?? undefined, | |
| traversableForUserPrompts: init.traversableForUserPrompts ?? 'client', | |
| urlList: init.urlList, | |
| url: init.urlList[0], | |
| headersList: init.headersList | |
| ? new HeadersList(init.headersList) | |
| : new HeadersList() | |
| } | |
| } | |
| // https://fetch.spec.whatwg.org/#concept-request-clone | |
| function cloneRequest (request) { | |
| // To clone a request request, run these steps: | |
| // 1. Let newRequest be a copy of request, except for its body. | |
| const newRequest = makeRequest({ ...request, body: null }) | |
| // 2. If request’s body is non-null, set newRequest’s body to the | |
| // result of cloning request’s body. | |
| if (request.body != null) { | |
| newRequest.body = cloneBody(request.body) | |
| } | |
| // 3. Return newRequest. | |
| return newRequest | |
| } | |
| /** | |
| * @see https://fetch.spec.whatwg.org/#request-create | |
| * @param {any} innerRequest | |
| * @param {import('../../dispatcher/agent')} dispatcher | |
| * @param {AbortSignal} signal | |
| * @param {'request' | 'immutable' | 'request-no-cors' | 'response' | 'none'} guard | |
| * @returns {Request} | |
| */ | |
| function fromInnerRequest (innerRequest, dispatcher, signal, guard) { | |
| const request = new Request(kConstruct) | |
| setRequestState(request, innerRequest) | |
| setRequestDispatcher(request, dispatcher) | |
| setRequestSignal(request, signal) | |
| const headers = new Headers(kConstruct) | |
| setRequestHeaders(request, headers) | |
| setHeadersList(headers, innerRequest.headersList) | |
| setHeadersGuard(headers, guard) | |
| return request | |
| } | |
| Object.defineProperties(Request.prototype, { | |
| method: kEnumerableProperty, | |
| url: kEnumerableProperty, | |
| headers: kEnumerableProperty, | |
| redirect: kEnumerableProperty, | |
| clone: kEnumerableProperty, | |
| signal: kEnumerableProperty, | |
| duplex: kEnumerableProperty, | |
| destination: kEnumerableProperty, | |
| body: kEnumerableProperty, | |
| bodyUsed: kEnumerableProperty, | |
| isHistoryNavigation: kEnumerableProperty, | |
| isReloadNavigation: kEnumerableProperty, | |
| keepalive: kEnumerableProperty, | |
| integrity: kEnumerableProperty, | |
| cache: kEnumerableProperty, | |
| credentials: kEnumerableProperty, | |
| attribute: kEnumerableProperty, | |
| referrerPolicy: kEnumerableProperty, | |
| referrer: kEnumerableProperty, | |
| mode: kEnumerableProperty, | |
| [Symbol.toStringTag]: { | |
| value: 'Request', | |
| configurable: true | |
| } | |
| }) | |
| webidl.is.Request = webidl.util.MakeTypeAssertion(Request) | |
| /** | |
| * @param {*} V | |
| * @returns {import('../../../types/fetch').Request|string} | |
| * | |
| * @see https://fetch.spec.whatwg.org/#requestinfo | |
| */ | |
| webidl.converters.RequestInfo = function (V) { | |
| if (typeof V === 'string') { | |
| return webidl.converters.USVString(V) | |
| } | |
| if (webidl.is.Request(V)) { | |
| return V | |
| } | |
| return webidl.converters.USVString(V) | |
| } | |
| /** | |
| * @param {*} V | |
| * @returns {import('../../../types/fetch').RequestInit} | |
| * @see https://fetch.spec.whatwg.org/#requestinit | |
| */ | |
| webidl.converters.RequestInit = webidl.dictionaryConverter([ | |
| { | |
| key: 'method', | |
| converter: webidl.converters.ByteString | |
| }, | |
| { | |
| key: 'headers', | |
| converter: webidl.converters.HeadersInit | |
| }, | |
| { | |
| key: 'body', | |
| converter: webidl.nullableConverter( | |
| webidl.converters.BodyInit | |
| ) | |
| }, | |
| { | |
| key: 'referrer', | |
| converter: webidl.converters.USVString | |
| }, | |
| { | |
| key: 'referrerPolicy', | |
| converter: webidl.converters.DOMString, | |
| // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy | |
| allowedValues: referrerPolicy | |
| }, | |
| { | |
| key: 'mode', | |
| converter: webidl.converters.DOMString, | |
| // https://fetch.spec.whatwg.org/#concept-request-mode | |
| allowedValues: requestMode | |
| }, | |
| { | |
| key: 'credentials', | |
| converter: webidl.converters.DOMString, | |
| // https://fetch.spec.whatwg.org/#requestcredentials | |
| allowedValues: requestCredentials | |
| }, | |
| { | |
| key: 'cache', | |
| converter: webidl.converters.DOMString, | |
| // https://fetch.spec.whatwg.org/#requestcache | |
| allowedValues: requestCache | |
| }, | |
| { | |
| key: 'redirect', | |
| converter: webidl.converters.DOMString, | |
| // https://fetch.spec.whatwg.org/#requestredirect | |
| allowedValues: requestRedirect | |
| }, | |
| { | |
| key: 'integrity', | |
| converter: webidl.converters.DOMString | |
| }, | |
| { | |
| key: 'keepalive', | |
| converter: webidl.converters.boolean | |
| }, | |
| { | |
| key: 'signal', | |
| converter: webidl.nullableConverter( | |
| (signal) => webidl.converters.AbortSignal( | |
| signal, | |
| 'RequestInit', | |
| 'signal' | |
| ) | |
| ) | |
| }, | |
| { | |
| key: 'window', | |
| converter: webidl.converters.any | |
| }, | |
| { | |
| key: 'duplex', | |
| converter: webidl.converters.DOMString, | |
| allowedValues: requestDuplex | |
| }, | |
| { | |
| key: 'dispatcher', // undici specific option | |
| converter: webidl.converters.any | |
| }, | |
| { | |
| key: 'priority', | |
| converter: webidl.converters.DOMString, | |
| allowedValues: ['high', 'low', 'auto'], | |
| defaultValue: () => 'auto' | |
| } | |
| ]) | |
| module.exports = { | |
| Request, | |
| makeRequest, | |
| fromInnerRequest, | |
| cloneRequest, | |
| getRequestDispatcher, | |
| getRequestState | |
| } | |