diff --git a/server/pruef-abzeichen.mjs b/server/pruef-abzeichen.mjs index cdb939da..214112f9 100644 --- a/server/pruef-abzeichen.mjs +++ b/server/pruef-abzeichen.mjs @@ -532,6 +532,132 @@ console.log("=== 7. Jede Adresse zaehlt nur ihr eigenes Haus ==="); } } +/* ======================================================================= + 8. DIENES MELDUNG: DIE APP IM HINTERGRUND (30.09.2026) + + „Die Benachrichtigungen werden nicht angezeigt, wenn neue + Nachrichten reinkommen. Erst, wenn man die App öffnet." + + Am echten Bestand nachgesehen: Diene HAT ein angemeldetes Geraet + (Android, Chrome, seit dem 29.09.), und alle zehn Geraete im Haus + melden `fehler = 0`. An der Zustellung liegt es nicht. + + DER VERDACHT steht in workspace-chat.js: + + if ((zuschauer.get(personId) || new Set()).size) continue; + + Wer eine offene Verbindung hat, bekommt KEINE Benachrichtigung. + Der Gedanke daneben ist richtig -- „wer die Seite offen hat, sieht + die Nachricht ohnehin, und ihm auch noch eine Meldung aufs Handy zu + schicken ist der schnellste Weg, dass er Benachrichtigungen + abschaltet." + + NUR HEISST EINE OFFENE VERBINDUNG NICHT, DASS JEMAND HINSIEHT. + Ein Handy mit der App im Hintergrund haelt sie weiter; der Server + haelt ihn dann fuer anwesend und schweigt. + + HERGESTELLT UND NICHT ANGENOMMEN: Der Strom wird geoeffnet und + offen gelassen -- genau das, was ein Telefon im Hintergrund tut. + ======================================================================= */ +console.log(""); +console.log("=== 8. Wenn die App im Hintergrund liegt ==="); +{ + const { request: httpRoh } = await import("node:http"); + /* EINE ROHE VERBINDUNG, DIE OFFEN BLEIBT. `fetch` wartet auf das + Ende; ein Ereignisstrom endet nie. Genau daran ist im September + schon einmal eine Pruefung haengengeblieben. */ + const stromAuf = (keks) => new Promise((fertig) => { + const a = httpRoh({ + host: "127.0.0.1", port: PORT, path: "/workspace/api/chat/strom", + method: "GET", + headers: { Cookie: keks, Host: "crew.dogfather-universe.com", Accept: "text/event-stream" }, + }, (antwort) => { + antwort.on("data", () => { }); /* mitlesen, nichts tun */ + fertig({ anfrage: a, code: antwort.statusCode }); + }); + a.on("error", () => fertig({ anfrage: a, code: 0 })); + a.end(); + }); + + const strom = await stromAuf(ayla.keks); + ok(strom.code === 200, `Aylas Handy haelt die Verbindung (HTTP ${strom.code})`); + /* Kurz warten, damit der Server sie wirklich eingetragen hat. */ + await new Promise((r) => setTimeout(r, 400)); + + const vorher = pakete.length; + const g = await rufe(dogi, `/workspace/api/chat/raeume/${raumId}/nachrichten`, + { text: "Kommt das an, wenn die App hinten liegt?" }, "POST"); + ok(g.ok, `DogFather schreibt ihr (HTTP ${g.status})`); + /* Auf das Ergebnis warten, nicht auf die Uhr -- aber hier ist das + AUSBLEIBEN das Ergebnis, also braucht es eine Frist. Drei + Sekunden sind grosszuegig: Der Weg zum Push-Dienst dauert + oertlich Millisekunden. */ + const bis = Date.now() + 3000; + while (pakete.length === vorher && Date.now() < bis) { + await new Promise((r) => setTimeout(r, 60)); + } + const kam = pakete.length > vorher; + console.log(` Benachrichtigung verschickt: ${kam ? "ja" : "NEIN"}`); + ok(kam, + kam + ? "sie bekommt die Meldung, obwohl eine Verbindung offen ist " + + "(die App liegt hinten)" + /* BACKTICKS, WEIL EINE DEUTSCHE ANFUEHRUNG DARIN STEHT. In + einem "..."-String beendet das ASCII-Zeichen nach dem Wort + den String -- mir heute zum fuenften Mal passiert. Wo eine + deutsche Anfuehrung hinein soll, gehoeren Backticks darum. */ + : `KEINE MELDUNG — der Server haelt eine offene Verbindung fuer ` + + `„sie sieht ja hin". Genau Dienes Befund.`); + + /* ==== DIE GEGENPROBE, OHNE DIE DIE ZEILE OBEN NICHTS BEWEIST ==== + + Wer WIRKLICH hinsieht, soll KEINE Meldung bekommen -- das war + der richtige Gedanke hinter der alten Zeile, und er bleibt. + Ohne diesen Teil hiesse die Reparatur nur „jetzt kommt immer + eine", und das waere der schnellste Weg, dass jemand + Benachrichtigungen abschaltet. + + GEMELDET WIRD, WAS DIE SEITE SAGEN WUERDE: `sichtbar: true` -- + genau das schickt die Kopfleiste, wenn die App vorn liegt. */ + const sicht = await rufe(ayla, "/workspace/api/chat/sicht", + { sichtbar: true }, "POST"); + ok(sicht.ok, `Ayla meldet: ich sehe hin (HTTP ${sicht.status})`); + const vorherS = pakete.length; + await rufe(dogi, `/workspace/api/chat/raeume/${raumId}/nachrichten`, + { text: "Und wenn sie wirklich hinsieht?" }, "POST"); + await new Promise((r) => setTimeout(r, 2500)); + ok(pakete.length === vorherS, + `wer hinsieht, bekommt KEINE Meldung aufs Handy ` + + `(${pakete.length - vorherS} verschickt)`); + + /* Und wenn sie die App weglegt, kommt sie wieder. */ + await rufe(ayla, "/workspace/api/chat/sicht", { sichtbar: false }, "POST"); + const vorherW = pakete.length; + await rufe(dogi, `/workspace/api/chat/raeume/${raumId}/nachrichten`, + { text: "App weggelegt." }, "POST"); + const bisW = Date.now() + 5000; + while (pakete.length === vorherW && Date.now() < bisW) { + await new Promise((r) => setTimeout(r, 60)); + } + ok(pakete.length > vorherW, + `legt sie die App weg, kommt die Meldung wieder ` + + `(${pakete.length - vorherW})`); + + /* GEGENPROBE ZUM SCHLUSS: Ohne offene Verbindung erst recht. */ + strom.anfrage.destroy(); + await new Promise((r) => setTimeout(r, 500)); + const vorher2 = pakete.length; + await rufe(dogi, `/workspace/api/chat/raeume/${raumId}/nachrichten`, + { text: "Und jetzt, mit geschlossener App?" }, "POST"); + const bis2 = Date.now() + 5000; + while (pakete.length === vorher2 && Date.now() < bis2) { + await new Promise((r) => setTimeout(r, 60)); + } + ok(pakete.length > vorher2, + `Gegenprobe: ohne offene Verbindung kommt sie sehr wohl ` + + `(${pakete.length - vorher2})`); +} + /* ------------------------------------------------------------------- */ await schliessen(); d.close(); diff --git a/server/pruef-anruf-klingelt.mjs b/server/pruef-anruf-klingelt.mjs index a6d5c0d9..8e129e6e 100644 --- a/server/pruef-anruf-klingelt.mjs +++ b/server/pruef-anruf-klingelt.mjs @@ -152,10 +152,34 @@ pruef("und die Art „anruf\" geht bis zur Benachrichtigung durch", 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. */ -pruef("wer die Seite offen hat, bekommt KEINE zusätzliche Meldung", - /\(zuschauer\.get\(personId\) \|\| new Set\(\)\)\.size\) continue;/.test(chat), +/* ==== 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 diff --git a/server/workspace-chat.js b/server/workspace-chat.js index 1e40ec30..67383958 100644 --- a/server/workspace-chat.js +++ b/server/workspace-chat.js @@ -3435,6 +3435,32 @@ chatRouter.delete("/workspace/api/chat/nachrichten/:id", gleicheHerkunft, (req, * Eine Person kann an zwei Geräten sitzen. */ const zuschauer = new Map(); +/* ==== SIEHT JEMAND HIN? (30.09.2026) ================================ + + Diene im Support: „Die Benachrichtigungen werden nicht angezeigt, + wenn neue Nachrichten reinkommen. Erst, wenn man die App öffnet." + + Nachgestellt (pruef-abzeichen, Abschnitt 8): Mit offener Verbindung + kam KEINE Meldung, ohne sie sehr wohl. + + Hier stand `zuschauer.size > 0`. Der Gedanke dahinter ist richtig + -- wer die Seite offen hat, sieht die Nachricht ohnehin, und eine + Meldung obendrauf ist der schnellste Weg, dass er sie abschaltet. + Nur beantwortet `zuschauer` eine ANDERE Frage: ob eine VERBINDUNG + offen ist. Ein Handy mit der App im Hintergrund haelt sie weiter. + + Die Seite weiss es (`document.visibilityState`), der Server nicht. + Also sagt sie es ihm -- ueber `/workspace/api/chat/sicht`. + + MIT VERFALL, und das ist der wichtige Teil: Ein Geraet, das + abstuerzt, im Funkloch steht oder eingefroren wird, sagt nichts + mehr. Ohne Verfall bliebe es fuer immer „sichtbar" und fuer immer + still. Wer nicht widerspricht, gilt nach zweieinhalb Minuten als + weg -- eine Meldung zu viel ist laestig, eine zu wenig ist genau + der Fehler, den Diene gemeldet hat. */ +const sichtbarSeit = new Map(); +const SICHT_FRIST = 150_000; + /** * @param erwaehnte Wer in DIESER Nachricht mit "@" angesprochen wurde. * Diese Menge bekommt eine ANDERE Meldung, nicht eine zweite @@ -3472,7 +3498,18 @@ function chatEreignis(raumId, nachricht, benachrichtigen, art = "neu", Absicht stand da, die Zeile tat das Gegenteil -- und `siehtZu()` weiter unten macht es mit `.size` schon richtig. Gefunden beim Nachverfolgen eines ganz anderen Fehlers. */ - if ((zuschauer.get(personId) || new Set()).size) continue; + /* ==== GEFRAGT WIRD, OB JEMAND HINSIEHT (berichtigt 30.09.2026) + + Hier stand `zuschauer.get(personId).size` -- also „ist eine + Verbindung offen". Ein Handy mit der App im Hintergrund haelt + sie weiter, und damit blieb Diene ohne Meldung, solange die App + nur im Hintergrund lag. Gemessen in pruef-abzeichen, + Abschnitt 8. + + Jetzt entscheidet `siehtZu()` -- dieselbe Funktion, die auch + andere Module fragen. Eine Abschrift daneben war genau der + Fehler. */ + if (siehtZu(personId)) continue; /* DIE ART GEHT MIT (18.09.2026). Sie stand als Parameter oben und endete im Ereignisstrom -- die Benachrichtigung bekam sie nie zu sehen und behandelte deshalb JEDES Ereignis als Chatnachricht. @@ -3660,6 +3697,29 @@ async function meldeNeueNachricht(personId, raumId, nachricht, art = "neu") { } } +/* ==== DIE SEITE SAGT, OB SIE VORN LIEGT (30.09.2026) =============== + + Ein winziger Weg, und er traegt die ganze Reparatur: Ohne ihn + koennte der Server „Verbindung offen" nicht von „sieht hin" + unterscheiden. + + OHNE RUMPF IST ES EIN „NEIN". Wer beim Weggehen noch schnell etwas + schickt (`keepalive`), soll nicht darauf angewiesen sein, dass ein + JSON-Koerper heil ankommt -- und „weg" ist die sichere Vorgabe: Sie + fuehrt zu einer Meldung zu viel, nicht zu einer zu wenig. */ +chatRouter.post("/workspace/api/chat/sicht", gleicheHerkunft, + express.json({ limit: "1kb" }), (req, res) => { + try { + const id = req.person.id; + if (req.body?.sichtbar === true) sichtbarSeit.set(id, Date.now()); + else sichtbarSeit.delete(id); + res.json({ ok: true }); + } catch (fehler) { + console.error("[chat] Sicht:", fehler?.message); + res.status(503).json({ fehler: "nicht_verfuegbar" }); + } + }); + chatRouter.get("/workspace/api/chat/strom", (req, res) => { res.set({ "Content-Type": "text/event-stream", @@ -3691,7 +3751,14 @@ chatRouter.get("/workspace/api/chat/strom", (req, res) => { const menge = zuschauer.get(personId); if (menge) { menge.delete(res); - if (!menge.size) zuschauer.delete(personId); + if (!menge.size) { + zuschauer.delete(personId); + /* UND DIE SICHTMELDUNG GEHT MIT. Sonst bliebe sie stehen, + bis sie verfaellt -- und beim naechsten Verbindungsaufbau + gaelte die Person sofort als „sieht hin", ohne dass sie es + gesagt haette. */ + sichtbarSeit.delete(personId); + } } }; req.on("close", aufraeumen); @@ -3847,8 +3914,22 @@ export function teilnehmerFuerAnruf(raumId) { /** Der Ereigniskanal -- der Hinweg fuer Klingeln und Verbindungsdaten. */ export { chatEreignis }; +/** Sieht diese Person gerade hin? + * + * ZWEI BEDINGUNGEN, ZWEI GRUENDE: + * eine offene Verbindung -- die Seite ist ueberhaupt geladen + * eine frische Sichtmeldung -- und sie liegt vorn, nicht hinten + * + * DIE EINE STELLE FUER DIESE FRAGE (30.09.2026). Sie wurde an zwei + * Orten gestellt: hier und als eigene Zeile in `chatEreignis`. Die + * dortige Abschrift war es, die Dienes Meldung verursacht hat -- + * sie fragte nur nach der Verbindung. Zwei Rechnungen fuer dieselbe + * Frage laufen auseinander; jetzt gibt es nur noch eine. */ export function siehtZu(personId) { - return (zuschauer.get(Number(personId)) || []).size > 0; + const id = Number(personId); + if (!(zuschauer.get(id) || []).size) return false; + const wann = sichtbarSeit.get(id); + return !!wann && (Date.now() - wann) < SICHT_FRIST; } /* ===================================================================== diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 39c44b6b..0685f0ee 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +