diff options
Diffstat (limited to 'source/app/AppState.mc')
| -rw-r--r-- | source/app/AppState.mc | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/source/app/AppState.mc b/source/app/AppState.mc new file mode 100644 index 0000000..05656e2 --- /dev/null +++ b/source/app/AppState.mc @@ -0,0 +1,114 @@ +// Author: cirakg & claude ai +// Summary: The app's shared state: options, the vault, the code cache, start-up errors and the crypto self-test. +// +// Created in getInitialView (never in glance mode, which has too little +// memory for the vault). Views reach the vault and options through here. + +import Toybox.Application; +import Toybox.Lang; +import Toybox.Time; + +module AppState { + + const SELF_TEST_KEY = "st"; + const SECONDS_PER_DAY = 86400; + + var options as Options = new Options(); + var vault as Vault? = null; + var codes as CodeCache? = null; + // A message that replaces the whole app (newer data, damaged metadata, + // failed self-test), or null. + var startError as String? = null; + // Codes are shown only after the self-test passed (README 7.10). + var cryptoReady as Boolean = false; + var selfTestSummary as String = "Not run yet"; + + // Load options and open the vault. Errors end up in startError. + function start() as Void { + options = new Options(); + options.load(); + if (TestMode.ON) { + Fixtures.resetStore(); + } + try { + var v = new Vault(new Persist.AppStorageBackend(), new TimeSource()); + v.eraseAfterFailures = options.eraseAfterFailures; + v.session.autoLockMs = TestMode.ON ? TestMode.AUTO_LOCK_MS : options.autoLockMs; + vault = v; + codes = new CodeCache(v); + if (TestMode.ON) { + Fixtures.load(v); + } + } catch (e instanceof VaultError) { + startError = e.code == VaultError.NEWER_SCHEMA + ? "Data from a newer version of OTP Wallet. Update the app." + : "Stored data is damaged. Reinstall the app to start over."; + } + } + + // The vault; only call after start() succeeded. + function theVault() as Vault { + return vault as Vault; + } + + function theCodes() as CodeCache { + return codes as CodeCache; + } + + // Run the known-answer self-test on the first start of each version and + // then once a day. Returns false (and sets startError) when it failed. + function runSelfTestIfDue() as Boolean { + var day = (Clock.nowUnix(0) / SECONDS_PER_DAY).toNumber(); + var last = Application.Storage.getValue(SELF_TEST_KEY); + var due = true; + if (last instanceof Array && last.size() == 2 && last[0] instanceof String && last[1] instanceof Number) { + due = !(last[0] as String).equals(AppInfo.VERSION) || (last[1] as Number) != day; + } + if (!due) { + selfTestSummary = "Passed (today)"; + cryptoReady = true; + return true; + } + var failed = SelfTest.run(); + if (failed != null) { + selfTestSummary = "FAILED: " + failed; + startError = "Crypto self-test failed on this watch (" + failed + "). Codes are not shown."; + return false; + } + Application.Storage.setValue(SELF_TEST_KEY, [AppInfo.VERSION, day] as Array<Application.Storage.ValueType>); + selfTestSummary = "Passed"; + cryptoReady = true; + return true; + } + + // Note user activity (restarts the auto-lock timeout). + function activity() as Void { + var v = vault; + if (v != null) { + v.session.touch(); + } + } + + // Lock if the inactivity timeout passed. True when this call locked. + function checkAutoLock() as Boolean { + var v = vault; + if (v != null && v.session.checkExpiry()) { + theCodes().clear(); + return true; + } + return false; + } + + // Lock now and forget every computed code. + function lock() as Void { + var v = vault; + if (v != null) { + v.lock(); + theCodes().clear(); + } + } + + function now() as Long { + return Clock.nowUnix(options.timeOffset); + } +} |
