diff --git a/LittleShop/wwwroot/js/pwa.js b/LittleShop/wwwroot/js/pwa.js
index dcb5606..f87fc48 100644
--- a/LittleShop/wwwroot/js/pwa.js
+++ b/LittleShop/wwwroot/js/pwa.js
@@ -1,537 +1,568 @@
-// Progressive Web App functionality
-// Handles service worker registration and PWA features
-
-class PWAManager {
- constructor() {
- this.swRegistration = null;
- this.vapidPublicKey = null;
- this.pushSubscription = null;
- this.init();
- }
-
- async init() {
- console.log('PWA: Initializing PWA Manager...');
-
- if ('serviceWorker' in navigator) {
- try {
- this.swRegistration = await navigator.serviceWorker.register('/sw.js');
- console.log('SW: Service Worker registered successfully');
-
- // Listen for updates
- this.swRegistration.addEventListener('updatefound', () => {
- console.log('SW: New version available');
- this.showUpdateNotification();
- });
-
- } catch (error) {
- console.log('SW: Service Worker registration failed:', error);
- }
- }
-
- // Setup PWA install prompt
- this.setupInstallPrompt();
-
- // Setup notifications (if enabled)
- this.setupNotifications();
-
- // Setup push notifications
- this.setupPushNotifications();
-
- // Show manual install option after 5 seconds if no prompt appeared and app not installed
- setTimeout(() => {
- if (!document.getElementById('pwa-install-btn') && !this.isInstalled()) {
- console.log('PWA: No install prompt appeared, showing manual install guide');
- this.showManualInstallButton();
- }
- }, 5000);
- }
-
- setupInstallPrompt() {
- let deferredPrompt;
-
- window.addEventListener('beforeinstallprompt', (e) => {
- console.log('PWA: beforeinstallprompt event fired');
- // Prevent Chrome 67 and earlier from automatically showing the prompt
- e.preventDefault();
- deferredPrompt = e;
-
- // Show custom install button
- this.showInstallButton(deferredPrompt);
- });
-
- window.addEventListener('appinstalled', () => {
- console.log('PWA: App was installed');
- this.hideInstallButton();
- });
-
- // Debug: Check if app is already installed
- if (this.isInstalled()) {
- console.log('PWA: App is already installed (standalone mode)');
- // Hide any existing install buttons
- this.hideInstallButton();
- } else {
- console.log('PWA: App is not installed, waiting for install prompt...');
- console.log('PWA: Current URL:', window.location.href);
- console.log('PWA: Display mode:', window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser');
- console.log('PWA: User agent:', navigator.userAgent);
- }
-
- // Periodically check if app becomes installed (for cases where user installs via browser menu)
- setInterval(() => {
- if (this.isInstalled()) {
- this.hideInstallButton();
- }
- }, 2000);
- }
-
- showInstallButton(deferredPrompt) {
- // Don't show install button if app is already installed
- if (this.isInstalled()) {
- console.log('PWA: App already installed, skipping install button');
- return;
- }
-
- // Create install button
- const installBtn = document.createElement('button');
- installBtn.id = 'pwa-install-btn';
- installBtn.className = 'btn btn-primary btn-sm';
- installBtn.innerHTML = ' Install App';
- installBtn.style.cssText = `
- position: fixed;
- bottom: 20px;
- right: 20px;
- z-index: 1000;
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
- `;
-
- installBtn.addEventListener('click', async () => {
- if (deferredPrompt) {
- deferredPrompt.prompt();
- const { outcome } = await deferredPrompt.userChoice;
- console.log('PWA: User response to install prompt:', outcome);
- deferredPrompt = null;
- this.hideInstallButton();
- }
- });
-
- document.body.appendChild(installBtn);
- }
-
- hideInstallButton() {
- const installBtn = document.getElementById('pwa-install-btn');
- if (installBtn) {
- installBtn.remove();
- }
- }
-
- showUpdateNotification() {
- // Show update available notification
- const notification = document.createElement('div');
- notification.className = 'alert alert-info alert-dismissible';
- notification.style.cssText = `
- position: fixed;
- top: 20px;
- right: 20px;
- z-index: 1050;
- max-width: 300px;
- `;
-
- notification.innerHTML = `
- Update Available!
- A new version of the app is ready.
-
-
- `;
-
- document.body.appendChild(notification);
-
- // Handle update
- document.getElementById('update-btn').addEventListener('click', () => {
- if (this.swRegistration && this.swRegistration.waiting) {
- this.swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
- window.location.reload();
- }
- });
- }
-
- async setupNotifications() {
- // Check if notifications are supported and get permission
- if ('Notification' in window) {
- const permission = await this.requestNotificationPermission();
- console.log('Notifications permission:', permission);
- }
- }
-
- async requestNotificationPermission() {
- if (Notification.permission === 'default') {
- // Only request permission when user interacts with a relevant feature
- // For now, just return the current status
- return Notification.permission;
- }
- return Notification.permission;
- }
-
- // Show notification (if permission granted)
- showNotification(title, options = {}) {
- if (Notification.permission === 'granted') {
- const notification = new Notification(title, {
- icon: '/icons/icon-192x192.png',
- badge: '/icons/icon-72x72.png',
- tag: 'littleshop-admin',
- ...options
- });
-
- // Auto-close after 5 seconds
- setTimeout(() => {
- notification.close();
- }, 5000);
-
- return notification;
- }
- }
-
- // Show manual install button for browsers that don't auto-prompt
- showManualInstallButton() {
- // Don't show install button if app is already installed
- if (this.isInstalled()) {
- console.log('PWA: App already installed, skipping manual install button');
- return;
- }
-
- console.log('PWA: Showing manual install button');
- const installBtn = document.createElement('button');
- installBtn.id = 'pwa-install-btn';
- installBtn.className = 'btn btn-primary btn-sm';
- installBtn.innerHTML = ' Install as App';
- installBtn.style.cssText = `
- position: fixed;
- bottom: 20px;
- right: 20px;
- z-index: 1000;
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
- `;
-
- installBtn.addEventListener('click', () => {
- const isChrome = navigator.userAgent.includes('Chrome');
- const isEdge = navigator.userAgent.includes('Edge');
- const isFirefox = navigator.userAgent.includes('Firefox');
-
- let instructions = 'To install this app:\\n\\n';
-
- if (isChrome || isEdge) {
- instructions += '1. Look for the install icon (⬇️) in the address bar\\n';
- instructions += '2. Or click the browser menu (⋮) → "Install LittleShop Admin"\\n';
- instructions += '3. Or check if there\'s an "Install app" option in the browser menu';
- } else if (isFirefox) {
- instructions += '1. Firefox doesn\'t support PWA installation yet\\n';
- instructions += '2. You can bookmark this page for easy access\\n';
- instructions += '3. Or use Chrome/Edge for the full PWA experience';
- } else {
- instructions += '1. Look for an install or "Add to Home Screen" option\\n';
- instructions += '2. Check your browser menu for app installation\\n';
- instructions += '3. Or bookmark this page for quick access';
- }
-
- alert(instructions);
- });
-
- document.body.appendChild(installBtn);
- }
-
- // Check if app is installed
- isInstalled() {
- return window.matchMedia('(display-mode: standalone)').matches ||
- window.navigator.standalone === true;
- }
-
- // Setup push notifications
- async setupPushNotifications() {
- if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
- console.log('PWA: Push notifications not supported');
- return;
- }
-
- try {
- // Get VAPID public key from server
- await this.getVapidPublicKey();
-
- // Check if user is already subscribed
- await this.checkPushSubscription();
-
- // Simple logic: only show prompt if user is not subscribed
- if (!this.pushSubscription) {
- // Check if we've already asked this session
- if (!sessionStorage.getItem('pushNotificationPromptShown')) {
- this.showPushNotificationSetup();
- sessionStorage.setItem('pushNotificationPromptShown', 'true');
- }
- } else {
- console.log('PWA: User already subscribed to push notifications');
- }
-
- } catch (error) {
- console.error('PWA: Failed to setup push notifications:', error);
- }
- }
-
- async getVapidPublicKey() {
- try {
- const response = await fetch('/api/push/vapid-key');
- if (response.ok) {
- const data = await response.json();
- this.vapidPublicKey = data.publicKey;
- console.log('PWA: VAPID public key retrieved');
- } else {
- throw new Error('Failed to get VAPID public key');
- }
- } catch (error) {
- console.error('PWA: Error getting VAPID public key:', error);
- throw error;
- }
- }
-
- async checkPushSubscription() {
- if (!this.swRegistration) {
- return;
- }
-
- try {
- this.pushSubscription = await this.swRegistration.pushManager.getSubscription();
- if (this.pushSubscription) {
- console.log('PWA: User has active push subscription');
- } else {
- console.log('PWA: User is not subscribed to push notifications');
- }
- } catch (error) {
- console.error('PWA: Error checking push subscription:', error);
- }
- }
-
- async subscribeToPushNotifications() {
- if (!this.swRegistration || !this.vapidPublicKey) {
- throw new Error('Service worker or VAPID key not available');
- }
-
- try {
- // Check current permission status
- if (Notification.permission === 'denied') {
- throw new Error('Notification permission was denied. Please enable notifications in your browser settings.');
- }
-
- // Request notification permission if not already granted
- let permission = Notification.permission;
- if (permission === 'default') {
- permission = await Notification.requestPermission();
- }
-
- if (permission !== 'granted') {
- throw new Error('Notification permission is required for push notifications. Please allow notifications and try again.');
- }
-
- // Subscribe to push notifications
- const subscription = await this.swRegistration.pushManager.subscribe({
- userVisibleOnly: true,
- applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
- });
-
- // Send subscription to server with timeout
- console.log('PWA: Sending subscription to server...');
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
-
- const response = await fetch('/api/push/subscribe', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- endpoint: subscription.endpoint,
- p256dh: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('p256dh')))),
- auth: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('auth'))))
- }),
- credentials: 'same-origin',
- signal: controller.signal
- });
-
- clearTimeout(timeoutId);
- console.log('PWA: Server response received:', response.status, response.statusText);
-
- if (response.ok) {
- this.pushSubscription = subscription;
- console.log('PWA: Successfully subscribed to push notifications');
- this.hidePushNotificationSetup();
- return true;
- } else {
- throw new Error('Failed to save push subscription to server');
- }
- } catch (error) {
- console.error('PWA: Failed to subscribe to push notifications:', error);
- throw error;
- }
- }
-
- async unsubscribeFromPushNotifications() {
- if (!this.pushSubscription) {
- return true;
- }
-
- try {
- // Unsubscribe from push manager
- await this.pushSubscription.unsubscribe();
-
- // Notify server
- await fetch('/api/push/unsubscribe', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- endpoint: this.pushSubscription.endpoint
- }),
- credentials: 'same-origin'
- });
-
- this.pushSubscription = null;
- console.log('PWA: Successfully unsubscribed from push notifications');
- return true;
- } catch (error) {
- console.error('PWA: Failed to unsubscribe from push notifications:', error);
- throw error;
- }
- }
-
- showPushNotificationSetup() {
- // Check if setup UI already exists
- if (document.getElementById('push-notification-setup')) {
- return;
- }
-
- const setupDiv = document.createElement('div');
- setupDiv.id = 'push-notification-setup';
- setupDiv.className = 'alert alert-info alert-dismissible';
- setupDiv.style.cssText = `
- position: fixed;
- top: 80px;
- right: 20px;
- z-index: 1050;
- max-width: 350px;
- `;
-
- setupDiv.innerHTML = `
-
-
-
- Push Notifications
- Get notified of new orders and updates
-
-
-
-
-
-
- `;
-
- document.body.appendChild(setupDiv);
-
- // Add event listener for subscribe button
- const subscribeBtn = document.getElementById('subscribe-push-btn');
-
- if (subscribeBtn) {
- subscribeBtn.addEventListener('click', async () => {
- subscribeBtn.disabled = true;
- subscribeBtn.innerHTML = 'Subscribing...';
-
- try {
- // Add timeout to prevent infinite hanging
- const subscriptionPromise = this.subscribeToPushNotifications();
- const timeoutPromise = new Promise((_, reject) =>
- setTimeout(() => reject(new Error('Push subscription timed out after 15 seconds. This may be due to network connectivity or browser push service issues.')), 15000)
- );
-
- await Promise.race([subscriptionPromise, timeoutPromise]);
-
- this.showNotification('Push notifications enabled!', {
- body: 'You will now receive notifications for new orders and updates.'
- });
- } catch (error) {
- console.error('PWA: Push subscription failed:', error);
-
- // Provide user-friendly error messages
- let userMessage = error.message;
- if (error.message.includes('permission')) {
- userMessage = 'Please allow notifications when your browser asks, then try again.';
- } else if (error.message.includes('timeout')) {
- userMessage = 'Push notification setup timed out. This may be due to network or browser issues. Please try again or check your internet connection.';
- }
-
- alert('Failed to enable push notifications: ' + userMessage);
- subscribeBtn.disabled = false;
- subscribeBtn.innerHTML = 'Enable';
- }
- });
- }
- }
-
- hidePushNotificationSetup() {
- const setupDiv = document.getElementById('push-notification-setup');
- if (setupDiv) {
- setupDiv.remove();
- console.log('PWA: Push notification setup hidden');
- }
- }
-
- async sendTestNotification() {
- try {
- const response = await fetch('/api/push/test', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- title: 'Test Notification',
- body: 'This is a test push notification from LittleShop Admin!'
- }),
- credentials: 'same-origin'
- });
-
- const result = await response.json();
- if (response.ok) {
- console.log('PWA: Test notification sent successfully');
- return true;
- } else {
- throw new Error(result.error || 'Failed to send test notification');
- }
- } catch (error) {
- console.error('PWA: Failed to send test notification:', error);
- throw error;
- }
- }
-
- // Helper function to convert VAPID key
- 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;
- }
-}
-
-// Initialize PWA Manager
-const pwaManager = new PWAManager();
-window.pwaManager = pwaManager;
-
-// Expose notification functions globally
-window.showNotification = (title, options) => pwaManager.showNotification(title, options);
-window.sendTestPushNotification = () => pwaManager.sendTestNotification();
-window.subscribeToPushNotifications = () => pwaManager.subscribeToPushNotifications();
+// Progressive Web App functionality
+// Handles service worker registration and PWA features
+
+class PWAManager {
+ constructor() {
+ this.swRegistration = null;
+ this.vapidPublicKey = null;
+ this.pushSubscription = null;
+ this.init();
+ }
+
+ async init() {
+ console.log('PWA: Initializing PWA Manager...');
+
+ if ('serviceWorker' in navigator) {
+ try {
+ this.swRegistration = await navigator.serviceWorker.register('/sw.js');
+ console.log('SW: Service Worker registered successfully');
+
+ // Listen for updates
+ this.swRegistration.addEventListener('updatefound', () => {
+ console.log('SW: New version available');
+ this.showUpdateNotification();
+ });
+
+ } catch (error) {
+ console.log('SW: Service Worker registration failed:', error);
+ }
+ }
+
+ // Setup PWA install prompt
+ this.setupInstallPrompt();
+
+ // Setup notifications (if enabled)
+ this.setupNotifications();
+
+ // Setup push notifications
+ this.setupPushNotifications();
+
+ // Show manual install option after 5 seconds if no prompt appeared and app not installed
+ setTimeout(() => {
+ if (!document.getElementById('pwa-install-btn') && !this.isInstalled()) {
+ console.log('PWA: No install prompt appeared, showing manual install guide');
+ this.showManualInstallButton();
+ }
+ }, 5000);
+ }
+
+ setupInstallPrompt() {
+ let deferredPrompt;
+
+ window.addEventListener('beforeinstallprompt', (e) => {
+ console.log('PWA: beforeinstallprompt event fired');
+ // Prevent Chrome 67 and earlier from automatically showing the prompt
+ e.preventDefault();
+ deferredPrompt = e;
+
+ // Show custom install button
+ this.showInstallButton(deferredPrompt);
+ });
+
+ window.addEventListener('appinstalled', () => {
+ console.log('PWA: App was installed');
+ this.hideInstallButton();
+ });
+
+ // Debug: Check if app is already installed
+ if (this.isInstalled()) {
+ console.log('PWA: App is already installed (standalone mode)');
+ // Hide any existing install buttons
+ this.hideInstallButton();
+ } else {
+ console.log('PWA: App is not installed, waiting for install prompt...');
+ console.log('PWA: Current URL:', window.location.href);
+ console.log('PWA: Display mode:', window.matchMedia('(display-mode: standalone)').matches ? 'standalone' : 'browser');
+ console.log('PWA: User agent:', navigator.userAgent);
+ }
+
+ // Periodically check if app becomes installed (for cases where user installs via browser menu)
+ setInterval(() => {
+ if (this.isInstalled()) {
+ this.hideInstallButton();
+ }
+ }, 2000);
+ }
+
+ showInstallButton(deferredPrompt) {
+ // Don't show install button if app is already installed
+ if (this.isInstalled()) {
+ console.log('PWA: App already installed, skipping install button');
+ return;
+ }
+
+ // Create install button
+ const installBtn = document.createElement('button');
+ installBtn.id = 'pwa-install-btn';
+ installBtn.className = 'btn btn-primary btn-sm';
+ installBtn.innerHTML = ' Install App';
+ installBtn.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ z-index: 1000;
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
+ `;
+
+ installBtn.addEventListener('click', async () => {
+ if (deferredPrompt) {
+ deferredPrompt.prompt();
+ const { outcome } = await deferredPrompt.userChoice;
+ console.log('PWA: User response to install prompt:', outcome);
+ deferredPrompt = null;
+ this.hideInstallButton();
+ }
+ });
+
+ document.body.appendChild(installBtn);
+ }
+
+ hideInstallButton() {
+ const installBtn = document.getElementById('pwa-install-btn');
+ if (installBtn) {
+ installBtn.remove();
+ }
+ }
+
+ showUpdateNotification() {
+ // Show update available notification
+ const notification = document.createElement('div');
+ notification.className = 'alert alert-info alert-dismissible';
+ notification.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 1050;
+ max-width: 300px;
+ `;
+
+ notification.innerHTML = `
+ Update Available!
+ A new version of the app is ready.
+
+
+ `;
+
+ document.body.appendChild(notification);
+
+ // Handle update
+ document.getElementById('update-btn').addEventListener('click', () => {
+ if (this.swRegistration && this.swRegistration.waiting) {
+ this.swRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
+ window.location.reload();
+ }
+ });
+ }
+
+ async setupNotifications() {
+ // Check if notifications are supported and get permission
+ if ('Notification' in window) {
+ const permission = await this.requestNotificationPermission();
+ console.log('Notifications permission:', permission);
+ }
+ }
+
+ async requestNotificationPermission() {
+ if (Notification.permission === 'default') {
+ // Only request permission when user interacts with a relevant feature
+ // For now, just return the current status
+ return Notification.permission;
+ }
+ return Notification.permission;
+ }
+
+ // Show notification (if permission granted)
+ showNotification(title, options = {}) {
+ if (Notification.permission === 'granted') {
+ const notification = new Notification(title, {
+ icon: '/icons/icon-192x192.png',
+ badge: '/icons/icon-72x72.png',
+ tag: 'littleshop-admin',
+ ...options
+ });
+
+ // Auto-close after 5 seconds
+ setTimeout(() => {
+ notification.close();
+ }, 5000);
+
+ return notification;
+ }
+ }
+
+ // Show manual install button for browsers that don't auto-prompt
+ showManualInstallButton() {
+ // Don't show install button if app is already installed
+ if (this.isInstalled()) {
+ console.log('PWA: App already installed, skipping manual install button');
+ return;
+ }
+
+ console.log('PWA: Showing manual install button');
+ const installBtn = document.createElement('button');
+ installBtn.id = 'pwa-install-btn';
+ installBtn.className = 'btn btn-primary btn-sm';
+ installBtn.innerHTML = ' Install as App';
+ installBtn.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ z-index: 1000;
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
+ `;
+
+ installBtn.addEventListener('click', () => {
+ const isChrome = navigator.userAgent.includes('Chrome');
+ const isEdge = navigator.userAgent.includes('Edge');
+ const isFirefox = navigator.userAgent.includes('Firefox');
+
+ let instructions = 'To install this app:\\n\\n';
+
+ if (isChrome || isEdge) {
+ instructions += '1. Look for the install icon (⬇️) in the address bar\\n';
+ instructions += '2. Or click the browser menu (⋮) → "Install LittleShop Admin"\\n';
+ instructions += '3. Or check if there\'s an "Install app" option in the browser menu';
+ } else if (isFirefox) {
+ instructions += '1. Firefox doesn\'t support PWA installation yet\\n';
+ instructions += '2. You can bookmark this page for easy access\\n';
+ instructions += '3. Or use Chrome/Edge for the full PWA experience';
+ } else {
+ instructions += '1. Look for an install or "Add to Home Screen" option\\n';
+ instructions += '2. Check your browser menu for app installation\\n';
+ instructions += '3. Or bookmark this page for quick access';
+ }
+
+ alert(instructions);
+ });
+
+ document.body.appendChild(installBtn);
+ }
+
+ // Check if app is installed
+ isInstalled() {
+ return window.matchMedia('(display-mode: standalone)').matches ||
+ window.navigator.standalone === true;
+ }
+
+ // Setup push notifications
+ async setupPushNotifications() {
+ if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
+ console.log('PWA: Push notifications not supported');
+ return;
+ }
+
+ try {
+ // Get VAPID public key from server
+ await this.getVapidPublicKey();
+
+ // Check if user is already subscribed
+ await this.checkPushSubscription();
+
+ // Simple logic: only show prompt if user is not subscribed
+ if (!this.pushSubscription) {
+ // Check if we've already asked this session
+ if (!sessionStorage.getItem('pushNotificationPromptShown')) {
+ this.showPushNotificationSetup();
+ sessionStorage.setItem('pushNotificationPromptShown', 'true');
+ }
+ } else {
+ console.log('PWA: User already subscribed to push notifications');
+ }
+
+ } catch (error) {
+ console.error('PWA: Failed to setup push notifications:', error);
+ }
+ }
+
+ async getVapidPublicKey() {
+ try {
+ const response = await fetch('/api/push/vapid-key');
+ if (response.ok) {
+ const data = await response.json();
+ this.vapidPublicKey = data.publicKey;
+ console.log('PWA: VAPID public key retrieved');
+ } else {
+ throw new Error('Failed to get VAPID public key');
+ }
+ } catch (error) {
+ console.error('PWA: Error getting VAPID public key:', error);
+ throw error;
+ }
+ }
+
+ async checkPushSubscription() {
+ if (!this.swRegistration) {
+ return;
+ }
+
+ try {
+ this.pushSubscription = await this.swRegistration.pushManager.getSubscription();
+ if (this.pushSubscription) {
+ console.log('PWA: User has active push subscription');
+ } else {
+ console.log('PWA: User is not subscribed to push notifications');
+ }
+ } catch (error) {
+ console.error('PWA: Error checking push subscription:', error);
+ }
+ }
+
+ async subscribeToPushNotifications() {
+ if (!this.swRegistration || !this.vapidPublicKey) {
+ throw new Error('Service worker or VAPID key not available');
+ }
+
+ try {
+ // Check current permission status
+ if (Notification.permission === 'denied') {
+ throw new Error('Notification permission was denied. Please enable notifications in your browser settings.');
+ }
+
+ // Request notification permission if not already granted
+ let permission = Notification.permission;
+ if (permission === 'default') {
+ permission = await Notification.requestPermission();
+ }
+
+ if (permission !== 'granted') {
+ throw new Error('Notification permission is required for push notifications. Please allow notifications and try again.');
+ }
+
+ // Subscribe to push notifications with enhanced debugging
+ console.log('PWA: Requesting push subscription from browser...');
+ console.log('PWA: VAPID public key (first 32 chars):', this.vapidPublicKey.substring(0, 32) + '...');
+
+ const subscriptionStartTime = Date.now();
+ let subscription;
+
+ try {
+ subscription = await Promise.race([
+ this.swRegistration.pushManager.subscribe({
+ userVisibleOnly: true,
+ applicationServerKey: this.urlBase64ToUint8Array(this.vapidPublicKey)
+ }),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Browser push subscription timed out after 30 seconds. This may indicate an issue with your browser\'s push service connectivity.')), 30000)
+ )
+ ]);
+
+ const subscriptionTime = Date.now() - subscriptionStartTime;
+ console.log(`PWA: Browser subscription completed in ${subscriptionTime}ms`);
+ console.log('PWA: Subscription endpoint:', subscription.endpoint);
+
+ } catch (subscriptionError) {
+ console.error('PWA: Browser subscription failed:', subscriptionError);
+ throw new Error(`Failed to subscribe with browser push service: ${subscriptionError.message}`);
+ }
+
+ // Send subscription to server with timeout
+ console.log('PWA: Sending subscription to server...');
+ const serverStartTime = Date.now();
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => {
+ controller.abort();
+ console.error('PWA: Server request timeout after 15 seconds');
+ }, 15000);
+
+ const response = await fetch('/api/push/subscribe', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ endpoint: subscription.endpoint,
+ p256dh: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('p256dh')))),
+ auth: btoa(String.fromCharCode(...new Uint8Array(subscription.getKey('auth'))))
+ }),
+ credentials: 'same-origin',
+ signal: controller.signal
+ });
+
+ clearTimeout(timeoutId);
+ const serverTime = Date.now() - serverStartTime;
+ console.log(`PWA: Server response received in ${serverTime}ms:`, response.status, response.statusText);
+
+ if (response.ok) {
+ this.pushSubscription = subscription;
+ console.log('PWA: Successfully subscribed to push notifications');
+ this.hidePushNotificationSetup();
+ return true;
+ } else {
+ throw new Error('Failed to save push subscription to server');
+ }
+ } catch (error) {
+ console.error('PWA: Failed to subscribe to push notifications:', error);
+ throw error;
+ }
+ }
+
+ async unsubscribeFromPushNotifications() {
+ if (!this.pushSubscription) {
+ return true;
+ }
+
+ try {
+ // Unsubscribe from push manager
+ await this.pushSubscription.unsubscribe();
+
+ // Notify server
+ await fetch('/api/push/unsubscribe', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ endpoint: this.pushSubscription.endpoint
+ }),
+ credentials: 'same-origin'
+ });
+
+ this.pushSubscription = null;
+ console.log('PWA: Successfully unsubscribed from push notifications');
+ return true;
+ } catch (error) {
+ console.error('PWA: Failed to unsubscribe from push notifications:', error);
+ throw error;
+ }
+ }
+
+ showPushNotificationSetup() {
+ // Check if setup UI already exists
+ if (document.getElementById('push-notification-setup')) {
+ return;
+ }
+
+ const setupDiv = document.createElement('div');
+ setupDiv.id = 'push-notification-setup';
+ setupDiv.className = 'alert alert-info alert-dismissible';
+ setupDiv.style.cssText = `
+ position: fixed;
+ top: 80px;
+ right: 20px;
+ z-index: 1050;
+ max-width: 350px;
+ `;
+
+ setupDiv.innerHTML = `
+
+
+
+ Push Notifications
+ Get notified of new orders and updates
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(setupDiv);
+
+ // Add event listener for subscribe button
+ const subscribeBtn = document.getElementById('subscribe-push-btn');
+
+ if (subscribeBtn) {
+ subscribeBtn.addEventListener('click', async () => {
+ subscribeBtn.disabled = true;
+ subscribeBtn.innerHTML = 'Subscribing...';
+
+ try {
+ // Add timeout to prevent infinite hanging
+ const subscriptionPromise = this.subscribeToPushNotifications();
+ const timeoutPromise = new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Push subscription timed out after 15 seconds. This may be due to network connectivity or browser push service issues.')), 15000)
+ );
+
+ await Promise.race([subscriptionPromise, timeoutPromise]);
+
+ this.showNotification('Push notifications enabled!', {
+ body: 'You will now receive notifications for new orders and updates.'
+ });
+ } catch (error) {
+ console.error('PWA: Push subscription failed:', error);
+
+ // Provide user-friendly error messages
+ let userMessage = error.message;
+ if (error.message.includes('permission')) {
+ userMessage = 'Please allow notifications when your browser asks, then try again.';
+ } else if (error.message.includes('timeout')) {
+ userMessage = 'Push notification setup timed out. This may be due to network or browser push service issues. Please check your internet connection and try again.';
+ } else if (error.message.includes('push service')) {
+ userMessage = 'Failed to connect to browser push service. This may be a temporary network issue. Please try again in a few moments.';
+ } else if (error.message.includes('AbortError')) {
+ userMessage = 'Request was cancelled due to timeout. Please check your internet connection and try again.';
+ }
+
+ console.error('PWA: Full error details:', error);
+ alert('Failed to enable push notifications: ' + userMessage);
+ subscribeBtn.disabled = false;
+ subscribeBtn.innerHTML = 'Enable';
+ }
+ });
+ }
+ }
+
+ hidePushNotificationSetup() {
+ const setupDiv = document.getElementById('push-notification-setup');
+ if (setupDiv) {
+ setupDiv.remove();
+ console.log('PWA: Push notification setup hidden');
+ }
+ }
+
+ async sendTestNotification() {
+ try {
+ const response = await fetch('/api/push/test', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ title: 'Test Notification',
+ body: 'This is a test push notification from LittleShop Admin!'
+ }),
+ credentials: 'same-origin'
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ console.log('PWA: Test notification sent successfully');
+ return true;
+ } else {
+ throw new Error(result.error || 'Failed to send test notification');
+ }
+ } catch (error) {
+ console.error('PWA: Failed to send test notification:', error);
+ throw error;
+ }
+ }
+
+ // Helper function to convert VAPID key
+ 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;
+ }
+}
+
+// Initialize PWA Manager
+const pwaManager = new PWAManager();
+window.pwaManager = pwaManager;
+
+// Expose notification functions globally
+window.showNotification = (title, options) => pwaManager.showNotification(title, options);
+window.sendTestPushNotification = () => pwaManager.sendTestNotification();
+window.subscribeToPushNotifications = () => pwaManager.subscribeToPushNotifications();
window.unsubscribeFromPushNotifications = () => pwaManager.unsubscribeFromPushNotifications();
\ No newline at end of file