WebPush-and-photo-upload-fixes
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
class PWAManager {
|
||||
constructor() {
|
||||
this.swRegistration = null;
|
||||
this.vapidPublicKey = null;
|
||||
this.pushSubscription = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -32,9 +34,12 @@ class PWAManager {
|
||||
// Setup notifications (if enabled)
|
||||
this.setupNotifications();
|
||||
|
||||
// Show manual install option after 3 seconds if no prompt appeared
|
||||
// Setup push notifications
|
||||
this.setupPushNotifications();
|
||||
|
||||
// Show manual install option after 3 seconds if no prompt appeared and app not installed
|
||||
setTimeout(() => {
|
||||
if (!document.getElementById('pwa-install-btn')) {
|
||||
if (!document.getElementById('pwa-install-btn') && !this.isInstalled()) {
|
||||
this.showManualInstallButton();
|
||||
}
|
||||
}, 3000);
|
||||
@@ -61,12 +66,27 @@ class PWAManager {
|
||||
// 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...');
|
||||
}
|
||||
|
||||
// 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';
|
||||
@@ -170,6 +190,12 @@ class PWAManager {
|
||||
|
||||
// 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';
|
||||
@@ -195,11 +221,383 @@ class PWAManager {
|
||||
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 {
|
||||
// Check if user has dismissed push notifications recently
|
||||
const dismissedUntil = localStorage.getItem('pushNotificationsDismissedUntil');
|
||||
if (dismissedUntil && new Date() < new Date(dismissedUntil)) {
|
||||
console.log('PWA: Push notifications dismissed by user, skipping setup');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get VAPID public key from server
|
||||
await this.getVapidPublicKey();
|
||||
|
||||
// Check if user is already subscribed
|
||||
await this.checkPushSubscription();
|
||||
|
||||
// Only show setup UI if not subscribed and not recently dismissed
|
||||
const isSubscribedFromCache = localStorage.getItem('pushNotificationsSubscribed') === 'true';
|
||||
if (!this.pushSubscription && !isSubscribedFromCache) {
|
||||
this.showPushNotificationSetup();
|
||||
} else {
|
||||
console.log('PWA: User already subscribed to push notifications, skipping setup UI');
|
||||
}
|
||||
|
||||
} 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: Browser has push subscription');
|
||||
|
||||
// Verify server-side subscription still exists by trying to send a test
|
||||
try {
|
||||
const response = await fetch('/api/push/subscriptions', {
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const subscriptions = await response.json();
|
||||
const hasServerSubscription = subscriptions.some(sub =>
|
||||
sub.endpoint && this.pushSubscription.endpoint.includes(sub.endpoint.substring(0, 50))
|
||||
);
|
||||
|
||||
if (!hasServerSubscription) {
|
||||
console.log('PWA: Server subscription missing, will re-subscribe on next attempt');
|
||||
localStorage.removeItem('pushNotificationsSubscribed');
|
||||
} else {
|
||||
console.log('PWA: Server subscription confirmed');
|
||||
localStorage.setItem('pushNotificationsSubscribed', 'true');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('PWA: Could not verify server subscription:', error.message);
|
||||
}
|
||||
} else {
|
||||
console.log('PWA: User is not subscribed to push notifications');
|
||||
localStorage.removeItem('pushNotificationsSubscribed');
|
||||
}
|
||||
} 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
|
||||
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'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
this.pushSubscription = subscription;
|
||||
console.log('PWA: Successfully subscribed to push notifications');
|
||||
|
||||
// Cache subscription state
|
||||
localStorage.setItem('pushNotificationsSubscribed', 'true');
|
||||
localStorage.setItem('pushNotificationsSubscribedAt', new Date().toISOString());
|
||||
|
||||
this.updatePushNotificationUI();
|
||||
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');
|
||||
|
||||
// Clear subscription cache
|
||||
localStorage.removeItem('pushNotificationsSubscribed');
|
||||
localStorage.removeItem('pushNotificationsSubscribedAt');
|
||||
|
||||
this.updatePushNotificationUI();
|
||||
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;
|
||||
`;
|
||||
|
||||
const isSubscribed = !!this.pushSubscription;
|
||||
|
||||
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>${isSubscribed ? 'You are subscribed to notifications' : 'Get notified of new orders and updates'}</small>
|
||||
</div>
|
||||
<div class="ms-2">
|
||||
${isSubscribed ?
|
||||
'<button type="button" class="btn btn-sm btn-outline-danger" id="unsubscribe-push-btn">Turn Off</button>' :
|
||||
'<div class="d-flex flex-column gap-1"><button type="button" class="btn btn-sm btn-primary" id="subscribe-push-btn">Turn On</button><button type="button" class="btn btn-sm btn-outline-secondary" id="dismiss-push-btn">Not Now</button></div>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" id="close-push-setup"></button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(setupDiv);
|
||||
|
||||
// Add event listeners
|
||||
const subscribeBtn = document.getElementById('subscribe-push-btn');
|
||||
const unsubscribeBtn = document.getElementById('unsubscribe-push-btn');
|
||||
const dismissBtn = document.getElementById('dismiss-push-btn');
|
||||
const closeBtn = document.getElementById('close-push-setup');
|
||||
|
||||
if (subscribeBtn) {
|
||||
subscribeBtn.addEventListener('click', async () => {
|
||||
subscribeBtn.disabled = true;
|
||||
subscribeBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Subscribing...';
|
||||
|
||||
try {
|
||||
await this.subscribeToPushNotifications();
|
||||
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.';
|
||||
}
|
||||
|
||||
alert('Failed to enable push notifications: ' + userMessage);
|
||||
subscribeBtn.disabled = false;
|
||||
subscribeBtn.innerHTML = 'Turn On';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (unsubscribeBtn) {
|
||||
unsubscribeBtn.addEventListener('click', async () => {
|
||||
unsubscribeBtn.disabled = true;
|
||||
unsubscribeBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Disabling...';
|
||||
|
||||
try {
|
||||
await this.unsubscribeFromPushNotifications();
|
||||
this.showNotification('Push notifications disabled', {
|
||||
body: 'You will no longer receive push notifications.'
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Failed to disable push notifications: ' + error.message);
|
||||
unsubscribeBtn.disabled = false;
|
||||
unsubscribeBtn.innerHTML = 'Turn Off';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle dismiss button
|
||||
if (dismissBtn) {
|
||||
dismissBtn.addEventListener('click', () => {
|
||||
// Dismiss for 24 hours
|
||||
const dismissUntil = new Date();
|
||||
dismissUntil.setHours(dismissUntil.getHours() + 24);
|
||||
localStorage.setItem('pushNotificationsDismissedUntil', dismissUntil.toISOString());
|
||||
|
||||
const element = document.getElementById('push-notification-setup');
|
||||
if (element) {
|
||||
element.remove();
|
||||
}
|
||||
console.log('PWA: Push notifications dismissed for 24 hours');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle close button
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
// Dismiss for 1 hour
|
||||
const dismissUntil = new Date();
|
||||
dismissUntil.setHours(dismissUntil.getHours() + 1);
|
||||
localStorage.setItem('pushNotificationsDismissedUntil', dismissUntil.toISOString());
|
||||
console.log('PWA: Push notifications dismissed for 1 hour');
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-hide after 15 seconds
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById('push-notification-setup');
|
||||
if (element) {
|
||||
element.remove();
|
||||
|
||||
// Auto-dismiss for 2 hours if user ignores
|
||||
const dismissUntil = new Date();
|
||||
dismissUntil.setHours(dismissUntil.getHours() + 2);
|
||||
localStorage.setItem('pushNotificationsDismissedUntil', dismissUntil.toISOString());
|
||||
console.log('PWA: Push notifications auto-dismissed for 2 hours');
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
updatePushNotificationUI() {
|
||||
const setupDiv = document.getElementById('push-notification-setup');
|
||||
if (setupDiv) {
|
||||
setupDiv.remove();
|
||||
this.showPushNotificationSetup();
|
||||
}
|
||||
}
|
||||
|
||||
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 function globally
|
||||
window.showNotification = (title, options) => pwaManager.showNotification(title, options);
|
||||
// Expose notification functions globally
|
||||
window.showNotification = (title, options) => pwaManager.showNotification(title, options);
|
||||
window.sendTestPushNotification = () => pwaManager.sendTestNotification();
|
||||
window.subscribeToPushNotifications = () => pwaManager.subscribeToPushNotifications();
|
||||
window.unsubscribeFromPushNotifications = () => pwaManager.unsubscribeFromPushNotifications();
|
||||
Reference in New Issue
Block a user