summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-16 00:30:25 +0100
committerFlorian Dold <florian@dold.me>2024-02-16 00:30:25 +0100
commitc909d6fc0657002a2e5d117e98b9685f7a04a9d4 (patch)
treed03d65354a12c5946025b04c939b04b49328b425
parent490ef893e04d0515c3093a5a3e1cf14e707f3ea6 (diff)
downloadwallet-core-c909d6fc0657002a2e5d117e98b9685f7a04a9d4.tar.gz
wallet-core-c909d6fc0657002a2e5d117e98b9685f7a04a9d4.tar.bz2
wallet-core-c909d6fc0657002a2e5d117e98b9685f7a04a9d4.zip
taler-util: cancellation and timeouts on qjs
-rw-r--r--packages/taler-harness/src/harness/harness.ts2
-rw-r--r--packages/taler-util/src/http-impl.qtart.ts80
-rw-r--r--packages/taler-util/src/index.ts1
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts12
-rw-r--r--packages/taler-wallet-core/src/index.ts1
-rw-r--r--packages/taler-wallet-core/src/internal-wallet-state.ts1
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/pay-merchant.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/testing.ts8
-rw-r--r--packages/taler-wallet-core/src/remote.ts3
-rw-r--r--packages/taler-wallet-core/src/shepherd.ts2
-rw-r--r--packages/taler-wallet-core/src/util/promiseUtils.ts112
-rw-r--r--packages/taler-wallet-core/src/util/query.ts3
-rw-r--r--packages/taler-wallet-core/src/wallet.ts8
-rw-r--r--packages/taler-wallet-embedded/src/wallet-qjs.ts3
15 files changed, 100 insertions, 140 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
index 975d73cf8..410462af2 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -44,6 +44,7 @@ import {
encodeCrock,
hash,
j2s,
+ openPromise,
parsePaytoUri,
stringToBytes,
} from "@gnu-taler/taler-util";
@@ -57,7 +58,6 @@ import {
WalletCoreRequestType,
WalletCoreResponseType,
WalletOperations,
- openPromise,
} from "@gnu-taler/taler-wallet-core";
import {
RemoteWallet,
diff --git a/packages/taler-util/src/http-impl.qtart.ts b/packages/taler-util/src/http-impl.qtart.ts
index a37029d6e..0be9f2c23 100644
--- a/packages/taler-util/src/http-impl.qtart.ts
+++ b/packages/taler-util/src/http-impl.qtart.ts
@@ -19,9 +19,9 @@
/**
* Imports.
*/
-import { Logger } from "@gnu-taler/taler-util";
+import { Logger, openPromise } from "@gnu-taler/taler-util";
import { TalerError } from "./errors.js";
-import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js";
+import { HttpLibArgs, encodeBody, getDefaultHeaders } from "./http-common.js";
import {
Headers,
HttpRequestLibrary,
@@ -29,12 +29,26 @@ import {
HttpResponse,
} from "./http.js";
import { RequestThrottler, TalerErrorCode, URL } from "./index.js";
-import { qjsOs } from "./qtart.js";
+import { QjsHttpResp, qjsOs } from "./qtart.js";
const logger = new Logger("http-impl.qtart.ts");
const textDecoder = new TextDecoder();
+export class RequestTimeoutError extends Error {
+ public constructor() {
+ super("Request timed out");
+ Object.setPrototypeOf(this, RequestTimeoutError.prototype);
+ }
+}
+
+export class RequestCancelledError extends Error {
+ public constructor() {
+ super("Request cancelled");
+ Object.setPrototypeOf(this, RequestCancelledError.prototype);
+ }
+}
+
/**
* Implementation of the HTTP request library interface for node.
*/
@@ -92,12 +106,70 @@ export class HttpLibImpl implements HttpRequestLibrary {
if (method === "POST") {
data = encodeBody(opt?.body);
}
- const res = await qjsOs.fetchHttp(url, {
+
+ const cancelPromCap = openPromise<QjsHttpResp>();
+
+ // Just like WHATWG fetch(), the qjs http client doesn't
+ // really support cancellation, so cancellation here just
+ // means that the result is ignored!
+ const fetchProm = qjsOs.fetchHttp(url, {
method,
data,
headers: headersList,
});
+ let timeoutHandle: any = undefined;
+ let cancelCancelledHandler: (() => void) | undefined = undefined;
+
+ if (opt?.timeout && opt.timeout.d_ms !== "forever") {
+ timeoutHandle = setTimeout(() => {
+ cancelPromCap.reject(new RequestTimeoutError());
+ }, opt.timeout.d_ms);
+ }
+
+ if (opt?.cancellationToken) {
+ cancelCancelledHandler = opt.cancellationToken.onCancelled(() => {
+ cancelPromCap.reject(new RequestCancelledError());
+ });
+ }
+
+ let res: QjsHttpResp;
+ try {
+ res = await Promise.race([fetchProm, cancelPromCap.promise]);
+ } catch (e) {
+ if (e instanceof RequestCancelledError) {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
+ {
+ requestUrl: url,
+ requestMethod: method,
+ httpStatusCode: 0,
+ },
+ `Request cancelled`,
+ );
+ }
+ if (e instanceof RequestTimeoutError) {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
+ {
+ requestUrl: url,
+ requestMethod: method,
+ httpStatusCode: 0,
+ },
+ `Request timed out`,
+ );
+ }
+ throw e;
+ }
+
+ if (timeoutHandle != null) {
+ clearTimeout(timeoutHandle);
+ }
+
+ if (cancelCancelledHandler != null) {
+ cancelCancelledHandler();
+ }
+
const headers: Headers = new Headers();
if (res.headers) {
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 2045a4717..edc9c4ff2 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -44,6 +44,7 @@ export {
export * from "./notifications.js";
export * from "./operation.js";
export * from "./payto.js";
+export * from "./promises.js";
export * from "./rfc3548.js";
export * from "./taler-crypto.js";
export * from "./taler-types.js";
diff --git a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
index 192e9cda1..83897f331 100644
--- a/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/crypto-dispatcher.ts
@@ -23,10 +23,14 @@
/**
* Imports.
*/
-import { j2s, Logger, TalerErrorCode } from "@gnu-taler/taler-util";
-import { TalerError } from "@gnu-taler/taler-util";
-import { openPromise } from "../../util/promiseUtils.js";
-import { timer, performanceNow, TimerHandle } from "../../util/timer.js";
+import {
+ j2s,
+ Logger,
+ openPromise,
+ TalerError,
+ TalerErrorCode,
+} from "@gnu-taler/taler-util";
+import { performanceNow, timer, TimerHandle } from "../../util/timer.js";
import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js";
diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts
index 643d65620..0eca64d1f 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -19,7 +19,6 @@
*/
// Util functionality
-export * from "./util/promiseUtils.js";
export * from "./util/query.js";
export * from "./versions.js";
diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts
index 4379f20b5..13578adda 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -39,7 +39,6 @@ import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { WalletStoresV1 } from "./db.js";
import { TaskScheduler } from "./shepherd.js";
-import { AsyncCondition } from "./util/promiseUtils.js";
import {
DbAccess,
GetReadOnlyAccess,
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 3f8126dba..460b47e73 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -27,6 +27,7 @@ import {
AbsoluteTime,
AgeRestriction,
Amounts,
+ AsyncFlag,
CancellationToken,
CoinRefreshRequest,
CoinStatus,
@@ -95,7 +96,6 @@ import {
WalletStoresV1,
} from "../db.js";
import {
- AsyncFlag,
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
PendingTaskType,
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index e00432bd0..fc34feb30 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -31,6 +31,7 @@ import {
AmountJson,
Amounts,
AmountString,
+ AsyncFlag,
codecForAbortResponse,
codecForMerchantContractTerms,
codecForMerchantOrderRefundPickupResponse,
@@ -103,7 +104,6 @@ import {
WalletStoresV1,
} from "../db.js";
import {
- AsyncFlag,
getCandidateWithdrawalDenomsTx,
PendingTaskType,
RefundGroupRecord,
diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts
index 5902e8362..17863450c 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -38,6 +38,8 @@ import {
j2s,
Logger,
NotificationType,
+ OpenedPromise,
+ openPromise,
parsePaytoUri,
PreparePayResultType,
TalerCorebankApiClient,
@@ -54,11 +56,7 @@ import {
HttpRequestLibrary,
readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
-import {
- getRefreshesForTransaction,
- OpenedPromise,
- openPromise,
-} from "../index.js";
+import { getRefreshesForTransaction } from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkLogicInvariant } from "../util/invariants.js";
import { getBalances } from "./balance.js";
diff --git a/packages/taler-wallet-core/src/remote.ts b/packages/taler-wallet-core/src/remote.ts
index 164f7cfe9..1ee0e1993 100644
--- a/packages/taler-wallet-core/src/remote.ts
+++ b/packages/taler-wallet-core/src/remote.ts
@@ -19,11 +19,12 @@ import {
CoreApiResponse,
j2s,
Logger,
+ OpenedPromise,
+ openPromise,
TalerError,
WalletNotification,
} from "@gnu-taler/taler-util";
import { connectRpc, JsonMessage } from "@gnu-taler/taler-util/twrpc";
-import { OpenedPromise, openPromise } from "./index.js";
import { WalletCoreApiClient } from "./wallet-api-types.js";
const logger = new Logger("remote.ts");
diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts
index d1648acc7..4aea2d15d 100644
--- a/packages/taler-wallet-core/src/shepherd.ts
+++ b/packages/taler-wallet-core/src/shepherd.ts
@@ -20,6 +20,7 @@
import { GlobalIDB } from "@gnu-taler/idb-bridge";
import {
AbsoluteTime,
+ AsyncCondition,
CancellationToken,
Duration,
Logger,
@@ -66,7 +67,6 @@ import { processRefreshGroup } from "./operations/refresh.js";
import { constructTransactionIdentifier } from "./operations/transactions.js";
import { processWithdrawalGroup } from "./operations/withdraw.js";
import { PendingTaskType, TaskId } from "./pending-types.js";
-import { AsyncCondition } from "./util/promiseUtils.js";
const logger = new Logger("shepherd.ts");
diff --git a/packages/taler-wallet-core/src/util/promiseUtils.ts b/packages/taler-wallet-core/src/util/promiseUtils.ts
deleted file mode 100644
index bc1e40260..000000000
--- a/packages/taler-wallet-core/src/util/promiseUtils.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- 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.
-
- 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
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * An opened promise.
- *
- * @see {@link openPromise}
- */
-export interface OpenedPromise<T> {
- promise: Promise<T>;
- resolve: (val: T) => void;
- reject: (err: any) => void;
- lastError?: any;
-}
-
-/**
- * Get an unresolved promise together with its extracted resolve / reject
- * function.
- *
- * Recent ECMAScript proposals also call this a promise capability.
- */
-export function openPromise<T>(): OpenedPromise<T> {
- let resolve: ((x?: any) => void) | null = null;
- let promiseReject: ((reason?: any) => void) | null = null;
- const promise = new Promise<T>((res, rej) => {
- resolve = res;
- promiseReject = rej;
- });
- if (!(resolve && promiseReject)) {
- // Never happens, unless JS implementation is broken
- throw Error("JS implementation is broken");
- }
- const result: OpenedPromise<T> = { resolve, reject: promiseReject, promise };
- function saveLastError(reason?: any) {
- result.lastError = reason;
- promiseReject!(reason);
- }
- result.reject = saveLastError;
- return result;
-}
-
-export class AsyncCondition {
- private promCap?: OpenedPromise<void> = undefined;
- constructor() {}
-
- wait(): Promise<void> {
- if (!this.promCap) {
- this.promCap = openPromise<void>();
- }
- return this.promCap.promise;
- }
-
- trigger(): void {
- if (this.promCap) {
- this.promCap.resolve();
- }
- this.promCap = undefined;
- }
-}
-
-/**
- * Flag that can be raised to notify asynchronous waiters.
- *
- * You can think of it as a promise that can
- * be un-resolved.
- */
-export class AsyncFlag {
- private promCap?: OpenedPromise<void> = undefined;
- private internalFlagRaised: boolean = false;
-
- constructor() {}
-
- /**
- * Wait until the flag is raised.
- *
- * Reset if before returning.
- */
- wait(): Promise<void> {
- if (this.internalFlagRaised) {
- return Promise.resolve();
- }
- if (!this.promCap) {
- this.promCap = openPromise<void>();
- }
- return this.promCap.promise;
- }
-
- raise(): void {
- this.internalFlagRaised = true;
- if (this.promCap) {
- this.promCap.resolve();
- }
- }
-
- reset(): void {
- this.internalFlagRaised = false;
- this.promCap = undefined;
- }
-}
diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts
index 5fba61f11..19fa0dbfd 100644
--- a/packages/taler-wallet-core/src/util/query.ts
+++ b/packages/taler-wallet-core/src/util/query.ts
@@ -34,8 +34,7 @@ import {
IDBValidKey,
IDBVersionChangeEvent,
} from "@gnu-taler/idb-bridge";
-import { Codec, Logger } from "@gnu-taler/taler-util";
-import { openPromise } from "./promiseUtils.js";
+import { Codec, Logger, openPromise } from "@gnu-taler/taler-util";
const logger = new Logger("query.ts");
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 0246597be..cfe171bd0 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -26,6 +26,7 @@ import { IDBFactory } from "@gnu-taler/idb-bridge";
import {
AmountString,
Amounts,
+ AsyncCondition,
CoinDumpJson,
CoinStatus,
CoreApiResponse,
@@ -40,6 +41,7 @@ import {
ListGlobalCurrencyAuditorsResponse,
ListGlobalCurrencyExchangesResponse,
Logger,
+ OpenedPromise,
PrepareWithdrawExchangeRequest,
PrepareWithdrawExchangeResponse,
RecoverStoredBackupRequest,
@@ -122,6 +124,7 @@ import {
codecForWithdrawTestBalance,
getErrorDetailFromException,
j2s,
+ openPromise,
parsePaytoUri,
parseTalerUri,
sampleWalletCoreTransactions,
@@ -266,11 +269,6 @@ import {
} from "./util/instructedAmountConversion.js";
import { checkDbInvariant } from "./util/invariants.js";
import {
- AsyncCondition,
- OpenedPromise,
- openPromise,
-} from "./util/promiseUtils.js";
-import {
DbAccess,
GetReadOnlyAccess,
GetReadWriteAccess,
diff --git a/packages/taler-wallet-embedded/src/wallet-qjs.ts b/packages/taler-wallet-embedded/src/wallet-qjs.ts
index 0296dfeb6..0fbcd7583 100644
--- a/packages/taler-wallet-embedded/src/wallet-qjs.ts
+++ b/packages/taler-wallet-embedded/src/wallet-qjs.ts
@@ -39,6 +39,7 @@ import {
enableNativeLogging,
getErrorDetailFromException,
j2s,
+ openPromise,
setGlobalLogLevelFromString,
} from "@gnu-taler/taler-util";
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
@@ -48,7 +49,6 @@ import {
Wallet,
WalletApiOperation,
createNativeWalletHost2,
- openPromise,
performanceNow,
} from "@gnu-taler/taler-wallet-core";
@@ -277,6 +277,7 @@ export async function testWithGv() {
corebankApiBaseUrl: "https://bank.demo.taler.net/",
exchangeBaseUrl: "https://exchange.demo.taler.net/",
merchantBaseUrl: "https://backend.demo.taler.net/",
+ merchantAuthToken: "secret-token:sandbox",
});
await w.wallet.runTaskLoop({
stopWhenDone: true,