Aller au contenu

Notifications Vapid/WebPush

Aperçu

Web Push est la façon dont un site web envoie une notification à un navigateur qui l’a déjà visité. Si vous n’en avez jamais configuré, l’essentiel à comprendre est que ce n’est pas vous qui choisissez la destination de la notification : c’est le navigateur qui vous l’indique.

Le déroulement est le suivant :

  1. Quelqu’un visite votre site et clique sur le bouton que vous utilisez pour demander l’autorisation d’envoyer des notifications.
  2. Son navigateur renvoie à votre site un petit bloc de JSON appelé abonnement. Il contient un endpoint (une longue adresse web appartenant à Google, Mozilla, Apple ou à l’éditeur de ce navigateur) ainsi que deux clés servant à chiffrer les messages pour que seul ce navigateur puisse les lire.
  3. Vous enregistrez ce JSON dans un fichier subscriptions.json.
  4. Apprise lit le fichier, chiffre votre message pour ce navigateur, le signe avec votre clé privée et l’envoie à l’endpoint fourni par le navigateur.

L’endpoint est propre à un navigateur sur un appareil donné. Rien dans votre URL Apprise ne décide de la destination d’un message : celle-ci provient entièrement de l’abonnement. C’est pourquoi le fichier subscriptions.json est nécessaire avant qu’Apprise puisse notifier qui que ce soit.

Trois éléments sont nécessaires :

ÉlémentDe quoi il s’agit
Une adresse de contactApprise l’utilise comme identité d’expéditeur VAPID. Il s’agit de la première partie après vapid://, par exemple vous@example.com.
Une clé privéeUn fichier private_key.pem utilisé pour signer chaque message afin que le service de push puisse confirmer qu’il vient bien de vous. Apprise peut le créer pour vous.
subscriptions.jsonLes abonnements de navigateurs que vous avez collectés, comme décrit ci-dessus. Vous devez fournir ce fichier vous-même.

Choisissez la paire de clés VAPID avant que les navigateurs s’abonnent. Votre site fournit la clé publique au navigateur sous le nom applicationServerKey ; Apprise a besoin du fichier private_key.pem correspondant. Le service de push rejette un message signé avec une autre clé.

Les valeurs p256dh et auth de subscriptions.json sont des clés de chiffrement propres au navigateur. Elles ne sont pas votre clé publique VAPID.

Si votre site crée déjà des abonnements Web Push, réutilisez sa clé privée VAPID. Un changement de clé oblige tous les navigateurs à se réabonner.

Créez une clé privée P-256 et gardez-la secrète :

Fenêtre de terminal
openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
chmod 600 private_key.pem

De nombreux frameworks web peuvent calculer la valeur publique. Si le vôtre ne le fait pas, exécutez ceci à côté de private_key.pem, puis copiez la valeur affichée dans le paramètre applicationServerKey de votre site :

Fenêtre de terminal
python - <<'PY'
from base64 import urlsafe_b64encode
from cryptography.hazmat.primitives import serialization
with open("private_key.pem", "rb") as stream:
key = serialization.load_pem_private_key(stream.read(), password=None)
raw = key.public_key().public_bytes(
serialization.Encoding.X962,
serialization.PublicFormat.UncompressedPoint,
)
print(urlsafe_b64encode(raw).rstrip(b"=").decode())
PY

Votre site demande ensuite au navigateur de s’abonner avec cette valeur publique et enregistre subscription.toJSON(). Le résultat contient les valeurs endpoint, p256dh et auth nécessaires à Apprise.

  1. Créez ou choisissez une paire de clés VAPID.
  2. Configurez sa valeur publique comme applicationServerKey du site.
  3. Demandez l’autorisation à chaque navigateur et enregistrez son abonnement JSON.
  4. Placez les abonnements dans le format nommé présenté ci-dessous.
  5. Donnez à Apprise la clé privée correspondante et le fichier d’abonnements.
  6. Envoyez un test avec -vv afin d’afficher tout problème de configuration.

Apprise doit trouver votre subscriptions.json (et un private_key.pem). Il y a trois façons de s’organiser ; choisissez celle qui correspond à votre manière d’exécuter Apprise.

Idéal lorsque vous exécutez Apprise vous-même et que vous avez accès au système de fichiers.

Omettez complètement subfile et keyfile de l’URL. Apprise attribue à chaque URL son propre répertoire et y cherche les fichiers. Il peut créer une paire de clés, mais les navigateurs doivent s’abonner avec sa clé publique : créez ou repérez donc la clé avant de collecter les abonnements.

Lorsque la génération automatique des clés PEM est activée (par défaut), Apprise crée tout fichier subscriptions.json manquant avec une entrée d’exemple. Remplacez cet espace réservé par votre propre abonnement avant d’envoyer des notifications.

  1. Trouvez le répertoire de votre URL :

    Fenêtre de terminal
    apprise storage list
  2. Copiez vos abonnements dans le répertoire indiqué :

    Fenêtre de terminal
    cp subscriptions.json ~/.local/share/apprise/cache/<id>/
  3. Utilisez une URL propre qui ne pointe vers rien :

    Fenêtre de terminal
    apprise -vv -t "Titre" -b "Contenu du message" \
    vapid://vous@example.com/phone

C’est l’option la plus propre. L’URL ne contient aucun chemin de fichier : elle peut donc être copiée d’une machine à l’autre et partagée dans un fichier de configuration. Voir Déplacer Votre Configuration vers un Autre Serveur.

La syntaxe valide est la suivante :

  • vapid://vous@example.com/
  • vapid://vous@example.com/telephone
  • vapid://vous@example.com/portable/telephone/

Une target est le nom donné à un abonnement dans subscriptions.json. Sans cible dans l’URL, Apprise recherche celle qui porte votre adresse de contact.

VariableRequisDescription
keyfileNonLa clé privée VAPID P-256 au format PEM. En son absence, Apprise utilise celle du stockage persistant de cette URL et peut en créer une. Sa partie publique doit correspondre à la clé utilisée par le navigateur.
subfileNonUn fichier subscriptions.json identifiant la configuration à référencer. Il peut s’agir d’un chemin local ou d’un emplacement distant tel que https://user:pass@example.com/subscriptions.json. En son absence, le fichier du répertoire de stockage persistant de cette URL est utilisé.
modeNonUne étiquette de compatibilité conservée dans l’URL (par défaut chrome). Elle ne choisit pas la destination. Laissez-la généralement inchangée. Valeurs possibles : chrome, firefox, edge, opera et apple.
ttlNonDurée, en secondes, pendant laquelle le service de push peut conserver un message pour un appareil hors ligne (par défaut 0, maximum 60). 0 signifie livrer immédiatement ou abandonner le message.

mode est conservé pour la compatibilité et fait partie de l’identité de stockage de l’URL. Il ne choisit pas le service de push : la livraison utilise toujours l’endpoint de chaque abonnement. La plupart des utilisateurs doivent conserver la valeur par défaut. Un changement peut sélectionner un autre répertoire de stockage persistant.

ModeFamille de navigateurs
chromeGoogle Chrome
firefoxMozilla Firefox
edgeMicrosoft Edge
operaOpera
appleSafari et autres navigateurs Apple
VariableDescription
overflowGère les messages qui dépassent la limite documentée d’un service. La valeur par défaut est upstream.
👉 upstream : envoie un seul message sans le découper ni le tronquer selon cette limite.
👉 truncate : conserve la partie qui tient dans la limite et supprime le reste.
👉 split : privilégie une coupure lisible, utilise une coupure stricte si nécessaire, puis envoie toutes les parties dans l’ordre.
Le découpage d’un contenu non déclaré ou structuré reste une solution au mieux. Utilisez upstream si le corps doit rester intact.
formatCe paramètre peut être défini sur text, html ou markdown. Certains services prennent en charge plusieurs formats de publication du contenu. La valeur par défaut varie selon le service choisi et peut correspondre à l’un de ces trois formats. Vous pouvez facultativement forcer cette option pour vous écarter du comportement par défaut. Si le service ne prend pas en charge plusieurs formats de transmission, ce champ est ignoré.
verifyLes requêtes externes vers des emplacements sécurisés, par exemple via https, utilisent des certificats. Par défaut, Apprise vérifie la validité de ces certificats ; si ce n’est pas le cas, aucune notification n’est envoyée à la source. Dans certains cas, un utilisateur ne dispose pas d’une autorité de certification pour valider la clé ou fait simplement confiance à la source ; dans ce cas, vous pouvez définir ce drapeau sur no. Par défaut, il vaut yes.
redirectPar défaut, Apprise suit les redirections HTTP (réponses 3xx) émises par le serveur distant, conformément au comportement de la bibliothèque requests sous-jacente. Si vous souhaitez empêcher la transmission des en-têtes personnalisés et des identifiants vers des destinations différentes de l’URL d’origine, définissez cette option sur no. Par défaut, elle vaut yes.
ctoSignifie Socket Connect Timeout. Il s’agit du nombre de secondes pendant lesquelles Requests attend que votre client établisse une connexion avec une machine distante, ce qui correspond à l’appel connect() sur la socket. La valeur par défaut est de 4.0 secondes.
rtoSignifie Socket Read Timeout. Il s’agit du nombre de secondes pendant lesquelles le client attend que le serveur envoie une réponse. La valeur par défaut est de 4.0 secondes.
emojisActive la prise en charge des emojis, par exemple :+1: qui sera traduit en 👍. Par défaut, cette option vaut no.
Remarque : selon la configuration côté serveur, l’administrateur peut désactiver globalement la prise en charge des emojis ; mais par défaut, ce n’est pas le cas.
tzIdentifie le fuseau horaire de la base IANA que vous souhaitez utiliser. Par défaut, celui-ci est détecté à partir de la configuration du serveur qui exécute Apprise. Vous pouvez le définir sur des valeurs comme America/Toronto, ou sur tout autre fuseau correctement formaté correspondant à votre région.
retryNombre de tentatives de remise supplémentaires après le premier échec, avant d’abandonner. Accepte un entier compris entre 0 et 10. La valeur par défaut est 0 (aucune nouvelle tentative — une seule tentative est effectuée). Lorsqu’il est combiné avec wait, Apprise marque une pause du nombre de secondes spécifié entre chaque tentative.
waitNombre de secondes à attendre entre les nouvelles tentatives. Accepte une valeur décimale comprise entre 0.0 et 20.0 ; les entiers sont automatiquement convertis en virgule flottante. La valeur par défaut est 0.5. Ce paramètre n’est utile que si retry est supérieur à zéro — un service avec retry=0 effectue exactement une tentative, quelle que soit la valeur de wait.
optionalLorsqu’il est défini sur yes, un échec de remise pour ce service est silencieusement absorbé. Le résultat global de notify() est quand même considéré comme réussi même si ce point de terminaison était injoignable, à condition que tous les services requis (non optionnels) du même lot aient réussi. Ce drapeau ne saute pas la remise ni ne contourne la logique de nouvelles tentatives — toutes les tentatives configurées sont effectuées avant que l’échec soit absorbé. Par défaut, il vaut no, ce qui signifie que chaque échec est propagé à l’appelant.

VAPID nécessite un fichier subscriptions.json. Apprise accepte deux formats :

  1. autonome ; dans l’exemple ci-dessous, la cible serait abc123

    {
    "endpoint": "https://fcm.googleapis.com/fcm/send/abc123",
    "keys": {
    "p256dh": "BNcW4oA7zq5H9TKIrA3XfKclN2fX9P_7NR...",
    "auth": "k9Xzm43nBGo="
    }
    }
  2. prise en charge de plusieurs cibles ; dans l’exemple ci-dessous, 2 cibles sont créées, nommées name1 et name2

    {
    "name1": {
    "endpoint": "https://fcm.googleapis.com/fcm/send/...",
    "keys": {
    "p256dh": "BNcW4oA7zq5H9TKIrA3XfKclN2fX9P_7NR...",
    "auth": "k9Xzm43nBGo="
    }
    },
    "name2": {
    "endpoint": "https://web.push.apple.com/...",
    "keys": {
    "p256dh": "BNcW4oA7zq5H9TKIrA3XfKclN2fX9P_7NR...",
    "auth": "k9Xzm43nBGo="
    }
    }
    }

Les cibles permettent de notifier un ou plusieurs endpoints. Les noms ne sont pas sensibles à la casse, et un même fichier peut contenir des abonnements de navigateurs différents.

Utilisez le format nommé à plusieurs cibles pour une installation normale. Il est plus facile à gérer et permet à Apprise de supprimer séparément les entrées expirées. Un fichier autonome ne possède aucun nom d’entrée à retirer : remplacez-le manuellement lorsque son abonnement expire.

Copiez chaque endpoint exactement tel que le navigateur vous l’a fourni. Apprise vérifie chacun d’eux avant de l’utiliser et ignore tout abonnement dont l’endpoint :

  • ne commence pas par https:// (Web Push ne fonctionne pas en http simple)
  • n’a pas de nom d’hôte, ou a un nom d’hôte invalide
  • contient un nom d’utilisateur ou un mot de passe

Les gens désinstallent des applications, effacent les données de leur navigateur et changent de téléphone. Dans ce cas, le service de push indique à Apprise que l’abonnement n’existe plus, et il ne sert plus à rien de réessayer cet endpoint. Ce qu’Apprise fait ensuite dépend de son droit ou non de modifier votre fichier :

  • Le fichier est accessible en écriture. L’entrée morte en est retirée, ce qui garde le fichier propre au fil des appareils qui arrivent et repartent. Seule cette entrée est supprimée ; tout le reste est laissé exactement tel quel, y compris une entrée qu’Apprise n’a pas pu lire. Si votre fichier contient une faute de frappe, elle y reste pour que vous la corrigiez plutôt que d’être supprimée en silence.
  • Le fichier est en lecture seule, ou il est distant. Apprise n’y touche pas et mémorise l’endpoint dans son stockage persistant pendant 30 jours. Un envoi ultérieur l’ignore ; après 30 jours, il peut être vérifié à nouveau. Sans stockage persistant, cette mémoire ne survit pas à un redémarrage.

Lorsqu’un fichier local est réécrit, les liens symboliques sont suivis et Apprise tente de conserver les permissions et le propriétaire. Le remplacement est atomique : les lecteurs ne voient pas de JSON à moitié écrit. Les écritures simultanées ne sont pas verrouillées ; évitez que plusieurs processus modifient le même fichier en même temps.

Apprise signe les messages avec private_key.pem. Ce fichier doit correspondre à l’applicationServerKey publique utilisée lors de l’abonnement du navigateur. Placez-le dans le stockage persistant, ou choisissez un fichier local ou distant avec keyfile. Consultez Utiliser la Même Clé VAPID Partout avant de créer des abonnements.

Lorsque le stockage persistant est activé, chaque URL Apprise reçoit son propre répertoire nommé d’après un identifiant calculé à partir de l’URL elle-même. Vos fichiers Vapid s’y trouvent :

<stockage>/<id>/subscriptions.json
<stockage>/<id>/private_key.pem

La commande suivante liste tous les emplacements de stockage persistant associés à votre configuration :

Fenêtre de terminal
apprise storage list

Repérez simplement l’ID associé au compte Vapid que vous souhaitez mettre à jour ; notez que les ID de répertoire se trouvent à :

  1. Microsoft Windows : %APPDATA%/Apprise/cache
  2. Linux : ~/.local/share/apprise/cache

Pour plus de détails à ce sujet, voir ici.

Déplacer Votre Configuration vers un Autre Serveur

Section intitulée « Déplacer Votre Configuration vers un Autre Serveur »

L’identifiant de stockage est calculé à partir de votre URL Apprise et non à partir d’un élément propre à une machine : la même URL aboutit donc au même identifiant partout. Déplacer une configuration fonctionnelle vers une autre installation d’Apprise se résume donc à une copie :

  1. Exécutez apprise storage list sur l’ancien serveur et notez l’ID de votre URL Vapid.
  2. Copiez les fichiers subscriptions.json et private_key.pem de ce répertoire dans le répertoire du même nom sous le chemin de stockage du nouveau serveur.
  3. Utilisez la même URL Apprise sur le nouveau serveur.

Votre URL reste propre et ne contient aucun chemin de fichier : elle peut donc être conservée sans risque dans un fichier de configuration partagé par plusieurs machines.

Pour une installation reproductible, conservez l’URL et les emplacements de fichiers dans une configuration YAML Apprise :

urls:
- vapid://vous@example.com/telephone:
keyfile: /path/to/private_key.pem
subfile: /path/to/subscriptions.json

Le keyfile et le subfile peuvent également être hébergés ailleurs, y compris derrière un nom d’utilisateur et un mot de passe. C’est ainsi que vous feriez fonctionner Vapid sous l’API Apprise, où vous n’avez aucun moyen simple de placer des fichiers sur le serveur :

urls:
- vapid://vous@example.com/telephone:
keyfile: https://user:pass123@example.com/private_key.pem
subfile: https://user:pass123@example.com/subscriptions.json

Si vous utilisez le stockage persistant, vous pouvez omettre les deux entièrement et laisser Apprise utiliser les fichiers du répertoire de stockage propre à cette URL :

urls:
- vapid://vous@example.com:

Notifier l’abonnement portant le nom de votre propre adresse de contact :

Fenêtre de terminal
apprise -vv -t "Titre" -b "Contenu du message" \
vapid://vous@example.com

Notifier deux cibles nommées de votre subscriptions.json :

Fenêtre de terminal
apprise -vv -t "Titre" -b "Contenu du message" \
vapid://vous@example.com/laptop/phone

Pointer vers vos propres fichiers au lieu des copies du stockage persistant :

Fenêtre de terminal
apprise -vv -t "Titre" -b "Contenu du message" \
"vapid://vous@example.com/phone?keyfile=/etc/apprise/private_key.pem&subfile=/etc/apprise/subscriptions.json"

Demander au service de push de conserver un message jusqu’à 30 secondes si l’appareil est hors ligne :

Fenêtre de terminal
apprise -vv -t "Titre" -b "Contenu du message" \
"vapid://vous@example.com/phone?ttl=30"

Exécutez les tests avec -vv ; les échecs VAPID proviennent généralement de la configuration.

SymptômePoints à Vérifier
Vapid could not load subscriptionsVérifiez subfile, le stockage persistant, la syntaxe JSON et les permissions du fichier.
Cible introuvableFaites correspondre la cible de l’URL à un nom de premier niveau dans subscriptions.json.
Réponse 401 ou 403La clé privée ne correspond probablement pas à l’applicationServerKey publique utilisée pour créer l’abonnement.
Réponse 404 ou 410L’abonnement a expiré. Demandez à ce navigateur de se réabonner et enregistrez son nouveau JSON.
Endpoint refuséCopiez exactement l’endpoint HTTPS du navigateur ; n’ajoutez pas d’identifiants et ne modifiez pas son hôte.
Fonctionne localement, mais pas dans un conteneurMontez les deux fichiers, utilisez le stockage persistant ou indiquez des chemins présents dans le conteneur.
Questions ou commentaires ?

Documentation

Vous avez repéré une faute de frappe ou une erreur ?

Problèmes Techniques

Vous rencontrez un problème avec le code ? Ouvrez un ticket sur GitHub :

Conçu avec amour depuis le Canada