Files
overlord/Overlord-Server/src/server/web-push.ts
T

67 lines
2.2 KiB
TypeScript

import webpush from "web-push";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { resolve } from "path";
import { ensureDataDir } from "../paths";
import { logger } from "../logger";
let vapidKeys: { publicKey: string; privateKey: string } | null = null;
function getVapidPath(): string {
return resolve(ensureDataDir(), "vapid-keys.json");
}
export function loadOrGenerateVapidKeys(): { publicKey: string; privateKey: string } {
if (vapidKeys) return vapidKeys;
const vapidPath = getVapidPath();
if (existsSync(vapidPath)) {
try {
const raw = readFileSync(vapidPath, "utf-8");
const parsed = JSON.parse(raw);
if (parsed.publicKey && parsed.privateKey) {
vapidKeys = { publicKey: parsed.publicKey, privateKey: parsed.privateKey };
logger.info("[web-push] loaded VAPID keys from " + vapidPath);
return vapidKeys;
}
} catch (err) {
logger.warn("[web-push] failed to read vapid-keys.json, regenerating", err);
}
}
const generated = webpush.generateVAPIDKeys();
vapidKeys = { publicKey: generated.publicKey, privateKey: generated.privateKey };
try {
writeFileSync(vapidPath, JSON.stringify(vapidKeys, null, 2));
logger.info("[web-push] generated and saved VAPID keys to " + vapidPath);
} catch (err) {
logger.warn("[web-push] failed to persist VAPID keys", err);
}
return vapidKeys;
}
export function getVapidPublicKey(): string {
return loadOrGenerateVapidKeys().publicKey;
}
export async function sendWebPush(
subscription: { endpoint: string; keys: { p256dh: string; auth: string } },
payload: string,
): Promise<{ success: boolean; gone?: boolean; error?: string }> {
const keys = loadOrGenerateVapidKeys();
webpush.setVapidDetails("mailto:overlord@localhost", keys.publicKey, keys.privateKey);
try {
await webpush.sendNotification(subscription, payload, { TTL: 60 * 60 });
return { success: true };
} catch (err: any) {
const statusCode = err?.statusCode;
if (statusCode === 404 || statusCode === 410) {
return { success: false, gone: true, error: `subscription gone (${statusCode})` };
}
return { success: false, error: err?.message || String(err) };
}
}