1083 lines
39 KiB
TypeScript
1083 lines
39 KiB
TypeScript
import { Platform } from 'react-native';
|
|
import * as Keychain from 'react-native-keychain';
|
|
|
|
import { sleep } from './SleepUtils';
|
|
import { settingsStore } from '../stores/Stores';
|
|
import {
|
|
Settings,
|
|
DEFAULT_FIAT_RATES_SOURCE,
|
|
DEFAULT_FIAT,
|
|
DEFAULT_LSP_MAINNET,
|
|
DEFAULT_LSP_TESTNET,
|
|
DEFAULT_NOSTR_RELAYS,
|
|
localeMigrationMapping,
|
|
DEFAULT_NEUTRINO_PEERS_MAINNET,
|
|
DEFAULT_NEUTRINO_PEERS_TESTNET,
|
|
DEFAULT_LSPS1_HOST_MAINNET,
|
|
DEFAULT_LSPS1_HOST_TESTNET,
|
|
DEFAULT_LSPS1_PUBKEY_MAINNET,
|
|
DEFAULT_LSPS1_PUBKEY_TESTNET,
|
|
DEFAULT_LSPS1_REST_MAINNET,
|
|
DEFAULT_LSPS1_REST_TESTNET,
|
|
DEFAULT_SPEEDLOADER,
|
|
DEFAULT_NOSTR_RELAYS_2023,
|
|
PosEnabled,
|
|
DEFAULT_SLIDE_TO_PAY_THRESHOLD,
|
|
STORAGE_KEY,
|
|
LEGACY_CURRENCY_CODES_KEY,
|
|
CURRENCY_CODES_KEY
|
|
} from '../stores/SettingsStore';
|
|
|
|
import { LEGACY_NOTES_KEY, NOTES_KEY } from '../stores/NotesStore';
|
|
import { LEGACY_CONTACTS_KEY, CONTACTS_KEY } from '../stores/ContactStore';
|
|
import {
|
|
LEGACY_LAST_CHANNEL_BACKUP_STATUS,
|
|
LEGACY_LAST_CHANNEL_BACKUP_TIME,
|
|
LAST_CHANNEL_BACKUP_STATUS,
|
|
LAST_CHANNEL_BACKUP_TIME
|
|
} from '../stores/ChannelBackupStore';
|
|
import {
|
|
LEGACY_ADDRESS_ACTIVATED_STRING,
|
|
LEGACY_HASHES_STORAGE_STRING,
|
|
ADDRESS_ACTIVATED_STRING,
|
|
HASHES_STORAGE_STRING
|
|
} from '../stores/LightningAddressStore';
|
|
import {
|
|
LEGACY_POS_HIDDEN_KEY,
|
|
LEGACY_POS_STANDALONE_KEY,
|
|
POS_HIDDEN_KEY,
|
|
POS_STANDALONE_KEY
|
|
} from '../stores/PosStore';
|
|
import {
|
|
LEGACY_CATEGORY_KEY,
|
|
LEGACY_PRODUCT_KEY,
|
|
CATEGORY_KEY,
|
|
PRODUCT_KEY
|
|
} from '../stores/InventoryStore';
|
|
import { LEGACY_UNIT_KEY, UNIT_KEY } from '../stores/UnitsStore';
|
|
import {
|
|
LEGACY_HIDDEN_ACCOUNTS_KEY,
|
|
HIDDEN_ACCOUNTS_KEY
|
|
} from '../stores/UTXOsStore';
|
|
|
|
import { LEGACY_LSPS1_ORDERS_KEY, LSPS_ORDERS_KEY } from '../stores/LSPStore';
|
|
|
|
import { LNC_STORAGE_KEY, hash } from '../backends/LNC/credentialStore';
|
|
|
|
import {
|
|
SWAPS_KEY,
|
|
REVERSE_SWAPS_KEY,
|
|
SWAPS_RESCUE_KEY,
|
|
SWAPS_LAST_USED_KEY
|
|
} from '../utils/SwapUtils';
|
|
|
|
import {
|
|
LEGACY_ACTIVITY_FILTERS_KEY,
|
|
ACTIVITY_FILTERS_KEY
|
|
} from '../stores/ActivityStore';
|
|
|
|
const LEGACY_IS_BACKED_UP_KEY = 'backup-complete';
|
|
export const IS_BACKED_UP_KEY = 'backup-complete-v2';
|
|
|
|
const KEYCHAIN_MIGRATION_KEY = 'ios-keychain-cloud-sync-migration-v1';
|
|
const CASHU_MIGRATION_KEY = 'ios-keychain-cashu-fix-v1';
|
|
|
|
import EncryptedStorage from 'react-native-encrypted-storage';
|
|
import Storage from '../storage';
|
|
|
|
class MigrationsUtils {
|
|
/**
|
|
* Migrates a key from old keychain (cloud or local) to new Storage namespace.
|
|
* Safe order: read → write → verify → delete
|
|
*
|
|
* Since Storage now uses a "zeus:" prefix, old and new keys are in different
|
|
* namespaces, so deleting old keys won't affect newly written data.
|
|
*/
|
|
private async migrateKey(key: string): Promise<string | null> {
|
|
try {
|
|
// 1. Check if already migrated to new Storage namespace
|
|
const existingData = await Storage.getItem(key);
|
|
if (existingData) return existingData;
|
|
|
|
// 2. Read from old keychain (local first, then cloud)
|
|
const credentials = await this.readFromOldKeychain(key);
|
|
if (!credentials) return null;
|
|
|
|
console.log(`[Migration] Moving ${key} to Local Storage...`);
|
|
|
|
// iOS: Add delay after read to allow iCloud sync
|
|
if (Platform.OS === 'ios') {
|
|
await sleep(500);
|
|
}
|
|
|
|
// 3. Write to new Storage namespace (zeus:key)
|
|
const writeSuccess = await Storage.setItem(
|
|
key,
|
|
credentials.password
|
|
);
|
|
|
|
if (!writeSuccess) {
|
|
throw new Error(
|
|
`Write failed for ${key}. Storage.setItem returned false.`
|
|
);
|
|
}
|
|
|
|
// 4. Verify write succeeded by reading back
|
|
const verifyData = await Storage.getItem(key);
|
|
if (!verifyData) {
|
|
throw new Error(
|
|
`Verification failed for ${key}. Data not found after write.`
|
|
);
|
|
}
|
|
|
|
// iOS: Add delay before delete to allow iCloud sync
|
|
if (Platform.OS === 'ios') {
|
|
await sleep(500);
|
|
}
|
|
|
|
// 5. Only delete old keychain entries after successful write + verify
|
|
await this.deleteFromOldKeychain(key);
|
|
|
|
console.log(`[Migration] Successfully migrated ${key}`);
|
|
return credentials.password;
|
|
} catch (error) {
|
|
console.error(`[Migration] Failed to migrate ${key}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads from old keychain (without zeus: prefix).
|
|
* Tries local keychain first, then cloud keychain as fallback.
|
|
*/
|
|
private async readFromOldKeychain(key: string) {
|
|
try {
|
|
// Try local keychain first
|
|
const localCreds = await Keychain.getInternetCredentials(key);
|
|
if (localCreds) return localCreds;
|
|
|
|
// Fallback to cloud keychain
|
|
return await Keychain.getInternetCredentials(key, {
|
|
cloudSync: true
|
|
});
|
|
} catch (e) {
|
|
console.warn(`[Migration] Read error for ${key}`, e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes from old keychain entries (without zeus: prefix).
|
|
*
|
|
* DISABLED: To prevent potential data loss during migration.
|
|
* Old keychain entries will remain but are harmless (orphaned data).
|
|
* The new Storage uses a "zeus:" prefix, so old and new keys don't conflict.
|
|
*/
|
|
private async deleteFromOldKeychain(key: string) {
|
|
// Safety measure: skip deletion to prevent any potential data loss
|
|
console.log(`[Migration] Skipping delete for ${key} (safety measure)`);
|
|
return;
|
|
|
|
// Original delete code - commented out for safety:
|
|
// try {
|
|
// await Keychain.resetInternetCredentials({
|
|
// server: key,
|
|
// cloudSync: true
|
|
// });
|
|
// } catch (e) {
|
|
// console.warn(
|
|
// `[Migration] Error deleting from cloud keychain: ${key}`,
|
|
// e
|
|
// );
|
|
// }
|
|
// try {
|
|
// await Keychain.resetInternetCredentials({ server: key });
|
|
// } catch (e) {
|
|
// console.warn(
|
|
// `[Migration] Error deleting from local keychain: ${key}`,
|
|
// e
|
|
// );
|
|
// }
|
|
}
|
|
|
|
private async migrateCashuForNode(lndDir: string) {
|
|
console.log(`Migrating Cashu data for node: ${lndDir}`);
|
|
|
|
const cashuKeys = [
|
|
`${lndDir}-cashu-mintUrls`,
|
|
`${lndDir}-cashu-selectedMintUrl`,
|
|
`${lndDir}-cashu-totalBalanceSats`,
|
|
`${lndDir}-cashu-invoices`,
|
|
`${lndDir}-cashu-payments`,
|
|
`${lndDir}-cashu-received-tokens`,
|
|
`${lndDir}-cashu-sent-tokens`,
|
|
`${lndDir}-cashu-seed-version`,
|
|
`${lndDir}-cashu-seed-phrase`,
|
|
`${lndDir}-cashu-seed`
|
|
];
|
|
|
|
for (const key of cashuKeys) {
|
|
await this.migrateKey(key);
|
|
}
|
|
|
|
const mintUrlsJson = await this.migrateKey(`${lndDir}-cashu-mintUrls`);
|
|
|
|
if (mintUrlsJson) {
|
|
try {
|
|
const mintUrls = JSON.parse(mintUrlsJson);
|
|
if (Array.isArray(mintUrls)) {
|
|
for (const mintUrl of mintUrls) {
|
|
const walletId = `${lndDir}==${mintUrl}`;
|
|
const walletKeys = [
|
|
`${walletId}-mintInfo`,
|
|
`${walletId}-counter`,
|
|
`${walletId}-proofs`,
|
|
`${walletId}-balance`,
|
|
`${walletId}-pubkey`
|
|
];
|
|
for (const wKey of walletKeys) {
|
|
await this.migrateKey(wKey);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn(`Failed to parse mintUrls for ${lndDir}`, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async legacySettingsMigrations(settings: string) {
|
|
const newSettings = JSON.parse(settings) as Settings;
|
|
if (!newSettings.fiatRatesSource) {
|
|
newSettings.fiatRatesSource = DEFAULT_FIAT_RATES_SOURCE;
|
|
}
|
|
|
|
// migrate fiat settings from older versions
|
|
if (!newSettings.fiat || newSettings.fiat === 'Disabled') {
|
|
newSettings.fiat = DEFAULT_FIAT;
|
|
newSettings.fiatEnabled = false;
|
|
} else if (newSettings.fiatEnabled == null) {
|
|
newSettings.fiatEnabled = true;
|
|
}
|
|
|
|
// set default LSPs if not defined
|
|
if (newSettings.enableLSP === undefined) {
|
|
newSettings.enableLSP = true;
|
|
}
|
|
if (!newSettings.lspMainnet) {
|
|
newSettings.lspMainnet = DEFAULT_LSP_MAINNET;
|
|
}
|
|
if (!newSettings.lspTestnet) {
|
|
newSettings.lspTestnet = DEFAULT_LSP_TESTNET;
|
|
}
|
|
|
|
// default Lightning Address settings
|
|
if (!newSettings.lightningAddress) {
|
|
newSettings.lightningAddress = {
|
|
enabled: false,
|
|
automaticallyAccept: true,
|
|
automaticallyAcceptAttestationLevel: 2,
|
|
automaticallyRequestOlympusChannels: false, // deprecated
|
|
routeHints: false,
|
|
allowComments: true,
|
|
nostrPrivateKey: '',
|
|
nostrRelays: DEFAULT_NOSTR_RELAYS,
|
|
notifications: 0,
|
|
mintUrl: '' // Cashu
|
|
};
|
|
}
|
|
|
|
// migrate locale to ISO 639-1
|
|
if (
|
|
newSettings.locale != null &&
|
|
localeMigrationMapping[newSettings.locale]
|
|
) {
|
|
newSettings.locale = localeMigrationMapping[newSettings.locale];
|
|
}
|
|
|
|
const MOD_KEY = 'lsp-taproot-mod';
|
|
const mod = await EncryptedStorage.getItem(MOD_KEY);
|
|
if (!mod) {
|
|
newSettings.requestSimpleTaproot = true;
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY, 'true');
|
|
}
|
|
|
|
const MOD_KEY2 = 'lsp-preview-mod';
|
|
const mod2 = await EncryptedStorage.getItem(MOD_KEY2);
|
|
if (!mod2) {
|
|
if (newSettings?.lspMainnet === 'https://lsp-preview.lnolymp.us') {
|
|
newSettings.lspMainnet = DEFAULT_LSP_MAINNET;
|
|
}
|
|
if (newSettings?.lspTestnet === 'https://testnet-lsp.lnolymp.us') {
|
|
newSettings.lspTestnet = DEFAULT_LSP_TESTNET;
|
|
}
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY2, 'true');
|
|
}
|
|
|
|
const MOD_KEY3 = 'neutrino-peers-mod1';
|
|
const mod3 = await EncryptedStorage.getItem(MOD_KEY3);
|
|
if (!mod3) {
|
|
const neutrinoPeersMainnetOld = [
|
|
'btcd1.lnolymp.us',
|
|
'btcd2.lnolymp.us',
|
|
'btcd-mainnet.lightning.computer',
|
|
'node.eldamar.icu',
|
|
'noad.sathoarder.com'
|
|
];
|
|
if (
|
|
JSON.stringify(newSettings?.neutrinoPeersMainnet) ===
|
|
JSON.stringify(neutrinoPeersMainnetOld)
|
|
) {
|
|
newSettings.neutrinoPeersMainnet =
|
|
DEFAULT_NEUTRINO_PEERS_MAINNET;
|
|
}
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY3, 'true');
|
|
}
|
|
|
|
const MOD_KEY4 = 'lsps1-hosts1';
|
|
const mod4 = await EncryptedStorage.getItem(MOD_KEY4);
|
|
if (!mod4) {
|
|
if (!newSettings?.lsps1HostMainnet) {
|
|
newSettings.lsps1HostMainnet = DEFAULT_LSPS1_HOST_MAINNET;
|
|
}
|
|
if (!newSettings?.lsps1HostTestnet) {
|
|
newSettings.lsps1HostTestnet = DEFAULT_LSPS1_HOST_TESTNET;
|
|
}
|
|
if (!newSettings?.lsps1PubkeyMainnet) {
|
|
newSettings.lsps1PubkeyMainnet = DEFAULT_LSPS1_PUBKEY_MAINNET;
|
|
}
|
|
if (!newSettings?.lsps1PubkeyTestnet) {
|
|
newSettings.lsps1PubkeyTestnet = DEFAULT_LSPS1_PUBKEY_TESTNET;
|
|
}
|
|
if (!newSettings?.lsps1RestMainnet) {
|
|
newSettings.lsps1RestMainnet = DEFAULT_LSPS1_REST_MAINNET;
|
|
}
|
|
if (!newSettings?.lsps1RestTestnet) {
|
|
newSettings.lsps1RestTestnet = DEFAULT_LSPS1_REST_TESTNET;
|
|
}
|
|
|
|
if (!newSettings?.lsps1Token) {
|
|
newSettings.lsps1Token = '';
|
|
}
|
|
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY4, 'true');
|
|
}
|
|
|
|
const MOD_KEY5 = 'millisat_amounts';
|
|
const mod5 = await EncryptedStorage.getItem(MOD_KEY5);
|
|
if (!mod5) {
|
|
if (!newSettings?.display?.showMillisatoshiAmounts) {
|
|
if (!newSettings.display) {
|
|
newSettings.display = {
|
|
showMillisatoshiAmounts: true
|
|
};
|
|
} else {
|
|
newSettings.display.showMillisatoshiAmounts = true;
|
|
}
|
|
}
|
|
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY5, 'true');
|
|
}
|
|
|
|
const MOD_KEY6 = 'egs-host';
|
|
const mod6 = await EncryptedStorage.getItem(MOD_KEY6);
|
|
if (!mod6) {
|
|
if (!newSettings?.speedloader) {
|
|
newSettings.speedloader = DEFAULT_SPEEDLOADER;
|
|
newSettings.customSpeedloader = '';
|
|
}
|
|
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY6, 'true');
|
|
}
|
|
|
|
// switch off bimodal pathfinding while bug exists
|
|
// https://github.com/lightningnetwork/lnd/issues/9085
|
|
const MOD_KEY7 = 'bimodal-bug-9085';
|
|
const mod7 = await EncryptedStorage.getItem(MOD_KEY7);
|
|
if (!mod7) {
|
|
if (newSettings?.bimodalPathfinding) {
|
|
newSettings.bimodalPathfinding = false;
|
|
}
|
|
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY7, 'true');
|
|
}
|
|
|
|
const MOD_KEY8 = 'nostr-relays-2024';
|
|
const mod8 = await EncryptedStorage.getItem(MOD_KEY8);
|
|
if (!mod8) {
|
|
if (
|
|
JSON.stringify(newSettings?.lightningAddress?.nostrRelays) ===
|
|
JSON.stringify(DEFAULT_NOSTR_RELAYS_2023)
|
|
) {
|
|
newSettings.lightningAddress.nostrRelays = DEFAULT_NOSTR_RELAYS;
|
|
}
|
|
|
|
settingsStore.setSettings(JSON.stringify(newSettings));
|
|
await EncryptedStorage.setItem(MOD_KEY8, 'true');
|
|
}
|
|
|
|
// migrate old POS squareEnabled setting to posEnabled
|
|
if (newSettings?.pos?.squareEnabled) {
|
|
newSettings.pos.posEnabled = PosEnabled.Square;
|
|
newSettings.pos.squareEnabled = false;
|
|
}
|
|
|
|
if (!newSettings.neutrinoPeersMainnet) {
|
|
newSettings.neutrinoPeersMainnet = DEFAULT_NEUTRINO_PEERS_MAINNET;
|
|
}
|
|
if (!newSettings.neutrinoPeersTestnet) {
|
|
newSettings.neutrinoPeersTestnet = DEFAULT_NEUTRINO_PEERS_TESTNET;
|
|
}
|
|
|
|
if (newSettings.payments == null) {
|
|
newSettings.payments = {
|
|
slideToPayThreshold: DEFAULT_SLIDE_TO_PAY_THRESHOLD
|
|
};
|
|
} else if (newSettings.payments.slideToPayThreshold == null) {
|
|
newSettings.payments.slideToPayThreshold =
|
|
DEFAULT_SLIDE_TO_PAY_THRESHOLD;
|
|
}
|
|
|
|
return newSettings;
|
|
}
|
|
|
|
public async storageMigrationV2(settings: any) {
|
|
const migrationTasks = [];
|
|
|
|
// Settings migration
|
|
console.log('Attemping settings migration');
|
|
const settingsMigration = Storage.setItem(STORAGE_KEY, settings).then(
|
|
(writeSuccess) => {
|
|
console.log('Settings migration status', writeSuccess);
|
|
return writeSuccess;
|
|
}
|
|
);
|
|
migrationTasks.push(settingsMigration);
|
|
|
|
// Contacts migration
|
|
const contactsMigration = (async () => {
|
|
try {
|
|
const contacts = await EncryptedStorage.getItem(
|
|
LEGACY_CONTACTS_KEY
|
|
);
|
|
if (contacts) {
|
|
console.log('Attemping contacts migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
CONTACTS_KEY,
|
|
contacts
|
|
);
|
|
console.log('Contacts migration status', writeSuccess);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading contacts from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(contactsMigration);
|
|
|
|
// Notes migration
|
|
const notesMigration = (async () => {
|
|
try {
|
|
const storedKeys = await EncryptedStorage.getItem(
|
|
LEGACY_NOTES_KEY
|
|
);
|
|
if (storedKeys) {
|
|
const noteKeys = JSON.parse(storedKeys);
|
|
console.log('Attemping notes migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
NOTES_KEY,
|
|
noteKeys
|
|
);
|
|
console.log('Notes keys migration status', writeSuccess);
|
|
|
|
// Load all legacy notes
|
|
const notesPromises = noteKeys.map(async (key: string) => {
|
|
const note = await EncryptedStorage.getItem(key);
|
|
if (note) {
|
|
const writeSuccess = await Storage.setItem(
|
|
key,
|
|
note
|
|
);
|
|
console.log(
|
|
`Notes keys migration status: ${key}`,
|
|
writeSuccess
|
|
);
|
|
return writeSuccess;
|
|
}
|
|
});
|
|
|
|
const noteResults = await Promise.all(notesPromises);
|
|
return (
|
|
writeSuccess &&
|
|
noteResults.every((result) => result !== false)
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading note keys from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(notesMigration);
|
|
|
|
// Lightning address migration
|
|
const lightningAddressMigration = (async () => {
|
|
try {
|
|
let activatedSuccess: any = true;
|
|
let hashesSuccess: any = true;
|
|
|
|
const activated = await EncryptedStorage.getItem(
|
|
LEGACY_ADDRESS_ACTIVATED_STRING
|
|
);
|
|
if (activated) {
|
|
console.log(
|
|
'Attemping lightning address activated migration'
|
|
);
|
|
activatedSuccess = await Storage.setItem(
|
|
ADDRESS_ACTIVATED_STRING,
|
|
activated
|
|
);
|
|
console.log(
|
|
'Lightning address activated migration status',
|
|
activatedSuccess
|
|
);
|
|
}
|
|
|
|
const hashes = await EncryptedStorage.getItem(
|
|
LEGACY_HASHES_STORAGE_STRING
|
|
);
|
|
if (hashes) {
|
|
console.log('Attemping lightning address hashes migration');
|
|
hashesSuccess = await Storage.setItem(
|
|
HASHES_STORAGE_STRING,
|
|
hashes
|
|
);
|
|
console.log(
|
|
'Lightning address hashes migration status',
|
|
hashesSuccess
|
|
);
|
|
}
|
|
|
|
return activatedSuccess && hashesSuccess;
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading lightning address data from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(lightningAddressMigration);
|
|
|
|
// Backup status migration
|
|
const backupStatusMigration = (async () => {
|
|
try {
|
|
let statusSuccess: any = true;
|
|
let timeSuccess: any = true;
|
|
|
|
const status = await EncryptedStorage.getItem(
|
|
LEGACY_LAST_CHANNEL_BACKUP_STATUS
|
|
);
|
|
if (status) {
|
|
console.log('Attemping backup status migration');
|
|
statusSuccess = await Storage.setItem(
|
|
LAST_CHANNEL_BACKUP_STATUS,
|
|
status
|
|
);
|
|
console.log(
|
|
'Backup status migration status',
|
|
statusSuccess
|
|
);
|
|
}
|
|
|
|
const time = await EncryptedStorage.getItem(
|
|
LEGACY_LAST_CHANNEL_BACKUP_TIME
|
|
);
|
|
if (time) {
|
|
console.log('Attemping backup time migration');
|
|
timeSuccess = await Storage.setItem(
|
|
LAST_CHANNEL_BACKUP_TIME,
|
|
time
|
|
);
|
|
console.log('Backup time migration status', timeSuccess);
|
|
}
|
|
|
|
return statusSuccess && timeSuccess;
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading backup status from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(backupStatusMigration);
|
|
|
|
// POS migration
|
|
const posMigration = (async () => {
|
|
try {
|
|
let hiddenKeySuccess: any = true;
|
|
let standaloneKeySuccess: any = true;
|
|
let categoriesSuccess: any = true;
|
|
let productsSuccess: any = true;
|
|
|
|
const posHiddenKey = await EncryptedStorage.getItem(
|
|
LEGACY_POS_HIDDEN_KEY
|
|
);
|
|
if (posHiddenKey) {
|
|
console.log('Attemping POS hidden key migration');
|
|
hiddenKeySuccess = await Storage.setItem(
|
|
POS_HIDDEN_KEY,
|
|
posHiddenKey
|
|
);
|
|
console.log(
|
|
'POS hidden key migration status',
|
|
hiddenKeySuccess
|
|
);
|
|
}
|
|
|
|
const posStandaloneKey = await EncryptedStorage.getItem(
|
|
LEGACY_POS_STANDALONE_KEY
|
|
);
|
|
if (posStandaloneKey) {
|
|
console.log('Attemping POS standalone key migration');
|
|
standaloneKeySuccess = await Storage.setItem(
|
|
POS_STANDALONE_KEY,
|
|
posStandaloneKey
|
|
);
|
|
console.log(
|
|
'POS standalone key migration status',
|
|
standaloneKeySuccess
|
|
);
|
|
}
|
|
|
|
const categories = await EncryptedStorage.getItem(
|
|
LEGACY_CATEGORY_KEY
|
|
);
|
|
if (categories) {
|
|
console.log('Attemping POS categories migration');
|
|
categoriesSuccess = await Storage.setItem(
|
|
CATEGORY_KEY,
|
|
categories
|
|
);
|
|
console.log(
|
|
'POS categories migration status',
|
|
categoriesSuccess
|
|
);
|
|
}
|
|
|
|
const products = await EncryptedStorage.getItem(
|
|
LEGACY_PRODUCT_KEY
|
|
);
|
|
if (products) {
|
|
console.log('Attemping POS products migration');
|
|
productsSuccess = await Storage.setItem(
|
|
PRODUCT_KEY,
|
|
products
|
|
);
|
|
console.log(
|
|
'POS products migration status',
|
|
productsSuccess
|
|
);
|
|
}
|
|
|
|
return (
|
|
hiddenKeySuccess &&
|
|
standaloneKeySuccess &&
|
|
categoriesSuccess &&
|
|
productsSuccess
|
|
);
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading POS data from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(posMigration);
|
|
|
|
// Units migration
|
|
const unitsMigration = (async () => {
|
|
try {
|
|
const units = await EncryptedStorage.getItem(LEGACY_UNIT_KEY);
|
|
if (units) {
|
|
console.log('Attemping units migration');
|
|
const writeSuccess = await Storage.setItem(UNIT_KEY, units);
|
|
console.log('Units migration status', writeSuccess);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading units data from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(unitsMigration);
|
|
|
|
// Hidden accounts migration
|
|
const hiddenAccountsMigration = (async () => {
|
|
try {
|
|
const accounts = await EncryptedStorage.getItem(
|
|
LEGACY_HIDDEN_ACCOUNTS_KEY
|
|
);
|
|
if (accounts) {
|
|
console.log('Attemping hidden accounts migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
HIDDEN_ACCOUNTS_KEY,
|
|
accounts
|
|
);
|
|
console.log(
|
|
'Hidden accounts migration status',
|
|
writeSuccess
|
|
);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading hidden accounts from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(hiddenAccountsMigration);
|
|
|
|
// Currency codes migration
|
|
const currencyCodesMigration = (async () => {
|
|
try {
|
|
const currencyCodes = await EncryptedStorage.getItem(
|
|
LEGACY_CURRENCY_CODES_KEY
|
|
);
|
|
if (currencyCodes) {
|
|
console.log('Attemping currency codes migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
CURRENCY_CODES_KEY,
|
|
currencyCodes
|
|
);
|
|
console.log(
|
|
'Currency codes migration status',
|
|
writeSuccess
|
|
);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading currency codes from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(currencyCodesMigration);
|
|
|
|
// Activity filters migration
|
|
const activityFiltersMigration = (async () => {
|
|
try {
|
|
const activityFilters = await EncryptedStorage.getItem(
|
|
LEGACY_ACTIVITY_FILTERS_KEY
|
|
);
|
|
if (activityFilters) {
|
|
console.log('Attemping activity filters migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
ACTIVITY_FILTERS_KEY,
|
|
activityFilters
|
|
);
|
|
console.log(
|
|
'Activity filters migration status',
|
|
writeSuccess
|
|
);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading activity filters from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(activityFiltersMigration);
|
|
|
|
// Embedded LND is backed up migration
|
|
const embeddedLndIsBackedUpMigration = (async () => {
|
|
try {
|
|
const embeddedLndIsBackedUp = await EncryptedStorage.getItem(
|
|
LEGACY_IS_BACKED_UP_KEY
|
|
);
|
|
if (embeddedLndIsBackedUp) {
|
|
console.log(
|
|
'Attemping Embedded LND is backed up migration'
|
|
);
|
|
const writeSuccess = await Storage.setItem(
|
|
IS_BACKED_UP_KEY,
|
|
embeddedLndIsBackedUp
|
|
);
|
|
console.log(
|
|
'Embedded LND is backed up migration status',
|
|
writeSuccess
|
|
);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading Embedded LND is backed up from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(embeddedLndIsBackedUpMigration);
|
|
|
|
// LSPS1 orders migration
|
|
const lsps1OrdersMigration = (async () => {
|
|
try {
|
|
const lsps1orders = await EncryptedStorage.getItem(
|
|
LEGACY_LSPS1_ORDERS_KEY
|
|
);
|
|
if (lsps1orders) {
|
|
console.log('Attemping LSPS1 orders migration');
|
|
const writeSuccess = await Storage.setItem(
|
|
LSPS_ORDERS_KEY,
|
|
lsps1orders
|
|
);
|
|
console.log('LSPS1 orders migration status', writeSuccess);
|
|
return writeSuccess;
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading LSPS1 orders from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(lsps1OrdersMigration);
|
|
|
|
// LNC migrations
|
|
const lncMigration = (async () => {
|
|
try {
|
|
const migrationPromises =
|
|
settings?.nodes.map(async (node: any) => {
|
|
if (node.implementation === 'lightning-node-connect') {
|
|
const baseKey = `${LNC_STORAGE_KEY}:${hash(
|
|
node.pairingPhrase
|
|
)}`;
|
|
const hostKey = `${baseKey}:host`;
|
|
|
|
const baseKeyData: string =
|
|
(await EncryptedStorage.getItem(baseKey)) ||
|
|
'{}';
|
|
|
|
console.log(
|
|
'Attemping LNC base key migration',
|
|
baseKey
|
|
);
|
|
const writeSuccess1 = await Storage.setItem(
|
|
baseKey,
|
|
JSON.parse(baseKeyData)
|
|
);
|
|
console.log(
|
|
'LNC base key migration status',
|
|
writeSuccess1
|
|
);
|
|
|
|
const hostKeyData = await EncryptedStorage.getItem(
|
|
hostKey
|
|
);
|
|
|
|
console.log(
|
|
'Attemping LNC host key migration',
|
|
baseKey
|
|
);
|
|
const writeSuccess2 = await Storage.setItem(
|
|
hostKey,
|
|
hostKeyData
|
|
);
|
|
console.log(
|
|
'LNC host key migration status',
|
|
writeSuccess2
|
|
);
|
|
|
|
return writeSuccess1 && writeSuccess2;
|
|
}
|
|
}) || [];
|
|
|
|
const results = await Promise.all(migrationPromises);
|
|
return results.every((result) => result !== false);
|
|
} catch (error) {
|
|
console.error(
|
|
'Error loading LNC data from encrypted storage',
|
|
error
|
|
);
|
|
return false;
|
|
}
|
|
})();
|
|
migrationTasks.push(lncMigration);
|
|
|
|
const results = await Promise.all(migrationTasks);
|
|
console.log('storageMigrationV2 completed!', results);
|
|
return results.every((result) => result === true);
|
|
}
|
|
|
|
public async migrateCashuSeedVersion(cashuStore: any) {
|
|
// cashuStore is passed as 'any' to avoid circular dependency issues
|
|
// but it's an instance of CashuStore
|
|
// TODO fix circular dependency
|
|
if (
|
|
cashuStore.settingsStore?.implementation === 'embedded-lnd' &&
|
|
cashuStore.seedVersion === undefined
|
|
) {
|
|
console.log('Migrating Cashu seed version to v1');
|
|
cashuStore.seedVersion = 'v1';
|
|
try {
|
|
await Storage.setItem(
|
|
`${cashuStore.getNodeDir()}-cashu-seed-version`,
|
|
'v1'
|
|
);
|
|
console.log('Cashu seed version migrated and saved as v1.');
|
|
} catch (error) {
|
|
console.error(
|
|
'Error saving migrated Cashu seed version:',
|
|
error
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async keychainCloudSyncMigration() {
|
|
try {
|
|
const hasMigrated = await EncryptedStorage.getItem(
|
|
KEYCHAIN_MIGRATION_KEY
|
|
);
|
|
|
|
if (hasMigrated !== 'true') {
|
|
console.log('Attempting keychain cloud sync migration...');
|
|
|
|
const settingsData = await this.migrateKey(STORAGE_KEY);
|
|
|
|
if (settingsData) {
|
|
settingsStore.isMigrating = true;
|
|
}
|
|
|
|
const migrationKeys = [
|
|
CONTACTS_KEY,
|
|
LAST_CHANNEL_BACKUP_STATUS,
|
|
LAST_CHANNEL_BACKUP_TIME,
|
|
ADDRESS_ACTIVATED_STRING,
|
|
HASHES_STORAGE_STRING,
|
|
POS_HIDDEN_KEY,
|
|
POS_STANDALONE_KEY,
|
|
CATEGORY_KEY,
|
|
PRODUCT_KEY,
|
|
UNIT_KEY,
|
|
HIDDEN_ACCOUNTS_KEY,
|
|
CURRENCY_CODES_KEY,
|
|
ACTIVITY_FILTERS_KEY,
|
|
IS_BACKED_UP_KEY,
|
|
LSPS_ORDERS_KEY,
|
|
SWAPS_KEY,
|
|
REVERSE_SWAPS_KEY,
|
|
SWAPS_RESCUE_KEY,
|
|
SWAPS_LAST_USED_KEY
|
|
];
|
|
|
|
for (const key of migrationKeys) {
|
|
await this.migrateKey(key);
|
|
}
|
|
|
|
const notesListJson = await this.migrateKey(NOTES_KEY);
|
|
if (notesListJson) {
|
|
const noteKeys = JSON.parse(notesListJson);
|
|
if (Array.isArray(noteKeys)) {
|
|
for (const noteKey of noteKeys) {
|
|
await this.migrateKey(noteKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (settingsData) {
|
|
const settings = JSON.parse(settingsData);
|
|
if (settings.nodes && Array.isArray(settings.nodes)) {
|
|
for (const node of settings.nodes) {
|
|
if (
|
|
node.implementation ===
|
|
'lightning-node-connect' &&
|
|
node.pairingPhrase
|
|
) {
|
|
const baseKey = `${LNC_STORAGE_KEY}:${hash(
|
|
node.pairingPhrase
|
|
)}`;
|
|
const hostKey = `${baseKey}:host`;
|
|
|
|
await this.migrateKey(baseKey);
|
|
await this.migrateKey(hostKey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await EncryptedStorage.setItem(KEYCHAIN_MIGRATION_KEY, 'true');
|
|
console.log(
|
|
'Keychain cloud sync migration completed successfully.'
|
|
);
|
|
}
|
|
|
|
const cashuMigration = await EncryptedStorage.getItem(
|
|
CASHU_MIGRATION_KEY
|
|
);
|
|
|
|
if (cashuMigration !== 'true') {
|
|
// Only run the Cashu keychain migration if the main keychain
|
|
// migration actually ran this time (hasMigrated was not 'true'
|
|
// when we entered). If the main migration was already done from
|
|
// a prior launch, there can't be unmigrated Cashu data in the
|
|
// old keychain — skip the expensive keychain reads.
|
|
if (hasMigrated !== 'true') {
|
|
console.log('Running Cashu Multi-Node Migration...');
|
|
|
|
const settingsJson = await Storage.getItem(STORAGE_KEY);
|
|
if (settingsJson) {
|
|
settingsStore.isMigrating = true;
|
|
const settings = JSON.parse(settingsJson);
|
|
if (settings.nodes && Array.isArray(settings.nodes)) {
|
|
for (const node of settings.nodes) {
|
|
if (node.implementation === 'embedded-lnd') {
|
|
const lndDir = node.lndDir || 'lnd';
|
|
await this.migrateCashuForNode(lndDir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
console.log('Cashu Migration Completed.');
|
|
} else {
|
|
console.log(
|
|
'Skipping Cashu migration - main keychain migration was already done.'
|
|
);
|
|
}
|
|
await EncryptedStorage.setItem(CASHU_MIGRATION_KEY, 'true');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during keychain cloud sync migration:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
const migrationsUtils = new MigrationsUtils();
|
|
export default migrationsUtils;
|