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

fixed switching between keys #1815

Merged
merged 4 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion packages/api/src/services/WalletService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default class Wallet extends Service {
}

async logIn(args: {
fingerprint: string;
fingerprint: number;
type?: 'normal' | 'skip' | 'restore_backup'; // skip is used to skip import
}) {
const { fingerprint, type = 'normal' } = args;
Expand Down
96 changes: 96 additions & 0 deletions packages/core/src/components/Auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useClearCache, useLogInMutation, useGetLoggedInFingerprintQuery } from '@chia-network/api-react';
import React, { useMemo, useCallback, useRef, useState, useEffect, createContext, type ReactNode } from 'react';

export const AuthContext = createContext<
| {
logIn: (fingerprint: number) => Promise<void>;
logOut: () => Promise<void>;
fingerprint?: number;
isLoading: boolean;
}
| undefined
>(undefined);

export type AuthProviderProps = {
children: ReactNode;
};

export default function AuthProvider(props: AuthProviderProps) {
const { children } = props;
const [isLoading, setIsLoading] = useState<boolean>(false);
const [fingerprint, setFingerprint] = useState<number | undefined>();
const { data: currentFingerprint } = useGetLoggedInFingerprintQuery();
const [logIn] = useLogInMutation();
const clearCache = useClearCache();

const isLoadingRef = useRef(isLoading);
isLoadingRef.current = isLoading;

const processNewFingerprint = useCallback(
async (newFingerprint: number) => {
if (!isLoadingRef.current) {
try {
setIsLoading(true);
await clearCache();
setFingerprint(newFingerprint);
} finally {
setIsLoading(false);
}
}
},
[clearCache]
);

// automatically log in if we have a fingerprint already and not logIn is not in progress
paninaro marked this conversation as resolved.
Show resolved Hide resolved
useEffect(() => {
if (!!currentFingerprint && currentFingerprint !== fingerprint) {
processNewFingerprint(currentFingerprint);
}
}, [currentFingerprint, fingerprint, processNewFingerprint]);

// immutable
const handleLogOut = useCallback(async () => {
// do nothing until backend change API,
// user is still logged in and syncing with previously selected fingerprint
}, []);

// immutable
const handleLogIn = useCallback(
async (logInFingerprint: number) => {
try {
if (isLoadingRef.current) {
return;
}

setIsLoading(true);

handleLogOut();

await logIn({
fingerprint: logInFingerprint,
type: 'skip',
}).unwrap();

// all data are from previous fingerprint, so we need to clear cache,
// invalidateTags just do refetch and showing old data until new data are fetched
await clearCache();
setFingerprint(logInFingerprint);
} finally {
setIsLoading(false);
}
},
[handleLogOut, logIn, clearCache]
);

const context = useMemo(
() => ({
logIn: handleLogIn,
logOut: handleLogOut,
fingerprint,
isLoading,
}),
[handleLogIn, handleLogOut, fingerprint, isLoading]
);

return <AuthContext.Provider value={context}>{children}</AuthContext.Provider>;
}
1 change: 1 addition & 0 deletions packages/core/src/components/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as AuthProvider } from './AuthProvider';
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import React, { type ReactNode, useState, Suspense, useCallback } from 'react';
import { useNavigate, Outlet } from 'react-router-dom';
import styled from 'styled-components';

import useAuth from '../../hooks/useAuth';
import useGetLatestVersionFromWebsite from '../../hooks/useGetLatestVersionFromWebsite';
import useOpenDialog from '../../hooks/useOpenDialog';
import EmojiAndColorPicker from '../../screens/SelectKey/EmojiAndColorPicker';
Expand Down Expand Up @@ -87,6 +88,7 @@ export default function LayoutDashboard(props: LayoutDashboardProps) {
const { children, sidebar, settings, outlet = false, actions } = props;

const navigate = useNavigate();
const { logOut } = useAuth();
const [editWalletName, setEditWalletName] = useState(false);
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
const { data: fingerprint, isLoading: isLoadingFingerprint } = useGetLoggedInFingerprintQuery();
Expand Down Expand Up @@ -131,8 +133,7 @@ export default function LayoutDashboard(props: LayoutDashboardProps) {
}, [openDialog, appVersion]);

async function handleLogout() {
localStorage.setItem('visibilityFilters', JSON.stringify(['visible']));
localStorage.setItem('typeFilter', JSON.stringify([]));
await logOut();

navigate('/');
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { default as AdvancedOptions } from './AdvancedOptions';
export { default as AlertDialog } from './AlertDialog';
export { default as Amount } from './Amount';
export { default as AspectRatio } from './AspectRatio';
export { AuthProvider } from './Auth';
export { default as Autocomplete } from './Autocomplete';
export { default as Back } from './Back';
export { default as Button } from './Button';
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as useAppVersion } from './useAppVersion';
export { default as useAuth } from './useAuth';
export { default as useDarkMode } from './useDarkMode';
export { default as useHiddenList } from './useHiddenList';
export { default as useCurrencyCode } from './useCurrencyCode';
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useContext } from 'react';

import { AuthContext } from '../components/Auth/AuthProvider';

export default function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}

return context;
}
39 changes: 13 additions & 26 deletions packages/core/src/screens/SelectKey/SelectKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import {
useGetKeyringStatusQuery,
useDeleteAllKeysMutation,
useGetKeysQuery,
useClearCache,
useLogInMutation,
type Serializable,
} from '@chia-network/api-react';
import { ChiaBlack, Coins } from '@chia-network/icons';
Expand All @@ -26,6 +24,7 @@ import Loading from '../../components/Loading';
import MenuItem from '../../components/MenuItem/MenuItem';
import More from '../../components/More';
import TooltipIcon from '../../components/TooltipIcon';
import useAuth from '../../hooks/useAuth';
import useKeyringMigrationPrompt from '../../hooks/useKeyringMigrationPrompt';
import useOpenDialog from '../../hooks/useOpenDialog';
import useShowError from '../../hooks/useShowError';
Expand All @@ -43,15 +42,15 @@ export default function SelectKey() {
const openDialog = useOpenDialog();
const navigate = useNavigate();
const [deleteAllKeys] = useDeleteAllKeysMutation();
const [logIn] = useLogInMutation();
const { data: publicKeyFingerprints, isLoading: isLoadingPublicKeys, error, refetch } = useGetKeysQuery();

const { isLoading: isLoggingIn, logIn } = useAuth();
const [selectedKey, setSelectedKey] = useState<number | null>(null);
const { data: publicKeyFingerprints, isLoading: isLoadingPublicKeys, error, refetch } = useGetKeysQuery({});
const { data: keyringState, isLoading: isLoadingKeyringStatus } = useGetKeyringStatusQuery();
const hasFingerprints = !!publicKeyFingerprints?.length;
const [selectedFingerprint, setSelectedFingerprint] = useState<number | undefined>();
const [skippedMigration] = useSkipMigration();
const [promptForKeyringMigration] = useKeyringMigrationPrompt();
const showError = useShowError();
const clearCache = useClearCache();
const [sortedWallets, setSortedWallets] = usePrefs('sortedWallets', []);

const keyItemsSortable = React.useRef<any>(null);
Expand Down Expand Up @@ -101,28 +100,16 @@ export default function SelectKey() {
}
}, [publicKeyFingerprints, fingerprintSettings, setFingerprintSettings, allColors]);

async function handleSelect(fingerprint: number) {
if (selectedFingerprint) {
return;
}

async function handleSelect(logInFingerprint: number) {
try {
setSelectedFingerprint(fingerprint);

// we need to clear cache before logging in, because we need to clean notifications
await logIn({
fingerprint,
type: 'skip',
}).unwrap();

// because some queries may be run during login , we need to clear them again
await clearCache();
setSelectedKey(logInFingerprint);
await logIn(logInFingerprint);

navigate('/dashboard/wallets');
} catch (err) {
showError(err);
showError(err as Error);
} finally {
setSelectedFingerprint(undefined);
setSelectedKey(null);
}
}

Expand Down Expand Up @@ -170,7 +157,7 @@ export default function SelectKey() {

function sortedFingerprints(fingerprints: string[]) {
const sorted = sortedWallets
.map((fingerprint: string) => fingerprints.find((f: any) => fingerprint === String(f.fingerprint)))
.map((value: string) => fingerprints.find((f: any) => value === String(f.fingerprint)))
.filter((x: any) => !!x); /* if we added a new wallet and order was not saved yet case */
fingerprints.forEach((f: any) => {
if (sorted.map((f2: any) => f2.fingerprint).indexOf(f.fingerprint) === -1) {
Expand Down Expand Up @@ -321,8 +308,8 @@ export default function SelectKey() {
index={index}
keyData={keyData}
onSelect={handleSelect}
loading={keyData.fingerprint === selectedFingerprint}
disabled={!!selectedFingerprint && keyData.fingerprint !== selectedFingerprint}
loading={isLoggingIn && keyData.fingerprint === selectedKey}
disabled={isLoggingIn}
/>
))}
</Flex>
Expand Down
39 changes: 21 additions & 18 deletions packages/gui/src/components/app/AppProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
dark,
light,
ErrorBoundary,
AuthProvider,
} from '@chia-network/core';
import { nativeTheme } from '@electron/remote';
import { Trans } from '@lingui/macro';
Expand Down Expand Up @@ -97,24 +98,26 @@ export default function App(props: AppProps) {
<LocaleProvider i18n={i18n} defaultLocale={defaultLocale} locales={locales}>
<ThemeProvider theme={theme} fonts global>
<ErrorBoundary>
<CacheProvider>
<LRUsProvider>
<NFTProvider>
<ModalDialogsProvider>
<Suspense fallback={<LayoutLoading />}>
<OffersProvider>
<NotificationsProvider>
<WalletConnectProvider projectId={WalletConnectChiaProjectId}>
<AppState>{outlet ? <Outlet /> : children}</AppState>
<ModalDialogs />
</WalletConnectProvider>
</NotificationsProvider>
</OffersProvider>
</Suspense>
</ModalDialogsProvider>
</NFTProvider>
</LRUsProvider>
</CacheProvider>
<AuthProvider>
<CacheProvider>
<LRUsProvider>
<NFTProvider>
<ModalDialogsProvider>
<Suspense fallback={<LayoutLoading />}>
<OffersProvider>
<NotificationsProvider>
<WalletConnectProvider projectId={WalletConnectChiaProjectId}>
<AppState>{outlet ? <Outlet /> : children}</AppState>
<ModalDialogs />
</WalletConnectProvider>
</NotificationsProvider>
</OffersProvider>
</Suspense>
</ModalDialogsProvider>
</NFTProvider>
</LRUsProvider>
</CacheProvider>
</AuthProvider>
</ErrorBoundary>
</ThemeProvider>
</LocaleProvider>
Expand Down
13 changes: 8 additions & 5 deletions packages/gui/src/hooks/useBlockchainNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
useDeleteNotificationsMutation,
useLazyGetTimestampForHeightQuery,
} from '@chia-network/api-react';
import { ConfirmDialog, useOpenDialog } from '@chia-network/core';
import { ConfirmDialog, useOpenDialog, useAuth } from '@chia-network/core';
import { useWalletState } from '@chia-network/wallets';
import { Trans } from '@lingui/macro';
import debug from 'debug';
Expand Down Expand Up @@ -33,6 +33,7 @@ export default function useBlockchainNotifications() {

const [getTimestampForHeight] = useLazyGetTimestampForHeightQuery();

const { isLoading: isLoggingIn } = useAuth();
const { state, isLoading: isLoadingWalletState } = useWalletState();

const [notifications, setNotifications] = useStateAbort<Notification[]>([]);
Expand All @@ -56,14 +57,16 @@ export default function useBlockchainNotifications() {
async (
blockchainNotificationsList: BlockchainNotification[],
isWalletSynced: boolean,
loggingIn: boolean,
abortSignal: AbortSignal
) => {
try {
setPreparingError(undefined, abortSignal);
setIsPreparingNotifications(true, abortSignal);

// without wallet sync we can't get timestamp
if (!blockchainNotificationsList?.length || !isWalletSynced) {
// without wallet sync we can't get timestamp, during loggingIn we have wrong
// list of notifications from previous fingerprint and we need to wait for clear cache
if (!blockchainNotificationsList?.length || !isWalletSynced || !loggingIn) {
setNotifications([], abortSignal);
return;
}
Expand Down Expand Up @@ -142,9 +145,9 @@ export default function useBlockchainNotifications() {
abortControllerRef.current.abort();
abortControllerRef.current = new AbortController();

prepareNotifications(blockchainNotifications, isSynced, abortControllerRef.current.signal);
prepareNotifications(blockchainNotifications, isSynced, isLoggingIn, abortControllerRef.current.signal);
}
}, [blockchainNotifications, prepareNotifications, isSynced]);
}, [blockchainNotifications, prepareNotifications, isSynced, isLoggingIn]);

const handleDeleteNotification = useCallback(
async (id: string) => {
Expand Down
Loading