feat: implement pwa push notifications
This commit is contained in:
@@ -21,3 +21,51 @@ export async function sendNotification(webhookUrl: string | null, message: strin
|
||||
console.error("Failed to send notification:", error);
|
||||
}
|
||||
}
|
||||
|
||||
import { sendPushNotification } from "./push";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export async function sendPlanNotification(planId: string, message: string, webhookUrl?: string | null) {
|
||||
// Parallelize sending
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
if (webhookUrl) {
|
||||
promises.push(sendNotification(webhookUrl, message));
|
||||
}
|
||||
|
||||
try {
|
||||
const subscriptions = await prisma.pushSubscription.findMany({
|
||||
where: { planId }
|
||||
});
|
||||
|
||||
if (subscriptions.length > 0) {
|
||||
const payload = {
|
||||
title: "Cat Sitting Planner",
|
||||
body: message,
|
||||
url: `/`
|
||||
// We could pass specific URL if needed, but for now root is okay or dashboard?
|
||||
// The service worker opens the URL.
|
||||
// Ideally, we want to open `/dashboard/[planId]`.
|
||||
};
|
||||
|
||||
// Refine URL in payload
|
||||
// We need 'lang'. We don't have it here easily unless passed.
|
||||
// But we can guess or just link to root and let redirect handle it?
|
||||
// Or just link to context.
|
||||
// Let's rely on SW opening `/`.
|
||||
|
||||
subscriptions.forEach(sub => {
|
||||
promises.push((async () => {
|
||||
const res = await sendPushNotification(sub, payload);
|
||||
if (!res.success && (res.statusCode === 410 || res.statusCode === 404)) {
|
||||
await prisma.pushSubscription.delete({ where: { id: sub.id } });
|
||||
}
|
||||
})());
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch/send push subscriptions", e);
|
||||
}
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
}
|
||||
|
||||
37
lib/push.ts
Normal file
37
lib/push.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import webpush from 'web-push';
|
||||
|
||||
if (!process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || !process.env.VAPID_PRIVATE_KEY) {
|
||||
console.warn("VAPID keys are missing. Push notifications will not work.");
|
||||
} else {
|
||||
webpush.setVapidDetails(
|
||||
process.env.VAPID_SUBJECT || 'mailto:admin@localhost',
|
||||
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
|
||||
process.env.VAPID_PRIVATE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
interface PushSubscriptionData {
|
||||
endpoint: string;
|
||||
p256dh: string;
|
||||
auth: string;
|
||||
}
|
||||
|
||||
export async function sendPushNotification(subscription: PushSubscriptionData, payload: any) {
|
||||
try {
|
||||
await webpush.sendNotification({
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: subscription.p256dh,
|
||||
auth: subscription.auth,
|
||||
}
|
||||
}, JSON.stringify(payload));
|
||||
return { success: true, statusCode: 201 };
|
||||
} catch (error: any) {
|
||||
if (error.statusCode === 410 || error.statusCode === 404) {
|
||||
// Subscription is gone
|
||||
return { success: false, statusCode: error.statusCode };
|
||||
}
|
||||
console.error("Error sending push:", error);
|
||||
return { success: false, statusCode: error.statusCode || 500 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user