Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ConnectWalletForm): ask consent for automatic key addition #637

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,6 @@
"connectWallet_action_connect": {
"message": "Connect"
},
"connectWallet_text_footerNotice": {
"message": "We'll automatically connect with your wallet provider."
},
"connectWallet_text_footerNoticeLearnMore": {
"message": "Learn more",
"description": "Learn more about how this works"
},
"connectWallet_error_urlRequired": {
"message": "Wallet address is required."
},
Expand Down Expand Up @@ -172,9 +165,31 @@
"connectWallet_error_grantRejected": {
"message": "Connect wallet cancelled. You rejected the request."
},
"connectWalletKeyService_text_consentP1": {
"message": "We'll automatically connect with your wallet provider."
},
"connectWalletKeyService_text_consentLearnMore": {
"message": "Learn more",
"description": "Learn more about how this works"
},
"connectWalletKeyService_text_consentP2": {
"message": "By agreeing, you provide us consent to automatically access your wallet to securely add a key."
},
"connectWalletKeyService_text_consentP3": {
"message": "Please note, this process does not involve accessing or handling your funds."
},
"connectWalletKeyService_label_consentAccept": {
"message": "Agree"
},
"connectWalletKeyService_label_consentDecline": {
"message": "Decline"
},
"connectWalletKeyService_error_notImplemented": {
"message": "Automatic key addition is not implemented for given wallet provider yet."
},
"connectWalletKeyService_error_noConsent": {
"message": "You declined consent for automatic key addition."
},
"connectWalletKeyService_error_failed": {
"message": "Automatic key addition failed at step “$STEP_ID$” with message “$MESSAGE$”.",
"placeholders": {
Expand Down
9 changes: 9 additions & 0 deletions src/background/services/keyAutoAdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ export class KeyAutoAddService {
}));
}
}

static supports(walletAddress: WalletAddress): boolean {
try {
void walletAddressToProvider(walletAddress);
return true;
} catch {
return false;
}
}
}

export function walletAddressToProvider(walletAddress: WalletAddress): {
Expand Down
21 changes: 19 additions & 2 deletions src/background/services/openPayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,13 @@ export class OpenPaymentsService {
this.setConnectState(null);
return;
}
const { walletAddressUrl, amount, recurring, skipAutoKeyShare } = params;
const {
walletAddressUrl,
amount,
recurring,
autoKeyAdd,
autoKeyAddConsent,
} = params;

const walletAddress = await getWalletInformation(walletAddressUrl);
const exchangeRates = await getExchangeRates();
Expand Down Expand Up @@ -385,8 +391,19 @@ export class OpenPaymentsService {
if (
isErrorWithKey(error) &&
error.key === 'connectWallet_error_invalidClient' &&
!skipAutoKeyShare
autoKeyAdd
) {
if (!KeyAutoAddService.supports(walletAddress)) {
this.updateConnectStateError(error);
throw new ErrorWithKey(
'connectWalletKeyService_error_notImplemented',
);
}
if (!autoKeyAddConsent) {
this.updateConnectStateError(error);
throw new ErrorWithKey('connectWalletKeyService_error_noConsent');
}

// add key to wallet and try again
try {
const tabId = await this.addPublicKeyToWallet(walletAddress);
Expand Down
105 changes: 73 additions & 32 deletions src/popup/components/ConnectWalletForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Inputs {
walletAddressUrl: string;
amount: string;
recurring: boolean;
autoKeyAddConsent: boolean;
}

interface ConnectWalletFormProps {
Expand Down Expand Up @@ -62,9 +63,14 @@ export const ConnectWalletForm = ({
const [recurring, setRecurring] = React.useState<Inputs['recurring']>(
defaultValues.recurring || false,
);

const [autoKeyShareFailed, setAutoKeyShareFailed] = React.useState(
isAutoKeyAddFailed(state),
);
const [showConsent, setShowConsent] = React.useState(false);
const autoKeyAddConsent = React.useRef<boolean>(
defaultValues.autoKeyAddConsent || false,
);

const resetState = React.useCallback(async () => {
await clearConnectState();
Expand Down Expand Up @@ -156,12 +162,8 @@ export const ConnectWalletForm = ({
[saveValue, currencySymbol, t],
);

const handleSubmit = async (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
if (!walletAddressInfo) {
setErrors((_) => ({ ..._, walletAddressUrl: 'Not fetched yet?!' }));
return;
}
const handleSubmit = async (ev?: React.FormEvent<HTMLFormElement>) => {
ev?.preventDefault();

const errWalletAddressUrl = validateWalletAddressUrl(walletAddressUrl);
const errAmount = validateAmount(amount, currencySymbol.symbol);
Expand All @@ -186,14 +188,19 @@ export const ConnectWalletForm = ({
walletAddressUrl: toWalletAddressUrl(walletAddressUrl),
amount,
recurring,
skipAutoKeyShare,
autoKeyAdd: !skipAutoKeyShare,
autoKeyAddConsent: autoKeyAddConsent.current,
});
if (res.success) {
onConnect();
} else {
if (isErrorWithKey(res.error)) {
const error = res.error;
if (error.key.startsWith('connectWalletKeyService_error_')) {
if (error.key === 'connectWalletKeyService_error_noConsent') {
setShowConsent(true);
return;
}
setErrors((_) => ({ ..._, keyPair: t(error) }));
} else {
setErrors((_) => ({ ..._, connect: t(error) }));
Expand Down Expand Up @@ -223,6 +230,26 @@ export const ConnectWalletForm = ({
}
}, [defaultValues.walletAddressUrl, handleWalletAddressUrlChange]);

if (showConsent) {
return (
<AutoKeyAddConsent
onAccept={() => {
autoKeyAddConsent.current = true;
// saveValue('autoKeyAddConsent', true);
setShowConsent(false);
handleSubmit();
}}
onDecline={() => {
setErrors((_) => ({
..._,
keyPair: t('connectWalletKeyService_error_noConsent'),
}));
setShowConsent(false);
}}
/>
);
}

return (
<form
data-testid="connect-wallet-form"
Expand Down Expand Up @@ -377,13 +404,46 @@ export const ConnectWalletForm = ({
>
{t('connectWallet_action_connect')}
</Button>
</div>
</form>
);
};

{!errors.keyPair && !autoKeyShareFailed && (
<Footer
text={t('connectWallet_text_footerNotice')}
learnMoreText={t('connectWallet_text_footerNoticeLearnMore')}
/>
)}
const AutoKeyAddConsent: React.FC<{
onAccept: () => void;
onDecline: () => void;
}> = ({ onAccept, onDecline }) => {
const t = useTranslation();
return (
<form
className="space-y-4 text-center"
data-testid="connect-wallet-auto-key-consent"
>
<p className="text-lg leading-snug text-weak">
{t('connectWalletKeyService_text_consentP1')}{' '}
<a
hidden
href="https://webmonetization.org"
className="text-primary hover:underline"
target="_blank"
rel="noreferrer"
>
{t('connectWalletKeyService_text_consentLearnMore')}
</a>
</p>

<div className="space-y-2 pt-12 text-medium">
<p>{t('connectWalletKeyService_text_consentP2')}</p>
<p>{t('connectWalletKeyService_text_consentP3')}</p>
</div>

<div className="mx-auto flex w-3/4 justify-around gap-4">
<Button onClick={onAccept}>
{t('connectWalletKeyService_label_consentAccept')}
</Button>
<Button onClick={onDecline} variant="destructive">
{t('connectWalletKeyService_label_consentDecline')}
</Button>
</div>
</form>
);
Expand Down Expand Up @@ -446,25 +506,6 @@ function isAutoKeyAddFailed(state: PopupTransientState['connect']) {
return false;
}

const Footer: React.FC<{
text: string;
learnMoreText: string;
}> = ({ text, learnMoreText }) => {
return (
<p className="text-center text-xs text-weak">
{text}{' '}
<a
href="https://webmonetization.org"
className="text-primary hover:underline"
target="_blank"
rel="noreferrer"
>
{learnMoreText}
</a>
</p>
);
};

type ErrorCodeWalletAddressUrl = Extract<
ErrorKeys,
`connectWallet_error_url${string}`
Expand Down
3 changes: 3 additions & 0 deletions src/popup/pages/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const Component = () => {
amount: localStorage?.getItem('connect.amount') || undefined,
walletAddressUrl:
localStorage?.getItem('connect.walletAddressUrl') || undefined,
autoKeyAddConsent:
localStorage?.getItem('connect.autoKeyAddConsent') === 'true' ||
false,
}}
saveValue={(key, val) => {
localStorage?.setItem(`connect.${key}`, val.toString());
Expand Down
3 changes: 2 additions & 1 deletion src/shared/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export interface ConnectWalletPayload {
walletAddressUrl: string;
amount: string;
recurring: boolean;
skipAutoKeyShare: boolean;
autoKeyAdd: boolean;
autoKeyAddConsent: boolean | null;
}

export interface AddFundsPayload {
Expand Down
10 changes: 9 additions & 1 deletion tests/e2e/connectAutoKeyTestWallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,17 @@ test('Connect to test wallet with automatic key addition when not logged-in to w
await page.close();
});

page = await test.step('shows login page', async () => {
await test.step('asks for key-add consent', async () => {
await connectButton.click();
await popup.waitForSelector(
`[data-testid="connect-wallet-auto-key-consent"]`,
);

expect(popup.getByTestId('connect-wallet-auto-key-consent')).toBeVisible();
await popup.getByRole('button', { name: 'Accept' }).click();
});

page = await test.step('shows login page', async () => {
const openedPage = await context.waitForEvent('page', {
predicate: (page) => page.url().startsWith(loginPageUrl),
timeout: 3 * 1000,
Expand Down