UNPKG

@opra/client

Version:
234 lines (233 loc) 10.2 kB
"use strict"; 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;