102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
export async function sendNotification(webhookUrl: string | null, message: string, imageUrl?: string) {
|
|
if (!webhookUrl) return;
|
|
|
|
try {
|
|
const payload: any = {
|
|
content: message,
|
|
text: message
|
|
}
|
|
|
|
// For Discord: use embed if image exists
|
|
if (imageUrl && webhookUrl.includes("discord")) {
|
|
payload.embeds = [{
|
|
image: {
|
|
url: imageUrl
|
|
}
|
|
}]
|
|
}
|
|
|
|
// For generic webhooks, just append URL to text if not Discord, or leave as is?
|
|
// Let's create a simpler payload if image exists but not discord?
|
|
// Actually, if we just send JSON, most won't render it unless specific format.
|
|
// Let's just append the image URL to the message if it's not a Discord webhook, so it's clickable.
|
|
if (imageUrl && !webhookUrl.includes("discord")) {
|
|
payload.content += `\n${imageUrl}`;
|
|
payload.text += `\n${imageUrl}`;
|
|
}
|
|
|
|
const response = await fetch(webhookUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"User-Agent": "CatSittingPlanner/1.0"
|
|
},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!response.ok) {
|
|
console.error(`[Notification] Webhook failed with status ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
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, imageUrl?: string) {
|
|
// Parallelize sending
|
|
const promises: Promise<any>[] = [];
|
|
|
|
if (webhookUrl) {
|
|
promises.push(sendNotification(webhookUrl, message, imageUrl));
|
|
}
|
|
|
|
try {
|
|
const subscriptions = await prisma.pushSubscription.findMany({
|
|
where: { planId }
|
|
});
|
|
|
|
if (subscriptions.length > 0) {
|
|
console.log(`[Push] Found ${subscriptions.length} subscriptions for plan ${planId}`);
|
|
const payload: any = {
|
|
title: "Cat Sitting Planner",
|
|
body: message,
|
|
url: `/`,
|
|
image: imageUrl
|
|
};
|
|
|
|
// 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 () => {
|
|
try {
|
|
console.log(`[Push] Sending to endpoint ${sub.endpoint.slice(0, 20)}...`);
|
|
const res = await sendPushNotification(sub, payload);
|
|
console.log(`[Push] Result for ${sub.endpoint.slice(0, 20)}...:`, res);
|
|
if (!res.success) {
|
|
console.error(`[Push] Failed for endpoint: ${res.statusCode}`);
|
|
if (res.statusCode === 410 || res.statusCode === 404) {
|
|
console.log(`[Push] Deleting stale subscription`);
|
|
await prisma.pushSubscription.delete({ where: { id: sub.id } });
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error(`[Push] Error inside loop:`, err);
|
|
}
|
|
})());
|
|
});
|
|
} else {
|
|
console.log(`[Push] No subscriptions found for plan ${planId}`);
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to fetch/send push subscriptions", e);
|
|
}
|
|
|
|
await Promise.allSettled(promises);
|
|
}
|