gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: fixes #8083


From: gnunet
Subject: [taler-wallet-core] branch master updated: fixes #8083
Date: Mon, 15 Jan 2024 21:36:58 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 2e2cf4049 fixes #8083
2e2cf4049 is described below

commit 2e2cf4049a771c82fcc520686de3ace7603baa05
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Jan 15 17:34:19 2024 -0300

    fixes #8083
---
 .../test-withdrawal-notify-before-tx.ts            | 80 ++++++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |  2 +
 .../taler-util/src/http-client/bank-integration.ts |  8 +++
 packages/taler-util/src/http-impl.node.ts          |  2 +-
 packages/taler-util/src/http-impl.qtart.ts         |  2 +-
 packages/taler-util/src/notifications.ts           |  8 +++
 packages/taler-util/src/transactions-types.ts      | 10 +++
 packages/taler-util/src/wallet-types.ts            | 32 ++++++---
 .../src/operations/transactions.ts                 | 62 +++++++++++++++--
 .../taler-wallet-core/src/operations/withdraw.ts   | 75 ++++++++++++--------
 packages/taler-wallet-core/src/wallet-api-types.ts | 22 ++++--
 packages/taler-wallet-core/src/wallet.ts           | 16 +++--
 .../src/cta/Withdraw/index.ts                      | 10 ++-
 .../src/cta/Withdraw/state.ts                      | 49 +++++++++++--
 .../src/cta/Withdraw/stories.tsx                   | 19 ++++-
 .../src/cta/Withdraw/test.ts                       | 22 ++++++
 .../src/cta/Withdraw/views.tsx                     | 29 +++++++-
 .../taler-wallet-webextension/src/utils/index.ts   |  9 ++-
 .../taler-wallet-webextension/src/wxBackend.ts     | 12 ++--
 packages/web-util/src/utils/http-impl.browser.ts   | 19 ++++-
 packages/web-util/src/utils/http-impl.sw.ts        | 19 ++++-
 21 files changed, 435 insertions(+), 72 deletions(-)

diff --git 
a/packages/taler-harness/src/integrationtests/test-withdrawal-notify-before-tx.ts
 
b/packages/taler-harness/src/integrationtests/test-withdrawal-notify-before-tx.ts
new file mode 100644
index 000000000..fc36b8fc3
--- /dev/null
+++ 
b/packages/taler-harness/src/integrationtests/test-withdrawal-notify-before-tx.ts
@@ -0,0 +1,80 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import { NotificationType, TalerCorebankApiClient } from 
"@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
+
+/**
+ * Run test for basic, bank-integrated withdrawal.
+ */
+export async function runWithdrawalNotifyBeforeTxTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const { walletClient, bank, exchange } =
+    await createSimpleTestkudosEnvironmentV2(t);
+
+  // Create a withdrawal operation
+
+  const bankAccessApiClient = new TalerCorebankApiClient(
+    bank.corebankApiBaseUrl,
+  );
+  const user = await bankAccessApiClient.createRandomBankUser();
+  bankAccessApiClient.setAuth(user);
+  const wop = await bankAccessApiClient.createWithdrawalOperation(
+    user.username,
+    "TESTKUDOS:10",
+  );
+
+  // Hand it to the wallet
+  const r1 = await 
walletClient.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
+    talerWithdrawUri: wop.taler_withdraw_uri,
+    notifyChangeFromPendingTimeoutMs: 10000
+  });
+
+  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+  // Withdraw
+
+  // Abort it
+  // const api = new TalerCoreBankHttpClient(bank.corebankApiBaseUrl);
+  // const token = await 
api.getAuthenticationAPI(user.username).createAccessToken(user.password, {
+  //   scope: "readwrite",
+  // })
+  // t.assertTrue(token.type !== "fail")
+
+  // const confirm = await api.confirmWithdrawalById({
+  //   username: user.username,
+  //   token: token.body.access_token,
+  // }, wop.withdrawal_id)
+  // t.assertTrue(confirm.type !== "fail")
+
+  await walletClient.waitForNotificationCond((x) => {
+    return (
+      x.type === NotificationType.WithdrawalOperationTransition &&
+      x.operationId === r1.operationId &&
+      x.state === "confirmed"
+    );
+  });
+
+  await t.shutdown();
+}
+
+runWithdrawalNotifyBeforeTxTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 1d8353acf..6a8eb9504 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -97,6 +97,7 @@ import { runMultiExchangeTest } from 
"./test-multiexchange.js";
 import { runAgeRestrictionsDepositTest } from 
"./test-age-restrictions-deposit.js";
 import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
 import { runPaymentDeletedTest } from "./test-payment-deleted.js";
+import { runWithdrawalNotifyBeforeTxTest } from 
"./test-withdrawal-notify-before-tx.js";
 
 /**
  * Test runner.
@@ -172,6 +173,7 @@ const allTests: TestMainFunction[] = [
   runWalletDblessTest,
   runWallettestingTest,
   runWithdrawalAbortBankTest,
+  // runWithdrawalNotifyBeforeTxTest,
   runWithdrawalBankIntegratedTest,
   runWithdrawalFakebankTest,
   runWithdrawalFeesTest,
diff --git a/packages/taler-util/src/http-client/bank-integration.ts 
b/packages/taler-util/src/http-client/bank-integration.ts
index 8131b36b6..757f1f897 100644
--- a/packages/taler-util/src/http-client/bank-integration.ts
+++ b/packages/taler-util/src/http-client/bank-integration.ts
@@ -1,6 +1,7 @@
 import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from 
"../http-common.js";
 import { HttpStatusCode } from "../http-status-codes.js";
 import { createPlatformHttpLib } from "../http.js";
+import { LibtoolVersion } from "../libtool-version.js";
 import { FailCasesByMethod, ResultByMethod, opKnownFailure, opSuccess, 
opUnknownFailure } from "../operation.js";
 import { TalerErrorCode } from "../taler-error-codes.js";
 import { codecForTalerErrorDetail } from "../wallet-types.js";
@@ -19,6 +20,8 @@ export type TalerBankIntegrationErrorsByMethod<prop extends 
keyof TalerBankInteg
  * The API is used by the wallets.
  */
 export class TalerBankIntegrationHttpClient {
+  public readonly PROTOCOL_VERSION = "3:0:3";
+
   httpLib: HttpRequestLibrary;
 
   constructor(
@@ -28,6 +31,11 @@ export class TalerBankIntegrationHttpClient {
     this.httpLib = httpClient ?? createPlatformHttpLib();
   }
 
+  isCompatible(version: string): boolean {
+    const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version)
+    return compare?.compatible ?? false
+  }
+
   /**
    * https://docs.taler.net/core/api-bank-integration.html#get--config
    * 
diff --git a/packages/taler-util/src/http-impl.node.ts 
b/packages/taler-util/src/http-impl.node.ts
index 5aca6e99d..8ec823eca 100644
--- a/packages/taler-util/src/http-impl.node.ts
+++ b/packages/taler-util/src/http-impl.node.ts
@@ -70,7 +70,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
   private requireTls = false;
 
   constructor(args?: HttpLibArgs) {
-    this.throttlingEnabled = args?.enableThrottling ?? false;
+    this.throttlingEnabled = args?.enableThrottling ?? true;
     this.requireTls = args?.requireTls ?? false;
   }
 
diff --git a/packages/taler-util/src/http-impl.qtart.ts 
b/packages/taler-util/src/http-impl.qtart.ts
index d4ec26bd0..73ca417f7 100644
--- a/packages/taler-util/src/http-impl.qtart.ts
+++ b/packages/taler-util/src/http-impl.qtart.ts
@@ -44,7 +44,7 @@ export class HttpLibImpl implements HttpRequestLibrary {
   private requireTls = false;
 
   constructor(args?: HttpLibArgs) {
-    this.throttlingEnabled = args?.enableThrottling ?? false;
+    this.throttlingEnabled = args?.enableThrottling ?? true;
     this.requireTls = args?.requireTls ?? false;
   }
 
diff --git a/packages/taler-util/src/notifications.ts 
b/packages/taler-util/src/notifications.ts
index a5c971bdd..d84d3706d 100644
--- a/packages/taler-util/src/notifications.ts
+++ b/packages/taler-util/src/notifications.ts
@@ -22,6 +22,7 @@
 /**
  * Imports.
  */
+import { WithdrawalOperationStatus } from "./index.node.js";
 import { TransactionState } from "./transactions-types.js";
 import { ExchangeEntryState, TalerErrorDetail } from "./wallet-types.js";
 
@@ -31,6 +32,7 @@ export enum NotificationType {
   PendingOperationProcessed = "pending-operation-processed",
   TransactionStateTransition = "transaction-state-transition",
   ExchangeStateTransition = "exchange-state-transition",
+  WithdrawalOperationTransition = "withdrawal-state-transition",
 }
 
 export interface ErrorInfoSummary {
@@ -105,10 +107,16 @@ export interface PendingOperationProcessedNotification {
   id: string;
   taskResultType: string;
 }
+export interface WithdrawalOperationTransitionNotification {
+  type: NotificationType.WithdrawalOperationTransition;
+  operationId: string;
+  state: WithdrawalOperationStatus;
+}
 
 export type WalletNotification =
   | BalanceChangeNotification
   | BackupOperationErrorNotification
   | ExchangeStateTransitionNotification
   | PendingOperationProcessedNotification
+  | WithdrawalOperationTransitionNotification
   | TransactionStateTransitionNotification;
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index 17b56d13b..00802577a 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -708,6 +708,16 @@ export const codecForTransactionByIdRequest =
       .property("transactionId", codecForString())
       .build("TransactionByIdRequest");
 
+export interface WithdrawalTransactionByURIRequest {
+  talerWithdrawUri: string;
+}
+
+export const codecForWithdrawalTransactionByURIRequest =
+  (): Codec<WithdrawalTransactionByURIRequest> =>
+    buildCodecForObject<WithdrawalTransactionByURIRequest>()
+      .property("talerWithdrawUri", codecForString())
+      .build("WithdrawalTransactionByURIRequest");
+
 export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
   buildCodecForObject<TransactionsRequest>()
     .property("currency", codecOptional(codecForString()))
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 8ccc93c38..583d5dff5 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -46,7 +46,7 @@ import {
   codecOptional,
   renderContext,
 } from "./codec.js";
-import { CurrencySpecification } from "./index.js";
+import { CurrencySpecification, WithdrawalOperationStatus } from "./index.js";
 import { VersionMatchResult } from "./libtool-version.js";
 import { PaytoUri } from "./payto.js";
 import { AgeCommitmentProof } from "./taler-crypto.js";
@@ -71,10 +71,12 @@ import {
 } from "./taler-types.js";
 import {
   AbsoluteTime,
+  Duration,
   TalerPreciseTimestamp,
   TalerProtocolDuration,
   TalerProtocolTimestamp,
   codecForAbsoluteTime,
+  codecForDuration,
   codecForTimestamp,
 } from "./time.js";
 import {
@@ -575,11 +577,11 @@ export interface CoinDumpJson {
     withdrawal_reserve_pub: string | undefined;
     coin_status: CoinStatus;
     spend_allocation:
-      | {
-          id: string;
-          amount: AmountString;
-        }
-      | undefined;
+    | {
+      id: string;
+      amount: AmountString;
+    }
+    | undefined;
     /**
      * Information about the age restriction
      */
@@ -942,13 +944,14 @@ export interface PreparePayResultAlreadyConfirmed {
 }
 
 export interface BankWithdrawDetails {
-  selectionDone: boolean;
-  transferDone: boolean;
+  status: WithdrawalOperationStatus,
   amount: AmountJson;
   senderWire?: string;
   suggestedExchange?: string;
   confirmTransferUrl?: string;
   wireTypes: string[];
+  operationId: string,
+  apiBaseUrl: string,
 }
 
 export interface AcceptWithdrawalResponse {
@@ -1799,6 +1802,7 @@ export const codecForApplyRefundFromPurchaseIdRequest =
 export interface GetWithdrawalDetailsForUriRequest {
   talerWithdrawUri: string;
   restrictAge?: number;
+  notifyChangeFromPendingTimeoutMs?: number;
 }
 
 export const codecForGetWithdrawalDetailsForUri =
@@ -1806,6 +1810,7 @@ export const codecForGetWithdrawalDetailsForUri =
     buildCodecForObject<GetWithdrawalDetailsForUriRequest>()
       .property("talerWithdrawUri", codecForString())
       .property("restrictAge", codecOptional(codecForNumber()))
+      .property("notifyChangeFromPendingTimeoutMs", 
codecOptional(codecForNumber()))
       .build("GetWithdrawalDetailsForUriRequest");
 
 export interface ListKnownBankAccountsRequest {
@@ -2185,6 +2190,9 @@ export interface TxIdResponse {
 }
 
 export interface WithdrawUriInfoResponse {
+  operationId: string;
+  status: WithdrawalOperationStatus,
+  confirmTransferUrl?: string;
   amount: AmountString;
   defaultExchangeBaseUrl?: string;
   possibleExchanges: ExchangeListItem[];
@@ -2193,6 +2201,14 @@ export interface WithdrawUriInfoResponse {
 export const codecForWithdrawUriInfoResponse =
   (): Codec<WithdrawUriInfoResponse> =>
     buildCodecForObject<WithdrawUriInfoResponse>()
+      .property("operationId", codecForString())
+      .property("confirmTransferUrl", codecOptional(codecForString()))
+      .property("status", codecForEither(
+        codecForConstString("pending"),
+        codecForConstString("selected"),
+        codecForConstString("aborted"),
+        codecForConstString("confirmed"),
+      ))
       .property("amount", codecForAmountString())
       .property("defaultExchangeBaseUrl", codecOptional(codecForString()))
       .property("possibleExchanges", codecForList(codecForExchangeListItem()))
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 908aa540a..d93396ca5 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -41,7 +41,9 @@ import {
   TransactionsResponse,
   TransactionState,
   TransactionType,
+  TransactionWithdrawal,
   WalletContractData,
+  WithdrawalTransactionByURIRequest,
   WithdrawalType,
 } from "@gnu-taler/taler-util";
 import {
@@ -520,7 +522,7 @@ function buildTransactionForPeerPullCredit(
     const silentWithdrawalErrorForInvoice =
       wsrOrt?.lastError &&
       wsrOrt.lastError.code ===
-        TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
+      TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
       Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => {
         return (
           e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
@@ -550,10 +552,10 @@ function buildTransactionForPeerPullCredit(
       kycUrl: pullCredit.kycUrl,
       ...(wsrOrt?.lastError
         ? {
-            error: silentWithdrawalErrorForInvoice
-              ? undefined
-              : wsrOrt.lastError,
-          }
+          error: silentWithdrawalErrorForInvoice
+            ? undefined
+            : wsrOrt.lastError,
+        }
         : {}),
     };
   }
@@ -641,7 +643,7 @@ function buildTransactionForPeerPushCredit(
 function buildTransactionForBankIntegratedWithdraw(
   wgRecord: WithdrawalGroupRecord,
   ort?: OperationRetryRecord,
-): Transaction {
+): TransactionWithdrawal {
   if (wgRecord.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated)
     throw Error("");
 
@@ -676,7 +678,7 @@ function buildTransactionForManualWithdraw(
   withdrawalGroup: WithdrawalGroupRecord,
   exchangeDetails: ExchangeWireDetails,
   ort?: OperationRetryRecord,
-): Transaction {
+): TransactionWithdrawal {
   if (withdrawalGroup.wgInfo.withdrawalType !== 
WithdrawalRecordType.BankManual)
     throw Error("");
 
@@ -948,6 +950,52 @@ async function buildTransactionForPurchase(
   };
 }
 
+export async function getWithdrawalTransactionByUri(
+  ws: InternalWalletState,
+  request: WithdrawalTransactionByURIRequest,
+): Promise<TransactionWithdrawal | undefined> {
+  return await ws.db
+    .mktx((x) => [
+      x.withdrawalGroups,
+      x.exchangeDetails,
+      x.exchanges,
+      x.operationRetries,
+    ])
+    .runReadWrite(async (tx) => {
+      const withdrawalGroupRecord = await 
tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+        request.talerWithdrawUri,
+      );
+
+      if (!withdrawalGroupRecord) {
+        return undefined;
+      }
+
+      const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
+      const ort = await tx.operationRetries.get(opId);
+
+      if (
+        withdrawalGroupRecord.wgInfo.withdrawalType ===
+        WithdrawalRecordType.BankIntegrated
+      ) {
+        return buildTransactionForBankIntegratedWithdraw(
+          withdrawalGroupRecord,
+          ort,
+        );
+      }
+      const exchangeDetails = await getExchangeWireDetailsInTx(
+        tx,
+        withdrawalGroupRecord.exchangeBaseUrl,
+      );
+      if (!exchangeDetails) throw Error("not exchange details");
+
+      return buildTransactionForManualWithdraw(
+        withdrawalGroupRecord,
+        exchangeDetails,
+        ort,
+      );
+    });
+}
+
 /**
  * Retrieve the full event history for this wallet.
  */
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 58df75964..6c7e8c37a 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -45,6 +45,7 @@ import {
   LibtoolVersion,
   Logger,
   NotificationType,
+  TalerBankIntegrationHttpClient,
   TalerError,
   TalerErrorCode,
   TalerErrorDetail,
@@ -556,19 +557,11 @@ export async function getBankWithdrawalInfo(
     throw Error(`can't parse URL ${talerWithdrawUri}`);
   }
 
-  const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl);
+  const bankApi = new 
TalerBankIntegrationHttpClient(uriResult.bankIntegrationApiBaseUrl, http);
 
-  const configResp = await http.fetch(configReqUrl.href);
-  const config = await readSuccessResponseJsonOrThrow(
-    configResp,
-    codecForIntegrationBankConfig(),
-  );
+  const { body: config } = await bankApi.getConfig()
 
-  const versionRes = LibtoolVersion.compare(
-    WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
-    config.version,
-  );
-  if (versionRes?.compatible != true) {
+  if (!bankApi.isCompatible(config.version)) {
     throw TalerError.fromDetail(
       TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE,
       {
@@ -579,29 +572,24 @@ export async function getBankWithdrawalInfo(
     );
   }
 
-  const reqUrl = new URL(
-    `withdrawal-operation/${uriResult.withdrawalOperationId}`,
-    uriResult.bankIntegrationApiBaseUrl,
-  );
-
-  logger.info(`bank withdrawal status URL: ${reqUrl.href}}`);
+  const resp = await 
bankApi.getWithdrawalOperationById(uriResult.withdrawalOperationId)
 
-  const resp = await http.fetch(reqUrl.href);
-  const status = await readSuccessResponseJsonOrThrow(
-    resp,
-    codecForWithdrawOperationStatusResponse(),
-  );
+  if (resp.type === "fail") {
+    throw TalerError.fromUncheckedDetail(resp.detail);
+  }
+  const { body: status } = resp
 
   logger.info(`bank withdrawal operation status: ${j2s(status)}`);
 
   return {
+    operationId: uriResult.withdrawalOperationId,
+    apiBaseUrl: uriResult.bankIntegrationApiBaseUrl,
     amount: Amounts.parseOrThrow(status.amount),
     confirmTransferUrl: status.confirm_transfer_url,
-    selectionDone: status.selection_done,
     senderWire: status.sender_wire,
     suggestedExchange: status.suggested_exchange,
-    transferDone: status.transfer_done,
     wireTypes: status.wire_types,
+    status: status.status,
   };
 }
 
@@ -1226,8 +1214,7 @@ export async function updateWithdrawalDenoms(
         denom.verificationStatus === DenominationVerificationStatus.Unverified
       ) {
         logger.trace(
-          `Validating denomination (${current + 1}/${
-            denominations.length
+          `Validating denomination (${current + 1}/${denominations.length
           }) signature of ${denom.denomPubHash}`,
         );
         let valid = false;
@@ -1872,7 +1859,7 @@ export async function getExchangeWithdrawalInfo(
     ) {
       logger.warn(
         `wallet's support for exchange protocol version 
${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
-          `(exchange has ${exchange.protocolVersionRange}), checking for 
updates`,
+        `(exchange has ${exchange.protocolVersionRange}), checking for 
updates`,
       );
     }
   }
@@ -1915,6 +1902,7 @@ export async function getExchangeWithdrawalInfo(
 
 export interface GetWithdrawalDetailsForUriOpts {
   restrictAge?: number;
+  notifyChangeFromPendingTimeoutMs?: number;
 }
 
 /**
@@ -1957,7 +1945,40 @@ export async function getWithdrawalDetailsForUri(
     );
   });
 
+  if (info.status === "pending" && opts.notifyChangeFromPendingTimeoutMs !== 
undefined) {
+    const bankApi = new TalerBankIntegrationHttpClient(info.apiBaseUrl, 
ws.http);
+    console.log(
+      `waiting operation (${info.operationId}) to change from pending`,
+    );
+    bankApi.getWithdrawalOperationById(info.operationId, {
+      old_state: "pending",
+      timeoutMs: opts.notifyChangeFromPendingTimeoutMs
+    }).then(resp => {
+      console.log(
+        `operation (${info.operationId}) to change to ${JSON.stringify(resp, 
undefined, 2)}`,
+      );
+      if (resp.type === "fail") {
+        //not found, this is rare since the previous request succeed
+        ws.notify({
+          type: NotificationType.WithdrawalOperationTransition,
+          operationId: info.operationId,
+          state: info.status,
+        })
+        return;
+      }
+
+      ws.notify({
+        type: NotificationType.WithdrawalOperationTransition,
+        operationId: info.operationId,
+        state: resp.body.status,
+      });
+    })
+  }
+
   return {
+    operationId: info.operationId,
+    confirmTransferUrl: info.confirmTransferUrl,
+    status: info.status,
     amount: Amounts.stringify(info.amount),
     defaultExchangeBaseUrl: info.suggestedExchange,
     possibleExchanges,
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index 7ac347b6d..7d3dc86a3 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -128,6 +128,8 @@ import {
   WalletCurrencyInfo,
   WithdrawTestBalanceRequest,
   WithdrawUriInfoResponse,
+  WithdrawalTransactionByURIRequest,
+  TransactionWithdrawal,
 } from "@gnu-taler/taler-util";
 import {
   AddBackupProviderRequest,
@@ -154,6 +156,7 @@ export enum WalletApiOperation {
   AddExchange = "addExchange",
   GetTransactions = "getTransactions",
   GetTransactionById = "getTransactionById",
+  GetWithdrawalTransactionByUri = "getWithdrawalTransactionByUri",
   TestingGetSampleTransactions = "testingGetSampleTransactions",
   ListExchanges = "listExchanges",
   GetExchangeEntryByUrl = "getExchangeEntryByUrl",
@@ -377,6 +380,12 @@ export type GetTransactionByIdOp = {
   response: Transaction;
 };
 
+export type GetWithdrawalTransactionByUriOp = {
+  op: WalletApiOperation.GetWithdrawalTransactionByUri;
+  request: WithdrawalTransactionByURIRequest;
+  response: TransactionWithdrawal | undefined;
+};
+
 export type RetryPendingNowOp = {
   op: WalletApiOperation.RetryPendingNow;
   request: EmptyObject;
@@ -1124,6 +1133,7 @@ export type WalletOperations = {
   [WalletApiOperation.GetTransactions]: GetTransactionsOp;
   [WalletApiOperation.TestingGetSampleTransactions]: 
TestingGetSampleTransactionsOp;
   [WalletApiOperation.GetTransactionById]: GetTransactionByIdOp;
+  [WalletApiOperation.GetWithdrawalTransactionByUri]: 
GetWithdrawalTransactionByUriOp;
   [WalletApiOperation.RetryPendingNow]: RetryPendingNowOp;
   [WalletApiOperation.GetPendingOperations]: GetPendingTasksOp;
   [WalletApiOperation.GetUserAttentionRequests]: GetUserAttentionRequests;
@@ -1219,10 +1229,10 @@ type Primitives = string | number | boolean;
 
 type RecursivePartial<T extends object> = {
   [P in keyof T]?: T[P] extends Array<infer U extends object>
-    ? Array<RecursivePartial<U>>
-    : T[P] extends Array<infer J extends Primitives>
-      ? Array<J>
-      : T[P] extends object
-        ? RecursivePartial<T[P]>
-        : T[P];
+  ? Array<RecursivePartial<U>>
+  : T[P] extends Array<infer J extends Primitives>
+  ? Array<J>
+  : T[P] extends object
+  ? RecursivePartial<T[P]>
+  : T[P];
 } & object;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index d6da2250a..1a876b2c8 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -264,6 +264,7 @@ import {
   failTransaction,
   getTransactionById,
   getTransactions,
+  getWithdrawalTransactionByUri,
   parseTransactionIdentifier,
   resumeTransaction,
   retryTransaction,
@@ -725,9 +726,9 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
           ageCommitmentProof: c.ageCommitmentProof,
           spend_allocation: c.spendAllocation
             ? {
-                amount: c.spendAllocation.amount,
-                id: c.spendAllocation.id,
-              }
+              amount: c.spendAllocation.amount,
+              id: c.spendAllocation.id,
+            }
             : undefined,
         });
       }
@@ -938,6 +939,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       const req = codecForTransactionByIdRequest().decode(payload);
       return await getTransactionById(ws, req);
     }
+    case WalletApiOperation.GetWithdrawalTransactionByUri: {
+      const req = codecForGetWithdrawalDetailsForUri().decode(payload);
+      return await getWithdrawalTransactionByUri(ws, req);
+    }
     case WalletApiOperation.AddExchange: {
       const req = codecForAddExchangeRequest().decode(payload);
       await fetchFreshExchange(ws, req.exchangeBaseUrl, {
@@ -997,7 +1002,10 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.GetWithdrawalDetailsForUri: {
       const req = codecForGetWithdrawalDetailsForUri().decode(payload);
-      return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
+      return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri, {
+        notifyChangeFromPendingTimeoutMs: req.notifyChangeFromPendingTimeoutMs,
+        restrictAge: req.restrictAge,
+      });
     }
     case WalletApiOperation.AcceptManualWithdrawal: {
       const req = codecForAcceptManualWithdrawalRequet().decode(payload);
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts
index 04713f3c4..1f8745a5d 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts
@@ -38,7 +38,7 @@ import { ErrorAlertView } from 
"../../components/CurrentAlerts.js";
 import { ErrorAlert } from "../../context/alert.js";
 import { ExchangeSelectionPage } from 
"../../wallet/ExchangeSelection/index.js";
 import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js";
-import { SelectAmountView, SuccessView } from "./views.js";
+import { FinalStateOperation, SelectAmountView, SuccessView } from 
"./views.js";
 
 export interface PropsFromURI {
   talerWithdrawUri: string | undefined;
@@ -60,6 +60,7 @@ export type State =
   | SelectExchangeState.NoExchangeFound
   | SelectExchangeState.Selecting
   | State.SelectAmount
+  | State.AlreadyCompleted
   | State.Success;
 
 export namespace State {
@@ -80,6 +81,12 @@ export namespace State {
     amount: AmountFieldHandler;
     currency: string;
   }
+  export interface AlreadyCompleted {
+    status: "already-completed";
+    operationState: "confirmed" | "aborted" | "selected";
+    confirmTransferUrl?: string,
+    error: undefined;
+  }
 
   export type Success = {
     status: "success";
@@ -116,6 +123,7 @@ const viewMapping: StateViewMap<State> = {
   "no-exchange-found": NoExchangesView,
   "selecting-exchange": ExchangeSelectionPage,
   success: SuccessView,
+  "already-completed": FinalStateOperation,
 };
 
 export const WithdrawPageFromURI = compose(
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 7bff13e51..bf460834d 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -21,11 +21,12 @@ import {
   ExchangeFullDetails,
   ExchangeListItem,
   ExchangeTosStatus,
+  NotificationType,
   TalerError,
   parseWithdrawExchangeUri,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect, useState, useMemo, useCallback } from "preact/hooks";
 import { alertFromError, useAlertContext } from "../../context/alert.js";
 import { useBackendContext } from "../../context/backend.js";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -208,17 +209,40 @@ export function useComponentStateFromURI({
       WalletApiOperation.GetWithdrawalDetailsForUri,
       {
         talerWithdrawUri,
+        notifyChangeFromPendingTimeoutMs: 30 * 1000
       },
     );
-    const { amount, defaultExchangeBaseUrl, possibleExchanges } = uriInfo;
+    const { amount, defaultExchangeBaseUrl, possibleExchanges, operationId, 
confirmTransferUrl, status } = uriInfo;
+    const transaction = await api.wallet.call(
+      WalletApiOperation.GetWithdrawalTransactionByUri,
+      { talerWithdrawUri },
+    );
     return {
       talerWithdrawUri,
+      operationId,
+      status,
+      transaction,
+      confirmTransferUrl,
       amount: Amounts.parseOrThrow(amount),
       thisExchange: defaultExchangeBaseUrl,
       exchanges: possibleExchanges,
     };
   });
 
+  const readyToListen = uriInfoHook && !uriInfoHook.hasError
+
+  useEffect(() => {
+    if (!uriInfoHook) {
+      return;
+    }
+    return api.listener.onUpdateNotification(
+      [NotificationType.WithdrawalOperationTransition],
+      () => {
+        uriInfoHook.retry()
+      },
+    );
+  }, [readyToListen]);
+
   if (!uriInfoHook) return { status: "loading", error: undefined };
 
   if (uriInfoHook.hasError) {
@@ -257,8 +281,20 @@ export function useComponentStateFromURI({
     };
   }
 
-  return () =>
-    exchangeSelectionState(
+  if (uriInfoHook.response.status !== "pending") {
+    if (uriInfoHook.response.transaction) {
+      onSuccess(uriInfoHook.response.transaction.transactionId)
+    }
+    return {
+      status: "already-completed",
+      operationState: uriInfoHook.response.status,
+      confirmTransferUrl: uriInfoHook.response.confirmTransferUrl,
+      error: undefined,
+    }
+  }
+
+  return useCallback(() => {
+    return exchangeSelectionState(
       doManagedWithdraw,
       cancel,
       onSuccess,
@@ -267,6 +303,7 @@ export function useComponentStateFromURI({
       exchangeList,
       defaultExchange,
     );
+  }, [])
 }
 
 type ManualOrManagedWithdrawFunction = (
@@ -294,7 +331,7 @@ function exchangeSelectionState(
     return selectedExchange;
   }
 
-  return (): State.Success | State.LoadingUriError | State.Loading => {
+  return useCallback((): State.Success | State.LoadingUriError | State.Loading 
=> {
     const { i18n } = useTranslationContext();
     const { pushAlertOnError } = useAlertContext();
     const [ageRestricted, setAgeRestricted] = useState(0);
@@ -428,5 +465,5 @@ function exchangeSelectionState(
       },
       cancel,
     };
-  };
+  }, []);
 }
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx
index a3127fafc..29f39054f 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx
@@ -23,7 +23,7 @@ import { CurrencySpecification, ExchangeListItem } from 
"@gnu-taler/taler-util";
 import * as tests from "@gnu-taler/web-util/testing";
 import { nullFunction } from "../../mui/handlers.js";
 // import { TermsState } from "../../utils/index.js";
-import { SuccessView } from "./views.js";
+import { SuccessView, FinalStateOperation } from "./views.js";
 
 export default {
   title: "withdraw",
@@ -67,6 +67,23 @@ export const TermsOfServiceNotYetLoaded = 
tests.createExample(SuccessView, {
   chooseCurrencies: [],
 });
 
+export const AlreadyAborted = tests.createExample(FinalStateOperation, {
+  error: undefined,
+  status: "already-completed",
+  operationState: "aborted"
+});
+export const AlreadySelected = tests.createExample(FinalStateOperation, {
+  error: undefined,
+  status: "already-completed",
+  operationState: "selected"
+});
+export const AlreadyConfirmed = tests.createExample(FinalStateOperation, {
+  error: undefined,
+  status: "already-completed",
+  operationState: "confirmed"
+});
+
+
 export const WithSomeFee = tests.createExample(SuccessView, {
   error: undefined,
   status: "success",
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
index 3493415d9..f90f7bed7 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
@@ -111,10 +111,19 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.GetWithdrawalDetailsForUri,
       undefined,
       {
+        status: "pending",
+        operationId: "123",
         amount: "EUR:2" as AmountString,
         possibleExchanges: [],
       },
     );
+    handler.addWalletCallResponse(
+      WalletApiOperation.GetWithdrawalTransactionByUri,
+      undefined,
+      {
+        transactionId: "123"
+      } as any,
+    );
 
     const hookBehavior = await tests.hookBehaveLikeThis(
       useComponentStateFromURI,
@@ -147,11 +156,20 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.GetWithdrawalDetailsForUri,
       undefined,
       {
+        status: "pending",
+        operationId: "123",
         amount: "ARS:2" as AmountString,
         possibleExchanges: exchanges,
         defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
       },
     );
+    handler.addWalletCallResponse(
+      WalletApiOperation.GetWithdrawalTransactionByUri,
+      undefined,
+      {
+        transactionId: "123"
+      } as any,
+    );
     handler.addWalletCallResponse(
       WalletApiOperation.GetWithdrawalDetailsForAmount,
       undefined,
@@ -217,6 +235,8 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.GetWithdrawalDetailsForUri,
       undefined,
       {
+        status: "pending",
+        operationId: "123",
         amount: "ARS:2" as AmountString,
         possibleExchanges: exchangeWithNewTos,
         defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl,
@@ -245,6 +265,8 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.GetWithdrawalDetailsForUri,
       undefined,
       {
+        status: "pending",
+        operationId: "123",
         amount: "ARS:2" as AmountString,
         possibleExchanges: exchanges,
         defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx 
b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
index 748b65817..bd9f75696 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx
@@ -21,7 +21,7 @@ import { Amount } from "../../components/Amount.js";
 import { Part } from "../../components/Part.js";
 import { QR } from "../../components/QR.js";
 import { SelectList } from "../../components/SelectList.js";
-import { Input, LinkSuccess, SvgIcon } from "../../components/styled/index.js";
+import { Input, LinkSuccess, SvgIcon, WarningBox } from 
"../../components/styled/index.js";
 import { TermsOfService } from "../../components/TermsOfService/index.js";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Button } from "../../mui/Button.js";
@@ -35,6 +35,33 @@ import { State } from "./index.js";
 import { Grid } from "../../mui/Grid.js";
 import { AmountField } from "../../components/AmountField.js";
 
+export function FinalStateOperation(state: State.AlreadyCompleted): VNode {
+  const { i18n } = useTranslationContext();
+
+  switch (state.operationState) {
+    case "confirmed": return <WarningBox>
+      <div style={{ justifyContent: "center", lineHeight: "25px" }}>
+        <i18n.Translate>This operation has already been completed by another 
wallet.</i18n.Translate>
+      </div>
+    </WarningBox>
+    case "aborted": return <WarningBox>
+      <div style={{ justifyContent: "center", lineHeight: "25px" }}>
+        <i18n.Translate>This operation has already been 
aborted</i18n.Translate>
+      </div>
+    </WarningBox>
+    case "selected": return <WarningBox>
+      <div style={{ justifyContent: "center", lineHeight: "25px" }}>
+        <i18n.Translate>This operation has already been used by another 
wallet.</i18n.Translate>
+      </div>
+      <div style={{ justifyContent: "center", lineHeight: "25px" }}>
+        <i18n.Translate>It can be confirmed in</i18n.Translate>&nbsp;<a 
target="_bank" rel="noreferrer" href={state.confirmTransferUrl}>
+          <i18n.Translate>this page</i18n.Translate>
+        </a>
+      </div>
+    </WarningBox>
+  }
+}
+
 export function SuccessView(state: State.Success): VNode {
   const { i18n } = useTranslationContext();
   // const currentTosVersionIsAccepted =
diff --git a/packages/taler-wallet-webextension/src/utils/index.ts 
b/packages/taler-wallet-webextension/src/utils/index.ts
index ad4eabf15..d83e6f472 100644
--- a/packages/taler-wallet-webextension/src/utils/index.ts
+++ b/packages/taler-wallet-webextension/src/utils/index.ts
@@ -15,6 +15,7 @@
  */
 
 import { createElement, VNode } from "preact";
+import { useCallback, useMemo } from "preact/hooks";
 
 function getJsonIfOk(r: Response): Promise<any> {
   if (r.ok) {
@@ -26,8 +27,7 @@ function getJsonIfOk(r: Response): Promise<any> {
   }
 
   throw new Error(
-    `Try another server: (${r.status}) ${
-      r.statusText || "internal server error"
+    `Try another server: (${r.status}) ${r.statusText || "internal server 
error"
     }`,
   );
 }
@@ -89,6 +89,7 @@ export function compose<SType extends { status: string }, 
PType>(
 ): (p: PType) => VNode {
   function withHook(stateHook: () => RecursiveState<SType>): () => VNode {
     function TheComponent(): VNode {
+      //if the function is the same, do not compute
       const state = stateHook();
 
       if (typeof state === "function") {
@@ -102,7 +103,9 @@ export function compose<SType extends { status: string }, 
PType>(
     }
     // TheComponent.name = `${name}`;
 
-    return TheComponent;
+    return useMemo(() => {
+      return TheComponent
+    }, [stateHook]);
   }
 
   return (p: PType) => {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index a194de0ff..1ecd66f05 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -272,11 +272,15 @@ async function reinitWallet(): Promise<void> {
   let timer;
 
   if (platform.useServiceWorkerAsBackgroundProcess()) {
-    httpLib = new ServiceWorkerHttpLib();
+    httpLib = new ServiceWorkerHttpLib({
+      // enableThrottling: false,
+    });
     cryptoWorker = new SynchronousCryptoWorkerFactoryPlain();
     timer = new SetTimeoutTimerAPI();
   } else {
-    httpLib = new BrowserHttpLib();
+    httpLib = new BrowserHttpLib({
+      // enableThrottling: false,
+    });
     // We could (should?) use the BrowserCryptoWorkerFactory here,
     // but right now we don't, to have less platform differences.
     // cryptoWorker = new BrowserCryptoWorkerFactory();
@@ -409,9 +413,9 @@ async function toggleHeaderListener(
       platform.registerTalerHeaderListener();
       return { newValue: true };
     } catch (e) {
-      logger.error("FAIL to toggle",e)
+      logger.error("FAIL to toggle", e)
     }
-    return  { newValue: false }
+    return { newValue: false }
   }
 
   const rem = await platform.getPermissionsApi().removeHostPermissions();
diff --git a/packages/web-util/src/utils/http-impl.browser.ts 
b/packages/web-util/src/utils/http-impl.browser.ts
index 18140ef13..5d65c3903 100644
--- a/packages/web-util/src/utils/http-impl.browser.ts
+++ b/packages/web-util/src/utils/http-impl.browser.ts
@@ -33,6 +33,7 @@ import {
   getDefaultHeaders,
   encodeBody,
   DEFAULT_REQUEST_TIMEOUT_MS,
+  HttpLibArgs,
 } from "@gnu-taler/taler-util/http";
 
 const logger = new Logger("browserHttpLib");
@@ -44,6 +45,12 @@ const logger = new Logger("browserHttpLib");
 export class BrowserHttpLib implements HttpRequestLibrary {
   private throttle = new RequestThrottler();
   private throttlingEnabled = true;
+  private requireTls = false;
+
+  constructor(args?: HttpLibArgs) {
+    this.throttlingEnabled = args?.enableThrottling ?? true;
+    this.requireTls = args?.requireTls ?? false;
+  }
 
   fetch(
     requestUrl: string,
@@ -55,8 +62,8 @@ export class BrowserHttpLib implements HttpRequestLibrary {
     const requestTimeout =
       options?.timeout ?? 
Duration.fromMilliseconds(DEFAULT_REQUEST_TIMEOUT_MS);
 
+    const parsedUrl = new URL(requestUrl);
     if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
-      const parsedUrl = new URL(requestUrl);
       throw TalerError.fromDetail(
         TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
         {
@@ -67,6 +74,16 @@ export class BrowserHttpLib implements HttpRequestLibrary {
         `request to origin ${parsedUrl.origin} was throttled`,
       );
     }
+    if (this.requireTls && parsedUrl.protocol !== "https:") {
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_NETWORK_ERROR,
+        {
+          requestMethod: requestMethod,
+          requestUrl: requestUrl,
+        },
+        `request to ${parsedUrl.origin} is not possible with protocol 
${parsedUrl.protocol}`,
+      );
+    }
 
     let myBody: ArrayBuffer | undefined =
       requestMethod === "POST" || requestMethod === "PUT" || requestMethod === 
"PATCH"
diff --git a/packages/web-util/src/utils/http-impl.sw.ts 
b/packages/web-util/src/utils/http-impl.sw.ts
index 3c269e695..2ae4ccd86 100644
--- a/packages/web-util/src/utils/http-impl.sw.ts
+++ b/packages/web-util/src/utils/http-impl.sw.ts
@@ -27,6 +27,7 @@ import {
 import {
   DEFAULT_REQUEST_TIMEOUT_MS,
   Headers,
+  HttpLibArgs,
   HttpRequestLibrary,
   HttpRequestOptions,
   HttpResponse,
@@ -41,6 +42,12 @@ import {
 export class ServiceWorkerHttpLib implements HttpRequestLibrary {
   private throttle = new RequestThrottler();
   private throttlingEnabled = true;
+  private requireTls = false;
+
+  public constructor(args?: HttpLibArgs) {
+    this.throttlingEnabled = args?.enableThrottling ?? true;
+    this.requireTls = args?.requireTls ?? false;
+  }
 
   async fetch(
     requestUrl: string,
@@ -52,8 +59,8 @@ export class ServiceWorkerHttpLib implements 
HttpRequestLibrary {
     const requestTimeout =
       options?.timeout ?? 
Duration.fromMilliseconds(DEFAULT_REQUEST_TIMEOUT_MS);
 
+    const parsedUrl = new URL(requestUrl);
     if (this.throttlingEnabled && this.throttle.applyThrottle(requestUrl)) {
-      const parsedUrl = new URL(requestUrl);
       throw TalerError.fromDetail(
         TalerErrorCode.WALLET_HTTP_REQUEST_THROTTLED,
         {
@@ -64,6 +71,16 @@ export class ServiceWorkerHttpLib implements 
HttpRequestLibrary {
         `request to origin ${parsedUrl.origin} was throttled`,
       );
     }
+    if (this.requireTls && parsedUrl.protocol !== "https:") {
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_NETWORK_ERROR,
+        {
+          requestMethod: requestMethod,
+          requestUrl: requestUrl,
+        },
+        `request to ${parsedUrl.origin} is not possible with protocol 
${parsedUrl.protocol}`,
+      );
+    }
 
     let myBody: ArrayBuffer | undefined =
       requestMethod === "POST" ? encodeBody(requestBody) : undefined;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]