Putting secrets into environment variables is problematic due to them being inherited by sub-processes, the ease as which these can be leaked in logs, and the lack of support for loading secrets into environment variables by systems such as systemd and docker. Change the loading of secrets to be done by loading the content of a file specified by an environment variable.
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
import * as fs from "node:fs/promises";
|
|
import type { H3Event } from "h3";
|
|
import webPush from "web-push";
|
|
import { readSubscriptions, writeSubscriptions } from "~/server/database";
|
|
|
|
let cachedVapidDetails: {
|
|
subject: string,
|
|
publicKey: string,
|
|
privateKey: string,
|
|
} | undefined;
|
|
|
|
async function useVapidDetails(event: H3Event) {
|
|
if (cachedVapidDetails) {
|
|
return cachedVapidDetails;
|
|
}
|
|
|
|
const runtimeConfig = useRuntimeConfig(event);
|
|
if (!runtimeConfig.vapidSubject) throw new Error("NUXT_VAPID_SUBJECT not set.")
|
|
if (!runtimeConfig.public.vapidPublicKey) throw new Error("NUXT_PUBLIC_VAPID_PUBLIC_KEY not set.")
|
|
if (!runtimeConfig.vapidPrivateKeyFile) throw new Error("NUXT_VAPID_PRIVATE_KEY_FILE not set.")
|
|
|
|
return cachedVapidDetails = {
|
|
subject: runtimeConfig.vapidSubject,
|
|
publicKey: runtimeConfig.public.vapidPublicKey,
|
|
privateKey: await fs.readFile(runtimeConfig.vapidPrivateKeyFile, "utf-8"),
|
|
}
|
|
}
|
|
|
|
export async function sendPush(event: H3Event, title: string, body: string) {
|
|
const vapidDetails = await useVapidDetails(event);
|
|
const payload = JSON.stringify({ title, body });
|
|
const subscriptions = await readSubscriptions();
|
|
console.log(`Sending "${payload}" to ${subscriptions.length} subscribers`);
|
|
const removeIndexes = [];
|
|
for (let index = 0; index < subscriptions.length; index += 1) {
|
|
const subscription = subscriptions[index];
|
|
if (subscription.type !== "push")
|
|
continue;
|
|
try {
|
|
await webPush.sendNotification(
|
|
subscription.push as webPush.PushSubscription,
|
|
payload,
|
|
{
|
|
TTL: 3600,
|
|
urgency: "high",
|
|
vapidDetails,
|
|
}
|
|
)
|
|
} catch (err: any) {
|
|
if (err?.statusCode === 410) {
|
|
removeIndexes.push(index);
|
|
} else {
|
|
console.error("Received error sending push notice:", err.message, err?.statusCode)
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
if (removeIndexes.length) {
|
|
console.log(`Removing indexes ${removeIndexes} from subscriptions`)
|
|
removeIndexes.reverse();
|
|
for (const index of removeIndexes) {
|
|
subscriptions.splice(index, 1);
|
|
}
|
|
await writeSubscriptions(subscriptions);
|
|
}
|
|
console.log("Push notices sent");
|
|
}
|