Skip to content

Commit de9eb35

Browse files
authored
feat: backup LDK activity and blocktank orders
1 parent f865545 commit de9eb35

File tree

12 files changed

+317
-1
lines changed

12 files changed

+317
-1
lines changed

__tests__/backups.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ import {
1717
addMetaSlashTagsUrlTag,
1818
} from '../src/store/actions/metadata';
1919
import {
20+
performBlocktankRestore,
21+
performLdkActivityRestore,
2022
performMetadataRestore,
2123
performRemoteBackup,
2224
performSettingsRestore,
2325
performWidgetsRestore,
2426
} from '../src/store/actions/backup';
2527
import {
28+
getActivityStore,
29+
getBlocktankStore,
30+
getDispatch,
2631
getMetaDataStore,
2732
getSettingsStore,
2833
getWidgetsStore,
@@ -36,6 +41,18 @@ import {
3641
setFeedWidget,
3742
updateWidgets,
3843
} from '../src/store/actions/widgets';
44+
import {
45+
addActivityItem,
46+
resetActivityStore,
47+
} from '../src/store/actions/activity';
48+
import { EActivityType } from '../src/store/types/activity';
49+
import { EPaymentType } from '../src/store/types/wallet';
50+
import {
51+
addPaidBlocktankOrder,
52+
resetBlocktankStore,
53+
} from '../src/store/actions/blocktank';
54+
import actions from '../src/store/actions/actions';
55+
import { defaultOrderResponse } from '../src/store/shapes/blocktank';
3956

4057
jest.setTimeout(30000);
4158

@@ -197,4 +214,87 @@ describe('Remote backups', () => {
197214
expect(store.getState().widgets).toEqual(backup);
198215
expect(store.getState().backup.remoteWidgetsBackupSynced).toEqual(true);
199216
});
217+
218+
it('Backups and restores LDK Activity', async () => {
219+
addActivityItem({
220+
id: 'id',
221+
activityType: EActivityType.lightning,
222+
txType: EPaymentType.received,
223+
txId: 'txId',
224+
message: '',
225+
address: 'invoice',
226+
value: 1,
227+
timestamp: new Date().getTime(),
228+
});
229+
230+
const backup = getActivityStore().items.filter(
231+
(a) => a.activityType === EActivityType.lightning,
232+
);
233+
234+
const uploadRes = await performRemoteBackup({
235+
slashtag,
236+
isSyncedKey: 'remoteLdkActivityBackupSynced',
237+
backupCategory: EBackupCategories.ldkActivity,
238+
backup,
239+
});
240+
241+
if (uploadRes.isErr()) {
242+
throw uploadRes.error;
243+
}
244+
245+
resetActivityStore();
246+
expect(store.getState().activity.items.length).toEqual(0);
247+
248+
const restoreRes = await performLdkActivityRestore({
249+
slashtag,
250+
});
251+
252+
if (restoreRes.isErr()) {
253+
throw restoreRes.error;
254+
}
255+
256+
expect(restoreRes.value.backupExists).toEqual(true);
257+
expect(store.getState().activity.items).toEqual(backup);
258+
expect(store.getState().backup.remoteLdkActivityBackupSynced).toEqual(true);
259+
});
260+
261+
it('Backups and restores Blocktank orders', async () => {
262+
const dispatch = getDispatch();
263+
addPaidBlocktankOrder({ orderId: 'id', txid: 'txid' });
264+
dispatch({
265+
type: actions.UPDATE_BLOCKTANK_ORDER,
266+
payload: defaultOrderResponse,
267+
});
268+
269+
const { orders, paidOrders } = getBlocktankStore();
270+
const backup = { orders, paidOrders };
271+
272+
const uploadRes = await performRemoteBackup({
273+
slashtag,
274+
isSyncedKey: 'remoteBlocktankBackupSynced',
275+
backupCategory: EBackupCategories.blocktank,
276+
backup,
277+
});
278+
279+
if (uploadRes.isErr()) {
280+
throw uploadRes.error;
281+
}
282+
283+
resetBlocktankStore();
284+
expect(store.getState().blocktank.orders.length).toEqual(0);
285+
expect(store.getState().blocktank.paidOrders).toMatchObject({});
286+
287+
const restoreRes = await performBlocktankRestore({
288+
slashtag,
289+
});
290+
291+
if (restoreRes.isErr()) {
292+
throw restoreRes.error;
293+
}
294+
295+
expect(restoreRes.value.backupExists).toEqual(true);
296+
expect(store.getState().blocktank.orders).toEqual(backup.orders);
297+
expect(store.getState().blocktank.paidOrders).toEqual(backup.paidOrders);
298+
expect(store.getState().backup.remoteBlocktankBackupSynced).toEqual(true);
299+
});
200300
});

src/store/actions/actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const actions = {
6161
// Activity
6262
UPDATE_ACTIVITY_ENTRIES: 'UPDATE_ACTIVITY_ENTRIES',
6363
ADD_ACTIVITY_ITEM: 'ADD_ACTIVITY_ITEM',
64+
ADD_ACTIVITY_ITEMS: 'ADD_ACTIVITY_ITEMS',
6465
UPDATE_ACTIVITY_ITEM: 'UPDATE_ACTIVITY_ITEM',
6566
RESET_ACTIVITY_STORE: 'RESET_ACTIVITY_STORE',
6667

@@ -69,6 +70,7 @@ const actions = {
6970
RESET_BACKUP_STORE: 'RESET_BACKUP_STORE',
7071

7172
// Blocktank
73+
UPDATE_BLOCKTANK: 'UPDATE_BLOCKTANK',
7274
UPDATE_BLOCKTANK_SERVICE_LIST: 'UPDATE_BLOCKTANK_SERVICE_LIST',
7375
UPDATE_BLOCKTANK_ORDER: 'UPDATE_BLOCKTANK_ORDER',
7476
UPDATE_BLOCKTANK_INFO: 'UPDATE_BLOCKTANK_INFO',

src/store/actions/activity.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ export const addActivityItem = (
2424
return ok('Activity Item Added.');
2525
};
2626

27+
export const addActivityItems = (
28+
activityItems: Array<IActivityItem>,
29+
): Result<string> => {
30+
dispatch({
31+
type: actions.ADD_ACTIVITY_ITEMS,
32+
payload: activityItems,
33+
});
34+
return ok('Activity Item Added.');
35+
};
36+
2737
/**
2838
* @param {string} id
2939
* @param {IActivityItem} newActivityItem

src/store/actions/backup.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import { getDefaultWidgetsShape } from '../shapes/widgets';
3333
import { IMetadata } from '../types/metadata';
3434
import { getDefaultMetadataShape } from '../shapes/metadata';
3535
import { updateMetadata } from './metadata';
36+
import { EActivityType } from '../types/activity';
37+
import { addActivityItems } from './activity';
38+
import { updateBlocktank } from './blocktank';
39+
import { IBlocktank } from '../types/blocktank';
40+
import { IActivity } from '../types/activity';
3641

3742
const dispatch = getDispatch();
3843

@@ -396,6 +401,85 @@ export const performMetadataRestore = async ({
396401
return ok({ backupExists: true });
397402
};
398403

404+
export const performLdkActivityRestore = async ({
405+
slashtag,
406+
selectedNetwork,
407+
}: {
408+
slashtag: Slashtag;
409+
selectedNetwork?: TAvailableNetworks;
410+
}): Promise<Result<{ backupExists: boolean }>> => {
411+
if (!selectedNetwork) {
412+
selectedNetwork = getSelectedNetwork();
413+
}
414+
415+
const backupRes = await getBackup<IActivity['items']>({
416+
slashtag,
417+
backupCategory: EBackupCategories.ldkActivity,
418+
selectedNetwork,
419+
});
420+
if (backupRes.isErr()) {
421+
return err(backupRes.error.message);
422+
}
423+
424+
const backup = backupRes.value;
425+
426+
if (!backup) {
427+
return ok({ backupExists: false });
428+
}
429+
430+
if (
431+
!(
432+
Array.isArray(backup) &&
433+
backup.every((i) => i.activityType === EActivityType.lightning)
434+
)
435+
) {
436+
return ok({ backupExists: false });
437+
}
438+
439+
addActivityItems(backup);
440+
updateBackup({ remoteLdkActivityBackupSynced: true });
441+
442+
// Restore success
443+
return ok({ backupExists: true });
444+
};
445+
446+
export const performBlocktankRestore = async ({
447+
slashtag,
448+
selectedNetwork,
449+
}: {
450+
slashtag: Slashtag;
451+
selectedNetwork?: TAvailableNetworks;
452+
}): Promise<Result<{ backupExists: boolean }>> => {
453+
if (!selectedNetwork) {
454+
selectedNetwork = getSelectedNetwork();
455+
}
456+
457+
const backupRes = await getBackup<Partial<IBlocktank>>({
458+
slashtag,
459+
backupCategory: EBackupCategories.blocktank,
460+
selectedNetwork,
461+
});
462+
if (backupRes.isErr()) {
463+
return err(backupRes.error.message);
464+
}
465+
466+
const backup = backupRes.value;
467+
468+
if (!backup) {
469+
return ok({ backupExists: false });
470+
}
471+
472+
if (!('orders' in backup && 'paidOrders' in backup)) {
473+
return ok({ backupExists: false });
474+
}
475+
476+
updateBlocktank(backup);
477+
updateBackup({ remoteLdkActivityBackupSynced: true });
478+
479+
// Restore success
480+
return ok({ backupExists: true });
481+
};
482+
399483
export const performFullRestoreFromLatestBackup = async (
400484
slashtag: Slashtag,
401485
): Promise<Result<{ backupExists: boolean }>> => {
@@ -430,7 +514,25 @@ export const performFullRestoreFromLatestBackup = async (
430514
});
431515
if (metadataBackupRes.isErr()) {
432516
//Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
433-
console.log('Error backing up widgets', metadataBackupRes.error.message);
517+
console.log('Error backing up metadata', metadataBackupRes.error.message);
518+
}
519+
520+
const ldkActivityRes = await performLdkActivityRestore({
521+
slashtag,
522+
selectedNetwork,
523+
});
524+
if (ldkActivityRes.isErr()) {
525+
//Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
526+
console.log('Error backing up ldkActivity', ldkActivityRes.error.message);
527+
}
528+
529+
const btBackupRes = await performBlocktankRestore({
530+
slashtag,
531+
selectedNetwork,
532+
});
533+
if (btBackupRes.isErr()) {
534+
//Since this backup feature is not critical and mostly for user convenience there's no reason to throw an error here.
535+
console.log('Error backing up blocktank', btBackupRes.error.message);
434536
}
435537

436538
// Restore success

src/store/actions/blocktank.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { showErrorNotification } from '../../utils/notifications';
4545
import { getDisplayValues } from '../../utils/exchange-rate';
4646
import { TWalletName } from '../types/wallet';
4747
import i18n from '../../utils/i18n';
48+
import { IBlocktank } from '../types/blocktank';
4849

4950
const dispatch = getDispatch();
5051

@@ -563,3 +564,13 @@ export const confirmChannelPurchase = async ({
563564
}).then();
564565
return ok(broadcastResponse.value);
565566
};
567+
568+
export const updateBlocktank = (
569+
payload: Partial<IBlocktank>,
570+
): Result<string> => {
571+
dispatch({
572+
type: actions.UPDATE_BLOCKTANK,
573+
payload,
574+
});
575+
return ok('');
576+
};

src/store/reducers/activity.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ const activity = (
2424
};
2525
}
2626

27+
case actions.ADD_ACTIVITY_ITEMS: {
28+
return {
29+
...state,
30+
items: [...action.payload, ...state.items],
31+
};
32+
}
33+
2734
case actions.UPDATE_ACTIVITY_ITEM: {
2835
const newItems = state.items.map((activityItem) => {
2936
if (activityItem.id === action.payload.id) {

src/store/reducers/backup.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import actions from '../actions/actions';
22
import { defaultBackupShape } from '../shapes/backup';
3+
import { EActivityType } from '../types/activity';
34
import { IBackup } from '../types/backup';
45

56
const backup = (state: IBackup = defaultBackupShape, action): IBackup => {
@@ -36,6 +37,19 @@ const backup = (state: IBackup = defaultBackupShape, action): IBackup => {
3637
remoteMetadataBackupSynced: false,
3738
};
3839

40+
case actions.ADD_ACTIVITY_ITEM:
41+
// we only listen for LN activity here
42+
return action.payload.activityType === EActivityType.lightning
43+
? { ...state, remoteLdkActivityBackupSynced: false }
44+
: state;
45+
46+
case actions.ADD_PAID_BLOCKTANK_ORDER:
47+
case actions.UPDATE_BLOCKTANK_ORDER:
48+
return {
49+
...state,
50+
remoteBlocktankBackupSynced: false,
51+
};
52+
3953
case actions.RESET_BACKUP_STORE:
4054
return defaultBackupShape;
4155

src/store/reducers/blocktank.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ const blocktank = (
5656
},
5757
};
5858

59+
case actions.UPDATE_BLOCKTANK:
60+
return {
61+
...state,
62+
...action.payload,
63+
};
64+
5965
case actions.RESET_BLOCKTANK_STORE:
6066
return defaultBlocktankShape;
6167

src/store/shapes/backup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export const defaultBackupShape: IBackup = {
66
remoteSettingsBackupSynced: false,
77
remoteWidgetsBackupSynced: false,
88
remoteMetadataBackupSynced: false,
9+
remoteLdkActivityBackupSynced: false,
10+
remoteBlocktankBackupSynced: false,
911

1012
iCloudBackupsEnabled: false,
1113
iCloudLdkBackupsSynced: false,

src/store/types/backup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface IBackup {
1111
remoteSettingsBackupSynced: boolean;
1212
remoteWidgetsBackupSynced: boolean;
1313
remoteMetadataBackupSynced: boolean;
14+
remoteLdkActivityBackupSynced: boolean;
15+
remoteBlocktankBackupSynced: boolean;
1416
//TODO transactions, slashtags, metadata, etc.
1517

1618
//iCloud

0 commit comments

Comments
 (0)