@opra/client
Version:
Opra Client package
234 lines (233 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FetchBackend = void 0;
const tslib_1 = require("tslib");
// /// <reference lib="dom" />
const type_is_1 = tslib_1.__importDefault(require("@browsery/type-is"));
const objects_1 = require("@jsopen/objects");
const common_1 = require("@opra/common");
const rxjs_1 = require("rxjs");
const isReadableStreamLike_1 = require("rxjs/internal/util/isReadableStreamLike");
const http_backend_js_1 = require("./http-backend.js");
const http_response_js_1 = require("./http-response.js");
const http_event_js_1 = require("./interfaces/http-event.js");
/**
*
* @class FetchBackend
*/
class FetchBackend extends http_backend_js_1.HttpBackend {
constructor(serviceUrl, options) {
super(serviceUrl, options);
// Create deduped interceptor array
this.interceptors = Array.from(new Set([...(options?.interceptors || [])]));
this.defaults = {
...options?.defaults,
headers: options?.defaults?.headers instanceof Headers
? options?.defaults?.headers
: new Headers(options?.defaults?.headers),
params: options?.defaults?.params instanceof URLSearchParams
? options?.defaults?.params
: new URLSearchParams(options?.defaults?.params),
};
}
handle(init) {
return new rxjs_1.Observable(subscriber => {
(async () => {
let request = this.prepareRequest(init);
if (request.body && init.reportProgress) {
const stream = request.body;
const contentLength = request.headers.get('content-length') || '0';
const total = parseInt(contentLength, 10) || 0;
let loaded = 0;
const progressTrackingStream = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk);
loaded += chunk.byteLength;
// Emit 'UploadProgress' event
subscriber.next({
type: http_event_js_1.HttpEventType.UploadProgress,
request,
total,
loaded,
});
},
});
request = new Request(request.url, {
cache: request.cache,
credentials: request.credentials,
headers: request.headers,
integrity: request.integrity,
keepalive: request.keepalive,
method: request.method,
mode: request.mode,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
signal: request.signal,
body: stream.pipeThrough(progressTrackingStream),
window: init.window,
...{
// undici library requires
duplex: 'half',
},
});
}
// Send request
const fetchPromise = this.send(request);
// Emit 'Sent' event
subscriber.next({
request,
type: http_event_js_1.HttpEventType.Sent,
});
const fetchResponse = await fetchPromise;
// Emit 'ResponseHeader' event
const headersResponse = this.createResponse({
url: fetchResponse.url,
headers: fetchResponse.headers,
status: fetchResponse.status,
statusText: fetchResponse.statusText,
hasBody: !!fetchResponse.body,
});
subscriber.next({
request,
type: http_event_js_1.HttpEventType.ResponseHeader,
response: headersResponse,
});
// Parse body
let body;
if (fetchResponse.body) {
if (init.reportProgress) {
const fetchBody = fetchResponse.body;
const contentLength = fetchResponse.headers.get('content-length') || '0';
const total = parseInt(contentLength, 10) || 0;
let loaded = 0;
const res = new Response(new ReadableStream({
async start(controller) {
const reader = fetchBody.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done)
break;
loaded += value.byteLength;
controller.enqueue(value);
// Emit 'DownloadProgress' event
subscriber.next({
type: http_event_js_1.HttpEventType.DownloadProgress,
request,
total,
loaded,
});
}
controller.close();
},
}));
body = await this.parseBody(res);
}
else {
body = await this.parseBody(fetchResponse);
}
}
const response = this.createResponse({
url: fetchResponse.url,
headers: fetchResponse.headers,
status: fetchResponse.status,
statusText: fetchResponse.statusText,
body,
});
// Emit 'Response' event
subscriber.next({
request,
type: http_event_js_1.HttpEventType.Response,
response,
});
subscriber.complete();
})().catch(error => subscriber.error(error));
});
}
send(request) {
return fetch(request);
}
prepareRequest(init) {
const headers = init.headers || new Headers();
const requestInit = {
...init,
...(0, objects_1.omit)(this.defaults, ['headers', 'params']),
headers,
};
this.defaults.headers.forEach((val, key) => {
if (!headers.has(key))
headers.set(key, val);
});
const url = new URL(requestInit.url, this.serviceUrl);
if (this.defaults.params.size) {
this.defaults.params.forEach((val, key) => {
if (!url.searchParams.has(key))
url.searchParams.set(key, val);
});
requestInit.url = url.toString();
}
const body = requestInit.body;
if (body) {
let contentType = '';
if (typeof body === 'string' ||
typeof body === 'number' ||
typeof body === 'boolean') {
contentType = 'text/plain; charset="UTF-8"';
requestInit.body = new Blob([String(body)], { type: contentType });
headers.set('Content-Length', String(requestInit.body.size));
delete requestInit.duplex;
}
else if ((0, isReadableStreamLike_1.isReadableStreamLike)(body)) {
contentType = 'application/octet-stream';
requestInit.duplex = 'half'; // undici library requires "duplex" option to be set for streams
}
else if (Buffer.isBuffer(body)) {
contentType = 'application/octet-stream';
headers.set('Content-Length', String(body.length));
delete requestInit.duplex;
}
else if ((0, common_1.isBlob)(body)) {
contentType = body.type || 'application/octet-stream';
headers.set('Content-Length', String(body.size));
delete requestInit.duplex;
}
else if ((0, common_1.isFormData)(body)) {
delete requestInit.duplex;
}
else {
contentType = 'application/json;charset="UTF-8"';
requestInit.body = new Blob([JSON.stringify(body)], {
type: contentType,
});
headers.set('Content-Length', String(requestInit.body.size));
delete requestInit.duplex;
}
if (contentType && !headers.has('Content-Type'))
headers.set('Content-Type', contentType);
}
return new Request(url.toString(), requestInit);
}
createResponse(init) {
return new http_response_js_1.HttpResponse(init);
}
async parseBody(fetchResponse) {
let body;
const contentType = fetchResponse.headers.get('Content-Type') || '';
if (type_is_1.default.is(contentType, ['json', 'application/*+json'])) {
body = await fetchResponse.json();
if (typeof body === 'string')
body = JSON.parse(body);
}
else if (type_is_1.default.is(contentType, ['text']))
body = await fetchResponse.text();
else if (type_is_1.default.is(contentType, ['multipart']))
body = await fetchResponse.formData();
else {
const buf = await fetchResponse.arrayBuffer();
if (buf.byteLength)
body = buf;
}
return body;
}
}
exports.FetchBackend = FetchBackend;