diff --git a/server/pruef-anruf-klingelt.mjs b/server/pruef-anruf-klingelt.mjs new file mode 100644 index 00000000..cc2d5b2b --- /dev/null +++ b/server/pruef-anruf-klingelt.mjs @@ -0,0 +1,165 @@ +/* ===================================================================== + pruef-anruf-klingelt.mjs — Klingelt es auch wirklich? (19.09.2026) + + Filipe: „irgendwas klappt mit dem telefonieren nicht." + + --------------------------------------------------------------------- + WIE DER FEHLER GEFUNDEN WURDE — UND WARUM DAS DER WICHTIGE TEIL IST + + Ich habe zuerst die Vermittlung verdächtigt: coturn geprüft, STUN + gemessen, eine echte Zuteilung von außen gemacht, den TCP-Weg + nachgerüstet. Alles richtig und alles nötig — aber es war NICHT die + Ursache. + + Die Ursache stand im Protokoll des Servers, und sie stand dort die + ganze Zeit: + + 24× anruf_start, jeder nach 7–39 Sekunden beendet + im Chat danach jedes Mal: „📞 Verpasster Anruf" + die Gegenseite schrieb: „Hab keinen Anrufeingang gehabt + und keine Benachrichtigung" + + Es ging nie um Ton oder Verbindung. Es hat bei der anderen Person + schlicht nicht geklingelt. + + Das ist dieselbe Lehre wie am 06.09. („kommt keine Mail an" — erst + fragen, OB gesendet wurde) und am 14.09. (zwei Tage Audiowege + verfolgt, während die CPU bei 98 % stand): **Erst das Protokoll + lesen, dann suchen.** Eine halbe Minute in der richtigen Tabelle + hätte Stunden gespart. + + --------------------------------------------------------------------- + WAS WIRKLICH DEFEKT WAR + + Die Benachrichtigung wurde zugestellt — `zuletzt_ok` am Gerät stand + auf genau die Anrufminute. Sie hat nur nicht geklingelt: + + 1. `Urgency: "normal"` stand FEST im Transport, für jede Meldung. + Auf Android entscheidet dieser Kopf, ob sofort zugestellt wird + oder bis zum nächsten Aufwachen gewartet. Im Stromsparmodus + werden daraus Minuten — bei einem Anruf, der 120 Sekunden + klingelt, ist das ein verpasster Anruf. + + 2. Im Service Worker war `renotify: false` und `tag: d.art`. Eine + zweite Meldung mit derselben Kennung ersetzt die erste LAUTLOS. + Wer ein zweites Mal anruft, WEIL nicht abgehoben wurde, löst + damit gar keinen Ton mehr aus. + + 3. Kein `vibrate`, kein `requireInteraction`. Ein stiller Kasten, + der von selbst verschwindet — in einer Hosentasche unsichtbar. + + Für „Aufgabe überfällig" ist all das genau richtig. Für ein + klingelndes Telefon ist es das Gegenteil. Deshalb unterscheidet das + Haus jetzt zwischen beidem. + + AUFRUF: node server/pruef-anruf-klingelt.mjs + Läuft in unter einer Sekunde, ohne Server, ohne Netz. + ===================================================================== */ + +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const REPO = join(dirname(fileURLToPath(import.meta.url)), ".."); +const lies = (p) => readFileSync(join(REPO, p), "utf8"); + +let ok = 0, fehl = 0; +const pruef = (name, bedingung, zusatz = "") => { + if (bedingung) { ok++; console.log(` OK ${name}`); } + else { fehl++; console.log(` FEHL ${name}${zusatz ? " — " + zusatz : ""}`); } +}; + +console.log("\n=== Klingelt ein Anruf auch wirklich? ===\n"); + +/* ===================================================================== + 1. DER SERVICE WORKER — die Anzeige auf dem Gerät + ===================================================================== */ +const sw = lies("workspace/sw.js"); + +pruef("der Dienst unterscheidet einen Anruf von anderen Meldungen", + /istAnruf\s*=\s*d\.art\s*===\s*"anruf"/.test(sw)); + +pruef("ein Anruf bleibt stehen, bis jemand reagiert", + /requireInteraction:\s*istAnruf/.test(sw), + "ohne requireInteraction verschwindet er von selbst"); + +pruef("ein zweiter Anruf meldet sich hörbar (renotify)", + /renotify:\s*istAnruf/.test(sw), + "mit renotify:false ersetzt der zweite den ersten LAUTLOS — genau der Fehler"); + +pruef("das Handy vibriert beim Anruf", + /vibrate:\s*istAnruf\s*\?/.test(sw)); + +pruef("jeder Anruf bekommt eine eigene Kennung", + /tag:\s*istAnruf\s*\?\s*\("anruf-"/.test(sw), + "eine gemeinsame Kennung lässt den zweiten Anruf den ersten still ersetzen"); + +/* DIE ANDEREN MELDUNGEN BLEIBEN, WIE SIE WAREN. Das ist kein + Nebensatz: Würden alle Benachrichtigungen stehenbleiben und + vibrieren, schaltet sie nach drei Tagen jemand ab -- und dann + klingelt auch der Anruf nie wieder. */ +pruef("und für alles andere bleibt es ruhig", + /\(d\.art \|\| "workspace"\)/.test(sw) && !/requireInteraction:\s*true\b/.test(sw)); + +/* ===================================================================== + 2. DER VERSAND — Dringlichkeit und Haltbarkeit + ===================================================================== */ +const krypto = lies("server/workspace-push-krypto.js"); +const versand = lies("server/workspace-push.js"); + +pruef("der Transport kennt überhaupt eine Dringlichkeit", + /dringend\s*=\s*false\s*\}\s*=\s*\{\}/.test(krypto), + "vorher stand Urgency fest auf \"normal\""); + +pruef("ein dringender Push geht mit Urgency: high hinaus", + /Urgency:\s*dringend\s*\?\s*"high"\s*:\s*"normal"/.test(krypto)); + +pruef("und verfällt, wenn der Anruf vorbei ist", + /TTL:\s*String\(dringend\s*\?\s*Math\.min\(ttl,\s*\d+\)/.test(krypto), + "sonst ploppt ein Anruf von vor einer Stunde nachträglich auf"); + +pruef("der Anruf wird als dringend verschickt", + /schicken\(a,\s*inhalt,\s*paar,\s*ABSENDER,\s*\{\s*dringend\s*\}\)/.test(versand)); + +/* DIESELBE BEDINGUNG WIE BEI DER RUHEZEIT. „Darf das nachts stören?" + und „darf das warten?" haben dieselbe Antwort -- zwei getrennte + Bedingungen wären die, die beim nächsten Nachschärfen auseinander + laufen. */ +pruef("und zwar an derselben Bedingung wie die Nachtruhe", + /const dringend = art === "test" \|\| art === "anruf";/.test(versand)); + +/* ===================================================================== + 3. DER WEG DORTHIN — wird beim Anruf überhaupt geweckt? + ===================================================================== */ +const anruf = lies("server/workspace-anruf.js"); +const chat = lies("server/workspace-chat.js"); + +pruef("ein Anruf weckt alle außer dem Anrufer", + /const wecken = leute\.map\(\(t\) => t\.id\)\.filter\(\(id\) => id !== req\.person\.id\)/.test(anruf)); + +pruef("und die Art „anruf\" geht bis zur Benachrichtigung durch", + /chatEreignis\(raumId,\s*\{[\s\S]*?art:\s*"klingelt"[\s\S]*?\},\s*wecken,\s*"anruf"\)/.test(anruf)); + +pruef("wer die Seite offen hat, bekommt KEINE zusätzliche Meldung", + /if \(\(zuschauer\.get\(personId\) \|\| \[\]\)\.length\) continue;/.test(chat), + "sonst meldet es sich doppelt — der schnellste Weg, dass jemand alles abschaltet"); + +/* ===================================================================== + DIE GEGENPROBEN + + Zu jeder Prüfung, die „in Ordnung" sagen kann, gehört der Beweis, + dass sie auch „nicht in Ordnung" sagen kann. + ===================================================================== */ +console.log("\n --- Gegenproben (jede MUSS anschlagen) ---"); + +pruef("Gegenprobe: das alte renotify:false würde auffallen", + !/renotify:\s*istAnruf/.test(' renotify: false,')); + +pruef("Gegenprobe: ein festes Urgency würde auffallen", + !/Urgency:\s*dringend\s*\?/.test('headers: { ...kopf, TTL: String(ttl), Urgency: "normal" }')); + +pruef("Gegenprobe: eine gemeinsame Kennung würde auffallen", + !/tag:\s*istAnruf\s*\?\s*\("anruf-"/.test(' tag: d.art || "workspace",')); + +console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`); +process.exit(fehl ? 1 : 0); diff --git a/server/workspace-push-krypto.js b/server/workspace-push-krypto.js index a2b69525..dc99b4fe 100644 --- a/server/workspace-push-krypto.js +++ b/server/workspace-push-krypto.js @@ -208,14 +208,33 @@ export function vapidKopf(endpunkt, paar, absender, stunden = 12) { * Berechtigung entzogen) und gehoert geloescht. Der Push-Dienst sagt * das mit 404 oder 410. */ -export async function schicken(anmeldung, text, paar, absender, { ttl = 3600 } = {}) { +/* DRINGLICHKEIT GEHT MIT (19.09.2026). + * + * Hier stand fest `Urgency: "normal"` -- fuer JEDE Benachrichtigung, + * auch fuer einen Anruf. + * + * Auf Android entscheidet dieser Kopf, ob der Push-Dienst sofort + * zustellt oder bis zum naechsten Aufwachen des Geraets wartet. Im + * Stromsparmodus koennen daraus Minuten werden. Ein Anruf klingelt + * 120 Sekunden -- eine Benachrichtigung, die danach ankommt, ist ein + * verpasster Anruf mit Zeitstempel. + * + * Und die Haltbarkeit gehoert dazu: Ein Anruf von vor einer Stunde + * soll NICHT nachtraeglich aufploppen. Deshalb bekommt er eine kurze + * TTL -- ist er vorbei, verfaellt auch die Meldung. */ +export async function schicken(anmeldung, text, paar, absender, + { ttl = 3600, dringend = false } = {}) { try { const koerper = verschluesseln(text, anmeldung.p256dh, anmeldung.auth); const kopf = vapidKopf(anmeldung.endpunkt, paar, absender); const a = await fetch(anmeldung.endpunkt, { method: "POST", - headers: { ...kopf, TTL: String(ttl), Urgency: "normal" }, + headers: { + ...kopf, + TTL: String(dringend ? Math.min(ttl, 150) : ttl), + Urgency: dringend ? "high" : "normal", + }, body: koerper, /* Ein haengender Push-Dienst darf den Lauf nicht aufhalten. */ signal: AbortSignal.timeout(15_000), diff --git a/server/workspace-push.js b/server/workspace-push.js index f93a46fd..fc36cd79 100644 --- a/server/workspace-push.js +++ b/server/workspace-push.js @@ -380,7 +380,14 @@ export async function benachrichtige(personId, art, { titel, text, ziel, merkmal let raus = 0; for (const a of anmeldungen) { - const e = await schicken(a, inhalt, paar, ABSENDER); + /* EIN ANRUF IST DRINGEND, ALLES ANDERE NICHT (19.09.2026). + + `dringend` ist hier dasselbe `art === "anruf"`, das zwanzig + Zeilen weiter oben schon ueber die Ruhezeit entscheidet -- die + Frage „darf das nachts stoeren?" und „darf das warten?" haben + dieselbe Antwort. Eine zweite Bedingung waere die, die beim + naechsten Nachschaerfen auseinanderlaeuft. */ + const e = await schicken(a, inhalt, paar, ABSENDER, { dringend }); if (e.ok) { raus++; db().prepare("UPDATE push_anmeldungen SET zuletzt_ok = ?, fehler = 0 WHERE endpunkt = ?") diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index e27aa28d..062f023d 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +