253 lines
10 KiB
JavaScript
253 lines
10 KiB
JavaScript
// 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();
|
|
} |