/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/polyfills/fetch.js
304 строки
7 KB
Justin Ridgewell
Lint remaining static enums in src (#36821)
16 ноя 2021, 01:07
Не верифицирован
16 ноя 2021, 01:07
556e94a
Код
Авторство
О чём код?
import {devAssert} from '#core/assert'; import {isArray, isObject} from '#core/types'; import {hasOwn, map} from '#core/types/object'; import {parseJson} from '#core/types/object/json'; import {utf8Encode} from '#core/types/string/bytes'; import {dev, user} from '#utils/log'; /** @const {!Array<string>} */ const allowedMethods = ['GET', 'POST']; /** * A record version of `XMLHttpRequest` that has all the necessary properties * and methods of `XMLHttpRequest` to construct a `FetchResponse` from a * serialized response returned by the viewer. * @typedef {{ * status: number, * statusText: string, * responseText: string, * getResponseHeader: function(this:XMLHttpRequestDef, string): string, * }} */ let XMLHttpRequestDef; /** * A minimal polyfill of Fetch API. It only polyfills what we currently use. * * See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch * * Notice that the "fetch" method itself is not exported as that would require * us to immediately support a much wide API. * * @param {string} input * @param {!Object|RequestInit=} init * @return {!Promise<!FetchResponse>} */ export function fetchPolyfill(input, init = {}) { return new Promise(function (resolve, reject) { const requestMethod = normalizeMethod(init.method || 'GET'); const xhr = createXhrRequest(requestMethod, input); if (init.credentials == 'include') { xhr.withCredentials = true; } if (init.responseType === 'document' || init.responseType === 'text') { xhr.responseType = init.responseType; } if (init.headers) { Object.keys(init.headers).forEach(function (header) { xhr.setRequestHeader(header, init.headers[header]); }); } xhr.onreadystatechange = () => { if (xhr.readyState < /* STATUS_RECEIVED */ 2) { return; } if (xhr.status < 100 || xhr.status > 599) { xhr.onreadystatechange = null; reject(user().createExpectedError(`Unknown HTTP status ${xhr.status}`)); return; } // TODO(dvoytenko): This is currently simplified: we will wait for the // whole document loading to complete. This is fine for the use cases // we have now, but may need to be reimplemented later. if (xhr.readyState == /* COMPLETE */ 4) { resolve(new FetchResponse(xhr)); } }; xhr.onerror = () => { reject(user().createExpectedError('Network failure')); }; xhr.onabort = () => { reject(user().createExpectedError('Request aborted')); }; if (requestMethod == 'POST') { xhr.send(init.body); } else { xhr.send(); } }); } /** * @param {string} method * @param {string} url * @return {!XMLHttpRequest} */ function createXhrRequest(method, url) { const xhr = new XMLHttpRequest(); if ('withCredentials' in xhr) { xhr.open(method, url, true); } else { throw dev().createExpectedError('CORS is not supported'); } return xhr; } /** * Response object in the Fetch API. * * See https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch */ class FetchResponse { /** * @param {!XMLHttpRequest|!XMLHttpRequestDef} xhr */ constructor(xhr) { /** @private @const {!XMLHttpRequest|!XMLHttpRequestDef} */ this.xhr_ = xhr; /** @const {number} */ this.status = this.xhr_.status; /** @const {string} */ this.statusText = this.xhr_.statusText; /** @const {boolean} */ this.ok = this.status >= 200 && this.status < 300; /** @const {!FetchResponseHeaders} */ this.headers = new FetchResponseHeaders(xhr); /** @type {boolean} */ this.bodyUsed = false; /** @type {?ReadableStream} */ this.body = null; /** @type {?string} */ this.url = xhr.responseURL; } /** * Create a copy of the response and return it. * @return {!FetchResponse} */ clone() { devAssert(!this.bodyUsed, 'Body already used'); return new FetchResponse(this.xhr_); } /** * Drains the response and returns the text. * @return {!Promise<string>} * @private */ drainText_() { devAssert(!this.bodyUsed, 'Body already used'); this.bodyUsed = true; return Promise.resolve(this.xhr_.responseText); } /** * Drains the response and returns a promise that resolves with the response * text. * @return {!Promise<string>} */ text() { return this.drainText_(); } /** * Drains the response and returns the JSON object. * @return {!Promise<!JsonObject>} */ json() { return /** @type {!Promise<!JsonObject>} */ ( this.drainText_().then(parseJson) ); } /** * Drains the response and returns a promise that resolves with the response * ArrayBuffer. * @return {!Promise<!ArrayBuffer>} */ arrayBuffer() { return /** @type {!Promise<!ArrayBuffer>} */ ( this.drainText_().then(utf8Encode) ); } } /** * Normalized method name by uppercasing. * @param {string|undefined} method * @return {string} * @private */ function normalizeMethod(method) { if (method === undefined) { return 'GET'; } method = method.toUpperCase(); devAssert( allowedMethods.includes(method), 'Only one of %s is currently allowed. Got %s', allowedMethods.join(', '), method ); return method; } /** * Provides access to the response headers as defined in the Fetch API. * @private Visible for testing. */ class FetchResponseHeaders { /** * @param {!XMLHttpRequest|!XMLHttpRequestDef} xhr */ constructor(xhr) { /** @private @const {!XMLHttpRequest|!XMLHttpRequestDef} */ this.xhr_ = xhr; } /** * @param {string} name * @return {string} */ get(name) { return this.xhr_.getResponseHeader(name); } /** * @param {string} name * @return {boolean} */ has(name) { return this.xhr_.getResponseHeader(name) != null; } } export class Response extends FetchResponse { /** * Returns instance of Response. * @param {?ResponseBodyInit=} body * @param {!ResponseInit=} init */ constructor(body, init = {}) { const lowercasedHeaders = map(); const data = { status: 200, statusText: 'OK', responseText: body ? String(body) : '', getResponseHeader(name) { const headerName = String(name).toLowerCase(); return hasOwn(lowercasedHeaders, headerName) ? lowercasedHeaders[headerName] : null; }, ...init, }; data.status = init.status === undefined ? 200 : parseInt(init.status, 10); if (isArray(init.headers)) { /** @type {!Array} */ (init.headers).forEach((entry) => { const headerName = entry[0]; const headerValue = entry[1]; lowercasedHeaders[String(headerName).toLowerCase()] = String(headerValue); }); } else if (isObject(init.headers)) { for (const key in init.headers) { lowercasedHeaders[String(key).toLowerCase()] = String( init.headers[key] ); } } if (init.statusText) { data.statusText = String(init.statusText); } super(/** @type {XMLHttpRequestDef} */ (data)); } } /** * Installs fetch and Response polyfill * @param {Window} win */ export function install(win) { if (!win.fetch) { Object.defineProperty(win, 'fetch', { value: fetchPolyfill, writable: true, enumerable: true, configurable: true, }); Object.defineProperty(win, 'Response', { value: Response, writable: true, enumerable: false, configurable: true, }); } }