using System.Text.Json; using GerbilManagerWebAPI.Models; using Microsoft.EntityFrameworkCore; using WebPush; namespace GerbilManagerWebAPI.Push { /// /// Versendet Web-Push-Benachrichtigungen an alle gespeicherten Abos (PWA der Züchterin). /// Ist kein VAPID-Schlüsselpaar konfiguriert, sind die Methoden No-Ops (Push deaktiviert). /// Veraltete Abos (404/410) werden beim Senden entfernt. /// public class PushNotifier { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _log; private readonly VapidDetails? _vapid; public PushNotifier(IConfiguration config, IServiceScopeFactory scopeFactory, ILogger log) { _scopeFactory = scopeFactory; _log = log; var subject = config["WebPush:Subject"]; var publicKey = config["WebPush:PublicKey"]; var privateKey = config["WebPush:PrivateKey"]; if (!string.IsNullOrWhiteSpace(subject) && !string.IsNullOrWhiteSpace(publicKey) && !string.IsNullOrWhiteSpace(privateKey)) { _vapid = new VapidDetails(subject, publicKey, privateKey); } } /// true, wenn Push konfiguriert ist (VAPID-Schlüssel vorhanden). public bool Enabled => _vapid is not null; public string? PublicKey => _vapid?.PublicKey; /// Eine Benachrichtigung an ALLE Abos senden. Fehler einzelner Abos werden geschluckt. public async Task NotifyAllAsync(string title, string body, string url, CancellationToken ct = default) { if (_vapid is null) return; using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var subs = await db.WebPushSubscriptions.ToListAsync(ct); if (subs.Count == 0) return; var client = new WebPushClient(); var payload = JsonSerializer.Serialize(new { title, body, url }); var stale = new List(); foreach (var s in subs) { try { var pushSub = new WebPush.PushSubscription(s.Endpoint, s.P256dh, s.Auth); await client.SendNotificationAsync(pushSub, payload, _vapid); } catch (WebPushException ex) when (ex.StatusCode is System.Net.HttpStatusCode.NotFound or System.Net.HttpStatusCode.Gone) { stale.Add(s); // Abo abgelaufen/abgemeldet -> aufräumen } catch (Exception ex) { _log.LogWarning(ex, "Push an {Endpoint} fehlgeschlagen", s.Endpoint); } } if (stale.Count > 0) { db.WebPushSubscriptions.RemoveRange(stale); await db.SaveChangesAsync(ct); } } } }