UNPKG

progresspulse-pwa

Version:

A modern PWA for tracking progress and achieving goals with iPhone-style design

291 lines (249 loc) 10.7 kB
import { useEffect, useState } from 'react'; import { pushNotificationService } from '@/services/pushNotificationService'; export function useAPKNotifications() { const [isAPK, setIsAPK] = useState(false); const [permissionRequested, setPermissionRequested] = useState(false); useEffect(() => { const checkAPKEnvironment = () => { const userAgent = navigator.userAgent.toLowerCase(); const isAndroidWebView = userAgent.includes('wv') && userAgent.includes('android'); const isCapacitor = !!(window as any).Capacitor; const isCordova = !!(window as any).cordova; const isStandalone = window.matchMedia('(display-mode: standalone)').matches; const isAndroidChrome = userAgent.includes('android') && userAgent.includes('chrome'); const isPWABuilder = userAgent.includes('pwabuilder') || userAgent.includes('twa'); const isWebAPK = userAgent.includes('webapk'); // More aggressive APK detection const apkDetected = isAndroidWebView || isCapacitor || isCordova || isPWABuilder || isWebAPK || (isStandalone && isAndroidChrome) || (window.location.protocol === 'https:' && isStandalone); console.log('🔔 APK Detection:', { isAndroidWebView, isCapacitor, isCordova, isStandalone, isAndroidChrome, isPWABuilder, isWebAPK, apkDetected, userAgent, protocol: window.location.protocol }); setIsAPK(apkDetected); return apkDetected; }; const initializeAPKNotifications = async () => { const isAPKEnv = checkAPKEnvironment(); if (isAPKEnv && !permissionRequested) { console.log('🔔 APK detected, initializing notifications...'); // Wait for app to fully load setTimeout(async () => { try { // Register enhanced service worker for APK if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw-notifications.js', { scope: '/' }); console.log('🔔 APK notification service worker registered:', registration); // Wait for service worker to be ready await navigator.serviceWorker.ready; } // Force permission request for APK - more aggressive approach console.log('🔔 APK: Current permission status:', Notification.permission); if (Notification.permission === 'default' || Notification.permission === 'denied') { console.log('🔔 APK: Requesting notification permission with dialog'); // Show custom permission dialog first const userWantsNotifications = await showAPKPermissionDialog(); if (userWantsNotifications) { // Multiple attempts to request permission let permission = await requestNotificationPermissionAggressively(); console.log('🔔 APK: Final permission result:', permission); if (permission === 'granted') { console.log('🔔 APK: Notification permission granted'); // Set up background sync for APK await setupAPKBackgroundSync(); // Show welcome notification setTimeout(() => { pushNotificationService.showNotification('🎯 ProgressPulse Ready!', { body: 'Notifications enabled! You\'ll receive updates and reminders.', tag: 'apk-welcome', requireInteraction: false }); }, 1000); } else { console.log('🔔 APK: Notification permission denied'); } } else { console.log('🔔 APK: User declined notification permission'); } } else if (Notification.permission === 'granted') { console.log('🔔 APK: Notifications already granted'); await setupAPKBackgroundSync(); } setPermissionRequested(true); } catch (error) { console.error('🔔 APK notification initialization error:', error); } }, 3000); // Wait 3 seconds for app to load } }; initializeAPKNotifications(); }, [permissionRequested]); return { isAPK, permissionRequested }; } async function setupAPKBackgroundSync() { try { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.ready; // Try to register background sync (may not be available in all browsers) try { const syncRegistration = (registration as any).sync; if (syncRegistration) { await syncRegistration.register('daily-reminder'); console.log('🔔 APK: Background sync registered for daily reminders'); await syncRegistration.register('check-updates'); console.log('🔔 APK: Background sync registered for update checks'); } } catch (error) { console.log('🔔 APK: Background sync not available:', (error as Error).message); } // Try to register periodic sync if available (experimental) try { const periodicSync = (registration as any).periodicSync; if (periodicSync) { await periodicSync.register('daily-reminder', { minInterval: 24 * 60 * 60 * 1000 // 24 hours }); console.log('🔔 APK: Periodic sync registered for daily reminders'); } } catch (error) { console.log('🔔 APK: Periodic sync not available:', (error as Error).message); } } } catch (error) { console.error('🔔 APK: Error setting up background sync:', error); } } // Aggressive notification permission request for APK async function requestNotificationPermissionAggressively(): Promise<NotificationPermission> { console.log('🔔 APK: Starting aggressive permission request...'); try { // Method 1: Standard request let permission = await Notification.requestPermission(); console.log('🔔 APK: Standard request result:', permission); if (permission === 'granted') return permission; // Method 2: Try with user gesture simulation await new Promise(resolve => setTimeout(resolve, 500)); // Create a user interaction event const button = document.createElement('button'); button.style.position = 'fixed'; button.style.top = '-1000px'; button.style.left = '-1000px'; button.style.opacity = '0'; button.style.pointerEvents = 'none'; document.body.appendChild(button); button.click(); permission = await Notification.requestPermission(); console.log('🔔 APK: Second attempt result:', permission); document.body.removeChild(button); if (permission === 'granted') return permission; // Method 3: Try with service worker registration if ('serviceWorker' in navigator) { try { const registration = await navigator.serviceWorker.ready; if (registration.pushManager) { await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: null }).catch(() => { console.log('🔔 APK: Push subscription failed, but continuing...'); }); } } catch (error) { console.log('🔔 APK: Service worker push attempt failed:', error); } permission = await Notification.requestPermission(); console.log('🔔 APK: Third attempt result:', permission); } return permission; } catch (error) { console.error('🔔 APK: Error in aggressive permission request:', error); return Notification.permission; } } // Custom permission dialog for APK function showAPKPermissionDialog(): Promise<boolean> { return new Promise((resolve) => { const dialog = document.createElement('div'); dialog.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `; dialog.innerHTML = ` <div style=" background: white; border-radius: 20px; padding: 30px; max-width: 320px; margin: 20px; text-align: center; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); "> <div style="font-size: 48px; margin-bottom: 20px;">🔔</div> <h2 style="margin: 0 0 15px 0; color: #333; font-size: 20px; font-weight: 600;"> Enable Notifications </h2> <p style="margin: 0 0 25px 0; color: #666; font-size: 16px; line-height: 1.4;"> Get daily reminders to track your progress and notifications when app updates are available. </p> <div style="display: flex; gap: 10px;"> <button id="deny-btn" style=" flex: 1; padding: 12px; border: 2px solid #ddd; background: white; color: #666; border-radius: 12px; font-size: 16px; font-weight: 500; cursor: pointer; ">Not Now</button> <button id="allow-btn" style=" flex: 1; padding: 12px; border: none; background: #007AFF; color: white; border-radius: 12px; font-size: 16px; font-weight: 500; cursor: pointer; ">Allow</button> </div> </div> `; document.body.appendChild(dialog); const allowBtn = dialog.querySelector('#allow-btn'); const denyBtn = dialog.querySelector('#deny-btn'); allowBtn?.addEventListener('click', () => { document.body.removeChild(dialog); resolve(true); }); denyBtn?.addEventListener('click', () => { document.body.removeChild(dialog); resolve(false); }); }); }