diff options
Diffstat (limited to 'packages/taler-wallet-webextension')
30 files changed, 582 insertions, 393 deletions
diff --git a/packages/taler-wallet-webextension/manifest-common.json b/packages/taler-wallet-webextension/manifest-common.json index 88f152d50..01248e964 100644 --- a/packages/taler-wallet-webextension/manifest-common.json +++ b/packages/taler-wallet-webextension/manifest-common.json @@ -2,7 +2,7 @@ "name": "GNU Taler Wallet (git)", "description": "Privacy preserving and transparent payments", "author": "GNU Taler Developers", - "version": "0.11.4", + "version": "0.12.2", "icons": { "16": "static/img/taler-logo-16.png", "19": "static/img/taler-logo-19.png", @@ -14,5 +14,5 @@ "256": "static/img/taler-logo-256.png", "512": "static/img/taler-logo-512.png" }, - "version_name": "0.11.4" + "version_name": "0.12.2" } diff --git a/packages/taler-wallet-webextension/package.json b/packages/taler-wallet-webextension/package.json index 90679cfdd..5c622da70 100644 --- a/packages/taler-wallet-webextension/package.json +++ b/packages/taler-wallet-webextension/package.json @@ -1,6 +1,6 @@ { "name": "@gnu-taler/taler-wallet-webextension", - "version": "0.11.4", + "version": "0.12.2", "description": "GNU Taler Wallet browser extension", "main": "./build/index.js", "types": "./build/index.d.ts", diff --git a/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx b/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx index b1ed3b02c..38d1b6b6b 100644 --- a/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx +++ b/packages/taler-wallet-webextension/src/components/CurrentAlerts.tsx @@ -22,6 +22,8 @@ import { } from "../context/alert.js"; import { Alert } from "../mui/Alert.js"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ButtonHandler } from "../mui/handlers.js"; +import { Button } from "../mui/Button.js"; /** * @@ -99,13 +101,23 @@ function AlertContext({ export function ErrorAlertView({ error, + retry, onClose, }: { error: AlertNotification; + retry?: ButtonHandler; onClose?: () => Promise<void>; }): VNode { + const { i18n } = useTranslationContext(); return ( <Wrapper> + {!retry ? undefined : ( + <section> + <Button variant="contained" color="success" onClick={retry.onClick}> + <i18n.Translate>Retry operation</i18n.Translate> + </Button> + </section> + )} <AlertView alert={error} onClose={onClose} /> </Wrapper> ); diff --git a/packages/taler-wallet-webextension/src/components/Modal.tsx b/packages/taler-wallet-webextension/src/components/Modal.tsx index f8c0f1651..c5f716c76 100644 --- a/packages/taler-wallet-webextension/src/components/Modal.tsx +++ b/packages/taler-wallet-webextension/src/components/Modal.tsx @@ -52,8 +52,7 @@ const Body = styled.div` export function Modal({ title, children, onClose }: Props): VNode { return ( - <div style={{ top: 0, width: "100%", height: "100%" }}> - + <div style={{ top: 0, position: "fixed", width: "100%", height: "100%" }}> <FullSize onClick={onClose?.onClick}> <div onClick={(e) => e.stopPropagation()} diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts index 1585e3992..7ef5b95a1 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/index.ts @@ -17,7 +17,11 @@ import { ComponentChildren } from "preact"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; -import { SelectFieldHandler, ToggleHandler } from "../../mui/handlers.js"; +import { + ButtonHandler, + SelectFieldHandler, + ToggleHandler, +} from "../../mui/handlers.js"; import { StateViewMap, compose } from "../../utils/index.js"; import { ErrorAlertView } from "../CurrentAlerts.js"; import { useComponentState } from "./state.js"; @@ -61,6 +65,7 @@ export namespace State { status: "show-content"; termsAccepted: ToggleHandler; showingTermsOfService?: ToggleHandler; + skipTos: ButtonHandler; tosLang: SelectFieldHandler; tosFormat: SelectFieldHandler; } @@ -68,7 +73,7 @@ export namespace State { status: "show-buttons-accepted"; termsAccepted: ToggleHandler; showingTermsOfService: ToggleHandler; - children: ComponentChildren, + children: ComponentChildren; } export interface ShowButtonsNotAccepted extends BaseInfo { status: "show-buttons-not-accepted"; diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts index 76524f0f4..96d14dadf 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/state.ts @@ -25,22 +25,28 @@ import { buildTermsOfServiceState } from "./utils.js"; const supportedFormats = { "text/html": "HTML", - "text/xml" : "XML", - "text/markdown" : "Markdown", - "text/plain" : "Plain text", - "text/pdf" : "PDF", -} - -export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, children }: Props): State { + "text/xml": "XML", + "text/markdown": "Markdown", + "text/plain": "Plain text", + "text/pdf": "PDF", +}; + +export function useComponentState({ + showEvenIfaccepted, + exchangeUrl, + readOnly, + children, +}: Props): State { const api = useBackendContext(); const [showContent, setShowContent] = useState<boolean>(!!readOnly); const { i18n, lang } = useTranslationContext(); - const [tosLang, setTosLang] = useState<string>() + const [forceAccepted, setForceAccepted] = useState<boolean>(); + const [tosLang, setTosLang] = useState<string>(); const { pushAlertOnError } = useAlertContext(); - const [format, setFormat] = useState("text/html") + const [format, setFormat] = useState("text/html"); - const acceptedLang = tosLang ?? lang + const acceptedLang = tosLang ?? lang; /** * For the exchange selected, bring the status of the terms of service */ @@ -54,10 +60,13 @@ export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, c }, ); - const supportedLangs = exchangeTos.tosAvailableLanguages.reduce((prev, cur) => { - prev[cur] = cur - return prev; - }, {} as Record<string, string>) + const supportedLangs = exchangeTos.tosAvailableLanguages.reduce( + (prev, cur) => { + prev[cur] = cur; + return prev; + }, + {} as Record<string, string>, + ); const state = buildTermsOfServiceState(exchangeTos); @@ -92,30 +101,35 @@ export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, c } else { // mark as not accepted } - terms?.retry() + terms?.retry(); } - const accepted = state.status === "accepted"; + const accepted = state.status === "accepted" || forceAccepted; const base = { error: undefined, showingTermsOfService: { value: showContent && (!accepted || showEvenIfaccepted), button: { - onClick: accepted && !showEvenIfaccepted ? undefined : pushAlertOnError(async () => { - setShowContent(!showContent); - }), + onClick: + accepted && !showEvenIfaccepted + ? undefined + : pushAlertOnError(async () => { + setShowContent(!showContent); + }), }, }, terms: state, termsAccepted: { value: accepted, button: { - onClick: readOnly ? undefined : pushAlertOnError(async () => { - const newValue = !accepted; //toggle - await onUpdate(newValue); - setShowContent(false); - }), + onClick: readOnly + ? undefined + : pushAlertOnError(async () => { + const newValue = !accepted; //toggle + await onUpdate(newValue); + setShowContent(false); + }), }, }, }; @@ -135,20 +149,25 @@ export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, c terms: state, showingTermsOfService: readOnly ? undefined : base.showingTermsOfService, termsAccepted: base.termsAccepted, + skipTos: { + onClick: pushAlertOnError(async () => { + setForceAccepted(true); + }), + }, tosFormat: { onChange: pushAlertOnError(async (s) => { - setFormat(s) + setFormat(s); }), list: supportedFormats, - value: format ?? "" + value: format ?? "", }, tosLang: { onChange: pushAlertOnError(async (s) => { - setTosLang(s) + setTosLang(s); }), list: supportedLangs, - value: tosLang ?? lang - } + value: tosLang ?? lang, + }, }; } //showing buttons @@ -156,5 +175,4 @@ export function useComponentState({ showEvenIfaccepted, exchangeUrl, readOnly, c status: "show-buttons-not-accepted", ...base, }; - } diff --git a/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx index 40cfba3bc..f3172a741 100644 --- a/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx +++ b/packages/taler-wallet-webextension/src/components/TermsOfService/views.tsx @@ -23,7 +23,7 @@ import { Input, LinkSuccess, TermsOfServiceStyle, - WarningBox + WarningBox, } from "../../components/styled/index.js"; import { Button } from "../../mui/Button.js"; import { State } from "./index.js"; @@ -50,7 +50,9 @@ export function ShowButtonsAcceptedTosView({ </LinkSuccess> </section> {termsAccepted.button.onClick !== undefined && ( - <section style={{ justifyContent: "space-around", display: "flex" }}> + <section + style={{ justifyContent: "space-around", display: "flex" }} + > <CheckboxOutlined name="terms" enabled={termsAccepted.value} @@ -75,36 +77,9 @@ export function ShowButtonsNonAcceptedTosView({ terms, }: State.ShowButtonsNotAccepted): VNode { const { i18n } = useTranslationContext(); - // const ableToReviewTermsOfService = - // showingTermsOfService.button.onClick !== undefined; - - // if (!ableToReviewTermsOfService) { - // return ( - // <Fragment> - // {terms.status === ExchangeTosStatus.Pending && ( - // <section style={{ justifyContent: "space-around", display: "flex" }}> - // <WarningText> - // <i18n.Translate> - // Exchange doesn't have terms of service - // </i18n.Translate> - // </WarningText> - // </section> - // )} - // </Fragment> - // ); - // } return ( <Fragment> - {/* {terms.status === ExchangeTosStatus.NotFound && ( - <section style={{ justifyContent: "space-around", display: "flex" }}> - <WarningText> - <i18n.Translate> - Exchange doesn't have terms of service - </i18n.Translate> - </WarningText> - </section> - )} */} <section style={{ justifyContent: "space-around", display: "flex" }}> <Button variant="contained" @@ -124,11 +99,35 @@ export function ShowTosContentView({ terms, tosLang, tosFormat, + skipTos, }: State.ShowContent): VNode { const { i18n } = useTranslationContext(); - const ableToReviewTermsOfService = - termsAccepted.button.onClick !== undefined; + const ableToReviewTermsOfService = termsAccepted.button.onClick !== undefined; + + if (terms.status === ExchangeTosStatus.MissingTos) { + return ( + <section> + <section style={{ justifyContent: "space-around", display: "flex" }}> + <WarningBox> + <i18n.Translate> + The exchange doesn't have a terms of service. + </i18n.Translate> + </WarningBox> + </section> + <section style={{ justifyContent: "space-around", display: "flex" }}> + <Button + variant="contained" + color="success" + disabled={!skipTos.onClick} + onClick={skipTos.onClick} + > + <i18n.Translate>Skip it for now.</i18n.Translate> + </Button> + </section> + </section> + ); + } return ( <section> <Input style={{ display: "flex", justifyContent: "end" }}> @@ -206,20 +205,21 @@ export function ShowTosContentView({ </LinkSuccess> </section> )} - {termsAccepted.button.onClick && terms.status !== ExchangeTosStatus.Accepted && ( - <section style={{ justifyContent: "space-around", display: "flex" }}> - <CheckboxOutlined - name="terms" - enabled={termsAccepted.value} - label={ - <i18n.Translate> - I accept the exchange terms of service - </i18n.Translate> - } - onToggle={termsAccepted.button.onClick} - /> - </section> - )} + {termsAccepted.button.onClick && + terms.status !== ExchangeTosStatus.Accepted && ( + <section style={{ justifyContent: "space-around", display: "flex" }}> + <CheckboxOutlined + name="terms" + enabled={termsAccepted.value} + label={ + <i18n.Translate> + I accept the exchange terms of service + </i18n.Translate> + } + onToggle={termsAccepted.button.onClick} + /> + </section> + )} </section> ); } diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx index f29d0b0f7..c0bc5532b 100644 --- a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx +++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx @@ -39,6 +39,7 @@ import { WxApiType } from "../wxApi.js"; import { WalletActivityTrack } from "../wxBackend.js"; import { Modal } from "./Modal.js"; import { Time } from "./Time.js"; +import { Checkbox } from "./Checkbox.js"; const OPEN_ACTIVITY_HEIGHT_PX = 250; const CLOSE_ACTIVITY_HEIGHT_PX = 40; @@ -212,7 +213,10 @@ function ShowBackupOperationError({ events, onClick }: MoreInfoPRops): VNode { <dd>{error.hint ?? "--"}</dd> <dt>Time</dt> <dd> - <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time + timestamp={error.when} + format="yyyy/MM/dd HH:mm:ss.SSS" + /> </dd> </dl> <pre @@ -360,34 +364,44 @@ function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode { const title = (function () { switch (not.event.type) { - case ObservabilityEventType.HttpFetchFinishError: - case ObservabilityEventType.HttpFetchFinishSuccess: case ObservabilityEventType.HttpFetchStart: return "HTTP Request"; - case ObservabilityEventType.DbQueryFinishSuccess: - case ObservabilityEventType.DbQueryFinishError: + case ObservabilityEventType.HttpFetchFinishSuccess: + return "HTTP Request (o)"; + case ObservabilityEventType.HttpFetchFinishError: + return "HTTP Request (x)"; case ObservabilityEventType.DbQueryStart: return "Database"; - case ObservabilityEventType.RequestFinishSuccess: - case ObservabilityEventType.RequestFinishError: + case ObservabilityEventType.DbQueryFinishSuccess: + return "Database (o)"; + case ObservabilityEventType.DbQueryFinishError: + return "Database (x)"; case ObservabilityEventType.RequestStart: return "Wallet"; - case ObservabilityEventType.CryptoFinishSuccess: - case ObservabilityEventType.CryptoFinishError: + case ObservabilityEventType.RequestFinishSuccess: + return "Wallet (o)"; + case ObservabilityEventType.RequestFinishError: + return "Wallet (x)"; case ObservabilityEventType.CryptoStart: return "Crypto"; + case ObservabilityEventType.CryptoFinishSuccess: + return "Crypto (o)"; + case ObservabilityEventType.CryptoFinishError: + return "Crypto (x)"; case ObservabilityEventType.TaskStart: - return "Task start"; + return "Task"; case ObservabilityEventType.TaskStop: - return "Task stop"; + return "Task (s)"; case ObservabilityEventType.TaskReset: - return "Task reset"; + return "Task (r)"; case ObservabilityEventType.ShepherdTaskResult: return "Schedule"; case ObservabilityEventType.DeclareTaskDependency: return "Task dependency"; case ObservabilityEventType.Message: return "Message"; + case ObservabilityEventType.DeclareConcernsTransaction: + return "DeclareConcernsTransaction"; } })(); @@ -401,12 +415,11 @@ function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode { ); }); return ( - <table> + <table style={{ width: "100%" }}> <thead> <td>Event</td> <td>Info</td> - <td>Start</td> - <td>End</td> + <td>When</td> </thead> <tbody>{asd}</tbody> </table> @@ -417,11 +430,9 @@ function ShowObervavilityDetails({ title, notif, onClick, - prev, }: { title: string; notif: ObservaNotifWithTime; - prev?: ObservaNotifWithTime; onClick: (content: VNode) => void; }): VNode { switch (notif.event.type) { @@ -443,7 +454,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -454,21 +465,21 @@ function ShowObervavilityDetails({ </td> <td> {notif.event.url}{" "} - {prev?.event.type === + {notif?.event.type === ObservabilityEventType.HttpFetchFinishSuccess ? ( - `(${prev.event.status})` - ) : prev?.event.type === + `(${notif.event.status})` + ) : notif?.event.type === ObservabilityEventType.HttpFetchFinishError ? ( <a href="#" onClick={(e) => { e.preventDefault(); if ( - prev.event.type !== + notif.event.type !== ObservabilityEventType.HttpFetchFinishError ) return; - const error = prev.event.error; + const error = notif.event.error; onClick( <Fragment> <dl> @@ -482,7 +493,7 @@ function ShowObervavilityDetails({ <dd> <Time timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" + format="yyyy/MM/dd HH:mm:ss.SSS" /> </dd> </dl> @@ -504,11 +515,7 @@ function ShowObervavilityDetails({ </td> <td> {" "} - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - {" "} - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); @@ -531,7 +538,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -544,10 +551,7 @@ function ShowObervavilityDetails({ {notif.event.location} {notif.event.name} </td> <td> - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); @@ -572,7 +576,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -583,10 +587,7 @@ function ShowObervavilityDetails({ </td> <td>{notif.event.taskId}</td> <td> - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); @@ -607,7 +608,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -618,10 +619,7 @@ function ShowObervavilityDetails({ </td> <td>{notif.event.resultType}</td> <td> - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); @@ -644,7 +642,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -655,10 +653,7 @@ function ShowObervavilityDetails({ </td> <td>{notif.event.operation}</td> <td> - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); @@ -681,7 +676,7 @@ function ShowObervavilityDetails({ wordBreak: "break-word", }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} + {JSON.stringify({ event: notif }, undefined, 2)} </pre> </Fragment>, ); @@ -692,27 +687,30 @@ function ShowObervavilityDetails({ </td> <td>{notif.event.type}</td> <td> - <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" /> - </td> - <td> - <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> </tr> ); } + case ObservabilityEventType.DeclareConcernsTransaction: case ObservabilityEventType.Message: // FIXME return <></>; } } +function createTabId(tab: chrome.tabs.Tab | undefined) { + return !tab ? "popup" : `${tab.windowId}:${tab.id}`; +} + function refresh( api: WxApiType, onUpdate: (list: WalletActivityTrack[]) => void, filter: string, + fromView?: chrome.tabs.Tab, ) { api.background - .call("getNotifications", { filter }) + .call("getNotifications", { filter, operationsFrom: createTabId(fromView) }) .then((notif) => { onUpdate(notif); }) @@ -721,6 +719,15 @@ function refresh( }); } +let currentTab: chrome.tabs.Tab | undefined; +// Allow running outside the extension for testing +// tslint:disable-next-line:no-string-literal +if (typeof chrome !== "undefined") { + chrome.tabs.getCurrent().then((d) => { + currentTab = d; + }); +} + export function ObservabilityEventsTable(): VNode { const { i18n } = useTranslationContext(); const api = useBackendContext(); @@ -728,11 +735,17 @@ export function ObservabilityEventsTable(): VNode { const [notifications, setNotifications] = useState<WalletActivityTrack[]>([]); const [showDetails, setShowDetails] = useState<VNode>(); const [filter, onChangeFilter] = useState(""); + const [onlyThisScreen, setOnlyThisScreen] = useState(true); useEffect(() => { let lastTimeout: ReturnType<typeof setTimeout>; function periodicRefresh() { - refresh(api, setNotifications, filter); + refresh( + api, + setNotifications, + filter, + onlyThisScreen ? currentTab : undefined, + ); lastTimeout = setTimeout(() => { periodicRefresh(); @@ -743,7 +756,7 @@ export function ObservabilityEventsTable(): VNode { }; } return periodicRefresh(); - }, [filter]); + }, [filter, onlyThisScreen]); return ( <div> @@ -754,6 +767,12 @@ export function ObservabilityEventsTable(): VNode { value={filter} onChange={onChangeFilter} /> + <Checkbox + label={i18n.str`All events`} + name="terms" + onToggle={async () => setOnlyThisScreen((v) => !v)} + enabled={!onlyThisScreen} + /> <div style={{ padding: 4, @@ -763,7 +782,12 @@ export function ObservabilityEventsTable(): VNode { }} onClick={() => { api.background.call("clearNotifications", undefined).then(() => { - refresh(api, setNotifications, filter); + refresh( + api, + setNotifications, + filter, + onlyThisScreen ? currentTab : undefined, + ); }); }} > @@ -784,7 +808,7 @@ export function ObservabilityEventsTable(): VNode { )} {notifications.map((not) => { return ( - <details key={not.id}> + <details key={not.groupId}> <summary> <div style={{ @@ -829,10 +853,13 @@ export function ObservabilityEventsTable(): VNode { })()} </div> <div style={{ padding: 4 }}> - <Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" /> + <Time + timestamp={not.start} + format="yyyy/MM/dd HH:mm:ss.SSS" + /> </div> <div style={{ padding: 4 }}> - <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss.SSS" /> </div> </div> </summary> @@ -936,7 +963,7 @@ function ErroDetailModal({ <dd>{error.hint ?? "--"}</dd> <dt>Time</dt> <dd> - <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss.SSS" /> </dd> </dl> <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> @@ -1009,11 +1036,14 @@ export function ActiveTasksTable(): VNode { <td> <Time timestamp={task.firstTry} - format="yyyy/MM/dd HH:mm:ss" + format="yyyy/MM/dd HH:mm:ss.SSS" /> </td> <td> - <Time timestamp={task.nextTry} format="yyyy/MM/dd HH:mm:ss" /> + <Time + timestamp={task.nextTry} + format="yyyy/MM/dd HH:mm:ss.SSS" + /> </td> <td> {!task.lastError?.code ? ( diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index fd3fb52f8..1ca7481be 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -47,6 +47,7 @@ export namespace State { export interface LoadingUriError { status: "error"; + retry: ButtonHandler; error: ErrorAlert; } diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index daa3ee76d..baaa9a3dd 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -34,6 +34,7 @@ export function useComponentState({ }: Props): RecursiveState<State> { const amount = Amounts.parseOrThrow(amountStr); const api = useBackendContext(); + const { pushAlertOnError } = useAlertContext(); const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {}), @@ -49,6 +50,11 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", + retry: { + onClick: pushAlertOnError(async () => { + hook.retry(); + }), + }, error: alertFromError( i18n, i18n.str`Could not load the list of exchanges`, @@ -103,6 +109,11 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", + retry: { + onClick: pushAlertOnError(async () => { + hook.retry(); + }), + }, error: alertFromError( i18n, i18n.str`Could not load the invoice status`, diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts index f0cd63fbe..dcb1f827b 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts @@ -50,6 +50,7 @@ export namespace State { export interface LoadingUriError { status: "error"; + retry: ButtonHandler; error: ErrorAlert; } diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts index 99de03d2d..deee83751 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -63,6 +63,11 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", + retry: { + onClick: pushAlertOnError(async () => { + hook.retry(); + }), + }, error: alertFromError( i18n, i18n.str`Could not load the transfer payment status`, diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts index 794d2ad1c..539ca207c 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson, AmountString, TalerErrorDetail } from "@gnu-taler/taler-util"; +import { + AmountJson, + AmountString, + TalerErrorDetail, +} from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; @@ -39,6 +43,7 @@ export namespace State { export interface LoadingUriError { status: "error"; + retry: ButtonHandler; error: ErrorAlert; } diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts index f092801ed..f15d48c23 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -57,6 +57,11 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", + retry: { + onClick: pushAlertOnError(async () => { + hook.retry(); + }), + }, error: alertFromError( i18n, i18n.str`Could not load the max amount to transfer`, diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts index 4e1301d6a..a7bb0b67a 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts @@ -14,16 +14,12 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - AbsoluteTime, - AmountJson, - TalerErrorDetail, -} from "@gnu-taler/taler-util"; +import { AbsoluteTime, AmountJson } from "@gnu-taler/taler-util"; import { ErrorAlertView } from "../../components/CurrentAlerts.js"; import { Loading } from "../../components/Loading.js"; import { ErrorAlert } from "../../context/alert.js"; import { ButtonHandler } from "../../mui/handlers.js"; -import { compose, StateViewMap } from "../../utils/index.js"; +import { StateViewMap, compose } from "../../utils/index.js"; import { useComponentState } from "./state.js"; import { ReadyView } from "./views.js"; @@ -43,6 +39,7 @@ export namespace State { export interface LoadingUriError { status: "error"; + retry: ButtonHandler; error: ErrorAlert; } diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts index 67f6d9113..28d8c9e70 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -49,6 +49,11 @@ export function useComponentState({ if (hook.hasError) { return { status: "error", + retry: { + onClick: pushAlertOnError(async () => { + hook.retry(); + }), + }, error: alertFromError( i18n, i18n.str`Could not load the invoice payment status`, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index af1ef213b..418fef505 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -18,7 +18,7 @@ import { AmountJson, AmountString, CurrencySpecification, - ExchangeListItem + ExchangeListItem, } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; @@ -85,7 +85,7 @@ export namespace State { operationState: "confirmed" | "aborted" | "selected"; thisWallet: boolean; redirectToTx: () => void; - confirmTransferUrl?: string, + confirmTransferUrl?: string; error: undefined; } @@ -99,8 +99,8 @@ export namespace State { editableAmount: boolean; bankFee: AmountJson; - withdrawalFee: AmountJson; toBeReceived: AmountJson; + toBeSent: AmountJson; doWithdrawal: ButtonHandler; doSelectExchange: ButtonHandler; @@ -109,10 +109,12 @@ export namespace State { chooseCurrencies: string[]; selectedCurrency: string; changeCurrency: (s: string) => void; - conversionInfo: { - spec: CurrencySpecification, - amount: AmountJson, - } | undefined; + conversionInfo: + | { + spec: CurrencySpecification; + amount: AmountJson; + } + | undefined; ageRestriction?: SelectFieldHandler; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index f8e27e688..0541bbf3f 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -391,7 +391,6 @@ function exchangeSelectionState( const safeAmount = wInfo.amount ? wInfo.amount : Amounts.zeroOfCurrency(wInfo.currency); - const [choosenAmount, setChoosenAmount] = useState(safeAmount); if (selectedExchange.status !== "ready") { return selectedExchange; @@ -403,8 +402,9 @@ function exchangeSelectionState( | State.Loading => { const { i18n } = useTranslationContext(); const { pushAlertOnError } = useAlertContext(); + + const [choosenAmount, setChoosenAmount] = useState(safeAmount); const [ageRestricted, setAgeRestricted] = useState(0); - const currentExchange = selectedExchange.selected; const [selectedCurrency, setSelectedCurrency] = useState<string>( wInfo.currency, @@ -417,7 +417,7 @@ function exchangeSelectionState( const info = await api.wallet.call( WalletApiOperation.GetWithdrawalDetailsForAmount, { - exchangeBaseUrl: currentExchange.exchangeBaseUrl, + exchangeBaseUrl: selectedExchange.selected.exchangeBaseUrl, amount: Amounts.stringify(choosenAmount), restrictAge: ageRestricted, }, @@ -430,13 +430,33 @@ function exchangeSelectionState( return { amount: withdrawAmount, + currentExchange: selectedExchange.selected, ageRestrictionOptions: info.ageRestrictionOptions, accounts: info.withdrawalAccountsList, }; - }, []); + }, [choosenAmount, selectedExchange.selected, ageRestricted]); const [doingWithdraw, setDoingWithdraw] = useState<boolean>(false); + if (!amountHook) { + return { status: "loading", error: undefined }; + } + if (amountHook.hasError) { + return { + status: "error", + error: alertFromError( + i18n, + i18n.str`Could not load the withdrawal details`, + amountHook, + ), + }; + } + if (!amountHook.response) { + return { status: "loading", error: undefined }; + } + + const currentExchange = amountHook.response.currentExchange; + async function doWithdrawAndCheckError(): Promise<void> { try { setDoingWithdraw(true); @@ -458,30 +478,10 @@ function exchangeSelectionState( setDoingWithdraw(false); } - if (!amountHook) { - return { status: "loading", error: undefined }; - } - if (amountHook.hasError) { - return { - status: "error", - error: alertFromError( - i18n, - i18n.str`Could not load the withdrawal details`, - amountHook, - ), - }; - } - if (!amountHook.response) { - return { status: "loading", error: undefined }; - } - - const withdrawalFee = Amounts.sub( - amountHook.response.amount.raw, - amountHook.response.amount.effective, - ).amount; + const toBeSent = amountHook.response.amount.raw; const toBeReceived = amountHook.response.amount.effective; - const bankFee = wInfo.amount; + const bankFee = wInfo.bankFee; const ageRestrictionOptions = amountHook.response.ageRestrictionOptions?.reduce( @@ -544,6 +544,7 @@ function exchangeSelectionState( editableExchange: wInfo.editableExchange, currentExchange, toBeReceived, + toBeSent, chooseCurrencies, bankFee, selectedCurrency, @@ -551,7 +552,6 @@ function exchangeSelectionState( setSelectedCurrency(s); }, conversionInfo, - withdrawalFee, amount: { value: choosenAmount, onInput: wInfo.editableAmount @@ -565,11 +565,11 @@ function exchangeSelectionState( ageRestriction, doWithdrawal: { onClick: - doingWithdraw && !amountError + doingWithdraw || amountError ? undefined : pushAlertOnError(doWithdrawAndCheckError), }, cancel, }; - }, []); + }, [selectedExchange.selected]); } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx index 1bfafb231..d9b7c380e 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/stories.tsx @@ -48,14 +48,20 @@ export const TermsOfServiceNotYetLoaded = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 10000000, - } + }, }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, + doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 10000000, value: 1, @@ -72,20 +78,19 @@ export const TermsOfServiceNotYetLoaded = tests.createExample(SuccessView, { export const AlreadyAborted = tests.createExample(FinalStateOperation, { error: undefined, status: "already-completed", - operationState: "aborted" + operationState: "aborted", }); export const AlreadySelected = tests.createExample(FinalStateOperation, { error: undefined, status: "already-completed", - operationState: "selected" + operationState: "selected", }); export const AlreadyConfirmed = tests.createExample(FinalStateOperation, { error: undefined, status: "already-completed", - operationState: "confirmed" + operationState: "confirmed", }); - export const WithSomeFee = tests.createExample(SuccessView, { error: undefined, status: "success", @@ -94,14 +99,20 @@ export const WithSomeFee = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 10000000, - } + }, }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, + doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 10000000, value: 1, @@ -123,14 +134,20 @@ export const WithoutFee = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 0, - } + }, }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, + doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 0, value: 0, @@ -152,14 +169,20 @@ export const EditExchangeUntouched = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 10000000, - } + }, }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, + doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 0, value: 0, @@ -181,14 +204,20 @@ export const EditExchangeModified = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 10000000, - } + }, + }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, }, + doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 0, value: 0, @@ -211,15 +240,21 @@ export const WithAgeRestriction = tests.createExample(SuccessView, { currency: "USD", value: 2, fraction: 10000000, - } + }, }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, + doSelectExchange: {}, doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.demo.taler.net", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "USD", fraction: 0, value: 0, @@ -240,8 +275,14 @@ export const WithAlternateCurrenciesNETZBON = tests.createExample(SuccessView, { currency: "NETZBON", value: 2, fraction: 10000000, - } + }, + }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, }, + chooseCurrencies: ["NETZBON", "EUR"], selectedCurrency: "NETZBON", doWithdrawal: { onClick: nullFunction }, @@ -249,7 +290,7 @@ export const WithAlternateCurrenciesNETZBON = tests.createExample(SuccessView, { exchangeBaseUrl: "https://exchange.netzbon.ch", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "NETZBON", fraction: 10000000, value: 1, @@ -270,27 +311,33 @@ export const WithAlternateCurrenciesEURO = tests.createExample(SuccessView, { currency: "NETZBON", value: 2, fraction: 10000000, - } + }, + }, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, }, + chooseCurrencies: ["NETZBON", "EUR"], selectedCurrency: "EUR", - changeCurrency: () => { }, + changeCurrency: () => {}, conversionInfo: { spec: { - name: "EUR" + name: "EUR", } as CurrencySpecification, amount: { currency: "EUR", fraction: 10000000, value: 1, - } + }, }, doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.netzbon.ch", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "NETZBON", fraction: 10000000, value: 1, @@ -311,27 +358,32 @@ export const WithAlternateCurrenciesEURO11 = tests.createExample(SuccessView, { currency: "NETZBON", value: 2, fraction: 10000000, - } + }, }, chooseCurrencies: ["NETZBON", "EUR"], selectedCurrency: "EUR", - changeCurrency: () => { }, + changeCurrency: () => {}, + bankFee: { + currency: "EUR", + fraction: 0, + value: 1, + }, conversionInfo: { spec: { - name: "EUR" + name: "EUR", } as CurrencySpecification, amount: { currency: "EUR", fraction: 10000000, value: 2, - } + }, }, doWithdrawal: { onClick: nullFunction }, currentExchange: { exchangeBaseUrl: "https://exchange.netzbon.ch", tos: {}, } as Partial<ExchangeListItem> as any, - withdrawalFee: { + toBeSent: { currency: "NETZBON", fraction: 10000000, value: 1, diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index bce5f71e3..5a75cb4be 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -123,7 +123,7 @@ describe("Withdraw CTA states", () => { editableExchange: false, maxAmount: "ARS:1", wireFee: "ARS:0", - }, + }, }, ); @@ -208,7 +208,7 @@ describe("Withdraw CTA states", () => { if (state.status !== "success") return; expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); + expect(state.toBeSent).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.amount.value).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.doWithdrawal.onClick).not.undefined; @@ -302,7 +302,7 @@ describe("Withdraw CTA states", () => { if (state.status !== "success") return; expect(state.toBeReceived).deep.equal(Amounts.parseOrThrow("ARS:2")); - expect(state.withdrawalFee).deep.equal(Amounts.parseOrThrow("ARS:0")); + expect(state.toBeSent).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.amount.value).deep.equal(Amounts.parseOrThrow("ARS:2")); expect(state.doWithdrawal.onClick).not.undefined; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index 86d7248a4..b6a356de8 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -39,6 +39,7 @@ import { getAmountWithFee, } from "../../wallet/Transaction.js"; import { State } from "./index.js"; +import { Amounts } from "@gnu-taler/taler-util"; export function FinalStateOperation(state: State.AlreadyCompleted): VNode { const { i18n } = useTranslationContext(); @@ -143,8 +144,6 @@ export function FinalStateOperation(state: State.AlreadyCompleted): VNode { export function SuccessView(state: State.Success): VNode { const { i18n } = useTranslationContext(); - // const currentTosVersionIsAccepted = - // state.currentExchange.tosStatus === ExchangeTosStatus.Accepted; return ( <Fragment> <section style={{ textAlign: "left" }}> @@ -212,9 +211,10 @@ export function SuccessView(state: State.Success): VNode { conversion={state.conversionInfo?.amount} amount={getAmountWithFee( state.toBeReceived, - state.amount.value, + state.toBeSent, "credit", )} + bankFee={state.bankFee} /> } /> @@ -232,7 +232,6 @@ export function SuccessView(state: State.Success): VNode { </section> <section> - {/* <div> */} <TermsOfService exchangeUrl={state.currentExchange.exchangeBaseUrl}> <Button variant="contained" @@ -245,20 +244,6 @@ export function SuccessView(state: State.Success): VNode { </i18n.Translate> </Button> </TermsOfService> - {/* </div> - <div style={{ marginTop: 20 }}> - <Button - variant="text" - color="success" - - disabled={!state.doAbort.onClick} - onClick={state.doAbort.onClick} - > - <i18n.Translate> - Cancel - </i18n.Translate> - </Button> - </div> */} </section> {state.talerWithdrawUri ? ( <WithdrawWithMobile talerWithdrawUri={state.talerWithdrawUri} /> diff --git a/packages/taler-wallet-webextension/src/i18n/de.po b/packages/taler-wallet-webextension/src/i18n/de.po index bc66f2136..e54c3067d 100644 --- a/packages/taler-wallet-webextension/src/i18n/de.po +++ b/packages/taler-wallet-webextension/src/i18n/de.po @@ -17,8 +17,8 @@ msgstr "" "Project-Id-Version: Taler Wallet\n" "Report-Msgid-Bugs-To: languages@taler.net\n" "POT-Creation-Date: 2016-11-23 00:00+0100\n" -"PO-Revision-Date: 2024-05-07 14:32+0000\n" -"Last-Translator: Stefan Kügel <skuegel@web.de>\n" +"PO-Revision-Date: 2024-07-01 22:17+0000\n" +"Last-Translator: Stefan Kügel <stefan.kuegel@taler.net>\n" "Language-Team: German <https://weblate.taler.net/projects/gnu-taler/" "webextensions/de/>\n" "Language: de\n" @@ -26,7 +26,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.4.3\n" +"X-Generator: Weblate 5.5.5\n" #: src/NavigationBar.tsx:139 #, c-format @@ -1365,7 +1365,7 @@ msgstr "Allgemeine Geschäftsbedingungen (AGB) ansehen" #: src/wallet/ExchangeAddConfirm.tsx:45 #, c-format msgid "Exchange URL" -msgstr "" +msgstr "URL des Exchange" #: src/wallet/ExchangeAddConfirm.tsx:70 #, c-format @@ -1735,8 +1735,8 @@ msgid "" "Please check in your %1$s settings that you have IndexedDB enabled (check " "the preference name %2$s)." msgstr "" -"Bitte prüfen Sie ihre %1$s Einstellungen, für die Sie IndexedDB verwenden (" -"preference name %2$s prüfen)." +"Bitte prüfen Sie in Ihren %1$s-Einstellungen, dass Sie IndexedDB aktiviert " +"haben, und überprüfen Sie %2$s." #: src/components/Diagnostics.tsx:70 #, c-format diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts index 3c116fab2..2388647c1 100644 --- a/packages/taler-wallet-webextension/src/platform/api.ts +++ b/packages/taler-wallet-webextension/src/platform/api.ts @@ -227,6 +227,7 @@ export interface BackgroundPlatformAPI { listenToAllChannels( notifyNewMessage: <Op extends WalletOperations | BackgroundOperations>( message: MessageFromFrontend<Op> & { id: string }, + from: string, ) => Promise<MessageResponse>, ): void; diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts index 056351e3f..276d464a0 100644 --- a/packages/taler-wallet-webextension/src/platform/chrome.ts +++ b/packages/taler-wallet-webextension/src/platform/chrome.ts @@ -53,7 +53,7 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { redirectTabToWalletPage, registerAllIncomingConnections, registerOnInstalled, - listenToAllChannels , + listenToAllChannels, registerReloadOnNewVersion, sendMessageToAllChannels, openNewURLFromPopup, @@ -276,18 +276,20 @@ async function sendMessageToBackground< Op extends WalletOperations | BackgroundOperations, >(message: MessageFromFrontend<Op>): Promise<MessageResponse> { nextMessageIndex = (nextMessageIndex + 1) % (Number.MAX_SAFE_INTEGER - 100); - const messageWithId = { ...message, id: `id_${nextMessageIndex}` }; + const messageWithId = { ...message, id: `fg:${nextMessageIndex}` }; return new Promise<MessageResponse>((resolve, reject) => { logger.trace("send operation to the wallet background", message); let timedout = false; const timerId = setTimeout(() => { timedout = true; - reject(TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, { - requestMethod: "wallet", - requestUrl: message.operation, - timeoutMs: 20 * 1000, - })); + reject( + TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, { + requestMethod: "wallet", + requestUrl: message.operation, + timeoutMs: 20 * 1000, + }), + ); }, 20 * 1000); chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { if (timedout) { @@ -309,7 +311,9 @@ async function sendMessageToBackground< * To be used by the foreground */ let notificationPort: chrome.runtime.Port | undefined; -function listenToWalletBackground(listener: (message: MessageFromBackend) => void): () => void { +function listenToWalletBackground( + listener: (message: MessageFromBackend) => void, +): () => void { if (notificationPort === undefined) { notificationPort = chrome.runtime.connect({ name: "notifications" }); } @@ -380,13 +384,18 @@ function registerAllIncomingConnections(): void { }); } +function createTabId(tab: chrome.tabs.Tab | undefined) { + return !tab ? "popup" : `${tab.windowId}:${tab.id}`; +} + function listenToAllChannels( notifyNewMessage: <Op extends WalletOperations | BackgroundOperations>( message: MessageFromFrontend<Op> & { id: string }, + id: string, ) => Promise<MessageResponse>, ): void { chrome.runtime.onMessage.addListener((message, sender, reply) => { - notifyNewMessage(message) + notifyNewMessage(message, createTabId(sender.tab)) .then((apiResponse) => { try { reply(apiResponse); @@ -483,26 +492,26 @@ function setAlertedIcon(): void { interface OffscreenCanvasRenderingContext2D extends CanvasState, - CanvasTransform, - CanvasCompositing, - CanvasImageSmoothing, - CanvasFillStrokeStyles, - CanvasShadowStyles, - CanvasFilters, - CanvasRect, - CanvasDrawPath, - CanvasUserInterface, - CanvasText, - CanvasDrawImage, - CanvasImageData, - CanvasPathDrawingStyles, - CanvasTextDrawingStyles, - CanvasPath { + CanvasTransform, + CanvasCompositing, + CanvasImageSmoothing, + CanvasFillStrokeStyles, + CanvasShadowStyles, + CanvasFilters, + CanvasRect, + CanvasDrawPath, + CanvasUserInterface, + CanvasText, + CanvasDrawImage, + CanvasImageData, + CanvasPathDrawingStyles, + CanvasTextDrawingStyles, + CanvasPath { readonly canvas: OffscreenCanvas; } declare const OffscreenCanvasRenderingContext2D: { prototype: OffscreenCanvasRenderingContext2D; - new(): OffscreenCanvasRenderingContext2D; + new (): OffscreenCanvasRenderingContext2D; }; interface OffscreenCanvas extends EventTarget { @@ -515,7 +524,7 @@ interface OffscreenCanvas extends EventTarget { } declare const OffscreenCanvas: { prototype: OffscreenCanvas; - new(width: number, height: number): OffscreenCanvas; + new (width: number, height: number): OffscreenCanvas; }; function createCanvas(size: number): OffscreenCanvas { @@ -760,7 +769,6 @@ function listenNetworkConnectionState( }; } - function runningOnPrivateMode(): boolean { return chrome.extension.inIncognitoContext; } diff --git a/packages/taler-wallet-webextension/src/platform/dev.ts b/packages/taler-wallet-webextension/src/platform/dev.ts index b53e8f3c4..844a5c517 100644 --- a/packages/taler-wallet-webextension/src/platform/dev.ts +++ b/packages/taler-wallet-webextension/src/platform/dev.ts @@ -95,7 +95,7 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { useServiceWorkerAsBackgroundProcess: () => false, listenToAllChannels: ( - notifyNewMessage: (message: any) => Promise<MessageResponse>, + notifyNewMessage: (message: any, from: string) => Promise<MessageResponse>, ) => { window.addEventListener( "message", @@ -103,7 +103,7 @@ const api: BackgroundPlatformAPI & ForegroundPlatformAPI = { if (event.data.type !== "command") return; const sender = event.data.header.replyMe; - notifyNewMessage(event.data.body as any).then((resp) => { + notifyNewMessage(event.data.body as any, sender).then((resp) => { logger.trace(`listenToAllChannels: from ${sender}`, event); if (event.source) { const msg: IframeMessageResponse = { @@ -199,4 +199,3 @@ interface IframeMessageCommand { } export default api; - diff --git a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts index 3b7cbcbb7..5e781121b 100644 --- a/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts +++ b/packages/taler-wallet-webextension/src/taler-wallet-interaction-loader.ts @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { CoreApiResponse, TalerError, TalerErrorCode } from "@gnu-taler/taler-util"; +import { + CoreApiResponse, + TalerError, + TalerErrorCode, +} from "@gnu-taler/taler-util"; import type { MessageFromBackend } from "./platform/api.js"; /** @@ -51,8 +55,6 @@ const rootElementIsHTML = // "meta[name=taler-support]", // ); - - function validateTalerUri(uri: string): boolean { return ( !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://")) @@ -61,7 +63,9 @@ function validateTalerUri(uri: string): boolean { function convertURIToWebExtensionPath(uri: string) { const url = new URL( - chrome.runtime.getURL(`static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`), + chrome.runtime.getURL( + `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`, + ), ); return url.href; } @@ -75,7 +79,7 @@ const shouldNotInject = !rootElementIsHTML; const logger = { - debug: (...msg: any[]) => { }, + debug: (...msg: any[]) => {}, info: (...msg: any[]) => console.log(`${new Date().toISOString()} TALER`, ...msg), error: (...msg: any[]) => @@ -87,7 +91,7 @@ const logger = { /** */ function redirectToTalerActionHandler(element: HTMLMetaElement) { - const name = element.getAttribute("name") + const name = element.getAttribute("name"); if (!name) return; if (name !== "taler-uri") return; const uri = element.getAttribute("content"); @@ -98,20 +102,20 @@ function redirectToTalerActionHandler(element: HTMLMetaElement) { return; } - const walletPage = convertURIToWebExtensionPath(uri) - window.location.replace(walletPage) + const walletPage = convertURIToWebExtensionPath(uri); + window.location.replace(walletPage); } function injectTalerSupportScript(head: HTMLHeadElement, trusted: boolean) { - const meta = head.querySelector("meta[name=taler-support]") + const meta = head.querySelector("meta[name=taler-support]"); if (!meta) return; const content = meta.getAttribute("content"); if (!content) return; - const features = content.split(",") + const features = content.split(","); const debugEnabled = meta.getAttribute("debug") === "true"; - const hijackEnabled = features.indexOf("uri") !== -1 - const talerApiEnabled = features.indexOf("api") !== -1 && trusted + const hijackEnabled = features.indexOf("uri") !== -1; + const talerApiEnabled = features.indexOf("api") !== -1 && trusted; const scriptTag = document.createElement("script"); scriptTag.setAttribute("async", "false"); @@ -131,14 +135,16 @@ function injectTalerSupportScript(head: HTMLHeadElement, trusted: boolean) { scriptTag.src = url.href; try { - head.insertBefore(scriptTag, head.children.length ? head.children[0] : null); + head.insertBefore( + scriptTag, + head.children.length ? head.children[0] : null, + ); } catch (e) { logger.info("inserting link handler failed!"); logger.error(e); } } - export interface ExtensionOperations { isAutoOpenEnabled: { request: void; @@ -177,31 +183,38 @@ async function callBackground<Op extends keyof ExtensionOperations>( return response.result as any; } - let nextMessageIndex = 0; /** - * - * @param message - * @returns + * + * @param message + * @returns */ async function sendMessageToBackground<Op extends keyof ExtensionOperations>( message: MessageFromExtension<Op>, ): Promise<MessageResponse> { - const messageWithId = { ...message, id: `id_${nextMessageIndex++ % 1000}` }; + const messageWithId = { ...message, id: `ld:${nextMessageIndex++ % 1000}` }; if (!chrome.runtime.id) { - return Promise.reject(TalerError.fromDetail(TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, {})) + return Promise.reject( + TalerError.fromDetail(TalerErrorCode.WALLET_CORE_NOT_AVAILABLE, {}), + ); } return new Promise<any>((resolve, reject) => { - logger.debug("send operation to the wallet background", message, chrome.runtime.id); + logger.debug( + "send operation to the wallet background", + message, + chrome.runtime.id, + ); let timedout = false; const timerId = setTimeout(() => { timedout = true; - reject(TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, { - requestMethod: "wallet", - requestUrl: message.operation, - timeoutMs: 20 * 1000, - })) + reject( + TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, { + requestMethod: "wallet", + requestUrl: message.operation, + timeoutMs: 20 * 1000, + }), + ); }, 20 * 1000); //five seconds try { chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => { @@ -218,7 +231,7 @@ async function sendMessageToBackground<Op extends keyof ExtensionOperations>( return true; }); } catch (e) { - console.log(e) + console.log(e); } }); } @@ -240,71 +253,76 @@ function listenToWalletBackground(listener: (m: any) => void): () => void { const loaderSettings = { isAutoOpenEnabled: false, isDomainTrusted: false, -} +}; function start( onTalerMetaTagFound: (listener: (el: HTMLMetaElement) => void) => void, - onHeadReady: (listener: (el: HTMLHeadElement) => void) => void + onHeadReady: (listener: (el: HTMLHeadElement) => void) => void, ) { // do not run everywhere, this is just expected to run on site // that are aware of taler if (shouldNotInject) return; - const isAutoOpenEnabled_promise = callBackground("isAutoOpenEnabled", undefined).then(result => { + const isAutoOpenEnabled_promise = callBackground( + "isAutoOpenEnabled", + undefined, + ).then((result) => { loaderSettings.isAutoOpenEnabled = result; return result; - }) + }); const isDomainTrusted_promise = callBackground("isDomainTrusted", { - domain: window.location.origin - }).then(result => { + domain: window.location.origin, + }).then((result) => { loaderSettings.isDomainTrusted = result; return result; - }) + }); onTalerMetaTagFound(async (el) => { await isAutoOpenEnabled_promise; if (!loaderSettings.isAutoOpenEnabled) { return; } - redirectToTalerActionHandler(el) - }) + redirectToTalerActionHandler(el); + }); onHeadReady(async (el) => { - const trusted = await isDomainTrusted_promise - injectTalerSupportScript(el, trusted) - }) + const trusted = await isDomainTrusted_promise; + injectTalerSupportScript(el, trusted); + }); listenToWalletBackground((e: MessageFromBackend) => { - if (e.type === "web-extension" && e.notification.type === "settings-change") { - const settings = e.notification.currentValue - loaderSettings.isAutoOpenEnabled = settings.autoOpen + if ( + e.type === "web-extension" && + e.notification.type === "settings-change" + ) { + const settings = e.notification.currentValue; + loaderSettings.isAutoOpenEnabled = settings.autoOpen; } - }) - + }); } function isCorrectMetaElement(el: HTMLMetaElement): boolean { - const name = el.getAttribute("name") + const name = el.getAttribute("name"); if (!name) return false; if (name !== "taler-uri") return false; const uri = el.getAttribute("content"); if (!uri) return false; - return true + return true; } /** * Tries to find taler meta tag ASAP and report - * @param notify - * @returns + * @param notify + * @returns */ function notifyWhenTalerUriIsFound(notify: (el: HTMLMetaElement) => void) { if (document.head) { - const element = document.head.querySelector("meta[name=taler-uri]") + const element = document.head.querySelector("meta[name=taler-uri]"); if (!element) return; if (!(element instanceof HTMLMetaElement)) return; if (isCorrectMetaElement(element)) { - notify(element) + notify(element); } return; } @@ -315,34 +333,33 @@ function notifyWhenTalerUriIsFound(notify: (el: HTMLMetaElement) => void) { mut.addedNodes.forEach((added) => { if (added instanceof HTMLMetaElement) { if (isCorrectMetaElement(added)) { - notify(added) - obs.disconnect() + notify(added); + obs.disconnect(); } } }); } }); } catch (e) { - console.error(e) + console.error(e); } - }) + }); obs.observe(document, { childList: true, subtree: true, attributes: false, - }) - + }); } /** * Tries to find HEAD tag ASAP and report - * @param notify - * @returns + * @param notify + * @returns */ function notifyWhenHeadIsFound(notify: (el: HTMLHeadElement) => void) { if (document.head) { - notify(document.head) + notify(document.head); return; } const obs = new MutationObserver(async function (mutations) { @@ -351,22 +368,22 @@ function notifyWhenHeadIsFound(notify: (el: HTMLHeadElement) => void) { if (mut.type === "childList") { mut.addedNodes.forEach((added) => { if (added instanceof HTMLHeadElement) { - notify(added) - obs.disconnect() + notify(added); + obs.disconnect(); } }); } }); } catch (e) { - console.error(e) + console.error(e); } - }) + }); obs.observe(document, { childList: true, subtree: true, attributes: false, - }) + }); } start(notifyWhenTalerUriIsFound, notifyWhenHeadIsFound); diff --git a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx index 8f23c0685..9feb03714 100644 --- a/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DeveloperPage.tsx @@ -22,7 +22,7 @@ import { LogLevel, NotificationType, ScopeType, - stringifyWithdrawExchange + stringifyWithdrawExchange, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -112,21 +112,21 @@ export function DeveloperPage(): VNode { const currencies: { [ex: string]: string } = {}; const money_by_exchange = coins.reduce( (prev, cur) => { - const denom = Amounts.parseOrThrow(cur.denom_value); - if (!prev[cur.exchange_base_url]) { - prev[cur.exchange_base_url] = []; - currencies[cur.exchange_base_url] = denom.currency; + const denom = Amounts.parseOrThrow(cur.denomValue); + if (!prev[cur.exchangeBaseUrl]) { + prev[cur.exchangeBaseUrl] = []; + currencies[cur.exchangeBaseUrl] = denom.currency; } - prev[cur.exchange_base_url].push({ + prev[cur.exchangeBaseUrl].push({ // ageKeysCount: cur.ageCommitmentProof?.proof.privateKeys.length, denom_value: denom.value, denom_fraction: denom.fraction, // remain_value: parseFloat( // Amounts.stringifyValue(Amounts.parseOrThrow(cur.remaining_value)), // ), - status: cur.coin_status, - from_refresh: cur.refresh_parent_coin_pub !== undefined, - id: cur.coin_pub, + status: cur.coinStatus, + from_refresh: cur.refreshParentCoinPub !== undefined, + id: cur.coinPub, }); return prev; }, @@ -351,7 +351,7 @@ export function DeveloperPage(): VNode { <a href={new URL(`/keys`, e.exchangeBaseUrl).href} target="_blank" - rel="noreferrer" + rel="noreferrer" > {e.exchangeBaseUrl} </a> diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 1f0293352..339ded173 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -1416,9 +1416,11 @@ export function TransferPickupDetails({ export function WithdrawDetails({ conversion, amount, + bankFee, }: { conversion?: AmountJson; amount: AmountWithFee; + bankFee?: AmountJson; }): VNode { const { i18n } = useTranslationContext(); @@ -1481,6 +1483,16 @@ export function WithdrawDetails({ </tr> </Fragment> )} + {!bankFee || Amounts.isZero(bankFee) ? undefined : ( + <tr> + <td> + <i18n.Translate>Bank fee</i18n.Translate> + </td> + <td> + <Amount value={bankFee} maxFracSize={amount.maxFrac} /> + </td> + </tr> + )} </PurchaseDetailsTable> ); } diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 47b466fcd..8361a098d 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -31,7 +31,7 @@ import { TalerError, TalerErrorCode, TalerErrorDetail, - WalletNotification + WalletNotification, } from "@gnu-taler/taler-util"; import { WalletCoreApiClient, @@ -55,7 +55,7 @@ import { WalletActivityTrack } from "./wxBackend.js"; const logger = new Logger("wxApi"); -export const WALLET_CORE_SUPPORTED_VERSION = "5:0:0" +export const WALLET_CORE_SUPPORTED_VERSION = "7:0:0"; export interface ExtendedPermissionsResponse { newValue: boolean; @@ -77,6 +77,7 @@ export interface BackgroundOperations { getNotifications: { request: { filter: string; + operationsFrom?: string; }; response: WalletActivityTrack[]; }; @@ -93,7 +94,10 @@ export interface BackgroundOperations { }; } -export type WalletEvent = { notification: WalletNotification, when: AbsoluteTime } +export type WalletEvent = { + notification: WalletNotification; + when: AbsoluteTime; +}; export interface BackgroundApiClient { call<Op extends keyof BackgroundOperations>( @@ -139,10 +143,14 @@ class BackgroundApiClientImpl implements BackgroundApiClient { response = await platform.sendMessageToBackground(message); } catch (error) { if (error instanceof Error) { - throw new BackgroundError(operation, { - code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, - when: AbsoluteTime.now(), - }, error); + throw new BackgroundError( + operation, + { + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + when: AbsoluteTime.now(), + }, + error, + ); } throw error; } @@ -168,6 +176,8 @@ class WalletApiClientImpl implements WalletCoreApiClient { ): Promise<WalletCoreResponseType<Op>> { let response: CoreApiResponse; try { + // FIXME: This type must be fixed and needs documentation! + // @ts-ignore const message: MessageFromFrontendWallet<Op> = { channel: "wallet", operation, @@ -176,10 +186,14 @@ class WalletApiClientImpl implements WalletCoreApiClient { response = await platform.sendMessageToBackground(message); } catch (error) { if (error instanceof Error) { - throw new BackgroundError(operation, { - code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, - when: AbsoluteTime.now(), - }, error); + throw new BackgroundError( + operation, + { + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + when: AbsoluteTime.now(), + }, + error, + ); } throw error; } @@ -187,7 +201,7 @@ class WalletApiClientImpl implements WalletCoreApiClient { throw new BackgroundError( `Wallet operation "${operation}" failed`, response.error, - TalerError.fromUncheckedDetail(response.error) + TalerError.fromUncheckedDetail(response.error), ); } logger.trace("got response", response); @@ -205,7 +219,9 @@ function onUpdateNotification( return; }; const onNewMessage = (message: MessageFromBackend): void => { - const shouldNotify = message.type === "wallet" && messageTypes.includes(message.notification.type); + const shouldNotify = + message.type === "wallet" && + messageTypes.includes(message.notification.type); if (shouldNotify) { doCallback(message.notification); } @@ -226,7 +242,7 @@ function trigger(w: ExtensionNotification) { platform.triggerWalletEvent({ type: "web-extension", notification: w, - }) + }); } export const wxApi = { diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index a0b9f2908..ab3c465c4 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -91,19 +91,15 @@ async function resetDb(): Promise<void> { } export type WalletActivityTrack = { - id: number; + // id: number; events: (WalletNotification & { when: AbsoluteTime })[]; start: AbsoluteTime; type: NotificationType; end: AbsoluteTime; groupId: string; + requestId?: string; //only for request event }; -let counter = 0; -function getUniqueId(): number { - return counter++; -} - //FIXME: maybe circular buffer const activity: WalletActivityTrack[] = []; @@ -120,10 +116,9 @@ function convertWalletActivityNotification( if (found) { found.end = event.when; found.events.unshift(event); - return found; + return undefined; } return { - id: getUniqueId(), type: event.type, start: event.when, end: AbsoluteTime.never(), @@ -134,7 +129,6 @@ function convertWalletActivityNotification( case NotificationType.BackupOperationError: { const groupId = ""; return { - id: getUniqueId(), type: event.type, start: event.when, end: AbsoluteTime.never(), @@ -148,10 +142,9 @@ function convertWalletActivityNotification( if (found) { found.end = event.when; found.events.unshift(event); - return found; + return undefined; } return { - id: getUniqueId(), type: event.type, start: event.when, end: AbsoluteTime.never(), @@ -168,10 +161,9 @@ function convertWalletActivityNotification( if (found) { found.end = event.when; found.events.unshift(event); - return found; + return undefined; } return { - id: getUniqueId(), type: event.type, start: event.when, end: AbsoluteTime.never(), @@ -181,14 +173,13 @@ function convertWalletActivityNotification( } case NotificationType.Idle: { const groupId = ""; - return({ - id: getUniqueId(), + return { type: event.type, start: event.when, end: AbsoluteTime.never(), events: [event], groupId, - }); + }; } case NotificationType.TaskObservabilityEvent: { const groupId = `${event.type}:${event.taskId}`; @@ -196,16 +187,15 @@ function convertWalletActivityNotification( if (found) { found.end = event.when; found.events.unshift(event); - return found; + return undefined; } - return({ - id: getUniqueId(), + return { type: event.type, start: event.when, end: AbsoluteTime.never(), events: [event], groupId, - }); + }; } case NotificationType.RequestObservabilityEvent: { const groupId = `${event.type}:${event.operation}:${event.requestId}`; @@ -213,16 +203,16 @@ function convertWalletActivityNotification( if (found) { found.end = event.when; found.events.unshift(event); - return found; + return undefined; } - return({ - id: getUniqueId(), + return { type: event.type, start: event.when, end: AbsoluteTime.never(), events: [event], groupId, - }); + requestId: event.requestId, + }; } } } @@ -241,14 +231,24 @@ function addNewWalletActivityNotification( async function getNotifications({ filter, + operationFrom, }: { filter: string; + operationFrom?: string; }): Promise<WalletActivityTrack[]> { - if (!filter) return activity; - const rg = new RegExp(`.*${filter}.*`); return activity.filter((event) => { - return rg.test(event.groupId.toLowerCase()); + if (operationFrom) { + if (event.type !== NotificationType.RequestObservabilityEvent) { + return false; + } + if (event.requestId) { + return event.requestId.startsWith(operationFrom); + } + } + if (!filter) return true; + const testFilter = rg.test(event.groupId.toLowerCase()); + return testFilter; }); } @@ -318,7 +318,10 @@ let nextMessageIndex = 0; async function dispatch< Op extends WalletOperations | BackgroundOperations | ExtensionOperations, ->(req: MessageFromFrontend<Op> & { id: string }): Promise<MessageResponse> { +>( + req: MessageFromFrontend<Op> & { id: string }, + from: string, +): Promise<MessageResponse> { nextMessageIndex = (nextMessageIndex + 1) % (Number.MAX_SAFE_INTEGER - 100); switch (req.channel) { @@ -402,7 +405,7 @@ async function dispatch< }; } //multiple client can create the same id, send the wallet an unique key - const newId = `${req.id}_${nextMessageIndex}`; + const newId = `${from}:${req.id}`; const resp = await w.handleCoreApiRequest( req.operation, newId, @@ -516,10 +519,10 @@ export async function wxMain(): Promise<void> { // Handlers for messages coming directly from the content // script on the page logger.trace("listen all channels"); - platform.listenToAllChannels(async (message) => { + platform.listenToAllChannels(async (message, from) => { //wait until wallet is initialized await afterWalletIsInitialized; - const result = await dispatch(message); + const result = await dispatch(message, from); return result; }); |