Skip to content

Commit 0ccc47f

Browse files
feat(extension): LW-6779 on-demand addresses discovery (#673)
--------- Co-authored-by: Tomek Marciniak <tmarciniakm@gmail.com> --------- Signed-off-by: Szymon Masłowski <szymon.maslowski@iohk.io> Co-authored-by: Tomek Marciniak <tmarciniakm@gmail.com>
1 parent 6a911a1 commit 0ccc47f

File tree

29 files changed

+340
-36
lines changed

29 files changed

+340
-36
lines changed
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import RefreshIcon from '@assets/icons/refresh.component.svg';
2+
import WarningIcon from '@assets/icons/warning.component.svg';
3+
import styles from '@components/MainLoader/MainLoader.module.scss';
4+
import { Loader, toast } from '@lace/common';
5+
import { AddressesDiscoveryStatus } from '@lib/communication';
6+
import { useWalletStore } from '@stores';
7+
import { WarningModal } from '@views/browser/components';
8+
import React, { FC, useEffect, useState } from 'react';
9+
import { useTranslation } from 'react-i18next';
10+
11+
export const AddressesDiscoveryOverlay: FC = ({ children }) => {
12+
const { t } = useTranslation();
13+
const { hdDiscoveryStatus, initialHdDiscoveryCompleted } = useWalletStore();
14+
const [prevHdDiscoveryStatus, setPrevHdDiscoveryStatus] = useState<AddressesDiscoveryStatus | null>();
15+
16+
useEffect(() => {
17+
const prevStatusWasInProgress = prevHdDiscoveryStatus === AddressesDiscoveryStatus.InProgress;
18+
setPrevHdDiscoveryStatus(hdDiscoveryStatus);
19+
20+
if (
21+
!prevStatusWasInProgress ||
22+
![AddressesDiscoveryStatus.Error, AddressesDiscoveryStatus.Idle].includes(hdDiscoveryStatus)
23+
) {
24+
return;
25+
}
26+
27+
toast.notify({
28+
icon: AddressesDiscoveryStatus.Error === hdDiscoveryStatus ? WarningIcon : RefreshIcon,
29+
text: t(
30+
AddressesDiscoveryStatus.Error === hdDiscoveryStatus
31+
? 'addressesDiscovery.toast.errorText'
32+
: 'addressesDiscovery.toast.successText'
33+
),
34+
withProgressBar: true
35+
});
36+
}, [hdDiscoveryStatus, prevHdDiscoveryStatus, t]);
37+
38+
return (
39+
<>
40+
{children}
41+
<WarningModal
42+
header={t('addressesDiscovery.overlay.title')}
43+
content={<Loader className={styles.loader} data-testid="hd-discovery-loader" />}
44+
visible={initialHdDiscoveryCompleted && hdDiscoveryStatus === AddressesDiscoveryStatus.InProgress}
45+
/>
46+
</>
47+
);
48+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { AddressesDiscoveryOverlay } from './AddressesDiscoveryOverlay';

apps/browser-extension-wallet/src/dapp-connector.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { BackgroundServiceAPIProvider } from '@providers/BackgroundServiceAPI';
1818
import { APP_MODE_POPUP } from './utils/constants';
1919
import { PostHogClientProvider } from '@providers/PostHogClientProvider';
2020
import { ExperimentsProvider } from '@providers/ExperimentsProvider/context';
21+
import { AddressesDiscoveryOverlay } from 'components/AddressesDiscoveryOverlay';
2122

2223
const App = (): React.ReactElement => (
2324
<BackgroundServiceAPIProvider>
@@ -32,7 +33,9 @@ const App = (): React.ReactElement => (
3233
<ExperimentsProvider>
3334
<AnalyticsProvider>
3435
<ThemeProvider>
35-
<DappConnectorView />
36+
<AddressesDiscoveryOverlay>
37+
<DappConnectorView />
38+
</AddressesDiscoveryOverlay>
3639
</ThemeProvider>
3740
</AnalyticsProvider>
3841
</ExperimentsProvider>

apps/browser-extension-wallet/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ export * from './useUpdateAddressStatus';
2222
export * from './useOnAddressSave';
2323
export * from './useSendEvent';
2424
export * from './useAppInit';
25+
export * from './useAddressesDiscoverer';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Shutdown } from '@cardano-sdk/util';
2+
import { Wallet } from '@lace/cardano';
3+
import { consumeAddressesDiscoverer, exposeKeyAgent } from '@lib/communication';
4+
import { useCallback } from 'react';
5+
6+
const addressesDiscoverer = consumeAddressesDiscoverer();
7+
let shutdownExposedKeyAgent: Shutdown['shutdown'];
8+
9+
type useAddressesDiscovererSetupParams = {
10+
asyncKeyAgent: Wallet.KeyManagement.AsyncKeyAgent;
11+
chainName: Wallet.ChainName;
12+
};
13+
14+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
15+
export const useAddressesDiscoverer = () => ({
16+
addressesDiscoverer,
17+
prepare: useCallback(async ({ asyncKeyAgent, chainName }: useAddressesDiscovererSetupParams) => {
18+
shutdownExposedKeyAgent?.();
19+
const { channelName, shutdown } = await exposeKeyAgent(asyncKeyAgent);
20+
shutdownExposedKeyAgent = shutdown;
21+
await addressesDiscoverer.setup({ chainName, keyAgentChannelName: channelName });
22+
}, [])
23+
});

apps/browser-extension-wallet/src/hooks/useAppInit.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/* eslint-disable unicorn/no-null */
22
import { WalletManagerUi } from '@cardano-sdk/web-extension';
3-
import { useWalletManager } from '@hooks';
3+
import { useAddressesDiscoverer, useWalletManager } from '@hooks';
4+
import { Wallet } from '@lace/cardano';
45
import { useWalletStore } from '@stores';
56
import { getValueFromLocalStorage } from '@utils/local-storage';
67
import { useCallback, useEffect } from 'react';
78
import { runtime } from 'webextension-polyfill';
8-
import { Wallet } from '@lace/cardano';
99

1010
const useAddressesListener = (
1111
callback: (addresses: Wallet.KeyManagement.GroupedAddress[]) => void | Promise<void>,
@@ -21,17 +21,34 @@ const useAddressesListener = (
2121
};
2222

2323
export const useAppInit = (): void => {
24-
const { environmentName, setWalletInfo, setWalletManagerUi, walletManagerUi } = useWalletStore();
24+
const { cardanoWallet, environmentName, setHdDiscoveryStatus, setWalletInfo, setWalletManagerUi, walletManagerUi } =
25+
useWalletStore();
2526
const { updateAddresses } = useWalletManager();
27+
const { addressesDiscoverer, prepare: prepareAddressesDiscoverer } = useAddressesDiscoverer();
2628

2729
useEffect(() => {
2830
const walletManager = new WalletManagerUi({ walletName: process.env.WALLET_NAME }, { logger: console, runtime });
2931
setWalletManagerUi(walletManager);
3032
}, [setWalletManagerUi]);
3133

34+
useEffect(() => {
35+
(async () => {
36+
if (!cardanoWallet?.asyncKeyAgent) return;
37+
await prepareAddressesDiscoverer({ chainName: environmentName, asyncKeyAgent: cardanoWallet?.asyncKeyAgent });
38+
})();
39+
}, [cardanoWallet?.asyncKeyAgent, environmentName, prepareAddressesDiscoverer]);
40+
41+
useEffect(() => {
42+
const subscription = addressesDiscoverer.status$.subscribe(setHdDiscoveryStatus);
43+
return () => {
44+
subscription.unsubscribe();
45+
};
46+
}, [addressesDiscoverer, setHdDiscoveryStatus]);
47+
3248
useAddressesListener(
3349
useCallback(
3450
async (addresses) => {
51+
if (!addresses) return;
3552
const { name } = getValueFromLocalStorage('wallet');
3653
setWalletInfo({
3754
name: name ?? 'Lace',
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2+
import { Wallet } from '@lace/cardano';
3+
import { consumeRemoteApi, exposeApi, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
4+
import { Subject, of } from 'rxjs';
5+
import { runtime } from 'webextension-polyfill';
6+
7+
type AddressesDiscovererSetupDependencies = {
8+
chainName: Wallet.ChainName;
9+
keyAgentChannelName: string;
10+
};
11+
12+
export enum AddressesDiscoveryStatus {
13+
Idle = 'Idle',
14+
InProgress = 'InProgress',
15+
Error = 'Error'
16+
}
17+
18+
export type AddressesDiscoverer = {
19+
status$: Subject<AddressesDiscoveryStatus>;
20+
discover: () => Promise<Wallet.KeyManagement.GroupedAddress[]>;
21+
setup: (dependencies: AddressesDiscovererSetupDependencies) => void;
22+
};
23+
24+
export type AddressesDiscovererExposed = Omit<AddressesDiscoverer, 'setup'> & {
25+
setup: (dependencies: AddressesDiscovererSetupDependencies) => Promise<void>;
26+
};
27+
28+
const commonConfig = {
29+
baseChannel: 'addresses-discoverer',
30+
properties: {
31+
status$: RemoteApiPropertyType.HotObservable,
32+
discover: RemoteApiPropertyType.MethodReturningPromise,
33+
setup: RemoteApiPropertyType.MethodReturningPromise
34+
}
35+
} as const;
36+
37+
export const exposeAddressesDiscoverer = (addressesDiscoverer: AddressesDiscoverer) =>
38+
exposeApi(
39+
{
40+
...commonConfig,
41+
api$: of(addressesDiscoverer)
42+
},
43+
{ logger: console, runtime }
44+
);
45+
46+
export const consumeAddressesDiscoverer = () =>
47+
consumeRemoteApi<AddressesDiscovererExposed>(commonConfig, { logger: console, runtime });
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export {
2+
AddressesDiscoveryStatus,
3+
consumeAddressesDiscoverer,
4+
exposeAddressesDiscoverer
5+
} from './addresses-discoverer';
6+
export type { AddressesDiscoverer } from './addresses-discoverer';
7+
export { consumeKeyAgent, exposeKeyAgent } from './key-agent';

0 commit comments

Comments
 (0)