| … | … | @@ -140,11 +140,19 @@ export async function boot (storage) { |
| 140 | 140 | // ⛔ AFTER `ready`, and non-fatal. Updating and signing are background concerns: a phone that cannot |
| 141 | 141 | // boot its atproto half must still show its own records. favela-android's rule — "a ready the UI |
| 142 | 142 | // cannot use is worse than a failure it can read". |
| 143 | try { | |
| 144 | state.atproto = await bootAtproto(node, { log: (m, d) => stage(m, d) }) | |
| 145 | } catch (e) { | |
| 146 | stage('atproto boot failed', { error: (e && e.message) || String(e) }) | |
| 147 | } | |
| 143 | // ⛔ NOT AWAITED, and this is the whole point. `bootAtproto` opens a corestore, a Hyperswarm and | |
| 144 | // joins a topic per identity — on a device that actually holds a DID it can take a long time or | |
| 145 | // never settle at all. Awaiting it here stopped everything BELOW from running, including the OTA | |
| 146 | // block that writes `app-update.json`. So the settings page kept rendering whatever the previous | |
| 147 | // boot had written: a stale version, and `staged !== running` for ever — a banner offering an | |
| 148 | // update that had in fact already been applied, with no way to clear it from the device. | |
| 149 | // | |
| 150 | // ⚠ The same class as the rule two lines up. Signing is a BACKGROUND concern, exactly like | |
| 151 | // updating: a phone that cannot boot its atproto half must still show its own records and must | |
| 152 | // still be able to say which build it is running. | |
| 153 | bootAtproto(node, { log: (m, d) => stage(m, d) }) | |
| 154 | .then((h) => { state.atproto = h }) | |
| 155 | .catch((e) => stage('atproto boot failed', { error: (e && e.message) || String(e) })) | |
| 148 | 156 | |
| 149 | 157 | // ⛔ START OTA, and note this is what makes `lib/host/ota.js` exist at all. It was copied here with |
| 150 | 158 | // the rest of the host layer and nothing imported it — so bare-pack, which walks the MODULE graph |
| … | … | @@ -1,6 +1,6 @@ |
| 1 | 1 | { |
| 2 | 2 | "name": "favela-surface-android", |
| 3 | "version": "0.0.28", | |
| 3 | "version": "0.0.29", | |
| 4 | 4 | "description": "favela-surface-android \u2014 a React Native shell over a bare-kit worklet running a full favela-peer node (markdown records, no atproto) with favela-surface rendering its pages in a WebView.", |
| 5 | 5 | "type": "module", |
| 6 | 6 | "private": true, |
| … | … | @@ -73,3 +73,23 @@ test('announceAll covers the namespace and the rooms the hand-rolled list missed |
| 73 | 73 | 'and so is the namespace — without this a shared registry is three private ones') |
| 74 | 74 | t.ok(rec.joined.includes(room), 'and the rooms, which the hand-rolled list also walked past') |
| 75 | 75 | }) |
| 76 | ||
| 77 | // ⛔ NOTHING IN boot() MAY BLOCK THE OTA STATE WRITE. `bootAtproto` opens a corestore, a swarm and a | |
| 78 | // topic per identity; awaiting it meant that on a device which actually held a DID, everything below | |
| 79 | // it never ran — including the write of app-update.json. The settings page then rendered the previous | |
| 80 | // boot's snapshot for ever: a stale version, and `staged !== running` permanently true, so the update | |
| 81 | // banner offered an update that had already been applied and could not be cleared from the device. | |
| 82 | // | |
| 83 | // Source-level for the same reason as the test above: the failure is an ordering one inside a | |
| 84 | // function that builds its own swarm, and a behavioural test cannot observe it without a DHT. | |
| 85 | test('boot does not await the atproto boot', (t) => { | |
| 86 | const src = fs.readFileSync(join(ROOT, 'lib/node.js'), 'utf8') | |
| 87 | t.absent(/await bootAtproto\(/.test(src), | |
| 88 | 'awaiting it stops the OTA state write, which is what tells settings which build is running') | |
| 89 | // ⚠ `[\s\S]*?` and not `[^)]*`: the call's own argument contains parentheses, so a | |
| 90 | // no-close-paren class stops inside it and the assertion fails on correct code. | |
| 91 | t.ok(/bootAtproto\([\s\S]{0,200}?\.then\(/.test(src), | |
| 92 | 'it still runs, and its result is still recorded') | |
| 93 | // And the write it must not block still happens. | |
| 94 | t.ok(/writeState\(\)/.test(src), 'boot still writes the update state') | |
| 95 | }) |