@@ -381,6 +381,20 @@ function getAccountsBackupRecoveryCandidates(path: string): string[] {
381381 return candidates ;
382382}
383383
384+ function normalizeStorageComparisonPath ( path : string ) : string {
385+ const resolved = resolvePath ( path ) ;
386+ if ( process . platform !== "win32" ) {
387+ return resolved ;
388+ }
389+ return resolved . replaceAll ( "\\" , "/" ) . toLowerCase ( ) ;
390+ }
391+
392+ function areEquivalentStoragePaths ( left : string , right : string ) : boolean {
393+ return (
394+ normalizeStorageComparisonPath ( left ) === normalizeStorageComparisonPath ( right )
395+ ) ;
396+ }
397+
384398async function getAccountsBackupRecoveryCandidatesWithDiscovery (
385399 path : string ,
386400) : Promise < string [ ] > {
@@ -813,8 +827,13 @@ function latestValidSnapshot(
813827 snapshots : BackupSnapshotMetadata [ ] ,
814828) : BackupSnapshotMetadata | undefined {
815829 return snapshots
816- . filter ( ( snapshot ) => snapshot . valid )
817- . sort ( ( left , right ) => ( right . mtimeMs ?? 0 ) - ( left . mtimeMs ?? 0 ) ) [ 0 ] ;
830+ . map ( ( snapshot , index ) => ( { snapshot, index } ) )
831+ . filter ( ( { snapshot } ) => snapshot . valid )
832+ . sort (
833+ ( left , right ) =>
834+ ( right . snapshot . mtimeMs ?? 0 ) - ( left . snapshot . mtimeMs ?? 0 ) ||
835+ left . index - right . index ,
836+ ) [ 0 ] ?. snapshot ;
818837}
819838
820839function buildMetadataSection (
@@ -1761,8 +1780,9 @@ async function loadAccountsFromJournal(
17611780
17621781async function loadAccountsInternal (
17631782 persistMigration : ( ( storage : AccountStorageV3 ) => Promise < void > ) | null ,
1783+ storagePath = getStoragePath ( ) ,
17641784) : Promise < AccountStorageV3 | null > {
1765- const path = getStoragePath ( ) ;
1785+ const path = storagePath ;
17661786 const resetMarkerPath = getIntentionalResetMarkerPath ( path ) ;
17671787 await cleanupStaleRotatingBackupArtifacts ( path ) ;
17681788 const migratedLegacyStorage = persistMigration
@@ -1926,8 +1946,11 @@ async function loadAccountsInternal(
19261946 }
19271947}
19281948
1929- async function saveAccountsUnlocked ( storage : AccountStorageV3 ) : Promise < void > {
1930- const path = getStoragePath ( ) ;
1949+ async function saveAccountsUnlocked (
1950+ storage : AccountStorageV3 ,
1951+ storagePath = getStoragePath ( ) ,
1952+ ) : Promise < void > {
1953+ const path = storagePath ;
19311954 const resetMarkerPath = getIntentionalResetMarkerPath ( path ) ;
19321955 const uniqueSuffix = `${ Date . now ( ) } .${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } ` ;
19331956 const tempPath = `${ path } .${ uniqueSuffix } .tmp` ;
@@ -2078,18 +2101,25 @@ export async function withAccountStorageTransaction<T>(
20782101 return withStorageLock ( async ( ) => {
20792102 const storagePath = getStoragePath ( ) ;
20802103 const state = {
2081- snapshot : await loadAccountsInternal ( saveAccountsUnlocked ) ,
2082- storagePath,
2104+ snapshot : await loadAccountsInternal (
2105+ ( storage ) => saveAccountsUnlocked ( storage , storagePath ) ,
2106+ storagePath ,
2107+ ) ,
20832108 active : true ,
2109+ storagePath,
20842110 } ;
20852111 const current = state . snapshot ;
20862112 const persist = async ( storage : AccountStorageV3 ) : Promise < void > => {
2087- await saveAccountsUnlocked ( storage ) ;
2113+ await saveAccountsUnlocked ( storage , storagePath ) ;
20882114 state . snapshot = storage ;
20892115 } ;
2090- return transactionSnapshotContext . run ( state , ( ) =>
2091- handler ( current , persist ) ,
2092- ) ;
2116+ return transactionSnapshotContext . run ( state , async ( ) => {
2117+ try {
2118+ return await handler ( current , persist ) ;
2119+ } finally {
2120+ state . active = false ;
2121+ }
2122+ } ) ;
20932123 } ) ;
20942124}
20952125
@@ -2105,9 +2135,12 @@ export async function withAccountAndFlaggedStorageTransaction<T>(
21052135 return withStorageLock ( async ( ) => {
21062136 const storagePath = getStoragePath ( ) ;
21072137 const state = {
2108- snapshot : await loadAccountsInternal ( saveAccountsUnlocked ) ,
2109- storagePath,
2138+ snapshot : await loadAccountsInternal (
2139+ ( storage ) => saveAccountsUnlocked ( storage , storagePath ) ,
2140+ storagePath ,
2141+ ) ,
21102142 active : true ,
2143+ storagePath,
21112144 } ;
21122145 const current = state . snapshot ;
21132146 const persist = async (
@@ -2116,13 +2149,13 @@ export async function withAccountAndFlaggedStorageTransaction<T>(
21162149 ) : Promise < void > => {
21172150 const previousAccounts = cloneAccountStorageForPersistence ( state . snapshot ) ;
21182151 const nextAccounts = cloneAccountStorageForPersistence ( accountStorage ) ;
2119- await saveAccountsUnlocked ( nextAccounts ) ;
2152+ await saveAccountsUnlocked ( nextAccounts , storagePath ) ;
21202153 try {
21212154 await saveFlaggedAccountsUnlocked ( flaggedStorage ) ;
21222155 state . snapshot = nextAccounts ;
21232156 } catch ( error ) {
21242157 try {
2125- await saveAccountsUnlocked ( previousAccounts ) ;
2158+ await saveAccountsUnlocked ( previousAccounts , storagePath ) ;
21262159 state . snapshot = previousAccounts ;
21272160 } catch ( rollbackError ) {
21282161 const combinedError = new AggregateError (
@@ -2141,9 +2174,13 @@ export async function withAccountAndFlaggedStorageTransaction<T>(
21412174 throw error ;
21422175 }
21432176 } ;
2144- return transactionSnapshotContext . run ( state , ( ) =>
2145- handler ( current , persist ) ,
2146- ) ;
2177+ return transactionSnapshotContext . run ( state , async ( ) => {
2178+ try {
2179+ return await handler ( current , persist ) ;
2180+ } finally {
2181+ state . active = false ;
2182+ }
2183+ } ) ;
21472184 } ) ;
21482185}
21492186
@@ -2477,22 +2514,27 @@ export async function exportAccounts(
24772514 beforeCommit ?: ( resolvedPath : string ) => Promise < void > | void ,
24782515) : Promise < void > {
24792516 const resolvedPath = resolvePath ( filePath ) ;
2480- const currentStoragePath = getStoragePath ( ) ;
2517+ const activeStoragePath = getStoragePath ( ) ;
24812518
24822519 if ( ! force && existsSync ( resolvedPath ) ) {
24832520 throw new Error ( `File already exists: ${ resolvedPath } ` ) ;
24842521 }
24852522
24862523 const transactionState = transactionSnapshotContext . getStore ( ) ;
2487- const storage =
2524+ if (
24882525 transactionState ?. active &&
2489- transactionState . storagePath === currentStoragePath
2490- ? transactionState . snapshot
2491- : transactionState ?. active
2492- ? await loadAccountsInternal ( saveAccountsUnlocked )
2493- : await withAccountStorageTransaction ( ( current ) =>
2494- Promise . resolve ( current ) ,
2495- ) ;
2526+ ! areEquivalentStoragePaths ( transactionState . storagePath , activeStoragePath )
2527+ ) {
2528+ throw new Error (
2529+ `Export blocked by storage path mismatch: transaction path is ` +
2530+ `${ transactionState . storagePath } , active path is ${ activeStoragePath } ` ,
2531+ ) ;
2532+ }
2533+ const storage = transactionState ?. active
2534+ ? transactionState . snapshot
2535+ : await withAccountStorageTransaction ( ( current ) =>
2536+ Promise . resolve ( current ) ,
2537+ ) ;
24962538 if ( ! storage || storage . accounts . length === 0 ) {
24972539 throw new Error ( "No accounts to export" ) ;
24982540 }
0 commit comments