// pushFlicIntegration.js import { getPublicVapidKey, getBackendUrl, FLIC_BUTTON_ID} from '../config.js'; let pushSubscription = null; // Keep track locally if needed let actionHandlers = {}; // Store handlers for different Flic actions // --- Helper Functions --- // Get stored basic auth credentials or prompt user for them function getBasicAuthCredentials() { const storedAuth = localStorage.getItem('basicAuthCredentials'); if (storedAuth) { try { const credentials = JSON.parse(storedAuth); // Check if the credentials are valid if (credentials.username && credentials.password) { console.log('Using stored basic auth credentials.'); return credentials; } } catch (error) { console.error('Failed to parse stored credentials:', error); } } // No valid stored credentials found // The function will return null and the caller should handle prompting if needed console.log('No valid stored credentials found.'); return null; } // Create Basic Auth header string function createBasicAuthHeader(credentials) { if (!credentials?.username || !credentials.password) return null; return 'Basic ' + btoa(`${credentials.username}:${credentials.password}`); } // Convert URL-safe base64 string to Uint8Array function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } // Convert ArrayBuffer to URL-safe Base64 string function arrayBufferToBase64(buffer) { let binary = ''; const bytes = new Uint8Array(buffer); for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } // --- Push Subscription Logic --- async function subscribeToPush() { const buttonId = FLIC_BUTTON_ID; // Use configured button ID if (!('serviceWorker' in navigator) || !('PushManager' in window)) { console.error('Push Messaging is not supported.'); alert('Push Notifications are not supported by your browser.'); return; } try { // First request notification permission console.log('Requesting notification permission...'); const permission = await Notification.requestPermission(); if (permission !== 'granted') { console.warn('Notification permission denied.'); alert('Please enable notifications to link the Flic button.'); return; } console.log('Notification permission granted.'); // Get stored credentials but don't prompt let credentials = getBasicAuthCredentials(); const hasExistingCreds = !!credentials; console.log('Has existing credentials:', hasExistingCreds); // No prompting for credentials - user must enter them manually in the UI if (!credentials) { console.log('No credentials found. User needs to enter them manually.'); // Just return if no credentials are available return; } const registration = await navigator.serviceWorker.ready; let existingSubscription = await registration.pushManager.getSubscription(); let needsResubscribe = !existingSubscription; console.log('Existing subscription found:', !!existingSubscription); if (existingSubscription) { const existingKey = existingSubscription.options?.applicationServerKey; if (!existingKey || arrayBufferToBase64(existingKey) !== getPublicVapidKey()) { console.log('VAPID key mismatch or missing. Unsubscribing old subscription.'); await existingSubscription.unsubscribe(); existingSubscription = null; needsResubscribe = true; } else { console.log('Existing valid subscription found.'); pushSubscription = existingSubscription; // Store it } } let finalSubscription = existingSubscription; if (needsResubscribe) { console.log('Subscribing for push notifications...'); const applicationServerKey = urlBase64ToUint8Array(getPublicVapidKey()); try { finalSubscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: applicationServerKey }); console.log('New push subscription obtained:', finalSubscription); pushSubscription = finalSubscription; // Store it } catch (subscribeError) { console.error('Error subscribing to push:', subscribeError); alert(`Failed to subscribe: ${subscribeError.message}`); return; } } if (!finalSubscription) { console.error("Failed to obtain a subscription object."); alert("Could not get subscription details."); return; } await sendSubscriptionToServer(finalSubscription, buttonId); } catch (error) { console.error('Error during push subscription:', error); alert(`Subscription failed: ${error.message}`); } } async function sendSubscriptionToServer(subscription, buttonId) { console.log(`Sending subscription for button "${buttonId}" to backend...`); const credentials = getBasicAuthCredentials(); if (!credentials) { console.log('No credentials found. User needs to enter them manually.'); return; } const headers = { 'Content-Type': 'application/json' }; const authHeader = createBasicAuthHeader(credentials); if (authHeader) headers['Authorization'] = authHeader; try { // Add support for handling CORS preflight with credentials console.log("BACKEND_URL Key: " + getBackendUrl()); console.log("FLIC_BUTTON_ID Key: " + FLIC_BUTTON_ID); const response = await fetch(`${getBackendUrl()}/subscribe`, { method: 'POST', body: JSON.stringify({ button_id: buttonId, subscription: subscription }), headers: headers, credentials: 'include' // This ensures credentials are sent with OPTIONS requests too }); if (response.ok) { const result = await response.json(); console.log('Subscription sent successfully:', result.message); // Update the UI to show subscription status as active const subscriptionStatusElement = document.getElementById('subscriptionStatus'); if (subscriptionStatusElement) { subscriptionStatusElement.textContent = 'active'; subscriptionStatusElement.className = 'status-active'; // Enable unsubscribe button when subscription is active const unsubscribeButton = document.getElementById('pushUnsubscribeButton'); if (unsubscribeButton) unsubscribeButton.disabled = false; // Change subscribe button text to "Re-subscribe" const resubscribeButton = document.getElementById('pushResubscribeButton'); if (resubscribeButton) resubscribeButton.textContent = 'Re-subscribe'; // Enable simulate button when subscription is active const simulateButton = document.getElementById('simulateClickButton'); if (simulateButton) simulateButton.disabled = false; } // Success alert removed as requested } else { let errorMsg = `Server error: ${response.status}`; if (response.status === 401 || response.status === 403) { localStorage.removeItem('basicAuthCredentials'); // Clear bad creds errorMsg = 'Authentication failed. Please try again.'; } else { try { errorMsg = (await response.json()).message || errorMsg; } catch (e) { /* use default */ } } console.error('Failed to send subscription:', errorMsg); alert(`Failed to save link: ${errorMsg}`); } } catch (error) { console.error('Network error sending subscription:', error); alert(`Network error: ${error.message}`); } } // --- Flic Action Handling --- // Called by app.js when a message is received from the service worker export function handleFlicAction(action, buttonId, timestamp, batteryLevel) { console.log(`[PushFlic] Received Action: ${action} from Button: ${buttonId} battery: ${batteryLevel}% at ${timestamp}`); // Ignore actions from buttons other than the configured one if (buttonId !== FLIC_BUTTON_ID) { console.warn(`[PushFlic] Ignoring action from unknown button: ${buttonId}`); return; } // Find the registered handler for this action const handler = actionHandlers[action]; if (handler && typeof handler === 'function') { console.log(`[PushFlic] Executing handler for ${action}`); try { // Execute the handler registered in app.js handler(); // Log success console.log(`[PushFlic] Successfully executed handler for ${action}`); } catch (error) { console.error(`[PushFlic] Error executing handler for ${action}:`, error); } } else { console.warn(`[PushFlic] No handler registered for action: ${action}. Available handlers:`, Object.keys(actionHandlers)); } } // --- Initialization --- // Initialize PushFlic with action handlers export function initPushFlic(handlers) { if (handlers && Object.keys(handlers).length > 0) { actionHandlers = handlers; console.log('[PushFlic] Stored action handlers:', Object.keys(actionHandlers)); } else { console.warn('[PushFlic] No action handlers provided to initPushFlic!'); } } // New function to manually trigger the subscription process export function setupPushNotifications() { console.log('[PushFlic] Manually triggering push notification setup'); subscribeToPush(); }