Il testo della notifica viaggia ora cifrato dentro la push (aes128gcm, RFC
8188/8291) invece di essere recuperato dal service worker con una fetch al
risveglio. Era quella fetch a non chiudersi in tempo: ad app chiusa il browser
tiene vivo il worker pochi secondi, showNotification non veniva mai chiamata e
non compariva niente, mentre ad app aperta con la rete calda sembrava tutto a
posto. La POST porta anche Urgency: high, che chiede la consegna immediata
invece di far accumulare i messaggi fino al risveglio del dispositivo.
Cadono i pezzi che esistevano solo per rimediare al payload vuoto: la route
push-messaggio, la coda promemoria_push con la sua scadenza a 12 ore,
messaggioPalloniOggi() e il timeout nel worker. Tutti e tre i mittenti avevano
gia il testo pronto prima di inviare.
Il worker si aggiorna da solo all'avvio e a ogni ritorno in primo piano
(mantieniWorkerPushAggiornato): nella webapp installata quello vecchio puo
sopravvivere a lungo, e senza questo un dispositivo resterebbe fermo alla
versione che va a cercare il testo in rete.
Profilo -> Opzioni ha "Mandami una notifica di prova", visibile solo a notifiche
attive: manda una push a questo dispositivo e riporta stato HTTP, corpo della
risposta e se l'endpoint risulta davvero in push_subscriptions. Senza, "non
arriva" era cieco: ogni prova richiedeva un admin, un evento nello stato giusto
e una seconda persona, e la risposta del servizio push veniva buttata via.
inviaPush torna { stato, corpo } e logga il corpo sui rifiuti.
Dalle prove sul campo: a parita di server, iPhone installato da Home riceve ad
app chiusa. Su Android installato come webapp resta da verificare: il WebAPK e
un'app Android a se, con permesso notifiche (Android 13+) e voce batteria
distinti da quelli di Chrome. Annotato nei limiti noti.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
import { createFileRoute } from "@tanstack/react-router";
|
|
import { z } from "zod";
|
|
import { richiediAdmin } from "@/lib/auth-route.server";
|
|
import { leggiEventi } from "@/lib/eventi.server";
|
|
import { inviaPush } from "@/lib/webpush.server";
|
|
|
|
const schema = z.object({ eventoId: z.string().min(1).max(50) });
|
|
|
|
/** Avviso "sondaggio pre-partita aperto": lo fa partire un admin dalla pagina partita. */
|
|
export const Route = createFileRoute("/api/public/apri-sondaggio")({
|
|
server: {
|
|
handlers: {
|
|
POST: async ({ request }) => {
|
|
const negato = await richiediAdmin(request);
|
|
if (negato) return negato;
|
|
|
|
const parsed = schema.safeParse(await request.json());
|
|
if (!parsed.success) return new Response("Dati non validi", { status: 400 });
|
|
|
|
const eventi = await leggiEventi();
|
|
const partita = eventi.find((e) => e.id === parsed.data.eventoId);
|
|
if (!partita) return new Response("Evento non trovato", { status: 404 });
|
|
|
|
const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
|
|
const { data: iscrizioni } = await supabaseAdmin
|
|
.from("push_subscriptions")
|
|
.select("endpoint, p256dh, auth");
|
|
|
|
const titolo = "💩 Sondaggio pre-partita aperto";
|
|
const testo = `${partita.titolo} · ore ${partita.ora}. Quante cacche hai fatto? Rispondi prima del fischio d'inizio.`;
|
|
|
|
let inviate = 0;
|
|
for (const iscrizione of iscrizioni ?? []) {
|
|
try {
|
|
const { stato } = await inviaPush(iscrizione, titolo, testo);
|
|
if (stato === 404 || stato === 410) {
|
|
await supabaseAdmin
|
|
.from("push_subscriptions")
|
|
.delete()
|
|
.eq("endpoint", iscrizione.endpoint);
|
|
} else if (stato >= 200 && stato < 300) {
|
|
inviate += 1;
|
|
}
|
|
} catch (error) {
|
|
console.error("apri-sondaggio", error);
|
|
}
|
|
}
|
|
|
|
return Response.json({ inviate, destinatari: (iscrizioni ?? []).length });
|
|
},
|
|
},
|
|
},
|
|
});
|