/* This file is part of GNU Taler (C) 2019 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 SPDX-License-Identifier: AGPL3.0-or-later */ /** * Imports. */ import { Logger } from "@gnu-taler/taler-util"; import { TalerError } from "./errors.js"; import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js"; import { Headers, HttpRequestLibrary, HttpRequestOptions, HttpResponse, } from "./http.js"; import { RequestThrottler, TalerErrorCode, URL } from "./index.js"; import { qjsOs } from "./qtart.js"; const logger = new Logger("http-impl.qtart.ts"); const textDecoder = new TextDecoder(); /** * Implementation of the HTTP request library interface for node. */ export class HttpLibImpl implements HttpRequestLibrary { private throttle = new RequestThrottler(); private throttlingEnabled = true; private requireTls = false; constructor(args?: HttpLibArgs) { this.throttlingEnabled = args?.enableThrottling ?? false; this.requireTls = args?.requireTls ?? false; } /** * Set whether requests should be throttled. */ setThrottling(enabled: boolean): void { this.throttlingEnabled = enabled; } async fetch(url: string, opt?: HttpRequestOptions): Promise { const method = (opt?.method ?? "GET").toUpperCase(); logger.trace(`Requesting ${method} ${url}`); const parsedUrl = new URL(url); if (this.throttlingEnabled && this.throttle.applyThrottle(url)) { throw TalerError.fromDetail( TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, { requestMethod: method, requestUrl: url, throttleStats: this.throttle.getThrottleStats(url), }, `request to origin ${parsedUrl.origin} was throttled`, ); } if (this.requireTls && parsedUrl.protocol !== "https:") { throw TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, { requestMethod: method, requestUrl: url, }, `request to ${parsedUrl.origin} is not possible with protocol ${parsedUrl.protocol}`, ); } let data: ArrayBuffer | undefined = undefined; const requestHeadersMap = { ...getDefaultHeaders(method), ...opt?.headers }; let headersList: string[] = []; for (let headerName of Object.keys(requestHeadersMap)) { headersList.push(`${headerName}: ${requestHeadersMap[headerName]}`); } if (method === "POST") { data = encodeBody(opt?.body); } const res = await qjsOs.fetchHttp(url, { method, data, headers: headersList, }); return { requestMethod: method, // FIXME: We don't return headers! headers: new Headers(), async bytes() { return res.data; }, json() { const text = textDecoder.decode(res.data); return JSON.parse(text); }, async text() { const text = textDecoder.decode(res.data); return text; }, requestUrl: url, status: res.status, }; } async get(url: string, opt?: HttpRequestOptions): Promise { return this.fetch(url, { method: "GET", ...opt, }); } async postJson( url: string, body: any, opt?: HttpRequestOptions, ): Promise { return this.fetch(url, { method: "POST", body, ...opt, }); } }