import "server-only"; import { and, eq, ne, or } from "drizzle-orm"; import webPush from "web-push"; import { getDb } from "@/db"; import { commissionPushSubscriptions, users } from "@/db/schema"; export function pushPublicKey() { return process.env.WEB_PUSH_PUBLIC_KEY && process.env.WEB_PUSH_PRIVATE_KEY && process.env.WEB_PUSH_SUBJECT ? process.env.WEB_PUSH_PUBLIC_KEY : null; } export function validPushEndpoint(value: string) { try { const url = new URL(value); const host = url.hostname.toLowerCase(); return url.protocol === "https:" && !url.username && !url.password && (host === "fcm.googleapis.com" || host === "updates.push.services.mozilla.com" || host === "web.push.apple.com" || host.endsWith(".push.apple.com") || host.endsWith(".notify.windows.com")); } catch { return false; } } export async function sendCommissionMessagePush(message: { id: string; ticketId: string; ticketTitle: string; customerId: string; authorId: string; authorName: string; text: string | null; }) { if (!pushPublicKey()) return; const subscriptions = await getDb().select({ subscription: commissionPushSubscriptions }) .from(commissionPushSubscriptions) .innerJoin(users, eq(users.id, commissionPushSubscriptions.userId)) .where(and(ne(commissionPushSubscriptions.userId, message.authorId), eq(users.banned, false), or(and(eq(commissionPushSubscriptions.scope, "customer"), eq(commissionPushSubscriptions.userId, message.customerId)), and(eq(commissionPushSubscriptions.scope, "admin"), eq(users.role, "admin"), eq(users.emailVerified, true))))); webPush.setVapidDetails(process.env.WEB_PUSH_SUBJECT!, process.env.WEB_PUSH_PUBLIC_KEY!, process.env.WEB_PUSH_PRIVATE_KEY!); for (let offset = 0; offset < subscriptions.length; offset += 10) { await Promise.all(subscriptions.slice(offset, offset + 10).map(async ({ subscription }) => { const payload = JSON.stringify({ id: message.id, title: `${message.authorName} · ${message.ticketTitle}`, body: message.text?.slice(0, 300) || "ส่งรูปภาพ", url: `/${subscription.scope === "admin" ? "admin/commission" : "commission/tickets"}/${message.ticketId}`, }); try { await webPush.sendNotification({ endpoint: subscription.endpoint, keys: { p256dh: subscription.p256dh, auth: subscription.auth } }, payload, { TTL: 60 * 60 * 24, timeout: 5000 }); } catch (cause) { if (cause && typeof cause === "object" && "statusCode" in cause && (cause.statusCode === 404 || cause.statusCode === 410)) await getDb().delete(commissionPushSubscriptions) .where(eq(commissionPushSubscriptions.endpoint, subscription.endpoint)); } })); } }