diff options
Diffstat (limited to 'src/app.js')
| -rw-r--r-- | src/app.js | 32 |
1 files changed, 32 insertions, 0 deletions
@@ -585,6 +585,15 @@ let _publicChatMode = false; // welcome screen is the one screen that must never be guessed at. The boot // shell holds the frame for those few ms instead; see the dispatch below. let _bootDeciding = true; +// Boot phases for the crash-loop guard (build.js BOOT_GUARD counts page +// starts): the app notes the phase it reaches, and clears the count once it +// has lived eight seconds or leaves cleanly. A page iOS kills while opening +// does neither, so the third start in a row opens in safe mode. +export function bootPhase(p) { try { localStorage.setItem('coinos-boot-phase', p); } catch {} } +function bootSettled() { try { localStorage.removeItem('coinos-boot'); } catch {} } +export const safeBoot = () => !!(typeof window !== 'undefined' && window.__safeBoot); +document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') bootSettled(); }); +addEventListener('pagehide', bootSettled); // Belt and braces: a boot path that returns early (or throws) must not leave // the welcome screen held back forever. Nothing legitimate is still deciding // four seconds in — the store wait alone caps at 800ms. @@ -4478,6 +4487,7 @@ function walletScreen() { brandHeader(true), h('div', { class: 'mt16' }, balanceCard()), ui.offlineFallback && wallet.offline ? offlineBanner() : null, + safeBootNotice(), backupNotice(), ...featureAll('walletNotices'), tabsBar(), @@ -4536,6 +4546,24 @@ function backupNotice() { } }, t('backupReminderGo'))); } +// Safe mode (see BOOT_GUARD in build.js): the app crashed on open twice in a +// row, so this boot made no automatic connections. Say so, name the phase +// the last boot reached, and offer the way back. +function safeBootNotice() { + if (!safeBoot()) return null; + const phase = (typeof window !== 'undefined' && window.__bootPhase) || '?'; + const report = () => ['Coinos safe mode', 'build ' + (typeof window._v === 'string' ? window._v : '?'), + 'starts without a clean boot: ' + window.__safeBoot, 'last phase: ' + phase, navigator.userAgent].join('\n'); + return h('div', { class: 'notice info col', style: 'margin:12px 0 0;gap:8px' }, + h('b', {}, t('safeBootTitle')), + h('span', { class: 'small' }, t('safeBootBody', { n: window.__safeBoot - 1, phase })), + h('div', { class: 'row', style: 'gap:8px' }, + h('button', { class: 'btn-sm', onClick: () => { bootSettled(); location.reload(); } }, t('safeBootOpen')), + h('button', { class: 'btn-sm', onClick: (e) => { + navigator.clipboard?.writeText(report()).then(() => { e.target.textContent = t('safeBootCopied'); }).catch(() => {}); + } }, t('safeBootCopy')))); +} + function offlineBanner() { return h( 'div', @@ -6824,6 +6852,7 @@ window.addEventListener('load', () => { }, 1000); }); loadLocale(getLang()).finally(async () => { + bootPhase('boot'); applyBootAutoLogout(); // clear an overdue session before we read it for claim targets // Public read-only community chat: no wallet, no login — the join material // ships in the bundle, so the page decrypts client-side (src/public-chat.js). @@ -6849,6 +6878,7 @@ loadLocale(getLang()).finally(async () => { // and a wallet opening in fiat should open with a number in it. refreshRates().then(() => { if (unit === 'fiat') render(); }).catch(() => {}); setInterval(() => { refreshRates().then(() => { if (unit === 'fiat') render(); }).catch(() => {}); }, 10 * 60_000); + bootPhase('store'); const storeReady = featureHook('arkStoreReady'); // the IndexedDB caches (profiles, reports, …) open alongside: a wallet's // caches are read as soon as it opens @@ -6859,6 +6889,8 @@ loadLocale(getLang()).finally(async () => { _bootDeciding = false; // from here on the screen is a decision, not a guess const opened = restoreAccountsState(); if (!opened) render(); + bootPhase('ui'); + setTimeout(bootSettled, 8000); if (WAKE_PAYLOAD != null) { // Nothing opened — a locked vault, or no wallet on this device. There is // nothing to answer with, so say so now. |
