700 lines
28 KiB
JavaScript
700 lines
28 KiB
JavaScript
// 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 = '<i class="fas fa-download"></i> 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 = `
|
|
<strong>Update Available!</strong><br>
|
|
A new version of the app is ready.
|
|
<button type="button" class="btn btn-sm btn-outline-info ms-2" id="update-btn">
|
|
Update Now
|
|
</button>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
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 = '<i class="fas fa-mobile-alt"></i> 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 or user declined
|
|
const promptShown = sessionStorage.getItem('pushNotificationPromptShown');
|
|
const userDeclined = localStorage.getItem('pushNotificationDeclined');
|
|
|
|
if (!promptShown && !userDeclined) {
|
|
this.showPushNotificationSetup();
|
|
sessionStorage.setItem('pushNotificationPromptShown', 'true');
|
|
} else if (promptShown) {
|
|
console.log('PWA: Push notification prompt already shown this session');
|
|
} else if (userDeclined) {
|
|
console.log('PWA: User previously declined push notifications');
|
|
}
|
|
} 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.');
|
|
}
|
|
|
|
// Enhanced connectivity diagnostics
|
|
console.log('PWA: Running push service connectivity diagnostics...');
|
|
await this.runConnectivityDiagnostics();
|
|
|
|
// 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 {
|
|
// Try shorter timeout first to fail faster
|
|
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 15 seconds. This usually indicates a network connectivity issue with Chrome\'s Firebase Cloud Messaging (FCM) service.')), 15000)
|
|
)
|
|
]);
|
|
|
|
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);
|
|
|
|
// Show enhanced error with diagnostics
|
|
const diagnosticsInfo = await this.getDiagnosticsInfo();
|
|
throw new Error(`Failed to subscribe with browser push service: ${subscriptionError.message}\n\nDiagnostics:\n${diagnosticsInfo}`);
|
|
}
|
|
|
|
// 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 = `
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-bell me-2"></i>
|
|
<div class="flex-grow-1">
|
|
<strong>Push Notifications</strong><br>
|
|
<small>Get notified of new orders and updates</small>
|
|
</div>
|
|
<div class="ms-2">
|
|
<button type="button" class="btn btn-sm btn-primary" id="subscribe-push-btn">Enable</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-1" id="skip-push-btn">Skip</button>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
document.body.appendChild(setupDiv);
|
|
|
|
// Add event listener for subscribe button
|
|
const subscribeBtn = document.getElementById('subscribe-push-btn');
|
|
const skipBtn = document.getElementById('skip-push-btn');
|
|
|
|
if (subscribeBtn) {
|
|
subscribeBtn.addEventListener('click', async () => {
|
|
subscribeBtn.disabled = true;
|
|
skipBtn.disabled = true;
|
|
subscribeBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>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 Chrome\'s Firebase Cloud Messaging (FCM) service connectivity issues. This can happen with corporate firewalls or VPNs.')), 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 with specific guidance
|
|
let userMessage = error.message;
|
|
let showAdvice = false;
|
|
|
|
if (error.message.includes('permission')) {
|
|
userMessage = 'Please allow notifications when your browser asks, then try again.';
|
|
} else if (error.message.includes('timeout') || error.message.includes('FCM')) {
|
|
userMessage = 'Chrome\'s push notification service is not responding. This is often caused by:\n\n• Corporate firewall blocking Google FCM\n• VPN interference\n• Network connectivity issues\n\nYou can try again later or use the browser without push notifications.';
|
|
showAdvice = true;
|
|
} 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);
|
|
|
|
if (showAdvice) {
|
|
if (confirm(userMessage + '\n\nWould you like to skip push notifications for now?')) {
|
|
localStorage.setItem('pushNotificationDeclined', 'true');
|
|
this.hidePushNotificationSetup();
|
|
return;
|
|
}
|
|
} else {
|
|
alert('Failed to enable push notifications: ' + userMessage);
|
|
}
|
|
|
|
subscribeBtn.disabled = false;
|
|
skipBtn.disabled = false;
|
|
subscribeBtn.innerHTML = 'Enable';
|
|
}
|
|
});
|
|
}
|
|
|
|
if (skipBtn) {
|
|
skipBtn.addEventListener('click', () => {
|
|
console.log('PWA: User skipped push notifications');
|
|
localStorage.setItem('pushNotificationDeclined', 'true');
|
|
this.hidePushNotificationSetup();
|
|
});
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async runConnectivityDiagnostics() {
|
|
try {
|
|
console.log('PWA: Testing network connectivity...');
|
|
|
|
// Test basic internet connectivity
|
|
const startTime = Date.now();
|
|
const response = await fetch('https://www.google.com/generate_204', {
|
|
method: 'HEAD',
|
|
cache: 'no-cache',
|
|
timeout: 5000
|
|
});
|
|
const latency = Date.now() - startTime;
|
|
|
|
console.log(`PWA: Internet connectivity: ${response.ok ? 'OK' : 'FAILED'} (${latency}ms)`);
|
|
|
|
// Test FCM endpoint accessibility
|
|
try {
|
|
const fcmResponse = await fetch('https://fcm.googleapis.com/fcm/send', {
|
|
method: 'HEAD',
|
|
cache: 'no-cache',
|
|
timeout: 5000
|
|
});
|
|
console.log(`PWA: FCM service accessibility: ${fcmResponse.status === 404 ? 'OK' : 'UNKNOWN'}`);
|
|
} catch (fcmError) {
|
|
console.log('PWA: FCM service accessibility: BLOCKED or TIMEOUT');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.log('PWA: Network diagnostics failed:', error.message);
|
|
}
|
|
}
|
|
|
|
async getDiagnosticsInfo() {
|
|
const info = [];
|
|
|
|
// Browser info
|
|
info.push(`Browser: ${navigator.userAgent}`);
|
|
info.push(`Connection: ${navigator.onLine ? 'Online' : 'Offline'}`);
|
|
|
|
// Service Worker info
|
|
if ('serviceWorker' in navigator) {
|
|
info.push(`Service Worker: Supported`);
|
|
if (this.swRegistration) {
|
|
info.push(`SW State: ${this.swRegistration.active ? 'Active' : 'Inactive'}`);
|
|
}
|
|
} else {
|
|
info.push(`Service Worker: Not Supported`);
|
|
}
|
|
|
|
// Push Manager info
|
|
if ('PushManager' in window) {
|
|
info.push(`Push Manager: Supported`);
|
|
} else {
|
|
info.push(`Push Manager: Not Supported`);
|
|
}
|
|
|
|
// Notification permission
|
|
info.push(`Notification Permission: ${Notification.permission}`);
|
|
|
|
// Network info (if available)
|
|
if ('connection' in navigator) {
|
|
const conn = navigator.connection;
|
|
info.push(`Network Type: ${conn.effectiveType || 'Unknown'}`);
|
|
info.push(`Network Speed: ${conn.downlink || 'Unknown'}Mbps`);
|
|
}
|
|
|
|
return info.join('\n- ');
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Add console helper for diagnostics
|
|
window.testPushConnectivity = async function() {
|
|
console.log('🔍 Running Push Notification Connectivity Test...');
|
|
|
|
if (!window.pwaManager) {
|
|
console.log('❌ PWA Manager not initialized');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await window.pwaManager.runConnectivityDiagnostics();
|
|
const diagnostics = await window.pwaManager.getDiagnosticsInfo();
|
|
console.log('\n📊 System Diagnostics:');
|
|
console.log('- ' + diagnostics.split('\n- ').join('\n- '));
|
|
|
|
console.log('\n💡 To test manually, run:');
|
|
console.log('navigator.serviceWorker.register("/service-worker.js").then(reg => reg.pushManager.subscribe({userVisibleOnly: true, applicationServerKey: window.pwaManager.urlBase64ToUint8Array("BDJtQu7zV0H3KF4FkrZ8nPwP3YD_3cEz3hqJvQ6L_gvNpG8ANksQB-FZy2-PDmFAu6duiN4p3mkcNAGnN4YRbws")}))');
|
|
|
|
} catch (error) {
|
|
console.log('❌ Diagnostics failed:', error.message);
|
|
}
|
|
}; |