Agent query: Could you check the browser's console logs for any subscription errors when clicking the subscribe button?
Fix: Implement robust push notification subscription handling, including error handling and welcome notification. Add necessary type definitions and VAPID key validation. Screenshot: https://storage.googleapis.com/screenshot-production-us-central1/9dda30b6-4149-4bce-89dc-76333005952c/2539846f-42c7-4fad-9462-984f310cf386.jpg
This commit is contained in:
@@ -89,18 +89,27 @@ export default function Home() {
|
||||
throw new Error('Notification permission denied');
|
||||
}
|
||||
|
||||
console.log('Getting service worker registration...');
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
console.log('Service worker registered successfully');
|
||||
|
||||
const vapidPublicKey = import.meta.env.VITE_VAPID_PUBLIC_KEY;
|
||||
if (!vapidPublicKey) {
|
||||
throw new Error('VAPID public key is not configured');
|
||||
}
|
||||
console.log('VAPID public key available:', vapidPublicKey.slice(0, 10) + '...');
|
||||
|
||||
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
|
||||
console.log('Converted VAPID key to Uint8Array');
|
||||
|
||||
console.log('Requesting push subscription...');
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: convertedVapidKey
|
||||
});
|
||||
console.log('Successfully subscribed to push notifications');
|
||||
|
||||
console.log('Sending subscription to server...');
|
||||
await apiRequest('POST', '/api/subscriptions', subscription);
|
||||
setIsSubscribed(true);
|
||||
toast({
|
||||
@@ -108,6 +117,7 @@ export default function Home() {
|
||||
description: "You'll receive notifications for new newsletters",
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Subscription error:', error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: error.message || "Failed to subscribe to notifications",
|
||||
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -41,6 +41,8 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"axios": "^1.7.9",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
@@ -3361,6 +3363,15 @@
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-schedule": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.7.tgz",
|
||||
"integrity": "sha512-G7Z3R9H7r3TowoH6D2pkzUHPhcJrDF4Jz1JOQ80AX0K2DWTHoN9VC94XzFAPNMdbW9TBzMZ3LjpFi7RYdbxtXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/passport": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
|
||||
@@ -3470,6 +3481,15 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/web-push": {
|
||||
"version": "3.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.6.4.tgz",
|
||||
"integrity": "sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@replit/vite-plugin-shadcn-theme-json": "^0.0.4",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"@types/node-schedule": "^2.1.7",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"axios": "^1.7.9",
|
||||
"cheerio": "^1.0.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
|
||||
@@ -7,14 +7,17 @@ import webpush from "web-push";
|
||||
import schedule from "node-schedule";
|
||||
|
||||
// Initialize web-push with VAPID keys
|
||||
if (!process.env.VAPID_PUBLIC_KEY || !process.env.VAPID_PRIVATE_KEY) {
|
||||
console.warn('VAPID keys not set. Push notifications will not work.');
|
||||
const vapidPublicKey = process.env.VAPID_PUBLIC_KEY;
|
||||
const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY;
|
||||
|
||||
if (!vapidPublicKey || !vapidPrivateKey) {
|
||||
throw new Error('VAPID keys are required for push notifications. Please set VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY environment variables.');
|
||||
}
|
||||
|
||||
webpush.setVapidDetails(
|
||||
'mailto:team@downtowner.com',
|
||||
process.env.VAPID_PUBLIC_KEY || '',
|
||||
process.env.VAPID_PRIVATE_KEY || ''
|
||||
vapidPublicKey,
|
||||
vapidPrivateKey
|
||||
);
|
||||
|
||||
export async function registerRoutes(app: Express): Promise<Server> {
|
||||
@@ -32,16 +35,19 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
if (newNewsletters.length > 0) {
|
||||
await storage.importNewsletters(newNewsletters);
|
||||
console.log(`Found ${newNewsletters.length} new newsletters, sending notifications...`);
|
||||
|
||||
// Send push notifications
|
||||
const subscriptions = await storage.getSubscriptions();
|
||||
console.log(`Sending notifications to ${subscriptions.length} subscribers`);
|
||||
|
||||
const notificationPayload = JSON.stringify({
|
||||
title: 'New Newsletters Available',
|
||||
body: `${newNewsletters.length} new newsletter${newNewsletters.length > 1 ? 's' : ''} published!`,
|
||||
icon: '/icon.png'
|
||||
});
|
||||
|
||||
await Promise.allSettled(
|
||||
const results = await Promise.allSettled(
|
||||
subscriptions.map(subscription =>
|
||||
webpush.sendNotification({
|
||||
endpoint: subscription.endpoint,
|
||||
@@ -52,6 +58,10 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}, notificationPayload)
|
||||
)
|
||||
);
|
||||
|
||||
const succeeded = results.filter(r => r.status === 'fulfilled').length;
|
||||
const failed = results.filter(r => r.status === 'rejected').length;
|
||||
console.log(`Push notifications sent: ${succeeded} succeeded, ${failed} failed`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Background job failed:', error);
|
||||
@@ -83,16 +93,47 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
|
||||
app.post("/api/subscriptions", async (req, res) => {
|
||||
try {
|
||||
const subscription = req.body;
|
||||
await storage.addSubscription({
|
||||
endpoint: subscription.endpoint,
|
||||
auth: subscription.keys.auth,
|
||||
p256dh: subscription.keys.p256dh
|
||||
console.log('Received subscription request:', {
|
||||
endpoint: req.body.endpoint,
|
||||
auth: req.body.keys?.auth ? '[present]' : '[missing]',
|
||||
p256dh: req.body.keys?.p256dh ? '[present]' : '[missing]'
|
||||
});
|
||||
|
||||
if (!req.body.endpoint || !req.body.keys?.auth || !req.body.keys?.p256dh) {
|
||||
throw new Error('Invalid subscription data');
|
||||
}
|
||||
|
||||
await storage.addSubscription({
|
||||
endpoint: req.body.endpoint,
|
||||
auth: req.body.keys.auth,
|
||||
p256dh: req.body.keys.p256dh
|
||||
});
|
||||
|
||||
// Test the subscription with a welcome notification
|
||||
try {
|
||||
await webpush.sendNotification({
|
||||
endpoint: req.body.endpoint,
|
||||
keys: {
|
||||
auth: req.body.keys.auth,
|
||||
p256dh: req.body.keys.p256dh
|
||||
}
|
||||
}, JSON.stringify({
|
||||
title: 'Subscription Successful',
|
||||
body: 'You will now receive notifications for new newsletters!',
|
||||
icon: '/icon.png'
|
||||
}));
|
||||
console.log('Welcome notification sent successfully');
|
||||
} catch (notifError) {
|
||||
console.error('Failed to send welcome notification:', notifError);
|
||||
}
|
||||
|
||||
res.json({ message: "Subscription added successfully" });
|
||||
} catch (error) {
|
||||
console.error('Error adding subscription:', error);
|
||||
res.status(500).json({ message: "Failed to add subscription" });
|
||||
res.status(500).json({
|
||||
message: "Failed to add subscription",
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user