@lifaon/rx-js-light
Version:
Blazing fast Observables
236 lines (235 loc) • 8.38 kB
JavaScript
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));
}