Feature: Push-Benachrichtigungen (ntfy/Webhook) bei Paket fertig/fehlgeschlagen + Run-Ende
Headless-Server: Paket-Ausgaenge waren bisher nur per RDP+Log sichtbar. Neues Modul notify.ts schickt einen fire-and-forget POST (ntfy-kompatibel: Title/ Priority/Tags als Header, Nachricht als Body) an eine konfigurierbare URL — mit der kostenlosen ntfy-App aufs Handy, ohne Account/Port/Firewall (outbound). - Settings: notifyUrl + 3 Ereignis-Toggles (Default aus) in Allgemein. - Hook 1: Post-Processing-Ende (Paket completed/failed nach Entpacken). - Hook 2: refreshPackageStatus fuer den Alle-Items-fehlgeschlagen-Fall (Link tot -> Paket erreicht das Post-Processing nie; ohne diesen Hook schwiege ausgerechnet der haeufigste Fehlerfall). - Hook 3: finishRun mit Run-Summary (X/Y erfolgreich, Dauer, Schnitt). - Dedup-Set pro Paket+Run, Lifecycle gespiegelt an historyRecordedPackages (Run-Start-Clear, Retry-Deletes, removePackageFromSession). Guard session.running || runPackageIds.has(id): nachlaufendes Entpacken nach Run-Ende benachrichtigt noch, Startup-Recovery nach App-Neustart nicht (sonst Doppel-Push fuer laengst fertige Pakete). - 5s-Timeout, Fehler nur als logger.warn — blockiert nie den Download-Pfad. - 9 Unit-Tests fuer notify.ts.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { logger } from "./logger";
|
||||
|
||||
export interface NotifyPayload {
|
||||
title: string;
|
||||
message: string;
|
||||
priority?: "default" | "high";
|
||||
tags?: string;
|
||||
}
|
||||
|
||||
const NOTIFY_TIMEOUT_MS = 5000;
|
||||
|
||||
export function isNotifyUrlValid(url: string): boolean {
|
||||
return /^https?:\/\/\S+$/i.test(String(url || "").trim());
|
||||
}
|
||||
|
||||
export function buildNotifyRequest(url: string, payload: NotifyPayload): { url: string; init: RequestInit } {
|
||||
const headers: Record<string, string> = {
|
||||
"Title": payload.title,
|
||||
"Content-Type": "text/plain; charset=utf-8"
|
||||
};
|
||||
if (payload.priority && payload.priority !== "default") {
|
||||
headers["Priority"] = payload.priority;
|
||||
}
|
||||
if (payload.tags) {
|
||||
headers["Tags"] = payload.tags;
|
||||
}
|
||||
return {
|
||||
url: String(url || "").trim(),
|
||||
init: { method: "POST", headers, body: payload.message }
|
||||
};
|
||||
}
|
||||
|
||||
export async function sendNotification(url: string, payload: NotifyPayload, fetchFn: typeof fetch = fetch): Promise<boolean> {
|
||||
if (!isNotifyUrlValid(url)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const request = buildNotifyRequest(url, payload);
|
||||
const response = await fetchFn(request.url, { ...request.init, signal: AbortSignal.timeout(NOTIFY_TIMEOUT_MS) });
|
||||
if (!response.ok) {
|
||||
logger.warn(`Benachrichtigung fehlgeschlagen (HTTP ${response.status}): ${payload.title}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.warn(`Benachrichtigung fehlgeschlagen: ${String(error)}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user