Skip to content

Commit

Permalink
feat: optimize loading states in Dapp connector views for nami mode
Browse files Browse the repository at this point in the history
  • Loading branch information
vetalcore committed Oct 10, 2024
1 parent a2c6615 commit 78cb8ee
Show file tree
Hide file tree
Showing 35 changed files with 469 additions and 531 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-statements */
import React, { useCallback, useMemo } from 'react';
import { DappConnector, DApp, DappOutsideHandlesProvider } from '@lace/nami';
import { DappConnector, DApp, DappOutsideHandlesProvider, CommonOutsideHandlesProvider } from '@lace/nami';
import { useWalletStore } from '@src/stores';
import { useBackgroundServiceAPIContext, useTheme } from '@providers';
import { useWalletManager } from '@hooks';
Expand All @@ -15,6 +15,7 @@ import * as cip30 from '@cardano-sdk/dapp-connector';
import { UserPromptService } from '@lib/scripts/background/services';
import { finalize, firstValueFrom, map, of } from 'rxjs';
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
import { useAnalytics } from './hooks';

const DAPP_TOAST_DURATION = 100;
const dappConnector: DappConnector = {
Expand Down Expand Up @@ -129,6 +130,7 @@ const dappConnector: DappConnector = {
};

export const NamiDappConnectorView = withDappContext((): React.ReactElement => {
const { sendEventToPostHog } = useAnalytics();
const backgroundServices = useBackgroundServiceAPIContext();
const { walletRepository } = useWalletManager();
const { walletUI, inMemoryWallet, walletType, currentChain, environmentName } = useWalletStore();
Expand All @@ -152,18 +154,24 @@ export const NamiDappConnectorView = withDappContext((): React.ReactElement => {
<DappOutsideHandlesProvider
{...{
theme: theme.name,
inMemoryWallet,
walletManager,
walletRepository,
environmentName,
dappConnector,
withSignTxConfirmation,
cardanoCoin,
walletType,
openHWFlow
dappConnector
}}
>
<DApp />
<CommonOutsideHandlesProvider
{...{
cardanoCoin,
walletType,
openHWFlow,
inMemoryWallet,
withSignTxConfirmation,
sendEventToPostHog
}}
>
<DApp />
</CommonOutsideHandlesProvider>
</DappOutsideHandlesProvider>
);
});
145 changes: 15 additions & 130 deletions apps/browser-extension-wallet/src/views/nami-mode/NamiView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable max-statements */
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { DappConnector, Main as Nami, OutsideHandlesProvider } from '@lace/nami';
import { CommonOutsideHandlesProvider, Main as Nami, OutsideHandlesProvider } from '@lace/nami';
import { useWalletStore } from '@src/stores';
import { config } from '@src/config';
import { useBackgroundServiceAPIContext, useCurrencyStore, useExternalLinkOpener, useTheme } from '@providers';
Expand All @@ -13,7 +13,7 @@ import {
useBuildDelegation,
useBalances
} from '@hooks';
import { signingCoordinator, walletManager, withSignTxConfirmation } from '@lib/wallet-api-ui';
import { walletManager, withSignTxConfirmation } from '@lib/wallet-api-ui';
import { useAnalytics } from './hooks';
import { useDappContext, withDappContext } from '@src/features/dapp/context';
import { localDappService } from '../browser-view/features/dapp/components/DappList/localDappService';
Expand All @@ -32,129 +32,10 @@ import { useWrapWithTimeout } from '../browser-view/features/multi-wallet/hardwa
import { certificateInspectorFactory } from '@src/features/dapp/components/confirm-transaction/utils';
import { useWalletState } from '@hooks/useWalletState';
import { isKeyHashAddress } from '@cardano-sdk/wallet';
import { BackgroundStorage, DappDataService } from '@lib/scripts/types';
import { consumeRemoteApi, exposeApi, RemoteApiPropertyType } from '@cardano-sdk/web-extension';
import { DAPP_CHANNELS } from '@src/utils/constants';
import { runtime } from 'webextension-polyfill';
import * as cip30 from '@cardano-sdk/dapp-connector';
import { UserPromptService } from '@lib/scripts/background/services';
import { finalize, firstValueFrom, map, of } from 'rxjs';
import { senderToDappInfo } from '@src/utils/senderToDappInfo';
import { BackgroundStorage } from '@lib/scripts/types';

const { AVAILABLE_CHAINS, DEFAULT_SUBMIT_API } = config();

const DAPP_TOAST_DURATION = 100;
const dappConnector: DappConnector = {
getDappInfo: () => {
const dappDataService = consumeRemoteApi<Pick<DappDataService, 'getDappInfo'>>(
{
baseChannel: DAPP_CHANNELS.dappData,
properties: {
getDappInfo: RemoteApiPropertyType.MethodReturningPromise
}
},
{ logger: console, runtime }
);
return dappDataService.getDappInfo();
},
authorizeDapp: (authorization: 'deny' | 'allow', url: string, onCleanup: () => void) => {
const api$ = of({
allowOrigin(origin: cip30.Origin): Promise<'deny' | 'allow'> {
if (!url.startsWith(origin)) {
return Promise.reject();
}
return Promise.resolve(authorization);
}
});

const userPromptService = exposeApi<Pick<UserPromptService, 'allowOrigin'>>(
{
api$,
baseChannel: DAPP_CHANNELS.userPrompt,
properties: { allowOrigin: RemoteApiPropertyType.MethodReturningPromise }
},
{ logger: console, runtime }
);

setTimeout(() => {
userPromptService.shutdown();
onCleanup();
}, DAPP_TOAST_DURATION);
},
getSignTxRequest: async () => {
const userPromptService = exposeApi<Pick<UserPromptService, 'readyToSignTx'>>(
{
api$: of({
async readyToSignTx(): Promise<boolean> {
return Promise.resolve(true);
}
}),
baseChannel: DAPP_CHANNELS.userPrompt,
properties: { readyToSignTx: RemoteApiPropertyType.MethodReturningPromise }
},
{ logger: console, runtime }
);

return firstValueFrom(
signingCoordinator.transactionWitnessRequest$.pipe(
map(async (r) => ({
dappInfo: await senderToDappInfo(r.signContext.sender),
request: {
data: { tx: r.transaction.toCbor(), addresses: r.signContext.knownAddresses },
reject: async (onCleanup: () => void) => {
await r.reject('User declined to sign');
setTimeout(() => {
onCleanup();
}, DAPP_TOAST_DURATION);
},
sign: async (password: string) => {
const passphrase = Buffer.from(password, 'utf8');
await r.sign(passphrase, { willRetryOnFailure: true });
}
}
})),
finalize(() => userPromptService.shutdown())
)
);
},
getSignDataRequest: async () => {
const userPromptService = exposeApi<Pick<UserPromptService, 'readyToSignData'>>(
{
api$: of({
async readyToSignData(): Promise<boolean> {
return Promise.resolve(true);
}
}),
baseChannel: DAPP_CHANNELS.userPrompt,
properties: { readyToSignData: RemoteApiPropertyType.MethodReturningPromise }
},
{ logger: console, runtime }
);

return firstValueFrom(
signingCoordinator.signDataRequest$.pipe(
map(async (r) => ({
dappInfo: await senderToDappInfo(r.signContext.sender),
request: {
data: { payload: r.signContext.payload, address: r.signContext.signWith },
reject: async (onCleanup: () => void) => {
await r.reject('User rejected to sign');
setTimeout(() => {
onCleanup();
}, DAPP_TOAST_DURATION);
},
sign: async (password: string) => {
const passphrase = Buffer.from(password, 'utf8');
return r.sign(passphrase, { willRetryOnFailure: true });
}
}
})),
finalize(() => userPromptService.shutdown())
)
);
}
};

export const NamiView = withDappContext((): React.ReactElement => {
const { setFiatCurrency, fiatCurrency } = useCurrencyStore();
const { priceResult } = useFetchCoinPrice();
Expand Down Expand Up @@ -285,18 +166,15 @@ export const NamiView = withDappContext((): React.ReactElement => {
connectedDapps,
isAnalyticsOptIn,
handleAnalyticsChoice,
sendEventToPostHog,
createWallet,
getMnemonic,
deleteWallet,
fiatCurrency: fiatCurrency.code,
setFiatCurrency,
theme: theme.name,
setTheme,
inMemoryWallet,
currentChain,
cardanoPrice,
withSignTxConfirmation,
walletManager,
walletRepository,
switchNetwork,
Expand All @@ -305,9 +183,7 @@ export const NamiView = withDappContext((): React.ReactElement => {
enableCustomNode,
getCustomSubmitApiForNetwork,
defaultSubmitApi: DEFAULT_SUBMIT_API,
cardanoCoin,
isValidURL,
dappConnector,
buildDelegation,
signAndSubmitTransaction,
passwordUtil,
Expand All @@ -328,14 +204,23 @@ export const NamiView = withDappContext((): React.ReactElement => {
eraSummaries: walletState?.eraSummaries,
getTxInputsValueAndAddress,
certificateInspectorFactory,
openHWFlow,
walletType,
connectHW,
createHardwareWalletRevamped,
saveHardwareWallet
}}
>
<Nami />
<CommonOutsideHandlesProvider
{...{
cardanoCoin,
walletType,
openHWFlow,
inMemoryWallet,
withSignTxConfirmation,
sendEventToPostHog
}}
>
<Nami />
</CommonOutsideHandlesProvider>
</OutsideHandlesProvider>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useWalletStore } from '@src/stores';
import { useAppInit } from '@hooks';
import { MainLoader } from '@components/MainLoader';
import { withDappContext } from '@src/features/dapp/context';
import { NamiView } from './NamiView';
import { NamiDappConnectorView } from './NamiDappConnectorView';
import '../../lib/scripts/keep-alive-ui';
import './index.scss';

Expand All @@ -12,5 +12,5 @@ export const NamiDappConnector = withDappContext((): React.ReactElement => {

useAppInit();

return <div id="nami-mode">{hdDiscoveryStatus === 'Idle' ? <NamiView /> : <MainLoader />}</div>;
return <div id="nami-mode">{hdDiscoveryStatus === 'Idle' ? <NamiDappConnectorView /> : <MainLoader />}</div>;
});
3 changes: 2 additions & 1 deletion packages/nami/src/adapters/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { Wallet } from '@lace/cardano';
import { useObservable } from '@lace/common';

import { useCommonOutsideHandles } from '../features/common-outside-handles-provider';
import { useOutsideHandles } from '../features/outside-handles-provider/useOutsideHandles';

import { toAsset } from './assets';
Expand Down Expand Up @@ -258,11 +259,11 @@ export const useTxInfo = (
const [txInfo, setTxInfo] = useState<TxInfo | undefined>();
const {
getTxInputsValueAndAddress,
inMemoryWallet,
eraSummaries,
walletAddresses,
certificateInspectorFactory,
} = useOutsideHandles();
const { inMemoryWallet } = useCommonOutsideHandles();
const protocolParameters = useObservable(inMemoryWallet.protocolParameters$);
const assetsInfo = useObservable(inMemoryWallet.assetInfo$);
const rewardAccounts = useObservable(
Expand Down
4 changes: 2 additions & 2 deletions packages/nami/src/api/extension/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { TX } from '../../config/config';

import { submitTx } from '.';

import type { OutsideHandlesContextValue } from '../../ui';
import type { CommonOutsideHandlesContextValue } from '../../features/common-outside-handles-provider';
import type { Serialization } from '@cardano-sdk/core';
import type { UnwitnessedTx } from '@cardano-sdk/tx-construction';
import type { Wallet } from '@lace/cardano';
Expand Down Expand Up @@ -43,7 +43,7 @@ export const signAndSubmit = async ({
}: Readonly<{
tx: UnwitnessedTx;
password: string;
withSignTxConfirmation: OutsideHandlesContextValue['withSignTxConfirmation'];
withSignTxConfirmation: CommonOutsideHandlesContextValue['withSignTxConfirmation'];
inMemoryWallet: Wallet.ObservableWallet;
}>) =>
withSignTxConfirmation(async () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/nami/src/features/analytics/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useOutsideHandles } from '../../features/outside-handles-provider/useOutsideHandles';
import { useCommonOutsideHandles } from '../../features/common-outside-handles-provider';

import type { Events } from './events';

export const useCaptureEvent = () => {
const { sendEventToPostHog } = useOutsideHandles();
const { sendEventToPostHog } = useCommonOutsideHandles();

return async (event: Events): Promise<void> => {
await sendEventToPostHog(event);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PropsWithChildren } from 'react';
import React, { useMemo } from 'react';

import { Provider } from './context';

import type { CommonOutsideHandlesContextValue } from './types';

type OutsideHandlesProviderProps =
PropsWithChildren<CommonOutsideHandlesContextValue>;

export const OutsideHandlesProvider = ({
children,
...props
}: Readonly<OutsideHandlesProviderProps>): React.ReactElement => {
const contextValue = useMemo<CommonOutsideHandlesContextValue>(
() => props,
Object.values(props),
);
return <Provider value={contextValue}>{children}</Provider>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createContext } from 'react';

import type { CommonOutsideHandlesContextValue } from './types';

export const context = createContext<CommonOutsideHandlesContextValue | null>(
// eslint-disable-next-line unicorn/no-null
null,
);

export const { Provider } = context;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { OutsideHandlesProvider as CommonOutsideHandlesProvider } from './OutsideHandlesProvider';
export { useOutsideHandles as useCommonOutsideHandles } from './useOutsideHandles';
export * from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Events } from '../../features/analytics/events';
import type { WalletType } from '@cardano-sdk/web-extension';
import type { Wallet } from '@lace/cardano';

export interface CommonOutsideHandlesContextValue {
cardanoCoin: Wallet.CoinId;
walletType: WalletType;
openHWFlow: (path: string) => void;
inMemoryWallet: Wallet.ObservableWallet;
withSignTxConfirmation: <T>(
action: () => Promise<T>,
password?: string,
) => Promise<T>;
sendEventToPostHog: (action: Events) => Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { fn } from '@storybook/test';

import * as actualApi from './useOutsideHandles';

export * from './useOutsideHandles';

export const useCommonOutsideHandles: jest.Mock = fn(
actualApi.useOutsideHandles,
).mockName('useOutsideHandles');
Loading

0 comments on commit 78cb8ee

Please sign in to comment.