UNPKG

@lifaon/rx-js-light

Version:

Blazing fast Observables

236 lines (235 loc) 8.38 kB
import {createAbortError} from "../../../../../../misc/errors/abort-error/create-abort-error.mjs"; import {createNetworkError} from "../../../../../../misc/errors/network-error/create-network-error.mjs"; import {createEventListener} from "../../../../../../misc/event-listener/functions/create-event-listener.mjs"; import {toTypedEventTarget} from "../../../../../../misc/event-listener/functions/to-typed-event-target.mjs"; function getResponseClass() { return typeof Response === 'undefined' ? function () { throw new Error(`Unsupported Response`); } : Response; } export class XHRResponse extends getResponseClass() { constructor(body, init) { super(body, init); this._url = init?.url ?? super.url; } get url() { return this._url; } } export function areReadableStreamSupported() { return globalThis.ReadableStream !== void 0; } export function isReadableStream(value) { return value instanceof ReadableStream; } export function setHeadersIntoXHR(headers, xhr) { headers.forEach((value, key) => { xhr.setRequestHeader(key, value); }); } export function getXHRWithCredentialsValueFromRequest(request) { switch (request.credentials) { case 'omit': return false; case 'same-origin': return window.location.origin === new URL(request.url).origin; case 'include': return true; default: throw new TypeError(`Unsupported request.credentials`); } } export function initXHRFromRequest(request, xhr, responseType) { if (request.signal.aborted) { xhr.abort(); } else { const end = () => { removeLoadEventListener(); removeErrorEventListener(); removeAbortEventListener(); }; xhr.open(request.method, request.url, true); if (responseType === 'binary-string') { xhr.responseType = 'text'; xhr.overrideMimeType('text/plain; charset=x-user-defined'); } else { xhr.responseType = responseType; } xhr.withCredentials = getXHRWithCredentialsValueFromRequest(request); setHeadersIntoXHR(request.headers, xhr); const removeLoadEventListener = createEventListener(toTypedEventTarget(xhr), 'load', end); const removeErrorEventListener = createEventListener(toTypedEventTarget(xhr), 'error', end); const removeAbortEventListener = createEventListener(toTypedEventTarget(request.signal), 'abort', () => { end(); xhr.abort(); }); } } export function initAndSendXHRFromRequestAndBody(xhr, responseType, request, body) { initXHRFromRequest(request, xhr, responseType); xhr.send(body); } export function initAndSendXHRFromRequestUsingReadableStream(xhr, responseType, request) { return initAndSendXHRFromRequestAndBody(xhr, responseType, request, request.body); } export function initAndSendXHRFromRequestUsingBlob(xhr, responseType, request, signal) { if (signal?.aborted) { return Promise.reject(createAbortError()); } else { return request.blob().then(blob => { if (signal?.aborted) { throw createAbortError(); } else { return initAndSendXHRFromRequestAndBody(xhr, responseType, request, blob); } }); } } export function initAndSendXHRFromRequest(xhr, responseType, request, signal) { return areReadableStreamSupported() && isReadableStream(request.body) ? new Promise(resolve => resolve(initAndSendXHRFromRequestUsingReadableStream(xhr, responseType, request))) : initAndSendXHRFromRequestUsingBlob(xhr, responseType, request, signal); } export function binaryStringToUint8Array(input) { const length = input.length; const array = new Uint8Array(length); for (let i = 0; i < length; i++) { array[i] = input.charCodeAt(i); } return array; } export function XHRResponseToUint8Array(xhr, responseType = xhr.responseType) { switch (responseType) { case '': case 'text': return new TextEncoder().encode(xhr.response); case 'binary-string': return binaryStringToUint8Array(xhr.response); case 'arraybuffer': return new Uint8Array(xhr.response); case 'blob': throw new TypeError(`Cannot synchronously convert a blob to an Uint8Array`); case 'document': return new TextEncoder().encode(new XMLSerializer().serializeToString(xhr.response)); case 'json': return new TextEncoder().encode(JSON.stringify(xhr.response)); default: throw new TypeError(`Unsupported response type '${responseType}'`); } } export function XHRResponseToBlob(xhr, responseType = xhr.responseType) { const contentType = xhr.getResponseHeader('content-type'); switch (responseType) { case '': case 'text': return new Blob([xhr.response], { type: contentType || 'text/plain' }); case 'binary-string': return new Blob([binaryStringToUint8Array(xhr.response)], { type: contentType || 'text/plain' }); case 'arraybuffer': return new Blob([xhr.response], { type: contentType || void 0 }); case 'blob': return xhr.response; case 'document': return new Blob([new XMLSerializer().serializeToString(xhr.response)], { type: contentType || xhr.response.contentType }); case 'json': return new Blob([JSON.stringify(xhr.response)], { type: contentType || 'application/json' }); default: throw new TypeError(`Unsupported response type '${responseType}'`); } } export function XHRResponseToReadableStream(xhr, responseType = xhr.responseType) { let end; return new ReadableStream({ start(controller) { const isStreamableResponseType = responseType === 'binary-string'; let readIndex = 0; end = () => { removeLoadEventListener(); removeErrorEventListener(); removeAbortEventListener(); if (removeProgressEventListener !== void 0) { removeProgressEventListener(); } }; const progress = () => { const index = xhr.response.length; if (index !== readIndex) { const data = binaryStringToUint8Array(xhr.response.substring(readIndex, index)); controller.enqueue(data); readIndex = index; } }; const removeLoadEventListener = createEventListener(toTypedEventTarget(xhr), 'load', () => { end(); if (isStreamableResponseType) { progress(); } else { controller.enqueue(XHRResponseToUint8Array(xhr.response, responseType)); } controller.close(); }); const removeErrorEventListener = createEventListener(toTypedEventTarget(xhr), 'error', () => { end(); controller.error(createNetworkError()); }); const removeAbortEventListener = createEventListener(toTypedEventTarget(xhr), 'abort', () => { end(); controller.error(createAbortError()); }); let removeProgressEventListener; if (isStreamableResponseType) { removeProgressEventListener = createEventListener(toTypedEventTarget(xhr), 'progress', progress); } }, cancel() { if (end !== void 0) { end(); } } }); } export function parseRawHeaders(headers) { return headers.split(/\r?\n/g).map(header => { const parts = header.split(': '); const key = parts.shift(); const value = parts.join(': '); return [key.trim(), value.trim()]; }).filter(([key]) => key !== ''); } export function XHRResponseToResponseInit(xhr) { return { headers: new Headers(parseRawHeaders(xhr.getAllResponseHeaders())), status: xhr.status, statusText: xhr.statusText, url: xhr.responseURL }; } export function XHRResponseToResponse(xhr, responseType = xhr.responseType) { const init = XHRResponseToResponseInit(xhr); switch (responseType) { case '': case 'text': return new XHRResponse(xhr.response, init); case 'arraybuffer': return new XHRResponse(xhr.response, init); case 'blob': return new XHRResponse(xhr.response, init); case 'document': case 'json': case 'binary-string': return new XHRResponse(XHRResponseToBlob(xhr, responseType), init); default: throw new TypeError(`Unsupported response type '${responseType}'`); } } export function XHRResponseToResponseUsingReadableStream(xhr, responseType) { return new XHRResponse(XHRResponseToReadableStream(xhr, responseType), XHRResponseToResponseInit(xhr)); }