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

fix: fetch response parsing, create safe retry #386

Merged
merged 18 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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 electron/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const homedir = os.homedir();
* - use "" (nothing as a suffix) for latest release candidate, for example "0.1.0rc26"
* - use "alpha" for alpha release, for example "0.1.0rc26-alpha"
*/
const OlasMiddlewareVersion = '0.1.0rc158';
const OlasMiddlewareVersion = '0.1.0rc162';

const path = require('path');
const { app } = require('electron');
Expand Down
104 changes: 84 additions & 20 deletions frontend/components/SetupPage/Create/SetupCreateSafe.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,100 @@
import { message, Typography } from 'antd';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Chain } from '@/client';
import { CardSection } from '@/components/styled/CardSection';
import { UNICODE_SYMBOLS } from '@/constants/symbols';
import { SUPPORT_URL } from '@/constants/urls';
import { Pages } from '@/enums/PageState';
import { useMasterSafe } from '@/hooks/useMasterSafe';
import { usePageState } from '@/hooks/usePageState';
import { useSetup } from '@/hooks/useSetup';
import { useWallet } from '@/hooks/useWallet';
import { WalletService } from '@/service/Wallet';
import { delayInSeconds } from '@/utils/delay';

export const SetupCreateSafe = () => {
const { masterSafeAddress } = useWallet();
const { backupSigner } = useSetup();
const { goto } = usePageState();
const { updateWallets } = useWallet();
const { updateMasterSafeOwners, masterSafeAddress, backupSafeAddress } =
useMasterSafe();
const { backupSigner } = useSetup();

const [isCreatingSafe, setIsCreatingSafe] = useState(false);
const [isError, setIsError] = useState(false);
const [isCreateSafeSuccessful, setIsCreateSafeSuccessful] = useState(false);
const [failed, setFailed] = useState(false);

const createSafeWithRetries = useCallback(
async (retries: number) => {
setIsCreatingSafe(true);

// If we have retried too many times, set failed
if (retries <= 0) {
setFailed(true);
setIsCreatingSafe(false);
setIsCreateSafeSuccessful(false);
return;
}

// Try to create the safe
WalletService.createSafe(Chain.GNOSIS, backupSigner)
.then(async () => {
// Backend returned success
message.success('Account created');

// Attempt wallet and master safe updates before proceeding
try {
await updateWallets();
await updateMasterSafeOwners();
} catch (e) {
console.error(e);
}

// Set states for successful creation
setIsCreatingSafe(false);
setIsCreateSafeSuccessful(true);
setFailed(false);
})
.catch(async (e) => {
console.error(e);
// Wait for 5 seconds before retrying
await delayInSeconds(5);
// Retry
const newRetries = retries - 1;
if (newRetries <= 0) {
message.error('Failed to create account');
} else {
message.error('Failed to create account, retrying in 5 seconds');
}
createSafeWithRetries(newRetries);
truemiller marked this conversation as resolved.
Show resolved Hide resolved
});
},
[backupSigner, updateMasterSafeOwners, updateWallets],
);

const creationStatusText = useMemo(() => {
if (isCreatingSafe) return 'Creating account';
if (masterSafeAddress && !backupSafeAddress) return 'Checking backup';
if (masterSafeAddress && backupSafeAddress) return 'Account created';
return 'Account creation in progress';
}, [backupSafeAddress, isCreatingSafe, masterSafeAddress]);

useEffect(() => {
if (isCreatingSafe) return;
setIsCreatingSafe(true);
// TODO: add backup signer
WalletService.createSafe(Chain.GNOSIS, backupSigner).catch((e) => {
console.error(e);
setIsError(true);
message.error('Failed to create an account. Please try again later.');
});
}, [backupSigner, isCreatingSafe]);
if (failed || isCreatingSafe || isCreateSafeSuccessful) return;
createSafeWithRetries(3);
}, [
backupSigner,
createSafeWithRetries,
failed,
isCreateSafeSuccessful,
isCreatingSafe,
]);

useEffect(() => {
// Only progress is the safe is created and accessible via context (updates on interval)
if (masterSafeAddress) goto(Pages.Main);
}, [goto, masterSafeAddress]);
if (masterSafeAddress && backupSafeAddress) goto(Pages.Main);
}, [backupSafeAddress, goto, masterSafeAddress]);

return (
<CardSection
Expand All @@ -44,14 +104,18 @@ export const SetupCreateSafe = () => {
padding="80px 24px"
gap={12}
>
{isError ? (
{failed ? (
<>
<Image src="/broken-robot.svg" alt="logo" width={80} height={80} />
<Typography.Text type="secondary" className="mt-12">
Error, please contact{' '}
<a target="_blank" href={SUPPORT_URL}>
Olas community {UNICODE_SYMBOLS.EXTERNAL_LINK}
Error, please restart the app and try again.
</Typography.Text>
<Typography.Text>
If the issue persists, please{' '}
<a href={SUPPORT_URL} target="_blank" rel="noreferrer">
contact Olas community support {UNICODE_SYMBOLS.EXTERNAL_LINK}
</a>
.
</Typography.Text>
</>
) : (
Expand All @@ -67,7 +131,7 @@ export const SetupCreateSafe = () => {
className="m-0 mt-12 loading-ellipses"
style={{ width: '220px' }}
>
Creating account
{creationStatusText}
</Typography.Title>
<Typography.Text type="secondary">
You will be redirected once the account is created
Expand Down
28 changes: 15 additions & 13 deletions frontend/components/SetupPage/Create/SetupSeedPhrase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,21 @@ export const SetupSeedPhrase = () => {
<Tag key={word}>{word}</Tag>
))}
</Flex>
<Button
size="large"
onClick={() =>
copyToClipboard(mnemonic.join(' ')).then(() =>
message.success('Seed phrase is copied!'),
)
}
>
<CopyOutlined /> Copy to clipboard
</Button>
<Button type="primary" size="large" onClick={handleNext}>
Continue
</Button>
<Flex gap={10}>
<Button
size="large"
onClick={() =>
copyToClipboard(mnemonic.join(' ')).then(() =>
message.success('Seed phrase is copied!'),
)
}
>
<CopyOutlined /> Copy to clipboard
</Button>
<Button type="primary" size="large" onClick={handleNext}>
Continue
</Button>
</Flex>
</CardFlex>
);
};
2 changes: 1 addition & 1 deletion frontend/components/styled/CardFlex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const CardFlex = styled(Card).withConfig({
const { gap, noBodyPadding } = props;

const gapStyle = gap ? `gap: ${gap}px;` : '';
const paddingStyle = noBodyPadding === 'true' ? 'padding: 0;' : undefined;
const paddingStyle = noBodyPadding === 'true' ? 'padding: 0;' : '';

return `${gapStyle} ${paddingStyle}`;
}}
Expand Down
3 changes: 3 additions & 0 deletions frontend/constants/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CONTENT_TYPE_JSON_UTF8 = {
'Content-Type': 'application/json; charset=utf-8',
} as const;
2 changes: 1 addition & 1 deletion frontend/context/MasterSafeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const MasterSafeContext = createContext<{
masterSafeAddress?: Address;
masterEoaAddress?: Address;
masterSafeOwners?: Address[];
updateMasterSafeOwners?: () => Promise<void>;
updateMasterSafeOwners: () => Promise<void>;
}>({
backupSafeAddress: undefined,
masterSafeAddress: undefined,
Expand Down
3 changes: 2 additions & 1 deletion frontend/context/ServicesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => {
setHasInitialLoaded(true);
})
.catch((e) => {
message.error(e.message);
console.error(e);
// message.error(e.message); Commented out to avoid showing error message; need to handle "isAuthenticated" in a better way
}),
[],
);
Expand Down
9 changes: 6 additions & 3 deletions frontend/context/WalletProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ export const WalletProvider = ({ children }: PropsWithChildren) => {
const masterSafeAddress: Address | undefined = wallets?.[0]?.safe;

const updateWallets = async () => {
const wallets = await WalletService.getWallets();
if (!wallets) return;
setWallets(wallets);
try {
const wallets = await WalletService.getWallets();
setWallets(wallets);
} catch (e) {
console.error(e);
}
};

useInterval(updateWallets, isOnline ? FIVE_SECONDS_INTERVAL : null);
Expand Down
34 changes: 22 additions & 12 deletions frontend/service/Account.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { CONTENT_TYPE_JSON_UTF8 } from '@/constants/headers';
import { BACKEND_URL } from '@/constants/urls';

/**
* Gets account status "is_setup"
*/
const getAccount = () =>
fetch(`${BACKEND_URL}/account`).then((res) => res.json());
fetch(`${BACKEND_URL}/account`, {
headers: {
...CONTENT_TYPE_JSON_UTF8,
},
}).then((res) => {
if (res.ok) return res.json();
throw new Error('Failed to get account');
});

/**
* Creates a local user account
Expand All @@ -13,41 +21,43 @@ const createAccount = (password: string) =>
fetch(`${BACKEND_URL}/account`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...CONTENT_TYPE_JSON_UTF8,
},
body: JSON.stringify({ password }),
}).then((res) => res.json());
}).then((res) => {
if (res.ok) return res.json();
throw new Error('Failed to create account');
});

/**
* Updates user's password
*/
const updateAccount = (oldPassword: string, newPassword: string) =>
fetch(`${BACKEND_URL}/account`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
headers: { ...CONTENT_TYPE_JSON_UTF8 },
body: JSON.stringify({
old_password: oldPassword,
new_password: newPassword,
}),
}).then((res) => res.json());
}).then((res) => {
if (res.ok) return res.json();
throw new Error('Failed to update account');
});

/**
* Logs in a user
*/
const loginAccount = (password: string) =>
fetch(`${BACKEND_URL}/account/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { ...CONTENT_TYPE_JSON_UTF8 },
body: JSON.stringify({
password,
}),
}).then((res) => {
if (![200, 201].includes(res.status)) throw new Error('Login failed');
res.json();
if (res.ok) return res.json();
throw new Error('Failed to login');
});

export const AccountService = {
Expand Down
Loading
Loading