Fa arrivare le push ad app chiusa e rende diagnosticabile quando non arrivano.
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>
This commit is contained in:
@@ -7,6 +7,8 @@ import assert from "node:assert/strict";
|
||||
import {
|
||||
attivaNotifiche,
|
||||
disattivaNotifiche,
|
||||
mantieniWorkerPushAggiornato,
|
||||
notificaDiProva,
|
||||
pushSupportato,
|
||||
statoNotifiche,
|
||||
} from "@/lib/push-client";
|
||||
@@ -21,7 +23,113 @@ assert.equal(await statoNotifiche(), false);
|
||||
// --- attivaNotifiche: rifiuta subito, senza chiedere permessi o rete ----------
|
||||
await assert.rejects(() => attivaNotifiche("g1"), /Notifiche non supportate su questo dispositivo/);
|
||||
|
||||
// --- notificaDiProva: rifiuta subito, senza toccare la rete -------------------
|
||||
await assert.rejects(() => notificaDiProva(), /Notifiche non supportate su questo dispositivo/);
|
||||
|
||||
// --- disattivaNotifiche: no-op silenzioso, nessun errore ----------------------
|
||||
await assert.doesNotReject(() => disattivaNotifiche());
|
||||
|
||||
// Il mantenimento del worker è innocuo anche durante SSR.
|
||||
assert.doesNotThrow(mantieniWorkerPushAggiornato());
|
||||
|
||||
const originali = new Map(
|
||||
["window", "navigator", "document"].map((nome) => [
|
||||
nome,
|
||||
Object.getOwnPropertyDescriptor(globalThis, nome),
|
||||
]),
|
||||
);
|
||||
const documento = Object.assign(new EventTarget(), { visibilityState: "visible" });
|
||||
let letture = 0;
|
||||
let aggiornamenti = 0;
|
||||
let presente = true;
|
||||
let errore = false;
|
||||
let sblocca: (() => void) | undefined;
|
||||
let attesa: Promise<void> | undefined;
|
||||
let interrompi = () => {};
|
||||
const ciclo = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
const visibilita = async (stato: string) => {
|
||||
documento.visibilityState = stato;
|
||||
documento.dispatchEvent(new Event("visibilitychange"));
|
||||
await ciclo();
|
||||
};
|
||||
|
||||
try {
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
configurable: true,
|
||||
value: { PushManager: {}, Notification: {} },
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", { configurable: true, value: documento });
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
serviceWorker: {
|
||||
async getRegistration(url: string) {
|
||||
assert.equal(url, "/push-sw.js");
|
||||
letture++;
|
||||
return presente
|
||||
? {
|
||||
async update() {
|
||||
aggiornamenti++;
|
||||
if (errore) throw new Error("offline");
|
||||
await attesa;
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
},
|
||||
// Un aggiornamento non deve cancellare o sostituire la registrazione.
|
||||
register() {
|
||||
assert.fail("non deve registrare un nuovo worker");
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interrompi = mantieniWorkerPushAggiornato();
|
||||
await ciclo();
|
||||
assert.equal(aggiornamenti, 1, "aggiorna il worker esistente all'avvio");
|
||||
|
||||
await visibilita("hidden");
|
||||
assert.equal(letture, 1, "nessun lavoro quando l'app viene chiusa");
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 2, "controlla gli aggiornamenti al ritorno nella webapp");
|
||||
|
||||
errore = true;
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 3, "un errore offline non blocca l'app");
|
||||
errore = false;
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 4, "riprova dopo l'errore");
|
||||
|
||||
attesa = new Promise<void>((resolve) => (sblocca = resolve));
|
||||
await visibilita("visible");
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 5, "accorpa gli aggiornamenti sovrapposti");
|
||||
sblocca!();
|
||||
await ciclo();
|
||||
attesa = undefined;
|
||||
|
||||
presente = false;
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 5, "nessuna nuova iscrizione senza registrazione");
|
||||
|
||||
interrompi();
|
||||
const prima = letture;
|
||||
await visibilita("visible");
|
||||
assert.equal(letture, prima, "rimuove il listener allo smontaggio");
|
||||
|
||||
documento.visibilityState = "hidden";
|
||||
presente = true;
|
||||
interrompi = mantieniWorkerPushAggiornato();
|
||||
await ciclo();
|
||||
assert.equal(letture, prima, "non aggiorna se parte già in background");
|
||||
await visibilita("visible");
|
||||
assert.equal(aggiornamenti, 6);
|
||||
} finally {
|
||||
interrompi();
|
||||
for (const [nome, originale] of originali) {
|
||||
if (originale) Object.defineProperty(globalThis, nome, originale);
|
||||
else Reflect.deleteProperty(globalThis, nome);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("push-client: ok");
|
||||
|
||||
Reference in New Issue
Block a user