mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
156 lines
5.0 KiB
TypeScript
156 lines
5.0 KiB
TypeScript
/**
|
|
* This file is where we do "rehydration" of your RootStore from AsyncStorage.
|
|
* This lets you persist your state between app launches.
|
|
*
|
|
* Navigation state persistence is handled in navigationUtilities.tsx.
|
|
*
|
|
* Note that Fast Refresh doesn't play well with this file, so if you edit this,
|
|
* do a full refresh of your app instead.
|
|
*
|
|
* @refresh reset
|
|
*/
|
|
import {
|
|
applySnapshot,
|
|
IDisposer,
|
|
onSnapshot,
|
|
} from 'mobx-state-tree'
|
|
import * as Sentry from '@sentry/react-native'
|
|
import type { RootStore } from '../RootStore'
|
|
import { MMKVStorage } from '../../services'
|
|
import { log } from '../../services/logService'
|
|
import { rootStoreModelVersion } from '../RootStore'
|
|
import AppError, { Err } from '../../utils/AppError'
|
|
|
|
|
|
/**
|
|
* The key we'll be saving our state as within storage.
|
|
*/
|
|
|
|
export const ROOT_STORAGE_KEY = 'minibits-root-storage'
|
|
|
|
/**
|
|
* Setup the root state.
|
|
*/
|
|
|
|
export async function setupRootStore(rootStore: RootStore) {
|
|
let restoredState: any
|
|
let _disposer: IDisposer | undefined
|
|
// let latestSnapshot: any
|
|
|
|
// Guards the onSnapshot installation below. If applySnapshot throws, rootStore
|
|
// stays at empty defaults. Installing the listener in that state would save those
|
|
// defaults back to MMKV on the first mutation, silently overwriting valid data.
|
|
let snapshotApplied = false
|
|
|
|
try {
|
|
// load the last known state from storage
|
|
const start = performance.now()
|
|
restoredState = MMKVStorage.load(ROOT_STORAGE_KEY) || {}
|
|
const mmkvLoaded = performance.now()
|
|
const dataSize = Buffer.byteLength(JSON.stringify(restoredState), 'utf8')
|
|
|
|
// log.trace({restoredState})
|
|
log.trace(`Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`, {caller: 'setupRootStore'})
|
|
|
|
// temp dirty migration of proofStore from array to map
|
|
if(restoredState?.proofsStore?.proofs && Array.isArray(restoredState.proofsStore.proofs)) {
|
|
restoredState.proofsStore.proofs = {}
|
|
}
|
|
|
|
applySnapshot(rootStore, restoredState)
|
|
snapshotApplied = true
|
|
|
|
const stateHydrated = performance.now()
|
|
log.trace(`Hydrating rooStoreModel took ${stateHydrated - mmkvLoaded} ms.`, {caller: 'setupRootStore'})
|
|
|
|
const {proofsStore, walletProfileStore, authStore, userSettingsStore, transactionsStore} = rootStore
|
|
|
|
if(walletProfileStore.walletId) {
|
|
Sentry.setUser({ id: walletProfileStore.walletId })
|
|
}
|
|
|
|
if(userSettingsStore.isOnboarded) {
|
|
// hydrate auth tokens to model from keychain
|
|
await authStore.loadTokensFromKeyChain()
|
|
}
|
|
|
|
// hydrate unspent and pending ecash proofs to model from database
|
|
await proofsStore.loadProofsFromDatabase()
|
|
// hydrate last transactions from database
|
|
await transactionsStore.loadRecentFromDatabase()
|
|
|
|
const proofsLoaded = performance.now()
|
|
log.trace(`Loading proofs and transactions from DB and hydrating took ${proofsLoaded - stateHydrated} ms.`, {
|
|
caller: 'setupRootStore'
|
|
})
|
|
|
|
} catch (e: any) {
|
|
log.error(Err.STORAGE_ERROR, {message: e.message, params: e.params, caller: 'setupRootStore'})
|
|
}
|
|
|
|
// stop tracking state changes if we've already setup
|
|
if (_disposer) {
|
|
_disposer()
|
|
}
|
|
|
|
if (snapshotApplied) {
|
|
_disposer = onSnapshot(rootStore, snapshot => {
|
|
MMKVStorage.save(ROOT_STORAGE_KEY, snapshot)
|
|
})
|
|
} else {
|
|
log.error('[setupRootStore]', 'State restore failed — skipping onSnapshot to preserve MMKV data', {caller: 'setupRootStore'})
|
|
}
|
|
|
|
// run migrations if needed, needs to be after onSnapshot to be persisted
|
|
if (snapshotApplied) {
|
|
try {
|
|
log.info(`RootStore loaded from MMKV, version is: ${rootStore.version}`, {caller: 'setupRootStore'})
|
|
|
|
if(rootStore.version < rootStoreModelVersion) {
|
|
await _runMigrations(rootStore)
|
|
}
|
|
} catch (e: any) {
|
|
log.error(Err.STORAGE_ERROR, e.message)
|
|
}
|
|
}
|
|
|
|
const unsubscribe = () => {
|
|
_disposer()
|
|
_disposer = undefined
|
|
}
|
|
|
|
return {rootStore, restoredState, unsubscribe}
|
|
}
|
|
|
|
/**
|
|
* Migrations code to execute based on code and on device model version.
|
|
*/
|
|
|
|
async function _runMigrations(rootStore: RootStore) {
|
|
const {
|
|
userSettingsStore,
|
|
mintsStore,
|
|
transactionsStore,
|
|
} = rootStore
|
|
|
|
let currentVersion = rootStore.version
|
|
|
|
try {
|
|
if(currentVersion < 29) {
|
|
log.trace(`Starting rootStore migrations from version v${currentVersion} -> v29`)
|
|
|
|
transactionsStore.addRecentByUnit()
|
|
|
|
rootStore.setVersion(rootStoreModelVersion)
|
|
log.info(`Completed rootStore migrations to the version v${rootStoreModelVersion}`, {caller: '_runMigrations'} )
|
|
}
|
|
} catch (e: any) {
|
|
throw new AppError(
|
|
Err.STORAGE_ERROR,
|
|
'Error when executing rootStore migrations',
|
|
e.message,
|
|
)
|
|
}
|
|
|
|
}
|