Files
wtr/lib/push-service.ts
zv 2182297adc
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
feat: add global weather support
2026-06-14 15:59:14 +02:00

106 lines
4.6 KiB
TypeScript

import webPush, { type PushSubscription } from "web-push";
import type { Language } from "@/lib/i18n";
import { formatProvinceName } from "@/lib/provinces";
import type { WeatherBrief } from "@/lib/weather-brief";
import type { WeatherWarning } from "@/types/imgw";
import type { WarningPushSubscription } from "@/types/notifications";
const VAPID_PUBLIC_KEY = process.env.WEB_PUSH_VAPID_PUBLIC_KEY;
const VAPID_PRIVATE_KEY = process.env.WEB_PUSH_VAPID_PRIVATE_KEY;
const VAPID_SUBJECT = process.env.WEB_PUSH_VAPID_SUBJECT ?? "mailto:alerts@wtr.local";
let configured = false;
export function getVapidPublicKey() {
return VAPID_PUBLIC_KEY ?? null;
}
export function isWebPushConfigured() {
return Boolean(VAPID_PUBLIC_KEY && VAPID_PRIVATE_KEY);
}
function ensureWebPushConfigured() {
if (!VAPID_PUBLIC_KEY || !VAPID_PRIVATE_KEY) throw new Error("Web Push VAPID keys are not configured.");
if (!configured) {
webPush.setVapidDetails(VAPID_SUBJECT, VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY);
configured = true;
}
}
function getWarningLevelLabel(warning: WeatherWarning, language: Language) {
if (warning.level === null) return language === "pl" ? "stopień nieokreślony" : "level not specified";
return language === "pl" ? `stopień ${warning.level}` : `level ${warning.level}`;
}
function formatWarningValidity(warning: WeatherWarning, language: Language) {
if (!warning.validFrom && !warning.validTo) return null;
const locale = language === "pl" ? "pl-PL" : "en-GB";
const formatter = new Intl.DateTimeFormat(locale, {
day: "2-digit",
month: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
const from = warning.validFrom ? formatter.format(new Date(warning.validFrom)) : null;
const to = warning.validTo ? formatter.format(new Date(warning.validTo)) : null;
if (from && to) return language === "pl" ? `od ${from} do ${to}` : `from ${from} to ${to}`;
if (from) return language === "pl" ? `od ${from}` : `from ${from}`;
return language === "pl" ? `do ${to}` : `until ${to}`;
}
function buildWarningPayload(preference: WarningPushSubscription, warning: WeatherWarning) {
const region = preference.province ? formatProvinceName(preference.province, preference.language) : preference.locationName ?? "wtr.";
const title = warning.title || (preference.language === "pl" ? "Ostrzeżenie meteorologiczne" : "Weather warning");
const validity = formatWarningValidity(warning, preference.language);
const level = getWarningLevelLabel(warning, preference.language);
const bodyParts = preference.language === "pl"
? [`${region}: ${level}`, validity, warning.probability !== null ? `prawdopodobieństwo ${warning.probability}%` : null]
: [`${region}: ${level}`, validity, warning.probability !== null ? `${warning.probability}% probability` : null];
return {
title: `IMGW: ${title}`,
body: bodyParts.filter(Boolean).join(" · "),
url: "/warnings",
warningId: warning.id,
};
}
export async function sendWarningNotification(preference: WarningPushSubscription, warning: WeatherWarning) {
ensureWebPushConfigured();
const payload = JSON.stringify(buildWarningPayload(preference, warning));
await webPush.sendNotification(preference.subscription as PushSubscription, payload);
}
export async function sendTestNotification(preference: WarningPushSubscription) {
ensureWebPushConfigured();
const location = preference.province ? formatProvinceName(preference.province, preference.language) : preference.locationName ?? "Open-Meteo";
const payload = JSON.stringify({
title: preference.language === "pl" ? "wtr.: test powiadomień" : "wtr.: notification test",
body: preference.language === "pl"
? `Powiadomienia są aktywne dla: ${location}.`
: `Notifications are active for: ${location}.`,
url: "/settings",
});
await webPush.sendNotification(preference.subscription as PushSubscription, payload);
}
export async function sendMorningBriefNotification(preference: WarningPushSubscription, brief: WeatherBrief) {
ensureWebPushConfigured();
const payload = JSON.stringify({
title: preference.language === "pl" ? "wtr.: brief dnia" : "wtr.: daily brief",
body: brief.pushBody,
url: "/",
});
await webPush.sendNotification(preference.subscription as PushSubscription, payload);
}
export async function sendTomorrowBriefNotification(preference: WarningPushSubscription, brief: WeatherBrief) {
ensureWebPushConfigured();
const payload = JSON.stringify({
title: preference.language === "pl" ? "wtr.: jutro" : "wtr.: tomorrow",
body: brief.pushBody,
url: "/",
});
await webPush.sendNotification(preference.subscription as PushSubscription, payload);
}