Kuma/src/lib/push/send-push.ts
2026-09-14 16:26:18 +02:00

76 lines
2.3 KiB
TypeScript

/**
* Versand von Web-Push-Benachrichtigungen an einen Empfänger (alle seine
* Geräte). Analog zu sendMail: wirft NIE, ein Push-Fehler darf keinen
* aufrufenden Vorgang (Buchung, Storno, ...) kippen.
*/
import { WebPushError } from "web-push";
import { prisma } from "@/src/lib/prisma";
import { getWebPush, isPushConfigured } from "./web-push";
export interface PushPayload {
title: string;
body: string;
/** Ziel-URL, die beim Klick auf die Benachrichtigung geöffnet wird. */
url?: string;
}
/**
* Sendet eine Push-Benachrichtigung an alle registrierten Geräte eines
* Empfängers (Manager-, Employee- oder User-ID). Tote Subscriptions (Browser
* hat sie serverseitig verworfen, HTTP 404/410) werden dabei aus der DB
* entfernt.
*/
export async function sendPushToRecipient(
recipientId: string,
payload: PushPayload
): Promise<void> {
if (!isPushConfigured()) return;
try {
const subscriptions = await prisma.pushSubscription.findMany({
where: { recipientId },
});
if (subscriptions.length === 0) return;
const webpush = getWebPush();
const body = JSON.stringify(payload);
const results = await Promise.allSettled(
subscriptions.map((sub) =>
webpush.sendNotification(
{
endpoint: sub.endpoint,
keys: { p256dh: sub.p256dh, auth: sub.auth },
},
body
)
)
);
const deadEndpoints: string[] = [];
results.forEach((result, i) => {
if (result.status === "rejected") {
const error = result.reason;
// 404/410 = Subscription existiert clientseitig nicht mehr
// (Browser-Daten gelöscht, App deinstalliert, ...) - dauerhaft tot.
if (
error instanceof WebPushError &&
(error.statusCode === 404 || error.statusCode === 410)
) {
deadEndpoints.push(subscriptions[i].endpoint);
} else {
console.error("sendPushToRecipient: Zustellung fehlgeschlagen:", error);
}
}
});
if (deadEndpoints.length > 0) {
await prisma.pushSubscription
.deleteMany({ where: { endpoint: { in: deadEndpoints } } })
.catch((error) => console.error("sendPushToRecipient: Aufräumen fehlgeschlagen:", error));
}
} catch (error) {
console.error("sendPushToRecipient fehlgeschlagen:", error);
}
}