diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index d113ad87e..e3f4f5e4b 100755 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -54,6 +54,12 @@ "keyRevoked_action_reconnectBtn": { "message": "Reconnect" }, + "disconnectWallet_error_generic": { + "message": "We were unable to disconnect your wallet ($ERROR$).", + "placeholders": { + "ERROR": { "content": "$1", "example": "Internal server error" } + } + }, "pay_action_pay": { "message": "Send now" }, diff --git a/src/background/services/background.ts b/src/background/services/background.ts index 2fa1946b2..5cf564061 100644 --- a/src/background/services/background.ts +++ b/src/background/services/background.ts @@ -266,12 +266,12 @@ export class Background { return; case 'DISCONNECT_WALLET': - await this.walletService.disconnectWallet(); + await this.walletService.disconnectWallet(message.payload.force); this.tabState.clearAllState('disconnect'); await this.browser.alarms.clear(ALARM_RESET_OUT_OF_FUNDS); await this.updateVisualIndicatorsForCurrentTab(); this.sendToPopup.send('SET_STATE', { state: {}, prevState: {} }); - return; + return success(undefined); case 'TOGGLE_CONTINUOUS_PAYMENTS': { await this.monetizationService.toggleContinuousPayments(); diff --git a/src/background/services/openPayments.ts b/src/background/services/openPayments.ts index 5ecbcdad0..a16eafd93 100644 --- a/src/background/services/openPayments.ts +++ b/src/background/services/openPayments.ts @@ -212,7 +212,7 @@ export class OpenPaymentsService { } } -const isOpenPaymentsClientError = (error: unknown) => +export const isOpenPaymentsClientError = (error: unknown) => error instanceof OpenPaymentsClientError; export const isKeyRevokedError = (error: unknown) => { diff --git a/src/background/services/wallet.ts b/src/background/services/wallet.ts index e8baa7011..a45b36431 100644 --- a/src/background/services/wallet.ts +++ b/src/background/services/wallet.ts @@ -23,7 +23,10 @@ import { } from '@/background/utils'; import { KeyAutoAddService } from '@/background/services/keyAutoAdd'; import { generateEd25519KeyPair, exportJWK } from '@/shared/crypto'; -import { isInvalidClientError } from '@/background/services/openPayments'; +import { + isInvalidClientError, + isOpenPaymentsClientError, +} from '@/background/services/openPayments'; import { APP_URL } from '@/background/constants'; import { bytesToHex } from '@noble/hashes/utils'; import type { Cradle } from '@/background/container'; @@ -37,6 +40,7 @@ export class WalletService { private storage: Cradle['storage']; private events: Cradle['events']; private browser: Cradle['browser']; + private logger: Cradle['logger']; private appName: Cradle['appName']; private browserName: Cradle['browserName']; private t: Cradle['t']; @@ -47,6 +51,7 @@ export class WalletService { storage, events, browser, + logger, appName, browserName, t, @@ -57,6 +62,7 @@ export class WalletService { storage, events, browser, + logger, appName, browserName, t, @@ -245,7 +251,7 @@ export class WalletService { this.resetConnectState(); } - async disconnectWallet() { + async disconnectWallet(force = false) { const { recurringGrant, oneTimeGrant } = await this.storage.get([ 'recurringGrant', 'oneTimeGrant', @@ -253,14 +259,33 @@ export class WalletService { if (!recurringGrant && !oneTimeGrant) { return; } + + const handleError = ( + err: unknown, + grantType: 'recurring' | 'oneTime', + force: boolean, + ) => { + this.logger.error(`Could not cancel ${grantType} grant`, { err }); + if (force) return; + + if (isOpenPaymentsClientError(err)) { + throw new ErrorWithKey('disconnectWallet_error_generic', [ + err.status ? `HTTP ${err.status} - ${err.message}` : err.message, + ]); + } + throw err; + }; + if (recurringGrant) { - await this.outgoingPaymentGrantService.cancelGrant( - recurringGrant.continue, - ); + await this.outgoingPaymentGrantService + .cancelGrant(recurringGrant.continue) + .catch((err) => handleError(err, 'recurring', force)); this.outgoingPaymentGrantService.disableRecurringGrant(); } if (oneTimeGrant) { - await this.outgoingPaymentGrantService.cancelGrant(oneTimeGrant.continue); + await this.outgoingPaymentGrantService + .cancelGrant(oneTimeGrant.continue) + .catch((err) => handleError(err, 'recurring', force)); this.outgoingPaymentGrantService.disableOneTimeGrant(); } await this.storage.clear(); diff --git a/src/pages/popup/components/ErrorKeyRevoked.tsx b/src/pages/popup/components/ErrorKeyRevoked.tsx index 01a0f245b..00d969bf3 100644 --- a/src/pages/popup/components/ErrorKeyRevoked.tsx +++ b/src/pages/popup/components/ErrorKeyRevoked.tsx @@ -11,7 +11,7 @@ import type { ReconnectWalletPayload, Response } from '@/shared/messages'; interface Props { info: Pick; - disconnectWallet: () => Promise; + disconnectWallet: (force: boolean) => Promise; reconnectWallet: (data: ReconnectWalletPayload) => Promise; onReconnect?: () => void; onDisconnect?: () => void; @@ -90,7 +90,7 @@ const MainScreen = ({ setErrorMsg(''); try { setIsLoading(true); - await disconnectWallet(); + await disconnectWallet(true); onDisconnect?.(); } catch (error) { setErrorMsg(error.message); diff --git a/src/pages/popup/components/Settings/WalletInformation.tsx b/src/pages/popup/components/Settings/WalletInformation.tsx index 1f66fe816..8a6cd1c13 100644 --- a/src/pages/popup/components/Settings/WalletInformation.tsx +++ b/src/pages/popup/components/Settings/WalletInformation.tsx @@ -4,7 +4,8 @@ import { Input } from '@/pages/shared/components/ui/Input'; import { Label } from '@/pages/shared/components/ui/Label'; import { Code } from '@/pages/shared/components/ui/Code'; import { Button } from '@/pages/shared/components/ui/Button'; -import { useMessage } from '@/popup/lib/context'; +import { toErrorInfoFactory } from '@/pages/shared/lib/utils'; +import { useMessage, useTranslation } from '@/popup/lib/context'; import { ROUTES_PATH } from '@/popup/Popup'; import { useLocation } from 'wouter'; import type { PopupStore } from '@/shared/types'; @@ -18,9 +19,32 @@ export const WalletInformation = ({ publicKey, walletAddress, }: WalletInformationProps) => { + const t = useTranslation(); const message = useMessage(); const [_location, navigate] = useLocation(); + const [disconnectError, setDisconnectError] = React.useState(''); const [isSubmitting, setIsSubmitting] = React.useState(false); + const toErrorInfo = toErrorInfoFactory(t); + + const disconnectWallet = async (force = false) => { + setIsSubmitting(true); + setDisconnectError(''); + try { + const res = await message.send('DISCONNECT_WALLET', { force }); + if (!res.success) { + if (res.error) { + throw new Error(toErrorInfo(res.error)!.message); + } + throw new Error(res.message); + } + navigate(ROUTES_PATH.HOME); + window.location.reload(); + } catch (error) { + setDisconnectError(error.message); + } finally { + setIsSubmitting(false); + } + }; return (
@@ -29,10 +53,7 @@ export const WalletInformation = ({ className="space-y-4" onSubmit={async (ev) => { ev.preventDefault(); - setIsSubmitting(true); - await message.send('DISCONNECT_WALLET'); - navigate(ROUTES_PATH.HOME); - window.location.reload(); + await disconnectWallet(); }} > Disconnect + + {disconnectError && ( +

+ {disconnectError} + +

+ )}
diff --git a/src/pages/popup/pages/ErrorKeyRevoked.tsx b/src/pages/popup/pages/ErrorKeyRevoked.tsx index 92753378f..f00d8b9eb 100644 --- a/src/pages/popup/pages/ErrorKeyRevoked.tsx +++ b/src/pages/popup/pages/ErrorKeyRevoked.tsx @@ -32,7 +32,7 @@ export default () => { info={{ publicKey, walletAddress }} reconnectWallet={(data) => message.send('RECONNECT_WALLET', data)} onReconnect={onReconnect} - disconnectWallet={() => message.send('DISCONNECT_WALLET')} + disconnectWallet={(force) => message.send('DISCONNECT_WALLET', { force })} onDisconnect={onDisconnect} /> ); diff --git a/src/shared/messages.ts b/src/shared/messages.ts index 894b0cc2f..52a9bda0c 100644 --- a/src/shared/messages.ts +++ b/src/shared/messages.ts @@ -177,7 +177,7 @@ export type PopupToBackgroundMessage = { output: never; }; DISCONNECT_WALLET: { - input: never; + input: { force: boolean }; output: never; }; TOGGLE_CONTINUOUS_PAYMENTS: {