Notifications WebPush
Les notifications WebPush affichent une notification du navigateur au niveau du système d'exploitation, même lorsque l'application Lagoon n'est pas au premier plan. Sur desktop, le navigateur doit généralement être lancé pour afficher les notifications reçues ; si des notifications arrivent pendant qu'il est fermé, elles s'affichent à sa prochaine ouverture. Chrome utilise les notifications système, tandis que Firefox utilise son propre affichage, plus limité pour les images. Sur Android, les notifications peuvent s'afficher même si le navigateur est fermé ; si le mobile est hors ligne, elles s'affichent au retour de la connectivité.
Lagoon s'appuie sur le module Lagoon.WebPush, sur un service worker côté client et sur le standard VAPID pour authentifier l'application auprès du service de push du navigateur.
L'utilisateur doit accepter explicitement les notifications. Il n'est pas possible de forcer l'abonnement. Si l'utilisateur se déconnecte, Lagoon supprime l'abonnement WebPush côté navigateur afin qu'il ne reçoive plus de notifications après la déconnexion.
Prérequis & installation
Package et service worker
Ajoutez le package Lagoon.WebPush côté serveur. Le client doit activer un service worker, car l'abonnement et la réception des événements push, notificationclick et pushsubscriptionchange passent par le navigateur.
Dans les anciennes applications Lagoon, l'activation du service worker était souvent liée au mode hors ligne. WebPush nécessite au minimum un service worker actif côté client.
Clés VAPID
La configuration attend trois clés :
{
"App": {
"Name": "Lagoon",
"VapidPublicKey": "BJBj9SRrYF6..."
},
"VapidPrivateKey": "ry-6f4nOC...",
"VapidSubject": "mailto:contact@example.com"
}
App:VapidPublicKeyest partagé avec le client et transmis auPushManagerdu navigateur.VapidPrivateKeyreste côté serveur.VapidSubjectdoit être une URL absolue ou une adressemailto:.
Les clés peuvent être générées avec le générateur VAPID disponible dans le menu de debug en environnement de développement : ouvrez le générateur de clés VAPID, lancez la génération, puis copiez la clé publique et la clé privée dans la configuration. La même génération est exposée côté serveur via :
VapidDetails GenerateVapidKeys();
La classe VapidDetails expose les propriétés suivantes :
public string Subject { get; set; }
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
public long Expiration { get; set; } = -1;
Ne partagez jamais la clé privée VAPID et ne la stockez pas côté client.
Enregistrement DI
Enregistrez les services WebPush côté serveur :
services.AddWebPush();
La méthode d'extension réelle est :
public static IServiceCollection AddWebPush(this IServiceCollection services)
Elle enregistre :
| Service | Implémentation |
|---|---|
IWebPushNotificationManager | WebPushNotificationManager |
IWebPushSubscriptionManager | WebPushSubscriptionManager |
Abonnement côté client
L'abonnement est porté par App.ServiceWorkerManager. Les méthodes publiques utiles sont :
public async Task<bool> SubscribeToPushNotificationAsync()
public async Task UnsubscribeToPushNotificationAsync(bool deleteServerSideRegistration = false)
public Task DisplayWebPushSubscriptionPopupAsync()
public bool IsPushNotificationGranted()
public bool IsServiceWorkerActive()
public bool IsWebPushAvailable()
SubscribeToPushNotificationAsync() vérifie App:VapidPublicKey, appelle le PushManager du navigateur avec la clé publique VAPID, puis envoie l'abonnement au serveur. L'objet échangé contient :
public string Endpoint { get; set; }
public string P256dh { get; set; }
public string Auth { get; set; }
public string OldEndpoint { get; set; }
public string UserId { get; set; }
public bool IsValid { get; }
Exemple d'écran proposant l'abonnement :
@inherits LgPage
<LgButton Text="#EnableWebPush" OnClick="SubscribeAsync" />
<LgButton Text="#DisableWebPush" Kind="ButtonKind.Secondary" OnClick="UnsubscribeAsync" />
public partial class NotificationSettingsPage : LgPage
{
private async Task SubscribeAsync(ActionEventArgs args)
{
try
{
// Déclenche la demande d'autorisation navigateur puis enregistre l'abonnement côté serveur.
bool subscribed = await App.ServiceWorkerManager.SubscribeToPushNotificationAsync();
if (!subscribed)
{
ShowWarning("#WebPushSubscriptionBlocked");
}
}
catch (Exception ex)
{
ShowException(ex);
}
}
private async Task UnsubscribeAsync(ActionEventArgs args)
{
try
{
// Supprime l'abonnement navigateur et demande aussi la suppression côté serveur.
await App.ServiceWorkerManager.UnsubscribeToPushNotificationAsync(true);
ShowSuccess("#WebPushSubscriptionDeleted");
}
catch (Exception ex)
{
ShowException(ex);
}
}
}
Le service worker affiche la notification avec showNotification, navigue vers la route placée dans Data au clic, et met à jour l'abonnement serveur lorsque le navigateur déclenche pushsubscriptionchange.
Envoi côté serveur
Le service principal d'envoi est IWebPushNotificationManager :
Task<bool> SendNotificationAsync(string userId, string title, string message, string icon = null);
Task<bool> SendNotificationAsync(string userId, PushNotificationMessage message);
IEnumerable<string> GetUserWithNotification();
SendNotificationAsync renvoie true si au moins un abonnement de l'utilisateur a reçu la notification. Les abonnements expirés ou invalides sont supprimés automatiquement lorsque le service de push répond Gone ou NotFound.
Exemple de service serveur :
public class UserAlertService
{
private readonly IWebPushNotificationManager _webPushNotificationManager;
public UserAlertService(IWebPushNotificationManager webPushNotificationManager)
{
_webPushNotificationManager = webPushNotificationManager;
}
public Task<bool> SendImportFinishedAsync(string userId, string title, string body, string targetRoute)
{
PushNotificationMessage message = new()
{
Title = title,
Body = body,
Icon = "icon-160.png",
RequireInteraction = true,
Data = targetRoute
};
// Envoie le même message à tous les navigateurs abonnés de l'utilisateur.
return _webPushNotificationManager.SendNotificationAsync(userId, message);
}
}
PushNotificationMessage sérialise les options transmises au service worker :
public string Title { get; set; }
public List<PushNotificationAction> Badge { get; set; }
public string Body { get; set; }
public string Data { get; set; }
public string Icon { get; set; }
public string Image { get; set; }
public bool Renotify { get; set; }
public bool RequireInteraction { get; set; }
public string Tag { get; set; }
public bool Silent { get; set; }
public int[] Vibrate { get; set; } = new int[] { 300, 100, 400 };
public override string ToString();
Options courantes :
Title: titre de la notification.Body: contenu affiché. Restez concis, car la longueur visible dépend du système.Icon: icône affichée à côté de la notification.Image: image optionnelle affichée par les navigateurs compatibles.RequireInteraction: sitrue, la notification ne se masque pas automatiquement.Data: route client Lagoon utilisée lors du clic sur la notification.
Lien avec les notifications utilisateur
Lorsque l'application utilise les notifications utilisateur temps réel et qu'un service worker WebPush est actif, les notifications utilisateur peuvent aussi déclencher une notification WebPush afin d'alerter l'utilisateur hors de l'onglet actif.
Gestion des abonnements
IWebPushSubscriptionManager centralise la persistance des abonnements :
void SaveRegistration(WebPushSubscription reg);
void UpdateRegistration(WebPushSubscription reg);
void DeleteRegistration(string userId, string endpoint);
VapidDetails GenerateVapidKeys();
Les endpoints internes Lagoon utilisent ces méthodes pour :
- sauvegarder un abonnement authentifié après
SubscribeToPushNotificationAsync(); - mettre à jour un abonnement renouvelé par le navigateur ;
- supprimer l'abonnement de l'utilisateur courant lors de la désinscription ;
- générer des clés VAPID en environnement de développement.
Une même personne peut avoir plusieurs abonnements, par exemple un navigateur desktop et un navigateur mobile.