mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-12 01:37:06 +02:00
Reprise d'AJEAN : le serveur pousse une notification vers les navigateurs abonnés, directement via leur service de push — donc app fermée et téléphone verrouillé, là où une notification côté page ne peut rien (un onglet caché relâche son flux SSE). Deux déclencheurs : la fin d'un tour utilisateur (sauf interruption par le bouton stop : celui qui a coupé est devant l'écran) et — ajout propre à Loki — la fin d'une TÂCHE PLANIFIÉE, succès comme échec. C'est le cas qui compte le plus : une tâche tourne justement quand personne ne regarde. Clés VAPID générées à la première demande et rangées dans la base ; abonnements persistés et purgés quand le service de push répond 404/410. Corps de notification générique, sans extrait de réponse : elle transite par Apple ou Google. /sw.js et /manifest.webmanifest sont servis à la racine (un service worker doit venir de l'origine) ; le worker ne fait QUE recevoir les push, sans cache — mettre l'UI en cache servirait une interface périmée après une mise à jour de l'image. Interrupteur dans Réglages → Mode agent, à armer sur chaque appareil. L'UI dit ce qui manque plutôt que d'échouer : HTTPS requis, notifications bloquées, ou iPhone à ajouter d'abord à l'écran d'accueil. Nouvelle dépendance : github.com/SherClockHolmes/webpush-go (RFC 8291). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
62 lines
2.2 KiB
Go
62 lines
2.2 KiB
Go
// web_push.go — endpoints d'inscription aux notifications Web Push (voir push.go).
|
|
// Tous passent par /api/* (donc par jfetch, et par le canal E2E à distance)
|
|
// en simple req/resp : le service worker et le manifest, eux, sont servis en clair
|
|
// à la racine (web_server.go), car un service worker doit venir de l'origine même.
|
|
package loki
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
// pushEndpointHost extrait l'hôte d'un endpoint de push pour les logs (ex.
|
|
// web.push.apple.com) sans divulguer le jeton complet.
|
|
func pushEndpointHost(endpoint string) string {
|
|
if u, err := url.Parse(endpoint); err == nil && u.Host != "" {
|
|
return u.Host
|
|
}
|
|
return "?"
|
|
}
|
|
|
|
// handlePushKey (GET) : remet la clé publique VAPID dont l'UI a besoin pour
|
|
// s'abonner (PushManager.subscribe applicationServerKey). Générée à la volée au
|
|
// premier appel, puis stable.
|
|
func handlePushKey(w http.ResponseWriter, r *http.Request) {
|
|
_, pub, err := vapidKeys()
|
|
if err != nil {
|
|
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
sendJSON(w, 200, map[string]any{"ok": true, "key": pub})
|
|
}
|
|
|
|
// handlePushSubscribe (POST) : enregistre l'abonnement PushSubscription du
|
|
// navigateur. Corps = l'objet renvoyé par subscription.toJSON() côté JS.
|
|
func handlePushSubscribe(w http.ResponseWriter, r *http.Request) {
|
|
var s pushSub
|
|
if err := json.NewDecoder(r.Body).Decode(&s); err != nil || s.Endpoint == "" {
|
|
sendJSON(w, 400, map[string]any{"ok": false, "error": "abonnement invalide"})
|
|
return
|
|
}
|
|
if err := addSub(s); err != nil {
|
|
fmt.Printf("[push] abonnement REFUSÉ (stockage) : %v\n", err)
|
|
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
|
|
return
|
|
}
|
|
fmt.Printf("[push] abonnement enregistré (%s) — %d au total\n", pushEndpointHost(s.Endpoint), len(loadSubs()))
|
|
sendJSON(w, 200, map[string]any{"ok": true})
|
|
}
|
|
|
|
// handlePushUnsubscribe (POST {endpoint}) : retire un abonnement (l'utilisateur a
|
|
// coupé les notifs dans les réglages).
|
|
func handlePushUnsubscribe(w http.ResponseWriter, r *http.Request) {
|
|
var body struct {
|
|
Endpoint string `json:"endpoint"`
|
|
}
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
removeSub(body.Endpoint)
|
|
sendJSON(w, 200, map[string]any{"ok": true})
|
|
}
|