/* ===================================================================== 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);