diff --git a/packages/core/fetch/index.js b/packages/core/fetch/index.js index 47e9a624d..dec761aed 100644 --- a/packages/core/fetch/index.js +++ b/packages/core/fetch/index.js @@ -1,539 +1,636 @@ -(function () { - "use strict"; +var g = global; - var support = { - searchParams: "URLSearchParams" in global, - iterable: "Symbol" in global && "iterator" in Symbol, - blob: - "FileReader" in global && - "Blob" in global && - (function () { - try { - new Blob(); - return true; - } catch (e) { - return false; - } - })(), - formData: "FormData" in global, - arrayBuffer: "ArrayBuffer" in global - }; +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); +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. + */ + // eslint-disable-next-line no-self-assign + 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 (support.arrayBuffer) { - var viewClasses = [ - "[object Int8Array]", - "[object Uint8Array]", - "[object Uint8ClampedArray]", - "[object Int16Array]", - "[object Uint16Array]", - "[object Int32Array]", - "[object Uint32Array]", - "[object Float32Array]", - "[object Float64Array]" - ]; + 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') + } + } + } - var isArrayBufferView = - ArrayBuffer.isView || - function (obj) { - return ( - obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 - ); - }; + 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 } - function normalizeName(name) { - if (typeof name !== "string") { - name = String(name); + 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) } - if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) { - throw new TypeError("Invalid character in header field name"); + } + }) + 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) } - return name.toLowerCase(); + }) + } else { + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value) + }) } - function normalizeValue(value) { - if (typeof value !== "string") { - value = String(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) } - 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; - } - - 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) { - 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.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); - reader.readAsText(blob); - 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) { - this._bodyInit = body; - if (!body) { - 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) { - return consumed(this) || Promise.resolve(this._bodyArrayBuffer); - } else { - return this.blob().then(readBlobAsArrayBuffer); - } - }; - } - - 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 = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]; - - function normalizeMethod(method) { - var upcased = method.toUpperCase(); - return methods.indexOf(upcased) > -1 ? upcased : method; - } - - function Request(input, options) { - 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; - 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); - } - - 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, " "); - preProcessedHeaders.split(/\r?\n/).forEach(function (line) { - var parts = line.split(":"); - var key = parts.shift().trim(); - if (key) { - var value = parts.join(":").trim(); - headers.append(key, value); - } - }); - return headers; - } - - Body.call(Request.prototype); - - function Response(bodyInit, options) { - if (!options) { - options = {}; - } - - this.type = "default"; - this.status = options.status === undefined ? 200 : options.status; - this.ok = this.status >= 200 && this.status < 300; - this.statusText = "statusText" in options ? options.statusText : "OK"; - 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: 0, statusText: "" }); - 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 } }); - }; - - exports.DOMException = global.DOMException; - try { - new exports.DOMException(); - } catch (err) { - exports.DOMException = function (message, name) { - this.message = message; - this.name = name; - var error = Error(message); - this.stack = error.stack; - }; - exports.DOMException.prototype = Object.create(Error.prototype); - exports.DOMException.prototype.constructor = exports.DOMException; - } - - 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 exports.DOMException("Aborted", "AbortError")); - } - - var xhr = new XMLHttpRequest(); - - function abortXhr() { - xhr.abort(); - } - - xhr.onload = function () { - var options = { - status: xhr.status, - statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || "") - }; - options.url = - "responseURL" in xhr - ? xhr.responseURL - : options.headers.get("X-Request-URL"); - var body = "response" in xhr ? xhr.response : xhr.responseText; - resolve(new Response(body, options)); - }; - - xhr.onerror = function () { - reject(new TypeError("Network request failed")); - }; - - xhr.ontimeout = function () { - reject(new TypeError("Network request failed")); - }; - - xhr.onabort = function () { - reject(new exports.DOMException("Aborted", "AbortError")); - }; - - xhr.open(request.method, request.url, true); - - if (request.credentials === "include") { - xhr.withCredentials = true; - } else if (request.credentials === "omit") { - xhr.withCredentials = false; - } - - if ("responseType" in xhr && support.blob) { - xhr.responseType = "blob"; - } - - 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; - - exports.Headers = Headers; - exports.Request = Request; - exports.Response = Response; - exports.fetch = fetch; - - Object.defineProperty(exports, "__esModule", { value: true }); -})(); + 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 +} \ No newline at end of file diff --git a/packages/core/globals/index.ts b/packages/core/globals/index.ts index 51d7dd959..09e2b7e34 100644 --- a/packages/core/globals/index.ts +++ b/packages/core/globals/index.ts @@ -1,7 +1,37 @@ -import type * as tslibType from 'tslib'; -const tslib: typeof tslibType = require('tslib'); +import tslib from 'tslib'; import { Observable } from '../data/observable'; import { trace as profilingTrace, time, uptime, level as profilingLevel } from '../profiling'; +import * as timer from '../timer'; +import * as animationFrame from '../animation-frame'; +import * as mediaQueryList from '../media-query-list'; +import * as uiDialogs from '../ui/dialogs'; +import * as text from '../text'; +import * as xhrImpl from '../xhr'; +import '../fetch'; +import * as wgc from '../wgc'; +import * as cryptoImpl from '../wgc/crypto'; +import * as subtleCryptoImpl from '../wgc/crypto/SubtleCrypto'; + +if (typeof global.__metadata === 'undefined') { + /** + * TS decorator metadata helper. + * @param metadataKey the metadata key (e.g. "design:type") + * @param metadataValue the metadata value (e.g. the constructor function) + * @returns a decorator function, or undefined if Reflect.metadata isn’t available + */ + global.__metadata = (metadataKey, metadataValue) => { + if ( + typeof Reflect === 'object' && + // @ts-expect-error + typeof Reflect.metadata === 'function' + ) { + // Delegate to the reflect-metadata shim + // @ts-expect-error + return Reflect.metadata(metadataKey, metadataValue); + } + // no-op if no Reflect.metadata + }; +} type ModuleLoader = (name?: string) => any; @@ -301,34 +331,34 @@ export function initGlobal() { }; // DOM api polyfills - global.registerModule('timer', () => require('../timer')); + global.registerModule('timer', () => timer); installPolyfills('timer', ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval']); - global.registerModule('animation', () => require('../animation-frame')); + global.registerModule('animation', () => animationFrame); installPolyfills('animation', ['requestAnimationFrame', 'cancelAnimationFrame']); - global.registerModule('media-query-list', () => require('../media-query-list')); + global.registerModule('media-query-list', () => mediaQueryList); installPolyfills('media-query-list', ['matchMedia', 'MediaQueryList']); - global.registerModule('ui-dialogs', () => require('../ui/dialogs')); + global.registerModule('ui-dialogs', () => uiDialogs); installPolyfills('ui-dialogs', ['alert', 'confirm', 'prompt', 'login', 'action']); - global.registerModule('text', () => require('../text')); + global.registerModule('text', () => text); installPolyfills('text', ['TextDecoder', 'TextEncoder']); - global.registerModule('xhr', () => require('../xhr')); + global.registerModule('xhr', () => xhrImpl); installPolyfills('xhr', ['XMLHttpRequest', 'FormData', 'Blob', 'File', 'FileReader']); - global.registerModule('fetch', () => require('../fetch')); - installPolyfills('fetch', ['fetch', 'Headers', 'Request', 'Response']); + // global.registerModule('fetch', () => require('../fetch')); + // installPolyfills('fetch', ['fetch', 'Headers', 'Request', 'Response']); - global.registerModule('wgc', () => require('../wgc')); + global.registerModule('wgc', () => wgc); installPolyfills('wgc', ['atob', 'btoa']); - global.registerModule('crypto', () => require('../wgc/crypto')); + global.registerModule('crypto', () => cryptoImpl); installPolyfills('crypto', ['Crypto']); - global.registerModule('subtle', () => require('../wgc/crypto/SubtleCrypto')); + global.registerModule('subtle', () => subtleCryptoImpl); installPolyfills('subtle-crypto', ['Subtle']); global.crypto = new global.Crypto(); diff --git a/packages/core/ui/builder/xml2ui.ts b/packages/core/ui/builder/xml2ui.ts index 338de9d9e..51f6d1b2a 100644 --- a/packages/core/ui/builder/xml2ui.ts +++ b/packages/core/ui/builder/xml2ui.ts @@ -8,6 +8,28 @@ import { Device } from '../../platform'; import { profile } from '../../profiling'; import { android, ios, visionos, apple, loadCustomComponent, defaultNameSpaceMatcher, getExports, Builder } from './index'; +// Note: after all circulars are resolve, try importing this from single place or see if globals/index.ts properly handles it +if (typeof global.__metadata === 'undefined') { + /** + * TS decorator metadata helper. + * @param metadataKey the metadata key (e.g. "design:type") + * @param metadataValue the metadata value (e.g. the constructor function) + * @returns a decorator function, or undefined if Reflect.metadata isn’t available + */ + global.__metadata = (metadataKey, metadataValue) => { + if ( + typeof Reflect === 'object' && + // @ts-expect-error + typeof Reflect.metadata === 'function' + ) { + // Delegate to the reflect-metadata shim + // @ts-expect-error + return Reflect.metadata(metadataKey, metadataValue); + } + // no-op if no Reflect.metadata + }; +} + export namespace xml2ui { /** * Pipes and filters: