// pushSettingsUI.js - UI handling for push notification settings import { setupPushNotifications, forceCredentialsPrompt } from '../services/serviceWorkerManager.js'; import { FLIC_BUTTON_ID } from '../config.js'; // --- DOM Elements --- const elements = { pushSettingsButton: null, pushSettingsModal: null, notificationPermissionStatus: null, subscriptionStatus: null, pushUsername: null, pushPassword: null, pushSaveButton: null, pushCancelButton: null, pushUnsubscribeButton: null, pushResubscribeButton: null, // Message monitor elements swMessagesOutput: null, simulateClickButton: null }; // --- State --- let currentSubscription = null; let messageListener = null; let isMonitoring = false; // --- Initialization --- export function initPushSettingsUI() { // Cache DOM elements elements.pushSettingsButton = document.getElementById('pushSettingsButton'); elements.pushSettingsModal = document.getElementById('pushSettingsModal'); elements.notificationPermissionStatus = document.getElementById('notificationPermissionStatus'); elements.subscriptionStatus = document.getElementById('subscriptionStatus'); elements.pushUsername = document.getElementById('pushUsername'); elements.pushPassword = document.getElementById('pushPassword'); elements.pushSaveButton = document.getElementById('pushSaveButton'); elements.pushCancelButton = document.getElementById('pushCancelButton'); elements.pushUnsubscribeButton = document.getElementById('pushUnsubscribeButton'); elements.pushResubscribeButton = document.getElementById('pushResubscribeButton'); // Message Monitor elements elements.swMessagesOutput = document.getElementById('swMessagesOutput'); elements.simulateClickButton = document.getElementById('simulateClickButton'); // Set up event listeners elements.pushSettingsButton.addEventListener('click', openPushSettingsModal); elements.pushCancelButton.addEventListener('click', closePushSettingsModal); elements.pushSaveButton.addEventListener('click', saveCredentialsAndSubscribe); elements.pushUnsubscribeButton.addEventListener('click', unsubscribeFromPush); elements.pushResubscribeButton.addEventListener('click', resubscribeToPush); // Initial status check updateNotificationStatus(); } // --- UI Functions --- // Open the push settings modal and update statuses function openPushSettingsModal() { // Update status displays updateNotificationStatus(); updateSubscriptionStatus(); // Load saved credentials if available loadSavedCredentials(); // Start monitoring automatically when modal opens startMessageMonitoring(); // Show the modal elements.pushSettingsModal.classList.add('active'); } // Close the push settings modal function closePushSettingsModal() { // Stop monitoring when the modal is closed to avoid unnecessary processing stopMessageMonitoring(); elements.pushSettingsModal.classList.remove('active'); } // --- Message Monitor Functions --- // Start monitoring service worker messages function startMessageMonitoring() { // If already monitoring, don't set up a new listener if (isMonitoring) { return; } if (!('serviceWorker' in navigator)) { elements.swMessagesOutput.textContent = 'Service Worker not supported in this browser.'; return; } // Reset the output area elements.swMessagesOutput.textContent = 'Monitoring for service worker messages...'; // Create and register the message listener messageListener = function(event) { const now = new Date().toISOString(); const formattedMessage = `[${now}] Message received: \n${JSON.stringify(event.data, null, 2)}\n\n`; elements.swMessagesOutput.textContent += formattedMessage; // Auto-scroll to the bottom elements.swMessagesOutput.scrollTop = elements.swMessagesOutput.scrollHeight; }; // Add the listener navigator.serviceWorker.addEventListener('message', messageListener); isMonitoring = true; } // Stop monitoring service worker messages function stopMessageMonitoring() { if (messageListener) { navigator.serviceWorker.removeEventListener('message', messageListener); messageListener = null; isMonitoring = false; } } // Update the notification permission status display function updateNotificationStatus() { if (!('Notification' in window)) { elements.notificationPermissionStatus.textContent = 'Not Supported'; elements.notificationPermissionStatus.className = 'status-denied'; // Disable subscribe button when notifications are not supported elements.pushResubscribeButton.disabled = true; elements.pushResubscribeButton.classList.add('disabled'); return; } const permission = Notification.permission; elements.notificationPermissionStatus.textContent = permission; switch (permission) { case 'granted': elements.notificationPermissionStatus.className = 'status-granted'; // Enable subscribe button when permission is granted elements.pushResubscribeButton.disabled = false; elements.pushResubscribeButton.classList.remove('disabled'); break; case 'denied': elements.notificationPermissionStatus.className = 'status-denied'; // Disable subscribe button when permission is denied elements.pushResubscribeButton.disabled = true; elements.pushResubscribeButton.classList.add('disabled'); break; default: elements.notificationPermissionStatus.className = 'status-default'; // Enable subscribe button for default state (prompt) elements.pushResubscribeButton.disabled = false; elements.pushResubscribeButton.classList.remove('disabled'); } } // Update the subscription status display async function updateSubscriptionStatus() { if (!('serviceWorker' in navigator) || !('PushManager' in window)) { elements.subscriptionStatus.textContent = 'Not Supported'; elements.subscriptionStatus.className = 'status-denied'; // Disable unsubscribe button when not supported elements.pushUnsubscribeButton.disabled = true; // Set subscribe button text elements.pushResubscribeButton.textContent = 'Subscribe'; // Disable simulate button when not supported if (elements.simulateClickButton) { elements.simulateClickButton.disabled = true; } return; } try { const registration = await navigator.serviceWorker.ready; currentSubscription = await registration.pushManager.getSubscription(); if (currentSubscription) { elements.subscriptionStatus.textContent = 'active'; elements.subscriptionStatus.className = 'status-active'; // Enable unsubscribe button when subscription is active elements.pushUnsubscribeButton.disabled = false; // Change subscribe button text to "Re-subscribe" elements.pushResubscribeButton.textContent = 'Re-subscribe'; // Enable simulate button when subscription is active if (elements.simulateClickButton) { elements.simulateClickButton.disabled = false; } } else { elements.subscriptionStatus.textContent = 'Not Subscribed'; elements.subscriptionStatus.className = 'status-inactive'; // Disable unsubscribe button when not subscribed elements.pushUnsubscribeButton.disabled = true; // Set subscribe button text elements.pushResubscribeButton.textContent = 'Subscribe'; // Disable simulate button when not subscribed if (elements.simulateClickButton) { elements.simulateClickButton.disabled = true; } } } catch (error) { console.error('Error checking subscription status:', error); elements.subscriptionStatus.textContent = 'Error'; elements.subscriptionStatus.className = 'status-denied'; // Disable unsubscribe button on error elements.pushUnsubscribeButton.disabled = true; // Set subscribe button text elements.pushResubscribeButton.textContent = 'Subscribe'; // Disable simulate button on error if (elements.simulateClickButton) { elements.simulateClickButton.disabled = true; } } } // Load saved credentials from localStorage function loadSavedCredentials() { try { const storedAuth = localStorage.getItem('basicAuthCredentials'); if (storedAuth) { const credentials = JSON.parse(storedAuth); if (credentials.username && credentials.password) { elements.pushUsername.value = credentials.username; elements.pushPassword.value = credentials.password; } } } catch (error) { console.error('Error loading saved credentials:', error); } } // --- Action Functions --- // Save credentials and close the modal async function saveCredentialsAndSubscribe() { const username = elements.pushUsername.value.trim(); const password = elements.pushPassword.value.trim(); if (!username || !password) { alert('Please enter both username and password'); return; } // Save credentials to localStorage const credentials = { username, password }; localStorage.setItem('basicAuthCredentials', JSON.stringify(credentials)); // Close the modal closePushSettingsModal(); } // Unsubscribe from push notifications async function unsubscribeFromPush() { if (!currentSubscription) { alert('No active subscription to unsubscribe from'); return; } try { await currentSubscription.unsubscribe(); await updateSubscriptionStatus(); // No success alert } catch (error) { console.error('Error unsubscribing:', error); alert(`Error unsubscribing: ${error.message}`); } } // Force subscription to push notifications async function resubscribeToPush() { try { let username = elements.pushUsername.value.trim(); let password = elements.pushPassword.value.trim(); // If fields are empty, try to use stored credentials if (!username || !password) { try { const storedAuth = localStorage.getItem('basicAuthCredentials'); if (storedAuth) { const credentials = JSON.parse(storedAuth); if (credentials.username && credentials.password) { username = credentials.username; password = credentials.password; // Update the form fields with stored values elements.pushUsername.value = username; elements.pushPassword.value = password; } } } catch (error) { console.error('Error loading stored credentials:', error); } } // Use the credentials to subscribe await forceCredentialsPrompt(); // Wait a moment for the subscription to complete await new Promise(resolve => setTimeout(resolve, 500)); // Get the updated subscription const registration = await navigator.serviceWorker.ready; currentSubscription = await registration.pushManager.getSubscription(); // Update the UI directly if (currentSubscription && elements.subscriptionStatus) { elements.subscriptionStatus.textContent = 'active'; elements.subscriptionStatus.className = 'status-active'; // Enable unsubscribe button when subscription is active elements.pushUnsubscribeButton.disabled = false; // Change subscribe button text to "Re-subscribe" elements.pushResubscribeButton.textContent = 'Re-subscribe'; // Enable simulate button when subscription is active if (elements.simulateClickButton) { elements.simulateClickButton.disabled = false; } } else { // Disable unsubscribe button when not subscribed elements.pushUnsubscribeButton.disabled = true; // Set subscribe button text elements.pushResubscribeButton.textContent = 'Subscribe'; // Disable simulate button when not subscribed if (elements.simulateClickButton) { elements.simulateClickButton.disabled = true; } // Fall back to the standard update function await updateSubscriptionStatus(); alert('Subscription failed. Please check your credentials and try again.'); } } catch (error) { console.error('Error subscribing:', error); alert(`Error subscribing: ${error.message}`); } } // Manually trigger sendSubscriptionToServer with the current subscription export async function sendSubscriptionToServer() { if (!currentSubscription) { await updateSubscriptionStatus(); if (!currentSubscription) { // No alert, just return silently return; } } // Get stored credentials let credentials; try { const storedAuth = localStorage.getItem('basicAuthCredentials'); if (storedAuth) { credentials = JSON.parse(storedAuth); if (!credentials.username || !credentials.password) { throw new Error('Invalid credentials'); } } else { throw new Error('No stored credentials'); } } catch (error) { // No alert, just open the modal to let the user set credentials openPushSettingsModal(); return; } // Create Basic Auth header const createBasicAuthHeader = (creds) => { return 'Basic ' + btoa(`${creds.username}:${creds.password}`); }; const headers = { 'Content-Type': 'application/json', 'Authorization': createBasicAuthHeader(credentials) }; // Import the backend URL from config let backendUrl; try { const configModule = await import('../config.js'); backendUrl = configModule.BACKEND_URL; } catch (error) { // No alert, just log the error and return console.error('Could not get backend URL from config:', error); return; } try { // Make the request to the server const response = await fetch(`${backendUrl}/subscribe`, { method: 'POST', body: JSON.stringify({ button_id: FLIC_BUTTON_ID, subscription: currentSubscription }), headers: headers, credentials: 'include' }); if (response.ok) { const result = await response.json(); // No success alert // Update the currentSubscription variable const registration = await navigator.serviceWorker.ready; currentSubscription = await registration.pushManager.getSubscription(); // Directly update the subscription status element in the DOM if (currentSubscription && elements.subscriptionStatus) { elements.subscriptionStatus.textContent = 'active'; elements.subscriptionStatus.className = 'status-active'; // Enable unsubscribe button when subscription is active elements.pushUnsubscribeButton.disabled = false; // Change subscribe button text to "Re-subscribe" elements.pushResubscribeButton.textContent = 'Re-subscribe'; // Enable simulate button when subscription is active if (elements.simulateClickButton) { elements.simulateClickButton.disabled = false; } } } else { let errorMsg = `Server error: ${response.status}`; if (response.status === 401 || response.status === 403) { localStorage.removeItem('basicAuthCredentials'); errorMsg = 'Authentication failed. Credentials cleared.'; } else { try { const errorData = await response.json(); errorMsg = errorData.message || errorMsg; } catch (e) { /* use default */ } } // No error alert, just log the error console.error(`Failed to send subscription: ${errorMsg}`); } } catch (error) { // No error alert, just log the error console.error(`Network error: ${error.message}`); } }