// deeplinks.js - Deep Link Manager for Game Timer // Available actions const VALID_ACTIONS = ['start', 'pause', 'toggle', 'nextplayer', 'reset']; // Class to manage all deep link functionality class DeepLinkManager { constructor() { this.actionHandlers = {}; // Initialize listeners this.initServiceWorkerListener(); this.initHashChangeListener(); this.initPopStateListener(); } // Register action handlers registerHandler(action, handlerFn) { if (VALID_ACTIONS.includes(action)) { this.actionHandlers[action] = handlerFn; console.log(`Registered handler for action: ${action}`); } else { console.warn(`Attempted to register handler for invalid action: ${action}`); } } // Extract action from URL parameters (search or hash) getActionFromUrl() { // Check for action in both search params and hash const searchParams = new URLSearchParams(window.location.search); const hashParams = new URLSearchParams(window.location.hash.substring(1)); // First check search params (for direct curl or navigation) const searchAction = searchParams.get('action'); if (searchAction && VALID_ACTIONS.includes(searchAction)) { console.log('Found action in search params:', searchAction); return searchAction; } // Then check hash params (existing deep link mechanism) const hashAction = hashParams.get('action'); if (hashAction && VALID_ACTIONS.includes(hashAction)) { console.log('Found action in hash params:', hashAction); return hashAction; } return null; } // Process an action handleAction(action) { if (!action) return; console.log('Processing action:', action); if (this.actionHandlers[action]) { this.actionHandlers[action](); } else { console.log('No handler registered for action:', action); } } // Handle deep links from URL processDeepLink() { // Get action from URL parameters const action = this.getActionFromUrl(); // Process the action if found if (action) { this.handleAction(action); // Clear the parameters to prevent duplicate actions if page is refreshed if (window.history && window.history.replaceState) { // Create new URL without the action parameter const newUrl = window.location.pathname; window.history.replaceState({}, document.title, newUrl); } } } // Initialize service worker message listener initServiceWorkerListener() { if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', (event) => { if (event.data && event.data.type === 'ACTION') { console.log('Received action from service worker:', event.data.action); this.handleAction(event.data.action); } }); } } // Initialize hash change listener initHashChangeListener() { window.addEventListener('hashchange', () => { console.log('Hash changed, checking for actions'); this.processDeepLink(); }); } // Initialize popstate listener for navigation events initPopStateListener() { window.addEventListener('popstate', () => { console.log('Navigation occurred, checking for actions'); this.processDeepLink(); }); } // Send an action to the service worker sendActionToServiceWorker(action) { if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'PROCESS_ACTION', action: action }); } } // Generate a deep link URL for a specific action generateDeepLink(action, useHash = false) { if (!VALID_ACTIONS.includes(action)) { console.warn(`Cannot generate deep link for invalid action: ${action}`); return null; } const baseUrl = window.location.origin + window.location.pathname; return useHash ? `${baseUrl}#action=${action}` : `${baseUrl}?action=${action}`; } } // Export singleton instance const deepLinkManager = new DeepLinkManager(); export default deepLinkManager;