| … | … | @@ -23,6 +23,10 @@ |
| 23 | 23 | // ⚠ The HTTP server below is a genuine boundary: a WebView is a browser and speaks HTTP. It binds |
| 24 | 24 | // loopback only, and serves the same pages a desktop gets. |
| 25 | 25 | |
| 26 | // ⛔ FIRST, before anything that reaches @noble via the atproto stack. Those modules use TextEncoder |
| 27 | // at module scope and Bare has no such global, so the import fails with "TextEncoder is not defined" |
| 28 | // — a shim loaded after the fact is loaded too late. favela-android does the same thing one line up. |
| 29 | import 'hyper-proto-peer/lib/bare-globals.js' |
| 26 | 30 | import fs from 'bare-fs' |
| 27 | 31 | import process from 'bare-process' |
| 28 | 32 | import { join } from 'bare-path' |
| … | … | @@ -68,7 +72,40 @@ let lastOtaNote = null |
| 68 | 72 | * socket server itself calls. |
| 69 | 73 | */ |
| 70 | 74 | export function inProcessClient (node) { |
| 71 | | return { call: (cmd, args = {}) => dispatch(node, cmd, args) } |
| 75 | return { |
| 76 | call: async (cmd, args = {}) => { |
| 77 | // ⛔ THE WORKLET OWNS THE COMPOSITION, and it has to: favela-peer's own boundary test forbids |
| 78 | // the daemon importing anything `atproto-`, and hyper-proto-peer's mint reaches |
| 79 | // atproto-identity. So the atproto ops cannot live in `dispatch` — but they can live HERE, |
| 80 | // where the two halves are already in one process, and the surface calls them the same way it |
| 81 | // calls everything else. Neither package learns about the other. |
| 82 | if (ATPROTO_OPS[cmd]) return ATPROTO_OPS[cmd](node, args) |
| 83 | return dispatch(node, cmd, args) |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | // ⛔ MINTING IS NOT SUBMITTING. `mintIdentity` never touches the network — submitting the genesis to |
| 89 | // the PLC directory is "a PERMANENT, PUBLIC, IRREVERSIBLE append to a global log" and is a separate |
| 90 | // call for that reason. A mint that cannot reach the directory still yields a working P2P identity, |
| 91 | // and the op can be re-derived from the mnemonic and submitted later. |
| 92 | // |
| 93 | // ⛔ AND IT IS P2P-ONLY: no handle, no PDS. hyper-proto-peer's mint records why — "creating an |
| 94 | // identity and joining someone's infrastructure are different decisions, and requiring both at once |
| 95 | // is what made minting undecidable". The handle and service endpoint arrive later as a PLC update. |
| 96 | const ATPROTO_OPS = { |
| 97 | 'did-mint': async (node, a) => { |
| 98 | const { mintIdentity } = await import('hyper-proto-peer/lib/mint.js') |
| 99 | const r = await mintIdentity({ rootMnemonic: a.mnemonic || null, onStep: () => {} }) |
| 100 | // ⚠ The root mnemonic comes back and is NOT stored here. hpp's mint is explicit: minting on the |
| 101 | // device is the one moment the recovery root exists there, so it is RETURNED for the caller to |
| 102 | // show once and nothing persists it in the config. Its own test asserts that. |
| 103 | return { did: r.did, mnemonic: r.rootMnemonic, device: r.devicePublicKey } |
| 104 | }, |
| 105 | 'did-submit': async (node, a) => { |
| 106 | const { submitMintedGenesis } = await import('hyper-proto-peer/lib/mint.js') |
| 107 | return submitMintedGenesis({ did: a.did }) |
| 108 | } |
| 72 | 109 | } |
| 73 | 110 | |
| 74 | 111 | /** |
| … | … | @@ -271,7 +308,15 @@ export async function boot (storage) { |
| 271 | 308 | // truth for the same three fields — favela-android's note, and the reason this is a file. |
| 272 | 309 | const writeState = () => { |
| 273 | 310 | let staged = null |
| 274 | | try { staged = (JSON.parse(fs.readFileSync(join(storage, 'bundles', 'current.json'), 'utf8')) || {}).treeHash } catch {} |
| 311 | let stagedBuiltAt = null |
| 312 | try { |
| 313 | const cur = JSON.parse(fs.readFileSync(join(storage, 'bundles', 'current.json'), 'utf8')) || {} |
| 314 | staged = cur.treeHash |
| 315 | // ⛔ THE STAGED BUNDLE'S BUILD TIME, because the page cannot decide without it. chooseBundle |
| 316 | // prefers the ASSET when the app is newer, so a staged-but-older bundle will never be |
| 317 | // applied — and a banner offering to restart for it is a button that does nothing, for ever. |
| 318 | stagedBuiltAt = cur.builtAt || null |
| 319 | } catch {} |
| 275 | 320 | // ⛔ THE VERSION IS THE HALF A PERSON CAN ACT ON. Two tree hashes tell you something changed; |
| 276 | 321 | // they do not tell you WHICH build you are running, so "is my update installed?" stayed |
| 277 | 322 | // unanswerable from the device. `version` is what a release page shows. |
| … | … | @@ -280,6 +325,7 @@ export async function boot (storage) { |
| 280 | 325 | available: !!ota, |
| 281 | 326 | version: VERSION, |
| 282 | 327 | builtAt: BUILT_AT, |
| 328 | stagedBuiltAt, |
| 283 | 329 | note: lastOtaNote |
| 284 | 330 | } |
| 285 | 331 | try { fs.writeFileSync(join(storage, STATE_FILE), JSON.stringify(st)) } catch { /* status only */ } |