diff options
author | Florian Dold <florian@dold.me> | 2024-04-23 00:16:35 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-04-23 00:16:35 +0200 |
commit | 920a384e90bc35f262361920aa8ddd1046abac34 (patch) | |
tree | 11cbf47f5321b23c778191828028b02ec4caab4a | |
parent | dfb1716769068859a49b1a2187f187b99a5fe88b (diff) | |
download | wallet-core-920a384e90bc35f262361920aa8ddd1046abac34.tar.xz |
wallet-core: avoid use of plain 'id' field, misc. fixes
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 20 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 16 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 4 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/components/WalletActivity.tsx | 1318 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/wxBackend.ts | 5 |
5 files changed, 812 insertions, 551 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index d39eb3ce9..0564c45f7 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -2621,30 +2621,30 @@ export const codecForWithdrawFakebankRequest = .build("WithdrawFakebankRequest"); export interface ActiveTask { - id: string; + taskId: string; transaction: TransactionIdStr | undefined; firstTry: AbsoluteTime | undefined; nextTry: AbsoluteTime | undefined; - counter: number | undefined; + retryCounter: number | undefined; lastError: TalerErrorDetail | undefined; } -export interface GetActiveTasks { +export interface GetActiveTasksResponse { tasks: ActiveTask[]; } export const codecForActiveTask = (): Codec<ActiveTask> => buildCodecForObject<ActiveTask>() - .property("id", codecForString()) + .property("taskId", codecForString()) .property("transaction", codecOptional(codecForTransactionIdStr())) - .property("counter", codecForNumber()) - .property("firstTry", codecForAbsoluteTime) - .property("nextTry", codecForAbsoluteTime) - .property("lastError", codecForTalerErrorDetail()) + .property("retryCounter", codecOptional(codecForNumber())) + .property("firstTry", codecOptional(codecForAbsoluteTime)) + .property("nextTry", codecOptional(codecForAbsoluteTime)) + .property("lastError", codecOptional(codecForTalerErrorDetail())) .build("ActiveTask"); -export const codecForGetActiveTasks = (): Codec<GetActiveTasks> => - buildCodecForObject<GetActiveTasks>() +export const codecForGetActiveTasks = (): Codec<GetActiveTasksResponse> => + buildCodecForObject<GetActiveTasksResponse>() .property("tasks", codecForList(codecForActiveTask())) .build("GetActiveTasks"); diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index f493a6b8b..ba28c009a 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -60,7 +60,7 @@ import { FailTransactionRequest, ForceRefreshRequest, ForgetKnownBankAccountsRequest, - GetActiveTasks, + GetActiveTasksResponse, GetAmountRequest, GetBalanceDetailRequest, GetContractTermsDetailsRequest, @@ -235,11 +235,6 @@ export enum WalletApiOperation { Recycle = "recycle", ApplyDevExperiment = "applyDevExperiment", ValidateIban = "validateIban", - TestingWaitTransactionsFinal = "testingWaitTransactionsFinal", - TestingWaitRefreshesFinal = "testingWaitRefreshesFinal", - TestingWaitTransactionState = "testingWaitTransactionState", - TestingWaitTasksDone = "testingWaitTasksDone", - TestingSetTimetravel = "testingSetTimetravel", GetCurrencySpecification = "getCurrencySpecification", ListStoredBackups = "listStoredBackups", CreateStoredBackup = "createStoredBackup", @@ -248,7 +243,6 @@ export enum WalletApiOperation { UpdateExchangeEntry = "updateExchangeEntry", ListExchangesForScopedCurrency = "listExchangesForScopedCurrency", PrepareWithdrawExchange = "prepareWithdrawExchange", - TestingInfiniteTransactionLoop = "testingInfiniteTransactionLoop", GetExchangeResources = "getExchangeResources", DeleteExchange = "deleteExchange", ListGlobalCurrencyExchanges = "listGlobalCurrencyExchanges", @@ -258,6 +252,12 @@ export enum WalletApiOperation { AddGlobalCurrencyAuditor = "addGlobalCurrencyAuditor", RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor", ListAssociatedRefreshes = "listAssociatedRefreshes", + TestingWaitTransactionsFinal = "testingWaitTransactionsFinal", + TestingWaitRefreshesFinal = "testingWaitRefreshesFinal", + TestingWaitTransactionState = "testingWaitTransactionState", + TestingWaitTasksDone = "testingWaitTasksDone", + TestingSetTimetravel = "testingSetTimetravel", + TestingInfiniteTransactionLoop = "testingInfiniteTransactionLoop", TestingListTaskForTransaction = "testingListTasksForTransaction", TestingGetDenomStats = "testingGetDenomStats", TestingPing = "testingPing", @@ -1074,7 +1074,7 @@ export type GetPendingTasksOp = { export type GetActiveTasksOp = { op: WalletApiOperation.GetActiveTasks; request: EmptyObject; - response: GetActiveTasks; + response: GetActiveTasksResponse; }; /** diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 45f9e6078..f743e82b1 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1043,8 +1043,8 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( const lastError = d?.lastError; return { - id: taskId, - counter, + taskId: taskId, + retryCounter: counter, firstTry, nextTry, lastError, diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx index 1c566c3e4..69a2c0675 100644 --- a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx +++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx @@ -22,7 +22,7 @@ import { TalerErrorDetail, TaskProgressNotification, WalletNotification, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -37,61 +37,80 @@ import { WxApiType } from "../wxApi.js"; import { Modal } from "./Modal.js"; import { Time } from "./Time.js"; -interface Props extends JSX.HTMLAttributes { -} +interface Props extends JSX.HTMLAttributes {} -export function WalletActivity({ }: Props): VNode { - const { i18n } = useTranslationContext() - const [settings, updateSettings] = useSettings() +export function WalletActivity({}: Props): VNode { + const { i18n } = useTranslationContext(); + const [settings, updateSettings] = useSettings(); const api = useBackendContext(); useEffect(() => { - document.body.style.marginBottom = "250px" + document.body.style.marginBottom = "250px"; return () => { - document.body.style.marginBottom = "0px" - } - }) - const [table, setTable] = useState<"tasks" | "events">("tasks") + document.body.style.marginBottom = "0px"; + }; + }); + const [table, setTable] = useState<"tasks" | "events">("tasks"); return ( - <div style={{ position: "fixed", bottom: 0, background: "white", zIndex: 1, height: 250, overflowY: "scroll", width: "100%" }}> - <div style={{ display: "flex", justifyContent: "space-between", float: "right" }}> + <div + style={{ + position: "fixed", + bottom: 0, + background: "white", + zIndex: 1, + height: 250, + overflowY: "scroll", + width: "100%", + }} + > + <div + style={{ + display: "flex", + justifyContent: "space-between", + float: "right", + }} + > <div /> <div> - <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => { - updateSettings("showWalletActivity", false) - }}> + <div + style={{ padding: 4, margin: 2, border: "solid 1px black" }} + onClick={() => { + updateSettings("showWalletActivity", false); + }} + > close </div> </div> </div> <div style={{ display: "flex", justifyContent: "space-around" }}> - <Button variant={table === "tasks" ? "contained" : "outlined"} + <Button + variant={table === "tasks" ? "contained" : "outlined"} style={{ margin: 4 }} onClick={async () => { - setTable("tasks") + setTable("tasks"); }} > <i18n.Translate>Tasks</i18n.Translate> </Button> - <Button variant={table === "events" ? "contained" : "outlined"} + <Button + variant={table === "events" ? "contained" : "outlined"} style={{ margin: 4 }} onClick={async () => { - setTable("events") + setTable("events"); }} > <i18n.Translate>Events</i18n.Translate> </Button> - </div> {(function (): VNode { switch (table) { case "events": { - return <ObservabilityEventsTable /> + return <ObservabilityEventsTable />; } case "tasks": { - return <ActiveTasksTable /> + return <ActiveTasksTable />; } default: { - assertUnreachable(table) + assertUnreachable(table); } } })()} @@ -99,398 +118,554 @@ export function WalletActivity({ }: Props): VNode { ); } -interface MoreInfoPRops { events: (WalletNotification & { when: AbsoluteTime })[], onClick: (content: VNode) => void } +interface MoreInfoPRops { + events: (WalletNotification & { when: AbsoluteTime })[]; + onClick: (content: VNode) => void; +} type Notif = { id: string; events: (WalletNotification & { when: AbsoluteTime })[]; description: string; start: AbsoluteTime; end: AbsoluteTime; - reference: { - eventType: NotificationType, - referenceType: "task" | "transaction" | "operation" | "exchange", - id: string; - } | undefined, + reference: + | { + eventType: NotificationType; + referenceType: "task" | "transaction" | "operation" | "exchange"; + id: string; + } + | undefined; MoreInfo: (p: MoreInfoPRops) => VNode; -} +}; function ShowBalanceChange({ events }: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; if (not.type !== NotificationType.BalanceChange) return <Fragment />; - return <Fragment> - <dt>Transaction</dt> - <dd> - <a title={not.hintTransactionId} href={Pages.balanceTransaction({ tid: not.hintTransactionId })}>{not.hintTransactionId.substring(0, 10)}</a> - </dd> - </Fragment> + return ( + <Fragment> + <dt>Transaction</dt> + <dd> + <a + title={not.hintTransactionId} + href={Pages.balanceTransaction({ tid: not.hintTransactionId })} + > + {not.hintTransactionId.substring(0, 10)} + </a> + </dd> + </Fragment> + ); } function ShowBackupOperationError({ events, onClick }: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; if (not.type !== NotificationType.BackupOperationError) return <Fragment />; - return <Fragment> - <dt>Error</dt> - <dd> - <a href="#" onClick={(e) => { - e.preventDefault(); - const error = not.error - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - </Fragment>) - }}>{TalerErrorCode[not.error.code]}</a> - </dd> - </Fragment> -} - -function ShowTransactionStateTransition({ events, onClick }: MoreInfoPRops): VNode { - if (!events.length) return <Fragment />; - const not = events[0]; - if (not.type !== NotificationType.TransactionStateTransition) return <Fragment />; - return <Fragment> - <dt>Old state</dt> - <dd> - {not.oldTxState.major} - {not.oldTxState.minor ?? ""} - </dd> - <dt>New state</dt> - <dd> - {not.newTxState.major} - {not.newTxState.minor ?? ""} - </dd> - <dt>Transaction</dt> - <dd> - <a title={not.transactionId} href={Pages.balanceTransaction({ tid: not.transactionId })}>{not.transactionId.substring(0, 10)}</a> - </dd> - {not.errorInfo ? <Fragment> + return ( + <Fragment> <dt>Error</dt> <dd> - <a href="#" onClick={(e) => { - if (!not.errorInfo) return; - e.preventDefault(); - const error = not.errorInfo; - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Message</dt> - <dd>{error.message ?? "--"}</dd> - </dl> - </Fragment>) - - }}>{TalerErrorCode[not.errorInfo.code]}</a> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + const error = not.error; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + </dd> + </dl> + <pre + style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + > + {JSON.stringify(error, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + {TalerErrorCode[not.error.code]} + </a> </dd> - </Fragment> : undefined} - <dt>Experimental</dt> - <dd> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(not.experimentalUserData, undefined, 2)} - </pre> - </dd> - - - </Fragment> + </Fragment> + ); } -function ShowExchangeStateTransition({ events, onClick }: MoreInfoPRops): VNode { + +function ShowTransactionStateTransition({ + events, + onClick, +}: MoreInfoPRops): VNode { if (!events.length) return <Fragment />; const not = events[0]; - if (not.type !== NotificationType.ExchangeStateTransition) return <Fragment />; - return <Fragment> - <dt>Exchange</dt> - <dd> - {not.exchangeBaseUrl} - </dd> - {not.oldExchangeState && not.newExchangeState.exchangeEntryStatus !== not.oldExchangeState?.exchangeEntryStatus && <Fragment> - <dt>Entry status</dt> + if (not.type !== NotificationType.TransactionStateTransition) + return <Fragment />; + return ( + <Fragment> + <dt>Old state</dt> <dd> - from {not.oldExchangeState.exchangeEntryStatus} to {not.newExchangeState.exchangeEntryStatus} + {not.oldTxState.major} - {not.oldTxState.minor ?? ""} </dd> - </Fragment>} - {not.oldExchangeState && not.newExchangeState.exchangeUpdateStatus !== not.oldExchangeState?.exchangeUpdateStatus && <Fragment> - <dt>Update status</dt> + <dt>New state</dt> <dd> - from {not.oldExchangeState.exchangeUpdateStatus} to {not.newExchangeState.exchangeUpdateStatus} + {not.newTxState.major} - {not.newTxState.minor ?? ""} </dd> - </Fragment>} - {not.oldExchangeState && not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && <Fragment> - <dt>Tos status</dt> + <dt>Transaction</dt> <dd> - from {not.oldExchangeState.tosStatus} to {not.newExchangeState.tosStatus} + <a + title={not.transactionId} + href={Pages.balanceTransaction({ tid: not.transactionId })} + > + {not.transactionId.substring(0, 10)} + </a> </dd> - </Fragment>} - </Fragment> + {not.errorInfo ? ( + <Fragment> + <dt>Error</dt> + <dd> + <a + href="#" + onClick={(e) => { + if (!not.errorInfo) return; + e.preventDefault(); + const error = not.errorInfo; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Message</dt> + <dd>{error.message ?? "--"}</dd> + </dl> + </Fragment>, + ); + }} + > + {TalerErrorCode[not.errorInfo.code]} + </a> + </dd> + </Fragment> + ) : undefined} + <dt>Experimental</dt> + <dd> + <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> + {JSON.stringify(not.experimentalUserData, undefined, 2)} + </pre> + </dd> + </Fragment> + ); +} +function ShowExchangeStateTransition({ + events, + onClick, +}: MoreInfoPRops): VNode { + if (!events.length) return <Fragment />; + const not = events[0]; + if (not.type !== NotificationType.ExchangeStateTransition) + return <Fragment />; + return ( + <Fragment> + <dt>Exchange</dt> + <dd>{not.exchangeBaseUrl}</dd> + {not.oldExchangeState && + not.newExchangeState.exchangeEntryStatus !== + not.oldExchangeState?.exchangeEntryStatus && ( + <Fragment> + <dt>Entry status</dt> + <dd> + from {not.oldExchangeState.exchangeEntryStatus} to{" "} + {not.newExchangeState.exchangeEntryStatus} + </dd> + </Fragment> + )} + {not.oldExchangeState && + not.newExchangeState.exchangeUpdateStatus !== + not.oldExchangeState?.exchangeUpdateStatus && ( + <Fragment> + <dt>Update status</dt> + <dd> + from {not.oldExchangeState.exchangeUpdateStatus} to{" "} + {not.newExchangeState.exchangeUpdateStatus} + </dd> + </Fragment> + )} + {not.oldExchangeState && + not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && ( + <Fragment> + <dt>Tos status</dt> + <dd> + from {not.oldExchangeState.tosStatus} to{" "} + {not.newExchangeState.tosStatus} + </dd> + </Fragment> + )} + </Fragment> + ); } -type ObservaNotifWithTime = ((TaskProgressNotification | RequestProgressNotification) & { +type ObservaNotifWithTime = ( + | TaskProgressNotification + | RequestProgressNotification +) & { when: AbsoluteTime; -}) +}; function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode { // let prev: ObservaNotifWithTime; - const asd = events.map(not => { - if (not.type !== NotificationType.RequestObservabilityEvent && not.type !== NotificationType.TaskObservabilityEvent) return <Fragment />; + const asd = events.map((not) => { + if ( + not.type !== NotificationType.RequestObservabilityEvent && + not.type !== NotificationType.TaskObservabilityEvent + ) + return <Fragment />; const title = (function () { switch (not.event.type) { case ObservabilityEventType.HttpFetchFinishError: case ObservabilityEventType.HttpFetchFinishSuccess: - case ObservabilityEventType.HttpFetchStart: return "HTTP Request" + case ObservabilityEventType.HttpFetchStart: + return "HTTP Request"; case ObservabilityEventType.DbQueryFinishSuccess: case ObservabilityEventType.DbQueryFinishError: - case ObservabilityEventType.DbQueryStart: return "Database" + case ObservabilityEventType.DbQueryStart: + return "Database"; case ObservabilityEventType.RequestFinishSuccess: case ObservabilityEventType.RequestFinishError: - case ObservabilityEventType.RequestStart: return "Wallet" + case ObservabilityEventType.RequestStart: + return "Wallet"; case ObservabilityEventType.CryptoFinishSuccess: case ObservabilityEventType.CryptoFinishError: - case ObservabilityEventType.CryptoStart: return "Crypto" - case ObservabilityEventType.TaskStart: return "Task start" - case ObservabilityEventType.TaskStop: return "Task stop" - case ObservabilityEventType.TaskReset: return "Task reset" - case ObservabilityEventType.ShepherdTaskResult: return "Schedule" - case ObservabilityEventType.DeclareTaskDependency: return "Task dependency" - case ObservabilityEventType.Message: return "Message" + case ObservabilityEventType.CryptoStart: + return "Crypto"; + case ObservabilityEventType.TaskStart: + return "Task start"; + case ObservabilityEventType.TaskStop: + return "Task stop"; + case ObservabilityEventType.TaskReset: + return "Task reset"; + case ObservabilityEventType.ShepherdTaskResult: + return "Schedule"; + case ObservabilityEventType.DeclareTaskDependency: + return "Task dependency"; + case ObservabilityEventType.Message: + return "Message"; } })(); - return <ShowObervavilityDetails title={title} notif={not} onClick={onClick} /> - - }) - return <table> - <thead> - <td>Event</td> - <td>Info</td> - <td>Start</td> - <td>End</td> - </thead> - <tbody> - {asd} - </tbody> - </table> + return ( + <ShowObervavilityDetails title={title} notif={not} onClick={onClick} /> + ); + }); + return ( + <table> + <thead> + <td>Event</td> + <td>Info</td> + <td>Start</td> + <td>End</td> + </thead> + <tbody>{asd}</tbody> + </table> + ); } -function ShowObervavilityDetails({ title, notif, onClick, prev }: { title: string, notif: ObservaNotifWithTime, prev?: ObservaNotifWithTime, onClick: (content: VNode) => void }): VNode { +function ShowObervavilityDetails({ + title, + notif, + onClick, + prev, +}: { + title: string; + notif: ObservaNotifWithTime; + prev?: ObservaNotifWithTime; + onClick: (content: VNode) => void; +}): VNode { switch (notif.event.type) { case ObservabilityEventType.HttpFetchStart: case ObservabilityEventType.HttpFetchFinishError: case ObservabilityEventType.HttpFetchFinishSuccess: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} - > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {notif.event.url} { - prev?.event.type === ObservabilityEventType.HttpFetchFinishSuccess ? `(${prev.event.status})` - : prev?.event.type === ObservabilityEventType.HttpFetchFinishError ? <a href="#" onClick={(e) => { + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { e.preventDefault(); - if (prev.event.type !== ObservabilityEventType.HttpFetchFinishError) return; - const error = prev.event.error - onClick(<Fragment> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - - </Fragment>) - }}>fail</a> : undefined - } - </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" - /></td> - </tr> - + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + {title} + </a> + </td> + <td> + {notif.event.url}{" "} + {prev?.event.type === + ObservabilityEventType.HttpFetchFinishSuccess ? ( + `(${prev.event.status})` + ) : prev?.event.type === + ObservabilityEventType.HttpFetchFinishError ? ( + <a + href="#" + onClick={(e) => { + e.preventDefault(); + if ( + prev.event.type !== + ObservabilityEventType.HttpFetchFinishError + ) + return; + const error = prev.event.error; + onClick( + <Fragment> + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time + timestamp={error.when} + format="yyyy/MM/dd HH:mm:ss" + /> + </dd> + </dl> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify(error, undefined, 2)} + </pre> + </Fragment>, + ); + }} + > + fail + </a> + ) : undefined} + </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" /> + </td> + </tr> + ); } case ObservabilityEventType.DbQueryStart: case ObservabilityEventType.DbQueryFinishSuccess: case ObservabilityEventType.DbQueryFinishError: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></td> - <td> - {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" - /> - </td> - </tr> + {title} + </a> + </td> + <td> + {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" /> + </td> + </tr> + ); } case ObservabilityEventType.TaskStart: case ObservabilityEventType.TaskStop: case ObservabilityEventType.DeclareTaskDependency: case ObservabilityEventType.TaskReset: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></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" - /> - </td> - </tr> + {title} + </a> + </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" /> + </td> + </tr> + ); } case ObservabilityEventType.ShepherdTaskResult: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></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" - /> - </td> - </tr> - + {title} + </a> + </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" /> + </td> + </tr> + ); } case ObservabilityEventType.CryptoStart: case ObservabilityEventType.CryptoFinishSuccess: case ObservabilityEventType.CryptoFinishError: { - return <tr> - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></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" - /> - </td> - </tr> + {title} + </a> + </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" /> + </td> + </tr> + ); } case ObservabilityEventType.RequestStart: case ObservabilityEventType.RequestFinishSuccess: case ObservabilityEventType.RequestFinishError: { - return <tr > - <td><a href="#" onClick={(e) => { - e.preventDefault(); - onClick(<Fragment> - <pre - style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }} + return ( + <tr> + <td> + <a + href="#" + onClick={(e) => { + e.preventDefault(); + onClick( + <Fragment> + <pre + style={{ + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {JSON.stringify({ event: notif, prev }, undefined, 2)} + </pre> + </Fragment>, + ); + }} > - {JSON.stringify({ event: notif, prev }, undefined, 2)} - </pre> - </Fragment>); - }}>{title}</a></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" - /> - </td> - </tr> + {title} + </a> + </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" /> + </td> + </tr> + ); } case ObservabilityEventType.Message: // FIXME @@ -498,11 +673,16 @@ function ShowObervavilityDetails({ title, notif, onClick, prev }: { title: strin } } -function getNotificationFor(id: string, event: WalletNotification, start: AbsoluteTime, list: Notif[]): Notif | undefined { - const eventWithTime = { ...event, when: start } +function getNotificationFor( + id: string, + event: WalletNotification, + start: AbsoluteTime, + list: Notif[], +): Notif | undefined { + const eventWithTime = { ...event, when: start }; switch (event.type) { case NotificationType.BalanceChange: { - return ({ + return { id, events: [eventWithTime], reference: { @@ -513,28 +693,32 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: "Balance change", start, end: AbsoluteTime.never(), - MoreInfo: ShowBalanceChange - }) + MoreInfo: ShowBalanceChange, + }; } case NotificationType.BackupOperationError: { - return ({ + return { id, events: [eventWithTime], reference: undefined, description: "Backup error", start, end: AbsoluteTime.never(), - MoreInfo: ShowBackupOperationError - }) + MoreInfo: ShowBackupOperationError, + }; } case NotificationType.TransactionStateTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.transactionId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.transactionId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -545,17 +729,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: event.type, start, end: AbsoluteTime.never(), - MoreInfo: ShowTransactionStateTransition - }) + MoreInfo: ShowTransactionStateTransition, + }; } case NotificationType.ExchangeStateTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.exchangeBaseUrl) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.exchangeBaseUrl, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], description: "Exchange update", @@ -566,17 +754,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu }, start, end: AbsoluteTime.never(), - MoreInfo: ShowExchangeStateTransition - }) + MoreInfo: ShowExchangeStateTransition, + }; } case NotificationType.TaskObservabilityEvent: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.taskId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.taskId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -587,17 +779,20 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `Task update ${event.taskId}`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } case NotificationType.WithdrawalOperationTransition: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.uri) + const found = list.find( + (a) => + a.reference?.eventType === event.type && a.reference.id === event.uri, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -608,17 +803,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `Withdrawal operation updated`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } case NotificationType.RequestObservabilityEvent: { - const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.requestId) + const found = list.find( + (a) => + a.reference?.eventType === event.type && + a.reference.id === event.requestId, + ); if (found) { found.end = start; - found.events.unshift(eventWithTime) - return undefined + found.events.unshift(eventWithTime); + return undefined; } - return ({ + return { id, events: [eventWithTime], reference: { @@ -629,145 +828,183 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu description: `wallet.${event.operation}(${event.requestId})`, start, end: AbsoluteTime.never(), - MoreInfo: ShowObservabilityEvent - }) + MoreInfo: ShowObservabilityEvent, + }; } + case NotificationType.Idle: + return undefined; default: { - assertUnreachable(event) + assertUnreachable(event); } } } - function refresh(api: WxApiType, onUpdate: (list: Notif[]) => void) { - api.background.call("getNotifications", undefined).then(notif => { - - const list: Notif[] = [] - for (const n of notif) { - if (n.notification.type === NotificationType.RequestObservabilityEvent && - n.notification.operation === "getActiveTasks") { - //ignore monitor request - continue; - } - const event = getNotificationFor(String(list.length), n.notification, n.when, list) - // pepe. - if (event) { - list.unshift(event) + api.background + .call("getNotifications", undefined) + .then((notif) => { + const list: Notif[] = []; + for (const n of notif) { + if ( + n.notification.type === NotificationType.RequestObservabilityEvent && + n.notification.operation === "getActiveTasks" + ) { + //ignore monitor request + continue; + } + const event = getNotificationFor( + String(list.length), + n.notification, + n.when, + list, + ); + // pepe. + if (event) { + list.unshift(event); + } } - } - onUpdate(list); - }).catch(error => { - console.log(error) - }) + onUpdate(list); + }) + .catch((error) => { + console.log(error); + }); } -export function ObservabilityEventsTable({ }: {}): VNode { - const { i18n } = useTranslationContext() +export function ObservabilityEventsTable({}: {}): VNode { + const { i18n } = useTranslationContext(); const api = useBackendContext(); - const [notifications, setNotifications] = useState<Notif[]>([]) - const [showDetails, setShowDetails] = useState<VNode>() + const [notifications, setNotifications] = useState<Notif[]>([]); + const [showDetails, setShowDetails] = useState<VNode>(); useEffect(() => { let lastTimeout: ReturnType<typeof setTimeout>; function periodicRefresh() { - - refresh(api, setNotifications) + refresh(api, setNotifications); lastTimeout = setTimeout(() => { periodicRefresh(); - }, 1000) + }, 1000); - //clear on unload - return () => { clearTimeout(lastTimeout) } + //clear on unload + return () => { + clearTimeout(lastTimeout); + }; } - return periodicRefresh() + return periodicRefresh(); }, [1]); - return <div> - <div style={{ display: "flex", justifyContent: "space-between" }}> - - <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => { - api.background.call("clearNotifications", undefined).then(d => { - refresh(api, setNotifications) - }) - }}> - clear + return ( + <div> + <div style={{ display: "flex", justifyContent: "space-between" }}> + <div + style={{ padding: 4, margin: 2, border: "solid 1px black" }} + onClick={() => { + api.background.call("clearNotifications", undefined).then((d) => { + refresh(api, setNotifications); + }); + }} + > + clear + </div> </div> - - - </div> - {showDetails && <Modal title="event details" onClose={{ onClick: (async () => { setShowDetails(undefined) }) as any }} > - {showDetails} - </Modal>} - {notifications.map((not) => { - return ( - <details key={not.id}> - <summary> - <div style={{ width: "90%", display: "inline-flex", justifyContent: "space-between", padding: 4 }}> - <div style={{ padding: 4 }}> - {not.description} - </div> - <div style={{ padding: 4 }}> - <Time - timestamp={not.start} - format="yyyy/MM/dd HH:mm:ss" - /> + {showDetails && ( + <Modal + title="event details" + onClose={{ + onClick: (async () => { + setShowDetails(undefined); + }) as any, + }} + > + {showDetails} + </Modal> + )} + {notifications.map((not) => { + return ( + <details key={not.id}> + <summary> + <div + style={{ + width: "90%", + display: "inline-flex", + justifyContent: "space-between", + padding: 4, + }} + > + <div style={{ padding: 4 }}>{not.description}</div> + <div style={{ padding: 4 }}> + <Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" /> + </div> + <div style={{ padding: 4 }}> + <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss" /> + </div> </div> - <div style={{ padding: 4 }}><Time - timestamp={not.end} - format="yyyy/MM/dd HH:mm:ss" - /></div> - </div> - </summary> - <not.MoreInfo events={not.events} onClick={(details) => { - setShowDetails(details) - }} /> - </details> - ); - })} - </div > + </summary> + <not.MoreInfo + events={not.events} + onClick={(details) => { + setShowDetails(details); + }} + /> + </details> + ); + })} + </div> + ); } -function ErroDetailModal({ error, onClose }: { error: TalerErrorDetail, onClose: () => void }): VNode { - return <Modal title="Full detail" onClose={{ - onClick: onClose as any - }}> - <dl> - <dt>Code</dt> - <dd>{TalerErrorCode[error.code]} ({error.code})</dd> - <dt>Hint</dt> - <dd>{error.hint ?? "--"}</dd> - <dt>Time</dt> - <dd><Time - timestamp={error.when} - format="yyyy/MM/dd HH:mm:ss" - /></dd> - </dl> - <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> - {JSON.stringify(error, undefined, 2)} - </pre> - </Modal> +function ErroDetailModal({ + error, + onClose, +}: { + error: TalerErrorDetail; + onClose: () => void; +}): VNode { + return ( + <Modal + title="Full detail" + onClose={{ + onClick: onClose as any, + }} + > + <dl> + <dt>Code</dt> + <dd> + {TalerErrorCode[error.code]} ({error.code}) + </dd> + <dt>Hint</dt> + <dd>{error.hint ?? "--"}</dd> + <dt>Time</dt> + <dd> + <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" /> + </dd> + </dl> + <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> + {JSON.stringify(error, undefined, 2)} + </pre> + </Modal> + ); } -export function ActiveTasksTable({ }: {}): VNode { - const { i18n } = useTranslationContext() +export function ActiveTasksTable({}: {}): VNode { + const { i18n } = useTranslationContext(); const api = useBackendContext(); const state = useAsyncAsHook(() => { return api.wallet.call(WalletApiOperation.GetActiveTasks, {}); }); - const [showError, setShowError] = useState<TalerErrorDetail>() + const [showError, setShowError] = useState<TalerErrorDetail>(); const tasks = state && !state.hasError ? state.response.tasks : []; useEffect(() => { - if (!state || state.hasError) return + if (!state || state.hasError) return; const lastTimeout = setTimeout(() => { state.retry(); - }, 1000) + }, 1000); return () => { - clearTimeout(lastTimeout) - } - }, [tasks]) + clearTimeout(lastTimeout); + }; + }, [tasks]); // const listenAllEvents = Array.from<NotificationType>({ length: 1 }); // listenAllEvents.includes = () => true @@ -776,59 +1013,88 @@ export function ActiveTasksTable({ }: {}): VNode { // state?.retry() // }); // }); - return <Fragment> - {showError && <ErroDetailModal error={showError} onClose={(async () => { setShowError(undefined) })} />} + return ( + <Fragment> + {showError && ( + <ErroDetailModal + error={showError} + onClose={async () => { + setShowError(undefined); + }} + /> + )} - <table style={{ width: "100%" }}> - <thead> - <tr> - <th> - <i18n.Translate>Type</i18n.Translate> - </th> - <th> - <i18n.Translate>Id</i18n.Translate> - </th> - <th> - <i18n.Translate>Since</i18n.Translate> - </th> - <th> - <i18n.Translate>Next try</i18n.Translate> - </th> - <th> - <i18n.Translate>Error</i18n.Translate> - </th> - <th> - <i18n.Translate>Transaction</i18n.Translate> - </th> - </tr> - </thead> - <tbody> - {tasks.map((task) => { - const [type, id] = task.id.split(":") - return ( - <tr> - <td>{type}</td> - <td title={id}>{id.substring(0, 10)}</td> - <td> - <Time - timestamp={task.firstTry} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td> - <Time - timestamp={task.nextTry} - format="yyyy/MM/dd HH:mm:ss" - /> - </td> - <td>{!task.lastError?.code ? "" : <a href="#" onClick={(e) => { e.preventDefault(); setShowError(task.lastError) }}>{TalerErrorCode[task.lastError.code]}</a>}</td> - <td> - {task.transaction ? <a title={task.transaction} href={Pages.balanceTransaction({ tid: task.transaction })}>{task.transaction.substring(0, 10)}</a> : "--"} - </td> - </tr> - ); - })} - </tbody> - </table> - </Fragment> -}
\ No newline at end of file + <table style={{ width: "100%" }}> + <thead> + <tr> + <th> + <i18n.Translate>Type</i18n.Translate> + </th> + <th> + <i18n.Translate>Id</i18n.Translate> + </th> + <th> + <i18n.Translate>Since</i18n.Translate> + </th> + <th> + <i18n.Translate>Next try</i18n.Translate> + </th> + <th> + <i18n.Translate>Error</i18n.Translate> + </th> + <th> + <i18n.Translate>Transaction</i18n.Translate> + </th> + </tr> + </thead> + <tbody> + {tasks.map((task) => { + const [type, id] = task.taskId.split(":"); + return ( + <tr> + <td>{type}</td> + <td title={id}>{id.substring(0, 10)}</td> + <td> + <Time + timestamp={task.firstTry} + format="yyyy/MM/dd HH:mm:ss" + /> + </td> + <td> + <Time timestamp={task.nextTry} format="yyyy/MM/dd HH:mm:ss" /> + </td> + <td> + {!task.lastError?.code ? ( + "" + ) : ( + <a + href="#" + onClick={(e) => { + e.preventDefault(); + setShowError(task.lastError); + }} + > + {TalerErrorCode[task.lastError.code]} + </a> + )} + </td> + <td> + {task.transaction ? ( + <a + title={task.transaction} + href={Pages.balanceTransaction({ tid: task.transaction })} + > + {task.transaction.substring(0, 10)} + </a> + ) : ( + "--" + )} + </td> + </tr> + ); + })} + </tbody> + </table> + </Fragment> + ); +} diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 315ab5332..bf70f68df 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -341,11 +341,6 @@ async function reinitWallet(): Promise<void> { }); }); - platform.keepAlive(() => { - return wallet.runTaskLoop().catch((e) => { - logger.error("error during wallet task loop", e); - }); - }); // Useful for debugging in the background page. if (typeof window !== "undefined") { (window as any).talerWallet = wallet; |