import { lazy, type ComponentType } from 'react'; export type RouteSection = 'monitor' | 'vehicles' | 'tracks' | 'history' | 'statistics' | 'access' | 'alerts' | 'operations'; type RouteModule = { default: ComponentType }; type RoutePreloadConnection = { saveData?: boolean; effectiveType?: string }; type RoutePreloadRuntime = { visibilityState?: DocumentVisibilityState; deviceMemory?: number }; type RoutePreloadScheduler = (callback: () => void) => () => void; type RouteLoadOptions = { timeoutMs?: number; retryDelayMs?: number; retries?: number }; const ROUTE_LOAD_TIMEOUT_MS = 10_000; const ROUTE_RETRY_DELAY_MS = 180; const importers: Record Promise> = { monitor: () => import('../pages/MonitorPage'), vehicles: () => import('../pages/VehiclePage'), tracks: () => import('../pages/TrackPage'), history: () => import('../pages/HistoryPage'), statistics: () => import('../pages/StatisticsPage'), access: () => import('../pages/AccessPage'), alerts: () => import('../pages/AlertsPage'), operations: () => import('../pages/OperationsPage') }; const pendingModules = new Map>(); const likelyNextRoutes: Record = { monitor: ['vehicles', 'tracks'], vehicles: ['monitor', 'tracks'], tracks: ['monitor', 'history'], history: ['statistics', 'tracks'], statistics: ['history', 'vehicles'], access: ['monitor', 'operations'], alerts: ['monitor', 'access'], operations: ['access', 'monitor'] }; function routeLoadTimeout(promise: Promise, section: string, timeoutMs: number) { return new Promise((resolve, reject) => { const timer = window.setTimeout(() => { const error = new Error(`Route chunk load timed out after ${timeoutMs}ms: ${section}`); error.name = 'RouteChunkLoadTimeoutError'; reject(error); }, timeoutMs); promise.then( (value) => { window.clearTimeout(timer); resolve(value); }, (error) => { window.clearTimeout(timer); reject(error); } ); }); } function retryDelay(delayMs: number) { return new Promise((resolve) => window.setTimeout(resolve, delayMs)); } function isRouteLoadTimeout(error: unknown) { return error instanceof Error && error.name === 'RouteChunkLoadTimeoutError'; } export async function loadRouteModule( loader: () => Promise, section: string, { timeoutMs = ROUTE_LOAD_TIMEOUT_MS, retryDelayMs = ROUTE_RETRY_DELAY_MS, retries = 1 }: RouteLoadOptions = {} ) { let lastError: unknown; for (let attempt = 0; attempt <= retries; attempt += 1) { try { return await routeLoadTimeout(Promise.resolve().then(loader), section, timeoutMs); } catch (error) { lastError = error; if (attempt >= retries || isRouteLoadTimeout(error)) break; await retryDelay(retryDelayMs); } } throw lastError; } function loadRoute(section: RouteSection) { const pending = pendingModules.get(section); if (pending) return pending; const next = loadRouteModule(importers[section], section); pendingModules.set(section, next); void next.catch(() => pendingModules.delete(section)); return next; } export function createRouteComponent(section: RouteSection) { return lazy(() => loadRoute(section)); } export function routeSectionForPath(pathname: string): RouteSection | undefined { const section = pathname.split('?')[0].split('#')[0].split('/').filter(Boolean)[0] as RouteSection | undefined; return section && section in importers ? section : undefined; } export async function preloadRoute(pathname: string) { const section = routeSectionForPath(pathname); if (!section) return undefined; try { return await loadRoute(section); } catch { // Intent/background preloads are speculative. loadRoute already evicts the // failed Promise so a later navigation can retry through the route boundary. return undefined; } } function browserConnection(): RoutePreloadConnection | undefined { return (navigator as Navigator & { connection?: RoutePreloadConnection }).connection; } function browserRuntime(): RoutePreloadRuntime { return { visibilityState: document.visibilityState, deviceMemory: (navigator as Navigator & { deviceMemory?: number }).deviceMemory }; } export function routePreloadBudget(connection = browserConnection(), runtime = browserRuntime()) { if (runtime.visibilityState === 'hidden' || connection?.saveData) return 0; const effectiveType = String(connection?.effectiveType ?? '').toLowerCase(); if (effectiveType.includes('2g') || (runtime.deviceMemory != null && runtime.deviceMemory <= 2)) return 0; if (effectiveType.includes('3g') || (runtime.deviceMemory != null && runtime.deviceMemory <= 4)) return 1; return 2; } export function shouldPreloadRoutes(connection = browserConnection(), runtime = browserRuntime()) { return routePreloadBudget(connection, runtime) > 0; } export function routePreloadCandidates(pathname: string) { const activeSection = routeSectionForPath(pathname); return activeSection ? likelyNextRoutes[activeSection] : []; } function shouldContinuePreloading() { return document.visibilityState !== 'hidden'; } export function shouldPreloadRouteOnIntent(connection = browserConnection()) { if (connection?.saveData) return false; return !String(connection?.effectiveType ?? '').toLowerCase().includes('2g'); } function scheduleWhenIdle(callback: () => void) { if (typeof window.requestIdleCallback === 'function') { const id = window.requestIdleCallback(callback, { timeout: 2_000 }); return () => window.cancelIdleCallback(id); } const id = window.setTimeout(callback, 900); return () => window.clearTimeout(id); } export function scheduleIdleRoutePreloads({ activePathname, connection = browserConnection(), runtime = browserRuntime(), preload = preloadRoute, schedule = scheduleWhenIdle, isVisible = shouldContinuePreloading }: { activePathname: string; connection?: RoutePreloadConnection; runtime?: RoutePreloadRuntime; preload?: (pathname: string) => Promise | undefined; schedule?: RoutePreloadScheduler; isVisible?: () => boolean; }) { const budget = routePreloadBudget(connection, runtime); if (!budget) return () => undefined; const queue = routePreloadCandidates(activePathname).slice(0, budget); let cancelled = false; let cancelScheduled: () => void = () => undefined; const warmNext = () => { if (cancelled || !isVisible()) return; const section = queue.shift(); if (!section) return; void Promise.resolve().then(() => preload(`/${section}`)) .finally(() => { if (!cancelled && queue.length && isVisible()) cancelScheduled = schedule(warmNext); }); }; cancelScheduled = schedule(warmNext); return () => { cancelled = true; cancelScheduled(); }; } export const RoutePages = { Monitor: createRouteComponent('monitor'), Vehicles: createRouteComponent('vehicles'), Tracks: createRouteComponent('tracks'), History: createRouteComponent('history'), Statistics: createRouteComponent('statistics'), Access: createRouteComponent('access'), Alerts: createRouteComponent('alerts'), Operations: createRouteComponent('operations') } as const; export const RoutePageFactories = { Monitor: () => createRouteComponent('monitor'), Vehicles: () => createRouteComponent('vehicles'), Tracks: () => createRouteComponent('tracks'), History: () => createRouteComponent('history'), Statistics: () => createRouteComponent('statistics'), Access: () => createRouteComponent('access'), Alerts: () => createRouteComponent('alerts'), Operations: () => createRouteComponent('operations') } as const;