Skip to content

Commit

Permalink
refactors signing flows to use a common helper hook
Browse files Browse the repository at this point in the history
  • Loading branch information
aristidesstaffieri committed Aug 25, 2023
1 parent 4b0d05a commit 47ea4f9
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 355 deletions.
137 changes: 137 additions & 0 deletions extension/src/popup/helpers/useSetupSigningFlow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { AppDispatch } from "popup/App";
import { signTransaction, rejectTransaction } from "popup/ducks/access";

import { Account } from "@shared/api/types";

import {
allAccountsSelector,
confirmPassword,
hardwareWalletTypeSelector,
hasPrivateKeySelector,
makeAccountActive,
publicKeySelector,
} from "popup/ducks/accountServices";

import {
ShowOverlayStatus,
startHwSign,
transactionSubmissionSelector,
} from "popup/ducks/transactionSubmission";

export function useSetupSigningFlow(
dispatch: AppDispatch,
reject: typeof rejectTransaction,
signFn: typeof signTransaction,
transactionXdr: string,
accountToSign?: string,
) {
const [isConfirming, setIsConfirming] = useState(false);
const [isPasswordRequired, setIsPasswordRequired] = useState(false);
const [startedHwSign, setStartedHwSign] = useState(false);
const [accountNotFound, setAccountNotFound] = useState(false);
const [currentAccount, setCurrentAccount] = useState({} as Account);

const allAccounts = useSelector(allAccountsSelector);
const hasPrivateKey = useSelector(hasPrivateKeySelector);
const hardwareWalletType = useSelector(hardwareWalletTypeSelector);
const publicKey = useSelector(publicKeySelector);

// the public key the user had selected before starting this flow
const defaultPublicKey = useRef(publicKey);
const allAccountsMap = useRef({} as { [key: string]: Account });
const isHardwareWallet = !!hardwareWalletType;
const {
hardwareWalletData: { status: hwStatus },
} = useSelector(transactionSubmissionSelector);

const rejectAndClose = () => {
dispatch(reject());
window.close();
};

const signAndClose = async () => {
if (isHardwareWallet) {
dispatch(
startHwSign({ transactionXDR: transactionXdr, shouldSubmit: false }),
);
setStartedHwSign(true);
} else {
await dispatch(signFn());
window.close();
}
};

const handleApprove = async () => {
setIsConfirming(true);

if (hasPrivateKey) {
await signAndClose();
} else {
setIsPasswordRequired(true);
}

setIsConfirming(false);
};

const verifyPasswordThenSign = async (password: string) => {
const confirmPasswordResp = await dispatch(confirmPassword(password));

if (confirmPassword.fulfilled.match(confirmPasswordResp)) {
await signAndClose();
}
};

useEffect(() => {
if (startedHwSign && hwStatus === ShowOverlayStatus.IDLE) {
window.close();
}
}, [startedHwSign, hwStatus]);

useEffect(() => {
// handle auto selecting the right account based on `accountToSign`
let autoSelectedAccountDetails;

allAccounts.forEach((account) => {
if (accountToSign) {
// does the user have the `accountToSign` somewhere in the accounts list?
if (account.publicKey === accountToSign) {
// if the `accountToSign` is found, but it isn't active, make it active
if (defaultPublicKey.current !== account.publicKey) {
dispatch(makeAccountActive(account.publicKey));
}

// save the details of the `accountToSign`
autoSelectedAccountDetails = account;
}
}

// create an object so we don't need to keep iterating over allAccounts when we switch accounts
allAccountsMap.current[account.publicKey] = account;
});

if (!autoSelectedAccountDetails) {
setAccountNotFound(true);
}
}, [accountToSign, allAccounts, dispatch]);

useEffect(() => {
// handle any changes to the current acct - whether by auto select or manual select
setCurrentAccount(allAccountsMap.current[publicKey] || ({} as Account));
}, [allAccounts, publicKey]);

return {
allAccounts,
accountNotFound,
currentAccount,
handleApprove,
publicKey,
hwStatus,
isConfirming,
isPasswordRequired,
rejectAndClose,
setIsPasswordRequired,
verifyPasswordThenSign,
};
}
142 changes: 27 additions & 115 deletions extension/src/popup/views/SignAuthEntry/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { useState } from "react";
import { Card, Icon } from "@stellar/design-system";
import { useLocation } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
Expand All @@ -23,137 +23,49 @@ import {
WarningMessage,
} from "popup/components/WarningMessages";
import { signEntry, rejectAuthEntry } from "popup/ducks/access";
import {
allAccountsSelector,
confirmPassword,
hasPrivateKeySelector,
makeAccountActive,
publicKeySelector,
hardwareWalletTypeSelector,
} from "popup/ducks/accountServices";
import { settingsExperimentalModeSelector } from "popup/ducks/settings";
import {
ShowOverlayStatus,
startHwSign,
transactionSubmissionSelector,
} from "popup/ducks/transactionSubmission";
import { ShowOverlayStatus } from "popup/ducks/transactionSubmission";
import { VerifyAccount } from "popup/views/VerifyAccount";

import { AppDispatch } from "popup/App";
import { EntryToSign, parsedSearchParam } from "helpers/urls";
import { Account } from "@shared/api/types";
import { AuthEntry } from "popup/components/signAuthEntry/AuthEntry";

import "./styles.scss";
import { useSetupSigningFlow } from "popup/helpers/useSetupSigningFlow";

export const SignAuthEntry = () => {
const location = useLocation();
const params = parsedSearchParam(location.search) as EntryToSign;
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

const location = useLocation();
const dispatch: AppDispatch = useDispatch();
const { t } = useTranslation();
const isExperimentalModeEnabled = useSelector(
settingsExperimentalModeSelector,
);

const hardwareWalletType = useSelector(hardwareWalletTypeSelector);
const isHardwareWallet = !!hardwareWalletType;
const {
hardwareWalletData: { status: hwStatus },
} = useSelector(transactionSubmissionSelector);

const [startedHwSign, setStartedHwSign] = useState(false);
const [currentAccount, setCurrentAccount] = useState({} as Account);
const [isPasswordRequired, setIsPasswordRequired] = useState(false);
const [isConfirming, setIsConfirming] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [accountNotFound, setAccountNotFound] = useState(false);

const allAccounts = useSelector(allAccountsSelector);
const publicKey = useSelector(publicKeySelector);
const hasPrivateKey = useSelector(hasPrivateKeySelector);

// the public key the user had selected before starting this flow
const defaultPublicKey = useRef(publicKey);
const allAccountsMap = useRef({} as { [key: string]: Account });
const accountToSign = params.accountToSign;

const rejectAndClose = () => {
dispatch(rejectAuthEntry());
window.close();
};

const handleApprove = (signAndClose: () => Promise<void>) => async () => {
setIsConfirming(true);

if (hasPrivateKey) {
await signAndClose();
} else {
setIsPasswordRequired(true);
}

setIsConfirming(false);
};

useEffect(() => {
if (startedHwSign && hwStatus === ShowOverlayStatus.IDLE) {
window.close();
}
}, [startedHwSign, hwStatus]);

useEffect(() => {
// handle auto selecting the right account based on `accountToSign`
let autoSelectedAccountDetails;

allAccounts.forEach((account) => {
if (accountToSign) {
// does the user have the `accountToSign` somewhere in the accounts list?
if (account.publicKey === accountToSign) {
// if the `accountToSign` is found, but it isn't active, make it active
if (defaultPublicKey.current !== account.publicKey) {
dispatch(makeAccountActive(account.publicKey));
}

// save the details of the `accountToSign`
autoSelectedAccountDetails = account;
}
}

// create an object so we don't need to keep iterating over allAccounts when we switch accounts
allAccountsMap.current[account.publicKey] = account;
});

if (!autoSelectedAccountDetails) {
setAccountNotFound(true);
}
}, [accountToSign, allAccounts, dispatch]);

useEffect(() => {
// handle any changes to the current acct - whether by auto select or manual select
setCurrentAccount(allAccountsMap.current[publicKey] || ({} as Account));
}, [allAccounts, publicKey]);

const signAndClose = async () => {
if (isHardwareWallet) {
await dispatch(
startHwSign({ transactionXDR: params.entry, shouldSubmit: false }),
);
setStartedHwSign(true);
} else {
await dispatch(signEntry());
window.close();
}
};

const _handleApprove = handleApprove(signAndClose);

const verifyPasswordThenSign = async (password: string) => {
const confirmPasswordResp = await dispatch(confirmPassword(password));
const params = parsedSearchParam(location.search) as EntryToSign;
const { accountToSign } = params;

if (confirmPassword.fulfilled.match(confirmPasswordResp)) {
await signAndClose();
}
};
const {
allAccounts,
accountNotFound,
currentAccount,
isConfirming,
isPasswordRequired,
publicKey,
handleApprove,
hwStatus,
rejectAndClose,
setIsPasswordRequired,
verifyPasswordThenSign,
} = useSetupSigningFlow(
dispatch,
rejectAuthEntry,
signEntry,
params.entry,
accountToSign,
);

if (!params.url.startsWith("https") && !isExperimentalModeEnabled) {
return (
Expand Down Expand Up @@ -266,7 +178,7 @@ export const SignAuthEntry = () => {
<Button
fullWidth
isLoading={isConfirming}
onClick={() => _handleApprove()}
onClick={() => handleApprove()}
>
{t("Approve")}
</Button>
Expand Down
Loading

0 comments on commit 47ea4f9

Please sign in to comment.