diff options
author | Sebastian <sebasjm@gmail.com> | 2024-04-04 16:24:55 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-04-04 16:25:08 -0300 |
commit | 072ac43b9f69807b8514eb11f8214637561a2573 (patch) | |
tree | b5b464682589f2b80dc4d8f547e9d549a4df353a /packages | |
parent | 01838bfcc74a2e4e828885d5ab0d6f64cc96f328 (diff) | |
download | wallet-core-072ac43b9f69807b8514eb11f8214637561a2573.tar.xz |
fix some API differences including whatwg-url params
Diffstat (limited to 'packages')
70 files changed, 638 insertions, 507 deletions
diff --git a/packages/aml-backoffice-ui/src/context/config.ts b/packages/aml-backoffice-ui/src/context/config.ts index 0ea491ca4..7004225eb 100644 --- a/packages/aml-backoffice-ui/src/context/config.ts +++ b/packages/aml-backoffice-ui/src/context/config.ts @@ -65,7 +65,9 @@ export const ExchangeApiProvider = ({ useEffect(() => { api.getConfig() .then((resp) => { - if (api.isCompatible(resp.body.version)) { + if (resp.type === "fail") { + setChecked({ type: "error", error: TalerError.fromUncheckedDetail(resp.detail) }); + }else if (api.isCompatible(resp.body.version)) { setChecked({ type: "ok", config: resp.body }); } else { setChecked({ type: "incompatible", result: resp.body, supported: api.PROTOCOL_VERSION }) diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx index 88580a4ce..faef0ca54 100644 --- a/packages/aml-backoffice-ui/src/pages/Cases.tsx +++ b/packages/aml-backoffice-ui/src/pages/Cases.tsx @@ -1,168 +1,198 @@ -import { HttpStatusCode, TalerError, TalerExchangeApi, TranslatedString, assertUnreachable } from "@gnu-taler/taler-util"; -import { ErrorLoading, Loading, createNewForm, useTranslationContext } from "@gnu-taler/web-util/browser"; +/* + This file is part of GNU Taler + (C) 2022 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/> + */ +import { + HttpStatusCode, + TalerError, + TalerExchangeApi, + assertUnreachable +} from "@gnu-taler/taler-util"; +import { + ErrorLoading, + Loading, + createNewForm, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useCases } from "../hooks/useCases.js"; import { Pages } from "../pages.js"; -import { Officer } from "./Officer.js"; import { amlStateConverter } from "../utils/converter.js"; import { AmlExchangeBackend } from "../utils/types.js"; +import { Officer } from "./Officer.js"; -export function CasesUI({ records, filter, onChangeFilter, onFirstPage, onNext }: { onFirstPage?: () => void, onNext?: () => void, filter: AmlExchangeBackend.AmlState, onChangeFilter: (f: AmlExchangeBackend.AmlState) => void, records: TalerExchangeApi.AmlRecord[] }): VNode { +export function CasesUI({ + records, + filter, + onChangeFilter, + onFirstPage, + onNext, +}: { + onFirstPage?: () => void; + onNext?: () => void; + filter: AmlExchangeBackend.AmlState; + onChangeFilter: (f: AmlExchangeBackend.AmlState) => void; + records: TalerExchangeApi.AmlRecord[]; +}): VNode { const { i18n } = useTranslationContext(); const form = createNewForm<{ state: AmlExchangeBackend.AmlState }>(); - return <div> - <div class="sm:flex sm:items-center"> - <div class="px-2 sm:flex-auto"> - <h1 class="text-base font-semibold leading-6 text-gray-900"> - <i18n.Translate> - Cases - </i18n.Translate> - </h1> - <p class="mt-2 text-sm text-gray-700 w-80"> - <i18n.Translate> - A list of all the account with the status - </i18n.Translate> - </p> - </div> - <div class="px-2"> - <form.Provider - initial={{ state: filter }} - onUpdate={(v) => { - onChangeFilter(v.state ?? filter); - }} - onSubmit={(v) => { }} - > - <form.InputChoiceHorizontal - name="state" - label={i18n.str`Filter`} - converter={amlStateConverter} - choices={[ - { - label: i18n.str`Pending`, - value: AmlExchangeBackend.AmlState.pending, - }, - { - label: i18n.str`Frozen`, - value: AmlExchangeBackend.AmlState.frozen, - }, - { - label: i18n.str`Normal`, - value: AmlExchangeBackend.AmlState.normal, - }, - ]} - /> - - </form.Provider> + return ( + <div> + <div class="sm:flex sm:items-center"> + <div class="px-2 sm:flex-auto"> + <h1 class="text-base font-semibold leading-6 text-gray-900"> + <i18n.Translate>Cases</i18n.Translate> + </h1> + <p class="mt-2 text-sm text-gray-700 w-80"> + <i18n.Translate> + A list of all the account with the status + </i18n.Translate> + </p> + </div> + <div class="px-2"> + <form.Provider + initial={{ state: filter }} + onUpdate={(v) => { + onChangeFilter(v.state ?? filter); + }} + onSubmit={(_v) => {}} + > + <form.InputChoiceHorizontal + name="state" + label={i18n.str`Filter`} + converter={amlStateConverter} + choices={[ + { + label: i18n.str`Pending`, + value: AmlExchangeBackend.AmlState.pending, + }, + { + label: i18n.str`Frozen`, + value: AmlExchangeBackend.AmlState.frozen, + }, + { + label: i18n.str`Normal`, + value: AmlExchangeBackend.AmlState.normal, + }, + ]} + /> + </form.Provider> + </div> </div> - </div> - <div class="mt-8 flow-root"> - <div class="overflow-x-auto"> - {!records.length ? ( - <div>empty result </div> - ) : ( - <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> - <table class="min-w-full divide-y divide-gray-300"> - <thead> - <tr> - <th - scope="col" - class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-80" - > - <i18n.Translate> - Account Id - </i18n.Translate> - </th> - <th - scope="col" - class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40" - > - <i18n.Translate> - Status - </i18n.Translate> - </th> - <th - scope="col" - class="sm:hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40" - > - <i18n.Translate> - Threshold - </i18n.Translate> - </th> - </tr> - </thead> - <tbody class="divide-y divide-gray-200 bg-white"> - {records.map((r) => { - return ( - <tr class="hover:bg-gray-100 "> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> - <div class="text-gray-900"> - <a - href={Pages.account.url({ account: r.h_payto })} - class="text-indigo-600 hover:text-indigo-900" - > - {r.h_payto.substring(0, 16)}... - </a> - </div> - </td> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500"> - {((state: AmlExchangeBackend.AmlState): VNode => { - switch (state) { - case AmlExchangeBackend.AmlState.normal: { - return ( - <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20"> - Normal - </span> - ); - } - case AmlExchangeBackend.AmlState.pending: { - return ( - <span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20"> - Pending - </span> - ); + <div class="mt-8 flow-root"> + <div class="overflow-x-auto"> + {!records.length ? ( + <div>empty result </div> + ) : ( + <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"> + <table class="min-w-full divide-y divide-gray-300"> + <thead> + <tr> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-80" + > + <i18n.Translate>Account Id</i18n.Translate> + </th> + <th + scope="col" + class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40" + > + <i18n.Translate>Status</i18n.Translate> + </th> + <th + scope="col" + class="sm:hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 w-40" + > + <i18n.Translate>Threshold</i18n.Translate> + </th> + </tr> + </thead> + <tbody class="divide-y divide-gray-200 bg-white"> + {records.map((r) => { + return ( + <tr key={r.h_payto} class="hover:bg-gray-100 "> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500 "> + <div class="text-gray-900"> + <a + href={Pages.account.url({ account: r.h_payto })} + class="text-indigo-600 hover:text-indigo-900" + > + {r.h_payto.substring(0, 16)}... + </a> + </div> + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500"> + {((state: AmlExchangeBackend.AmlState): VNode => { + switch (state) { + case AmlExchangeBackend.AmlState.normal: { + return ( + <span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20"> + Normal + </span> + ); + } + case AmlExchangeBackend.AmlState.pending: { + return ( + <span class="inline-flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-700 ring-1 ring-inset ring-green-600/20"> + Pending + </span> + ); + } + case AmlExchangeBackend.AmlState.frozen: { + return ( + <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20"> + Frozen + </span> + ); + } } - case AmlExchangeBackend.AmlState.frozen: { - return ( - <span class="inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-green-600/20"> - Frozen - </span> - ); - } - } - })(r.current_state)} - </td> - <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900"> - {r.threshold} - </td> - </tr> - ); - })} - </tbody> - </table> - <Pagination onFirstPage={onFirstPage} onNext={onNext} /> - </div> - )} + })(r.current_state)} + </td> + <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-900"> + {r.threshold} + </td> + </tr> + ); + })} + </tbody> + </table> + <Pagination onFirstPage={onFirstPage} onNext={onNext} /> + </div> + )} + </div> </div> </div> - </div> - + ); } - export function Cases() { - const [stateFilter, setStateFilter] = useState(AmlExchangeBackend.AmlState.pending); + const [stateFilter, setStateFilter] = useState( + AmlExchangeBackend.AmlState.pending, + ); const list = useCases(stateFilter); if (!list) { - return <Loading /> + return <Loading />; } if (list instanceof TalerError) { - return <ErrorLoading error={list} /> + return <ErrorLoading error={list} />; } if (list.data.type === "fail") { @@ -170,34 +200,81 @@ export function Cases() { case HttpStatusCode.Unauthorized: case HttpStatusCode.Forbidden: case HttpStatusCode.NotFound: - case HttpStatusCode.Conflict: return <Officer /> - default: assertUnreachable(list.data) + case HttpStatusCode.Conflict: + return <Officer />; + default: + assertUnreachable(list.data); } } - const { records } = list.data.body + const { records } = list.data.body; - return <CasesUI - records={records} - onFirstPage={list.pagination && !list.pagination.isFirstPage ? list.pagination.reset : undefined} - onNext={list.pagination && !list.pagination.isLastPage ? list.pagination.loadMore : undefined} - filter={stateFilter} - onChangeFilter={setStateFilter} - /> + return ( + <CasesUI + records={records} + onFirstPage={ + list.pagination && !list.pagination.isFirstPage + ? list.pagination.reset + : undefined + } + onNext={ + list.pagination && !list.pagination.isLastPage + ? list.pagination.loadMore + : undefined + } + filter={stateFilter} + onChangeFilter={setStateFilter} + /> + ); } -export const PeopleIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> - <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /> -</svg> +export const PeopleIcon = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + class="w-6 h-6" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" + /> + </svg> +); -export const HomeIcon = () => <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> - <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" /> -</svg> +export const HomeIcon = () => ( + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + stroke-width="1.5" + stroke="currentColor" + class="w-6 h-6" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" + /> + </svg> +); -function Pagination({ onFirstPage, onNext }: { onFirstPage?: () => void, onNext?: () => void, }) { - const { i18n } = useTranslationContext() +function Pagination({ + onFirstPage, + onNext, +}: { + onFirstPage?: () => void; + onNext?: () => void; +}) { + const { i18n } = useTranslationContext(); return ( - <nav class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" aria-label="Pagination"> + <nav + class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg" + aria-label="Pagination" + > <div class="flex flex-1 justify-between sm:justify-end"> <button class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" @@ -215,6 +292,5 @@ function Pagination({ onFirstPage, onNext }: { onFirstPage?: () => void, onNext? </button> </div> </nav> - - ) + ); } diff --git a/packages/bank-ui/src/context/config.ts b/packages/bank-ui/src/context/config.ts index 9522c72bc..342a65c4f 100644 --- a/packages/bank-ui/src/context/config.ts +++ b/packages/bank-ui/src/context/config.ts @@ -125,7 +125,9 @@ export const BankCoreApiProvider = ({ bankClient .getConfig() .then((resp) => { - if (bankClient.isCompatible(resp.body.version)) { + if (resp.type === "fail") { + setChecked({ type: "error", error: TalerError.fromUncheckedDetail(resp.detail) }); + } else if (bankClient.isCompatible(resp.body.version)) { setChecked({ type: "ok", config: resp.body, hints: [] }); } else { // this API supports version 3.0.3 diff --git a/packages/bank-ui/src/pages/admin/AdminHome.tsx b/packages/bank-ui/src/pages/admin/AdminHome.tsx index 7cdbdb450..acae09b40 100644 --- a/packages/bank-ui/src/pages/admin/AdminHome.tsx +++ b/packages/bank-ui/src/pages/admin/AdminHome.tsx @@ -31,15 +31,7 @@ import { } from "@gnu-taler/web-util/browser"; import { format, - getDaysInMonth, - getHours, - getMonth, - getYear, - setDate, - setHours, - setMonth, - setYear, - sub, + sub } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; diff --git a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx index cb4442897..2a24dfbe2 100644 --- a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx +++ b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx @@ -20,10 +20,10 @@ */ import { - useTranslationContext + useMerchantApiContext, + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { useSessionContext } from "../../context/session.js"; import { Entity } from "../../paths/admin/create/CreatePage.js"; import { Input } from "../form/Input.js"; import { InputDuration } from "../form/InputDuration.js"; @@ -42,15 +42,13 @@ export function DefaultInstanceFormFields({ showId: boolean; }): VNode { const { i18n } = useTranslationContext(); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); return ( <Fragment> {showId && ( <InputWithAddon<Entity> name="id" - addonBefore={new URL("instances/", backendUrl).href} + addonBefore={new URL("instances/", backendUrl.href).href} readonly={readonlyId} label={i18n.str`Identifier`} tooltip={i18n.str`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx index d6a9308bf..9875ce42e 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -40,12 +40,15 @@ export function Sidebar({ mobile }: Props): VNode { const { i18n } = useTranslationContext(); const kycStatus = useInstanceKYCDetails(); - const needKYC = kycStatus !== undefined && !(kycStatus instanceof TalerError) && kycStatus.type === "ok" && !!kycStatus.body; + const needKYC = + kycStatus !== undefined && + !(kycStatus instanceof TalerError) && + kycStatus.type === "ok" && + !!kycStatus.body; const { state, logOut } = useSessionContext(); const isLoggedIn = state.status === "loggedIn"; const hasToken = isLoggedIn && state.token !== undefined; - const backendURL = state.backendUrl; - const { config } = useMerchantApiContext(); + const { config, url: backendURL } = useMerchantApiContext(); return ( <aside @@ -212,7 +215,7 @@ export function Sidebar({ mobile }: Props): VNode { <i class="mdi mdi-web" /> </span> <span class="menu-item-label"> - {new URL(backendURL).hostname} + {backendURL.hostname} </span> </div> </li> diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx index 468e5f635..781d2de2c 100644 --- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -19,13 +19,14 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { AmountString, TalerMerchantApi } from "@gnu-taler/taler-util"; import { + useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { h } from "preact"; import { useCallback, useEffect, useState } from "preact/hooks"; import * as yup from "yup"; -import { useSessionContext } from "../../context/session.js"; import { ProductCreateSchema as createSchema, ProductUpdateSchema as updateSchema, @@ -38,7 +39,6 @@ import { InputNumber } from "../form/InputNumber.js"; import { InputStock, Stock } from "../form/InputStock.js"; import { InputTaxes } from "../form/InputTaxes.js"; import { InputWithAddon } from "../form/InputWithAddon.js"; -import { AmountString, TalerMerchantApi } from "@gnu-taler/taler-util"; type Entity = TalerMerchantApi.ProductDetail & { product_id: string }; @@ -84,11 +84,11 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { } } const hasErrors = Object.keys(errors).some( - (k) => (errors as any)[k] !== undefined, + (k) => (errors as Record<string, unknown>)[k] !== undefined, ); const submit = useCallback((): Entity | undefined => { - const stock: Stock = (value as any).stock; + const stock = (value).stock; if (!stock) { value.total_stock = -1; @@ -101,7 +101,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { : stock.nextRestock; value.address = stock.address; } - delete (value as any).stock; + delete value.stock; if (typeof value.minimum_age !== "undefined" && value.minimum_age < 1) { delete value.minimum_age; @@ -116,9 +116,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { onSubscribe(hasErrors ? undefined : submit); }, [submit, hasErrors]); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); const { i18n } = useTranslationContext(); return ( @@ -132,7 +130,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { {alreadyExist ? undefined : ( <InputWithAddon<Entity> name="product_id" - addonBefore={new URL("product/", backendUrl).href} + addonBefore={new URL("product/", backendUrl.href).href} label={i18n.str`ID`} tooltip={i18n.str`product identification to use in URLs (for internal use only)`} /> diff --git a/packages/merchant-backoffice-ui/src/context/session.ts b/packages/merchant-backoffice-ui/src/context/session.ts index 98cb27400..7a5ef33d7 100644 --- a/packages/merchant-backoffice-ui/src/context/session.ts +++ b/packages/merchant-backoffice-ui/src/context/session.ts @@ -39,7 +39,6 @@ export type SessionState = LoggedIn | LoggedOut | Expired; interface LoggedIn { status: "loggedIn"; - backendUrl: string; isAdmin: boolean; instance: string; token: AccessToken | undefined; @@ -52,7 +51,6 @@ interface Impersonate { } interface Expired { status: "expired"; - backendUrl: string; isAdmin: boolean; instance: string; token?: undefined; @@ -60,7 +58,6 @@ interface Expired { } interface LoggedOut { status: "loggedOut"; - backendUrl: string; instance: string; isAdmin: boolean; token?: undefined; @@ -69,7 +66,6 @@ interface LoggedOut { export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> => buildCodecForObject<LoggedIn>() .property("status", codecForConstString("loggedIn")) - .property("backendUrl", codecForString()) .property("instance", codecForString()) .property("impersonate", codecOptional(codecForImpresonate())) .property("token", codecOptional(codecForString() as Codec<AccessToken>)) @@ -79,7 +75,6 @@ export const codecForSessionStateLoggedIn = (): Codec<LoggedIn> => export const codecForSessionStateExpired = (): Codec<Expired> => buildCodecForObject<Expired>() .property("status", codecForConstString("expired")) - .property("backendUrl", codecForString()) .property("instance", codecForString()) .property("impersonate", codecOptional(codecForImpresonate())) .property("isAdmin", codecForBoolean()) @@ -88,7 +83,6 @@ export const codecForSessionStateExpired = (): Codec<Expired> => export const codecForSessionStateLoggedOut = (): Codec<LoggedOut> => buildCodecForObject<LoggedOut>() .property("status", codecForConstString("loggedOut")) - .property("backendUrl", codecForString()) .property("instance", codecForString()) .property("isAdmin", codecForBoolean()) .build("SessionState.LoggedOut"); @@ -121,7 +115,6 @@ export const defaultState = (url: URL): SessionState => { return { status: "loggedIn", instance, - backendUrl: url.href, isAdmin: instance === DEFAULT_ADMIN_USERNAME, token: undefined, impersonate: undefined, @@ -151,7 +144,7 @@ export interface SessionStateHandler { * from loggedIn to impersonate * @param info */ - impersonate(info: { instance: string; token?: AccessToken }): void; + impersonate(info: { instance: string; baseUrl: URL, token?: AccessToken }): void; } const SESSION_STATE_KEY = buildStorageKey( @@ -169,20 +162,19 @@ export const INSTANCE_ID_LOOKUP = /\/instances\/([^/]*)\/?$/; * base URL. */ export function useSessionContext(): SessionStateHandler { - const { url } = useMerchantApiContext(); + const { url: merchantUrl, changeBackend } = useMerchantApiContext(); const { value: state, update } = useLocalStorage( SESSION_STATE_KEY, - defaultState(url), + defaultState(merchantUrl), ); return { state, logOut() { - const instance = inferInstanceName(url); + const instance = inferInstanceName(merchantUrl); const nextState: SessionState = { status: "loggedOut", - backendUrl: url.href, instance, isAdmin: instance === DEFAULT_ADMIN_USERNAME, }; @@ -196,9 +188,10 @@ export function useSessionContext(): SessionStateHandler { if (state.impersonate === undefined) { return; } + const newURL = new URL(`/`, state.impersonate.originalBackendUrl); + changeBackend(newURL); const nextState: SessionState = { status: "loggedIn", - backendUrl: state.impersonate.originalBackendUrl, isAdmin: state.impersonate.originalInstance === DEFAULT_ADMIN_USERNAME, instance: state.impersonate.originalInstance, token: state.impersonate.originalToken, @@ -211,16 +204,15 @@ export function useSessionContext(): SessionStateHandler { // can't impersonate if not loggedin return; } + changeBackend(info.baseUrl); const nextState: SessionState = { status: "loggedIn", - backendUrl: new URL(`instances/${info.instance}`, state.backendUrl) - .href, isAdmin: info.instance === DEFAULT_ADMIN_USERNAME, instance: info.instance, // FIXME: bank and merchant should have consistent behavior token: info.token?.substring("secret-token:".length) as AccessToken, impersonate: { - originalBackendUrl: state.backendUrl, + originalBackendUrl: merchantUrl.href, originalToken: state.token, originalInstance: state.instance, }, diff --git a/packages/merchant-backoffice-ui/src/hooks/bank.ts b/packages/merchant-backoffice-ui/src/hooks/bank.ts index e1f2638ed..513314f17 100644 --- a/packages/merchant-backoffice-ui/src/hooks/bank.ts +++ b/packages/merchant-backoffice-ui/src/hooks/bank.ts @@ -17,7 +17,6 @@ import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; import { useState } from "preact/hooks"; -import { PAGE_SIZE } from "../utils/constants.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 import { AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util"; @@ -38,12 +37,12 @@ export function revalidateInstanceBankAccounts() { } export function useInstanceBankAccounts() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, bid]: [AccessToken, string]) { - return await management.listBankAccounts(token, { + return await instance.listBankAccounts(token, { limit: 5, offset: bid, order: "dec", @@ -55,28 +54,6 @@ export function useInstanceBankAccounts() { TalerHttpError >([session.token, offset, "listBankAccounts"], fetcher); - const isLastPage = - data && data.type === "ok" && data.body.accounts.length <= PAGE_SIZE; - const isFirstPage = !offset; - - const result = - data && data.type == "ok" ? structuredClone(data.body.accounts) : []; - if (result.length == PAGE_SIZE + 1) { - result.pop(); - } - const pagination = { - result, - isLastPage, - isFirstPage, - loadNext: () => { - if (!result.length) return; - setOffset(result[result.length - 1].h_wire); - }, - loadFirst: () => { - setOffset(undefined); - }, - }; - if (error) return error; if (data === undefined) return undefined; if (data.type !== "ok") return data; @@ -93,10 +70,10 @@ export function revalidateBankAccountDetails() { } export function useBankAccountDetails(h_wire: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([token, wireId]: [AccessToken, string]) { - return await management.getBankAccountDetails(token, wireId); + return await instance.getBankAccountDetails(token, wireId); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts index 64f534a06..f409592b0 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.test.ts @@ -20,6 +20,7 @@ */ import { TalerMerchantApi } from "@gnu-taler/taler-util"; +import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; import * as tests from "@gnu-taler/web-util/testing"; import { expect } from "chai"; import { @@ -36,7 +37,6 @@ import { API_UPDATE_CURRENT_INSTANCE_AUTH, API_UPDATE_INSTANCE_BY_ID, } from "./urls.js"; -import { useMerchantApiContext } from "@gnu-taler/web-util/browser"; describe("instance api interaction with details", () => { it("should evict cache when updating an instance", async () => { @@ -81,7 +81,7 @@ describe("instance api interaction with details", () => { name: "other_name", } as TalerMerchantApi.QueryInstancesResponse, }); - api.management.updateCurrentInstance(undefined, { + api.instance.updateCurrentInstance(undefined, { name: "other_name", } as TalerMerchantApi.InstanceReconfigurationMessage); }, @@ -242,7 +242,7 @@ describe("instance api interaction with details", () => { } as TalerMerchantApi.QueryInstancesResponse, }); - api.management.updateCurrentInstanceAuthentication(undefined, { + api.instance.updateCurrentInstanceAuthentication(undefined, { method: "external" }); }, @@ -380,7 +380,7 @@ describe("instance admin api interaction with listing", () => { }, }); - api.management.createInstance(undefined, { + api.instance.createInstance(undefined, { name: "other_name", } as TalerMerchantApi.InstanceConfigurationMessage) }, @@ -470,7 +470,7 @@ describe("instance admin api interaction with listing", () => { }, }); - api.management.deleteInstance(undefined, "the_id"); + api.instance.deleteInstance(undefined, "the_id"); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -628,7 +628,7 @@ describe("instance admin api interaction with listing", () => { }, }); - api.management.deleteInstance(undefined, "the_id", { purge: true }) + api.instance.deleteInstance(undefined, "the_id", { purge: true }) }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -712,7 +712,7 @@ describe("instance management api interaction with listing", () => { }, }); - api.management.updateCurrentInstance(undefined, { + api.instance.updateCurrentInstance(undefined, { name: "other_name", } as TalerMerchantApi.InstanceConfigurationMessage); }, diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts index cc907bd8f..1fa84c9d9 100644 --- a/packages/merchant-backoffice-ui/src/hooks/instance.ts +++ b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -33,10 +33,10 @@ export function revalidateInstanceDetails() { } export function useInstanceDetails() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([token]: [AccessToken]) { - return await management.getCurrentInstanceDetails(token); + return await instance.getCurrentInstanceDetails(token); } const { data, error } = useSWR< @@ -58,10 +58,10 @@ export function revalidateInstanceKYCDetails() { } export function useInstanceKYCDetails() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([token]: [AccessToken]) { - return await management.getCurrentIntanceKycStatus(token, {}); + return await instance.getCurrentIntanceKycStatus(token, {}); } const { data, error } = useSWR< @@ -85,10 +85,10 @@ export function revalidateManagedInstanceDetails() { } export function useManagedInstanceDetails(instanceId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([token, instanceId]: [AccessToken, string]) { - return await management.getInstanceDetails(token, instanceId); + return await instance.getInstanceDetails(token, instanceId); } const { data, error } = useSWR< @@ -110,10 +110,10 @@ export function revalidateBackendInstances() { } export function useBackendInstances() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([token]: [AccessToken]) { - return await management.listInstances(token); + return await instance.listInstances(token); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/hooks/order.test.ts b/packages/merchant-backoffice-ui/src/hooks/order.test.ts index 243415bdd..1aa2fcf0a 100644 --- a/packages/merchant-backoffice-ui/src/hooks/order.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/order.test.ts @@ -86,7 +86,7 @@ describe("order api interaction with listing", () => { }, }); - api.management.createOrder(undefined, { + api.instance.createOrder(undefined, { order: { amount: "ARS:12" as AmountString, summary: "pay me" }, }) }, @@ -171,7 +171,7 @@ describe("order api interaction with listing", () => { }, }); - api.management.addRefund(undefined, "1", { + api.instance.addRefund(undefined, "1", { reason: "double pay", refund: "EUR:1" as AmountString, }) @@ -247,7 +247,7 @@ describe("order api interaction with listing", () => { }, }); - api.management.deleteOrder(undefined, "1") + api.instance.deleteOrder(undefined, "1") }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -316,7 +316,7 @@ describe("order api interaction with details", () => { } as unknown as TalerMerchantApi.CheckPaymentPaidResponse, }); - api.management.addRefund(undefined, "1", { + api.instance.addRefund(undefined, "1", { reason: "double pay", refund: "EUR:1" as AmountString, }) @@ -386,7 +386,7 @@ describe("order api interaction with details", () => { } as unknown as TalerMerchantApi.CheckPaymentPaidResponse, }); - api.management.forgetOrder(undefined, "1", { + api.instance.forgetOrder(undefined, "1", { fields: ["$.summary"], }) }, diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts b/packages/merchant-backoffice-ui/src/hooks/order.ts index 47ddf1c38..b1805f6e3 100644 --- a/packages/merchant-backoffice-ui/src/hooks/order.ts +++ b/packages/merchant-backoffice-ui/src/hooks/order.ts @@ -36,10 +36,10 @@ export function revalidateOrderDetails() { } export function useOrderDetails(oderId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([dId, token]: [string, AccessToken]) { - return await management.getOrderDetails(token, dId); + return await instance.getOrderDetails(token, dId); } const { data, error } = useSWR< @@ -65,12 +65,12 @@ export function useInstanceOrders( updatePosition: (d: string | undefined) => void = () => { }, ) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); // const [offset, setOffset] = useState<string | undefined>(args?.position); async function fetcher([token, o, p, r, w, d]: [AccessToken, string, boolean, boolean, boolean, AbsoluteTime]) { - return await management.listOrders(token, { + return await instance.listOrders(token, { limit: PAGE_SIZE, offset: o, order: "dec", diff --git a/packages/merchant-backoffice-ui/src/hooks/otp.ts b/packages/merchant-backoffice-ui/src/hooks/otp.ts index 69e4a0f4f..898a27a69 100644 --- a/packages/merchant-backoffice-ui/src/hooks/otp.ts +++ b/packages/merchant-backoffice-ui/src/hooks/otp.ts @@ -35,12 +35,12 @@ export function revalidateInstanceOtpDevices() { } export function useInstanceOtpDevices() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, bid]: [AccessToken, string]) { - return await management.listOtpDevices(token, { + return await instance.listOtpDevices(token, { limit: PAGE_SIZE, offset: bid, order: "dec", @@ -68,10 +68,10 @@ export function revalidateOtpDeviceDetails() { } export function useOtpDeviceDetails(deviceId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([dId, token]: [string, AccessToken]) { - return await management.getOtpDeviceDetails(token, dId); + return await instance.getOtpDeviceDetails(token, dId); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/hooks/product.test.ts b/packages/merchant-backoffice-ui/src/hooks/product.test.ts index 1be00201a..39281241c 100644 --- a/packages/merchant-backoffice-ui/src/hooks/product.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/product.test.ts @@ -99,7 +99,7 @@ describe("product api interaction with listing", () => { } as TalerMerchantApi.ProductDetail, }); - api.management.addProduct(undefined, { + api.instance.addProduct(undefined, { price: "ARS:23", } as any); }, @@ -187,7 +187,7 @@ describe("product api interaction with listing", () => { } as TalerMerchantApi.ProductDetail, }); - api.management.updateProduct(undefined, "1234", { + api.instance.updateProduct(undefined, "1234", { price: "ARS:13", } as any); }, @@ -267,7 +267,7 @@ describe("product api interaction with listing", () => { price: "ARS:12", } as TalerMerchantApi.ProductDetail, }); - api.management.deleteProduct(undefined, "2345"); + api.instance.deleteProduct(undefined, "2345"); }, ({ query, api }) => { expect(env.assertJustExpectedRequestWereMade()).deep.eq({ @@ -337,7 +337,7 @@ describe("product api interaction with details", () => { } as TalerMerchantApi.ProductDetail, }); - api.management.updateProduct(undefined, "12", { + api.instance.updateProduct(undefined, "12", { description: "other description", } as any); }, diff --git a/packages/merchant-backoffice-ui/src/hooks/product.ts b/packages/merchant-backoffice-ui/src/hooks/product.ts index 6721136a5..cfbd4a653 100644 --- a/packages/merchant-backoffice-ui/src/hooks/product.ts +++ b/packages/merchant-backoffice-ui/src/hooks/product.ts @@ -40,14 +40,14 @@ export function revalidateInstanceProducts() { } export function useInstanceProducts() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); const [offset, setOffset] = useState<number | undefined>(); async function fetcher([token, bid]: [AccessToken, number]) { - const list = await management.listProducts(token, { + const list = await instance.listProducts(token, { limit: PAGE_SIZE, - offset: String(bid), + offset: bid === undefined ? undefined: String(bid), order: "dec", }); if (list.type !== "ok") { @@ -55,7 +55,7 @@ export function useInstanceProducts() { } const all: Array<ProductWithId | undefined> = await Promise.all( list.body.products.map(async (c) => { - const r = await management.getProductDetails(token, c.product_id); + const r = await instance.getProductDetails(token, c.product_id); if (r.type === "fail") { return undefined; } @@ -89,10 +89,10 @@ export function revalidateProductDetails() { } export function useProductDetails(productId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([pid, token]: [string, AccessToken]) { - return await management.getProductDetails(token, pid); + return await instance.getProductDetails(token, pid); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/hooks/templates.ts b/packages/merchant-backoffice-ui/src/hooks/templates.ts index 10e480b01..dbea93fdf 100644 --- a/packages/merchant-backoffice-ui/src/hooks/templates.ts +++ b/packages/merchant-backoffice-ui/src/hooks/templates.ts @@ -39,12 +39,12 @@ export function revalidateInstanceTemplates() { } export function useInstanceTemplates() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, bid]: [AccessToken, string]) { - return await management.listTemplates(token, { + return await instance.listTemplates(token, { limit: PAGE_SIZE, offset: bid, order: "dec", @@ -73,10 +73,10 @@ export function revalidateProductDetails() { } export function useTemplateDetails(templateId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([tid, token]: [string, AccessToken]) { - return await management.getTemplateDetails(token, tid); + return await instance.getTemplateDetails(token, tid); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts index b424e9686..d0865d236 100644 --- a/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts +++ b/packages/merchant-backoffice-ui/src/hooks/transfer.test.ts @@ -86,7 +86,7 @@ describe("transfer api interaction with listing", () => { }, }); - api.management.informWireTransfer(undefined, { + api.instance.informWireTransfer(undefined, { wtid: "3", credit_amount: "EUR:1" as AmountString, exchange_url: "exchange.url", diff --git a/packages/merchant-backoffice-ui/src/hooks/transfer.ts b/packages/merchant-backoffice-ui/src/hooks/transfer.ts index 2810a4cba..44068f52d 100644 --- a/packages/merchant-backoffice-ui/src/hooks/transfer.ts +++ b/packages/merchant-backoffice-ui/src/hooks/transfer.ts @@ -43,12 +43,12 @@ export function useInstanceTransfers( updatePosition: (id: string | undefined) => void = (() => { }), ) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); // const [offset, setOffset] = useState<string | undefined>(args?.position); async function fetcher([token, o, p, v]: [AccessToken, string, string, boolean]) { - return await management.listWireTransfers(token, { + return await instance.listWireTransfers(token, { paytoURI: p, verified: v, limit: PAGE_SIZE, diff --git a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts index 5e2e08bcc..c69db6e80 100644 --- a/packages/merchant-backoffice-ui/src/hooks/webhooks.ts +++ b/packages/merchant-backoffice-ui/src/hooks/webhooks.ts @@ -37,12 +37,12 @@ export function revalidateInstanceWebhooks() { } export function useInstanceWebhooks() { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); const [offset, setOffset] = useState<string | undefined>(); async function fetcher([token, bid]: [AccessToken, string]) { - return await management.listWebhooks(token, { + return await instance.listWebhooks(token, { limit: 5, offset: bid, order: "dec", @@ -104,10 +104,10 @@ export function revalidateWebhookDetails() { } export function useWebhookDetails(webhookId: string) { const { state: session } = useSessionContext(); - const { lib: { management } } = useMerchantApiContext(); + const { lib: { instance } } = useMerchantApiContext(); async function fetcher([hookId, token]: [string, AccessToken]) { - return await management.getWebhookDetails(token, hookId); + return await instance.getWebhookDetails(token, hookId); } const { data, error } = useSWR< diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx index 39fdb6bdc..54d947e14 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx @@ -19,9 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode, FunctionalComponent } from "preact"; -import { CreatePage as TestedComponent } from "./CreatePage.js"; import { MerchantApiProviderTesting } from "@gnu-taler/web-util/browser"; +import { FunctionalComponent, h } from "preact"; +import { CreatePage as TestedComponent } from "./CreatePage.js"; export default { title: "Pages/Instance/Create", @@ -40,6 +40,7 @@ function createExample<Props>( <MerchantApiProviderTesting value={{ cancelRequest: () => {}, + changeBackend: () => {}, config: { currency: "ARS", version: "1", diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx index 431015d6f..8ee8608a3 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx @@ -54,7 +54,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { ) => { if (state.status !== "loggedIn") return; try { - await lib.management.createInstance(state.token, d); + await lib.instance.createInstance(state.token, d); if (d.auth.token) { //if auth has been updated, request a new access token const result = await lib.authenticate.createAccessTokenBearer( diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx index 8166dc739..d4258058b 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx @@ -40,6 +40,7 @@ function createExample<Props>( <MerchantApiProviderTesting value={{ cancelRequest: () => {}, + changeBackend: () => {}, config: { currency: "ARS", version: "1", diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx index a03a2659b..923c095d3 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx @@ -20,7 +20,10 @@ */ import { TalerMerchantApi } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { + useMerchantApiContext, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; import { h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; import { useSessionContext } from "../../../context/session.js"; @@ -149,7 +152,8 @@ function Table({ onPurge, }: TableProps): VNode { const { i18n } = useTranslationContext(); - const { impersonate } = useSessionContext() + const { lib } = useMerchantApiContext(); + const { impersonate } = useSessionContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> @@ -198,10 +202,16 @@ function Table({ </td> <td> <a - href={`#/orders?instance=${i.id}`} - onClick={(e) => { - impersonate({instance: i.id}); + href={`#/orders`} + onClick={async (e) => { e.preventDefault(); + const newInstanceApi = lib.subInstanceApi(i.id); + //not checking /config since this comes from instance list + impersonate({ + instance: i.id, + baseUrl: new URL(newInstanceApi.instance.baseUrl), + token: undefined, + }); }} > {i.id} diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx index f26ff8935..7bf64cdbb 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx @@ -91,7 +91,7 @@ export default function Instances({ return; } try { - await lib.management.deleteInstance(state.token, deleting.id); + await lib.instance.deleteInstance(state.token, deleting.id); // pushNotification({message: 'delete_success', type: 'SUCCESS' }) setNotif({ message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, @@ -118,7 +118,7 @@ export default function Instances({ return; } try { - await lib.management.deleteInstance(state.token, purging.id, { purge: true }); + await lib.instance.deleteInstance(state.token, purging.id, { purge: true }); setNotif({ message: i18n.str`Instance '${purging.name}' (ID: ${purging.id}) has been disabled`, type: "SUCCESS", diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx index 0ce126b76..3d27b9a1a 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx @@ -46,7 +46,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: Entity) => { - return api.management.addBankAccount(state.token, request) + return api.instance.addBankAccount(state.token, request) .then(() => { onConfirm() }) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx index 50cf0fe70..4ee68cd80 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/ListPage.tsx @@ -24,12 +24,12 @@ import { h, VNode } from "preact"; import { CardTable } from "./Table.js"; export interface Props { - devices: TalerMerchantApi.BankAccountEntry[]; - onLoadMoreBefore?: () => void; - onLoadMoreAfter?: () => void; + devices: TalerMerchantApi.BankAccountSummaryEntry[]; + // onLoadMoreBefore?: () => void; + // onLoadMoreAfter?: () => void; onCreate: () => void; - onDelete: (e: TalerMerchantApi.BankAccountEntry) => void; - onSelect: (e: TalerMerchantApi.BankAccountEntry) => void; + onDelete: (e: TalerMerchantApi.BankAccountSummaryEntry) => void; + onSelect: (e: TalerMerchantApi.BankAccountSummaryEntry) => void; } export function ListPage({ @@ -37,8 +37,8 @@ export function ListPage({ onCreate, onDelete, onSelect, - onLoadMoreBefore, - onLoadMoreAfter, + // onLoadMoreBefore, + // onLoadMoreAfter, }: Props): VNode { return ( @@ -51,10 +51,10 @@ export function ListPage({ onCreate={onCreate} onDelete={onDelete} onSelect={onSelect} - onLoadMoreBefore={onLoadMoreBefore} - hasMoreBefore={!onLoadMoreBefore} - onLoadMoreAfter={onLoadMoreAfter} - hasMoreAfter={!onLoadMoreAfter} + // onLoadMoreBefore={onLoadMoreBefore} + // hasMoreBefore={!onLoadMoreBefore} + // onLoadMoreAfter={onLoadMoreAfter} + // hasMoreAfter={!onLoadMoreAfter} /> </section> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx index 690e3a2fc..efe484402 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/Table.tsx @@ -24,17 +24,13 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; -type Entity = TalerMerchantApi.BankAccountEntry; +type Entity = TalerMerchantApi.BankAccountSummaryEntry; interface Props { accounts: Entity[]; onDelete: (e: Entity) => void; onSelect: (e: Entity) => void; onCreate: () => void; - onLoadMoreBefore?: () => void; - hasMoreBefore?: boolean; - hasMoreAfter?: boolean; - onLoadMoreAfter?: () => void; } export function CardTable({ @@ -42,10 +38,6 @@ export function CardTable({ onCreate, onDelete, onSelect, - onLoadMoreAfter, - onLoadMoreBefore, - hasMoreAfter, - hasMoreBefore, }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState<string[]>([]); @@ -83,10 +75,6 @@ export function CardTable({ onSelect={onSelect} rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} - onLoadMoreAfter={onLoadMoreAfter} - onLoadMoreBefore={onLoadMoreBefore} - hasMoreAfter={hasMoreAfter} - hasMoreBefore={hasMoreBefore} /> ) : ( <EmptyTable /> @@ -103,20 +91,12 @@ interface TableProps { onDelete: (e: Entity) => void; onSelect: (e: Entity) => void; rowSelectionHandler: StateUpdater<string[]>; - onLoadMoreBefore?: () => void; - hasMoreBefore?: boolean; - hasMoreAfter?: boolean; - onLoadMoreAfter?: () => void; } function Table({ accounts, - onLoadMoreAfter, onDelete, onSelect, - onLoadMoreBefore, - hasMoreAfter, - hasMoreBefore, }: TableProps): VNode { const { i18n } = useTranslationContext(); const emptyList: Record<PaytoType | "unknown", { parsed: PaytoUri, acc: Entity }[]> = { "bitcoin": [], "x-taler-bank": [], "iban": [], "unknown": [], } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx index a9454cd07..ccfab3c45 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/list/index.tsx @@ -81,16 +81,16 @@ export default function ListOtpDevices({ } <ListPage devices={result.body} - onLoadMoreBefore={ - result.isFirstPage ? undefined: result.loadFirst - } - onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext} + // onLoadMoreBefore={ + // result.isFirstPage ? undefined: result.loadFirst + // } + // onLoadMoreAfter={result.isLastPage ? undefined : result.loadNext} onCreate={onCreate} onSelect={(e) => { onSelect(e.h_wire); }} - onDelete={(e: TalerMerchantApi.BankAccountEntry) => { - return api.management.deleteBankAccount(state.token, e.h_wire) + onDelete={(e: TalerMerchantApi.BankAccountSummaryEntry) => { + return api.instance.deleteBankAccount(state.token, e.h_wire) .then(() => setNotif({ message: i18n.str`bank account delete successfully`, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx index 97610e96b..6b8af50a9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx @@ -80,7 +80,7 @@ export default function UpdateValidator({ account={{ ...result.body, id: bid }} onBack={onBack} onUpdate={(data) => { - return api.management.updateBankAccount(state.token, bid, data) + return api.instance.updateBankAccount(state.token, bid, data) .then(onConfirm) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx index bb1ee944b..76e3bf878 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/details/index.tsx @@ -77,7 +77,7 @@ export default function Detail({ return } try { - await lib.management.deleteCurrentInstance(state.token); + await lib.instance.deleteCurrentInstance(state.token); onDelete(); } catch (error) { //FIXME: show message error diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx index 6914b7432..42cb1cb02 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx @@ -40,6 +40,7 @@ function createExample<Props>( <MerchantApiProviderTesting value={{ cancelRequest: () => { }, + changeBackend: () => { }, config: { currency: "ARS", version: "1", diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx index f612389fe..849711df6 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/index.tsx @@ -94,7 +94,7 @@ export default function OrderCreate({ <CreatePage onBack={onBack} onCreate={(request: TalerMerchantApi.PostOrderRequest) => { - lib.management.createOrder(state.token, request) + lib.instance.createOrder(state.token, request) .then((r) => { if (r.type === "ok") { return onConfirm(r.body.order_id) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx index 4ed78b002..4afc40285 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx @@ -25,7 +25,7 @@ import { TalerMerchantApi, stringifyRefundUri, } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { format, formatDistance } from "date-fns"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -38,7 +38,6 @@ import { InputGroup } from "../../../../components/form/InputGroup.js"; import { InputLocation } from "../../../../components/form/InputLocation.js"; import { TextField } from "../../../../components/form/TextField.js"; import { ProductList } from "../../../../components/product/ProductList.js"; -import { useSessionContext } from "../../../../context/session.js"; import { datetimeFormatForSettings, usePreference, @@ -427,12 +426,10 @@ function PaidPage({ }); const [value, valueHandler] = useState<Partial<Paid>>(order); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); const refundurl = stringifyRefundUri({ - merchantBaseUrl: backendUrl, + merchantBaseUrl: backendUrl.href, orderId: order.contract_terms.order_id, }); const refundable = diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx index b232a146b..4785c795d 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx @@ -84,7 +84,7 @@ export default function Update({ oid, onBack }: Props): VNode { if (state.status !== "loggedIn") { return; } - api.management + api.instance .addRefund(state.token, id, value) .then(() => setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx index 165ced3dc..217eb998a 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx @@ -110,7 +110,7 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { <JumpToElementById testIfExist={async (order) => { - const resp = await lib.management.getOrderDetails(state.token, order); + const resp = await lib.instance.getOrderDetails(state.token, order); return resp.type === "ok"; }} onSelect={onSelect} @@ -135,7 +135,7 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { jumpToDate={filter.date} onSelectDate={setNewDate} onCopyURL={async (id) => { - const resp = await lib.management.getOrderDetails(state.token, id); + const resp = await lib.instance.getOrderDetails(state.token, id); if (resp.type === "ok") { if (resp.body.order_status === "unpaid") { copyToClipboard(resp.body.taler_pay_uri); @@ -161,7 +161,7 @@ export default function OrderList({ onCreate, onSelect }: Props): VNode { id={orderToBeRefunded.order_id} onCancel={() => setOrderToBeRefunded(undefined)} onConfirm={(value) => { - lib.management + lib.instance .addRefund(state.token, orderToBeRefunded.order_id, value) .then(() => setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx index b1b4a0cf7..982132057 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/CreatedSuccessfully.tsx @@ -15,7 +15,7 @@ */ import { TalerMerchantApi } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { QR } from "../../../../components/exception/QR.js"; import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js"; @@ -33,11 +33,9 @@ export function CreatedSuccessfully({ onConfirm, }: Props): VNode { const { i18n } = useTranslationContext(); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); const { state } = useSessionContext(); - const issuer = backendUrl; + const issuer = backendUrl.href; const qrText = `otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key}`; const qrTextSafe = `otpauth://totp/${state.instance}/${entity.otp_device_id}?issuer=${issuer}&algorithm=SHA1&digits=8&period=30&secret=${entity.otp_key.substring(0, 6)}...`; diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx index 6ad1295ed..864190c9f 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/create/index.tsx @@ -52,7 +52,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: Entity) => { - return api.management.addOtpDevice(state.token, request) + return api.instance.addOtpDevice(state.token, request) .then((d) => { setCreated(request) }) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx index 6b3eded17..324207f59 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/list/index.tsx @@ -85,7 +85,7 @@ export default function ListOtpDevices({ onCreate, onSelect }: Props): VNode { onSelect(e.otp_device_id); }} onDelete={(e: TalerMerchantApi.OtpDeviceEntry) => { - return lib.management + return lib.instance .deleteOtpDevice(state.token, e.otp_device_id) .then(() => setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx index 4dc3ec67f..5e34e4c8a 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/otp_devices/update/index.tsx @@ -98,7 +98,7 @@ export default function UpdateValidator({ }} onBack={onBack} onUpdate={async (newInfo) => { - return lib.management + return lib.instance .updateOtpDevice(state.token, vid, newInfo) .then((d) => { if (d.type === "ok") { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx index 6cb083025..e1e3c846a 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx @@ -45,7 +45,7 @@ export default function CreateProduct({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: TalerMerchantApi.ProductAddDetail) => { - return lib.management.addProduct(state.token, request) + return lib.instance.addProduct(state.token, request) .then(() => onConfirm()) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx index 73c221662..dfd633150 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx @@ -81,7 +81,7 @@ export default function ProductList({ <JumpToElementById testIfExist={async (id) => { - const resp = await lib.management.getProductDetails(state.token, id); + const resp = await lib.instance.getProductDetails(state.token, id); return resp.type === "ok"; }} onSelect={onSelect} @@ -94,7 +94,7 @@ export default function ProductList({ onCreate={onCreate} onUpdate={async (id, prod) => { try { - await lib.management.updateProduct(state.token, id, prod); + await lib.instance.updateProduct(state.token, id, prod); setNotif({ message: i18n.str`product updated successfully`, type: "SUCCESS", @@ -123,7 +123,7 @@ export default function ProductList({ onCancel={() => setDeleting(null)} onConfirm={async (): Promise<void> => { try { - await lib.management.deleteProduct(state.token, deleting.id); + await lib.instance.deleteProduct(state.token, deleting.id); setNotif({ message: i18n.str`Product "${deleting.description}" (ID: ${deleting.id}) has been deleted`, type: "SUCCESS", diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx index 08b169610..06f813b14 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx @@ -79,7 +79,7 @@ export default function UpdateProduct({ product={{ ...result.body, product_id: pid }} onBack={onBack} onUpdate={(data) => { - return lib.management.updateProduct(state.token, pid, data) + return lib.instance.updateProduct(state.token, pid, data) .then(onConfirm) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx index 82c0d0e53..2ba637f44 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx @@ -28,6 +28,7 @@ import { assertUnreachable, } from "@gnu-taler/taler-util"; import { + useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; @@ -44,7 +45,6 @@ import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js"; import { InputTab } from "../../../../components/form/InputTab.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; -import { useSessionContext } from "../../../../context/session.js"; import { useInstanceOtpDevices } from "../../../../hooks/otp.js"; enum Steps { @@ -73,9 +73,7 @@ interface Props { export function CreatePage({ onCreate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); const devices = useInstanceOtpDevices(); const [state, setState] = useState<Partial<Entity>>({ @@ -201,7 +199,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { > <InputWithAddon<Entity> name="id" - help={new URL(`templates/${state.id ?? ""}`, backendUrl).href} + help={new URL(`templates/${state.id ?? ""}`, backendUrl.href).href} label={i18n.str`Identifier`} tooltip={i18n.str`Name of the template in URLs.`} /> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx index d23afb609..f71ca4794 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx @@ -46,7 +46,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: TalerMerchantApi.TemplateAddDetails) => { - return lib.management.addTemplate(state.token, request) + return lib.instance.addTemplate(state.token, request) .then(() => onConfirm()) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx index 23bc95943..f9ab6678b 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx @@ -85,7 +85,7 @@ export default function ListTemplates({ <JumpToElementById testIfExist={async (id) => { - const resp = await lib.management.getTemplateDetails(state.token, id) + const resp = await lib.instance.getTemplateDetails(state.token, id) return resp.type === "ok" }} onSelect={onSelect} @@ -124,7 +124,7 @@ export default function ListTemplates({ onCancel={() => setDeleting(null)} onConfirm={async (): Promise<void> => { try { - await lib.management.deleteTemplate(state.token, deleting.template_id); + await lib.instance.deleteTemplate(state.token, deleting.template_id); setNotif({ message: i18n.str`Template "${deleting.template_description}" (ID: ${deleting.template_id}) has been deleted`, type: "SUCCESS", diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx index d48e5e956..0749f45d3 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/qr/QrPage.tsx @@ -33,7 +33,6 @@ import { } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; -import { useSessionContext } from "../../../../context/session.js"; type Entity = TalerMerchantApi.UsingTemplateDetails; @@ -45,10 +44,7 @@ interface Props { export function QrPage({ contract, id: templateId, onBack }: Props): VNode { const { i18n } = useTranslationContext(); - const { - state: { backendUrl }, - } = useSessionContext(); - const { config } = useMerchantApiContext(); + const { config, url: backendUrl } = useMerchantApiContext(); const [state, setState] = useState<Partial<Entity>>({ amount: contract.amount, @@ -73,7 +69,7 @@ export function QrPage({ contract, id: templateId, onBack }: Props): VNode { templateParams.summary = state.summary ?? ""; } - const merchantBaseUrl = backendUrl; + const merchantBaseUrl = backendUrl.href; const payTemplateUri = stringifyPayTemplateUri({ merchantBaseUrl, diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx index cf1c13fc4..e1493a870 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx @@ -27,7 +27,7 @@ import { TalerMerchantApi, assertUnreachable } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { useMerchantApiContext, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; @@ -41,7 +41,6 @@ import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputSearchOnList } from "../../../../components/form/InputSearchOnList.js"; import { InputTab } from "../../../../components/form/InputTab.js"; -import { useSessionContext } from "../../../../context/session.js"; import { useInstanceOtpDevices } from "../../../../hooks/otp.js"; enum Steps { @@ -68,9 +67,7 @@ interface Props { export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { const { i18n } = useTranslationContext(); - const { - state: { backendUrl }, - } = useSessionContext(); + const { url: backendUrl } = useMerchantApiContext(); const intialStep = @@ -190,7 +187,7 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { <div class="level-left"> <div class="level-item"> <span class="is-size-4"> - {new URL(`templates/${template.otp_id}`,backendUrl).href} + {new URL(`templates/${template.otp_id}`,backendUrl.href).href} </span> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx index 5fc8bee93..2c0c358e2 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx @@ -82,7 +82,7 @@ export default function UpdateTemplate({ template={result.body} onBack={onBack} onUpdate={(data) => { - return lib.management.updateTemplate(state.token, tid, data) + return lib.instance.updateTemplate(state.token, tid, data) .then(onConfirm) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx index d631cef96..46d4da8d7 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/index.tsx @@ -83,7 +83,7 @@ export default function TemplateUsePage({ request: TalerMerchantApi.UsingTemplateDetails, ) => { - return lib.management.useTemplateCreateOrder(tid, request) + return lib.instance.useTemplateCreateOrder(tid, request) .then((res) => { if (res.type === "ok") { onOrderCreated(res.body.order_id) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx index 768e21325..cc8f7f9e8 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/token/index.tsx @@ -70,7 +70,7 @@ export default function Token({ hasToken={hasToken} onClearToken={async (currentToken): Promise<void> => { try { - await lib.management.updateCurrentInstanceAuthentication(currentToken, { + await lib.instance.updateCurrentInstanceAuthentication(currentToken, { method: "external", }) onChange(); @@ -86,7 +86,7 @@ export default function Token({ }} onNewToken={async (currentToken, newToken): Promise<void> => { try { - await lib.management.updateCurrentInstanceAuthentication(currentToken, { + await lib.instance.updateCurrentInstanceAuthentication(currentToken, { token: newToken, method: "token" }) diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx index 35389f5f5..4a92c1178 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx @@ -56,7 +56,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { onBack={onBack} accounts={accounts} onCreate={(request: TalerMerchantApi.TransferInformation) => { - return lib.management + return lib.instance .informWireTransfer(state.token, request) .then(() => onConfirm()) .catch((error) => { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx index b5319bc2d..4afc400f8 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx @@ -45,15 +45,15 @@ export interface Props { export default function Update(props: Props): VNode { const { lib } = useMerchantApiContext(); - const updateInstance = lib.management.updateCurrentInstance.bind(lib.management) + const updateInstance = lib.instance.updateCurrentInstance.bind(lib.instance) const result = useInstanceDetails(); return CommonUpdate(props, result, updateInstance,); } export function AdminUpdate(props: Props & { instanceId: string }): VNode { const { lib } = useMerchantApiContext(); - const t = lib.instance(props.instanceId) - const updateInstance = lib.instance(props.instanceId).updateCurrentInstance.bind(t) + const t = lib.subInstanceApi(props.instanceId).instance; + const updateInstance = t.updateCurrentInstance.bind(t) const result = useManagedInstanceDetails(props.instanceId); return CommonUpdate(props, result, updateInstance,); } diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx index 50c431079..e4d260b04 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/create/index.tsx @@ -46,7 +46,7 @@ export default function CreateWebhook({ onConfirm, onBack }: Props): VNode { <CreatePage onBack={onBack} onCreate={(request: TalerMerchantApi.WebhookAddDetails) => { - return lib.management.addWebhook(state.token, request) + return lib.instance.addWebhook(state.token, request) .then(() => onConfirm()) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx index 102aef96e..6c68bc973 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/list/index.tsx @@ -84,7 +84,7 @@ export default function ListWebhooks({ onCreate, onSelect }: Props): VNode { onSelect(e.webhook_id); }} onDelete={(e: TalerMerchantApi.WebhookEntry) => { - return lib.management + return lib.instance .deleteWebhook(state.token, e.webhook_id) .then(() => setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx index 262e5bba4..1253cd9a2 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/webhooks/update/index.tsx @@ -82,7 +82,7 @@ export default function UpdateWebhook({ webhook={{ ...result.body, id: tid }} onBack={onBack} onUpdate={(data) => { - return lib.management.updateWebhook(state.token, tid, data) + return lib.instance.updateWebhook(state.token, tid, data) .then(onConfirm) .catch((error) => { setNotif({ diff --git a/packages/merchant-backoffice-ui/src/paths/login/index.tsx b/packages/merchant-backoffice-ui/src/paths/login/index.tsx index 86ec9a9e6..30b5c37bd 100644 --- a/packages/merchant-backoffice-ui/src/paths/login/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/login/index.tsx @@ -19,9 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { - HttpStatusCode -} from "@gnu-taler/taler-util"; +import { HttpStatusCode } from "@gnu-taler/taler-util"; import { useMerchantApiContext, useTranslationContext, @@ -29,12 +27,10 @@ import { import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../components/menu/index.js"; -import { - useSessionContext -} from "../../context/session.js"; +import { useSessionContext } from "../../context/session.js"; import { Notification } from "../../utils/types.js"; -interface Props { } +interface Props {} const tokenRequest = { scope: "write", @@ -47,18 +43,30 @@ const tokenRequest = { export function LoginPage(_p: Props): VNode { const [token, setToken] = useState(""); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const { state, logIn } = useSessionContext(); + const { state, logIn, impersonate } = useSessionContext(); const { lib } = useMerchantApiContext(); const { i18n } = useTranslationContext(); async function doImpersonateImpl(instanceId: string) { - const result = await lib - .impersonate(instanceId) - .createAccessTokenBearer(token, tokenRequest); + const newInstanceApi = lib.subInstanceApi(instanceId); + const cfg = await newInstanceApi.instance.getConfig(); + if (cfg.type !== "ok") { + setNotif({ + message: "Could not load the configuration of this instance.", + description: newInstanceApi.instance.baseUrl, + type: "ERROR", + }); + return; + } + const result = await newInstanceApi.authenticate.createAccessTokenBearer( + token, + tokenRequest, + ); + if (result.type === "ok") { const { token } = result.body; - logIn({ token }); + impersonate({ instance: instanceId, baseUrl: new URL(newInstanceApi.instance.baseUrl), token }); return; } else { switch (result.case) { @@ -126,7 +134,8 @@ export function LoginPage(_p: Props): VNode { > <p> <i18n.Translate> - Need the access token for the instance <b>"{state.instance}"</b> + Need the access token for the instance{" "} + <b>"{state.instance}"</b> </i18n.Translate> </p> <div class="field is-horizontal"> @@ -190,7 +199,9 @@ export function LoginPage(_p: Props): VNode { class="modal-card-body" style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} > - <i18n.Translate>Please enter your access token for <b>"{state.instance}"</b>.</i18n.Translate> + <i18n.Translate> + Please enter your access token for <b>"{state.instance}"</b>. + </i18n.Translate> <div class="field is-horizontal"> <div class="field-label is-normal"> diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index 727c3b674..5f2708b85 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -653,6 +653,12 @@ deploymentCli const bc = await bank.getConfig() + if (bc.type === "fail") { + logger.error( + `couldn't get bank config. ${bc.detail.hint}`, + ); + return; + } if (!bank.isCompatible(bc.body.version)) { logger.error( `bank server version is not compatible: ${bc.body.version}, client version: ${bank.PROTOCOL_VERSION}`, @@ -660,6 +666,12 @@ deploymentCli return; } const mc = await merchantManager.getConfig() + if (mc.type === "fail") { + logger.error( + `couldn't get merchant config. ${mc.detail.hint}`, + ); + return; + } if (!merchantManager.isCompatible(mc.body.version)) { logger.error( `merchant server version is not compatible: ${mc.body.version}, client version: ${merchantManager.PROTOCOL_VERSION}`, diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index b89ac8af6..59698a68b 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -128,6 +128,8 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForCoreBankConfig()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -146,9 +148,9 @@ export class TalerCoreBankHttpClient { body: TalerCorebankApi.RegisterAccountRequest, ) { const url = new URL(`accounts`, this.baseUrl); - const headers: Record<string, string> = {} + const headers: Record<string, string> = {}; if (auth) { - headers.Authorization = makeBearerTokenAuthHeader(auth) + headers.Authorization = makeBearerTokenAuthHeader(auth); } const resp = await this.httpLib.fetch(url.href, { method: "POST", @@ -936,7 +938,7 @@ export class TalerCoreBankHttpClient { ); } if (params.date) { - const {t_s: seconds} = AbsoluteTime.toProtocolTimestamp(params.date) + const { t_s: seconds } = AbsoluteTime.toProtocolTimestamp(params.date); if (seconds !== "never") { url.searchParams.set("date_s", String(seconds)); } diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts index befde4d20..ea7f44cf9 100644 --- a/packages/taler-util/src/http-client/exchange.ts +++ b/packages/taler-util/src/http-client/exchange.ts @@ -71,6 +71,8 @@ export class TalerExchangeHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForExchangeConfig()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index 7d80cacb5..cfe3155d1 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -127,6 +127,8 @@ export class TalerMerchantInstanceHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForMerchantConfig()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } @@ -1677,7 +1679,7 @@ export class TalerMerchantInstanceHttpClient { }); switch (resp.status) { - case HttpStatusCode.NoContent: + case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForWebhookSummaryResponse()); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 8037b5a16..dd2161deb 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -524,17 +524,24 @@ export const codecForAccountAddResponse = export const codecForAccountsSummaryResponse = (): Codec<TalerMerchantApi.AccountsSummaryResponse> => buildCodecForObject<TalerMerchantApi.AccountsSummaryResponse>() - .property("accounts", codecForList(codecForBankAccountEntry())) + .property("accounts", codecForList(codecForBankAccountSummaryEntry())) .build("TalerMerchantApi.AccountsSummaryResponse"); +export const codecForBankAccountSummaryEntry = + (): Codec<TalerMerchantApi.BankAccountSummaryEntry> => + buildCodecForObject<TalerMerchantApi.BankAccountSummaryEntry>() + .property("payto_uri", codecForPaytoString()) + .property("h_wire", codecForString()) + .build("TalerMerchantApi.BankAccountSummaryEntry"); + export const codecForBankAccountEntry = (): Codec<TalerMerchantApi.BankAccountEntry> => buildCodecForObject<TalerMerchantApi.BankAccountEntry>() .property("payto_uri", codecForPaytoString()) .property("h_wire", codecForString()) .property("salt", codecForString()) - .property("credit_facade_url", codecForURL()) - .property("active", codecForBoolean()) + .property("credit_facade_url", codecOptional(codecForURL())) + .property("active", codecOptional(codecForBoolean())) .build("TalerMerchantApi.BankAccountEntry"); export const codecForInventorySummaryResponse = @@ -3772,7 +3779,14 @@ export namespace TalerMerchantApi { export interface AccountsSummaryResponse { // List of accounts that are known for the instance. - accounts: BankAccountEntry[]; + accounts: BankAccountSummaryEntry[]; + } + export interface BankAccountSummaryEntry { + // payto:// URI of the account. + payto_uri: PaytoString; + + // Hash over the wire details (including over the salt). + h_wire: HashCode; } export interface BankAccountEntry { // payto:// URI of the account. @@ -3790,7 +3804,7 @@ export namespace TalerMerchantApi { // true if this account is active, // false if it is historic. - active: boolean; + active?: boolean; } export interface ProductAddDetail { diff --git a/packages/taler-util/src/whatwg-url.ts b/packages/taler-util/src/whatwg-url.ts index 991528ae6..13abf5397 100644 --- a/packages/taler-util/src/whatwg-url.ts +++ b/packages/taler-util/src/whatwg-url.ts @@ -1908,15 +1908,22 @@ function parseURL( } export class URLImpl { - constructor(url: string, base?: string) { + //Include URL type for "url" and "base" params. + constructor(url: string | URL, base?: string | URL) { let parsedBase = null; if (base !== undefined) { + if (base instanceof URL) { + base = base.href; + } parsedBase = basicURLParse(base); if (parsedBase === null) { throw new TypeError(`Invalid base URL: ${base}`); } } + if (url instanceof URL) { + url = url.href; + } const parsedURL = basicURLParse(url, { baseURL: parsedBase }); if (parsedURL === null) { throw new TypeError(`Invalid URL: ${url}`); diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts index d59501212..3d5a105ec 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/index.ts @@ -14,12 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { OperationFailWithBody, OperationOk, OperationResult, TalerExchangeApi } from "@gnu-taler/taler-util"; +import { OperationFailWithBody, OperationOk, TalerExchangeApi } from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; import { TextFieldHandler } from "../../mui/handlers.js"; -import { compose, StateViewMap } from "../../utils/index.js"; +import { StateViewMap, compose } from "../../utils/index.js"; import { useComponentState } from "./state.js"; import { ConfirmView, VerifyView } from "./views.js"; @@ -37,6 +37,7 @@ export type State = State.Loading export type CheckExchangeErrors = { "invalid-version": string; "invalid-currency": string; + "not-found": void; "already-active": void; "invalid-protocol": void; } diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts index 5ae0aa8f4..4a04f762a 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts +++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/state.ts @@ -14,15 +14,15 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ExchangeEntryStatus, OperationFailWithBody, OperationOk, TalerExchangeApi, TalerExchangeHttpClient, canonicalizeBaseUrl, opKnownFailureWithBody } from "@gnu-taler/taler-util"; +import { ExchangeEntryStatus, TalerExchangeHttpClient, canonicalizeBaseUrl, opKnownFailureWithBody } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { BrowserFetchHttpLib } from "@gnu-taler/web-util/browser"; import { useCallback, useEffect, useState } from "preact/hooks"; import { useBackendContext } from "../../context/backend.js"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { withSafe } from "../../mui/handlers.js"; import { RecursiveState } from "../../utils/index.js"; import { CheckExchangeErrors, Props, State } from "./index.js"; -import { BrowserFetchHttpLib } from "@gnu-taler/web-util/browser"; function urlFromInput(str: string): URL { let result: URL; @@ -83,6 +83,9 @@ export function useComponentState({ onBack, currency, noDebounce }: Props): Recu */ const api = new TalerExchangeHttpClient(baseUrl.href, new BrowserFetchHttpLib() as any); const config = await api.getConfig() + if (config.type === "fail") { + return opKnownFailureWithBody<CheckExchangeErrors>("not-found", undefined) + } if (!api.isCompatible(config.body.version)) { return opKnownFailureWithBody<CheckExchangeErrors>("invalid-version", config.body.version) } @@ -155,7 +158,7 @@ function useDebounce<T>( const [result, setResult] = useState<T | undefined>(undefined); const [error, setError] = useState<Error | undefined>(undefined); - const [handler, setHandler] = useState<any | undefined>(undefined); + const [handler, setHandler] = useState<number | undefined>(undefined); if (!disabled) { useEffect(() => { @@ -180,7 +183,7 @@ function useDebounce<T>( setResult(undefined); } }, 500); - setHandler(h); + setHandler(h as unknown as number); }, [value, setHandler, onTrigger]); } diff --git a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx index 489d7eb3b..21309fd7b 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx +++ b/packages/taler-wallet-webextension/src/wallet/AddExchange/views.tsx @@ -17,13 +17,18 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; import { ErrorMessage } from "../../components/ErrorMessage.js"; -import { Input, LightText, SubTitle, Title, WarningBox } from "../../components/styled/index.js"; +import { + Input, + LightText, + SubTitle, + Title, + WarningBox, +} from "../../components/styled/index.js"; import { TermsOfService } from "../../components/TermsOfService/index.js"; import { Button } from "../../mui/Button.js"; import { State } from "./index.js"; import { assertUnreachable } from "@gnu-taler/taler-util"; - export function VerifyView({ expectedCurrency, onCancel, @@ -57,40 +62,71 @@ export function VerifyView({ {(() => { if (!result) return; if (result.type == "ok") { - return <LightText> - <i18n.Translate> - An exchange has been found! Review the information and click next - </i18n.Translate> - </LightText> + return ( + <LightText> + <i18n.Translate> + An exchange has been found! Review the information and click + next + </i18n.Translate> + </LightText> + ); } switch (result.case) { case "already-active": { - return <WarningBox> - <i18n.Translate>This exchange is already in your list.</i18n.Translate> - </WarningBox> + return ( + <WarningBox> + <i18n.Translate> + This exchange is already in your list. + </i18n.Translate> + </WarningBox> + ); } case "invalid-protocol": { - return <WarningBox> - <i18n.Translate>Only exchange accessible through "http" and "https" are allowed.</i18n.Translate> - </WarningBox> + return ( + <WarningBox> + <i18n.Translate> + Only exchange accessible through "http" and "https" are + allowed. + </i18n.Translate> + </WarningBox> + ); } case "invalid-version": { - return <WarningBox> - <i18n.Translate>This exchange protocol version is not supported: "{result.body}".</i18n.Translate> - </WarningBox> + return ( + <WarningBox> + <i18n.Translate> + This exchange protocol version is not supported: " + {result.body}". + </i18n.Translate> + </WarningBox> + ); } case "invalid-currency": { - return <WarningBox> - <i18n.Translate>This exchange currency "{result.body}" doesn't match the expected currency {expectedCurrency}.</i18n.Translate> - </WarningBox> + return ( + <WarningBox> + <i18n.Translate> + This exchange currency "{result.body}" doesn't match + the expected currency {expectedCurrency}. + </i18n.Translate> + </WarningBox> + ); + } + case "not-found": { + return ( + <WarningBox> + <i18n.Translate> + No exchange found in that URL. + </i18n.Translate> + </WarningBox> + ); } default: { - assertUnreachable(result.case) + assertUnreachable(result.case); } } })()} <p> - <Input invalid={result && result.type !== "ok"} > + <Input invalid={result && result.type !== "ok"}> <label>URL</label> <input type="text" @@ -98,7 +134,7 @@ export function VerifyView({ value={url.value} onInput={(e) => { if (url.onInput) { - url.onInput(e.currentTarget.value) + url.onInput(e.currentTarget.value); } }} /> @@ -138,10 +174,7 @@ export function VerifyView({ </Button> <Button variant="contained" - disabled={ - !result || - result.type !== "ok" - } + disabled={!result || result.type !== "ok"} onClick={onAccept} > <i18n.Translate>Next</i18n.Translate> @@ -149,14 +182,22 @@ export function VerifyView({ </footer> <section> <ul> - {knownExchanges.map(ex => { - return <li><a href="#" onClick={(e) => { - if (url.onInput) { - url.onInput(ex.href) - } - e.preventDefault() - }}> - {ex.href}</a></li> + {knownExchanges.map((ex) => { + return ( + <li key={ex.href}> + <a + href="#" + onClick={(e) => { + if (url.onInput) { + url.onInput(ex.href); + } + e.preventDefault(); + }} + > + {ex.href} + </a> + </li> + ); })} </ul> </section> @@ -164,7 +205,6 @@ export function VerifyView({ ); } - export function ConfirmView({ url, onCancel, @@ -186,8 +226,7 @@ export function ConfirmView({ </div> </section> - - <TermsOfService key="terms" exchangeUrl={url} > + <TermsOfService key="terms" exchangeUrl={url}> <footer> <Button key="cancel" diff --git a/packages/web-util/src/context/activity.ts b/packages/web-util/src/context/activity.ts index c38f133a1..9a16f6673 100644 --- a/packages/web-util/src/context/activity.ts +++ b/packages/web-util/src/context/activity.ts @@ -55,10 +55,9 @@ export interface APIClient<T, C> { } export interface MerchantLib { - management: TalerMerchantManagementHttpClient; + instance: TalerMerchantManagementHttpClient; authenticate: TalerAuthenticationHttpClient; - instance: (instanceId: string) => TalerMerchantInstanceHttpClient; - impersonate: (instanceId: string) => TalerAuthenticationHttpClient; + subInstanceApi: (instanceId: string) => MerchantLib; } export interface BankLib { diff --git a/packages/web-util/src/context/bank-api.ts b/packages/web-util/src/context/bank-api.ts index a5e5654ac..d2938f150 100644 --- a/packages/web-util/src/context/bank-api.ts +++ b/packages/web-util/src/context/bank-api.ts @@ -25,7 +25,7 @@ import { TalerCoreBankCacheEviction, TalerCoreBankHttpClient, TalerCorebankApi, - TalerError + TalerError, } from "@gnu-taler/taler-util"; import { ComponentChildren, @@ -56,7 +56,8 @@ export type BankContextType = { // @ts-expect-error default value to undefined, should it be another thing? const BankContext = createContext<BankContextType>(undefined); -export const useBankCoreApiContext = (): BankContextType => useContext(BankContext); +export const useBankCoreApiContext = (): BankContextType => + useContext(BankContext); enum VersionHint { NONE, @@ -65,7 +66,7 @@ enum VersionHint { type Evictors = { conversion?: CacheEvictor<TalerBankConversionCacheEviction>; bank?: CacheEvictor<TalerCoreBankCacheEviction>; -} +}; type ConfigResult<T> = | undefined @@ -81,13 +82,15 @@ export const BankApiProvider = ({ }: { baseUrl: URL; children: ComponentChildren; - evictors?: Evictors, + evictors?: Evictors; frameOnError: FunctionComponent<{ children: ComponentChildren }>; }): VNode => { - const [checked, setChecked] = useState<ConfigResult<TalerCorebankApi.Config>>(); + const [checked, setChecked] = + useState<ConfigResult<TalerCorebankApi.Config>>(); const { i18n } = useTranslationContext(); - const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } = buildBankApiClient(baseUrl, evictors); + const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } = + buildBankApiClient(baseUrl, evictors); useEffect(() => { getRemoteConfig() @@ -110,7 +113,9 @@ export const BankApiProvider = ({ }, []); if (checked === undefined) { - return h(frameOnError, { children: h("div", {}, "checking compatibility with server...") }); + return h(frameOnError, { + children: h("div", {}, "checking compatibility with server..."), + }); } if (checked.type === "error") { return h(frameOnError, { @@ -141,7 +146,9 @@ export const BankApiProvider = ({ }); }; -function buildBankApiClient(url: URL, evictors: Evictors, +function buildBankApiClient( + url: URL, + evictors: Evictors, ): APIClient<BankLib, TalerCorebankApi.Config> { const httpFetch = new BrowserFetchHttpLib({ enableThrottling: true, @@ -154,11 +161,7 @@ function buildBankApiClient(url: URL, evictors: Evictors, }, }); - const bank = new TalerCoreBankHttpClient( - url.href, - httpLib, - evictors.bank, - ); + const bank = new TalerCoreBankHttpClient(url.href, httpLib, evictors.bank); const conversion = new TalerBankConversionHttpClient( bank.getConversionInfoAPI().href, httpLib, @@ -170,32 +173,36 @@ function buildBankApiClient(url: URL, evictors: Evictors, httpLib, ); - async function getRemoteConfig() { - const resp = await bank.getConfig() - return resp.body + async function getRemoteConfig(): Promise<TalerCorebankApi.Config> { + const resp = await bank.getConfig(); + if (resp.type === "fail") { + throw TalerError.fromUncheckedDetail(resp.detail); + } + return resp.body; } return { getRemoteConfig, VERSION: bank.PROTOCOL_VERSION, lib: { - bank, conversion, auth + bank, + conversion, + auth, }, onActivity: tracker.subscribe, cancelRequest: httpLib.cancelRequest, }; } - export const BankApiProviderTesting = ({ children, value, }: { - value: BankContextType + value: BankContextType; children: ComponentChildren; }): VNode => { return h(BankContext.Provider, { value, children, }); -} +}; diff --git a/packages/web-util/src/context/merchant-api.ts b/packages/web-util/src/context/merchant-api.ts index 9e2869b62..26d9c9e85 100644 --- a/packages/web-util/src/context/merchant-api.ts +++ b/packages/web-util/src/context/merchant-api.ts @@ -23,7 +23,6 @@ import { TalerError, TalerMerchantApi, TalerMerchantInstanceCacheEviction, - TalerMerchantInstanceHttpClient, TalerMerchantManagementCacheEviction, TalerMerchantManagementHttpClient, } from "@gnu-taler/taler-util"; @@ -35,14 +34,13 @@ import { h, } from "preact"; import { useContext, useEffect, useState } from "preact/hooks"; +import { BrowserFetchHttpLib } from "../index.browser.js"; import { APIClient, ActiviyTracker, MerchantLib, Subscriber, } from "./activity.js"; -import { useTranslationContext } from "./translation.js"; -import { BrowserFetchHttpLib, ErrorLoading } from "../index.browser.js"; /** * @@ -56,6 +54,7 @@ export type MerchantContextType = { hints: VersionHint[]; onActivity: Subscriber<ObservabilityEvent>; cancelRequest: (eventId: string) => void; + changeBackend: (url: URL) => void; }; // FIXME: below @@ -94,14 +93,17 @@ export const MerchantApiProvider = ({ baseUrl: URL; evictors?: Evictors; children: ComponentChildren; - frameOnError: FunctionComponent<{ state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined }>; + frameOnError: FunctionComponent<{ + state: ConfigResultFail<TalerMerchantApi.VersionResponse> | undefined; + }>; }): VNode => { const [checked, setChecked] = useState<ConfigResult<TalerMerchantApi.VersionResponse>>(); - const { i18n } = useTranslationContext(); + + const [merchantEndpoint, changeMerchantEndpoint] = useState(baseUrl); const { getRemoteConfig, VERSION, lib, cancelRequest, onActivity } = - buildMerchantApiClient(baseUrl, evictors); + buildMerchantApiClient(merchantEndpoint, evictors); useEffect(() => { getRemoteConfig() @@ -128,11 +130,12 @@ export const MerchantApiProvider = ({ } const value: MerchantContextType = { - url: baseUrl, + url: merchantEndpoint, config: checked.config, onActivity: onActivity, lib, cancelRequest, + changeBackend: changeMerchantEndpoint, hints: checked.hints, }; return h(MerchantContext.Provider, { @@ -157,40 +160,51 @@ function buildMerchantApiClient( }, }); - const management = new TalerMerchantManagementHttpClient( + const instance = new TalerMerchantManagementHttpClient( url.href, httpLib, evictors.management, ); - const instance = (instanceId: string) => - new TalerMerchantInstanceHttpClient( - management.getSubInstanceAPI(instanceId).href, - httpLib, - evictors.instance ? evictors.instance(instanceId) : undefined, - ); const authenticate = new TalerAuthenticationHttpClient( - management.getAuthenticationAPI().href, + instance.getAuthenticationAPI().href, httpLib, ); - const impersonate = (instanceId: string) => - new TalerAuthenticationHttpClient( - instance(instanceId).getAuthenticationAPI().href, - httpLib, - ); + + // const instance = (instanceId: string): TalerMerchantInstanceHttpClient => { + // return new TalerMerchantInstanceHttpClient( + // management.getSubInstanceAPI(instanceId).href, + // httpLib, + // evictors.instance ? evictors.instance(instanceId) : undefined, + // ); + // } + // const impersonate = (instanceId: string): TalerAuthenticationHttpClient => { + // return new TalerAuthenticationHttpClient( + // instance(instanceId).getAuthenticationAPI().href, + // httpLib, + // ); + // } + const rootUrl = url; + function getSubInstanceAPI(instanceId: string): MerchantLib { + const newURL = new URL(`instance/${instanceId}/`, rootUrl); + const api = buildMerchantApiClient(newURL, evictors); + return api.lib; + } async function getRemoteConfig(): Promise<TalerMerchantApi.VersionResponse> { - const resp = await management.getConfig(); + const resp = await instance.getConfig(); + if (resp.type === "fail") { + throw TalerError.fromUncheckedDetail(resp.detail) + } return resp.body; } return { getRemoteConfig, - VERSION: management.PROTOCOL_VERSION, + VERSION: instance.PROTOCOL_VERSION, lib: { - management, - authenticate, - impersonate, instance, + authenticate, + subInstanceApi: getSubInstanceAPI, }, onActivity: tracker.subscribe, cancelRequest: httpLib.cancelRequest, diff --git a/packages/web-util/src/utils/http-impl.sw.ts b/packages/web-util/src/utils/http-impl.sw.ts index 4d7f3a8a1..3c4b8b587 100644 --- a/packages/web-util/src/utils/http-impl.sw.ts +++ b/packages/web-util/src/utils/http-impl.sw.ts @@ -18,11 +18,10 @@ * Imports. */ import { + Duration, RequestThrottler, - TalerErrorCode, TalerError, - Duration, - CancellationToken, + TalerErrorCode } from "@gnu-taler/taler-util"; import { |