diff options
-rw-r--r-- | packages/taler-util/src/RequestThrottler.ts (renamed from packages/taler-wallet-core/src/util/RequestThrottler.ts) | 13 | ||||
-rw-r--r-- | packages/taler-util/src/index.ts | 1 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/headless/NodeHttpLib.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/tsconfig.json | 2 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/browserHttpLib.ts | 44 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts | 51 |
6 files changed, 73 insertions, 40 deletions
diff --git a/packages/taler-wallet-core/src/util/RequestThrottler.ts b/packages/taler-util/src/RequestThrottler.ts index d79afe47a..7689b4215 100644 --- a/packages/taler-wallet-core/src/util/RequestThrottler.ts +++ b/packages/taler-util/src/RequestThrottler.ts @@ -14,20 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { Logger } from "./logging.js"; +import { getTimestampNow, timestampCmp, timestampDifference } from "./time.js"; + /** * Implementation of token bucket throttling. */ -/** - * Imports. - */ -import { - getTimestampNow, - timestampDifference, - timestampCmp, - Logger, - URL, -} from "@gnu-taler/taler-util"; const logger = new Logger("RequestThrottler.ts"); diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index 0141be13b..573b4a5c7 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -30,3 +30,4 @@ export { secretbox_open, crypto_sign_keyPair_fromSeed, } from "./nacl-fast.js"; +export { RequestThrottler } from "./RequestThrottler.js"; diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts index 5a90994b1..2a8c9e36c 100644 --- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts +++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts @@ -25,7 +25,7 @@ import { HttpRequestOptions, HttpResponse, } from "../util/http.js"; -import { RequestThrottler } from "../util/RequestThrottler.js"; +import { RequestThrottler } from "@gnu-taler/taler-util"; import Axios, { AxiosResponse } from "axios"; import { OperationFailedError, makeErrorDetails } from "../errors.js"; import { Logger, bytesToString } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-core/tsconfig.json b/packages/taler-wallet-core/tsconfig.json index 3da332364..c3366373e 100644 --- a/packages/taler-wallet-core/tsconfig.json +++ b/packages/taler-wallet-core/tsconfig.json @@ -21,7 +21,7 @@ "esModuleInterop": true, "importHelpers": true, "rootDir": "./src", - "typeRoots": ["./node_modules/@types"], + "typeRoots": ["./node_modules/@types"] }, "references": [ { diff --git a/packages/taler-wallet-webextension/src/browserHttpLib.ts b/packages/taler-wallet-webextension/src/browserHttpLib.ts index 63fd456f4..8877edfc3 100644 --- a/packages/taler-wallet-webextension/src/browserHttpLib.ts +++ b/packages/taler-wallet-webextension/src/browserHttpLib.ts @@ -24,7 +24,11 @@ import { HttpResponse, Headers, } from "@gnu-taler/taler-wallet-core"; -import { Logger, TalerErrorCode } from "@gnu-taler/taler-util"; +import { + Logger, + RequestThrottler, + TalerErrorCode, +} from "@gnu-taler/taler-util"; const logger = new Logger("browserHttpLib"); @@ -33,12 +37,32 @@ const logger = new Logger("browserHttpLib"); * browser's XMLHttpRequest. */ export class BrowserHttpLib implements HttpRequestLibrary { - fetch(url: string, options?: HttpRequestOptions): Promise<HttpResponse> { - const method = options?.method ?? "GET"; + private throttle = new RequestThrottler(); + private throttlingEnabled = true; + + fetch( + requestUrl: string, + options?: HttpRequestOptions, + ): Promise<HttpResponse> { + const requestMethod = options?.method ?? "GET"; let requestBody = options?.body; + + if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) { + const parsedUrl = new URL(requestUrl); + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + `request to origin ${parsedUrl.origin} was throttled`, + { + requestMethod, + requestUrl, + throttleStats: this.throttle.getThrottleStats(requestUrl), + }, + ); + } + return new Promise<HttpResponse>((resolve, reject) => { const myRequest = new XMLHttpRequest(); - myRequest.open(method, url); + myRequest.open(requestMethod, requestUrl); if (options?.headers) { for (const headerName in options.headers) { myRequest.setRequestHeader(headerName, options.headers[headerName]); @@ -58,7 +82,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_NETWORK_ERROR, "Could not make request", { - requestUrl: url, + requestUrl: requestUrl, }, ), ); @@ -71,7 +95,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_NETWORK_ERROR, "HTTP request failed (status 0, maybe URI scheme was wrong?)", { - requestUrl: url, + requestUrl: requestUrl, }, ); reject(exc); @@ -92,7 +116,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, "Invalid JSON from HTTP response", { - requestUrl: url, + requestUrl: requestUrl, httpStatusCode: myRequest.status, }, ); @@ -102,7 +126,7 @@ export class BrowserHttpLib implements HttpRequestLibrary { TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, "Invalid JSON from HTTP response", { - requestUrl: url, + requestUrl: requestUrl, httpStatusCode: myRequest.status, }, ); @@ -126,10 +150,10 @@ export class BrowserHttpLib implements HttpRequestLibrary { headerMap.set(headerName, value); }); const resp: HttpResponse = { - requestUrl: url, + requestUrl: requestUrl, status: myRequest.status, headers: headerMap, - requestMethod: method, + requestMethod: requestMethod, json: makeJson, text: makeText, bytes: async () => myRequest.response, diff --git a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts index a66d4e097..6f2585c1e 100644 --- a/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts +++ b/packages/taler-wallet-webextension/src/serviceWorkerHttpLib.ts @@ -17,37 +17,55 @@ /** * Imports. */ -import { Logger, TalerErrorCode } from "@gnu-taler/taler-util"; +import { RequestThrottler, TalerErrorCode } from "@gnu-taler/taler-util"; import { - Headers, HttpRequestLibrary, + Headers, + HttpRequestLibrary, HttpRequestOptions, HttpResponse, - OperationFailedError + OperationFailedError, } from "@gnu-taler/taler-wallet-core"; -const logger = new Logger("browserHttpLib"); - /** * An implementation of the [[HttpRequestLibrary]] using the * browser's XMLHttpRequest. */ export class ServiceWorkerHttpLib implements HttpRequestLibrary { - async fetch(requestUrl: string, options?: HttpRequestOptions): Promise<HttpResponse> { + private throttle = new RequestThrottler(); + private throttlingEnabled = true; + + async fetch( + requestUrl: string, + options?: HttpRequestOptions, + ): Promise<HttpResponse> { 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 OperationFailedError.fromCode( + TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED, + `request to origin ${parsedUrl.origin} was throttled`, + { + requestMethod, + requestUrl, + throttleStats: this.throttle.getThrottleStats(requestUrl), + }, + ); + } + 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, @@ -56,11 +74,9 @@ export class ServiceWorkerHttpLib implements HttpRequestLibrary { json: makeJsonHandler(response, requestUrl), text: makeTextHandler(response, requestUrl), bytes: async () => (await response.blob()).arrayBuffer(), - } - + }; } - get(url: string, opt?: HttpRequestOptions): Promise<HttpResponse> { return this.fetch(url, { method: "GET", @@ -89,7 +105,7 @@ function makeTextHandler(response: Response, requestUrl: string) { return async function getJsonFromResponse(): Promise<any> { let respText; try { - respText = await response.text() + respText = await response.text(); } catch (e) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, @@ -100,15 +116,15 @@ function makeTextHandler(response: Response, requestUrl: string) { }, ); } - return respText - } + return respText; + }; } function makeJsonHandler(response: Response, requestUrl: string) { return async function getJsonFromResponse(): Promise<any> { let responseJson; try { - responseJson = await response.json() + responseJson = await response.json(); } catch (e) { throw OperationFailedError.fromCode( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, @@ -129,7 +145,6 @@ function makeJsonHandler(response: Response, requestUrl: string) { }, ); } - return responseJson - } + return responseJson; + }; } - |