diff --git a/src/components/backup/BackupSheet.tsx b/src/components/backup/BackupSheet.tsx
index 12e15c60190..fc08b7da52d 100644
--- a/src/components/backup/BackupSheet.tsx
+++ b/src/components/backup/BackupSheet.tsx
@@ -1,11 +1,14 @@
import { RouteProp, useRoute } from '@react-navigation/native';
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { BackupCloudStep, RestoreCloudStep } from '.';
import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes';
import BackupWalletPrompt from '@/components/backup/BackupWalletPrompt';
+import ManualBackupPrompt from '@/components/backup/ManualBackupPrompt';
import { BackgroundProvider } from '@/design-system';
import { SimpleSheet } from '@/components/sheet/SimpleSheet';
import { getHeightForStep } from '@/navigation/config';
+import CloudBackupPrompt from './CloudBackupPrompt';
+import { backupsStore } from '@/state/backups/backups';
type BackupSheetParams = {
BackupSheet: {
@@ -22,16 +25,38 @@ export default function BackupSheet() {
const renderStep = useCallback(() => {
switch (step) {
- case WalletBackupStepTypes.backup_cloud:
+ case WalletBackupStepTypes.create_cloud_backup:
return ;
case WalletBackupStepTypes.restore_from_backup:
return ;
case WalletBackupStepTypes.backup_prompt:
+ return ;
+ case WalletBackupStepTypes.backup_prompt_manual:
+ return ;
+ case WalletBackupStepTypes.backup_prompt_cloud:
+ return ;
default:
return ;
}
}, [step]);
+ useEffect(() => {
+ return () => {
+ if (
+ [
+ WalletBackupStepTypes.backup_prompt,
+ WalletBackupStepTypes.backup_prompt_manual,
+ WalletBackupStepTypes.backup_prompt_cloud,
+ ].includes(step)
+ ) {
+ if (backupsStore.getState().timesPromptedForBackup === 0) {
+ backupsStore.getState().setTimesPromptedForBackup(1);
+ }
+ backupsStore.getState().setLastBackupPromptAt(Date.now());
+ }
+ };
+ }, [step]);
+
return (
{({ backgroundColor }) => (
diff --git a/src/components/backup/CloudBackupPrompt.tsx b/src/components/backup/CloudBackupPrompt.tsx
new file mode 100644
index 00000000000..7d015cae976
--- /dev/null
+++ b/src/components/backup/CloudBackupPrompt.tsx
@@ -0,0 +1,122 @@
+import React, { useCallback } from 'react';
+import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system';
+import * as lang from '@/languages';
+import { ImgixImage } from '../images';
+import WalletsAndBackupIcon from '@/assets/WalletsAndBackup.png';
+import { Source } from 'react-native-fast-image';
+import { cloudPlatform } from '@/utils/platform';
+import { ButtonPressAnimation } from '../animations';
+import Routes from '@/navigation/routesNames';
+import { useNavigation } from '@/navigation';
+import { useWallets } from '@/hooks';
+import { format } from 'date-fns';
+import { useCreateBackup } from './useCreateBackup';
+import { executeFnIfCloudBackupAvailable } from '@/model/backup';
+import { backupsStore } from '@/state/backups/backups';
+
+const imageSize = 72;
+
+export default function CloudBackupPrompt() {
+ const { navigate, goBack } = useNavigation();
+ const { mostRecentBackup } = backupsStore(state => ({
+ mostRecentBackup: state.mostRecentBackup,
+ }));
+ const { selectedWallet } = useWallets();
+ const createBackup = useCreateBackup();
+
+ const onCloudBackup = useCallback(() => {
+ // pop the bottom sheet, and navigate to the backup section inside settings sheet
+ goBack();
+ navigate(Routes.SETTINGS_SHEET, {
+ screen: Routes.SETTINGS_SECTION_BACKUP,
+ initial: false,
+ });
+
+ executeFnIfCloudBackupAvailable({
+ fn: () =>
+ createBackup({
+ walletId: selectedWallet.id,
+ }),
+ logout: true,
+ });
+ }, [createBackup, goBack, navigate, selectedWallet.id]);
+
+ const onMaybeLater = useCallback(() => goBack(), [goBack]);
+
+ return (
+
+
+
+
+
+ {lang.t(lang.l.back_up.cloud.add_wallet_to_cloud_backups)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {' '}
+ {lang.t(lang.l.back_up.cloud.back_to_cloud_platform_now, {
+ cloudPlatform,
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {lang.t(lang.l.back_up.cloud.mayber_later)}
+
+
+
+
+
+
+
+
+
+
+ {mostRecentBackup && (
+
+
+
+
+ {lang.t(lang.l.back_up.cloud.latest_backup, {
+ date: format(new Date(mostRecentBackup.lastModified), "M/d/yy 'at' h:mm a"),
+ })}
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/components/backup/ManualBackupPrompt.tsx b/src/components/backup/ManualBackupPrompt.tsx
new file mode 100644
index 00000000000..e882215bc99
--- /dev/null
+++ b/src/components/backup/ManualBackupPrompt.tsx
@@ -0,0 +1,101 @@
+import React, { useCallback, useEffect } from 'react';
+import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system';
+import * as lang from '@/languages';
+import { ImgixImage } from '../images';
+import ManuallyBackedUpIcon from '@/assets/ManuallyBackedUp.png';
+import { Source } from 'react-native-fast-image';
+import { ButtonPressAnimation } from '../animations';
+import { useNavigation } from '@/navigation';
+import Routes from '@/navigation/routesNames';
+import { useWallets } from '@/hooks';
+import walletTypes from '@/helpers/walletTypes';
+import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes';
+import walletBackupTypes from '@/helpers/walletBackupTypes';
+import { backupsStore } from '@/state/backups/backups';
+
+const imageSize = 72;
+
+export default function ManualBackupPrompt() {
+ const { navigate, goBack } = useNavigation();
+ const { selectedWallet } = useWallets();
+
+ const onManualBackup = async () => {
+ const title =
+ selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey
+ ? (selectedWallet.addresses || [])[0].label
+ : selectedWallet.name;
+
+ goBack();
+ navigate(Routes.SETTINGS_SHEET, {
+ screen: SETTINGS_BACKUP_ROUTES.SECRET_WARNING,
+ params: {
+ isBackingUp: true,
+ title,
+ backupType: walletBackupTypes.manual,
+ walletId: selectedWallet.id,
+ },
+ });
+ };
+
+ const onMaybeLater = useCallback(() => goBack(), [goBack]);
+
+ return (
+
+
+
+
+
+ {lang.t(lang.l.back_up.manual.backup_manually_now)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {lang.t(lang.l.back_up.manual.back_up_now)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {lang.t(lang.l.back_up.manual.already_backed_up)}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/backup/useCreateBackup.ts b/src/components/backup/useCreateBackup.ts
index e227b1173c4..40a4e4e3beb 100644
--- a/src/components/backup/useCreateBackup.ts
+++ b/src/components/backup/useCreateBackup.ts
@@ -138,7 +138,7 @@ export const useCreateBackup = () => {
return new Promise(resolve => {
return Navigation.handleAction(Routes.BACKUP_SHEET, {
nativeScreen: true,
- step: walletBackupStepTypes.backup_cloud,
+ step: walletBackupStepTypes.create_cloud_backup,
onSuccess: async (password: string) => {
return resolve(password);
},
diff --git a/src/handlers/walletReadyEvents.ts b/src/handlers/walletReadyEvents.ts
index c33363ff727..ce9b662139f 100644
--- a/src/handlers/walletReadyEvents.ts
+++ b/src/handlers/walletReadyEvents.ts
@@ -13,7 +13,8 @@ import { checkKeychainIntegrity } from '@/redux/wallets';
import Routes from '@/navigation/routesNames';
import { logger } from '@/logger';
import { IS_TEST } from '@/env';
-import { backupsStore, LoadingStates } from '@/state/backups/backups';
+import { backupsStore, CloudBackupState, LoadingStates, oneWeekInMs } from '@/state/backups/backups';
+import walletBackupTypes from '@/helpers/walletBackupTypes';
export const runKeychainIntegrityChecks = async () => {
const keychainIntegrityState = await getKeychainIntegrityState();
@@ -34,10 +35,28 @@ const promptForBackupOnceReadyOrNotAvailable = async (): Promise => {
status = backupsStore.getState().status;
}
- logger.debug(`[walletReadyEvents]: BackupSheet: showing backup now sheet for selected wallet`);
+ if (status !== CloudBackupState.Ready) {
+ return false;
+ }
+
+ const { backupProvider, timesPromptedForBackup, lastBackupPromptAt } = backupsStore.getState();
+
+ // prompt for backup every week if first time prompting, otherwise prompt every 2 weeks
+ if (lastBackupPromptAt && Date.now() - lastBackupPromptAt < oneWeekInMs * (timesPromptedForBackup + 1)) {
+ return false;
+ }
+
+ const step =
+ backupProvider === walletBackupTypes.cloud
+ ? WalletBackupStepTypes.backup_prompt_cloud
+ : backupProvider === walletBackupTypes.manual
+ ? WalletBackupStepTypes.backup_prompt_manual
+ : WalletBackupStepTypes.backup_prompt;
+
+ logger.debug(`[walletReadyEvents]: BackupSheet: showing ${step} backup sheet`);
triggerOnSwipeLayout(() =>
Navigation.handleAction(Routes.BACKUP_SHEET, {
- step: WalletBackupStepTypes.backup_prompt,
+ step,
})
);
return true;
diff --git a/src/helpers/walletBackupStepTypes.ts b/src/helpers/walletBackupStepTypes.ts
index 2fbf0cb8f9e..67b14680737 100644
--- a/src/helpers/walletBackupStepTypes.ts
+++ b/src/helpers/walletBackupStepTypes.ts
@@ -1,9 +1,8 @@
export default {
backup_prompt: 'backup_prompt',
- backup_manual: 'backup_manual',
- backup_cloud: 'backup_cloud',
+ backup_prompt_manual: 'backup_prompt_manual',
+ backup_prompt_cloud: 'backup_prompt_cloud',
restore_from_backup: 'restore_from_backup',
- backup_now_to_cloud: 'cloud',
- backup_now_manually: 'manual',
+ create_cloud_backup: 'create_cloud_backup',
check_identifier: 'check_identifier',
};
diff --git a/src/hooks/useImportingWallet.ts b/src/hooks/useImportingWallet.ts
index 096099c8f61..051aebd864c 100644
--- a/src/hooks/useImportingWallet.ts
+++ b/src/hooks/useImportingWallet.ts
@@ -31,6 +31,7 @@ import { ReviewPromptAction } from '@/storage/schema';
import { ChainId } from '@/state/backendNetworks/types';
import { backupsStore } from '@/state/backups/backups';
import { IS_TEST } from '@/env';
+import walletBackupTypes from '@/helpers/walletBackupTypes';
export default function useImportingWallet({ showImportModal = true } = {}) {
const { accountAddress } = useAccountSettings();
@@ -315,9 +316,7 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
dangerouslyGetParent?.()?.goBack();
InteractionManager.runAfterInteractions(async () => {
if (previousWalletCount === 0) {
- // on Android replacing is not working well, so we navigate and then remove the screen below
- const action = navigate;
- action(Routes.SWIPE_LAYOUT, {
+ navigate(Routes.SWIPE_LAYOUT, {
params: { initialized: true },
screen: Routes.WALLET_SCREEN,
});
@@ -333,6 +332,21 @@ export default function useImportingWallet({ showImportModal = true } = {}) {
handleSetImporting(false);
}
+ if (
+ backupProvider === walletBackupTypes.cloud &&
+ !(
+ IS_TEST ||
+ isENSAddressFormat(input) ||
+ isUnstoppableAddressFormat(input) ||
+ isValidAddress(input) ||
+ isValidBluetoothDeviceId(input)
+ )
+ ) {
+ Navigation.handleAction(Routes.BACKUP_SHEET, {
+ step: WalletBackupStepTypes.backup_prompt_cloud,
+ });
+ }
+
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
handleReviewPromptAction(ReviewPromptAction.WatchWallet);
diff --git a/src/hooks/useInitializeWallet.ts b/src/hooks/useInitializeWallet.ts
index 80aa4e903ea..cbd813c8439 100644
--- a/src/hooks/useInitializeWallet.ts
+++ b/src/hooks/useInitializeWallet.ts
@@ -16,7 +16,6 @@ import useLoadAccountData from './useLoadAccountData';
import useLoadGlobalEarlyData from './useLoadGlobalEarlyData';
import useOpenSmallBalances from './useOpenSmallBalances';
import { WrappedAlert as Alert } from '@/helpers/alert';
-import { PROFILES, useExperimentalFlag } from '@/config';
import { runKeychainIntegrityChecks } from '@/handlers/walletReadyEvents';
import { RainbowError, logger } from '@/logger';
import { getOrCreateDeviceId, getWalletContext } from '@/analytics/utils';
@@ -32,7 +31,6 @@ export default function useInitializeWallet() {
const { network } = useAccountSettings();
const hideSplashScreen = useHideSplashScreen();
const { setIsSmallBalancesOpen } = useOpenSmallBalances();
- const profilesEnabled = useExperimentalFlag(PROFILES);
const getWalletStatusForPerformanceMetrics = (isNew: boolean, isImporting: boolean): string => {
if (isNew) {
@@ -180,7 +178,7 @@ export default function useInitializeWallet() {
return null;
}
},
- [dispatch, hideSplashScreen, loadAccountData, loadGlobalEarlyData, network, profilesEnabled, setIsSmallBalancesOpen]
+ [dispatch, hideSplashScreen, loadAccountData, loadGlobalEarlyData, network, setIsSmallBalancesOpen]
);
return initializeWallet;
diff --git a/src/navigation/Routes.android.tsx b/src/navigation/Routes.android.tsx
index 1e03c2d96a3..21493ff777a 100644
--- a/src/navigation/Routes.android.tsx
+++ b/src/navigation/Routes.android.tsx
@@ -209,11 +209,7 @@ function BSNavigator() {
const { params: { step } = {} as any } = route.route;
let heightForStep = backupSheetSizes.short;
- if (
- step === walletBackupStepTypes.backup_cloud ||
- step === walletBackupStepTypes.backup_manual ||
- step === walletBackupStepTypes.restore_from_backup
- ) {
+ if (step === walletBackupStepTypes.create_cloud_backup || step === walletBackupStepTypes.restore_from_backup) {
heightForStep = backupSheetSizes.long;
} else if (step === walletBackupStepTypes.backup_prompt) {
heightForStep = backupSheetSizes.medium;
diff --git a/src/navigation/config.tsx b/src/navigation/config.tsx
index 9097c84e1d6..05ad779c1bf 100644
--- a/src/navigation/config.tsx
+++ b/src/navigation/config.tsx
@@ -99,8 +99,7 @@ export const backupSheetSizes = {
export const getHeightForStep = (step: string) => {
switch (step) {
- case WalletBackupStepTypes.backup_cloud:
- case WalletBackupStepTypes.backup_manual:
+ case WalletBackupStepTypes.create_cloud_backup:
case WalletBackupStepTypes.restore_from_backup:
return backupSheetSizes.long;
case WalletBackupStepTypes.backup_prompt:
diff --git a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
index d488790c223..863ca437e8a 100644
--- a/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
+++ b/src/screens/SettingsSheet/components/Backups/WalletsAndBackup.tsx
@@ -199,13 +199,15 @@ export const WalletsAndBackup = () => {
loadingState: null,
});
scrollviewRef.current?.scrollTo({ y: 0, animated: true });
+ const step =
+ backupProvider === WalletBackupTypes.cloud ? walletBackupStepTypes.backup_prompt_cloud : walletBackupStepTypes.backup_prompt;
Navigation.handleAction(Routes.BACKUP_SHEET, {
- step: walletBackupStepTypes.backup_prompt,
+ step,
});
}
},
});
- }, [dispatch, initializeWallet, navigate, walletTypeCount.phrase]);
+ }, [dispatch, initializeWallet, navigate, walletTypeCount.phrase, backupProvider]);
const onPressLearnMoreAboutCloudBackups = useCallback(() => {
navigate(Routes.LEARN_WEB_VIEW_SCREEN, {
diff --git a/src/state/backups/backups.ts b/src/state/backups/backups.ts
index 9c2c2ae87e6..26bbae4e5db 100644
--- a/src/state/backups/backups.ts
+++ b/src/state/backups/backups.ts
@@ -25,9 +25,17 @@ export enum CloudBackupState {
const DEFAULT_TIMEOUT = 10_000;
const MAX_RETRIES = 3;
+export const oneWeekInMs = 7 * 24 * 60 * 60 * 1000;
+
export const LoadingStates = [CloudBackupState.Initializing, CloudBackupState.Syncing, CloudBackupState.Fetching];
interface BackupsStore {
+ timesPromptedForBackup: number;
+ setTimesPromptedForBackup: (timesPromptedForBackup: number) => void;
+
+ lastBackupPromptAt: number | undefined;
+ setLastBackupPromptAt: (lastBackupPromptAt: number | undefined) => void;
+
storedPassword: string;
setStoredPassword: (storedPassword: string) => void;
@@ -57,136 +65,156 @@ interface BackupsStore {
const returnEarlyIfLockedStates = [CloudBackupState.Syncing, CloudBackupState.Fetching];
-export const backupsStore = createRainbowStore((set, get) => ({
- storedPassword: '',
- setStoredPassword: storedPassword => set({ storedPassword }),
+export const backupsStore = createRainbowStore(
+ (set, get) => ({
+ timesPromptedForBackup: 0,
+ setTimesPromptedForBackup: timesPromptedForBackup => set({ timesPromptedForBackup }),
- backupProvider: undefined,
- setBackupProvider: provider => set({ backupProvider: provider }),
+ lastBackupPromptAt: undefined,
+ setLastBackupPromptAt: lastBackupPromptAt => set({ lastBackupPromptAt }),
+ storedPassword: '',
+ setStoredPassword: storedPassword => set({ storedPassword }),
- status: CloudBackupState.Initializing,
- setStatus: status => set({ status }),
+ backupProvider: undefined,
+ setBackupProvider: provider => set({ backupProvider: provider }),
- backups: { files: [] },
- setBackups: backups => set({ backups }),
+ status: CloudBackupState.Initializing,
+ setStatus: status => set({ status }),
- mostRecentBackup: undefined,
- setMostRecentBackup: backup => set({ mostRecentBackup: backup }),
+ backups: { files: [] },
+ setBackups: backups => set({ backups }),
- password: '',
- setPassword: password => set({ password }),
+ mostRecentBackup: undefined,
+ setMostRecentBackup: backup => set({ mostRecentBackup: backup }),
- syncAndFetchBackups: async (retryOnFailure = true, retryCount = 0) => {
- const { status } = get();
+ password: '',
+ setPassword: password => set({ password }),
- const timeoutPromise = new Promise<{ success: boolean; retry?: boolean }>(resolve => {
- setTimeout(() => {
- resolve({ success: false, retry: retryOnFailure });
- }, DEFAULT_TIMEOUT);
- });
+ syncAndFetchBackups: async (retryOnFailure = true, retryCount = 0) => {
+ const { status } = get();
- const syncAndPullFiles = async (): Promise<{ success: boolean; retry?: boolean }> => {
- try {
- const isAvailable = await isCloudBackupAvailable();
- if (!isAvailable) {
- logger.debug('[backupsStore]: Cloud backup is not available');
- set({ backupProvider: undefined, status: CloudBackupState.NotAvailable, backups: { files: [] }, mostRecentBackup: undefined });
- return {
- success: false,
- retry: false,
- };
- }
+ const timeoutPromise = new Promise<{ success: boolean; retry?: boolean }>(resolve => {
+ setTimeout(() => {
+ resolve({ success: false, retry: retryOnFailure });
+ }, DEFAULT_TIMEOUT);
+ });
- if (IS_ANDROID) {
- const gdata = await getGoogleAccountUserData(true);
- if (!gdata) {
- logger.debug('[backupsStore]: Google account is not available');
+ const syncAndPullFiles = async (): Promise<{ success: boolean; retry?: boolean }> => {
+ try {
+ const isAvailable = await isCloudBackupAvailable();
+ if (!isAvailable) {
+ logger.debug('[backupsStore]: Cloud backup is not available');
set({ backupProvider: undefined, status: CloudBackupState.NotAvailable, backups: { files: [] }, mostRecentBackup: undefined });
return {
success: false,
retry: false,
};
}
- }
- set({ status: CloudBackupState.Syncing });
- logger.debug('[backupsStore]: Syncing with cloud');
- await syncCloud();
+ if (IS_ANDROID) {
+ const gdata = await getGoogleAccountUserData(true);
+ if (!gdata) {
+ logger.debug('[backupsStore]: Google account is not available');
+ set({
+ backupProvider: undefined,
+ status: CloudBackupState.NotAvailable,
+ backups: { files: [] },
+ mostRecentBackup: undefined,
+ });
+ return {
+ success: false,
+ retry: false,
+ };
+ }
+ }
- set({ status: CloudBackupState.Fetching });
- logger.debug('[backupsStore]: Fetching backups');
- const backups = await fetchAllBackups();
+ set({ status: CloudBackupState.Syncing });
+ logger.debug('[backupsStore]: Syncing with cloud');
+ await syncCloud();
- set({ backups });
+ set({ status: CloudBackupState.Fetching });
+ logger.debug('[backupsStore]: Fetching backups');
+ const backups = await fetchAllBackups();
- const { wallets } = store.getState().wallets;
+ set({ backups });
- // if the user has any cloud backups, set the provider to cloud
- if (backups.files.length > 0) {
- set({
- backupProvider: walletBackupTypes.cloud,
- mostRecentBackup: getMostRecentCloudBackup(backups.files),
+ const { wallets } = store.getState().wallets;
+
+ // if the user has any cloud backups, set the provider to cloud
+ if (backups.files.length > 0) {
+ set({
+ backupProvider: walletBackupTypes.cloud,
+ mostRecentBackup: getMostRecentCloudBackup(backups.files),
+ });
+ } else if (hasManuallyBackedUpWallet(wallets)) {
+ set({ backupProvider: walletBackupTypes.manual });
+ } else {
+ set({ backupProvider: undefined });
+ }
+
+ logger.debug(`[backupsStore]: Retrieved ${backups.files.length} backup files`);
+
+ set({ status: CloudBackupState.Ready });
+ return {
+ success: true,
+ retry: false,
+ };
+ } catch (e) {
+ logger.error(new RainbowError('[backupsStore]: Failed to fetch all backups'), {
+ error: e,
});
- } else if (hasManuallyBackedUpWallet(wallets)) {
- set({ backupProvider: walletBackupTypes.manual });
- } else {
- set({ backupProvider: undefined });
+ set({ status: CloudBackupState.FailedToInitialize });
+
+ // See https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInStatusCodes#public-static-final-int-sign_in_cancelled
+ const stringifiedError = JSON.stringify(e);
+ if (stringifiedError.includes('12501')) {
+ logger.warn('[backupsStore]: Google sign in / oauth cancelled');
+ return {
+ success: false,
+ retry: false,
+ };
+ }
}
- logger.debug(`[backupsStore]: Retrieved ${backups.files.length} backup files`);
+ return {
+ success: false,
+ retry: retryOnFailure,
+ };
+ };
- set({ status: CloudBackupState.Ready });
+ if (mutex.isLocked() || returnEarlyIfLockedStates.includes(status)) {
+ logger.debug('[backupsStore]: Mutex is locked or returnEarlyIfLockedStates includes status', {
+ status,
+ });
return {
- success: true,
+ success: false,
retry: false,
};
- } catch (e) {
- logger.error(new RainbowError('[backupsStore]: Failed to fetch all backups'), {
- error: e,
- });
- set({ status: CloudBackupState.FailedToInitialize });
+ }
- // See https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInStatusCodes#public-static-final-int-sign_in_cancelled
- const stringifiedError = JSON.stringify(e);
- if (stringifiedError.includes('12501')) {
- logger.warn('[backupsStore]: Google sign in / oauth cancelled');
- return {
- success: false,
- retry: false,
- };
- }
+ const releaser = await mutex.acquire();
+ logger.debug('[backupsStore]: Acquired mutex');
+ const { success, retry } = await Promise.race([syncAndPullFiles(), timeoutPromise]);
+ releaser();
+ logger.debug('[backupsStore]: Released mutex');
+ if (retry && retryCount < MAX_RETRIES) {
+ logger.debug(`[backupsStore]: Retrying sync and fetch backups attempt: ${retryCount + 1}`);
+ return get().syncAndFetchBackups(retryOnFailure, retryCount + 1);
}
- return {
- success: false,
- retry: retryOnFailure,
- };
- };
+ if (retry && retryCount >= MAX_RETRIES) {
+ logger.error(new RainbowError('[backupsStore]: Max retry attempts reached. Sync failed.'));
+ }
- if (mutex.isLocked() || returnEarlyIfLockedStates.includes(status)) {
- logger.debug('[backupsStore]: Mutex is locked or returnEarlyIfLockedStates includes status', {
- status,
- });
- return {
- success: false,
- retry: false,
- };
- }
-
- const releaser = await mutex.acquire();
- logger.debug('[backupsStore]: Acquired mutex');
- const { success, retry } = await Promise.race([syncAndPullFiles(), timeoutPromise]);
- releaser();
- logger.debug('[backupsStore]: Released mutex');
- if (retry && retryCount < MAX_RETRIES) {
- logger.debug(`[backupsStore]: Retrying sync and fetch backups attempt: ${retryCount + 1}`);
- return get().syncAndFetchBackups(retryOnFailure, retryCount + 1);
- }
-
- if (retry && retryCount >= MAX_RETRIES) {
- logger.error(new RainbowError('[backupsStore]: Max retry attempts reached. Sync failed.'));
- }
-
- return { success, retry };
- },
-}));
+ return { success, retry };
+ },
+ }),
+ {
+ storageKey: 'backups',
+ version: 0,
+ partialize: state => ({
+ lastBackupPromptAt: state.lastBackupPromptAt,
+ timesPromptedForBackup: state.timesPromptedForBackup,
+ }),
+ }
+);