OverviewFilesCommitsIssuesDocsReleasesPeers

p2pbuilders commits / 812f29f

web: never strand the boot splash — watchdog + boot error surfacing + quiet crypto fallback
"connecting to peers…" could sit forever with no explanation: - file:// opens: browsers block ES-module scripts, so app.js never runs. An inline classic script (which file:// does execute) now replaces the splash with instructions to serve the app (dev server or PearBrowser). - any boot() failure previously hung the splash silently; it now surfaces the error on the splash and in the console. - non-module load stalls on http get a "check the console" hint after 10s. - crypto.js no longer attempts import('node:crypto') in browsers — on insecure origins (no SubtleCrypto) it just produced a CORS console error before landing in the same 'none' fallback. Verified in headless Chromium: localhost and LAN-IP origins boot with zero console errors; file:// shows the guidance. Full browser E2E still 44/44, engine checks 28/28. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QAqfE2dpo3vsVd9BorLY6w
812f29fa737636b2cd364da38fa98d2625a3fa24
by Claude <noreply@anthropic.com> · 2026-07-29T12:09:54.000Z

Changed files (3) +38 -2

web/index.html +25 -0
……@@ -14,6 +14,31 @@
1414 <div class="boot-name">p2pbuilders</div>
1515 <div class="boot-sub">connecting to peers…</div>
1616 </div>
17 <script>
18 // Boot watchdog (classic script: runs even when module loading is blocked).
19 // Browsers refuse ES modules on file:// — the app never starts and the
20 // splash would sit there forever. Explain instead of hanging.
21 (function () {
22 var explain = function (html) {
23 var sub = document.querySelector('.boot .boot-sub')
24 if (sub) sub.innerHTML = html
25 }
26 if (location.protocol === 'file:') {
27 setTimeout(function () {
28 if (!document.querySelector('.boot')) return
29 explain('this app can&#39;t run from a file:// URL (browsers block module scripts).<br>' +
30 'serve it instead:<br><code>cd web &amp;&amp; node dev-server.mjs</code><br>' +
31 'then open <code>http://localhost:8778</code> — or open the hyper:// link in PearBrowser.')
32 }, 1500)
33 } else {
34 setTimeout(function () {
35 if (!document.querySelector('.boot')) return
36 explain('still starting… this usually means a script failed to load.<br>' +
37 'check the browser console (F12) for errors, then hard-refresh.')
38 }, 10000)
39 }
40 })()
41 </script>
1742 <script type="module" src="js/app.js"></script>
1843</body>
1944</html>
web/js/app.js +10 -1
……@@ -457,6 +457,15 @@ async function seedDemo () {
457457 toast('demo ready'); location.hash = '#/'; route()
458458}
459459
460// A boot failure must never strand the splash screen silently.
461function bootSafe () {
462 boot().catch((err) => {
463 console.error('boot failed:', err)
464 const sub = document.querySelector('.boot-sub')
465 if (sub) sub.innerHTML = `failed to start: ${esc(err && err.message ? err.message : String(err))}<br>see the browser console for details.`
466 })
467}
468
460469if (typeof window !== 'undefined') window.__p2pb = { get data () { return data }, get sync () { return sync }, route }
461if (typeof document !== 'undefined') { if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot() }
470if (typeof document !== 'undefined') { if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bootSafe); else bootSafe() }
462471export { boot }
web/js/crypto.js +3 -1
……@@ -35,8 +35,10 @@ async function detect () {
3535 if (okk) { backend = 'subtle'; return }
3636 }
3737 } catch {}
38 // Fall back to node:crypto.
38 // Fall back to node:crypto — never in a browser, where the import can't
39 // succeed and only produces a scary CORS/404 console error.
3940 try {
41 if (typeof window !== 'undefined') { backend = 'none'; return }
4042 const mod = await import('node:crypto')
4143 nodeCrypto = mod.default || mod
4244 const { privateKey, publicKey } = nodeCrypto.generateKeyPairSync('ed25519')