const webpush = require('web-push'); // VAPID Keys (sollten in Umgebungsvariablen gespeichert werden) const vapidKeys = { publicKey: 'BJmNVx0C3XeVxeKGTP9c-Z4HcuZNmdk6QdiLocZgCmb-miCS0ESFO3W2TvJlRhhNAShV63pWA5p36BTVSetyTds', privateKey: 'HBdRCtmZUAzsWpVjZ2LDaoWliIPHldAb5ExAt8bvDeg' }; // Configure web-push webpush.setVapidDetails( 'mailto:admin@reptilfpv.de', vapidKeys.publicKey, vapidKeys.privateKey ); class PushService { constructor() { this.subscriptions = new Map(); // Map> } // Subscribe user to push notifications subscribe(userId, subscription) { if (!this.subscriptions.has(userId)) { this.subscriptions.set(userId, new Set()); } this.subscriptions.get(userId).add(subscription); console.log(`User ${userId} subscribed to push notifications (${this.subscriptions.get(userId).size} total subscriptions)`); } // Unsubscribe user from push notifications unsubscribe(userId) { this.subscriptions.delete(userId); console.log(`User ${userId} unsubscribed from push notifications`); } // Unsubscribe specific device from push notifications unsubscribeDevice(userId, endpoint) { const subscriptions = this.subscriptions.get(userId); if (!subscriptions) { console.log(`No subscriptions found for user ${userId}`); return false; } // Find and remove the specific subscription by endpoint let removed = false; for (const subscription of subscriptions) { if (subscription.endpoint === endpoint) { subscriptions.delete(subscription); removed = true; break; } } // If no subscriptions left, remove the user entirely if (subscriptions.size === 0) { this.subscriptions.delete(userId); console.log(`User ${userId} unsubscribed from push notifications (all devices)`); } else { console.log(`Device ${endpoint} unsubscribed from push notifications for user ${userId} (${subscriptions.size} devices remaining)`); } return removed; } // Send push notification to specific user async sendToUser(userId, payload) { const subscriptions = this.subscriptions.get(userId); if (!subscriptions || subscriptions.size === 0) { console.log(`No subscriptions found for user ${userId}`); return false; } let successCount = 0; let totalCount = subscriptions.size; for (const subscription of subscriptions) { try { await webpush.sendNotification(subscription, JSON.stringify(payload)); successCount++; } catch (error) { console.error(`Error sending push notification to user ${userId}:`, error); // If subscription is invalid, remove it if (error.statusCode === 410) { subscriptions.delete(subscription); } } } console.log(`Push notification sent to ${successCount}/${totalCount} subscriptions for user ${userId}`); return successCount > 0; } // Send push notification to all subscribed users async sendToAll(payload) { const results = []; for (const [userId, subscription] of this.subscriptions) { const result = await this.sendToUser(userId, payload); results.push({ userId, success: result }); } return results; } // Send achievement notification async sendAchievementNotification(userId, achievementName) { const payload = { title: '🏆 Neues Achievement!', body: `Du hast "${achievementName}" erreicht!`, icon: '/pictures/icon-192.png', badge: '/pictures/icon-192.png', data: { type: 'achievement', achievement: achievementName, timestamp: Date.now() }, actions: [ { action: 'view', title: 'Dashboard öffnen' } ] }; return await this.sendToUser(userId, payload); } // Send best time notification async sendBestTimeNotification(userId, timeType, locationName) { const payload = { title: `🏁 ${timeType} Bestzeit!`, body: `Du hast die beste Zeit in ${locationName} erreicht!`, icon: '/pictures/icon-192.png', badge: '/pictures/icon-192.png', data: { type: 'best_time', timeType: timeType, location: locationName, timestamp: Date.now() }, actions: [ { action: 'view', title: 'Dashboard öffnen' } ] }; return await this.sendToUser(userId, payload); } // Get subscription count getSubscriptionCount() { return this.subscriptions.size; } // Get all user IDs with subscriptions getSubscribedUsers() { return Array.from(this.subscriptions.keys()); } } module.exports = new PushService();