diff --git a/packages/react-native/Libraries/Network/fetch.js b/packages/react-native/Libraries/Network/fetch.js index 292e0dddfc76..b014ddbc1d46 100644 --- a/packages/react-native/Libraries/Network/fetch.js +++ b/packages/react-native/Libraries/Network/fetch.js @@ -12,7 +12,7 @@ // side-effectful require() to put fetch, // Headers, Request, Response in global scope -require('./whatwg-fetch'); +require('whatwg-fetch'); export const fetch = global.fetch; export const Headers = global.Headers; diff --git a/packages/react-native/Libraries/Network/whatwg-fetch.js b/packages/react-native/Libraries/Network/whatwg-fetch.js deleted file mode 100644 index 1dce11082523..000000000000 --- a/packages/react-native/Libraries/Network/whatwg-fetch.js +++ /dev/null @@ -1,651 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Originally vendored from whatwg-fetch v3.6.20 - * Copyright (c) 2014-2023 GitHub, Inc. - * Licensed under the MIT license. - * https://github.com/github/fetch - * - * @noflow - * @nolint - */ - -/* eslint-disable */ -var g = globalThis; - -var support = { - searchParams: 'URLSearchParams' in g, - iterable: 'Symbol' in g && 'iterator' in Symbol, - blob: - 'FileReader' in g && - 'Blob' in g && - (function() { - try { - new Blob() - return true - } catch (e) { - return false - } - })(), - formData: 'FormData' in g, - arrayBuffer: 'ArrayBuffer' in g -} - -function isDataView(obj) { - return obj && DataView.prototype.isPrototypeOf(obj) -} - -if (support.arrayBuffer) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ] - - var isArrayBufferView = - ArrayBuffer.isView || - function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 - } -} - -function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === '') { - throw new TypeError('Invalid character in header field name: "' + name + '"') - } - return name.toLowerCase() -} - -function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value -} - -// Build a destructive iterator for the value list -function iteratorFor(items) { - var iterator = { - next: function() { - var value = items.shift() - return {done: value === undefined, value: value} - } - } - - if (support.iterable) { - iterator[Symbol.iterator] = function() { - return iterator - } - } - - return iterator -} - -export function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - } else if (Array.isArray(headers)) { - headers.forEach(function(header) { - if (header.length != 2) { - throw new TypeError('Headers constructor: expected name/value pair to be length 2, found' + header.length) - } - this.append(header[0], header[1]) - }, this) - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } -} - -Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var oldValue = this.map[name] - this.map[name] = oldValue ? oldValue + ', ' + value : value -} - -Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] -} - -Headers.prototype.get = function(name) { - name = normalizeName(name) - return this.has(name) ? this.map[name] : null -} - -Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) -} - -Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = normalizeValue(value) -} - -Headers.prototype.forEach = function(callback, thisArg) { - for (var name in this.map) { - if (this.map.hasOwnProperty(name)) { - callback.call(thisArg, this.map[name], name, this) - } - } -} - -Headers.prototype.keys = function() { - var items = [] - this.forEach(function(value, name) { - items.push(name) - }) - return iteratorFor(items) -} - -Headers.prototype.values = function() { - var items = [] - this.forEach(function(value) { - items.push(value) - }) - return iteratorFor(items) -} - -Headers.prototype.entries = function() { - var items = [] - this.forEach(function(value, name) { - items.push([name, value]) - }) - return iteratorFor(items) -} - -if (support.iterable) { - Headers.prototype[Symbol.iterator] = Headers.prototype.entries -} - -function consumed(body) { - if (body._noBody) return - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true -} - -function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) -} - -function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsArrayBuffer(blob) - return promise -} - -function readBlobAsText(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type) - var encoding = match ? match[1] : 'utf-8' - reader.readAsText(blob, encoding) - return promise -} - -function readArrayBufferAsText(buf) { - var view = new Uint8Array(buf) - var chars = new Array(view.length) - - for (var i = 0; i < view.length; i++) { - chars[i] = String.fromCharCode(view[i]) - } - return chars.join('') -} - -function bufferClone(buf) { - if (buf.slice) { - return buf.slice(0) - } else { - var view = new Uint8Array(buf.byteLength) - view.set(new Uint8Array(buf)) - return view.buffer - } -} - -function Body() { - this.bodyUsed = false - - this._initBody = function(body) { - /* - fetch-mock wraps the Response object in an ES6 Proxy to - provide useful test harness features such as flush. However, on - ES5 browsers without fetch or Proxy support pollyfills must be used; - the proxy-pollyfill is unable to proxy an attribute unless it exists - on the object before the Proxy is created. This change ensures - Response.bodyUsed exists on the instance, while maintaining the - semantic of setting Request.bodyUsed in the constructor before - _initBody is called. - */ - this.bodyUsed = this.bodyUsed - this._bodyInit = body - if (!body) { - this._noBody = true; - this._bodyText = '' - } else if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString() - } else if (support.arrayBuffer && support.blob && isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer) - // IE 10-11 can't handle a DataView body. - this._bodyInit = new Blob([this._bodyArrayBuffer]) - } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body) - } else { - this._bodyText = body = Object.prototype.toString.call(body) - } - - if (!this.headers.get('content-type')) { - if (typeof body === 'string') { - this.headers.set('content-type', 'text/plain;charset=UTF-8') - } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set('content-type', this._bodyBlob.type) - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') - } - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - } - - this.arrayBuffer = function() { - if (this._bodyArrayBuffer) { - var isConsumed = consumed(this) - if (isConsumed) { - return isConsumed - } else if (ArrayBuffer.isView(this._bodyArrayBuffer)) { - return Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer.byteOffset, - this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength - ) - ) - } else { - return Promise.resolve(this._bodyArrayBuffer) - } - } else if (support.blob) { - return this.blob().then(readBlobAsArrayBuffer) - } else { - throw new Error('could not read as ArrayBuffer') - } - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this -} - -// HTTP methods whose capitalization should be normalized -var methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'] - -function normalizeMethod(method) { - var upcased = method.toUpperCase() - return methods.indexOf(upcased) > -1 ? upcased : method -} - -export function Request(input, options) { - if (!(this instanceof Request)) { - throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') - } - - options = options || {} - var body = options.body - - if (input instanceof Request) { - if (input.bodyUsed) { - throw new TypeError('Already read') - } - this.url = input.url - this.credentials = input.credentials - if (!options.headers) { - this.headers = new Headers(input.headers) - } - this.method = input.method - this.mode = input.mode - this.signal = input.signal - if (!body && input._bodyInit != null) { - body = input._bodyInit - input.bodyUsed = true - } - } else { - this.url = String(input) - } - - this.credentials = options.credentials || this.credentials || 'same-origin' - if (options.headers || !this.headers) { - this.headers = new Headers(options.headers) - } - this.method = normalizeMethod(options.method || this.method || 'GET') - this.mode = options.mode || this.mode || null - this.signal = options.signal || this.signal || (function () { - if ('AbortController' in g) { - var ctrl = new AbortController(); - return ctrl.signal; - } - }()); - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(body) - - if (this.method === 'GET' || this.method === 'HEAD') { - if (options.cache === 'no-store' || options.cache === 'no-cache') { - // Search for a '_' parameter in the query string - var reParamSearch = /([?&])_=[^&]*/ - if (reParamSearch.test(this.url)) { - // If it already exists then set the value with the current time - this.url = this.url.replace(reParamSearch, '$1_=' + new Date().getTime()) - } else { - // Otherwise add a new '_' parameter to the end with the current time - var reQueryString = /\?/ - this.url += (reQueryString.test(this.url) ? '&' : '?') + '_=' + new Date().getTime() - } - } - } -} - -Request.prototype.clone = function() { - return new Request(this, {body: this._bodyInit}) -} - -function decode(body) { - var form = new FormData() - body - .trim() - .split('&') - .forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form -} - -function parseHeaders(rawHeaders) { - var headers = new Headers() - // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space - // https://tools.ietf.org/html/rfc7230#section-3.2 - var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ') - // Avoiding split via regex to work around a common IE11 bug with the core-js 3.6.0 regex polyfill - // https://github.com/github/fetch/issues/748 - // https://github.com/zloirock/core-js/issues/751 - preProcessedHeaders - .split('\r') - .map(function(header) { - return header.indexOf('\n') === 0 ? header.substr(1, header.length) : header - }) - .forEach(function(line) { - var parts = line.split(':') - var key = parts.shift().trim() - if (key) { - var value = parts.join(':').trim() - try { - headers.append(key, value) - } catch (error) { - console.warn('Response ' + error.message) - } - } - }) - return headers -} - -Body.call(Request.prototype) - -export function Response(bodyInit, options) { - if (!(this instanceof Response)) { - throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.') - } - if (!options) { - options = {} - } - - this.type = 'default' - this.status = options.status === undefined ? 200 : options.status - if (this.status < 200 || this.status > 599) { - throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].") - } - this.ok = this.status >= 200 && this.status < 300 - this.statusText = options.statusText === undefined ? '' : '' + options.statusText - this.headers = new Headers(options.headers) - this.url = options.url || '' - this._initBody(bodyInit) -} - -Body.call(Response.prototype) - -Response.prototype.clone = function() { - return new Response(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new Headers(this.headers), - url: this.url - }) -} - -Response.error = function() { - var response = new Response(null, {status: 200, statusText: ''}) - response.ok = false - response.status = 0 - response.type = 'error' - return response -} - -var redirectStatuses = [301, 302, 303, 307, 308] - -Response.redirect = function(url, status) { - if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError('Invalid status code') - } - - return new Response(null, {status: status, headers: {location: url}}) -} - -export var DOMException = g.DOMException -try { - new DOMException() -} catch (err) { - DOMException = function(message, name) { - this.message = message - this.name = name - var error = Error(message) - this.stack = error.stack - } - DOMException.prototype = Object.create(Error.prototype) - DOMException.prototype.constructor = DOMException -} - -export function fetch(input, init) { - return new Promise(function(resolve, reject) { - var request = new Request(input, init) - - if (request.signal && request.signal.aborted) { - return reject(new DOMException('Aborted', 'AbortError')) - } - - var xhr = new XMLHttpRequest() - - function abortXhr() { - xhr.abort() - } - - xhr.onload = function() { - var options = { - statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || '') - } - // This check if specifically for when a user fetches a file locally from the file system - // Only if the status is out of a normal range - if (request.url.indexOf('file://') === 0 && (xhr.status < 200 || xhr.status > 599)) { - options.status = 200; - } else { - options.status = xhr.status; - } - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') - var body = 'response' in xhr ? xhr.response : xhr.responseText - setTimeout(function() { - resolve(new Response(body, options)) - }, 0) - } - - xhr.onerror = function() { - setTimeout(function() { - reject(new TypeError('Network request failed')) - }, 0) - } - - xhr.ontimeout = function() { - setTimeout(function() { - reject(new TypeError('Network request timed out')) - }, 0) - } - - xhr.onabort = function() { - setTimeout(function() { - reject(new DOMException('Aborted', 'AbortError')) - }, 0) - } - - function fixUrl(url) { - try { - return url === '' && g.location.href ? g.location.href : url - } catch (e) { - return url - } - } - - xhr.open(request.method, fixUrl(request.url), true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } else if (request.credentials === 'omit') { - xhr.withCredentials = false - } - - if ('responseType' in xhr) { - if (support.blob) { - xhr.responseType = 'blob' - } else if ( - support.arrayBuffer - ) { - xhr.responseType = 'arraybuffer' - } - } - - if (init && typeof init.headers === 'object' && !(init.headers instanceof Headers || (g.Headers && init.headers instanceof g.Headers))) { - var names = []; - Object.getOwnPropertyNames(init.headers).forEach(function(name) { - names.push(normalizeName(name)) - xhr.setRequestHeader(name, normalizeValue(init.headers[name])) - }) - request.headers.forEach(function(value, name) { - if (names.indexOf(name) === -1) { - xhr.setRequestHeader(name, value) - } - }) - } else { - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) - } - - if (request.signal) { - request.signal.addEventListener('abort', abortXhr) - - xhr.onreadystatechange = function() { - // DONE (success or failure) - if (xhr.readyState === 4) { - request.signal.removeEventListener('abort', abortXhr) - } - } - } - - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) -} - -fetch.polyfill = true - -if (!g.fetch) { - g.fetch = fetch - g.Headers = Headers - g.Request = Request - g.Response = Response -} diff --git a/packages/react-native/Libraries/Network/whatwg-fetch.js.flow b/packages/react-native/Libraries/Network/whatwg-fetch.js.flow deleted file mode 100644 index 24939524e3c3..000000000000 --- a/packages/react-native/Libraries/Network/whatwg-fetch.js.flow +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Originally vendored from whatwg-fetch v3.6.20 - * Copyright (c) 2014-2023 GitHub, Inc. - * Licensed under the MIT license. - * https://github.com/github/fetch - * - * @nolint - */ - -/* @flow strict */ - -type CredentialsType = 'omit' | 'same-origin' | 'include' - -type ResponseType = 'default' | 'error' - -type BodyInit = string | URLSearchParams | FormData | Blob | ArrayBuffer | $ArrayBufferView - -type RequestInfo = Request | URL | string - -type RequestOptions = {| - body?: ?BodyInit; - - credentials?: CredentialsType; - headers?: HeadersInit; - method?: string; - mode?: string; - referrer?: string; - signal?: ?AbortSignal; -|} - -type ResponseOptions = {| - status?: number; - statusText?: string; - headers?: HeadersInit; -|} - -type HeadersInit = Headers | {[string]: string} - -// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L902-L914 -declare class Headers { - @@iterator(): Iterator<[string, string]>; - constructor(init?: HeadersInit): void; - append(name: string, value: string): void; - delete(name: string): void; - entries(): Iterator<[string, string]>; - forEach((value: string, name: string, headers: Headers) => $FlowFixMe, thisArg?: $FlowFixMe): void; - get(name: string): null | string; - has(name: string): boolean; - keys(): Iterator; - set(name: string, value: string): void; - values(): Iterator; -} - -// https://github.com/facebook/flow/pull/6548 -interface AbortSignal { - aborted: boolean; - addEventListener(type: string, listener: (Event) => unknown, options?: EventListenerOptionsOrUseCapture): void; - removeEventListener(type: string, listener: (Event) => unknown, options?: EventListenerOptionsOrUseCapture): void; -} - -// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L994-L1018 -// unsupported in polyfill: -// - cache -// - integrity -// - redirect -// - referrerPolicy -declare class Request { - constructor(input: RequestInfo, init?: RequestOptions): void; - clone(): Request; - - url: string; - - credentials: CredentialsType; - headers: Headers; - method: string; - mode: ModeType; - referrer: string; - signal: ?AbortSignal; - - // Body methods and attributes - bodyUsed: boolean; - - arrayBuffer(): Promise; - blob(): Promise; - formData(): Promise; - json(): Promise<$FlowFixMe>; - text(): Promise; -} - -// https://github.com/facebook/flow/blob/f68b89a5012bd995ab3509e7a41b7325045c4045/lib/bom.js#L968-L992 -// unsupported in polyfill: -// - body -// - redirected -// - trailer -declare class Response { - constructor(input?: ?BodyInit, init?: ResponseOptions): void; - clone(): Response; - static error(): Response; - static redirect(url: string, status?: number): Response; - - type: ResponseType; - url: string; - ok: boolean; - status: number; - statusText: string; - headers: Headers; - - // Body methods and attributes - bodyUsed: boolean; - - arrayBuffer(): Promise; - blob(): Promise; - formData(): Promise; - json(): Promise<$FlowFixMe>; - text(): Promise; -} - -declare class DOMException extends Error { - constructor(message?: string, name?: string): void; -} - -declare module.exports: { - fetch(input: RequestInfo, init?: RequestOptions): Promise; - Headers: typeof Headers; - Request: typeof Request; - Response: typeof Response; - DOMException: typeof DOMException; -} diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 6d6344b460a8..bce8b72a0a00 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -191,6 +191,7 @@ "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.6.20", "ws": "^7.5.10", "yargs": "^17.6.2" }, diff --git a/yarn.lock b/yarn.lock index a7448d6cd922..f9782c695269 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9368,6 +9368,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +whatwg-fetch@^3.6.20: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"