/* This file is part of GNU Taler (C) 2020 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 } from "@gnu-taler/taler-util"; import { Headers, HttpRequestLibrary, HttpRequestOptions, HttpResponse, TalerError, } from "@gnu-taler/taler-wallet-core"; /** * 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; 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`, ); } const response = await fetch(requestUrl, { headers: requestHeader, body: requestBody, method: requestMethod, // timeout: options?.timeout }); 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), text: makeTextHandler(response, requestUrl), bytes: async () => (await response.blob()).arrayBuffer(), }; } get(url: string, opt?: HttpRequestOptions): Promise { return this.fetch(url, { method: "GET", ...opt, }); } // FIXME: "Content-Type: application/json" goes here, // after Sebastian suggestion. 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) { return async function getJsonFromResponse(): Promise { let respText; try { respText = await response.text(); } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } return respText; }; } function makeJsonHandler(response: Response, requestUrl: string) { return async function getJsonFromResponse(): Promise { let responseJson; try { responseJson = await response.json(); } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } if (responseJson === null || typeof responseJson !== "object") { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, httpStatusCode: response.status, }, "Invalid JSON from HTTP response", ); } return responseJson; }; }