Skip to content

Commit

Permalink
Merge pull request #495 from secretkeylabs/vic/guards
Browse files Browse the repository at this point in the history
feat: add guard for import ledger page
  • Loading branch information
yknl committed Jul 1, 2023
2 parents de33746 + 407eddb commit 1d8f713
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 36 deletions.
6 changes: 3 additions & 3 deletions src/app/components/guards/onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react';
import { Navigate } from 'react-router-dom';

import { useSingleTabGuard } from '@components/guards/singleTab';
import useHasStateRehydrated from '@hooks/stores/useHasRehydrated';
import useWalletSelector from '@hooks/useWalletSelector';

Expand All @@ -9,18 +10,17 @@ import {
WalletExistsContextProps,
useWalletExistsContext,
} from './WalletExistsContext';
import useOnboardingSingleton from './useOnboardingSingleton';

interface WalletExistsGuardProps {
children: React.ReactElement;
}

/**
* This guard is used to redirect the user to the wallet exists page if they have a wallet and ensures
* that only 1 onboarding workflow tab exists at a time (via the useOnboardingSingleton hook).
* that only 1 onboarding workflow tab exists at a time (via the useSingleTabGuard hook).
*/
function OnboardingGuard({ children }: WalletExistsGuardProps): React.ReactElement {
useOnboardingSingleton();
useSingleTabGuard('onboarding');

const [walletExistsGuardEnabled, setWalletExistsGuardEnabled] = useState(true);

Expand Down
30 changes: 0 additions & 30 deletions src/app/components/guards/onboarding/useOnboardingSingleton.ts

This file was deleted.

66 changes: 66 additions & 0 deletions src/app/components/guards/singleTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useEffect } from 'react';

type GuardType = 'onboarding' | 'importLedger' | 'closeWallet';

const getChannelAndPingNames = (guardName: GuardType) => {
const channelName = `${guardName}Channel`;
const pingName = `${guardName}Ping`;
return { channelName, pingName };
};

// NOTE: This is for calling the guard from a non-component context
// The window that is sending it will also receive it if the channel it is broadcasting to is open
// This is predominantly made for the reset wallet guard to close all tabs
// Use with care.
export const PostGuardPing = (guardName: GuardType): void => {
const { channelName, pingName } = getChannelAndPingNames(guardName);
const broadcastChannel = new BroadcastChannel(channelName);
broadcastChannel.postMessage(pingName);
broadcastChannel.close();
};

/**
* This hook is used to ensure that only one window with the guard name is open at a time.
* It fires off an event only once on its first render and will close the window if it receives
* the event.
*/
export const useSingleTabGuard = (guardName: GuardType, broadcastOnLoad = true): void => {
const { channelName, pingName } = getChannelAndPingNames(guardName);

useEffect(() => {
const broadcastChannel = new BroadcastChannel(channelName);

broadcastChannel.onmessage = (message) => {
if (message.data !== pingName) {
return;
}

broadcastChannel.close();
window.close();
};

if (broadcastOnLoad) {
broadcastChannel.postMessage(pingName);
}

return () => {
broadcastChannel.close();
};
}, []);
};

type SingleTabProps = {
guardName: GuardType;
children?: React.ReactElement | React.ReactElement[];
};

/**
* This guard is used to ensure that only one window with the guard name is open at a time.
* It fires off an event only once on its first render and will close the window if it receives
* the event.
*/
export function SingleTabGuard({ guardName, children }: SingleTabProps): React.ReactNode {
useSingleTabGuard(guardName);

return children;
}
17 changes: 17 additions & 0 deletions src/app/components/guards/walletCloseGuard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useSingleTabGuard } from '@components/guards/singleTab';

type WalletCloseGuardProps = {
children?: React.ReactElement | React.ReactElement[];
};

/**
* This guard is used to close any open tabs when the wallet is locked or reset.
* It should only be rendered at the root of the options page.
*/
function WalletCloseGuard({ children }: WalletCloseGuardProps): React.ReactNode {
useSingleTabGuard('closeWallet', false);

return children;
}

export default WalletCloseGuard;
9 changes: 8 additions & 1 deletion src/app/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ExtendedScreenContainer from '@components/extendedScreenContainer';
import AuthGuard from '@components/guards/auth';
import OnboardingGuard from '@components/guards/onboarding';
import { SingleTabGuard } from '@components/guards/singleTab';
import ScreenContainer from '@components/screenContainer';
import AccountList from '@screens/accountList';
import AuthenticationRequest from '@screens/authenticationRequest';
Expand Down Expand Up @@ -79,7 +80,13 @@ const router = createHashRouter([
},
{
path: 'import-ledger',
element: <ImportLedger />,
element: (
<AuthGuard>
<SingleTabGuard guardName='importLedger'>
<ImportLedger />
</SingleTabGuard>
</AuthGuard>
)
},
{
index: true,
Expand Down
7 changes: 6 additions & 1 deletion src/app/stores/wallet/actions/actionCreators.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PostGuardPing } from '@components/guards/singleTab';
import { AccountType } from '@secretkeylabs/xverse-core';
import {
Account,
BaseWallet,
Expand All @@ -9,7 +11,6 @@ import {
TransactionData,
} from '@secretkeylabs/xverse-core/types';
import BigNumber from 'bignumber.js';
import { AccountType } from '@secretkeylabs/xverse-core';
import * as actions from './types';

export function setWalletAction(wallet: BaseWallet): actions.SetWallet {
Expand All @@ -27,6 +28,8 @@ export function unlockWalletAction(seed: string) {
}

export function lockWalletAction() {
// We post the closeWallet action to the guard so that any open tabs will close
PostGuardPing('closeWallet');
return {
type: actions.LockWalletKey,
};
Expand All @@ -47,6 +50,8 @@ export function setWalletSeedPhraseAction(seedPhrase: string): actions.SetWallet
}

export function resetWalletAction(): actions.ResetWallet {
// We post the closeWallet action to the guard so that any open tabs will close
PostGuardPing('closeWallet');
return {
type: actions.ResetWalletKey,
};
Expand Down
7 changes: 6 additions & 1 deletion src/pages/Options/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import WalletCloseGuard from '@components/guards/walletCloseGuard';
import { decryptMnemonic } from '@stacks/encryption';
import rootStore from '@stores/index';
import { setWalletSeedPhraseAction } from '@stores/wallet/actions/actionCreators';
Expand Down Expand Up @@ -27,7 +28,11 @@ const renderApp = async () => {
});
const container = document.getElementById('app');
const root = createRoot(container!);
return root.render(<App />);
return root.render(
<WalletCloseGuard>
<App />
</WalletCloseGuard>,
);
};

renderApp();

0 comments on commit 1d8f713

Please sign in to comment.