La route era disegnata per un cron quotidiano: calcolava chi è di turno oggi e gli mandava una push. Ma nel repository nessun cron esiste, e palloni.md lo annotava già come "da verificare lato hosting": nei fatti quel promemoria non è mai partito. Il segreto condiviso introdotto ieri proteggeva una porta che nessuno apriva, al prezzo di una variabile d'ambiente da configurare ovunque. Ora la fa partire un amministratore dal pulsante "Avvisa chi è di turno", dentro il riquadro palloni dell'evento. La route accetta un eventoId e avvisa i destinatari di quell'evento invece della giornata corrente: chi deve prendere i palloni e chi deve riportarli, con un testo diverso per ciascuno. Stesso precedente di apri-sondaggio, manuale fin dalla v1.0.6. Il testo va in coda su promemoria_push prima dell'invio. Serve: la push parte vuota e il service worker chiede a push-messaggio cosa mostrare, ma quella route sa raccontare solo la giornata corrente, quindi un avviso mandato il martedì per il sabato arriverebbe con il testo generico. avvisiPalloniEvento() è la nuova funzione pura, con i suoi test: due destinatari con testi diversi, un avviso solo quando sono la stessa persona, niente avvisi per un evento inesistente o senza turni. Un'asserzione verifica che il testo non dica mai "oggi", perché può arrivare giorni prima. richiediSegreto e CRON_SEGRETO spariscono: tutte e tre le route di notifica usano ora richiediAdmin, e non resta nessuna variabile da configurare. destinatariPromemoriaPalloni() resta in palloni-core.ts con i suoi test perché push-messaggio continua a usarla per il testo calcolato al volo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.0 KiB
Modulo — Notifiche
Stato: implementato — un unico opt-in dispositivo abilita tutto il canale push
File principali: src/lib/notifiche-smart.ts, src/lib/push-client.ts,
src/lib/webpush.server.ts, src/routes/api/public/push-config.ts,
src/routes/api/public/push-messaggio.ts, src/routes/api/public/push-subscribe.ts,
public/push-sw.js
Obiettivo
Tenere aggiornati i giocatori senza che debbano aprire l'app, con due meccanismi indipendenti:
- Push VAPID — arrivano anche ad app chiusa (turno palloni, sollecito presenze).
- Notifiche smart — notifiche locali mostrate solo ad app aperta, generate da badge, serie e obiettivi appena raggiunti; non è un canale push separato.
Dati
push_subscriptions (un dispositivo per riga, chiave endpoint), promemoria_push (coda
"consuma e cancella" del testo da mostrare — nonostante il nome, non è uno storico
persistente: la riga viene eliminata non appena letta dal service worker).
Iscrizione alle notifiche push
In Profilo → Opzioni c’è un solo interruttore («Notifiche»). Non esistono preferenze separate per tipo di messaggio: l’iscrizione registra il dispositivo e lo rende destinatario di tutte le push (promemoria palloni, solleciti presenze) e abilita anche le notifiche smart in app, che usano lo stesso service worker.
- Il giocatore attiva «Notifiche» in
/profilo→ richiesta permesso browser. GET /api/public/push-configrestituisce solo la chiave pubblica VAPID.- Registrazione del service worker
public/push-sw.jsepushManager.subscribe(). POST /api/public/push-subscriberegistra endpoint e chiavi inpush_subscriptions(upsert).
Ruolo delle tre route pubbliche
push-config— espone la sola chiave pubblica VAPID.push-subscribe— registra o rimuove l'iscrizione di un dispositivo.apri-sondaggio— premuto da un admin dalla pagina partita: mette in coda supromemoria_pushl'avviso di apertura del sondaggio pre-partita per tutti i dispositivi iscritti e manda la push (vedi Scout Live).push-messaggio— non invia nulla: il service worker la interroga al momento della ricezione di una push (che arriva sempre "vuota", senza testo, per compatibilità) per sapere quale messaggio mostrare. Priorità: un messaggio in coda supromemoria_push(scritto dasollecita-presenze, vedi Presenze), altrimenti il messaggio calcolato al volo sul turno palloni (vedi Palloni).
L'invio effettivo (src/lib/webpush.server.ts, funzione inviaPush) firma un JWT VAPID
(ECDSA P-256) e fa una POST senza corpo all'endpoint push del browser; è riusato identico da
sollecita-presenze.ts e promemoria-palloni.ts.
Chi può farle partire (DD-024, DD-025)
Queste route usano la service role e saltano la RLS, quindi il permesso deve stare nella
route. Tutte e tre partono da un gesto di un amministratore dentro l'app, quindi il controllo
è uno solo (richiediAdmin in src/lib/auth-route.server.ts) e non serve configurare nessuna
variabile d'ambiente.
| Route | Controllo | Chi la chiama |
|---|---|---|
apri-sondaggio, sollecita-presenze, promemoria-palloni |
richiediAdmin — token della sessione Supabase, poi ruolo admin in user_roles |
l'app, da un pulsante riservato agli admin |
csi, push-config, push-subscribe, push-messaggio |
nessuno | il browser prima del login e il service worker, che una sessione non ce l'hanno |
Notifiche smart
calcolaNotifiche() (notifiche-smart.ts) genera un evento solo quando "c'è qualcosa di
reale": badge appena sbloccato, "sei a un passo" da un traguardo, serie che raggiunge un
traguardo esatto, obiettivo di squadra tra il 90 e il 100%, badge social vinto. Ogni notifica
ha un id deterministico; quelli già mostrati sono salvati in localStorage per non
ripetersi — deduplica puramente locale al dispositivo, non sincronizzata.
Limiti noti
- Non ci sono preferenze granulari (solo palloni / solo presenze / solo smart): un dispositivo è iscritto o no. Separare i canali richiederebbe schema e UI dedicati.
promemoria_pushè descritta altrove come "storico" ma nel codice è una coda che si autocancella alla lettura: non conserva nulla.- Nessuna verifica di autenticazione su
push-messaggio: chiunque conosca un endpoint push valido può leggerne il messaggio. Non è chiudibile con un segreto, perché a chiamarla è il service worker, dove qualsiasi segreto sarebbe pubblico; di fatto la protegge il dover conoscere l'endpoint, che è un URL segreto per dispositivo.promemoria-palloniinvece è chiusa da DD-024. - Compatibilità iOS/Safari non gestita esplicitamente nel codice (nessun branch dedicato): serve l'installazione da schermata Home per funzionare, ma l'app non lo segnala esplicitamente.
- Le notifiche smart dipendono da un service worker già registrato: se il giocatore non ha
mai attivato le push,
notificaSistema()non ha unrega cui appoggiarsi e la notifica locale non viene mai mostrata, anche con permesso concesso. - Payload push sempre vuoto: ogni notifica richiede una fetch aggiuntiva (
push-messaggio) per ottenere il testo, quindi serve rete disponibile anche solo per mostrare il messaggio.
Evoluzioni possibili
- Preferenze per canale (palloni, solleciti, smart), se servono davvero alla squadra.
- Aggiungere autenticazione alle route pubbliche coinvolte.
- Gestire esplicitamente il caso iOS (messaggio se l'app non è installata da Home).