fix(ios): return a retryable error when AsyncStorage protected data is unavailable#5
fix(ios): return a retryable error when AsyncStorage protected data is unavailable#5exodus-jee wants to merge 1 commit into
Conversation
7ca8098 to
6321d73
Compare
6321d73 to
2c4be4c
Compare
|
Closing this. The root fix for the proven symptom landed elsewhere: ExodusMovement/exodus-hydra#17212 gates the pay-user report on This native change is defense-in-depth, and right now it is not load-bearing, so I would rather not ship it as-is:
Happy to reopen once we (a) add a diagnostic that surfaces the NSError code/domain and confirm the before-first-unlock case is actually dominant, and (b) wire the JS-side retry/defer (the #39599 allowlist). The branch |
Summary
When the app touches AsyncStorage before the first unlock after a reboot (iOS can background-launch us via
remote-notification), the storage files are protection-locked and the read fails. Today that surfaces as a generic, permanent-lookingAsyncStorageError. This change detects that window viaUIApplication.isProtectedDataAvailableand returns a distinct, explicitly-retryable error instead, without changing the files' data protection class (encryption at rest is preserved).This replaces the earlier idea of relaxing the class to
NSFileProtectionNone(see #4 and the first revision of this PR). That approach was wrong on three counts, so it is dropped:CompleteUntilFirstUserAuthentication(nodefault-data-protectionentitlement), so fix(ios): relax AsyncStorage data protection to allow access while locked #4'sCompleteUntilFirstUserAuthenticationwas a no-op (thanks @exo-mv);_ensureSetupalready refuses to create a fresh manifest when the read errors, and_haveSetupis per-process, so it self-heals on the next unlocked launch. There was no permanent storage corruption to "fix" with a class change.What this actually does
In
_ensureSetup, before any file read, if protected data is unavailable, returnAsyncStorageError [AsyncStorage protected data unavailable, retry after device unlock]and do nothing else (no manifest read, no directory mutation). The flag is cached fromUIApplicationProtectedDataDidBecomeAvailable/WillBecomeUnavailablenotifications and read lock-free off the storage queue, so the main-thread-onlyUIApplicationAPI is never touched off the main thread. Guarded with#if !TARGET_OS_OSX.Net effect: the before-first-unlock window is correctly classified as transient/expected rather than reported as a hard failure, and we never weaken the protection class.
Honest scope (what this does and does not fix)
This is the native half. Full resolution of #39598 also needs:
protected data unavailablemessage as retryable/deferrable instorage-mobile(the #39599 allowlist) so callers back off until unlock instead of erroring.payUserAtom.get()unconditionally, so a locked background export hangs on deferred storage until the 15s timeout. That is the actual source of the "Export took longer than the maximum export timeout" symptom, and it is independent of the protection class. Root fix opened: ExodusMovement/exodus-hydra#17212 (gate the report onisLocked, likepayAuth/walletAccountsalready do).NSError.code/domain(RCTMakeErrordrops them;errors.jsmatches on message strings only), so we cannot distinguish data-protection (Cocoa 257) from disk-full (640) or directory-missing (4). A diagnostic that surfaces the code/domain should land so we can confirm the before-first-unlock case is actually dominant before relying on this.Security
No change to data protection class. Files keep the default
CompleteUntilFirstUserAuthentication(encrypted at rest, passcode-gated). No entitlement change.Release / test plan
Bumps to
1.17.11-exodus.6(exodus.5is already taken by a separate RN 0.85 release line). Native build plus on-device QA: with files created under the default class, background-wake the app before the first unlock after a reboot and confirm the operation returns the newprotected data unavailableretryable error (not a generic permanentAsyncStorageError), and that a normal unlocked launch reads/writes fine. Then bump@exodus/react-native-async-storagein exodus-mobile from1.17.11-exodus.4to1.17.11-exodus.6and wire the JS-side retry/defer (item 1 above).