mirror of
https://github.com/jason5ng32/MyIP.git
synced 2026-11-04 08:55:47 +00:00
nav.Title / PingTest / MTRTest / RuleTest had no reader in any locale. The store takes isFireBaseSet from firebase-init.js instead of recomputing it in checkFirebaseEnv(). The --page-bg comment no longer claims index.html's colours must match it; they are tuned separately. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GbLTGMZ45MV78qd2tHoco
176 lines
7.2 KiB
JavaScript
176 lines
7.2 KiB
JavaScript
import { createApp } from 'vue'
|
|
import { createPinia } from 'pinia';
|
|
import { useMainStore } from './store';
|
|
import App from './App.vue'
|
|
import i18n, { loadActiveLocaleMessages } from './locales/i18n';
|
|
import router from './router';
|
|
import { analytics } from './utils/analytics';
|
|
import { getTimezoneInfo } from './utils/time-utils';
|
|
import { isRunningAsPwa } from './utils/pwa';
|
|
import { readAuthHint } from './utils/auth-hint';
|
|
import { hasPendingRedirectSignIn } from './utils/auth-redirect';
|
|
import { unregisterLegacyServiceWorker } from './utils/unregister-service-worker';
|
|
import { addCollection } from '@iconify/vue';
|
|
|
|
import { detectOS } from './utils/system-detect';
|
|
import './style/style.css'
|
|
|
|
// vConsole — mobile on-screen devtools. Dev-only AND mobile-only.
|
|
// Sets `window.__vConsoleActive` so `frontend/utils/shortcut.js` can disable
|
|
// global keyboard shortcuts while vConsole is around — typing in its filter
|
|
// input would otherwise still trigger app-wide hotkeys (vConsole renders
|
|
// inside its own Shadow DOM, so the listener's `target.tagName === 'INPUT'`
|
|
// guard doesn't catch those keystrokes).
|
|
if (import.meta.env.DEV) {
|
|
const dbgOS = detectOS();
|
|
if (dbgOS.isAndroid || dbgOS.isIOS) {
|
|
import('vconsole').then(({ default: VConsole }) => {
|
|
new VConsole();
|
|
window.__vConsoleActive = true;
|
|
// Pin the floating switch to bottom-left.
|
|
const styleEl = document.createElement('style');
|
|
styleEl.textContent =
|
|
'#__vconsole .vc-switch { left: 8px !important; right: auto !important; bottom: 36px !important; }';
|
|
document.head.appendChild(styleEl);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Stale-deploy self-heal. After a deploy, pages loaded before it lazy-import
|
|
// hashed assets that no longer exist; Vite surfaces every such failure
|
|
// (module or CSS) as `vite:preloadError`. Reload once to pick up the fresh
|
|
// index.html. The timestamp latch (per-tab) stops a reload loop when the
|
|
// real cause is elsewhere (offline, blocked CDN): within the window the
|
|
// event just propagates as before. Registered at module eval so it's in
|
|
// place before any lazy import can fail.
|
|
window.addEventListener('vite:preloadError', (event) => {
|
|
const LATCH_KEY = 'preloadReloadAt';
|
|
const lastReload = Number(sessionStorage.getItem(LATCH_KEY) || 0);
|
|
if (Date.now() - lastReload < 60 * 1000) return;
|
|
sessionStorage.setItem(LATCH_KEY, String(Date.now()));
|
|
event.preventDefault();
|
|
window.location.reload();
|
|
});
|
|
|
|
// The flag icon JSON is hundreds of KB — loaded after mount (see the mount
|
|
// chain below) so it doesn't compete for boot bandwidth.
|
|
const loadFlagIcons = () => import('@iconify-json/circle-flags/icons.json')
|
|
.then((mod) => { if (mod?.default) addCollection(mod.default); })
|
|
.catch(() => { /* non-fatal: flag icons degrade gracefully */ });
|
|
|
|
const app = createApp(App);
|
|
const pinia = createPinia();
|
|
|
|
app.use(pinia);
|
|
const store = useMainStore(pinia);
|
|
|
|
app.use(i18n);
|
|
app.use(router);
|
|
|
|
// Sentry — build-time env gate: without the DSN, or in a dev server run, the
|
|
// chunk neither ships nor loads (local runs would otherwise report every
|
|
// experiment against the production project). The SDK chunk stays OFF the
|
|
// boot critical path: it loads after mount (see the mount chain's finally
|
|
// below) so it never competes with the locale pack the first render waits
|
|
// on. Until init, a tiny buffer catches uncaught errors / rejections — and
|
|
// any of them triggers an immediate load, so a boot that never reaches mount
|
|
// still reports. Perf data survives the late init (buffered observers,
|
|
// backdated pageload span).
|
|
const earlyErrors = [];
|
|
let loadSentry = () => {};
|
|
if (import.meta.env.VITE_SENTRY_DSN_FRONTEND && !import.meta.env.DEV) {
|
|
const onEarlyError = (event) => {
|
|
earlyErrors.push(event);
|
|
loadSentry();
|
|
};
|
|
window.addEventListener('error', onEarlyError);
|
|
window.addEventListener('unhandledrejection', onEarlyError);
|
|
let loading = null;
|
|
loadSentry = () => {
|
|
loading ??= import('./sentry-init')
|
|
.then(({ initSentry }) => {
|
|
initSentry(app, router, earlyErrors);
|
|
window.removeEventListener('error', onEarlyError);
|
|
window.removeEventListener('unhandledrejection', onEarlyError);
|
|
})
|
|
.catch(() => {});
|
|
};
|
|
}
|
|
|
|
//
|
|
// Initialize a series of operations
|
|
//
|
|
|
|
// Set language when app starts
|
|
store.lang = i18n.global.locale;
|
|
|
|
// Detect operating system
|
|
const os = detectOS();
|
|
|
|
// Handle window size change
|
|
function handleResize() {
|
|
store.setIsMobile(window.innerWidth < 768 || os.isAndroid || os.isIOS );
|
|
}
|
|
handleResize();
|
|
|
|
// Listen to window size change
|
|
window.addEventListener('resize', handleResize);
|
|
|
|
// Start Google Analytics. `app_mode` is a user-scoped custom dimension
|
|
// (register it in GA admin as a user property) that splits every report by
|
|
// how the app was opened: installed PWA window vs regular browser tab.
|
|
analytics.page();
|
|
const { timezone, offset } = getTimezoneInfo();
|
|
analytics.setUserProperties({
|
|
...(timezone || offset ? { timezone, tz_offset: offset } : {}),
|
|
app_mode: isRunningAsPwa() ? 'pwa' : 'web',
|
|
});
|
|
unregisterLegacyServiceWorker();
|
|
|
|
|
|
// Backend configs load fire-and-forget: components read `store.configs`
|
|
// reactively, so the first render never waits on this round trip.
|
|
store.fetchConfigs();
|
|
|
|
// Gate the first render only on what it actually needs; the legs all run in
|
|
// parallel. Auth is hint-gated (utils/auth-hint.js): only a previously
|
|
// signed-in visitor loads Firebase and waits for it before first render, so
|
|
// the first authenticatedFetch round carries their token. A return from a
|
|
// redirect sign-in (utils/auth-redirect.js marker) is consumed before mount
|
|
// for the same reason; if it signs no one in, a signed-in hint still gets the
|
|
// listener. Everyone else mounts without the SDK.
|
|
const authHint = readAuthHint();
|
|
const resolveBootAuth = () => {
|
|
if (hasPendingRedirectSignIn()) {
|
|
return store.completeRedirectSignIn()
|
|
.then((ok) => (ok || authHint !== '1') ? undefined : store.initializeAuthListener());
|
|
}
|
|
return store.isFireBaseSet && authHint === '1' ? store.initializeAuthListener() : Promise.resolve();
|
|
};
|
|
Promise.all([
|
|
resolveBootAuth(),
|
|
store.loadPreferences(),
|
|
loadActiveLocaleMessages(),
|
|
]).then(() => {
|
|
app.mount('#app');
|
|
}).catch(error => {
|
|
earlyErrors.push(error); // reaches Sentry once it initializes below
|
|
console.error("Failed to initialize the app properly:", error);
|
|
app.mount('#app'); // Mount even if initialization partially failed
|
|
}).finally(() => {
|
|
loadFlagIcons(); // deferred boot-bandwidth work, app is on screen now
|
|
loadSentry();
|
|
// Unknown hint (first visit since the flag shipped, or storage cleared):
|
|
// probe auth once in the background so an already-signed-in user is
|
|
// recognized and the next boot takes the exact path.
|
|
if (store.isFireBaseSet && authHint === null) {
|
|
// Probe failure means "treat as visitor" — never an error: an
|
|
// uncaught rejection here would reach Sentry as a crash signal.
|
|
setTimeout(() => {
|
|
store.initializeAuthListener().catch((error) => {
|
|
console.warn('Background auth probe failed, continuing as visitor:', error);
|
|
});
|
|
}, 3000);
|
|
}
|
|
});
|