From e3850158c249d890399fdb9e083ec7e654a8380f Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 14 Aug 2020 13:06:42 +0530 Subject: re-implement integration test functionalty that will be used by the exchange for testing --- packages/taler-wallet-android/src/index.ts | 3 +- packages/taler-wallet-cli/package.json | 1 + packages/taler-wallet-cli/src/index.ts | 8 +- packages/taler-wallet-core/src/index.ts | 4 +- .../taler-wallet-core/src/operations/testing.ts | 281 +++++++++++++++++- .../taler-wallet-core/src/types/walletTypes.ts | 187 +++++++++++- packages/taler-wallet-core/src/wallet.ts | 194 ++++++++++++- .../taler-wallet-core/src/walletCoreApiHandler.ts | 318 --------------------- .../taler-wallet-webextension/src/wxBackend.ts | 9 +- pnpm-lock.yaml | 2 + 10 files changed, 665 insertions(+), 342 deletions(-) delete mode 100644 packages/taler-wallet-core/src/walletCoreApiHandler.ts diff --git a/packages/taler-wallet-android/src/index.ts b/packages/taler-wallet-android/src/index.ts index 4430848c3..07d15d584 100644 --- a/packages/taler-wallet-android/src/index.ts +++ b/packages/taler-wallet-android/src/index.ts @@ -38,7 +38,6 @@ import { WalletNotification, WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_MERCHANT_PROTOCOL_VERSION, - handleCoreApiRequest, } from "taler-wallet-core"; import fs from "fs"; @@ -229,7 +228,7 @@ class AndroidWalletMessageHandler { } default: { const wallet = await this.wp.promise; - return await handleCoreApiRequest(wallet, operation, id, args); + return await wallet.handleCoreApiRequest(operation, id, args); } } } diff --git a/packages/taler-wallet-cli/package.json b/packages/taler-wallet-cli/package.json index 1870aa296..72a737d40 100644 --- a/packages/taler-wallet-cli/package.json +++ b/packages/taler-wallet-cli/package.json @@ -43,6 +43,7 @@ "typescript": "^3.9.7" }, "dependencies": { + "axios": "^0.19.2", "source-map-support": "^0.5.19", "taler-wallet-core": "workspace:*", "tslib": "^2.0.0" diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index a32ed2267..94f01ba80 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -26,7 +26,6 @@ import { NodeHttpLib, PreparePayResultType, setDangerousTimetravel, - handleCoreApiRequest, classifyTalerUri, TalerUriType, decodeCrock, @@ -34,10 +33,10 @@ import { codecForList, codecForString, printTestVectors, + NodeThreadCryptoWorkerFactory, + CryptoApi, } from "taler-wallet-core"; import * as clk from "./clk"; -import { NodeThreadCryptoWorkerFactory } from "taler-wallet-core/lib/crypto/workers/nodeThreadWorker"; -import { CryptoApi } from "taler-wallet-core/lib/crypto/workers/cryptoApi"; // This module also serves as the entry point for the crypto // thread worker, and thus must expose these two handlers. @@ -210,8 +209,7 @@ walletCli console.error("Invalid JSON"); process.exit(1); } - const resp = await handleCoreApiRequest( - wallet, + const resp = await wallet.handleCoreApiRequest( args.api.operation, "reqid-1", requestJson, diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 784c9d63b..1d5dc493b 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -37,8 +37,6 @@ export * from "./types/walletTypes"; export * from "./types/talerTypes"; -export * from "./walletCoreApiHandler"; - export * from "./util/taleruri"; export * from "./util/time"; @@ -54,7 +52,7 @@ export * from "./util/testvectors"; export * from "./operations/versions"; export type { CryptoWorker } from "./crypto/workers/cryptoWorker"; -export type { CryptoWorkerFactory } from "./crypto/workers/cryptoApi"; +export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi"; export * from "./util/http"; diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 3ccfafc93..629cd92ff 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -21,10 +21,19 @@ import { checkSuccessResponseOrThrow, } from "../util/http"; import { codecForAny } from "../util/codec"; -import { AmountString } from "../types/talerTypes"; +import { + AmountString, + CheckPaymentResponse, + codecForCheckPaymentResponse, +} from "../types/talerTypes"; import { InternalWalletState } from "./state"; import { createTalerWithdrawReserve } from "./reserves"; import { URL } from "../util/url"; +import { Wallet } from "../wallet"; +import { Amounts } from "../util/amounts"; +import { NodeHttpLib } from "../headless/NodeHttpLib"; +import { getDefaultNodeWallet } from "../headless/helpers"; +import { TestPayArgs, PreparePayResultType, IntegrationTestArgs } from "../types/walletTypes"; const logger = new Logger("operations/testing.ts"); @@ -38,6 +47,11 @@ interface BankWithdrawalResponse { withdrawal_id: string; } +interface MerchantBackendInfo { + baseUrl: string; + apikey: string; +} + /** * Generate a random alphanumeric ID. Does *not* use cryptographically * secure randomness. @@ -154,3 +168,268 @@ async function registerRandomBankUser( await checkSuccessResponseOrThrow(resp); return bankUser; } + +async function refund( + http: HttpRequestLibrary, + merchantBackend: MerchantBackendInfo, + orderId: string, + reason: string, + refundAmount: string, +): Promise { + const reqUrl = new URL( + `private/orders/${orderId}/refund`, + merchantBackend.baseUrl, + ); + const refundReq = { + order_id: orderId, + reason, + refund: refundAmount, + }; + const resp = await http.postJson(reqUrl.href, refundReq, { + headers: { + Authorization: `ApiKey ${merchantBackend.apikey}`, + }, + }); + const r = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + const refundUri = r.taler_refund_uri; + if (!refundUri) { + throw Error("no refund URI in response"); + } + return refundUri; +} + +async function createOrder( + http: HttpRequestLibrary, + merchantBackend: MerchantBackendInfo, + amount: string, + summary: string, + fulfillmentUrl: string, +): Promise<{ orderId: string }> { + const t = Math.floor(new Date().getTime() / 1000) + 15 * 60; + const reqUrl = new URL("private/orders", merchantBackend.baseUrl).href; + const orderReq = { + order: { + amount, + summary, + fulfillment_url: fulfillmentUrl, + refund_deadline: { t_ms: t * 1000 }, + wire_transfer_deadline: { t_ms: t * 1000 }, + }, + }; + const resp = await http.postJson(reqUrl, orderReq, { + headers: { + Authorization: `ApiKey ${merchantBackend.apikey}`, + }, + }); + const r = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + const orderId = r.order_id; + if (!orderId) { + throw Error("no order id in response"); + } + return { orderId }; +} + +async function checkPayment( + http: HttpRequestLibrary, + merchantBackend: MerchantBackendInfo, + orderId: string, +): Promise { + const reqUrl = new URL(`/private/orders/${orderId}`, merchantBackend.baseUrl); + reqUrl.searchParams.set("order_id", orderId); + const resp = await http.get(reqUrl.href, { + headers: { + Authorization: `ApiKey ${merchantBackend.apikey}`, + }, + }); + return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse()); +} + +interface BankUser { + username: string; + password: string; +} + +interface BankWithdrawalResponse { + taler_withdraw_uri: string; + withdrawal_id: string; +} + +async function makePayment( + http: HttpRequestLibrary, + wallet: Wallet, + merchant: MerchantBackendInfo, + amount: string, + summary: string, +): Promise<{ orderId: string }> { + const orderResp = await createOrder( + http, + merchant, + amount, + summary, + "taler://fulfillment-success/thx", + ); + + console.log("created order with orderId", orderResp.orderId); + + let paymentStatus = await checkPayment(http, merchant, orderResp.orderId); + + console.log("payment status", paymentStatus); + + const talerPayUri = paymentStatus.taler_pay_uri; + if (!talerPayUri) { + throw Error("no taler://pay/ URI in payment response"); + } + + const preparePayResult = await wallet.preparePayForUri(talerPayUri); + + console.log("prepare pay result", preparePayResult); + + if (preparePayResult.status != "payment-possible") { + throw Error("payment not possible"); + } + + const confirmPayResult = await wallet.confirmPay( + preparePayResult.proposalId, + undefined, + ); + + console.log("confirmPayResult", confirmPayResult); + + paymentStatus = await checkPayment(http, merchant, orderResp.orderId); + + console.log("payment status after wallet payment:", paymentStatus); + + if (paymentStatus.order_status !== "paid") { + throw Error("payment did not succeed"); + } + + return { + orderId: orderResp.orderId, + }; +} + +export async function runIntegrationTest( + http: HttpRequestLibrary, + wallet: Wallet, + args: IntegrationTestArgs, +): Promise { + logger.info("running test with arguments", args); + + const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend); + const currency = parsedSpendAmount.currency; + + const myHttpLib = new NodeHttpLib(); + myHttpLib.setThrottling(false); + + const myWallet = await getDefaultNodeWallet({ httpLib: myHttpLib }); + + myWallet.runRetryLoop().catch((e) => { + console.error("exception during retry loop:", e); + }); + + logger.info("withdrawing test balance"); + await wallet.withdrawTestBalance( + args.amountToWithdraw, + args.bankBaseUrl, + args.exchangeBaseUrl, + ); + logger.info("done withdrawing test balance"); + + const balance = await myWallet.getBalances(); + + console.log(JSON.stringify(balance, null, 2)); + + const myMerchant: MerchantBackendInfo = { + baseUrl: args.merchantBaseUrl, + apikey: args.merchantApiKey, + }; + + await makePayment( + http, + wallet, + myMerchant, + args.amountToSpend, + "hello world", + ); + + // Wait until the refresh is done + await myWallet.runUntilDone(); + + console.log("withdrawing test balance for refund"); + const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`); + const spendAmountTwo = Amounts.parseOrThrow(`${currency}:7`); + const refundAmount = Amounts.parseOrThrow(`${currency}:6`); + const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`); + + await myWallet.withdrawTestBalance( + Amounts.stringify(withdrawAmountTwo), + args.bankBaseUrl, + args.exchangeBaseUrl, + ); + + // Wait until the withdraw is done + await myWallet.runUntilDone(); + + const { orderId: refundOrderId } = await makePayment( + http, + myWallet, + myMerchant, + Amounts.stringify(spendAmountTwo), + "order that will be refunded", + ); + + const refundUri = await refund( + http, + myMerchant, + refundOrderId, + "test refund", + Amounts.stringify(refundAmount), + ); + + console.log("refund URI", refundUri); + + await myWallet.applyRefund(refundUri); + + // Wait until the refund is done + await myWallet.runUntilDone(); + + await makePayment( + http, + myWallet, + myMerchant, + Amounts.stringify(spendAmountThree), + "payment after refund", + ); + + await myWallet.runUntilDone(); +} + +export async function testPay( + http: HttpRequestLibrary, + wallet: Wallet, + args: TestPayArgs, +) { + console.log("creating order"); + const merchant = { apikey: args.apikey, baseUrl: args.merchant }; + const orderResp = await createOrder( + http, + merchant, + args.amount, + args.summary, + "taler://fulfillment-success/thank+you", + ); + console.log("created new order with order ID", orderResp.orderId); + const checkPayResp = await checkPayment(http, merchant, orderResp.orderId); + const talerPayUri = checkPayResp.taler_pay_uri; + if (!talerPayUri) { + console.error("fatal: no taler pay URI received from backend"); + process.exit(1); + return; + } + console.log("taler pay URI:", talerPayUri); + const result = await wallet.preparePayForUri(talerPayUri); + if (result.status !== PreparePayResultType.PaymentPossible) { + throw Error(`unexpected prepare pay status: ${result.status}`); + } + await wallet.confirmPay(result.proposalId, undefined); +} diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts index 8521af3ff..e64187e72 100644 --- a/packages/taler-wallet-core/src/types/walletTypes.ts +++ b/packages/taler-wallet-core/src/types/walletTypes.ts @@ -229,9 +229,7 @@ export const codecForConfirmPayResultPending = (): Codec< .property("type", codecForConstString(ConfirmPayResultType.Pending)) .build("ConfirmPayResultPending"); -export const codecForConfirmPayResultDone = (): Codec< - ConfirmPayResultDone -> => +export const codecForConfirmPayResultDone = (): Codec => buildCodecForObject() .property("type", codecForConstString(ConfirmPayResultType.Done)) .property("nextUrl", codecForString()) @@ -240,7 +238,10 @@ export const codecForConfirmPayResultDone = (): Codec< export const codecForConfirmPayResult = (): Codec => buildCodecForUnion() .discriminateOn("type") - .alternative(ConfirmPayResultType.Pending, codecForConfirmPayResultPending()) + .alternative( + ConfirmPayResultType.Pending, + codecForConfirmPayResultPending(), + ) .alternative(ConfirmPayResultType.Done, codecForConfirmPayResultDone()) .build("ConfirmPayResult"); @@ -650,3 +651,181 @@ export interface GetExchangeTosResult { */ acceptedEtag: string | undefined; } + +export interface TestPayArgs { + merchant: string; + apikey: string; + amount: string; + summary: string; +} + +export const codecForTestPayArgs = (): Codec => + buildCodecForObject() + .property("merchant", codecForString()) + .property("apikey", codecForString()) + .property("amount", codecForString()) + .property("summary", codecForString()) + .build("TestPayArgs"); + +export interface IntegrationTestArgs { + exchangeBaseUrl: string; + bankBaseUrl: string; + merchantBaseUrl: string; + merchantApiKey: string; + amountToWithdraw: string; + amountToSpend: string; +} + +export const codecForIntegrationTestArgs = (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("bankBaseUrl", codecForString()) + .property("merchantBaseUrl", codecForString()) + .property("merchantApiKey", codecForString()) + .property("amountToSpend", codecForAmountString()) + .property("amountToWithdraw", codecForAmountString()) + .build("IntegrationTestArgs"); + +export interface AddExchangeRequest { + exchangeBaseUrl: string; +} + +export const codecForAddExchangeRequest = (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .build("AddExchangeRequest"); + +export interface GetExchangeTosRequest { + exchangeBaseUrl: string; +} + +export const codecForGetExchangeTosRequest = (): Codec => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .build("GetExchangeTosRequest"); + +export interface AcceptManualWithdrawalRequest { + exchangeBaseUrl: string; + amount: string; +} + +export const codecForAcceptManualWithdrawalRequet = (): Codec< + AcceptManualWithdrawalRequest +> => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("AcceptManualWithdrawalRequest"); + +export interface GetWithdrawalDetailsForAmountRequest { + exchangeBaseUrl: string; + amount: string; +} + +export interface AcceptBankIntegratedWithdrawalRequest { + talerWithdrawUri: string; + exchangeBaseUrl: string; +} + +export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec< + AcceptBankIntegratedWithdrawalRequest +> => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("talerWithdrawUri", codecForString()) + .build("AcceptBankIntegratedWithdrawalRequest"); + +export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec< + GetWithdrawalDetailsForAmountRequest +> => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForString()) + .build("GetWithdrawalDetailsForAmountRequest"); + +export interface AcceptExchangeTosRequest { + exchangeBaseUrl: string; + etag: string; +} + +export const codecForAcceptExchangeTosRequest = (): Codec< + AcceptExchangeTosRequest +> => + buildCodecForObject() + .property("exchangeBaseUrl", codecForString()) + .property("etag", codecForString()) + .build("AcceptExchangeTosRequest"); + +export interface ApplyRefundRequest { + talerRefundUri: string; +} + +export const codecForApplyRefundRequest = (): Codec => + buildCodecForObject() + .property("talerRefundUri", codecForString()) + .build("ApplyRefundRequest"); + +export interface GetWithdrawalDetailsForUriRequest { + talerWithdrawUri: string; +} + +export const codecForGetWithdrawalDetailsForUri = (): Codec< + GetWithdrawalDetailsForUriRequest +> => + buildCodecForObject() + .property("talerWithdrawUri", codecForString()) + .build("GetWithdrawalDetailsForUriRequest"); + +export interface AbortProposalRequest { + proposalId: string; +} + +export const codecForAbortProposalRequest = (): Codec => + buildCodecForObject() + .property("proposalId", codecForString()) + .build("AbortProposalRequest"); + +export interface PreparePayRequest { + talerPayUri: string; +} + +export const codecForPreparePayRequest = (): Codec => + buildCodecForObject() + .property("talerPayUri", codecForString()) + .build("PreparePay"); + +export interface ConfirmPayRequest { + proposalId: string; + sessionId?: string; +} + +export const codecForConfirmPayRequest = (): Codec => + buildCodecForObject() + .property("proposalId", codecForString()) + .property("sessionId", codecOptional(codecForString())) + .build("ConfirmPay"); + +export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; + +export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification; + +export interface CoreApiNotification { + type: "notification"; + payload: unknown; +} + +export interface CoreApiResponseSuccess { + // To distinguish the message from notifications + type: "response"; + operation: string; + id: string; + result: unknown; +} + +export interface CoreApiResponseError { + // To distinguish the message from notifications + type: "error"; + operation: string; + id: string; + error: OperationErrorDetails; +} diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 4a409f58d..0b3e2ed60 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -70,6 +70,22 @@ import { GetExchangeTosResult, AcceptManualWithdrawalResult, BalancesResponse, + TestPayArgs, + PreparePayResultType, + IntegrationTestArgs, + codecForAddExchangeRequest, + codecForGetWithdrawalDetailsForUri, + codecForAcceptManualWithdrawalRequet, + codecForGetWithdrawalDetailsForAmountRequest, + codecForAcceptExchangeTosRequest, + codecForApplyRefundRequest, + codecForAcceptBankIntegratedWithdrawalRequest, + codecForGetExchangeTosRequest, + codecForAbortProposalRequest, + codecForConfirmPayRequest, + CoreApiResponse, + codecForPreparePayRequest, + codecForIntegrationTestArgs, } from "./types/walletTypes"; import { Logger } from "./util/logging"; @@ -107,13 +123,23 @@ import { WalletNotification, NotificationType } from "./types/notifications"; import { processPurchaseQueryRefund, applyRefund } from "./operations/refund"; import { durationMin, Duration } from "./util/time"; import { processRecoupGroup } from "./operations/recoup"; -import { OperationFailedAndReportedError } from "./operations/errors"; +import { + OperationFailedAndReportedError, + OperationFailedError, + makeErrorDetails, +} from "./operations/errors"; import { TransactionsRequest, TransactionsResponse, + codecForTransactionsRequest, } from "./types/transactions"; import { getTransactions } from "./operations/transactions"; -import { withdrawTestBalance } from "./operations/testing"; +import { + withdrawTestBalance, + runIntegrationTest, + testPay, +} from "./operations/testing"; +import { TalerErrorCode } from "."; const builtinCurrencies: CurrencyRecord[] = [ { @@ -879,4 +905,168 @@ export class Wallet { ): Promise { await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl); } + + async runIntegrationtest(args: IntegrationTestArgs): Promise { + return runIntegrationTest(this.ws.http, this, args); + } + + async testPay(args: TestPayArgs) { + return testPay(this.ws.http, this, args); + } + + /** + * Implementation of the "wallet-core" API. + */ + + private async dispatchRequestInternal( + operation: string, + payload: unknown, + ): Promise> { + switch (operation) { + case "withdrawTestkudos": { + await this.withdrawTestBalance(); + return {}; + } + case "runIntegrationtest": { + const req = codecForIntegrationTestArgs().decode(payload); + await this.runIntegrationtest(req); + return {} + } + case "testPay": { + const req = codecForIntegrationTestArgs().decode(payload); + await this.runIntegrationtest(req); + return {} + } + case "getTransactions": { + const req = codecForTransactionsRequest().decode(payload); + return await this.getTransactions(req); + } + case "addExchange": { + const req = codecForAddExchangeRequest().decode(payload); + await this.updateExchangeFromUrl(req.exchangeBaseUrl); + return {}; + } + case "listExchanges": { + return await this.getExchanges(); + } + case "getWithdrawalDetailsForUri": { + const req = codecForGetWithdrawalDetailsForUri().decode(payload); + return await this.getWithdrawalDetailsForUri(req.talerWithdrawUri); + } + case "acceptManualWithdrawal": { + const req = codecForAcceptManualWithdrawalRequet().decode(payload); + const res = await this.acceptManualWithdrawal( + req.exchangeBaseUrl, + Amounts.parseOrThrow(req.amount), + ); + return res; + } + case "getWithdrawalDetailsForAmount": { + const req = codecForGetWithdrawalDetailsForAmountRequest().decode( + payload, + ); + return await this.getWithdrawalDetailsForAmount( + req.exchangeBaseUrl, + Amounts.parseOrThrow(req.amount), + ); + } + case "getBalances": { + return await this.getBalances(); + } + case "getPendingOperations": { + return await this.getPendingOperations(); + } + case "setExchangeTosAccepted": { + const req = codecForAcceptExchangeTosRequest().decode(payload); + await this.acceptExchangeTermsOfService( + req.exchangeBaseUrl, + req.etag, + ); + return {}; + } + case "applyRefund": { + const req = codecForApplyRefundRequest().decode(payload); + return await this.applyRefund(req.talerRefundUri); + } + case "acceptBankIntegratedWithdrawal": { + const req = codecForAcceptBankIntegratedWithdrawalRequest().decode( + payload, + ); + return await this.acceptWithdrawal( + req.talerWithdrawUri, + req.exchangeBaseUrl, + ); + } + case "getExchangeTos": { + const req = codecForGetExchangeTosRequest().decode(payload); + return this.getExchangeTos(req.exchangeBaseUrl); + } + case "abortProposal": { + const req = codecForAbortProposalRequest().decode(payload); + await this.refuseProposal(req.proposalId); + return {}; + } + case "retryPendingNow": { + await this.runPending(true); + return {}; + } + case "preparePay": { + const req = codecForPreparePayRequest().decode(payload); + return await this.preparePayForUri(req.talerPayUri); + } + case "confirmPay": { + const req = codecForConfirmPayRequest().decode(payload); + return await this.confirmPay(req.proposalId, req.sessionId); + } + } + throw OperationFailedError.fromCode( + TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, + "unknown operation", + { + operation, + }, + ); + } + + /** + * Handle a request to the wallet-core API. + */ + async handleCoreApiRequest( + operation: string, + id: string, + payload: unknown, + ): Promise { + try { + const result = await this.dispatchRequestInternal(operation, payload); + return { + type: "response", + operation, + id, + result, + }; + } catch (e) { + if ( + e instanceof OperationFailedError || + e instanceof OperationFailedAndReportedError + ) { + return { + type: "error", + operation, + id, + error: e.operationError, + }; + } else { + return { + type: "error", + operation, + id, + error: makeErrorDetails( + TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, + `unexpected exception: ${e}`, + {}, + ), + }; + } + } + } } diff --git a/packages/taler-wallet-core/src/walletCoreApiHandler.ts b/packages/taler-wallet-core/src/walletCoreApiHandler.ts deleted file mode 100644 index 5bc9005a8..000000000 --- a/packages/taler-wallet-core/src/walletCoreApiHandler.ts +++ /dev/null @@ -1,318 +0,0 @@ -/* - 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 - */ - -import { Wallet } from "./wallet"; -import { - OperationFailedError, - OperationFailedAndReportedError, - makeErrorDetails, -} from "./operations/errors"; -import { TalerErrorCode } from "./TalerErrorCode"; -import { codecForTransactionsRequest } from "./types/transactions"; -import { - buildCodecForObject, - codecForString, - Codec, - codecOptional, -} from "./util/codec"; -import { Amounts } from "./util/amounts"; -import { OperationErrorDetails } from "./types/walletTypes"; - -export interface AddExchangeRequest { - exchangeBaseUrl: string; -} - -export const codecForAddExchangeRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .build("AddExchangeRequest"); - -export interface GetExchangeTosRequest { - exchangeBaseUrl: string; -} - -export const codecForGetExchangeTosRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .build("GetExchangeTosRequest"); - -export interface AcceptManualWithdrawalRequest { - exchangeBaseUrl: string; - amount: string; -} - -export const codecForAcceptManualWithdrawalRequet = (): Codec< - AcceptManualWithdrawalRequest -> => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("AcceptManualWithdrawalRequest"); - -export interface GetWithdrawalDetailsForAmountRequest { - exchangeBaseUrl: string; - amount: string; -} - -export interface AcceptBankIntegratedWithdrawalRequest { - talerWithdrawUri: string; - exchangeBaseUrl: string; -} - -export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec< - AcceptBankIntegratedWithdrawalRequest -> => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("talerWithdrawUri", codecForString()) - .build("AcceptBankIntegratedWithdrawalRequest"); - -export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec< - GetWithdrawalDetailsForAmountRequest -> => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("amount", codecForString()) - .build("GetWithdrawalDetailsForAmountRequest"); - -export interface AcceptExchangeTosRequest { - exchangeBaseUrl: string; - etag: string; -} - -export const codecForAcceptExchangeTosRequest = (): Codec => - buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("etag", codecForString()) - .build("AcceptExchangeTosRequest"); - -export interface ApplyRefundRequest { - talerRefundUri: string; -} - -export const codecForApplyRefundRequest = (): Codec => - buildCodecForObject() - .property("talerRefundUri", codecForString()) - .build("ApplyRefundRequest"); - -export interface GetWithdrawalDetailsForUriRequest { - talerWithdrawUri: string; -} - -export const codecForGetWithdrawalDetailsForUri = (): Codec< - GetWithdrawalDetailsForUriRequest -> => - buildCodecForObject() - .property("talerWithdrawUri", codecForString()) - .build("GetWithdrawalDetailsForUriRequest"); - -export interface AbortProposalRequest { - proposalId: string; -} - -export const codecForAbortProposalRequest = (): Codec => - buildCodecForObject() - .property("proposalId", codecForString()) - .build("AbortProposalRequest"); - -export interface PreparePayRequest { - talerPayUri: string; -} - -const codecForPreparePayRequest = (): Codec => - buildCodecForObject() - .property("talerPayUri", codecForString()) - .build("PreparePay"); - -export interface ConfirmPayRequest { - proposalId: string; - sessionId?: string; -} - -export const codecForConfirmPayRequest = (): Codec => - buildCodecForObject() - .property("proposalId", codecForString()) - .property("sessionId", codecOptional(codecForString())) - .build("ConfirmPay"); - -/** - * Implementation of the "wallet-core" API. - */ - -async function dispatchRequestInternal( - wallet: Wallet, - operation: string, - payload: unknown, -): Promise> { - switch (operation) { - case "withdrawTestkudos": - await wallet.withdrawTestBalance(); - return {}; - case "getTransactions": { - const req = codecForTransactionsRequest().decode(payload); - return await wallet.getTransactions(req); - } - case "addExchange": { - const req = codecForAddExchangeRequest().decode(payload); - await wallet.updateExchangeFromUrl(req.exchangeBaseUrl); - return {}; - } - case "listExchanges": { - return await wallet.getExchanges(); - } - case "getWithdrawalDetailsForUri": { - const req = codecForGetWithdrawalDetailsForUri().decode(payload); - return await wallet.getWithdrawalDetailsForUri(req.talerWithdrawUri); - } - case "acceptManualWithdrawal": { - const req = codecForAcceptManualWithdrawalRequet().decode(payload); - const res = await wallet.acceptManualWithdrawal( - req.exchangeBaseUrl, - Amounts.parseOrThrow(req.amount), - ); - return res; - } - case "getWithdrawalDetailsForAmount": { - const req = codecForGetWithdrawalDetailsForAmountRequest().decode( - payload, - ); - return await wallet.getWithdrawalDetailsForAmount( - req.exchangeBaseUrl, - Amounts.parseOrThrow(req.amount), - ); - } - case "getBalances": { - return await wallet.getBalances(); - } - case "getPendingOperations": { - return await wallet.getPendingOperations(); - } - case "setExchangeTosAccepted": { - const req = codecForAcceptExchangeTosRequest().decode(payload); - await wallet.acceptExchangeTermsOfService(req.exchangeBaseUrl, req.etag); - return {}; - } - case "applyRefund": { - const req = codecForApplyRefundRequest().decode(payload); - return await wallet.applyRefund(req.talerRefundUri); - } - case "acceptBankIntegratedWithdrawal": { - const req = codecForAcceptBankIntegratedWithdrawalRequest().decode( - payload, - ); - return await wallet.acceptWithdrawal( - req.talerWithdrawUri, - req.exchangeBaseUrl, - ); - } - case "getExchangeTos": { - const req = codecForGetExchangeTosRequest().decode(payload); - return wallet.getExchangeTos(req.exchangeBaseUrl); - } - case "abortProposal": { - const req = codecForAbortProposalRequest().decode(payload); - await wallet.refuseProposal(req.proposalId); - return {}; - } - case "retryPendingNow": { - await wallet.runPending(true); - return {}; - } - case "preparePay": { - const req = codecForPreparePayRequest().decode(payload); - return await wallet.preparePayForUri(req.talerPayUri); - } - case "confirmPay": { - const req = codecForConfirmPayRequest().decode(payload); - return await wallet.confirmPay(req.proposalId, req.sessionId); - } - } - throw OperationFailedError.fromCode( - TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, - "unknown operation", - { - operation, - }, - ); -} - -export type CoreApiResponse = CoreApiResponseSuccess | CoreApiResponseError; - -export type CoreApiEnvelope = CoreApiResponse | CoreApiNotification; - -export interface CoreApiNotification { - type: "notification"; - payload: unknown; -} - -export interface CoreApiResponseSuccess { - // To distinguish the message from notifications - type: "response"; - operation: string; - id: string; - result: unknown; -} - -export interface CoreApiResponseError { - // To distinguish the message from notifications - type: "error"; - operation: string; - id: string; - error: OperationErrorDetails; -} - -/** - * Handle a request to the wallet-core API. - */ -export async function handleCoreApiRequest( - w: Wallet, - operation: string, - id: string, - payload: unknown, -): Promise { - try { - const result = await dispatchRequestInternal(w, operation, payload); - return { - type: "response", - operation, - id, - result, - }; - } catch (e) { - if ( - e instanceof OperationFailedError || - e instanceof OperationFailedAndReportedError - ) { - return { - type: "error", - operation, - id, - error: e.operationError, - }; - } else { - return { - type: "error", - operation, - id, - error: makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception: ${e}`, - {}, - ), - }; - } - } -} diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 86008dd99..60d0b6d49 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -24,7 +24,6 @@ * Imports. */ import { isFirefox, getPermissionsApi } from "./compat"; -import * as wxApi from "./wxApi"; import MessageSender = chrome.runtime.MessageSender; import { extendedPermissions } from "./permissions"; @@ -32,16 +31,12 @@ import { Wallet, OpenedPromise, openPromise, - deleteTalerDatabase, - WALLET_DB_MINOR_VERSION, - WalletDiagnostics, openTalerDatabase, Database, classifyTalerUri, TalerUriType, makeErrorDetails, TalerErrorCode, - handleCoreApiRequest, } from "taler-wallet-core"; import { BrowserHttpLib } from "./browserHttpLib"; import { BrowserCryptoWorkerFactory } from "./browserCryptoWorkerFactory"; @@ -82,7 +77,7 @@ async function dispatch( return; } - const r = await handleCoreApiRequest(w, req.operation, req.id, req.payload); + const r = await w.handleCoreApiRequest(req.operation, req.id, req.payload); try { sendResponse(r); } catch (e) { @@ -251,7 +246,7 @@ function headerListener( ); case TalerUriType.TalerRefund: return makeSyncWalletRedirect( - "/static/refund.html", + "refund.html", details.tabId, details.url, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78011a19e..9bdba271c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,6 +83,7 @@ importers: typescript: ^3.9.7 packages/taler-wallet-cli: dependencies: + axios: 0.19.2 source-map-support: 0.5.19 taler-wallet-core: 'link:../taler-wallet-core' tslib: 2.0.1 @@ -105,6 +106,7 @@ importers: '@rollup/plugin-node-resolve': ^8.4.0 '@rollup/plugin-replace': ^2.3.3 '@types/node': ^14.0.27 + axios: ^0.19.2 prettier: ^2.0.5 rimraf: ^3.0.2 rollup: ^2.23.0 -- cgit v1.2.3