From e0e82cdf07930d766081e42203c5a4e66d43191f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 8 Mar 2024 12:19:20 -0300 Subject: wip #8489 --- .../auditor-backoffice-ui/src/hooks/testing.tsx | 8 +- packages/demobank-ui/src/Routing.tsx | 9 +- packages/demobank-ui/src/context/config.ts | 201 ++++++++++----------- packages/demobank-ui/src/hooks/account.ts | 12 +- packages/demobank-ui/src/hooks/regional.ts | 28 +-- packages/demobank-ui/src/pages/LoginForm.tsx | 5 +- .../demobank-ui/src/pages/OperationState/state.ts | 10 +- .../src/pages/PaytoWireTransferForm.tsx | 2 +- packages/demobank-ui/src/pages/QrCodeSection.tsx | 2 +- .../demobank-ui/src/pages/RegistrationPage.tsx | 2 +- .../demobank-ui/src/pages/SolveChallengePage.tsx | 2 +- .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 2 +- .../src/pages/WithdrawalConfirmationQuestion.tsx | 2 +- .../src/pages/WithdrawalOperationPage.tsx | 4 +- .../src/pages/account/ShowAccountDetails.tsx | 2 +- .../src/pages/account/UpdateAccountPassword.tsx | 2 +- .../src/pages/admin/CreateNewAccount.tsx | 2 +- .../demobank-ui/src/pages/admin/DownloadStats.tsx | 2 +- .../demobank-ui/src/pages/admin/RemoveAccount.tsx | 2 +- .../src/pages/regional/ConversionConfig.tsx | 6 +- .../src/pages/regional/CreateCashout.tsx | 2 +- .../merchant-backoffice-ui/src/hooks/testing.tsx | 8 +- .../taler-util/src/http-client/bank-conversion.ts | 13 +- packages/taler-util/src/http-client/bank-core.ts | 78 ++++---- packages/taler-util/src/http-client/utils.ts | 8 + packages/taler-util/src/index.ts | 1 + 26 files changed, 213 insertions(+), 202 deletions(-) diff --git a/packages/auditor-backoffice-ui/src/hooks/testing.tsx b/packages/auditor-backoffice-ui/src/hooks/testing.tsx index 3ea22475b..20b9c8a35 100644 --- a/packages/auditor-backoffice-ui/src/hooks/testing.tsx +++ b/packages/auditor-backoffice-ui/src/hooks/testing.tsx @@ -27,7 +27,7 @@ import { ApiContextProvider } from "@gnu-taler/web-util/browser"; import { BackendContextProvider } from "../context/backend.js"; import { InstanceContextProvider } from "../context/instance.js"; import { HttpResponseOk, RequestOptions } from "@gnu-taler/web-util/browser"; -import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient } from "@gnu-taler/taler-util"; +import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient } from "@gnu-taler/taler-util"; export class ApiMockEnvironment extends MockEnvironment { constructor(debug = false) { @@ -144,9 +144,9 @@ export class ApiMockEnvironment extends MockEnvironment { } const bankCore = new TalerCoreBankHttpClient("http://localhost", mockHttpClient) - const bankIntegration = bankCore.getIntegrationAPI() - const bankRevenue = bankCore.getRevenueAPI("a") - const bankWire = bankCore.getWireGatewayAPI("b") + const bankIntegration = new TalerBankIntegrationHttpClient(bankCore.getIntegrationAPI(), mockHttpClient) + const bankRevenue = new TalerRevenueHttpClient(bankCore.getRevenueAPI("a"), "a", mockHttpClient) + const bankWire = new TalerWireGatewayHttpClient(bankCore.getWireGatewayAPI("b"), "b", mockHttpClient) return ( diff --git a/packages/demobank-ui/src/Routing.tsx b/packages/demobank-ui/src/Routing.tsx index c85d74e17..f9b388ed1 100644 --- a/packages/demobank-ui/src/Routing.tsx +++ b/packages/demobank-ui/src/Routing.tsx @@ -94,7 +94,7 @@ function PublicRounting({ const { i18n } = useTranslationContext(); const location = useCurrentLocation(publicPages); const { navigateTo } = useNavigationContext(); - const { api } = useBankCoreApiContext(); + const { bank, authenticator } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification(); useEffect(() => { @@ -109,8 +109,7 @@ function PublicRounting({ async function doAutomaticLogin(username: string, password: string) { await handleError(async () => { - const resp = await api - .getAuthenticationAPI(username) + const resp = await authenticator(username) .createAccessToken(password, { scope: "readwrite", duration: { d_us: "forever" }, @@ -570,11 +569,11 @@ function PrivateRouting({ routeMyAccountDetails={privatePages.myAccountDetails} routeMyAccountPassword={privatePages.myAccountPassword} routeConversionConfig={privatePages.conversionConfig} - routeCancel={privatePages.home} + routeCancel={privatePages.home} onUpdateSuccess={() => { navigateTo(privatePages.home.url({})) }} - />; + />; } case "homeWireTransfer": { return ( diff --git a/packages/demobank-ui/src/context/config.ts b/packages/demobank-ui/src/context/config.ts index 72c176679..39d12be86 100644 --- a/packages/demobank-ui/src/context/config.ts +++ b/packages/demobank-ui/src/context/config.ts @@ -15,20 +15,16 @@ */ import { - AccessToken, - AmountJson, - HttpStatusCode, + assertUnreachable, LibtoolVersion, - OperationFail, - OperationOk, - TalerBankConversionApi, + TalerBankConversionCacheEviction, TalerBankConversionHttpClient, TalerCorebankApi, + TalerAuthenticationHttpClient, + TalerCoreBankCacheEviction, TalerCoreBankHttpClient, - TalerError, - UserAndToken, + TalerError } from "@gnu-taler/taler-util"; -import { HttpRequestLibrary } from "@gnu-taler/taler-util/http"; import { BrowserHttpLib, ErrorLoading, @@ -61,7 +57,9 @@ import { export type Type = { url: URL; config: TalerCorebankApi.Config; - api: TalerCoreBankHttpClient; + bank: TalerCoreBankHttpClient; + conversion: TalerBankConversionHttpClient; + authenticator: (user: string) => TalerAuthenticationHttpClient; hints: VersionHint[]; }; @@ -95,13 +93,14 @@ export const BankCoreApiProvider = ({ }): VNode => { const [checked, setChecked] = useState(); const { i18n } = useTranslationContext(); - const url = new URL(baseUrl); - const api = new CacheAwareTalerCoreBankHttpClient(url.href, new BrowserHttpLib()); + + const { bankClient, conversionClient, authClient } = buildApiClient(new URL(baseUrl)) + useEffect(() => { - api + bankClient .getConfig() .then((resp) => { - if (api.isCompatible(resp.body.version)) { + if (bankClient.isCompatible(resp.body.version)) { setChecked({ type: "ok", config: resp.body, hints: [] }); } else { // this API supports version 3.0.3 @@ -116,7 +115,7 @@ export const BankCoreApiProvider = ({ setChecked({ type: "incompatible", result: resp.body, - supported: api.PROTOCOL_VERSION, + supported: bankClient.PROTOCOL_VERSION, }); } } @@ -146,9 +145,11 @@ export const BankCoreApiProvider = ({ }); } const value: Type = { - url, + url: new URL(bankClient.baseUrl), config: checked.config, - api: api, + bank: bankClient, + conversion: conversionClient, + authenticator: authClient, hints: checked.hints, }; return h(Context.Provider, { @@ -158,100 +159,84 @@ export const BankCoreApiProvider = ({ }; /** - * + * build http client with cache breaker due to SWR + * @param url + * @returns */ -class CacheAwareTalerBankConversionHttpClient extends TalerBankConversionHttpClient { - constructor(baseUrl: string, httpClient?: HttpRequestLibrary) { - super(baseUrl, httpClient); - } - async updateConversionRate(auth: AccessToken, body: TalerBankConversionApi.ConversionRate) { - const resp = await super.updateConversionRate(auth, body); - if (resp.type === "ok") { - await revalidateConversionInfo(); - } - return resp - } -} +function buildApiClient(url: URL) { + const httpLib = new BrowserHttpLib(); -class CacheAwareTalerCoreBankHttpClient extends TalerCoreBankHttpClient { - constructor(baseUrl: string, httpClient?: HttpRequestLibrary) { - super(baseUrl, httpClient); - } - async deleteAccount(auth: UserAndToken, cid?: string | undefined) { - const resp = await super.deleteAccount(auth, cid); - if (resp.type === "ok") { - await revalidatePublicAccounts(); - await revalidateBusinessAccounts(); - } - return resp; - } - async createAccount( - auth: AccessToken, - body: TalerCorebankApi.RegisterAccountRequest, - ) { - const resp = await super.createAccount(auth, body); - if (resp.type === "ok") { - // admin balance change on new account - await revalidateAccountDetails(); - await revalidateTransactions(); - await revalidatePublicAccounts(); - await revalidateBusinessAccounts(); - } - return resp; - } - async updateAccount( - auth: UserAndToken, - body: TalerCorebankApi.AccountReconfiguration, - cid?: string | undefined, - ) { - const resp = await super.updateAccount(auth, body, cid); - if (resp.type === "ok") { - await revalidateAccountDetails(); - } - return resp; - } - async createTransaction( - auth: UserAndToken, - body: TalerCorebankApi.CreateTransactionRequest, - cid?: string | undefined, - ) { - const resp = await super.createTransaction(auth, body, cid); - if (resp.type === "ok") { - await revalidateAccountDetails(); - await revalidateTransactions(); - } - return resp; - } - async confirmWithdrawalById( - auth: UserAndToken, - wid: string, - cid?: string | undefined, - ) { - const resp = await super.confirmWithdrawalById(auth, wid, cid); - if (resp.type === "ok") { - await revalidateAccountDetails(); - await revalidateTransactions(); + const bankClient = new TalerCoreBankHttpClient(url.href, httpLib, { + async notifySuccess(op) { + switch (op) { + case TalerCoreBankCacheEviction.DELELE_ACCOUNT: { + await Promise.all([ + revalidatePublicAccounts(), + revalidateBusinessAccounts(), + ]); + return + } + case TalerCoreBankCacheEviction.CREATE_ACCOUNT: { + // admin balance change on new account + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + revalidatePublicAccounts(), + revalidateBusinessAccounts(), + ]) + return; + } + case TalerCoreBankCacheEviction.UPDATE_ACCOUNT: { + await Promise.all([ + revalidateAccountDetails(), + ]) + return; + } + case TalerCoreBankCacheEviction.CREATE_TRANSACTION: { + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.CONFIRM_WITHDRAWAL: { + await Promise.all([ + revalidateAccountDetails(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.CREATE_CASHOUT: { + await Promise.all([ + revalidateAccountDetails(), + revalidateCashouts(), + revalidateTransactions(), + ]) + return; + } + case TalerCoreBankCacheEviction.UPDATE_PASSWORD: + case TalerCoreBankCacheEviction.ABORT_WITHDRAWAL: + case TalerCoreBankCacheEviction.CREATE_WITHDRAWAL: + return; + default: + assertUnreachable(op) + } } - return resp; - } - async createCashout( - auth: UserAndToken, - body: TalerCorebankApi.CashoutRequest, - cid?: string | undefined, - ) { - const resp = await super.createCashout(auth, body, cid); - if (resp.type === "ok") { - await revalidateAccountDetails(); - await revalidateCashouts(); - await revalidateTransactions(); + }); + const conversionClient = new TalerBankConversionHttpClient(bankClient.getConversionInfoAPI(), httpLib, { + async notifySuccess(op) { + switch (op) { + case TalerBankConversionCacheEviction.UPDATE_RATE: { + await revalidateConversionInfo(); + return + } + default: + assertUnreachable(op) + } } - return resp; - } - - getConversionInfoAPI(): TalerBankConversionHttpClient { - const api = super.getConversionInfoAPI(); - return new CacheAwareTalerBankConversionHttpClient(api.baseUrl, this.httpLib) - } + }); + const authClient = (user: string) => new TalerAuthenticationHttpClient(bankClient.getAuthenticationAPI(user), user, httpLib); + return { bankClient, conversionClient, authClient } } export const BankCoreApiProviderTesting = ({ @@ -267,7 +252,7 @@ export const BankCoreApiProviderTesting = ({ url: new URL(url), config: state, // @ts-expect-error this API is not being used, not really needed - api: undefined, + bank: undefined, hints: [], }; diff --git a/packages/demobank-ui/src/hooks/account.ts b/packages/demobank-ui/src/hooks/account.ts index 61a11b1a5..aa0745253 100644 --- a/packages/demobank-ui/src/hooks/account.ts +++ b/packages/demobank-ui/src/hooks/account.ts @@ -44,7 +44,7 @@ export function revalidateAccountDetails() { export function useAccountDetails(account: string) { const { state: credentials } = useSessionState(); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); async function fetcher([username, token]: [string, AccessToken]) { return await api.getAccount({ username, token }); @@ -66,7 +66,7 @@ export function revalidateWithdrawalDetails() { } export function useWithdrawalDetails(wid: string) { - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [latestStatus, setLatestStatus] = useState(); async function fetcher([wid, old_state]: [ @@ -117,7 +117,7 @@ export function useTransactionDetails(account: string, tid: number) { const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); async function fetcher([username, token, txid]: [ string, @@ -147,7 +147,7 @@ export function useTransactionDetails(account: string, tid: number) { return undefined; } -export function revalidatePublicAccounts() { +export async function revalidatePublicAccounts() { return mutate( (key) => Array.isArray(key) && key[key.length - 1] === "getPublicAccounts", undefined, { revalidate: true } ); @@ -158,7 +158,7 @@ export function usePublicAccounts( ) { const [offset, setOffset] = useState(initial); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); async function fetcher([account, txid]: [ string | undefined, @@ -233,7 +233,7 @@ export function useTransactions(account: string, initial?: number) { credentials.status !== "loggedIn" ? undefined : credentials.token; const [offset, setOffset] = useState(initial); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); async function fetcher([username, token, txid]: [ string, diff --git a/packages/demobank-ui/src/hooks/regional.ts b/packages/demobank-ui/src/hooks/regional.ts index 3469bf501..bf948d293 100644 --- a/packages/demobank-ui/src/hooks/regional.ts +++ b/packages/demobank-ui/src/hooks/regional.ts @@ -60,10 +60,10 @@ export function revalidateConversionInfo() { ); } export function useConversionInfo() { - const { api, config } = useBankCoreApiContext(); + const { conversion, config } = useBankCoreApiContext(); async function fetcher() { - return await api.getConversionInfoAPI().getConfig(); + return await conversion.getConfig(); } const { data, error } = useSWR< TalerBankConversionResultByMethod<"getConfig">, @@ -86,10 +86,10 @@ export function useConversionInfo() { } export function useCashinEstimator(): ConversionEstimators { - const { api } = useBankCoreApiContext(); + const { conversion } = useBankCoreApiContext(); return { estimateByCredit: async (fiatAmount, fee) => { - const resp = await api.getConversionInfoAPI().getCashinRate({ + const resp = await conversion.getCashinRate({ credit: fiatAmount, }); if (resp.type === "fail") { @@ -114,7 +114,7 @@ export function useCashinEstimator(): ConversionEstimators { }; }, estimateByDebit: async (regionalAmount, fee) => { - const resp = await api.getConversionInfoAPI().getCashinRate({ + const resp = await conversion.getCashinRate({ debit: regionalAmount, }); if (resp.type === "fail") { @@ -142,10 +142,10 @@ export function useCashinEstimator(): ConversionEstimators { } export function useCashoutEstimator(): ConversionEstimators { - const { api } = useBankCoreApiContext(); + const { bank, conversion } = useBankCoreApiContext(); return { estimateByCredit: async (fiatAmount, fee) => { - const resp = await api.getConversionInfoAPI().getCashoutRate({ + const resp = await conversion.getCashoutRate({ credit: fiatAmount, }); if (resp.type === "fail") { @@ -170,7 +170,7 @@ export function useCashoutEstimator(): ConversionEstimators { }; }, estimateByDebit: async (regionalAmount, fee) => { - const resp = await api.getConversionInfoAPI().getCashoutRate({ + const resp = await conversion.getCashoutRate({ debit: regionalAmount, }); if (resp.type === "fail") { @@ -204,14 +204,14 @@ export function useEstimator(): ConversionEstimators { return useCashoutEstimator() } -export function revalidateBusinessAccounts() { +export async function revalidateBusinessAccounts() { return mutate((key) => Array.isArray(key) && key[key.length - 1] === "getAccounts", undefined, { revalidate: true }); } export function useBusinessAccounts() { const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [offset, setOffset] = useState(); @@ -281,7 +281,7 @@ export function revalidateOnePendingCashouts() { } export function useOnePendingCashouts(account: string) { const { state: credentials } = useSessionState(); - const { api, config } = useBankCoreApiContext(); + const { bank: api, config } = useBankCoreApiContext(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; @@ -338,7 +338,7 @@ export function revalidateCashouts() { } export function useCashouts(account: string) { const { state: credentials } = useSessionState(); - const { api, config } = useBankCoreApiContext(); + const { bank: api, config } = useBankCoreApiContext(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; @@ -392,7 +392,7 @@ export function revalidateCashoutDetails() { export function useCashoutDetails(cashoutId: number | undefined) { const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); async function fetcher([username, token, id]: [string, AccessToken, number]) { return api.getCashoutById({ username, token }, id); @@ -443,7 +443,7 @@ export function useLastMonitorInfo( previousMoment: number, timeframe: TalerCorebankApi.MonitorTimeframeParam, ) { - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx b/packages/demobank-ui/src/pages/LoginForm.tsx index e62759415..bd20e79c8 100644 --- a/packages/demobank-ui/src/pages/LoginForm.tsx +++ b/packages/demobank-ui/src/pages/LoginForm.tsx @@ -53,7 +53,7 @@ export function LoginForm({ ); const [password, setPassword] = useState(); const { i18n } = useTranslationContext(); - const { api } = useBankCoreApiContext(); + const { authenticator } = useBankCoreApiContext(); const [notification, withErrorHandler] = useLocalNotificationHandler(); const { config } = useBankCoreApiContext(); @@ -77,8 +77,7 @@ export function LoginForm({ } const loginHandler = !username || !password ? undefined : withErrorHandler( - async () => api - .getAuthenticationAPI(username) + async () => authenticator(username) .createAccessToken(password, { // scope: "readwrite" as "write", // FIX: different than merchant scope: "readwrite", diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts index 693179d40..5baf2d51c 100644 --- a/packages/demobank-ui/src/pages/OperationState/state.ts +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -45,7 +45,7 @@ export function useComponentState({ const [bankState, updateBankState] = useBankState(); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; - const { api } = useBankCoreApiContext(); + const { bank } = useBankCoreApiContext(); const [failure, setFailure] = useState< TalerCoreBankErrorsByMethod<"createWithdrawal"> | undefined @@ -56,7 +56,7 @@ export function useComponentState({ // FIXME: if amount is not enough use balance const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`); if (!creds) return; - const resp = await api.createWithdrawal(creds, { + const resp = await bank.createWithdrawal(creds, { amount: Amounts.stringify(parsedAmount), }); if (resp.type === "fail") { @@ -91,7 +91,7 @@ export function useComponentState({ async function doAbort() { if (!creds) return; - const resp = await api.abortWithdrawalById(creds, wid); + const resp = await bank.abortWithdrawalById(creds, wid); if (resp.type === "ok") { // updateBankState("currentWithdrawalOperationId", undefined) onAbort(); @@ -104,7 +104,7 @@ export function useComponentState({ TalerCoreBankErrorsByMethod<"confirmWithdrawalById"> | undefined > { if (!creds) return; - const resp = await api.confirmWithdrawalById(creds, wid); + const resp = await bank.confirmWithdrawalById(creds, wid); if (resp.type === "ok") { mutate(() => true); //clean withdrawal state } else { @@ -113,7 +113,7 @@ export function useComponentState({ } const uri = stringifyWithdrawUri({ - bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl, + bankIntegrationApiBaseUrl: bank.getIntegrationAPI(), withdrawalOperationId, }); const parsedUri = parseWithdrawUri(uri); diff --git a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx index f746094ce..791a3b440 100644 --- a/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/demobank-ui/src/pages/PaytoWireTransferForm.tsx @@ -81,7 +81,7 @@ export function PaytoWireTransferForm({ }: Props): VNode { const [isRawPayto, setIsRawPayto] = useState(false); const { state: credentials } = useSessionState(); - const { api, config, url } = useBankCoreApiContext(); + const { bank: api, config, url } = useBankCoreApiContext(); const sendingToFixedAccount = withAccount !== undefined; diff --git a/packages/demobank-ui/src/pages/QrCodeSection.tsx b/packages/demobank-ui/src/pages/QrCodeSection.tsx index d4f5a5455..da11e631d 100644 --- a/packages/demobank-ui/src/pages/QrCodeSection.tsx +++ b/packages/demobank-ui/src/pages/QrCodeSection.tsx @@ -51,7 +51,7 @@ export function QrCodeSection({ const [notification, handleError] = useLocalNotificationHandler(); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const onAbortHandler = handleError( async () => { diff --git a/packages/demobank-ui/src/pages/RegistrationPage.tsx b/packages/demobank-ui/src/pages/RegistrationPage.tsx index 18b4c470b..e9f7e602f 100644 --- a/packages/demobank-ui/src/pages/RegistrationPage.tsx +++ b/packages/demobank-ui/src/pages/RegistrationPage.tsx @@ -80,7 +80,7 @@ function RegistrationForm({ const [notification, _, handleError] = useLocalNotification(); const settings = useSettingsContext(); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); // const { register } = useTestingAPI(); const { i18n } = useTranslationContext(); diff --git a/packages/demobank-ui/src/pages/SolveChallengePage.tsx b/packages/demobank-ui/src/pages/SolveChallengePage.tsx index 7e117f535..b2e053b3c 100644 --- a/packages/demobank-ui/src/pages/SolveChallengePage.tsx +++ b/packages/demobank-ui/src/pages/SolveChallengePage.tsx @@ -57,7 +57,7 @@ export function SolveChallengePage({ onChallengeCompleted: () => void; routeClose: RouteDefinition; }): VNode { - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const { i18n } = useTranslationContext(); const [bankState, updateBankState] = useBankState(); const [code, setCode] = useState(undefined); diff --git a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx index caf205f31..001d90fa1 100644 --- a/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx +++ b/packages/demobank-ui/src/pages/WalletWithdrawForm.tsx @@ -63,7 +63,7 @@ function OldWithdrawalForm({ // const { navigateTo } = useNavigationContext(); const [bankState, updateBankState] = useBankState(); - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 4efc82017..5925719c3 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -74,7 +74,7 @@ export function WithdrawalConfirmationQuestion({ const [notification, notify, handleError] = useLocalNotification(); - const { config, api } = useBankCoreApiContext(); + const { config, bank: api } = useBankCoreApiContext(); async function doTransfer() { await handleError(async () => { diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx index 7075c34c6..973a23011 100644 --- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx @@ -36,9 +36,9 @@ export function WithdrawalOperationPage({ routeClose: RouteDefinition; routeWithdrawalDetails: RouteDefinition<{ wopid: string }>; }): VNode { - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const uri = stringifyWithdrawUri({ - bankIntegrationApiBaseUrl: api.getIntegrationAPI().baseUrl, + bankIntegrationApiBaseUrl: api.getIntegrationAPI(), withdrawalOperationId: operationId, }); const parsedUri = parseWithdrawUri(uri); diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index 8ab3998ad..62c8df7f8 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -67,7 +67,7 @@ export function ShowAccountDetails({ const { i18n } = useTranslationContext(); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const accountIsTheCurrentUser = credentials.status === "loggedIn" ? credentials.username === account diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx index b9a334088..e21ac2464 100644 --- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx @@ -65,7 +65,7 @@ export function UpdateAccountPassword({ const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [current, setCurrent] = useState(); const [password, setPassword] = useState(); diff --git a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx index 3ae2b636c..38119735e 100644 --- a/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/CreateNewAccount.tsx @@ -45,7 +45,7 @@ export function CreateNewAccount({ const { state: credentials } = useSessionState(); const token = credentials.status !== "loggedIn" ? undefined : credentials.token; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [submitAccount, setSubmitAccount] = useState< TalerCorebankApi.RegisterAccountRequest | undefined diff --git a/packages/demobank-ui/src/pages/admin/DownloadStats.tsx b/packages/demobank-ui/src/pages/admin/DownloadStats.tsx index 66ef73d19..fba366676 100644 --- a/packages/demobank-ui/src/pages/admin/DownloadStats.tsx +++ b/packages/demobank-ui/src/pages/admin/DownloadStats.tsx @@ -59,7 +59,7 @@ export function DownloadStats({ routeCancel }: Props): VNode { credentials.status !== "loggedIn" || !credentials.isUserAdministrator ? undefined : credentials; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [options, setOptions] = useState({ compareWithPrevious: true, diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 6039db326..61def9a95 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -64,7 +64,7 @@ export function RemoveAccount({ const { state } = useSessionState(); const token = state.status !== "loggedIn" ? undefined : state.token; - const { api } = useBankCoreApiContext(); + const { bank: api } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification(); const [, updateBankState] = useBankState(); diff --git a/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx b/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx index 63423353b..8845ec9a0 100644 --- a/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx +++ b/packages/demobank-ui/src/pages/regional/ConversionConfig.tsx @@ -88,7 +88,7 @@ function useComponentState({ return () => { const { i18n } = useTranslationContext(); - const { api, config } = useBankCoreApiContext(); + const { bank, conversion, config } = useBankCoreApiContext(); const [notification, notify, handleError] = useLocalNotification(); @@ -153,9 +153,7 @@ function useComponentState({ if (!creds) return await handleError(async () => { if (status.status === "fail") return; - const resp = await api - .getConversionInfoAPI() - .updateConversionRate(creds.token, status.result.conv) + const resp = await conversion.updateConversionRate(creds.token, status.result.conv) if (resp.type === "ok") { setSection("detail") } else { diff --git a/packages/demobank-ui/src/pages/regional/CreateCashout.tsx b/packages/demobank-ui/src/pages/regional/CreateCashout.tsx index a5b8f774a..2f15d16b4 100644 --- a/packages/demobank-ui/src/pages/regional/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/regional/CreateCashout.tsx @@ -86,7 +86,7 @@ export function CreateCashout({ const creds = credentials.status !== "loggedIn" ? undefined : credentials; const [, updateBankState] = useBankState(); - const { api, config, hints } = useBankCoreApiContext(); + const { bank: api, config, hints } = useBankCoreApiContext(); const [form, setForm] = useState>({ isDebit: true }); const [notification, notify, handleError] = useLocalNotification(); const info = useConversionInfo(); diff --git a/packages/merchant-backoffice-ui/src/hooks/testing.tsx b/packages/merchant-backoffice-ui/src/hooks/testing.tsx index 3ea22475b..20b9c8a35 100644 --- a/packages/merchant-backoffice-ui/src/hooks/testing.tsx +++ b/packages/merchant-backoffice-ui/src/hooks/testing.tsx @@ -27,7 +27,7 @@ import { ApiContextProvider } from "@gnu-taler/web-util/browser"; import { BackendContextProvider } from "../context/backend.js"; import { InstanceContextProvider } from "../context/instance.js"; import { HttpResponseOk, RequestOptions } from "@gnu-taler/web-util/browser"; -import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient } from "@gnu-taler/taler-util"; +import { TalerBankIntegrationHttpClient, TalerCoreBankHttpClient, TalerRevenueHttpClient, TalerWireGatewayHttpClient } from "@gnu-taler/taler-util"; export class ApiMockEnvironment extends MockEnvironment { constructor(debug = false) { @@ -144,9 +144,9 @@ export class ApiMockEnvironment extends MockEnvironment { } const bankCore = new TalerCoreBankHttpClient("http://localhost", mockHttpClient) - const bankIntegration = bankCore.getIntegrationAPI() - const bankRevenue = bankCore.getRevenueAPI("a") - const bankWire = bankCore.getWireGatewayAPI("b") + const bankIntegration = new TalerBankIntegrationHttpClient(bankCore.getIntegrationAPI(), mockHttpClient) + const bankRevenue = new TalerRevenueHttpClient(bankCore.getRevenueAPI("a"), "a", mockHttpClient) + const bankWire = new TalerWireGatewayHttpClient(bankCore.getWireGatewayAPI("b"), "b", mockHttpClient) return ( diff --git a/packages/taler-util/src/http-client/bank-conversion.ts b/packages/taler-util/src/http-client/bank-conversion.ts index 91d07b11b..ea247ccfc 100644 --- a/packages/taler-util/src/http-client/bank-conversion.ts +++ b/packages/taler-util/src/http-client/bank-conversion.ts @@ -39,7 +39,7 @@ import { codecForCashoutConversionResponse, codecForConversionBankConfig, } from "./types.js"; -import { makeBearerTokenAuthHeader } from "./utils.js"; +import { CacheEvictor, makeBearerTokenAuthHeader, nullEvictor } from "./utils.js"; export type TalerBankConversionResultByMethod< prop extends keyof TalerBankConversionHttpClient, @@ -48,6 +48,10 @@ export type TalerBankConversionErrorsByMethod< prop extends keyof TalerBankConversionHttpClient, > = FailCasesByMethod; +export enum TalerBankConversionCacheEviction { + UPDATE_RATE, +} + /** * The API is used by the wallets. */ @@ -55,12 +59,15 @@ export class TalerBankConversionHttpClient { public readonly PROTOCOL_VERSION = "0:0:0"; httpLib: HttpRequestLibrary; + cacheEvictor: CacheEvictor; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = cacheEvictor ?? nullEvictor; } isCompatible(version: string): boolean { @@ -195,8 +202,10 @@ export class TalerBankConversionHttpClient { body, }); switch (resp.status) { - case HttpStatusCode.NoContent: + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess(TalerBankConversionCacheEviction.UPDATE_RATE); return opEmptySuccess(resp); + } case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotImplemented: diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index 40e29dcef..4131edb11 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -38,11 +38,6 @@ import { opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; -import { TalerAuthenticationHttpClient } from "./authentication.js"; -import { TalerBankConversionHttpClient } from "./bank-conversion.js"; -import { TalerBankIntegrationHttpClient } from "./bank-integration.js"; -import { TalerRevenueHttpClient } from "./bank-revenue.js"; -import { TalerWireGatewayHttpClient } from "./bank-wire.js"; import { AccessToken, PaginationParams, @@ -66,9 +61,11 @@ import { codecForWithdrawalPublicInfo, } from "./types.js"; import { + CacheEvictor, addLongPollingParam, addPaginationParams, makeBearerTokenAuthHeader, + nullEvictor, } from "./utils.js"; export type TalerCoreBankResultByMethod< @@ -78,25 +75,38 @@ export type TalerCoreBankErrorsByMethod< prop extends keyof TalerCoreBankHttpClient, > = FailCasesByMethod; -/** - * Protocol version spoken with the core bank. - * - * Endpoint must be ordered in the same way that in the docs - * Response code (http and taler) must have the same order that in the docs - * That way is easier to see changes - * - * Uses libtool's current:revision:age versioning. - */ -export class TalerCoreBankHttpClient { +export enum TalerCoreBankCacheEviction { + DELELE_ACCOUNT, + CREATE_ACCOUNT, + UPDATE_ACCOUNT, + UPDATE_PASSWORD, + CREATE_TRANSACTION, + CONFIRM_WITHDRAWAL, + ABORT_WITHDRAWAL, + CREATE_WITHDRAWAL, + CREATE_CASHOUT, +} + /** + * Protocol version spoken with the core bank. + * + * Endpoint must be ordered in the same way that in the docs + * Response code (http and taler) must have the same order that in the docs + * That way is easier to see changes + * + * Uses libtool's current:revision:age versioning. + */ + export class TalerCoreBankHttpClient { public readonly PROTOCOL_VERSION = "4:0:0"; httpLib: HttpRequestLibrary; - + cacheEvictor: CacheEvictor; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = cacheEvictor ?? nullEvictor; } isCompatible(version: string): boolean { @@ -142,8 +152,10 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: + case HttpStatusCode.Ok: { + await this.cacheEvictor.notifySuccess(TalerCoreBankCacheEviction.CREATE_ACCOUNT) return opSuccessFromHttp(resp, codecForRegisterAccountResponse()); + } case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: @@ -948,47 +960,47 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api * */ - getIntegrationAPI(): TalerBankIntegrationHttpClient { - const url = new URL(`taler-integration/`, this.baseUrl); - return new TalerBankIntegrationHttpClient(url.href, this.httpLib); + getIntegrationAPI(): string { + return new URL(`taler-integration/`, this.baseUrl).href; + // return new TalerBankIntegrationHttpClient(url.href, this.httpLib); } /** * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api * */ - getWireGatewayAPI(username: string): TalerWireGatewayHttpClient { - const url = new URL( + getWireGatewayAPI(username: string): string { + return new URL( `accounts/${username}/taler-wire-gateway/`, this.baseUrl, - ); - return new TalerWireGatewayHttpClient(url.href, username, this.httpLib); + ).href; + // return new TalerWireGatewayHttpClient(url.href, username, this.httpLib); } /** * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api * */ - getRevenueAPI(username: string): TalerRevenueHttpClient { - const url = new URL(`accounts/${username}/taler-revenue/`, this.baseUrl); - return new TalerRevenueHttpClient(url.href, username, this.httpLib); + getRevenueAPI(username: string): string { + return new URL(`accounts/${username}/taler-revenue/`, this.baseUrl).href; + // return new TalerRevenueHttpClient(url.href, username, this.httpLib); } /** * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token * */ - getAuthenticationAPI(username: string): TalerAuthenticationHttpClient { - const url = new URL(`accounts/${username}/`, this.baseUrl); - return new TalerAuthenticationHttpClient(url.href, username, this.httpLib); + getAuthenticationAPI(username: string): string { + return new URL(`accounts/${username}/`, this.baseUrl).href; + // return new TalerAuthenticationHttpClient(url.href, username, this.httpLib); } /** * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token * */ - getConversionInfoAPI(): TalerBankConversionHttpClient { - const url = new URL(`conversion-info/`, this.baseUrl); - return new TalerBankConversionHttpClient(url.href, this.httpLib); + getConversionInfoAPI(): string { + return new URL(`conversion-info/`, this.baseUrl).href; + // TalerBankConversionHttpClient } } diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts index 7abedae63..2b8920b66 100644 --- a/packages/taler-util/src/http-client/utils.ts +++ b/packages/taler-util/src/http-client/utils.ts @@ -65,3 +65,11 @@ export function addLongPollingParam(url: URL, param?: LongPollParams) { url.searchParams.set("long_poll_ms", String(param.timeoutMs)); } } + +export interface CacheEvictor { + notifySuccess: (op: T) => Promise; +} + +export const nullEvictor: CacheEvictor = { + notifySuccess: () => Promise.resolve() +} diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index 28909facb..74ef9e8e6 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -19,6 +19,7 @@ export * from "./errors.js"; export { fnutil } from "./fnutils.js"; export * from "./helpers.js"; export * from "./http-client/bank-conversion.js"; +export * from "./http-client/authentication.js"; export * from "./http-client/bank-core.js"; export * from "./http-client/bank-integration.js"; export * from "./http-client/bank-revenue.js"; -- cgit v1.2.3