| … | … | @@ -533,7 +533,28 @@ export async function bootSwarmDevice ({ storage, deviceMnemonic, did, signingKe |
| 533 | 533 | // no I/O, so the ordering is free. |
| 534 | 534 | const sessions = makeSessions({ did, handle, appPasswordHash, jwtSecret }) |
| 535 | 535 | serving = serveDevice(device, controlSwarm, { canWrite, blobs, sessions }) |
| 536 | | await serving.ready() |
| 536 | // ⛔ TIME-BOXED, because `ready()` on BOTH surfaces is `Promise.all([topic.flushed(), …])` — the DHT |
| 537 | // ANNOUNCE completing, which is not the same thing as the surface working. `swarm.join()` has already |
| 538 | // registered the topics and connections are served meanwhile: on the phone five `device control |
| 539 | // connection`s were logged while boot was still sitting inside this await. On a workstation the flush |
| 540 | // returns in milliseconds; behind carrier-grade NAT it can take minutes or never resolve, and awaiting |
| 541 | // it wedged bootSwarmDevice → bootMultiDevice never returned → favela-android never sent `ready` and |
| 542 | // the app sat on its splash screen with a node that was, in fact, up (hyper-social #40). |
| 543 | // |
| 544 | // So boot proceeds and the announce finishes in the background. Nothing is skipped and nothing is |
| 545 | // swallowed silently: a timeout says so, naming the surface. The membrane projection is time-boxed on |
| 546 | // the same reasoning — the event-driven tick re-syncs on the next StateBase advance anyway, so a slow |
| 547 | // first pass costs a beat of freshness, never correctness. |
| 548 | const awaitOrContinue = async (what, promise, ms = 15000) => { |
| 549 | let timer = null |
| 550 | const timeout = new Promise((resolve) => { timer = setTimeout(() => resolve('__timeout__'), ms); if (timer && timer.unref) timer.unref() }) |
| 551 | const outcome = await Promise.race([promise.then(() => '__ok__', (e) => e), timeout]) |
| 552 | clearTimeout(timer) |
| 553 | if (outcome === '__timeout__') console.error(JSON.stringify({ msg: 'boot continued WITHOUT waiting for ' + what + ' — it is still in progress in the background; the surface already accepts connections', did, waitedMs: ms })) |
| 554 | else if (outcome !== '__ok__') console.error(JSON.stringify({ msg: what + ' failed (boot continues)', did, error: (outcome && outcome.message) || String(outcome) })) |
| 555 | return outcome === '__ok__' |
| 556 | } |
| 557 | await awaitOrContinue('the control-surface DHT announce', serving.ready()) |
| 537 | 558 | // projectRepoCore comes straight off the DID's config entry (bootMultiDevice spreads `...entry`), |
| 538 | 559 | // exactly like storeBackend. FALSE = the stateless virtual device: no membrane-repo Hyperbee is |
| 539 | 560 | // opened, record bytes exist only as the content-addressed files, and a consumer derives the repo |
| … | … | @@ -541,8 +562,10 @@ export async function bootSwarmDevice ({ storage, deviceMnemonic, did, signingKe |
| 541 | 562 | // nothing changes until a DID is explicitly flipped — rolled out one DID and one node at a time. |
| 542 | 563 | // Flipping back is instant: the existing core on disk is untouched, just not opened. |
| 543 | 564 | membraneServing = await serveMembrane(device, { corestore: membraneStore, swarm: controlSwarm, did, handle, sessions, blobs, projectRepoCore }) |
| 544 | | await membraneServing.ready() |
| 545 | | await membraneServing.sync().catch(() => {}) |
| 565 | await awaitOrContinue('the membrane DHT announce', membraneServing.ready()) |
| 566 | // sync() reads the device's __commit__ log through the StateBase. On a device that is not yet admitted |
| 567 | // (roster projected by another machine — #40 §6) that base may never advance, so this too is bounded. |
| 568 | await awaitOrContinue('the initial membrane projection', membraneServing.sync(), 30000) |
| 546 | 569 | } |
| 547 | 570 | |
| 548 | 571 | const close = async () => { |