/** * Dashboard entry point. Guards against loading outside the dashboard context, * initialises navigation and all page event handlers, then starts the 2-second * refresh cycle. * Depends on: all other dashboard modules (loaded before this file) */ /** * Guard: only run when loaded as the actual dashboard page. */ if (!document.getElementById('page-dashboard') && !document.querySelector('.sidebar-logo')) { throw new Error('dashboard scripts loaded outside dashboard context — aborting'); } /** * Update boot splash copy when sync identity is present (host already finished warmup in getState). * @param {object|null} state */ function updateBootSplashFromState(state) { const lineEl = document.getElementById('bootSplashLine'); const subEl = document.getElementById('bootSplashSub'); if (!lineEl || !subEl) return; lineEl.textContent = 'Please wait'; if (state && state.syncIdentityPresent) { subEl.textContent = 'Reconnecting your sync group and restoring tunnels. This can take a moment — thanks for waiting.'; } else { subEl.textContent = 'Restoring tunnels and loading your dashboard…'; } } /** * Fade out and remove the boot splash after the UI has applied state. */ function dismissBootSplash() { const el = document.getElementById('bootSplash'); if (!el || el.dataset.dismissed === '1') return; el.dataset.dismissed = '1'; el.setAttribute('aria-busy', 'false'); el.classList.add('boot-splash--exiting'); const removeEl = () => { try { el.remove(); } catch (_) {} }; el.addEventListener( 'transitionend', (e) => { if (e.target === el && e.propertyName === 'opacity') removeEl(); }, { once: true } ); setTimeout(removeEl, 520); } /** * Initialise the dashboard: set the version string, wire up navigation and all * page event handlers, run the first state refresh, and start the 2-second polling interval. * @returns {Promise} */ async function init() { const initialState = await fetchState(); updateBootSplashFromState(initialState); log('Dashboard initializing…'); try { const manifest = chrome.runtime.getManifest(); const versionEl = $('sidebarVersion'); if (versionEl && manifest.version) { versionEl.textContent = 'v' + manifest.version + ' · hole.sail'; } } catch (_) {} setupNavigation(); setupKeyboardShortcutsHelp(); setupEvents(); setupCertValidator(); setupSshEvents(); setupRdpEvents(); setupBackupEvents(); setupSyncEvents(); await refresh(); dismissBootSplash(); // When native host emits syncApplied, refresh state immediately chrome.runtime.onMessage.addListener((msg) => { if (msg.type === 'holesail-event' && msg.payload && msg.payload.event === 'syncApplied') { refresh(); } }); // Main state-refresh interval: configurable (2s / 5s / 10s), or paused when tab is hidden or setting is 0. let _refreshIntervalId = null; function _getRefreshMs() { const ms = (settings.dashboardRefreshIntervalMs !== undefined ? settings.dashboardRefreshIntervalMs : 2000); return (typeof ms === 'number' && ms >= 0 ? ms : 2000); } function _startRefreshInterval() { if (_refreshIntervalId) clearInterval(_refreshIntervalId); _refreshIntervalId = null; if (document.visibilityState === 'hidden') return; const ms = _getRefreshMs(); if (ms <= 0) return; _refreshIntervalId = setInterval(refresh, ms); } function _restartRefreshInterval() { _startRefreshInterval(); } document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { refresh(); _startRefreshInterval(); } else { if (_refreshIntervalId) { clearInterval(_refreshIntervalId); _refreshIntervalId = null; } } }); _startRefreshInterval(); window._restartRefreshInterval = _restartRefreshInterval; // Latency-ping interval — driven by settings.latencyPingIntervalMs. let _pingInterval = null; function _restartPingInterval() { if (_pingInterval) clearInterval(_pingInterval); _pingInterval = null; if (settings.latencyPingEnabled === false) return; const ms = (settings.latencyPingIntervalMs > 0 ? settings.latencyPingIntervalMs : 5000); _pingInterval = setInterval(_pingLatencyBadges, ms); } _restartPingInterval(); window._restartPingInterval = _restartPingInterval; window.addEventListener('beforeunload', () => { if (_refreshIntervalId) clearInterval(_refreshIntervalId); if (_pingInterval) clearInterval(_pingInterval); }, { once: true }); } init();