mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-07-22 07:48:28 +00:00
State management fixes
This commit is contained in:
5
firebase.json
Normal file
5
firebase.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"react-native": {
|
||||
"messaging_ios_auto_register_for_remote_messages": false
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export const MintProofsCounterModel = types
|
||||
|
||||
removeInFlightRequest(transactionId: number) {
|
||||
if (!isAlive(self)) {
|
||||
log.error('[removeInFlightRequest]', 'ProofsCounter is not alive', { keyset: self.keyset })
|
||||
log.error('[removeInFlightRequest]', 'ProofsCounter is not alive')
|
||||
return
|
||||
}
|
||||
const key = transactionId.toString()
|
||||
@@ -137,7 +137,7 @@ export const MintProofsCounterModel = types
|
||||
|
||||
clearAllInFlightRequests() {
|
||||
if (!isAlive(self)) {
|
||||
log.error('[clearAllInFlightRequests]', 'ProofsCounter is not alive', { keyset: self.keyset })
|
||||
log.error('[clearAllInFlightRequests]', 'ProofsCounter is not alive')
|
||||
return
|
||||
}
|
||||
const count = self.inFlightRequests.size
|
||||
@@ -176,7 +176,7 @@ export const MintProofsCounterModel = types
|
||||
|
||||
removeMeltCounterValue(transactionId: number) {
|
||||
if (!isAlive(self)) {
|
||||
log.error('[removeMeltCounterValue]', 'ProofsCounter is not alive', { keyset: self.keyset })
|
||||
log.error('[removeMeltCounterValue]', 'ProofsCounter is not alive')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export const MintProofsCounterModel = types
|
||||
|
||||
clearAllMeltCounterValues() {
|
||||
if (!isAlive(self)) {
|
||||
log.error('[clearAllMeltCounterValues]', 'ProofsCounter is not alive', { keyset: self.keyset })
|
||||
log.error('[clearAllMeltCounterValues]', 'ProofsCounter is not alive')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ export const MintModel = types
|
||||
const index = self.proofsCounters.findIndex(p => p.keyset === counter.keyset)
|
||||
|
||||
if(index !== -1) {
|
||||
self.proofsCounters.splice(index, 0)
|
||||
self.proofsCounters.splice(index, 1)
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
}
|
||||
},
|
||||
@@ -572,13 +572,14 @@ export const MintModel = types
|
||||
if(info.name.length > 0) {
|
||||
shortname = info.name
|
||||
}
|
||||
|
||||
self.shortname = shortname
|
||||
|
||||
} catch (e: any) {
|
||||
log.warn('[setShortname]', {error: e.message})
|
||||
self.shortname = shortname
|
||||
}
|
||||
|
||||
// Mint may have been removed while the network call was in flight
|
||||
if (!isAlive(self)) return
|
||||
|
||||
self.shortname = shortname
|
||||
}),
|
||||
setRandomColor() {
|
||||
self.color = getRandomIconColor()
|
||||
@@ -637,10 +638,7 @@ export const MintModel = types
|
||||
return counters || []
|
||||
},
|
||||
get allInFlightRequests() {
|
||||
const requests = self.proofsCounters // Get all counters as an array
|
||||
.flatMap((counter) => counter.inFlightRequests) // Combine all `inFlightRequests` arrays
|
||||
|
||||
return requests
|
||||
return self.proofsCounters.flatMap((counter) => counter.allInFlightRequests)
|
||||
},
|
||||
get balances(): MintBalance | undefined {
|
||||
const mintBalance: MintBalance | undefined = getRootStore(self).proofsStore.getMintBalance(self.mintUrl)
|
||||
|
||||
@@ -220,8 +220,8 @@ import {
|
||||
proofsByKeyset.set(proof.id, (proofsByKeyset.get(proof.id) || []).concat(proofNode))
|
||||
}
|
||||
|
||||
// Update counters only for non-pending proofs
|
||||
if (!isPending) {
|
||||
// Update counters only for newly received, spendable proofs
|
||||
if (!isPending && !isSpent) {
|
||||
for (const [keysetId, proofs] of proofsByKeyset) {
|
||||
const counter = mintInstance.getProofsCounterByKeysetId(keysetId)
|
||||
counter?.increaseProofsCounter(proofs.length)
|
||||
@@ -239,14 +239,12 @@ import {
|
||||
// Only call this when proofs are locally pending (ecash send, melt prepare, etc.)
|
||||
// Does NOT touch pendingByMintSecrets
|
||||
moveToPending(proofs: Proof[]) {
|
||||
Database.addOrUpdateProofs(proofs, true, false)
|
||||
const liveProofs = proofs.filter(p => isAlive(p))
|
||||
if (liveProofs.length === 0) return
|
||||
|
||||
for (const p of proofs) {
|
||||
if (!isAlive(p)) {
|
||||
log.error('[moveToPending]', 'Proof instance is not alive, aborting state update', { secret: p.secret })
|
||||
continue
|
||||
}
|
||||
Database.addOrUpdateProofs(liveProofs, true, false)
|
||||
|
||||
for (const p of liveProofs) {
|
||||
p.isPending = true
|
||||
p.isSpent = false
|
||||
}
|
||||
@@ -270,35 +268,31 @@ import {
|
||||
},
|
||||
|
||||
moveToSpent(proofs: Proof[]) {
|
||||
Database.addOrUpdateProofs(proofs, false, true)
|
||||
const liveProofs = proofs.filter(p => isAlive(p))
|
||||
if (liveProofs.length === 0) return
|
||||
|
||||
for (const p of proofs) {
|
||||
if (!isAlive(p)) {
|
||||
log.error('[moveToSpent]', 'Proof instance is not alive, aborting state update', { secret: p.secret })
|
||||
continue
|
||||
}
|
||||
Database.addOrUpdateProofs(liveProofs, false, true)
|
||||
|
||||
p.isPending = false
|
||||
p.isSpent = true
|
||||
for (const p of liveProofs) {
|
||||
p.isPending = false
|
||||
p.isSpent = true
|
||||
}
|
||||
// Automatically clean if any were in mint-pending list
|
||||
const secrets = new Set(proofs.map(p => p.secret))
|
||||
const secrets = new Set(liveProofs.map(p => p.secret))
|
||||
self.pendingByMintSecrets.replace(
|
||||
self.pendingByMintSecrets.filter(s => !secrets.has(s))
|
||||
self.pendingByMintSecrets.filter(s => !secrets.has(s))
|
||||
)
|
||||
},
|
||||
|
||||
revertToSpendable(proofs: Proof[]) {
|
||||
Database.addOrUpdateProofs(proofs, false, false)
|
||||
const liveProofs = proofs.filter(p => isAlive(p))
|
||||
if (liveProofs.length === 0) return
|
||||
|
||||
for (const p of proofs) {
|
||||
if (!isAlive(p)) {
|
||||
log.error('[revertToSpendable]', 'Proof instance is not alive, aborting state update', { secret: p.secret })
|
||||
continue
|
||||
}
|
||||
Database.addOrUpdateProofs(liveProofs, false, false)
|
||||
|
||||
p.isPending = false
|
||||
p.isSpent = false
|
||||
for (const p of liveProofs) {
|
||||
p.isPending = false
|
||||
p.isSpent = false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -72,14 +72,14 @@ export const TransactionModel = types
|
||||
},
|
||||
update(fields: Partial<Transaction>) {
|
||||
// log.trace('[update]', {fields})
|
||||
// Update multiple fields in database with a single query
|
||||
const updatedTransaction = Database.updateTransaction(self.id, fields)
|
||||
|
||||
if (!isAlive(self)) {
|
||||
log.error('[update]', 'Transaction instance is not alive, aborting state update', { id: self.id })
|
||||
log.error('[update]', 'Transaction instance is not alive, aborting update')
|
||||
return
|
||||
}
|
||||
|
||||
// Update multiple fields in database with a single query
|
||||
const updatedTransaction = Database.updateTransaction(self.id, fields)
|
||||
|
||||
// Update the model to keep store in sync
|
||||
Object.keys(updatedTransaction).forEach(key => {
|
||||
;(self as any)[key] = (updatedTransaction as any)[key]
|
||||
|
||||
@@ -34,9 +34,14 @@ export const ROOT_STORAGE_KEY = 'minibits-root-storage'
|
||||
|
||||
export async function setupRootStore(rootStore: RootStore) {
|
||||
let restoredState: any
|
||||
let _disposer: IDisposer
|
||||
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()
|
||||
@@ -53,6 +58,7 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
}
|
||||
|
||||
applySnapshot(rootStore, restoredState)
|
||||
snapshotApplied = true
|
||||
|
||||
const stateHydrated = performance.now()
|
||||
log.trace(`Hydrating rooStoreModel took ${stateHydrated - mmkvLoaded} ms.`, {caller: 'setupRootStore'})
|
||||
@@ -87,19 +93,25 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
_disposer()
|
||||
}
|
||||
|
||||
_disposer = onSnapshot(rootStore, snapshot => {
|
||||
MMKVStorage.save(ROOT_STORAGE_KEY, snapshot)
|
||||
})
|
||||
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
|
||||
try {
|
||||
log.info(`RootStore loaded from MMKV, version is: ${rootStore.version}`, {caller: 'setupRootStore'})
|
||||
if (snapshotApplied) {
|
||||
try {
|
||||
log.info(`RootStore loaded from MMKV, version is: ${rootStore.version}`, {caller: 'setupRootStore'})
|
||||
|
||||
if(rootStore.version < rootStoreModelVersion) {
|
||||
await _runMigrations(rootStore)
|
||||
if(rootStore.version < rootStoreModelVersion) {
|
||||
await _runMigrations(rootStore)
|
||||
}
|
||||
} catch (e: any) {
|
||||
log.error(Err.STORAGE_ERROR, e.message)
|
||||
}
|
||||
} catch (e: any) {
|
||||
log.error(Err.STORAGE_ERROR, e.message)
|
||||
}
|
||||
|
||||
const unsubscribe = () => {
|
||||
|
||||
Reference in New Issue
Block a user