diff --git a/__tests__/backups.ts b/__tests__/backups.ts
deleted file mode 100644
index 9bffc5a78..000000000
--- a/__tests__/backups.ts
+++ /dev/null
@@ -1,320 +0,0 @@
-import SDK from '@synonymdev/slashtags-sdk';
-import RAM from 'random-access-memory';
-import { stringToBytes } from '@synonymdev/react-native-lnurl/dist/utils/helpers';
-
-import '../src/utils/i18n';
-import {
- EBackupCategories,
- fetchBackup,
- uploadBackup,
-} from '../src/utils/backup/backpack';
-import { bytesToString } from '../src/utils/converters';
-import store from '../src/store';
-import {
- addMetaTxTag,
- updatePendingInvoice,
- addMetaTxSlashtagsUrl,
- resetMetadataState,
-} from '../src/store/slices/metadata';
-import {
- performBlocktankRestore,
- performLdkActivityRestore,
- performMetadataRestore,
- performRemoteBackup,
- performSettingsRestore,
- performWidgetsRestore,
-} from '../src/store/utils/backup';
-import {
- dispatch,
- getActivityStore,
- getBlocktankStore,
- getMetaDataStore,
- getSettingsStore,
- getWidgetsStore,
-} from '../src/store/helpers';
-import {
- updateSettings,
- resetSettingsState,
-} from '../src/store/slices/settings';
-import {
- resetWidgetsState,
- setFeedWidget,
- updateWidgets,
-} from '../src/store/slices/widgets';
-import {
- addActivityItem,
- resetActivityState,
-} from '../src/store/slices/activity';
-import { EActivityType } from '../src/store/types/activity';
-import { EPaymentType } from '../src/store/types/wallet';
-import {
- addPaidBlocktankOrder,
- resetBlocktankState,
-} from '../src/store/slices/blocktank';
-import { defaultOrderResponse } from '../src/store/shapes/blocktank';
-import { updateBlocktankOrder } from '../src/store/slices/blocktank';
-import { EAvailableNetwork } from '../src/utils/networks';
-
-jest.setTimeout(30000);
-
-describe('Remote backups', () => {
- let sdk, slashtag;
- beforeAll(async () => {
- sdk = new SDK({
- primaryKey: new Uint8Array(32), //For testing, so we don't fill up server with junk after each test
- storage: RAM,
- relay: 'wss://dht-relay.synonym.to',
- });
- await sdk.ready();
- slashtag = sdk.slashtag();
- });
-
- afterAll(async () => {
- await sdk.close();
- });
-
- it('Backups up and restores a blob', async () => {
- const message = 'Back me up plz';
- const category = EBackupCategories.jest;
-
- const uploadRes = await uploadBackup(
- slashtag,
- stringToBytes(message),
- category,
- EAvailableNetwork.bitcoinRegtest,
- );
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- const timestamp = uploadRes.value;
-
- const fetchRes = await fetchBackup(
- slashtag,
- timestamp,
- category,
- EAvailableNetwork.bitcoinRegtest,
- );
-
- if (fetchRes.isErr()) {
- throw fetchRes.error;
- }
- const jsonString = bytesToString(fetchRes.value.content);
- expect(jsonString).toEqual(message);
- });
-
- it('Backups and restores metadata', async () => {
- dispatch(addMetaTxTag({ txId: 'txid1', tag: 'tag' }));
- dispatch(
- updatePendingInvoice({
- id: 'id123',
- tags: ['futuretag'],
- address: 'address',
- payReq: 'lightningInvoice',
- }),
- );
- dispatch(addMetaTxSlashtagsUrl({ txId: 'txid2', url: 'slashtag' }));
-
- const backup = getMetaDataStore();
-
- const uploadRes = await performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteMetadataBackupSynced',
- syncRequiredKey: 'remoteMetadataBackupSyncRequired',
- syncCompletedKey: 'remoteMetadataBackupLastSync',
- backupCategory: EBackupCategories.metadata,
- backup,
- });
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- dispatch(resetMetadataState());
- expect(store.getState().metadata.tags).toMatchObject({});
-
- const restoreRes = await performMetadataRestore({
- slashtag,
- });
-
- if (restoreRes.isErr()) {
- throw restoreRes.error;
- }
-
- expect(restoreRes.value.backupExists).toEqual(true);
- expect(store.getState().metadata).toEqual(backup);
- expect(store.getState().backup.remoteMetadataBackupSynced).toEqual(true);
- });
-
- it('Backups and restores settings', async () => {
- dispatch(
- updateSettings({
- selectedCurrency: 'GBP',
- enableOfflinePayments: false,
- }),
- );
-
- const backup = getSettingsStore();
-
- const uploadRes = await performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteSettingsBackupSynced',
- syncRequiredKey: 'remoteSettingsBackupSyncRequired',
- syncCompletedKey: 'remoteSettingsBackupLastSync',
- backupCategory: EBackupCategories.settings,
- backup,
- });
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- dispatch(resetSettingsState());
- expect(store.getState().settings.selectedCurrency).toEqual('USD');
-
- const restoreRes = await performSettingsRestore({
- slashtag,
- });
-
- if (restoreRes.isErr()) {
- throw restoreRes.error;
- }
-
- expect(restoreRes.value.backupExists).toEqual(true);
- expect(store.getState().settings).toEqual(backup);
- expect(store.getState().backup.remoteSettingsBackupSynced).toEqual(true);
- });
-
- it('Backups and restores widgets', async () => {
- dispatch(
- setFeedWidget({
- url: 'url',
- type: 'type',
- fields: [
- {
- name: 'name',
- main: 'main',
- files: {},
- },
- ],
- }),
- );
- dispatch(updateWidgets({ onboardedWidgets: true }));
-
- const backup = getWidgetsStore();
-
- const uploadRes = await performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteWidgetsBackupSynced',
- syncRequiredKey: 'remoteWidgetsBackupSyncRequired',
- syncCompletedKey: 'remoteWidgetsBackupLastSync',
- backupCategory: EBackupCategories.widgets,
- backup,
- });
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- dispatch(resetWidgetsState());
- expect(store.getState().widgets.widgets).toMatchObject({});
-
- const restoreRes = await performWidgetsRestore({
- slashtag,
- });
-
- if (restoreRes.isErr()) {
- throw restoreRes.error;
- }
-
- expect(restoreRes.value.backupExists).toEqual(true);
- expect(store.getState().widgets).toEqual(backup);
- expect(store.getState().backup.remoteWidgetsBackupSynced).toEqual(true);
- });
-
- it('Backups and restores LDK Activity', async () => {
- dispatch(
- addActivityItem({
- id: 'id',
- activityType: EActivityType.lightning,
- txType: EPaymentType.received,
- message: '',
- address: 'invoice',
- confirmed: true,
- value: 1,
- timestamp: new Date().getTime(),
- }),
- );
-
- const backup = getActivityStore().items.filter(
- (a) => a.activityType === EActivityType.lightning,
- );
-
- const uploadRes = await performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteLdkActivityBackupSynced',
- syncRequiredKey: 'remoteLdkActivityBackupSyncRequired',
- syncCompletedKey: 'remoteLdkActivityBackupLastSync',
- backupCategory: EBackupCategories.ldkActivity,
- backup,
- });
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- dispatch(resetActivityState());
- expect(store.getState().activity.items.length).toEqual(0);
-
- const restoreRes = await performLdkActivityRestore({
- slashtag,
- });
-
- if (restoreRes.isErr()) {
- throw restoreRes.error;
- }
-
- expect(restoreRes.value.backupExists).toEqual(true);
- expect(store.getState().activity.items).toEqual(backup);
- expect(store.getState().backup.remoteLdkActivityBackupSynced).toEqual(true);
- });
-
- it('Backups and restores Blocktank orders', async () => {
- dispatch(addPaidBlocktankOrder({ orderId: 'id', txid: 'txid' }));
- dispatch(updateBlocktankOrder(defaultOrderResponse));
-
- const { orders, paidOrders } = getBlocktankStore();
- const backup = { orders, paidOrders };
-
- const uploadRes = await performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteBlocktankBackupSynced',
- syncRequiredKey: 'remoteBlocktankBackupSyncRequired',
- syncCompletedKey: 'remoteBlocktankBackupLastSync',
- backupCategory: EBackupCategories.blocktank,
- backup,
- });
-
- if (uploadRes.isErr()) {
- throw uploadRes.error;
- }
-
- dispatch(resetBlocktankState());
- expect(store.getState().blocktank.orders.length).toEqual(0);
- expect(store.getState().blocktank.paidOrders).toMatchObject({});
-
- const restoreRes = await performBlocktankRestore({
- slashtag,
- });
-
- if (restoreRes.isErr()) {
- throw restoreRes.error;
- }
-
- expect(restoreRes.value.backupExists).toEqual(true);
- expect(store.getState().blocktank.orders).toEqual(backup.orders);
- expect(store.getState().blocktank.paidOrders).toEqual(backup.paidOrders);
- expect(store.getState().backup.remoteBlocktankBackupSynced).toEqual(true);
- });
-});
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index f33821404..124ccfdf3 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -392,7 +392,7 @@ PODS:
- React-Core
- react-native-keep-awake (1.2.2):
- React-Core
- - react-native-ldk (0.0.127):
+ - react-native-ldk (0.0.129):
- React
- react-native-mmkv (2.11.0):
- MMKV (>= 1.2.13)
@@ -905,7 +905,7 @@ SPEC CHECKSUMS:
react-native-flipper: 9c1957af24b76493ba74f46d000a5c1d485e7731
react-native-image-picker: 2e2e82aba9b6a91a7c78f7d9afde341a2659c7b8
react-native-keep-awake: ad1d67f617756b139536977a0bf06b27cec0714a
- react-native-ldk: 4ab3d26d5e1356313c572814289cc516dc18dd88
+ react-native-ldk: 30a80ced71007307cfceb8a79e4996788ab413ea
react-native-mmkv: e97c0c79403fb94577e5d902ab1ebd42b0715b43
react-native-netinfo: 5ddbf20865bcffab6b43d0e4e1fd8b3896beb898
react-native-quick-base64: a5dbe4528f1453e662fcf7351029500b8b63e7bb
diff --git a/package.json b/package.json
index 5bb6e075a..ac26570db 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"@synonymdev/blocktank-client": "0.0.50",
"@synonymdev/blocktank-lsp-http-client": "0.13.1",
"@synonymdev/feeds": "2.1.1",
- "@synonymdev/react-native-ldk": "0.0.127",
+ "@synonymdev/react-native-ldk": "0.0.129",
"@synonymdev/react-native-lnurl": "0.0.7",
"@synonymdev/result": "0.0.2",
"@synonymdev/slashtags-auth": "1.0.0-alpha.6",
diff --git a/src/assets/icons/wallet.ts b/src/assets/icons/wallet.ts
index 8dbf94d8e..dc6b13028 100644
--- a/src/assets/icons/wallet.ts
+++ b/src/assets/icons/wallet.ts
@@ -182,9 +182,6 @@ export const usersIcon = (color = 'white'): string =>
export const userIcon = (color = 'white'): string =>
``;
-export const userRectangleIcon = (color = 'white'): string =>
- ``;
-
export const speedFastIcon = (color = 'white'): string =>
``;
diff --git a/src/screens/Recovery/Lightning.tsx b/src/screens/Recovery/Lightning.tsx
index 1f3985e8d..7edbc77ee 100644
--- a/src/screens/Recovery/Lightning.tsx
+++ b/src/screens/Recovery/Lightning.tsx
@@ -17,7 +17,7 @@ import { useSelectedSlashtag } from '../../hooks/slashtags';
import { SlashtagsProvider } from '../../components/SlashtagsProvider';
import { SlashtagsProvider2 } from '../../components/SlashtagsProvider2';
import {
- EBackupCategories,
+ EBackupCategoriesOld,
fetchBackup,
listBackups,
} from '../../utils/backup/backpack';
@@ -70,7 +70,7 @@ const LightningWithSlashtags = ({
const listLdkBackups = async (): Promise => {
const res = await listBackups(
slashtag.slashtag,
- EBackupCategories.ldkComplete,
+ EBackupCategoriesOld.ldkComplete,
__DEV__ ? selectedNetwork : EAvailableNetwork.bitcoin,
);
@@ -116,7 +116,7 @@ const LightningWithSlashtags = ({
const res = await fetchBackup(
slashtag.slashtag,
timestamp,
- EBackupCategories.ldkComplete,
+ EBackupCategoriesOld.ldkComplete,
selectedNetwork,
);
diff --git a/src/screens/Settings/AppStatus/index.tsx b/src/screens/Settings/AppStatus/index.tsx
index d1584bb8b..65777d734 100644
--- a/src/screens/Settings/AppStatus/index.tsx
+++ b/src/screens/Settings/AppStatus/index.tsx
@@ -1,32 +1,35 @@
-import { SettingsScreenProps } from '../../../navigation/types';
import React, { memo, ReactElement, useEffect, useMemo, useState } from 'react';
-import { ScrollView, View as ThemedView } from '../../../styles/components';
-import SettingsView from '../SettingsView';
-import { Caption13M, Text01M } from '../../../styles/text';
-import { StyleSheet, View } from 'react-native';
import { useTranslation } from 'react-i18next';
-import {
- BitcoinSlantedIcon,
- BroadcastIcon,
- CloudCheckIcon,
- GlobeSimpleIcon,
- LightningHollow,
-} from '../../../styles/icons';
-import { IColors } from '../../../styles/colors';
+import { StyleSheet, View } from 'react-native';
+
import { useAppSelector } from '../../../hooks/redux';
+import { SettingsScreenProps } from '../../../navigation/types';
+import { backupSelector } from '../../../store/reselect/backup';
+import { blocktankPaidOrdersFullSelector } from '../../../store/reselect/blocktank';
+import {
+ openChannelsSelector,
+ pendingChannelsSelector,
+} from '../../../store/reselect/lightning';
import {
isConnectedToElectrumSelector,
isLDKReadySelector,
isOnlineSelector,
} from '../../../store/reselect/ui';
+import { TBackupItem } from '../../../store/types/backup';
+import { EBackupCategories } from '../../../store/utils/backup';
+import { IColors } from '../../../styles/colors';
+import { ScrollView, View as ThemedView } from '../../../styles/components';
import {
- openChannelsSelector,
- pendingChannelsSelector,
-} from '../../../store/reselect/lightning';
-import { blocktankPaidOrdersFullSelector } from '../../../store/reselect/blocktank';
-import { backupSelector } from '../../../store/reselect/backup';
-import { i18nTime } from '../../../utils/i18n';
+ BitcoinSlantedIcon,
+ BroadcastIcon,
+ CloudCheckIcon,
+ GlobeSimpleIcon,
+ LightningHollow,
+} from '../../../styles/icons';
+import { Caption13M, Text01M } from '../../../styles/text';
import { FAILED_BACKUP_CHECK_TIME } from '../../../utils/backup/backups-subscriber';
+import { i18nTime } from '../../../utils/i18n';
+import SettingsView from '../SettingsView';
type TStatusItem =
| 'internet'
@@ -128,19 +131,15 @@ const AppStatus = ({}: SettingsScreenProps<'AppStatus'>): ReactElement => {
}, []);
const isBackupSyncOk = useMemo(() => {
- const isSyncOk = (key: number | undefined): boolean => {
- // undefined = no sync required = ok
- return key ? now - key < FAILED_BACKUP_CHECK_TIME : true;
+ const isSyncOk = (b: TBackupItem): boolean => {
+ return (
+ b.synced > b.required || now - b.required < FAILED_BACKUP_CHECK_TIME
+ );
};
- return (
- isSyncOk(backup.remoteLdkBackupLastSyncRequired) &&
- isSyncOk(backup.remoteLdkActivityBackupSyncRequired) &&
- isSyncOk(backup.remoteBlocktankBackupSyncRequired) &&
- isSyncOk(backup.remoteSettingsBackupSyncRequired) &&
- isSyncOk(backup.remoteMetadataBackupSyncRequired) &&
- isSyncOk(backup.remoteWidgetsBackupSyncRequired)
- );
+ return Object.values(EBackupCategories).every((key) => {
+ return isSyncOk(backup[key]);
+ });
}, [backup, now]);
const fullBackupState: { state: TItemState; subtitle?: string } =
@@ -148,14 +147,9 @@ const AppStatus = ({}: SettingsScreenProps<'AppStatus'>): ReactElement => {
if (!isBackupSyncOk) {
return { state: 'error' };
}
- const syncTimes = [
- backup.remoteLdkBackupLastSync,
- backup.remoteLdkActivityBackupLastSync,
- backup.remoteBlocktankBackupLastSync,
- backup.remoteSettingsBackupLastSync,
- backup.remoteMetadataBackupLastSync,
- backup.remoteWidgetsBackupLastSync,
- ].filter((i) => i !== undefined) as Array;
+ const syncTimes = Object.values(EBackupCategories).map((key) => {
+ return backup[key].synced;
+ });
const max = Math.max(...syncTimes);
let subtitle = tTime('dateTime', {
v: new Date(max),
diff --git a/src/screens/Settings/Backup/Metadata.tsx b/src/screens/Settings/Backup/Metadata.tsx
index 9af554e2f..e32d13935 100644
--- a/src/screens/Settings/Backup/Metadata.tsx
+++ b/src/screens/Settings/Backup/Metadata.tsx
@@ -12,6 +12,7 @@ import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
import { closeSheet } from '../../../store/slices/ui';
import { backupSelector } from '../../../store/reselect/backup';
import { i18nTime } from '../../../utils/i18n';
+import { EBackupCategories } from '../../../store/utils/backup';
const imageSrc = require('../../../assets/illustrations/tag.png');
@@ -21,16 +22,11 @@ const Metadata = (): ReactElement => {
const dispatch = useAppDispatch();
const backup = useAppSelector(backupSelector);
- const arr = [
- backup.remoteLdkBackupLastSync,
- backup.remoteSettingsBackupLastSync,
- backup.remoteWidgetsBackupLastSync,
- backup.remoteMetadataBackupLastSync,
- backup.remoteLdkActivityBackupLastSync,
- backup.remoteBlocktankBackupLastSync,
- ].filter((i) => i !== undefined) as Array;
-
- const max = Math.max(...arr);
+ const max = Math.max(
+ ...Object.values(EBackupCategories).map((key) => {
+ return backup[key].synced;
+ }),
+ );
const handleButtonPress = (): void => {
dispatch(closeSheet('backupNavigation'));
diff --git a/src/screens/Settings/BackupSettings/index.tsx b/src/screens/Settings/BackupSettings/index.tsx
index a6463a6fd..6a9e716d5 100644
--- a/src/screens/Settings/BackupSettings/index.tsx
+++ b/src/screens/Settings/BackupSettings/index.tsx
@@ -1,19 +1,21 @@
-import React, { ReactElement, ReactNode, memo, useMemo, useState } from 'react';
-import { StyleSheet, View } from 'react-native';
+import React, { ReactElement, ReactNode, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View } from 'react-native';
import { EItemType, IListData } from '../../../components/List';
-import SettingsView from '../SettingsView';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux';
-import { showBottomSheet } from '../../../store/utils/ui';
import { SettingsScreenProps } from '../../../navigation/types';
-import { Caption13M, Caption13Up, Text01M } from '../../../styles/text';
+import { backupSelector } from '../../../store/reselect/backup';
+import { lightningBackupSelector } from '../../../store/reselect/lightning';
+import { forceBackup } from '../../../store/slices/backup';
+import { TBackupItem } from '../../../store/types/backup';
+import { EBackupCategories } from '../../../store/utils/backup';
+import { showBottomSheet } from '../../../store/utils/ui';
import {
ScrollView,
- TouchableOpacity,
View as ThemedView,
+ TouchableOpacity,
} from '../../../styles/components';
-import { backupSelector } from '../../../store/reselect/backup';
import {
ArrowClockwise,
LightningHollow,
@@ -21,41 +23,45 @@ import {
RectanglesTwo,
SettingsIcon,
TagIcon,
- TransferIcon,
UsersIcon,
- UserRectangleIcon,
} from '../../../styles/icons';
-import { FAILED_BACKUP_CHECK_TIME } from '../../../utils/backup/backups-subscriber';
-import { updateBackup } from '../../../store/slices/backup';
+import { Caption13M, Caption13Up, Text01M } from '../../../styles/text';
+import { IThemeColors } from '../../../styles/themes';
import { i18nTime } from '../../../utils/i18n';
+import SettingsView from '../SettingsView';
const Status = ({
Icon,
title,
- isSyncedKey,
- lastSync,
- syncRequired,
+ status,
+ category,
+ disableRetry,
}: {
Icon: React.FunctionComponent;
title: ReactNode;
- isSyncedKey?: string;
- lastSync?: number;
- syncRequired?: number;
+ status: TBackupItem;
+ category?: EBackupCategories;
+ disableRetry?: boolean;
}): ReactElement => {
const { t } = useTranslation('settings');
const { t: tTime } = useTranslation('intl', { i18n: i18nTime });
const dispatch = useAppDispatch();
- const [hideRetry, setHideRetry] = useState(false);
- const failed =
- syncRequired &&
- new Date().getTime() - syncRequired > FAILED_BACKUP_CHECK_TIME;
+ let subtitle: string;
+ let iconColor: keyof IThemeColors;
+ let iconBackground: keyof IThemeColors;
+ let showRetry = false;
- let subtitle;
- if (failed) {
- subtitle = t('backup.status_failed', {
+ if (status.running) {
+ iconColor = 'yellow';
+ iconBackground = 'yellow16';
+ subtitle = 'Running';
+ } else if (status.synced >= status.required) {
+ iconColor = 'green';
+ iconBackground = 'green16';
+ subtitle = t('backup.status_success', {
time: tTime('dateTime', {
- v: new Date(syncRequired),
+ v: new Date(status.synced),
formatParams: {
v: {
year: 'numeric',
@@ -67,10 +73,13 @@ const Status = ({
},
}),
});
- } else if (lastSync) {
- subtitle = t('backup.status_success', {
+ } else {
+ iconColor = 'red';
+ iconBackground = 'red16';
+ showRetry = true;
+ subtitle = t('backup.status_failed', {
time: tTime('dateTime', {
- v: new Date(lastSync),
+ v: new Date(status.synced),
formatParams: {
v: {
year: 'numeric',
@@ -82,30 +91,27 @@ const Status = ({
},
}),
});
- } else {
- subtitle = t('backup.status_empty');
}
const retry = (): void => {
- if (isSyncedKey === undefined) {
+ if (!category) {
return;
}
- setHideRetry(true);
- dispatch(updateBackup({ [isSyncedKey]: false }));
+ dispatch(forceBackup({ category }));
};
return (
-
-
+
+
{title}
{subtitle}
- {failed && isSyncedKey && !hideRetry && (
+ {!disableRetry && showRetry && (
@@ -114,70 +120,94 @@ const Status = ({
);
};
+type TBackupCategory = {
+ Icon: React.FunctionComponent;
+ title: string;
+ category?: EBackupCategories;
+ status: TBackupItem;
+ disableRetry?: boolean;
+};
+
const BackupSettings = ({
navigation,
}: SettingsScreenProps<'BackupSettings'>): ReactElement => {
const { t } = useTranslation('settings');
const pin = useAppSelector((state) => state.settings.pin);
const backup = useAppSelector(backupSelector);
+ const lightningBackup = useAppSelector(lightningBackupSelector);
- const categories = [
- {
- Icon: LightningHollow,
- title: t('backup.category_connections'),
- isSyncedKey: 'remoteLdkBackupSynced',
- lastSync: backup.remoteLdkBackupLastSync,
- syncRequired: backup.remoteLdkBackupLastSyncRequired,
- },
+ // find lightning latest backup item to show
+ const lightning = useMemo(() => {
+ const channels = Object.entries(lightningBackup).filter(([key]) =>
+ key.startsWith('channel_'),
+ );
+ if (channels.length === 0) {
+ return;
+ }
+ return channels.reduce((acc, [, value]) => {
+ return value.lastQueued > acc.lastQueued ? value : acc;
+ }, channels[0][1]);
+ }, [lightningBackup]);
+
+ const categories: Array = [
{
Icon: NoteIcon,
title: t('backup.category_connection_receipts'),
- isSyncedKey: 'remoteBlocktankBackupSynced',
- lastSync: backup.remoteBlocktankBackupLastSync,
- syncRequired: backup.remoteBlocktankBackupSyncRequired,
- },
- {
- Icon: TransferIcon,
- title: t('backup.category_transaction_log'),
- isSyncedKey: 'remoteLdkActivityBackupSynced',
- lastSync: backup.remoteLdkActivityBackupLastSync,
- syncRequired: backup.remoteLdkActivityBackupSyncRequired,
+ category: EBackupCategories.blocktank,
+ status: backup[EBackupCategories.blocktank],
},
+ // {
+ // Icon: TransferIcon,
+ // title: t('backup.category_transaction_log'),
+ // isSyncedKey: 'remoteLdkActivityBackupSynced',
+ // lastSync: backup.remoteLdkActivityBackupLastSync,
+ // syncRequired: backup.remoteLdkActivityBackupSyncRequired,
+ // },
{
Icon: SettingsIcon,
title: t('backup.category_settings'),
- isSyncedKey: 'remoteSettingsBackupSynced',
- lastSync: backup.remoteSettingsBackupLastSync,
- syncRequired: backup.remoteSettingsBackupSyncRequired,
+ category: EBackupCategories.settings,
+ status: backup[EBackupCategories.settings],
},
{
Icon: RectanglesTwo,
title: t('backup.category_widgets'),
- isSyncedKey: 'remoteWidgetsBackupSynced',
- lastSync: backup.remoteWidgetsBackupLastSync,
- syncRequired: backup.remoteWidgetsBackupSyncRequired,
+ category: EBackupCategories.widgets,
+ status: backup[EBackupCategories.widgets],
},
{
Icon: TagIcon,
title: t('backup.category_tags'),
- isSyncedKey: 'remoteMetadataBackupSynced',
- lastSync: backup.remoteMetadataBackupLastSync,
- syncRequired: backup.remoteMetadataBackupSyncRequired,
- },
- {
- Icon: UserRectangleIcon,
- title: t('backup.category_profile'),
- lastSync: backup.hyperProfileSeedCheckSuccess,
- syncRequired: backup.hyperProfileCheckRequested,
+ category: EBackupCategories.metadata,
+ status: backup[EBackupCategories.metadata],
},
+ // {
+ // Icon: UserRectangleIcon,
+ // title: t('backup.category_profile'),
+ // lastSync: backup.hyperProfileSeedCheckSuccess,
+ // syncRequired: backup.hyperProfileCheckRequested,
+ // },
{
Icon: UsersIcon,
title: t('backup.category_contacts'),
- lastSync: backup.hyperContactsCheckSuccess,
- syncRequired: backup.hyperContactsCheckRequested,
+ category: EBackupCategories.slashtags,
+ status: backup[EBackupCategories.slashtags],
},
];
+ if (lightning) {
+ categories.unshift({
+ Icon: LightningHollow,
+ title: t('backup.category_connections'),
+ status: {
+ running: false,
+ synced: lightning.lastPersisted ?? 0,
+ required: lightning.lastQueued,
+ },
+ disableRetry: true,
+ });
+ }
+
const settingsListData: IListData[] = useMemo(
() => [
{
diff --git a/src/store/migrations/index.ts b/src/store/migrations/index.ts
index cf0d1b540..7e5c73ed2 100644
--- a/src/store/migrations/index.ts
+++ b/src/store/migrations/index.ts
@@ -1,22 +1,22 @@
// Add migrations for every persisted store version change
+import { EAddressType } from 'beignet';
+import { defaultAddressContent } from 'beignet/src/shapes/wallet';
import { PersistedState } from 'redux-persist';
-import { initialActivityState } from '../slices/activity';
+import { __WEB_RELAY__ } from '../../constants/env';
+import { getDefaultSettings } from '../../screens/Widgets/WidgetEdit';
+import { EAvailableNetwork } from '../../utils/networks';
+import { initialBackupState } from '../shapes/backup';
import { defaultBlocktankInfoShape } from '../shapes/blocktank';
import { initialTodosState } from '../shapes/todos';
import { defaultViewControllers } from '../shapes/ui';
-import { initialChecksState } from '../slices/checks';
-import { initialBackupState } from '../shapes/backup';
-import { initialWidgetsState } from '../slices/widgets';
import {
getDefaultWalletStoreShape,
getNetworkContent,
} from '../shapes/wallet';
-import { getDefaultSettings } from '../../screens/Widgets/WidgetEdit';
-import { __WEB_RELAY__ } from '../../constants/env';
-import { EAvailableNetwork } from '../../utils/networks';
-import { defaultAddressContent } from 'beignet/src/shapes/wallet';
-import { EAddressType } from 'beignet';
+import { initialActivityState } from '../slices/activity';
+import { initialChecksState } from '../slices/checks';
+import { initialWidgetsState } from '../slices/widgets';
const migrations = {
0: (state): PersistedState => {
@@ -432,6 +432,28 @@ const migrations = {
},
};
},
+ 35: (state): PersistedState => {
+ const newNodes = { ...state.lightning.nodes };
+ // Loop through all nodes
+ for (const walletName in newNodes) {
+ newNodes[walletName] = {
+ ...newNodes[walletName],
+ backup: getNetworkContent({}),
+ };
+ }
+
+ return {
+ ...state,
+ backup: {
+ ...initialBackupState,
+ ...state.backup,
+ },
+ lightning: {
+ ...state.lightning,
+ nodes: newNodes,
+ },
+ };
+ },
};
export default migrations;
diff --git a/src/store/reselect/lightning.ts b/src/store/reselect/lightning.ts
index 422eaf2e7..58eaf77a9 100644
--- a/src/store/reselect/lightning.ts
+++ b/src/store/reselect/lightning.ts
@@ -219,3 +219,11 @@ export const lightningBalanceSelector = createSelector(
};
},
);
+
+export const lightningBackupSelector = createSelector(
+ [lightningState, selectedWalletSelector, selectedNetworkSelector],
+ (lightning, selectedWallet, selectedNetwork) => {
+ const node = lightning.nodes[selectedWallet];
+ return node?.backup[selectedNetwork] ?? {};
+ },
+);
diff --git a/src/store/shapes/backup.ts b/src/store/shapes/backup.ts
index 6f7ba41d5..a64e03b46 100644
--- a/src/store/shapes/backup.ts
+++ b/src/store/shapes/backup.ts
@@ -1,36 +1,16 @@
-import { TBackupState } from '../types/backup';
+import { TBackupItem, TBackupState } from '../types/backup';
+import { EBackupCategories } from '../utils/backup';
-export const initialBackupState: TBackupState = {
- remoteBackupsEnabled: false,
- remoteLdkBackupSynced: false,
- remoteLdkBackupLastSyncRequired: undefined,
- remoteSettingsBackupSynced: false,
- remoteSettingsBackupLastSync: undefined,
- remoteSettingsBackupSyncRequired: undefined,
- remoteWidgetsBackupSynced: false,
- remoteWidgetsBackupLastSync: undefined,
- remoteWidgetsBackupSyncRequired: undefined,
- remoteMetadataBackupSynced: false,
- remoteMetadataBackupLastSync: undefined,
- remoteMetadataBackupSyncRequired: undefined,
- remoteLdkActivityBackupSynced: false,
- remoteLdkActivityBackupLastSync: undefined,
- remoteLdkActivityBackupSyncRequired: undefined,
- remoteBlocktankBackupSynced: false,
- remoteBlocktankBackupLastSync: undefined,
- remoteBlocktankBackupSyncRequired: undefined,
- remoteSlashtagsBackupSynced: false,
- remoteSlashtagsBackupLastSync: undefined,
- remoteSlashtagsBackupSyncRequired: undefined,
-
- hyperProfileSeedCheckSuccess: undefined,
- hyperProfileCheckRequested: undefined,
- hyperContactsCheckSuccess: undefined,
- hyperContactsCheckRequested: undefined,
-
- iCloudBackupsEnabled: false,
- iCloudLdkBackupsSynced: false,
+const item: TBackupItem = {
+ required: Date.now() - 1000,
+ synced: Date.now(),
+ running: false,
+};
- gDriveBackupsEnabled: false,
- gDriveLdkBackupsSynced: false,
+export const initialBackupState: TBackupState = {
+ [EBackupCategories.widgets]: { ...item },
+ [EBackupCategories.settings]: { ...item },
+ [EBackupCategories.metadata]: { ...item },
+ [EBackupCategories.blocktank]: { ...item },
+ [EBackupCategories.slashtags]: { ...item },
};
diff --git a/src/store/shapes/lightning.ts b/src/store/shapes/lightning.ts
index bd23617a7..ab9239ad5 100644
--- a/src/store/shapes/lightning.ts
+++ b/src/store/shapes/lightning.ts
@@ -8,6 +8,7 @@ export const defaultLightningShape: TNode = {
openChannelIds: getNetworkContent([]),
peers: getNetworkContent([]),
claimableBalances: getNetworkContent([]),
+ backup: getNetworkContent({}),
};
export const initialLightningState: TLightningState = {
diff --git a/src/store/slices/backup.ts b/src/store/slices/backup.ts
index f4752a509..a9dfda563 100644
--- a/src/store/slices/backup.ts
+++ b/src/store/slices/backup.ts
@@ -1,11 +1,8 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
-import { updateActivityItems } from './activity';
-import { addPaidBlocktankOrder, updateBlocktankOrder } from './blocktank';
-import { updateSettings } from './settings';
-import { addContact, addContacts, deleteContact } from './slashtags';
-import { setFeedWidget } from './widgets';
+
import { initialBackupState } from '../shapes/backup';
-import { TBackupState } from '../types/backup';
+import { EBackupCategories } from '../utils/backup';
+import { addPaidBlocktankOrder, updateBlocktankOrder } from './blocktank';
import {
addLastUsedTag,
addMetaTxSlashtagsUrl,
@@ -18,68 +15,49 @@ import {
updateMetaTxTags,
updatePendingInvoice,
} from './metadata';
-import { EActivityType } from '../types/activity';
+import { updateSettings } from './settings';
+import { addContact, addContacts, deleteContact } from './slashtags';
+import { setFeedWidget } from './widgets';
export const backupSlice = createSlice({
name: 'backup',
initialState: initialBackupState,
reducers: {
- updateBackup: (state, action: PayloadAction>) => {
- state = Object.assign(state, action.payload);
+ resetBackupState: () => initialBackupState,
+ backupStart: (state, action: PayloadAction<{ category: string }>) => {
+ const { category } = action.payload;
+ state[category].running = true;
},
- startBackupSeederCheck: (state) => {
- state.hyperProfileCheckRequested =
- state.hyperProfileCheckRequested ?? new Date().getTime();
- state.hyperContactsCheckRequested =
- state.hyperContactsCheckRequested ?? new Date().getTime();
+ backupSuccess: (state, action: PayloadAction<{ category: string }>) => {
+ const { category } = action.payload;
+ state[category].running = false;
+ state[category].synced = Date.now();
},
- endBackupSeederCheck: (
- state,
- action: PayloadAction<{
- profile: boolean;
- contacts: boolean;
- }>,
- ) => {
- state.hyperProfileCheckRequested = action.payload.profile
- ? undefined
- : state.hyperProfileCheckRequested;
- state.hyperContactsCheckRequested = action.payload.contacts
- ? undefined
- : state.hyperContactsCheckRequested;
- state.hyperProfileSeedCheckSuccess = action.payload.profile
- ? new Date().getTime()
- : state.hyperProfileSeedCheckSuccess;
- state.hyperContactsCheckSuccess = action.payload.contacts
- ? new Date().getTime()
- : state.hyperContactsCheckSuccess;
+ backupError: (state, action: PayloadAction<{ category: string }>) => {
+ const { category } = action.payload;
+ state[category].running = false;
+ },
+ forceBackup: (state, action: PayloadAction<{ category: string }>) => {
+ const { category } = action.payload;
+ state[category].required = Date.now();
+ state[category].running = true;
},
- resetBackupState: () => initialBackupState,
},
extraReducers: (builder) => {
const blocktankReducer = (state): void => {
- state.remoteBlocktankBackupSynced = false;
- state.remoteBlocktankBackupSyncRequired =
- state.remoteBlocktankBackupSyncRequired ?? new Date().getTime();
+ state[EBackupCategories.blocktank].required = Date.now();
};
const metadataReducer = (state): void => {
- state.remoteMetadataBackupSynced = false;
- state.remoteMetadataBackupSyncRequired =
- state.remoteMetadataBackupSyncRequired ?? new Date().getTime();
+ state[EBackupCategories.metadata].required = Date.now();
};
const settingsReducer = (state): void => {
- state.remoteSettingsBackupSynced = false;
- state.remoteSettingsBackupSyncRequired =
- state.remoteSettingsBackupSyncRequired ?? new Date().getTime();
+ state[EBackupCategories.settings].required = Date.now();
};
const slashtagsReducer = (state): void => {
- state.remoteSlashtagsBackupSynced = false;
- state.remoteSlashtagsBackupSyncRequired =
- state.remoteSlashtagsBackupSyncRequired ?? new Date().getTime();
+ state[EBackupCategories.slashtags].required = Date.now();
};
const widgetsReducer = (state): void => {
- state.remoteWidgetsBackupSynced = false;
- state.remoteWidgetsBackupSyncRequired =
- state.remoteWidgetsBackupSyncRequired ?? new Date().getTime();
+ state[EBackupCategories.widgets].required = Date.now();
};
builder
@@ -99,28 +77,29 @@ export const backupSlice = createSlice({
.addCase(addContact, slashtagsReducer)
.addCase(addContacts, slashtagsReducer)
.addCase(deleteContact, slashtagsReducer)
- .addCase(setFeedWidget, widgetsReducer)
- .addCase(updateActivityItems, (state, action) => {
- // we only listen for LN activity here
- const hasLnActivity = action.payload.some(
- (item) => item.activityType === EActivityType.lightning,
- );
- if (hasLnActivity) {
- state.remoteLdkActivityBackupSynced = false;
- state.remoteLdkActivityBackupSyncRequired =
- state.remoteLdkActivityBackupSyncRequired ?? new Date().getTime();
- }
- });
+ .addCase(setFeedWidget, widgetsReducer);
+ // .addCase(updateActivityItems, (state, action) => {
+ // // we only listen for LN activity here
+ // const hasLnActivity = action.payload.some(
+ // (item) => item.activityType === EActivityType.lightning,
+ // );
+ // if (hasLnActivity) {
+ // state.remoteLdkActivityBackupSynced = false;
+ // state.remoteLdkActivityBackupSyncRequired =
+ // state.remoteLdkActivityBackupSyncRequired ?? new Date().getTime();
+ // }
+ // });
},
});
const { actions, reducer } = backupSlice;
export const {
- updateBackup,
- startBackupSeederCheck,
- endBackupSeederCheck,
resetBackupState,
+ backupStart,
+ backupSuccess,
+ backupError,
+ forceBackup,
} = actions;
export default reducer;
diff --git a/src/store/slices/lightning.ts b/src/store/slices/lightning.ts
index 2c234cfb0..c951a0f40 100644
--- a/src/store/slices/lightning.ts
+++ b/src/store/slices/lightning.ts
@@ -1,5 +1,8 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
-import { TClaimableBalance } from '@synonymdev/react-native-ldk';
+import {
+ TBackupStateUpdate,
+ TClaimableBalance,
+} from '@synonymdev/react-native-ldk';
import { initialLightningState } from '../shapes/lightning';
import { EAvailableNetwork } from '../../utils/networks';
@@ -87,6 +90,17 @@ export const lightningSlice = createSlice({
state.nodes[selectedWallet].claimableBalances[selectedNetwork] =
claimableBalances;
},
+ updateBackupState: (
+ state,
+ action: PayloadAction<{
+ backup: TBackupStateUpdate;
+ selectedWallet: TWalletName;
+ selectedNetwork: EAvailableNetwork;
+ }>,
+ ) => {
+ const { backup, selectedWallet, selectedNetwork } = action.payload;
+ state.nodes[selectedWallet].backup[selectedNetwork] = backup;
+ },
updateLdkAccountVersion: (
state,
action: PayloadAction,
@@ -106,6 +120,7 @@ export const {
saveLightningPeer,
removeLightningPeer,
updateClaimableBalances,
+ updateBackupState,
updateLdkAccountVersion,
resetLightningState,
} = actions;
diff --git a/src/store/types/backup.ts b/src/store/types/backup.ts
index a4271a7fe..0dddfb7f3 100644
--- a/src/store/types/backup.ts
+++ b/src/store/types/backup.ts
@@ -1,47 +1,14 @@
import { ENetworks, TAccount } from '@synonymdev/react-native-ldk';
+import { EBackupCategories } from '../utils/backup';
-export type TBackupState = {
- //Backpack
- remoteBackupsEnabled: boolean;
- remoteLdkBackupSynced: boolean;
- remoteLdkBackupLastSync?: number;
- remoteLdkBackupLastSyncRequired?: number;
- remoteSettingsBackupSynced: boolean;
- remoteSettingsBackupLastSync?: number;
- remoteSettingsBackupSyncRequired?: number;
- remoteWidgetsBackupSynced: boolean;
- remoteWidgetsBackupLastSync?: number;
- remoteWidgetsBackupSyncRequired?: number;
- remoteMetadataBackupSynced: boolean;
- remoteMetadataBackupLastSync?: number;
- remoteMetadataBackupSyncRequired?: number;
- remoteLdkActivityBackupSynced: boolean;
- remoteLdkActivityBackupLastSync?: number;
- remoteLdkActivityBackupSyncRequired?: number;
- remoteBlocktankBackupSynced: boolean;
- remoteBlocktankBackupLastSync?: number;
- remoteBlocktankBackupSyncRequired?: number;
- remoteSlashtagsBackupSynced: boolean;
- remoteSlashtagsBackupLastSync?: number;
- remoteSlashtagsBackupSyncRequired?: number;
-
- //Hyperdrives
- hyperProfileSeedCheckSuccess?: number;
- hyperProfileCheckRequested?: number;
- hyperContactsCheckSuccess?: number;
- hyperContactsCheckRequested?: number;
-
- //iCloud
- iCloudBackupsEnabled: boolean;
- iCloudLdkBackupsSynced: boolean;
- iCloudLdkBackupLastSync?: number;
- //TODO transactions, slashtags, metadata, etc.
+export type TBackupItem = {
+ running: boolean;
+ required: number; // timestamp of last time this backup was required
+ synced: number; // timestamp of last time this backup was synced
+};
- //Google Drive
- gDriveBackupsEnabled: boolean;
- gDriveLdkBackupsSynced: boolean;
- gDriveLdkBackupLastSync?: number;
- //TODO transactions, slashtags, metadata, etc.
+export type TBackupState = {
+ [key in EBackupCategories]: TBackupItem;
};
export declare type TAccountBackup = {
@@ -50,3 +17,9 @@ export declare type TAccountBackup = {
network: ENetworks;
data: T;
};
+
+export type TBackupMetadata = {
+ category: EBackupCategories;
+ timestamp: number;
+ version: number;
+};
diff --git a/src/store/types/lightning.ts b/src/store/types/lightning.ts
index 989211611..e27a0d0f2 100644
--- a/src/store/types/lightning.ts
+++ b/src/store/types/lightning.ts
@@ -1,4 +1,5 @@
import {
+ TBackupStateUpdate,
TChannel,
TClaimableBalance,
TCreatePaymentReq,
@@ -27,6 +28,7 @@ export type TNode = {
info: IWalletItem<{}>;
peers: IWalletItem;
claimableBalances: IWalletItem;
+ backup: IWalletItem;
};
export type TNodes = {
diff --git a/src/store/utils/backup.ts b/src/store/utils/backup.ts
index 55709c50a..c9a667205 100644
--- a/src/store/utils/backup.ts
+++ b/src/store/utils/backup.ts
@@ -1,188 +1,65 @@
+import lm, {
+ ENetworks,
+ ldk,
+ TBackupServerDetails,
+ TLdkData,
+} from '@synonymdev/react-native-ldk';
import { err, ok, Result } from '@synonymdev/result';
-import lm, { ldk, ENetworks, TLdkData } from '@synonymdev/react-native-ldk';
-import { getBackupStore, dispatch } from '../helpers';
import {
- EBackupCategories,
+ __BACKUPS_SERVER_HOST__,
+ __BACKUPS_SERVER_PUBKEY__,
+} from '../../constants/env';
+import { Slashtag } from '../../hooks/slashtags';
+import {
+ EBackupCategoriesOld,
fetchBackup,
listBackups,
- uploadBackup,
} from '../../utils/backup/backpack';
-import { bytesToString, stringToBytes } from '../../utils/converters';
-import { Slashtag } from '../../hooks/slashtags';
+import { bytesToString } from '../../utils/converters';
+import { isObjPartialMatch } from '../../utils/helpers';
import {
checkAccountVersion,
- exportBackup,
getLdkAccount,
setAccount,
setLdkStoragePath,
} from '../../utils/lightning';
import { EAvailableNetwork } from '../../utils/networks';
import { getSelectedNetwork } from '../../utils/wallet';
-import { TBackupState, TAccountBackup } from '../types/backup';
-import { isObjPartialMatch } from '../../utils/helpers';
-import { getDefaultSettingsShape } from '../shapes/settings';
-import { addActivityItems, TActivity } from '../slices/activity';
-import { initialMetadataState, updateMetadata } from '../slices/metadata';
-import { updateSettings, TSettings } from '../slices/settings';
import {
- updateWidgets,
- initialWidgetsState,
- TWidgetsState,
-} from '../slices/widgets';
+ dispatch,
+ getBlocktankStore,
+ getMetaDataStore,
+ getSettingsStore,
+ getSlashtagsStore,
+ getStore,
+ getWidgetsStore,
+} from '../helpers';
+import { getDefaultSettingsShape } from '../shapes/settings';
+import { backupError, backupStart, backupSuccess } from '../slices/backup';
import { updateBlocktank } from '../slices/blocktank';
+import { initialMetadataState, updateMetadata } from '../slices/metadata';
+import { TSettings, updateSettings } from '../slices/settings';
import { addContacts } from '../slices/slashtags';
-import { EActivityType } from '../types/activity';
+import { updateWidgets } from '../slices/widgets';
+import { TAccountBackup, TBackupMetadata } from '../types/backup';
import { IBlocktank } from '../types/blocktank';
import { TMetadataState } from '../types/metadata';
-import { checkBackup } from '../../utils/slashtags';
-import { showToast } from '../../utils/notifications';
-import { FAILED_BACKUP_CHECK_TIME } from '../../utils/backup/backups-subscriber';
-import i18n from '../../utils/i18n';
import { TSlashtagsState } from '../types/slashtags';
-import {
- __BACKUPS_SERVER_HOST__,
- __BACKUPS_SERVER_PUBKEY__,
-} from '../../constants/env';
-import {
- endBackupSeederCheck,
- startBackupSeederCheck,
- updateBackup,
-} from '../slices/backup';
-
-/**
- * Triggers a full remote backup
- * @return {Promise>}
- */
-export const performFullBackup = async (
- slashtag: Slashtag,
-): Promise> => {
- const ldkRemoteRes = await performRemoteLdkBackup(slashtag);
- //TODO perform other backup types
-
- //TODO(slashtags): Send all drives (public + contacts) to the seeding server.
- //TODO check results of each time and return errors if any
-
- if (ldkRemoteRes.isErr()) {
- return err(ldkRemoteRes.error);
- }
-
- return ok('Backup success');
-};
-
-export const performRemoteLdkBackup = async (
- slashtag: Slashtag,
- backup?: TAccountBackup,
-): Promise> => {
- dispatch(updateBackup({ remoteLdkBackupSynced: false }));
-
- let ldkBackup: TAccountBackup;
- //Automated backup events pass the latest state through
- if (backup) {
- ldkBackup = backup;
- } else {
- const res = await exportBackup();
- if (res.isErr()) {
- return err(res.error);
- }
-
- ldkBackup = res.value;
- }
-
- //Translate LDK type to our wallet type
- let network = EAvailableNetwork.bitcoin;
- switch (ldkBackup.network) {
- case ENetworks.regtest: {
- network = EAvailableNetwork.bitcoinRegtest;
- break;
- }
- case ENetworks.testnet: {
- network = EAvailableNetwork.bitcoinTestnet;
- break;
- }
- case ENetworks.mainnet: {
- network = EAvailableNetwork.bitcoin;
- break;
- }
- }
-
- const res = await uploadBackup(
- slashtag,
- stringToBytes(JSON.stringify(backup)),
- EBackupCategories.ldkComplete,
- network,
- );
-
- if (res.isErr()) {
- return err(res.error);
- }
-
- dispatch(
- updateBackup({
- remoteLdkBackupSynced: true,
- remoteLdkBackupLastSync: new Date().getTime(),
- remoteLdkBackupLastSyncRequired: undefined,
- }),
- );
-
- return ok('Backup success');
-};
-
-export const performRemoteBackup = async ({
- slashtag,
- isSyncedKey,
- syncRequiredKey,
- syncCompletedKey,
- backupCategory,
- backup,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- isSyncedKey: keyof TBackupState;
- syncRequiredKey: keyof TBackupState;
- syncCompletedKey: keyof TBackupState;
- backupCategory: EBackupCategories;
- backup?: T;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- //Automated backup events pass the latest state through
- if (!backup) {
- return ok('Nothing to backup.');
- }
-
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
-
- const backupJson = JSON.stringify(backup);
- const bytes = stringToBytes(backupJson);
-
- const res = await uploadBackup(
- slashtag,
- bytes,
- backupCategory,
- selectedNetwork,
- );
-
- if (res.isErr()) {
- return err(res.error);
- }
-
- dispatch(
- updateBackup({
- [isSyncedKey]: true,
- [syncRequiredKey]: undefined,
- [syncCompletedKey]: new Date().getTime(),
- }),
- );
-
- return ok('Backup success');
-};
+export enum EBackupCategories {
+ settings = 'bitkit_settings',
+ widgets = 'bitkit_widgets',
+ metadata = 'bitkit_metadata',
+ blocktank = 'bitkit_blocktank_orders',
+ slashtags = 'bitkit_slashtags_contacts',
+}
export const performLdkRestore = async ({
+ backupServerDetails,
selectedNetwork,
}: {
+ backupServerDetails: TBackupServerDetails;
selectedNetwork?: EAvailableNetwork;
}): Promise> => {
if (!selectedNetwork) {
@@ -213,10 +90,6 @@ export const performLdkRestore = async ({
break;
}
- const backupServerDetails = {
- host: __BACKUPS_SERVER_HOST__,
- serverPubKey: __BACKUPS_SERVER_PUBKEY__,
- };
const backupSetupRes = await ldk.backupSetup({
seed: lightningAccount.value.seed,
network,
@@ -269,7 +142,7 @@ export const performLdkRestoreDeprecated = async ({
}
const res = await listBackups(
slashtag,
- EBackupCategories.ldkComplete,
+ EBackupCategoriesOld.ldkComplete,
selectedNetwork,
);
if (res.isErr()) {
@@ -284,7 +157,7 @@ export const performLdkRestoreDeprecated = async ({
const fetchRes = await fetchBackup(
slashtag,
res.value[0].timestamp,
- EBackupCategories.ldkComplete,
+ EBackupCategoriesOld.ldkComplete,
selectedNetwork,
);
if (fetchRes.isErr()) {
@@ -312,419 +185,313 @@ export const performLdkRestoreDeprecated = async ({
return ok({ backupExists: true });
};
-/**
- * Retrieves the backup data for the provided backupCategory.
- * @param {Slashtag} slashtag
- * @param {EBackupCategories} backupCategory
- * @param {EAvailableNetwork} [selectedNetwork]
- * @returns {Promise>}
- */
-export const getBackup = async ({
- slashtag,
- backupCategory,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- backupCategory: EBackupCategories;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
- const res = await listBackups(slashtag, backupCategory, selectedNetwork);
- if (res.isErr()) {
- return err(res.error);
- }
-
- // No backup exists for the provided slashtag.
- if (res.value.length === 0) {
- return ok(undefined);
- }
-
- const fetchRes = await fetchBackup(
- slashtag,
- res.value[0].timestamp,
- backupCategory,
- selectedNetwork,
- );
- if (fetchRes.isErr()) {
- return err(fetchRes.error);
- }
-
- let jsonString = bytesToString(fetchRes.value.content);
+export const performFullRestoreFromLatestBackup = async (
+ slashtag: Slashtag,
+): Promise> => {
+ try {
+ const backupServerDetails = {
+ host: __BACKUPS_SERVER_HOST__,
+ serverPubKey: __BACKUPS_SERVER_PUBKEY__,
+ };
- if (
- backupCategory === EBackupCategories.ldkActivity ||
- backupCategory === EBackupCategories.metadata
- ) {
- // Remove previously incorrectly encoded emojis from the backup
- // eslint-disable-next-line no-control-regex
- jsonString = jsonString.replace(/([\u0000-\u001F])/g, '');
- }
+ // ldk restore should be performed for all networks
+ for (const network of Object.values(EAvailableNetwork)) {
+ const ldkBackupRes = await performLdkRestore({
+ backupServerDetails,
+ selectedNetwork: network,
+ });
+ if (ldkBackupRes.isErr()) {
+ return err(ldkBackupRes.error.message);
+ }
- const backup: T = JSON.parse(jsonString);
+ //No backup found on new server, try deprecated backup server
+ if (!ldkBackupRes.value.backupExists) {
+ const ldkBackupDeprecatedRes = await performLdkRestoreDeprecated({
+ slashtag,
+ selectedNetwork: network,
+ });
- // Restore success
- return ok(backup);
-};
+ if (ldkBackupDeprecatedRes.isErr()) {
+ return err(ldkBackupDeprecatedRes.error.message);
+ }
+ }
+ }
-export const performSettingsRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
+ // reset backup settings once again before restoring all other backups
+ const selectedNetwork = getSelectedNetwork();
+ let network: ENetworks;
+ switch (selectedNetwork) {
+ case 'bitcoin':
+ network = ENetworks.mainnet;
+ break;
+ case 'bitcoinTestnet':
+ network = ENetworks.testnet;
+ break;
+ default:
+ network = ENetworks.regtest;
+ break;
+ }
+ const version = await checkAccountVersion();
+ const lightningAccount = await getLdkAccount({ selectedNetwork, version });
+ if (lightningAccount.isErr()) {
+ return err(lightningAccount.error);
+ }
+ const backupSetupRes = await ldk.backupSetup({
+ seed: lightningAccount.value.seed,
+ network,
+ details: backupServerDetails,
+ });
- const backupRes = await getBackup({
- slashtag,
- backupCategory: EBackupCategories.settings,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
- }
+ if (backupSetupRes.isErr()) {
+ return err(backupSetupRes.error);
+ }
- const backup = backupRes.value;
- if (!backup) {
- return ok({ backupExists: false });
- }
+ const backups = [
+ ['settings', performSettingsRestore],
+ ['widgets', performWidgetsRestore],
+ ['metadata', performMetadataRestore],
+ ['blocktank', performBlocktankRestore],
+ ['slashtags', performSlashtagsRestore],
+ ] as const;
+
+ for (const [name, func] of backups) {
+ const res = await func();
+ if (res.isErr()) {
+ // Since this backup feature is not critical and mostly for user convenience
+ // there's no reason to throw an error here.
+ console.log(`Error restoring ${name}`, res.error.message);
+ }
+ }
- const expectedBackupShape = getDefaultSettingsShape();
- //If the keys in the backup object are not found in the reference object assume the backup does not exist.
- if (!isObjPartialMatch(backup, expectedBackupShape)) {
- return ok({ backupExists: false });
+ // Restore success
+ return ok({ backupExists: true });
+ } catch (e) {
+ console.log(e);
+ return err(e);
}
-
- dispatch(
- updateSettings({
- ...expectedBackupShape,
- ...backup,
- biometrics: false,
- pin: false,
- pinForPayments: false,
- pinOnLaunch: true,
- }),
- );
- dispatch(updateBackup({ remoteSettingsBackupSynced: true }));
-
- // Restore success
- return ok({ backupExists: true });
};
-export const performWidgetsRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
- const backupRes = await getBackup({
- slashtag,
- backupCategory: EBackupCategories.widgets,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
- }
-
- const backup = backupRes.value;
- if (!backup) {
- return ok({ backupExists: false });
- }
-
- const expectedBackupShape = initialWidgetsState;
- //If the keys in the backup object are not found in the reference object assume the backup does not exist.
- if (!isObjPartialMatch(backup, expectedBackupShape, ['widgets'])) {
- return ok({ backupExists: false });
- }
-
- dispatch(
- updateWidgets({
- ...expectedBackupShape,
- ...backup,
- onboardedWidgets: true,
- }),
- );
- dispatch(updateBackup({ remoteWidgetsBackupSynced: true }));
+export const performBackup = async (
+ category: EBackupCategories,
+): Promise> => {
+ try {
+ let data: {};
+ switch (category) {
+ case EBackupCategories.settings:
+ data = getSettingsStore();
+ break;
+ case EBackupCategories.widgets:
+ data = getWidgetsStore();
+ break;
+ case EBackupCategories.metadata:
+ data = getMetaDataStore();
+ break;
+ case EBackupCategories.blocktank:
+ const { paidOrders, orders } = getBlocktankStore();
+ data = { paidOrders, orders };
+ break;
+ case EBackupCategories.slashtags:
+ const { contacts } = getSlashtagsStore();
+ data = { contacts };
+ break;
+ }
- // Restore success
- return ok({ backupExists: true });
-};
+ const metadata: TBackupMetadata = {
+ category,
+ timestamp: Date.now(),
+ version: getStore()._persist.version,
+ };
-export const performMetadataRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
+ const content = JSON.stringify({ data, metadata });
- const backupRes = await getBackup({
- slashtag,
- backupCategory: EBackupCategories.metadata,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
+ dispatch(backupStart({ category }));
+ const backupRes = await ldk.backupFile(category, content);
+ if (backupRes.isErr()) {
+ throw backupRes.error;
+ }
+ dispatch(backupSuccess({ category }));
+ return ok(`Backup ${category} success`);
+ } catch (e) {
+ console.log(`Backup ${category} error`, e.message);
+ dispatch(backupError({ category }));
+ return err(e);
}
+};
- const backup = backupRes.value;
-
- if (!backup) {
- return ok({ backupExists: false });
- }
+/**
+ * Retrieves the backup data for the provided backupCategory.
+ * @param {EBackupCategories} category
+ * @returns {Promise>}
+ */
+const getBackup = async (
+ category: EBackupCategories,
+): Promise> => {
+ try {
+ const fetchRes = await ldk.fetchBackupFile(category);
+ if (fetchRes.isErr()) {
+ return err(fetchRes.error);
+ }
- const expectedBackupShape = initialMetadataState;
- //If the keys in the backup object are not found in the reference object assume the backup does not exist.
- if (
- !isObjPartialMatch(backup, expectedBackupShape, ['tags', 'slashTagsUrls'])
- ) {
- return ok({ backupExists: false });
+ const content = JSON.parse(fetchRes.value);
+ return ok(content);
+ } catch (e) {
+ console.log(`GetBackup ${category} error`, e.message);
+ return err(e);
}
-
- dispatch(updateMetadata({ ...expectedBackupShape, ...backup }));
- dispatch(updateBackup({ remoteMetadataBackupSynced: true }));
-
- // Restore success
- return ok({ backupExists: true });
};
-export const performLdkActivityRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
-
- const backupRes = await getBackup({
- slashtag,
- backupCategory: EBackupCategories.ldkActivity,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
- }
+const performSettingsRestore = async (): Promise<
+ Result<{ backupExists: boolean }>
+> => {
+ try {
+ const backupRes = await getBackup(EBackupCategories.settings);
+ if (backupRes.isErr()) {
+ return err(backupRes.error.message);
+ }
- const backup = backupRes.value;
+ const backup = backupRes.value.data;
+ const expectedBackupShape = getDefaultSettingsShape();
+ //If the keys in the backup object are not found in the reference object assume the backup does not exist.
+ if (!isObjPartialMatch(backup, expectedBackupShape)) {
+ return ok({ backupExists: false });
+ }
- if (!backup) {
- return ok({ backupExists: false });
- }
+ dispatch(
+ updateSettings({
+ ...expectedBackupShape,
+ ...backup,
+ biometrics: false,
+ pin: false,
+ pinForPayments: false,
+ pinOnLaunch: true,
+ }),
+ );
+ dispatch(backupSuccess({ category: EBackupCategories.settings }));
- if (
- !(
- Array.isArray(backup) &&
- backup.every((i) => i.activityType === EActivityType.lightning)
- )
- ) {
- return ok({ backupExists: false });
+ // Restore success
+ return ok({ backupExists: true });
+ } catch (e) {
+ console.log(`Restore ${EBackupCategories.settings} error`, e.message);
+ return err(e);
}
-
- dispatch(addActivityItems(backup));
- dispatch(updateBackup({ remoteLdkActivityBackupSynced: true }));
-
- // Restore success
- return ok({ backupExists: true });
};
-export const performBlocktankRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
-
- const backupRes = await getBackup>({
- slashtag,
- backupCategory: EBackupCategories.blocktank,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
- }
+const performWidgetsRestore = async (): Promise<
+ Result<{ backupExists: boolean }>
+> => {
+ try {
+ const backupRes = await getBackup(EBackupCategories.widgets);
+ if (backupRes.isErr()) {
+ return err(backupRes.error.message);
+ }
- const backup = backupRes.value;
+ const backup = backupRes.value.data;
+ const expectedBackupShape = getDefaultSettingsShape();
+ //If the keys in the backup object are not found in the reference object assume the backup does not exist.
+ if (!isObjPartialMatch(backup, expectedBackupShape, ['widgets'])) {
+ return ok({ backupExists: false });
+ }
- if (!backup) {
- return ok({ backupExists: false });
- }
+ dispatch(
+ updateWidgets({
+ ...expectedBackupShape,
+ ...backup,
+ onboardedWidgets: true,
+ }),
+ );
+ dispatch(backupSuccess({ category: EBackupCategories.widgets }));
- if (!('orders' in backup && 'paidOrders' in backup)) {
- return ok({ backupExists: false });
+ // Restore success
+ return ok({ backupExists: true });
+ } catch (e) {
+ console.log(`Restore ${EBackupCategories.settings} error`, e.message);
+ return err(e);
}
-
- dispatch(updateBlocktank(backup));
- dispatch(updateBackup({ remoteBlocktankBackupSynced: true }));
-
- // Restore success
- return ok({ backupExists: true });
};
-export const performSlashtagsRestore = async ({
- slashtag,
- selectedNetwork,
-}: {
- slashtag: Slashtag;
- selectedNetwork?: EAvailableNetwork;
-}): Promise> => {
- if (!selectedNetwork) {
- selectedNetwork = getSelectedNetwork();
- }
-
- const backupRes = await getBackup>({
- slashtag,
- backupCategory: EBackupCategories.slashtags,
- selectedNetwork,
- });
- if (backupRes.isErr()) {
- return err(backupRes.error.message);
- }
+const performMetadataRestore = async (): Promise<
+ Result<{ backupExists: boolean }>
+> => {
+ try {
+ const backupRes = await getBackup(
+ EBackupCategories.metadata,
+ );
+ if (backupRes.isErr()) {
+ return err(backupRes.error.message);
+ }
- const backup = backupRes.value;
+ const backup = backupRes.value.data;
+ const expectedBackupShape = initialMetadataState;
+ //If the keys in the backup object are not found in the reference object assume the backup does not exist.
+ if (
+ !isObjPartialMatch(backup, expectedBackupShape, ['tags', 'slashTagsUrls'])
+ ) {
+ return ok({ backupExists: false });
+ }
- if (!backup) {
- return ok({ backupExists: false });
- }
+ dispatch(updateMetadata({ ...expectedBackupShape, ...backup }));
+ dispatch(backupSuccess({ category: EBackupCategories.metadata }));
- if (!('contacts' in backup)) {
- return ok({ backupExists: false });
+ // Restore success
+ return ok({ backupExists: true });
+ } catch (e) {
+ console.log(`Restore ${EBackupCategories.settings} error`, e.message);
+ return err(e);
}
-
- dispatch(addContacts(backup.contacts!));
- dispatch(updateBackup({ remoteSlashtagsBackupSynced: true }));
-
- // Restore success
- return ok({ backupExists: true });
};
-export const performFullRestoreFromLatestBackup = async (
- slashtag: Slashtag,
-): Promise> => {
+const performBlocktankRestore = async (): Promise<
+ Result<{ backupExists: boolean }>
+> => {
try {
- // ldk restore should be performed for all networks
- for (const network of Object.values(EAvailableNetwork)) {
- const ldkBackupRes = await performLdkRestore({
- selectedNetwork: network,
- });
- if (ldkBackupRes.isErr()) {
- return err(ldkBackupRes.error.message);
- }
-
- //No backup found on new server, try deprecated backup server
- if (!ldkBackupRes.value.backupExists) {
- const ldkBackupDeprecatedRes = await performLdkRestoreDeprecated({
- slashtag,
- selectedNetwork: network,
- });
-
- if (ldkBackupDeprecatedRes.isErr()) {
- return err(ldkBackupDeprecatedRes.error.message);
- }
- }
+ const backupRes = await getBackup>(
+ EBackupCategories.blocktank,
+ );
+ if (backupRes.isErr()) {
+ return err(backupRes.error.message);
}
- const selectedNetwork = getSelectedNetwork();
-
- const settingsBackupRes = await performSettingsRestore({
- slashtag,
- selectedNetwork,
- });
- if (settingsBackupRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up settings', settingsBackupRes.error.message);
+ const backup = backupRes.value.data;
+ //If the keys in the backup object are not found in the reference object assume the backup does not exist.
+ if (!('orders' in backup && 'paidOrders' in backup)) {
+ return ok({ backupExists: false });
}
- const widgetsBackupRes = await performWidgetsRestore({
- slashtag,
- selectedNetwork,
- });
- if (widgetsBackupRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up widgets', widgetsBackupRes.error.message);
- }
+ dispatch(updateBlocktank(backup));
+ dispatch(backupSuccess({ category: EBackupCategories.blocktank }));
- const metadataBackupRes = await performMetadataRestore({
- slashtag,
- selectedNetwork,
- });
- if (metadataBackupRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up metadata', metadataBackupRes.error.message);
- }
+ // Restore success
+ return ok({ backupExists: true });
+ } catch (e) {
+ console.log(`Restore ${EBackupCategories.settings} error`, e.message);
+ return err(e);
+ }
+};
- const ldkActivityRes = await performLdkActivityRestore({
- slashtag,
- selectedNetwork,
- });
- if (ldkActivityRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up ldkActivity', ldkActivityRes.error.message);
+const performSlashtagsRestore = async (): Promise<
+ Result<{ backupExists: boolean }>
+> => {
+ try {
+ const backupRes = await getBackup>(
+ EBackupCategories.slashtags,
+ );
+ if (backupRes.isErr()) {
+ return err(backupRes.error.message);
}
- const btBackupRes = await performBlocktankRestore({
- slashtag,
- selectedNetwork,
- });
- if (btBackupRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up blocktank', btBackupRes.error.message);
+ const backup = backupRes.value.data;
+ //If the keys in the backup object are not found in the reference object assume the backup does not exist.
+ if (!('contacts' in backup)) {
+ return ok({ backupExists: false });
}
- const slashBackupRes = await performSlashtagsRestore({
- slashtag,
- selectedNetwork,
- });
- if (slashBackupRes.isErr()) {
- //Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
- console.log('Error backing up contacts', slashBackupRes.error.message);
- }
+ dispatch(addContacts(backup.contacts!));
+ dispatch(backupSuccess({ category: EBackupCategories.slashtags }));
// Restore success
return ok({ backupExists: true });
} catch (e) {
- console.log(e);
+ console.log(`Restore ${EBackupCategories.settings} error`, e.message);
return err(e);
}
};
-
-export const checkProfileAndContactsBackup = async (
- slashtag: Slashtag,
-): Promise => {
- dispatch(startBackupSeederCheck());
- const payload = await checkBackup(slashtag);
- dispatch(endBackupSeederCheck(payload));
-
- // now check if backup is too old and show warning if it is
- const now = new Date().getTime();
- const backup = getBackupStore();
- if (
- (backup.hyperProfileCheckRequested &&
- now - backup.hyperProfileCheckRequested > FAILED_BACKUP_CHECK_TIME) ||
- (backup.hyperContactsCheckRequested &&
- now - backup.hyperContactsCheckRequested > FAILED_BACKUP_CHECK_TIME)
- ) {
- showToast({
- type: 'error',
- title: i18n.t('settings:backup.failed_title'),
- description: i18n.t('settings:backup.failed_message'),
- });
- }
-};
diff --git a/src/styles/icons.ts b/src/styles/icons.ts
index 4d525fc7d..df38cf5ec 100644
--- a/src/styles/icons.ts
+++ b/src/styles/icons.ts
@@ -27,7 +27,6 @@ import {
clipboardTextIcon,
usersIcon,
userIcon,
- userRectangleIcon,
speedFastIcon,
speedNormalIcon,
speedSlowIcon,
@@ -282,16 +281,6 @@ export const UserIcon = styled(SvgXml).attrs((props) => ({
color: props.color ? props.theme.colors[props.color] : 'white',
}));
-export const UserRectangleIcon = styled(SvgXml).attrs((props) => ({
- xml: userRectangleIcon(
- props.color ? props.theme.colors[props.color] : 'white',
- ),
- height: props.height ?? '32px',
- width: props.width ?? '32px',
-}))((props) => ({
- color: props.color ? props.theme.colors[props.color] : 'white',
-}));
-
export const SpeedFastIcon = styled(SvgXml).attrs((props) => ({
xml: speedFastIcon(props.color ? props.theme.colors[props.color] : 'white'),
height: props.height ?? '32px',
diff --git a/src/utils/backup/backpack.ts b/src/utils/backup/backpack.ts
index 9524184d1..b3884b6de 100644
--- a/src/utils/backup/backpack.ts
+++ b/src/utils/backup/backpack.ts
@@ -2,7 +2,6 @@ import BackupProtocol from 'backpack-client/src/backup-protocol.js';
import { ok, err, Result } from '@synonymdev/result';
import { Slashtag } from '@synonymdev/slashtags-sdk';
-import { name as appName, version as appVersion } from '../../../package.json';
import { EAvailableNetwork } from '../networks';
import {
__BACKUPS_SERVER_SLASHTAG__,
@@ -10,11 +9,11 @@ import {
} from '../../constants/env';
const categoryWithNetwork = (
- category: EBackupCategories,
+ category: EBackupCategoriesOld,
network: EAvailableNetwork,
): string => `${category}.${network}`.toLowerCase();
-export enum EBackupCategories {
+export enum EBackupCategoriesOld {
jest = 'bitkit.jest',
transactions = 'bitkit.transactions',
ldkComplete = 'bitkit.ldk.complete',
@@ -47,50 +46,6 @@ const backupsFactory = async (slashtag: Slashtag): Promise => {
return backupsInstances[key];
};
-/**
- * Uploads a backup to the server
- * @param {Slashtag} slashtag
- * @param {Uint8Array} content
- * @param {EBackupCategories} category
- * @param {EAvailableNetwork} network
- * @returns {Promise>}
- */
-export const uploadBackup = async (
- slashtag: Slashtag,
- content: Uint8Array,
- category: EBackupCategories,
- network: EAvailableNetwork,
-): Promise> => {
- try {
- const backups = await backupsFactory(slashtag);
-
- const encryptedContent = backups.encrypt(content, slashtag.key);
-
- // Prepare some data to back up
- const data = {
- appName,
- appVersion,
- category: categoryWithNetwork(category, network),
- content: encryptedContent,
- };
-
- const { error, results, success } = await backups.backupData(
- __BACKUPS_SERVER_SLASHTAG__,
- data,
- );
-
- if (!success) {
- return err(error);
- }
-
- const { timestamp } = results;
-
- return ok(timestamp);
- } catch (e) {
- return err(e);
- }
-};
-
type TFetchResult = {
appName: string;
appVersion: string;
@@ -103,14 +58,14 @@ type TFetchResult = {
* Fetches a backup from the server
* @param {Slashtag} slashtag
* @param {number} timestamp
- * @param {EBackupCategories} category
+ * @param {EBackupCategoriesOld} category
* @param {EAvailableNetwork} network
* @returns {Promise>}
*/
export const fetchBackup = async (
slashtag: Slashtag,
timestamp: number,
- category: EBackupCategories,
+ category: EBackupCategoriesOld,
network: EAvailableNetwork,
): Promise> => {
try {
@@ -139,13 +94,13 @@ export const fetchBackup = async (
/**
* Returns list of backups in order of newest to oldest
* @param {Slashtag} slashtag
- * @param {EBackupCategories} category
+ * @param {EBackupCategoriesOld} category
* @param {EAvailableNetwork} network
* @returns {Promise>}
*/
export const listBackups = async (
slashtag: Slashtag,
- category: EBackupCategories,
+ category: EBackupCategoriesOld,
network: EAvailableNetwork,
): Promise> => {
try {
diff --git a/src/utils/backup/backups-subscriber.tsx b/src/utils/backup/backups-subscriber.tsx
index e13c5a18d..e14e9c03c 100644
--- a/src/utils/backup/backups-subscriber.tsx
+++ b/src/utils/backup/backups-subscriber.tsx
@@ -1,28 +1,12 @@
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
-import lm from '@synonymdev/react-native-ldk';
-import { useAppSelector } from '../../hooks/redux';
import { useTranslation } from 'react-i18next';
-import {
- checkProfileAndContactsBackup,
- performRemoteBackup,
- performRemoteLdkBackup,
-} from '../../store/utils/backup';
-import { __DISABLE_SLASHTAGS__ } from '../../constants/env';
-import { useSelectedSlashtag } from '../../hooks/slashtags';
-import { backupSelector } from '../../store/reselect/backup';
-import { selectedNetworkSelector } from '../../store/reselect/wallet';
-import { EBackupCategories } from './backpack';
+import { __DISABLE_SLASHTAGS__, __E2E__ } from '../../constants/env';
import { useDebouncedEffect } from '../../hooks/helpers';
-import { settingsSelector } from '../../store/reselect/settings';
-import { metadataState } from '../../store/reselect/metadata';
-import { widgetsState } from '../../store/reselect/widgets';
-import { activityItemsState } from '../../store/reselect/activity';
-import { EActivityType } from '../../store/types/activity';
-import { blocktankSelector } from '../../store/reselect/blocktank';
-import { slashtagsSelector } from '../../store/reselect/slashtags';
+import { useAppSelector } from '../../hooks/redux';
+import { backupSelector } from '../../store/reselect/backup';
+import { EBackupCategories, performBackup } from '../../store/utils/backup';
import { showToast } from '../notifications';
-import { __E2E__ } from '../../constants/env';
const BACKUP_DEBOUNCE = 5000; // 5 seconds
const BACKUP_CHECK_INTERVAL = 60 * 1000; // 1 minute
@@ -31,174 +15,63 @@ const FAILED_BACKUP_NOTIFICATION_INTERVAL = 10 * 60 * 1000; // 10 minutes
const EnabledSlashtag = (): ReactElement => {
const { t } = useTranslation('settings');
- const selectedNetwork = useAppSelector(selectedNetworkSelector);
- const { slashtag } = useSelectedSlashtag();
const backup = useAppSelector(backupSelector);
- const settings = useAppSelector(settingsSelector);
- const metadata = useAppSelector(metadataState);
- const widgets = useAppSelector(widgetsState);
- const activity = useAppSelector(activityItemsState);
- const blocktank = useAppSelector(blocktankSelector);
- const slashtags = useAppSelector(slashtagsSelector);
const [now, setNow] = useState(new Date().getTime());
- useEffect(() => {
- const sub = lm.subscribeToBackups((res) => {
- performRemoteLdkBackup(
- slashtag,
- res.isOk() ? res.value : undefined,
- ).catch((e) => {
- console.error('LDK backup error', e);
- });
- });
-
- return () => lm.unsubscribeFromBackups(sub);
- }, [slashtag]);
+ const backupSettings = backup[EBackupCategories.settings];
+ const backupWidgets = backup[EBackupCategories.widgets];
+ const backupMetadata = backup[EBackupCategories.metadata];
+ const backupBlocktank = backup[EBackupCategories.blocktank];
+ const backupSlashtags = backup[EBackupCategories.slashtags];
- // Attempts to backup settings anytime remoteSettingsBackupSynced is set to false.
useDebouncedEffect(
() => {
- if (backup.remoteSettingsBackupSynced) {
+ if (backupSettings.synced > backupSettings.required) {
return;
}
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteSettingsBackupSynced',
- syncRequiredKey: 'remoteSettingsBackupSyncRequired',
- syncCompletedKey: 'remoteSettingsBackupLastSync',
- backupCategory: EBackupCategories.settings,
- selectedNetwork,
- backup: settings,
- }).then();
+ performBackup(EBackupCategories.settings);
},
- [backup.remoteSettingsBackupSynced, slashtag, settings, selectedNetwork],
+ [backupSettings.synced, backupSettings.required],
BACKUP_DEBOUNCE,
);
-
- // Attempts to backup widgets anytime remoteWidgetsBackupSynced is set to false.
useDebouncedEffect(
() => {
- if (backup.remoteWidgetsBackupSynced) {
+ if (backupWidgets.synced > backupWidgets.required) {
return;
}
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteWidgetsBackupSynced',
- syncRequiredKey: 'remoteWidgetsBackupSyncRequired',
- syncCompletedKey: 'remoteWidgetsBackupLastSync',
- backupCategory: EBackupCategories.widgets,
- selectedNetwork,
- backup: widgets,
- }).then();
+ performBackup(EBackupCategories.widgets);
},
- [backup.remoteWidgetsBackupSynced, slashtag, widgets, selectedNetwork],
+ [backupWidgets.synced, backupWidgets.required],
BACKUP_DEBOUNCE,
);
-
- // Attempts to backup metadata anytime remoteMetadataBackupSynced is set to false.
useDebouncedEffect(
() => {
- if (backup.remoteMetadataBackupSynced) {
+ if (backupMetadata.synced > backupMetadata.required) {
return;
}
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteMetadataBackupSynced',
- syncRequiredKey: 'remoteMetadataBackupSyncRequired',
- syncCompletedKey: 'remoteMetadataBackupLastSync',
- backupCategory: EBackupCategories.metadata,
- selectedNetwork,
- backup: metadata,
- }).then();
- },
- [backup.remoteMetadataBackupSynced, slashtag, metadata, selectedNetwork],
- BACKUP_DEBOUNCE,
- );
-
- // Attempts to backup ldkActivity anytime remoteLdkActivityBackupSynced is set to false.
- useDebouncedEffect(
- () => {
- if (backup.remoteLdkActivityBackupSynced) {
- return;
- }
-
- const ldkActivity = activity.filter(
- (a) => a.activityType === EActivityType.lightning,
- );
-
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteLdkActivityBackupSynced',
- syncRequiredKey: 'remoteLdkActivityBackupSyncRequired',
- syncCompletedKey: 'remoteLdkActivityBackupLastSync',
- backupCategory: EBackupCategories.ldkActivity,
- selectedNetwork,
- backup: ldkActivity,
- }).then();
+ performBackup(EBackupCategories.metadata);
},
- [backup.remoteLdkActivityBackupSynced, slashtag, activity, selectedNetwork],
+ [backupMetadata.synced, backupMetadata.required],
BACKUP_DEBOUNCE,
);
-
- // Attempts to backup blocktank anytime remoteBlocktankBackupSynced is set to false.
useDebouncedEffect(
() => {
- if (backup.remoteBlocktankBackupSynced) {
+ if (backupBlocktank.synced > backupBlocktank.required) {
return;
}
-
- const back = {
- orders: blocktank.orders,
- paidOrders: blocktank.paidOrders,
- };
-
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteBlocktankBackupSynced',
- syncRequiredKey: 'remoteBlocktankBackupSyncRequired',
- syncCompletedKey: 'remoteBlocktankBackupLastSync',
- backupCategory: EBackupCategories.blocktank,
- selectedNetwork,
- backup: back,
- }).then();
+ performBackup(EBackupCategories.blocktank);
},
- [
- backup.remoteBlocktankBackupSynced,
- slashtag,
- blocktank.orders,
- blocktank.paidOrders,
- selectedNetwork,
- ],
+ [backupBlocktank.synced, backupBlocktank.required],
BACKUP_DEBOUNCE,
);
-
- // Attempts to backup contacts anytime remoteSlashtagsBackupSynced is set to false.
useDebouncedEffect(
() => {
- if (backup.remoteSlashtagsBackupSynced) {
+ if (backupSlashtags.synced > backupSlashtags.required) {
return;
}
-
- const back = {
- contacts: slashtags.contacts,
- };
-
- performRemoteBackup({
- slashtag,
- isSyncedKey: 'remoteSlashtagsBackupSynced',
- syncRequiredKey: 'remoteSlashtagsBackupSyncRequired',
- syncCompletedKey: 'remoteSlashtagsBackupLastSync',
- backupCategory: EBackupCategories.slashtags,
- selectedNetwork,
- backup: back,
- }).then();
+ performBackup(EBackupCategories.slashtags);
},
- [
- backup.remoteSlashtagsBackupSynced,
- slashtag,
- slashtags.contacts,
- selectedNetwork,
- ],
+ [backupSlashtags.synced, backupSlashtags.required],
BACKUP_DEBOUNCE,
);
@@ -207,32 +80,13 @@ const EnabledSlashtag = (): ReactElement => {
return false;
}
- if (
- (backup.remoteSettingsBackupSyncRequired &&
- now - backup.remoteSettingsBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteWidgetsBackupSyncRequired &&
- now - backup.remoteWidgetsBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteMetadataBackupSyncRequired &&
- now - backup.remoteMetadataBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteLdkBackupLastSyncRequired &&
- now - backup.remoteLdkBackupLastSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteBlocktankBackupSyncRequired &&
- now - backup.remoteBlocktankBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteSlashtagsBackupSyncRequired &&
- now - backup.remoteSlashtagsBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME) ||
- (backup.remoteLdkActivityBackupSyncRequired &&
- now - backup.remoteLdkActivityBackupSyncRequired >
- FAILED_BACKUP_CHECK_TIME)
- ) {
- return true;
- }
- return false;
+ // find if there are any backup categories that have been failing for more than 30 minutes
+ return Object.values(EBackupCategories).some((key) => {
+ return (
+ backup[key].synced < backup[key].required &&
+ now - backup[key].required > FAILED_BACKUP_CHECK_TIME
+ );
+ });
}, [backup, now]);
useEffect(() => {
@@ -263,20 +117,6 @@ const EnabledSlashtag = (): ReactElement => {
};
}, [t, shouldShowBackupWarning]);
- useEffect(() => {
- if (__E2E__) {
- return;
- }
-
- const timer = setInterval(() => {
- checkProfileAndContactsBackup(slashtag);
- }, BACKUP_CHECK_INTERVAL);
-
- return (): void => {
- clearInterval(timer);
- };
- }, [slashtag]);
-
return <>>;
};
diff --git a/src/utils/lightning/index.ts b/src/utils/lightning/index.ts
index 02fbe97bd..a5c45aed3 100644
--- a/src/utils/lightning/index.ts
+++ b/src/utils/lightning/index.ts
@@ -10,7 +10,6 @@ import lm, {
EEventTypes,
ENetworks,
TAccount,
- TAccountBackup,
TChannel,
TChannelManagerClaim,
TChannelManagerPaymentSent,
@@ -24,6 +23,7 @@ import lm, {
TTransactionData,
TTransactionPosition,
TGetFees,
+ TBackupStateUpdate,
} from '@synonymdev/react-native-ldk';
import {
@@ -51,6 +51,7 @@ import {
} from '../../store/helpers';
import { defaultHeader } from '../../store/shapes/wallet';
import {
+ updateBackupState,
updateLdkAccountVersion,
updateLightningNodeId,
} from '../../store/slices/lightning';
@@ -114,6 +115,7 @@ export const FALLBACK_BLOCKTANK_PEERS: IWalletItem = {
let paymentSubscription: EmitterSubscription | undefined;
let onChannelSubscription: EmitterSubscription | undefined;
let onSpendableOutputsSubscription: EmitterSubscription | undefined;
+let onBackupStateUpdate: EmitterSubscription | undefined;
/**
* Wipes LDK data from storage
@@ -277,6 +279,18 @@ export const setupLdk = async ({
return err(storageRes.error);
}
const rapidGossipSyncUrl = getStore().settings.rapidGossipSyncUrl;
+ const backupRes = await ldk.backupSetup({
+ network,
+ seed: account.value.seed,
+ details: {
+ host: __BACKUPS_SERVER_HOST__,
+ serverPubKey: __BACKUPS_SERVER_PUBKEY__,
+ },
+ });
+ if (backupRes.isErr()) {
+ return err(backupRes.error);
+ }
+
const lmStart = await lm.start({
account: account.value,
getFees,
@@ -300,10 +314,6 @@ export const setupLdk = async ({
manually_accept_inbound_channels: true,
},
trustedZeroConfPeers: __TRUSTED_ZERO_CONF_PEERS__,
- backupServerDetails: {
- host: __BACKUPS_SERVER_HOST__,
- serverPubKey: __BACKUPS_SERVER_PUBKEY__,
- },
rapidGossipSyncUrl,
skipParamCheck: true, //Switch off for debugging LDK networking issues
});
@@ -467,12 +477,34 @@ export const subscribeToLightningPayments = ({
() => {},
);
}
+ if (!onBackupStateUpdate) {
+ onBackupStateUpdate = ldk.onEvent(
+ EEventTypes.backup_state_update,
+ (res: TBackupStateUpdate) => {
+ if (!selectedWallet) {
+ selectedWallet = getSelectedWallet();
+ }
+ if (!selectedNetwork) {
+ selectedNetwork = getSelectedNetwork();
+ }
+
+ dispatch(
+ updateBackupState({
+ backup: res,
+ selectedWallet,
+ selectedNetwork,
+ }),
+ );
+ },
+ );
+ }
};
export const unsubscribeFromLightningSubscriptions = (): void => {
paymentSubscription?.remove();
onChannelSubscription?.remove();
onSpendableOutputsSubscription?.remove();
+ onBackupStateUpdate?.remove();
};
let isRefreshing = false;
@@ -980,27 +1012,6 @@ export const getSha256 = (str: string): string => {
return hash.toString('hex');
};
-/**
- * Exports complete backup string for current LDK account.
- * @param account
- * @returns {Promise>}
- */
-export const exportBackup = async (
- account?: TAccount,
-): Promise> => {
- if (!account) {
- const res = await getLdkAccount();
- if (res.isErr()) {
- return err(res.error);
- }
-
- account = res.value;
- }
- return await lm.backupAccount({
- account,
- });
-};
-
/**
* Returns last known header information from storage.
* @returns {Promise}
diff --git a/yarn.lock b/yarn.lock
index eff7b153e..e9dac4c24 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4222,10 +4222,10 @@
dependencies:
b4a "^1.5.3"
-"@synonymdev/react-native-ldk@0.0.127":
- version "0.0.127"
- resolved "https://registry.yarnpkg.com/@synonymdev/react-native-ldk/-/react-native-ldk-0.0.127.tgz#01dbf45319b9003adba111934a3723345a3a461c"
- integrity sha512-CmPCq6C8Km55Zg35uM8wfFwY4GtqVQLHOJ1ppmEB36auGvCa0mdfOi7zUaRBw9E4c3W5fnOnAzeReju5WFyR5Q==
+"@synonymdev/react-native-ldk@0.0.129":
+ version "0.0.129"
+ resolved "https://registry.yarnpkg.com/@synonymdev/react-native-ldk/-/react-native-ldk-0.0.129.tgz#77b30fbf268afe01e753ba8ff54ef3966ac96365"
+ integrity sha512-xpAHIBiphmbe8niuIcRCZ+XVrdV/leBJOmpN9W5eHNMIbT9SYEHS7Tgyzh+vzQ+xM9/AEfKmSWEgA+MkVFqpNg==
dependencies:
bech32 "^2.0.0"
bitcoinjs-lib "^6.0.2"