/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ /** * Imports. */ import { RequestThrottler, TalerErrorCode, TalerError, } from "@gnu-taler/taler-util"; import { Headers, HttpRequestLibrary, HttpRequestOptions, HttpResponse, } from "@gnu-taler/taler-util/http"; /** * An implementation of the [[HttpRequestLibrary]] using the * browser's XMLHttpRequest. */ export class ServiceWorkerHttpLib implements HttpRequestLibrary { private throttle = new RequestThrottler(); private throttlingEnabled = true; async fetch( requestUrl: string, options?: HttpRequestOptions, ): Promise { const requestMethod = options?.method ?? "GET"; const requestBody = options?.body; const requestHeader = options?.headers; const requestTimeout = options?.timeout ?? { d_ms: 2 * 1000 }; if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { const parsedUrl = new URL(requestUrl); throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, { requestMethod, requestUrl, throttleStats: this.throttle.getThrottleStats(requestUrl), }, `request to origin ${parsedUrl.origin} was throttled`, ); } let myBody: BodyInit | undefined = undefined; if (requestBody != null) { if (typeof requestBody === "string") { myBody = requestBody; } else if (requestBody instanceof ArrayBuffer) { myBody = requestBody; } else if (ArrayBuffer.isView(requestBody)) { myBody = requestBody; } else if (typeof requestBody === "object") { myBody = JSON.stringify(requestBody); } else { throw Error("unsupported request body type"); } } const controller = new AbortController(); let timeoutId: any | undefined; if (requestTimeout.d_ms !== "forever") { timeoutId = setTimeout(() => { controller.abort(TalerErrorCode.WALLET_HTTP_REQUEST_GENERIC_TIMEOUT); }, requestTimeout.d_ms); } try { const response = await fetch(requestUrl, { headers: requestHeader, body: myBody, method: requestMethod, signal: controller.signal, }); if (timeoutId) { clearTimeout(timeoutId); } const headerMap = new Headers(); response.headers.forEach((value, key) => { headerMap.set(key, value); }); return { headers: headerMap, status: response.status, requestMethod, requestUrl, json: makeJsonHandler(response, requestUrl, requestMethod), text: makeTextHandler(response, requestUrl, requestMethod), bytes: async () => (await response.blob()).arrayBuffer(), }; } catch (e) { if (controller.signal) { throw TalerError.fromDetail( controller.signal.reason, {}, `request to ${requestUrl} timed out`, ); } throw e; } } get(url: string, opt?: HttpRequestOptions): Promise { return this.fetch(url, { method: "GET", ...opt, }); } postJson( url: string, body: any, opt?: HttpRequestOptions, ): Promise { return this.fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), ...opt, }); } stop(): void { // Nothing to do } } function makeTextHandler( response: Response, requestUrl: string, requestMethod: string, ) { return async function getJsonFromResponse(): Promise { let respText; try { respText = await response.text(); } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, requestMethod, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } return respText; }; } function makeJsonHandler( response: Response, requestUrl: string, requestMethod: string, ) { return async function getJsonFromResponse(): Promise { let responseJson; try { responseJson = await response.json(); } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, requestMethod, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } if (responseJson === null || typeof responseJson !== "object") { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, requestMethod, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } return responseJson; }; }