Integrating Mithril localStorage backup with route state restoration and integrity verification
0 reputation · 08 Feb 2022, 19:15 UTC
0 reputation · 08 Feb 2022, 19:15 UTC
The goal is to restore a Mithril application’s UI state from a JSON backup stored in localStorage while simultaneously re‑establishing the current route and ensuring that the recovered data has not been corrupted. Developers must work within the ~5 MB origin limit of localStorage, avoid a flash of stale UI before the backup is applied, and perform integrity checks without incurring prohibitive performance penalties from deep virtual‑DOM equality checks.
What is the most efficient way to compute and compare a hash of the backup data during startup without blocking the initial render? How can route parameters be restored via m.route.set before the component tree re‑hydrates to prevent a UI flash? Should large backup objects be split or lazily loaded to stay within storage limits and reduce redraw latency?
35275 reputation · 08 Feb 2022, 19:28 UTC
The fastest, non‑blocking way to verify a backup is to compute an HMAC‑SHA256 with the Web Crypto API, compare it in constant time, and then restore the route before the component tree mounts. This avoids a flash of stale UI and stays well within the ~5 MB localStorage quota as long as the stored state remains small.
localStorage.getItem('mithril-route-backup')). It should contain a JSON string with two fields: payload (the route‑state JSON) and hmac (hex‑encoded HMAC‑SHA256).crypto.subtle.importKey to bring the secret key (embedded in the build as a raw buffer) into a CryptoKey object.crypto.subtle.sign('HMAC', key, textEncoder.encode(payload)). This returns a Promise that resolves with the signature buffer.hmac using a constant‑time function (e.g., sjcl.misc.cca or a simple loop that accumulates XOR differences).payload with JSON.parse and feed the resulting object to Mithril’s route state (via onmatch or oninit of the root component). If it fails, log a warning, discard the backup, and start with the default state.{path: '/user/123', params: {id: '123'}}).m.mount, call m.route.set(route.path, route.params, {replace: true}). This updates the URL and Mithril’s internal route state without triggering a render because the view has not yet been attached to the DOM.m.mount(document.getElementById('app'), rootComponent)). The root component’s onmatch (or oninit) will receive the restored state directly, so the first render reflects the correct UI.If the route‑state JSON approaches several megabytes, consider:
For most applications, keeping only the essential UI route state (navigation, filters, pagination) keeps the backup well under the limit, making splitting unnecessary.
Use comments to ask for clarification. Post a solution as an answer.
6,640 reputation · 08 Feb 2022, 19:53 UTC
One clarification worth adding to the HMAC approach: compute the digest over the exact stored payload string, not over JSON.stringify(JSON.parse(payload)). JavaScript object key order is insertion order, so a re-serialized object can produce different bytes than the original even when the data is logically identical — and your integrity check will report corruption that never happened.
Two related points:
JSON.parse it after verification succeeds. This also avoids paying parse cost for data you're about to discard.m.route.set(path, params, {replace: true}) before m.mount() runs (Mithril v2.x). route.set triggers its own redraw, so calling it after mount can produce exactly the stale-UI flash you're trying to avoid. Verify in your target version, since v1 and v2 differ here.For quota safety, prefixing keys per route (e.g., backup:/user/123) makes lazy per-route loading straightforward — fetch and verify only the segment matching the restored route instead of the whole snapshot.