Diene hat gemeldet: "Die Benachrichtigungen werden nicht angezeigt,
wenn neue Nachrichten reinkommen. Erst, wenn man die App oeffnet."
Alles Messbare war gruen: Der Service Worker zeigt die Meldung bei
geschlossener App (pruef-push-zu, 13 Pruefungen), jede Anmeldung im
Haus stand auf fehler = 0, der Push-Dienst quittierte mit 201. Nur
den Kopf auf der Leitung hat nie jemand gemessen:
Urgency: normal
Beide Dienste behandeln das ausdruecklich als "darf warten".
Android/FCM haelt solche Meldungen zurueck, solange das Telefon
doest, und stellt sie zu, wenn es aufwacht -- typischerweise beim
Entsperren oder Oeffnen der App. Apple/APNs nennt es "verzoegert,
gebuendelt oder gedrosselt". Das ist Dienes Satz, Wort fuer Wort,
und es stand als Vorgabe im eigenen Quelltext.
zuletzt_ok konnte das nie zeigen: Es beweist, dass der DIENST
angenommen hat, nicht dass das GERAET etwas angezeigt hat.
Bis hierher hing die Dringlichkeit an der Ruheregel (dringend =
regel === "nie"), mit der Begruendung, "darf das nachts stoeren?"
und "darf das warten?" haetten dieselbe Antwort. Haben sie nicht:
Ein Chat um 3 Uhr soll schweigen, um 14 Uhr aber nicht vierzig
Minuten liegen bleiben. Jetzt zwei Felder -- in DERSELBEN Zeile
derselben Artenliste, damit sie nicht auseinanderlaufen koennen.
10 von 19 Arten sind eilig (Anruf, Chat, Erwaehnung, Support, Hilfe,
Termin, Wecker, zweimal Live, Probe). Die anderen neun duerfen
warten -- waere alles eilig, waere nichts mehr eilig.
DIE FALLE BEIM BEHEBEN: dringend steuerte auch die Haltbarkeit
(TTL 150). Ein einfach auf "dringend" gestellter Chat waere nach
150 s verfallen -- wer sein Telefon drei Minuten aus hat, haette die
Nachricht GAR nicht mehr bekommen. Aus "zu spaet" waere "nie"
geworden. Deshalb sind eilig und kurzlebig getrennt; kurzlebig
bleibt genau beim Anruf und der Probe.
Der alte Name kracht jetzt, statt still "normal" zu liefern.
pruef-push-eilig.mjs (neu, 13 Pruefungen): die Entscheidung je Art
unabhaengig notiert statt aus der Artenliste abgelesen, jede Art
muss eine Entscheidung haben, und der Kopf wird durch
benachrichtige() hindurch an einem nachgebauten Push-Dienst
gemessen. Gegenprobe gelaufen: ohne das Feld meldet sie
"chat_nachricht geht mit Urgency: normal hinaus", 4 Fehler.
Die Uhr ist dort eine Eingabe, keine Annahme -- die Zeitzone wird so
gewaehlt, dass der Lauf immer auf 12 Uhr faellt, sonst waere die
Pruefung nachts rot ohne Befund.
pruef-anruf-klingelt 25 -> 26. Dabei aufgefallen: Eine ihrer
Pruefungen war gruen, obwohl die Zeile aus dem Code verschwunden war
-- sie stand nur noch in einem Kommentar, der die alte Fassung
zitiert. Quelltextpruefungen lesen dort jetzt ohne Kommentare.
Co-Authored-By: Claude Opus 5 <[email protected]>
295 lines
13 KiB
JavaScript
295 lines
13 KiB
JavaScript
/* =====================================================================
|
||
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");
|
||
|
||
/* ---- NUR DER CODE, OHNE DIE ERZAEHLUNG DRUMHERUM (03.10.2026) -------
|
||
|
||
Diese Datei prueft Quelltext mit Mustern. Das ist schnell und
|
||
braucht keinen Server -- hat aber eine Luecke, die heute
|
||
aufgefallen ist: Die Pruefung "und zwar an derselben Bedingung wie
|
||
die Nachtruhe" war gruen, OBWOHL die Zeile aus dem Code
|
||
verschwunden war. Sie stand nur noch in einem Kommentar, der die
|
||
alte Fassung zitiert -- und ein Muster sieht den Unterschied
|
||
nicht.
|
||
|
||
Das ist die dritte Sorte gruener Haken: nicht uebersprungen, nicht
|
||
falsch geprueft, sondern am Beleg vorbei. Deshalb werden
|
||
Kommentare vorher entfernt. Was hier geprueft wird, muss laufender
|
||
Code sein. */
|
||
const nurCode = (t) => t
|
||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||
.replace(/(^|[^:])\/\/.*/g, "$1");
|
||
|
||
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 = nurCode(lies("server/workspace-push-krypto.js"));
|
||
const versand = nurCode(lies("server/workspace-push.js"));
|
||
|
||
pruef("der Transport kennt überhaupt eine Dringlichkeit",
|
||
/eilig\s*=\s*false/.test(krypto),
|
||
"vorher stand Urgency fest auf \"normal\"");
|
||
|
||
pruef("ein eiliger Push geht mit Urgency: high hinaus",
|
||
/Urgency:\s*eilig\s*\?\s*"high"\s*:\s*"normal"/.test(krypto));
|
||
|
||
pruef("und verfällt, wenn der Anruf vorbei ist",
|
||
/TTL:\s*String\(kurzlebig\s*\?\s*Math\.min\(ttl,\s*\d+\)/.test(krypto),
|
||
"sonst ploppt ein Anruf von vor einer Stunde nachträglich auf");
|
||
|
||
pruef("der Anruf wird eilig UND kurzlebig verschickt",
|
||
/schicken\(a,\s*inhalt,\s*paar,\s*ABSENDER,\s*\{\s*eilig,\s*kurzlebig\s*\}\)/.test(versand));
|
||
|
||
/* ---- ZWEI FRAGEN, ZWEI FELDER (geändert 03.10.2026) -----------------
|
||
|
||
Hier stand bis heute das Gegenteil: „Darf das nachts stören?" und
|
||
„darf das warten?" hätten dieselbe Antwort, zwei getrennte
|
||
Bedingungen wären die, die auseinanderlaufen. Die Vorhersage war
|
||
richtig, nur in die andere Richtung — sie laufen auseinander, WEIL
|
||
es zwei Fragen sind:
|
||
|
||
Chat um 3 Uhr nachts -> nicht stören (Ruhezeit greift)
|
||
Chat um 14 Uhr -> nicht warten (eilig)
|
||
|
||
Genau diese Gleichsetzung hat Dienes Meldung verursacht: Weil der
|
||
Chat nicht nachts stören darf, bekam er auch tagsüber
|
||
`Urgency: normal` — und Android legt das beiseite, bis jemand die
|
||
App öffnet. Das Zusammenlegen war die Ursache, nicht der Schutz.
|
||
|
||
Auseinanderlaufen können die beiden trotzdem nicht: Sie stehen als
|
||
`eilig` und `ruhe` in DERSELBEN Zeile derselben Artenliste. */
|
||
pruef("der Anruf ist eilig und kurzlebig, beides in einer Zeile",
|
||
/\{\s*schluessel:\s*"anruf",\s*eilig:\s*true,[^}]*ruhe:\s*"nie"/.test(versand));
|
||
|
||
pruef("und die Dringlichkeit wird aus der Artenliste abgeleitet",
|
||
/const eilig = eiligRegel\(art\);/.test(versand),
|
||
"nicht aus einer zweiten Aufzählung daneben");
|
||
|
||
/* =====================================================================
|
||
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));
|
||
|
||
/* `.size` UND NICHT `.length` (berichtigt 19.09.2026).
|
||
|
||
Hier stand die alte Form -- und sie war grün, obwohl die Zeile
|
||
nichts tat: `zuschauer` hält Mengen, und eine Menge hat kein
|
||
`length`. Der Ausdruck war immer `undefined`, also immer falsch,
|
||
also wurde nie übersprungen: Wer die Seite offen hatte, bekam
|
||
zusätzlich eine Meldung aufs Handy.
|
||
|
||
Die Prüfung hat den alten Wortlaut bestätigt statt sein Verhalten.
|
||
Genau davor warnt die Hausregel vom 01.09.: Ein grüner Haken sagt
|
||
nur, dass die Bedingung erfüllt war -- nicht, dass sie das Richtige
|
||
geprüft hat. */
|
||
/* ==== UND SEIT DEM 30.09.2026 IST ES EINE ANDERE FRAGE ============
|
||
|
||
Diene im Support: „Die Benachrichtigungen werden nicht angezeigt,
|
||
wenn neue Nachrichten reinkommen. Erst, wenn man die App öffnet."
|
||
|
||
Nachgestellt (pruef-abzeichen, Abschnitt 8): Der Server hat die
|
||
Meldung unterdrückt, solange eine VERBINDUNG offen war. Ein Handy
|
||
mit der App im Hintergrund hält sie weiter — also galt Diene als
|
||
anwesend, während sein Bildschirm schwarz war.
|
||
|
||
Die Absicht der Zeile darüber bleibt richtig und wird weiter
|
||
geprüft: Wer WIRKLICH hinsieht, bekommt keine zweite Meldung.
|
||
Gefragt wird nur nicht mehr nach der Verbindung, sondern nach
|
||
`siehtZu()` — und das verlangt zusätzlich eine frische Sichtmeldung
|
||
der Seite.
|
||
|
||
UND DIESE PRÜFUNG HAT DEN FEHLER MITGETRAGEN. Sie hielt den
|
||
WORTLAUT fest, nicht das Verhalten — genau das, wovor ihr eigener
|
||
Kommentar drei Zeilen weiter oben warnt. Deshalb steht hier jetzt
|
||
BEIDES: dass gefragt wird, und dass die Frage die richtige ist. */
|
||
pruef("wer wirklich hinsieht, bekommt KEINE zusätzliche Meldung",
|
||
/if \(siehtZu\(personId\)\) continue;/.test(chat),
|
||
"sonst meldet es sich doppelt — der schnellste Weg, dass jemand alles abschaltet");
|
||
|
||
pruef("und „hinsehen\" heißt mehr als „eine Verbindung offen haben\"",
|
||
/function siehtZu[\s\S]{0,400}?sichtbarSeit\.get\(id\)[\s\S]{0,200}?SICHT_FRIST/.test(chat),
|
||
"sonst wäre es wieder die alte Frage, nur mit neuem Namen");
|
||
|
||
/* =====================================================================
|
||
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",'));
|
||
|
||
/* =====================================================================
|
||
4. DER ZWEITE WEG — selbst nachfragen (19.09.2026)
|
||
|
||
Filipe: „wenn vanvan rangeht und redet klingelt es immer noch bei
|
||
mir weiter."
|
||
|
||
Im Protokoll nachgemessen: Sie WAR im Gespräch (24 Sekunden, mit
|
||
eigenem `anruf_ende`). Das Ereignis „dabei" wurde also verschickt
|
||
und kam beim Anrufer nicht an.
|
||
|
||
Geprüft und ausgeschlossen: Raumzugehörigkeit, Ereignis-Verkabelung,
|
||
Tonsteuerung, Service Worker. Es geht auf einem Weg verloren, der
|
||
von hier aus nicht messbar ist.
|
||
|
||
Deshalb wird die Ursache nicht weiter gesucht, sondern die
|
||
ABHÄNGIGKEIT beseitigt: Solange es klingelt, fragt der Anrufer
|
||
selbst nach. Der Ereignisstrom bleibt der schnellere erste Weg --
|
||
dies ist das Netz darunter.
|
||
===================================================================== */
|
||
console.log("\n --- Der zweite Weg ---");
|
||
|
||
const anrufJs = lies("workspace/assets/js/anruf.js");
|
||
|
||
pruef("der Anrufer fragt selbst nach, ob schon jemand dran ist",
|
||
/function nachfrageStarten\(raumId\)/.test(anrufJs),
|
||
"ohne das hängt ein klingelndes Telefon an einem einzigen Kanal");
|
||
|
||
pruef("und zwar ab dem Moment des Wählens",
|
||
/klingelWache\(KLINGELT_MS\);[\s\S]{0,160}nachfrageStarten\(raumId\)/.test(anrufJs));
|
||
|
||
pruef("sie hält an, sobald die Verbindung steht",
|
||
/if \(anruf\.beginn\) return nachfrageStoppen\(\)/.test(anrufJs));
|
||
|
||
/* SIE MUSS AN JEDEM ENDE AUFHOEREN. Eine Schleife, die weiterläuft,
|
||
nachdem das Gespräch vorbei ist, fragt den Server alle zwei Sekunden
|
||
nach einem Anruf, den es nicht mehr gibt -- und das auf jedem Gerät,
|
||
das je telefoniert hat. */
|
||
for (const [wo, muster] of [
|
||
["beim Auflegen", /async function auflegen\(melden\) \{[\s\S]{0,160}nachfrageStoppen\(\)/],
|
||
["wenn die Verbindung steht", /nachfrageStoppen\(\);\s*\/\/ und die Nachfrage/],
|
||
["wenn das Ereignis doch ankommt", /d\.art === 'dabei'[\s\S]{0,260}nachfrageStoppen\(\)/],
|
||
]) {
|
||
pruef(`die Nachfrage hört auf: ${wo}`, muster.test(anrufJs));
|
||
}
|
||
|
||
/* =====================================================================
|
||
5. DAS PROTOKOLL MUSS DIE FRAGE BEANTWORTEN KOENNEN
|
||
|
||
Bis heute standen dort nur Anfang und Ende. Die wichtigste Frage bei
|
||
der Suche -- „ist sie ueberhaupt rangegangen?" -- war nur ueber
|
||
einen Umweg zu beantworten. Das ist eine Schlussfolgerung, keine
|
||
Auskunft.
|
||
===================================================================== */
|
||
pruef("das Rangehen steht im Protokoll",
|
||
/protokolliere\("anruf_dabei"/.test(lies("server/workspace-anruf.js")),
|
||
"sonst lässt sich später nicht sagen, OB jemand rangegangen ist");
|
||
|
||
console.log(`\n ${ok} bestanden, ${fehl} fehlgeschlagen\n`);
|
||
process.exit(fehl ? 1 : 0);
|