aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/app.js b/src/app.js
index eb51e2f..ffe9ba8 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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.