// Main application entry point import { CONFIG, loadDomainConfig } from './config.js'; import { hideLoading, showStatus, setViewportDimensions, updateLoadingMessage } from './utils.js'; import { checkAuth } from './auth.js'; import { initializeMap, getMap } from './map-manager.js'; import { loadLocations } from './location-manager.js'; import { setupEventListeners } from './ui-controls.js'; import { UnifiedSearchManager } from './search-manager.js'; import { cutManager } from './cut-manager.js'; import { initializeCutControls } from './cut-controls.js'; // Application state let refreshInterval = null; let unifiedSearchManager = null; // Initialize the application document.addEventListener('DOMContentLoaded', async () => { // Set initial viewport dimensions and listen for resize events setViewportDimensions(); window.addEventListener('resize', setViewportDimensions); window.addEventListener('orientationchange', () => { // Add a small delay for orientation change to complete setTimeout(setViewportDimensions, 100); }); console.log('DOM loaded, checking authentication...'); try { // Load domain configuration first updateLoadingMessage('Loading configuration...'); await loadDomainConfig(); // CRITICAL: Check authentication FIRST before any UI initialization // This prevents the map from briefly showing before redirect updateLoadingMessage('Authenticating...'); await checkAuth(); console.log('Authentication confirmed, initializing application...'); // Setup temp user security measures immediately after authentication await setupUserSecurity(); // Then initialize the map updateLoadingMessage('Initializing map...'); await initializeMap(); // Initialize cut manager after map is ready updateLoadingMessage('Loading map overlays...'); await cutManager.initialize(getMap()); // Initialize cut controls for public map await initializeCutControls(); // Only load locations after map is ready updateLoadingMessage('Loading locations...'); await loadLocations(); // Setup other features setupEventListeners(); setupAutoRefresh(); // Initialize Unified Search updateLoadingMessage('Setting up search...'); await initializeUnifiedSearch(); } catch (error) { console.error('Initialization error:', error); // If authentication fails, we should already be redirected // But in case of other errors, clean up the loading state if (!error.message.includes('Not authenticated') && !error.message.includes('Account expired')) { document.body.classList.remove('authenticating'); document.body.classList.add('authenticated'); document.getElementById('app').classList.remove('app-hidden'); showStatus('Failed to initialize application', 'error'); } } finally { // Only hide loading if we're not being redirected for authentication if (!document.body.classList.contains('authenticating')) { hideLoading(); } } }); function setupAutoRefresh() { // Import currentUser to check user type import('./auth.js').then(authModule => { const { currentUser } = authModule; // Use longer interval for temp users to avoid rate limiting const refreshInterval_ms = currentUser?.userType === 'temp' ? 120000 : // 2 minutes for temp users 60000; // 1 minute for regular users (increased from 30 seconds) let consecutiveErrors = 0; const maxErrors = 3; refreshInterval = setInterval(async () => { try { // Check if user is still authenticated const authResponse = await fetch('/api/auth/check'); const authData = await authResponse.json(); if (!authData.authenticated) { // Stop auto-refresh if not authenticated clearInterval(refreshInterval); console.log('Auto-refresh stopped - user not authenticated'); return; } await loadLocations(); consecutiveErrors = 0; // Reset error count on success } catch (error) { consecutiveErrors++; console.warn(`Location refresh failed (${consecutiveErrors}/${maxErrors}):`, error); // If we've had too many consecutive errors, slow down the refresh if (consecutiveErrors >= maxErrors) { console.log('Too many consecutive errors, slowing down refresh interval'); clearInterval(refreshInterval); // Restart with a longer interval const slowInterval = refreshInterval_ms * 2; // Double the interval refreshInterval = setInterval(async () => { try { // Check if user is still authenticated const authResponse = await fetch('/api/auth/check'); const authData = await authResponse.json(); if (!authData.authenticated) { // Stop auto-refresh if not authenticated clearInterval(refreshInterval); console.log('Auto-refresh stopped - user not authenticated'); return; } await loadLocations(); consecutiveErrors = 0; } catch (error) { console.warn('Location refresh failed:', error); } }, slowInterval); } } }, refreshInterval_ms); if (currentUser?.userType === 'temp') { console.log('Auto-refresh set to 2 minutes for temporary account'); } else { console.log('Auto-refresh set to 1 minute for regular account'); } }).catch(error => { // Fallback to default interval if auth module fails to load refreshInterval = setInterval(() => { loadLocations(); }, CONFIG.REFRESH_INTERVAL); }); } // Clean up on page unload window.addEventListener('beforeunload', () => { if (refreshInterval) { clearInterval(refreshInterval); } }); // Initialize Unified Search async function initializeUnifiedSearch() { try { // Get config from server const configResponse = await fetch('/api/config'); const config = await configResponse.json(); unifiedSearchManager = new UnifiedSearchManager({ mkdocsUrl: config.mkdocsUrl || 'http://localhost:4002', minSearchLength: 2 }); const initialized = await unifiedSearchManager.initialize(); if (initialized) { // Bind to search container const searchContainer = document.querySelector('.unified-search-container'); if (searchContainer) { const bound = unifiedSearchManager.bindToElements(searchContainer); if (bound) { console.log('Unified search ready'); } else { console.warn('Failed to bind unified search to elements'); } } else { console.warn('Unified search container not found'); } } else { console.warn('Unified search could not be initialized'); } } catch (error) { console.error('Error setting up unified search:', error); } } // Setup security measures for non-admin users async function setupUserSecurity() { // Import currentUser from auth module const { currentUser } = await import('./auth.js'); if (currentUser?.userType !== 'admin') { // Override console methods for all non-admin users const noop = () => {}; console.log = noop; console.debug = noop; console.info = noop; console.warn = noop; console.error = noop; console.trace = noop; console.dir = noop; console.dirxml = noop; console.group = noop; console.groupEnd = noop; console.time = noop; console.timeEnd = noop; console.assert = noop; console.profile = noop; } // Additional temp user specific restrictions if (currentUser?.userType === 'temp') { // Disable right-click context menu document.addEventListener('contextmenu', (e) => { e.preventDefault(); return false; }); // Disable common keyboard shortcuts for developer tools document.addEventListener('keydown', (e) => { // F12 if (e.keyCode === 123) { e.preventDefault(); return false; } // Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U if (e.ctrlKey && e.shiftKey && (e.keyCode === 73 || e.keyCode === 74)) { e.preventDefault(); return false; } // Ctrl+U (view source) if (e.ctrlKey && e.keyCode === 85) { e.preventDefault(); return false; } }); } }