diff --git a/server/pruef-anruf-ruhezeit.mjs b/server/pruef-anruf-ruhezeit.mjs new file mode 100644 index 00000000..4af869d1 --- /dev/null +++ b/server/pruef-anruf-ruhezeit.mjs @@ -0,0 +1,135 @@ +/* ===================================================================== + EIN ANRUF KLINGELT AUCH NACHTS -- 18.09.2026 + + Filipe, 23:44 Uhr: „sie bekommt nur eine benarichtigung das eine + neue nachricht ist aber sonst nichts irgendwas laeuft da gewaltig + schief." + + Serverseitig war alles in Ordnung: Der Anruf kam an, der Raum + stimmte, sie war angemeldet, sie hatte ein Geraet. Die + Benachrichtigung wurde trotzdem nie verschickt -- eine einzige + Zeile, die ich beim Bauen des Anrufs nie gesehen hatte: + + if (art !== "test" && istRuhezeit()) return { grund: "ruhezeit" }; + + Ab 22 Uhr geht nichts mehr hinaus. Fuer eine Aufgabenerinnerung ist + das genau richtig -- die schickt der Server von sich aus, weil eine + Frist naeher rueckt. Ein Anruf ist das Gegenteil: Ein Mensch + drueckt gerade auf den Hoerer und wartet auf eine Antwort. + + DIESE PRUEFUNG HAELT DIE UNTERSCHEIDUNG FEST. Ohne sie waere die + Ausnahme eine Zeile, die beim naechsten Umbau jemand „aufraeumt". + + Aufrufen mit: node server/pruef-anruf-ruhezeit.mjs + ===================================================================== */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { portMussFreiSein } from "./helfer-port.mjs"; + +const PORT = await portMussFreiSein(4199, "pruef-anruf-ruhezeit"); +const ordner = mkdtempSync(join(tmpdir(), "ws-ruhe-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +/* IMMER RUHEZEIT. Die Pruefung soll nicht davon abhaengen, wann sie + laeuft -- eine Pruefung, die tagsueber gruen und nachts rot ist, + waere schlimmer als keine. */ +process.env.PUSH_RUHE_AB = "0"; +process.env.PUSH_RUHE_BIS = "24"; + +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(90_000, "pruef-anruf-ruhezeit"); +await new Promise((r) => setTimeout(r, 600)); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const { istRuhezeit, benachrichtige } = await import("./workspace-push.js"); +const { DatabaseSync } = await import("node:sqlite"); +const { randomBytes, scryptSync } = await import("node:crypto"); + +/* Eine Person, damit `willHaben` etwas zu fragen hat. */ +let personId; +{ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const salz = randomBytes(16).toString("hex"); + const hash = scryptSync("CODE-X", salz, 64, + { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,1,?)") + .run("Mara", "hand", hash, salz, 32768, new Date().toISOString()); + personId = d.prepare("SELECT last_insert_rowid() AS id").get().id; + d.close(); +} + +melde(""); +melde("=== 1. Es ist Ruhezeit ==="); +ok(istRuhezeit() === true, "die Ruhezeit gilt (fuer diese Pruefung rund um die Uhr)"); + +melde(""); +melde("=== 2. Was durchkommt und was nicht ==="); +{ + /* KEIN ECHTER PUSH NOETIG. `benachrichtige` nennt den Grund, warum + es nicht hinausging -- und genau der ist die Frage: + + "ruhezeit" -> an der Uhrzeit gescheitert + "keine_geraete" -> durchgekommen, es fehlt nur ein Empfaenger + + Ein Anruf, der bis "keine_geraete" kommt, hat die Ruhezeit hinter + sich. Das ist der ganze Unterschied. */ + const erinnerung = await benachrichtige(personId, "chat_nachricht", { + titel: "Neue Nachricht", text: "Hallo", ziel: "/workspace/chat.html", + }); + ok(erinnerung.grund === "ruhezeit", + `eine Chatnachricht bleibt nachts liegen (${erinnerung.grund})`); + + const anruf = await benachrichtige(personId, "anruf", { + titel: "Filipe ruft an", text: "Tippen zum Rangehen", + ziel: "/workspace/chat.html?raum=1", + }); + ok(anruf.grund !== "ruhezeit", + `ein Anruf kommt durch (${anruf.grund})`); + ok(anruf.grund === "keine_geraete", + "und scheitert erst daran, dass kein Geraet angemeldet ist" + + ` (${anruf.grund}) -- also wirklich an der Ruhezeit vorbei`); + + /* UND DIE ART MUSS ES GEBEN. Eine unbekannte Art wird stillschweigend + verworfen -- dann waere oben „nicht ruhezeit" wahr, ohne dass + jemals etwas verschickt wuerde. */ + const erfunden = await benachrichtige(personId, "gibtsnicht", { + titel: "x", text: "y", ziel: "/workspace/start.html", + }); + ok(erfunden.grund === "unbekannte_art", + `eine erfundene Art faellt durch (${erfunden.grund}) -- „anruf" also nicht zufaellig gruen`); +} + +melde(""); +melde("=== 3. Und sie laesst sich abschalten ==="); +{ + /* Wer nachts seine Ruhe will, soll sie bekommen -- aber als eigene + Entscheidung, nicht als Regel, die man nicht kennt. */ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + /* Die Spalte heisst "art", nicht "schluessel" -- abgeschrieben aus + workspace-push.js statt geraten. */ + d.prepare("INSERT INTO push_einstellungen (person_id, art, an)" + + " VALUES (?,?,0) ON CONFLICT(person_id, art) DO UPDATE SET an = 0") + .run(personId, "anruf"); + d.close(); + + const aus = await benachrichtige(personId, "anruf", { + titel: "Filipe ruft an", text: "…", ziel: "/workspace/chat.html?raum=1", + }); + ok(aus.grund === "abgeschaltet", + `wer sie abschaltet, hat Ruhe (${aus.grund})`); +} + +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest */ } +process.exit(fehler ? 1 : 0); diff --git a/server/workspace-chat.js b/server/workspace-chat.js index 3a6bda50..828f618b 100644 --- a/server/workspace-chat.js +++ b/server/workspace-chat.js @@ -1883,7 +1883,9 @@ async function meldeNeueNachricht(personId, raumId, nachricht, art = "neu") { Jetzt steht dort, was los ist -- und beim Tippen landet man im richtigen Gespraech, wo es dank der Route `/anruf/offen` auch wirklich klingelt. */ - await benachrichtige(personId, "chat_nachricht", + /* ALS EIGENE ART, wenn es ein Anruf ist. Sie umgeht die Ruhezeit + und laesst sich getrennt abschalten -- siehe workspace-push.js. */ + await benachrichtige(personId, art === "anruf" ? "anruf" : "chat_nachricht", pushTextFuer(art, nachricht, raumId)); } catch (fehler) { console.error("[chat] Benachrichtigung:", fehler?.message); diff --git a/server/workspace-push.js b/server/workspace-push.js index d33eee35..f5746609 100644 --- a/server/workspace-push.js +++ b/server/workspace-push.js @@ -87,6 +87,31 @@ export const ARTEN = [ die am haeufigsten kommt, und wer die Liste ueberfliegt, sucht zuerst sie. */ { schluessel: "chat_nachricht", name: "Neue Nachricht im Chat", vorgabe: true }, + /* ---- ANRUFE SIND EINE EIGENE ART (18.09.2026) ------------------ + + Filipe: „sie bekommt nur eine benarichtigung das eine neue + nachricht ist aber sonst nichts irgendwas laeuft da gewaltig + schief." + + Es lief zweierlei schief, und beides haengt an dieser Zeile. + + ERSTENS ging das Klingeln als „chat_nachricht" hinaus. Wer im + Chat viel schreibt und die Benachrichtigungen dafuer abstellt, + stellt damit auch Anrufe ab -- ohne es zu wissen. Das sind zwei + verschiedene Entscheidungen. + + ZWEITENS -- und das war der eigentliche Grund -- galt fuer sie die + RUHEZEIT. Ab 22 Uhr wird nichts mehr verschickt. Um 23:44 kam + deshalb gar keine Benachrichtigung an; ihr Geraet konnte nicht + klingeln, weil es nichts zu klingeln gab. + + Fuer eine Aufgabenerinnerung ist das genau richtig. Ein Anruf ist + etwas anderes: Ihn loest ein Mensch in diesem Moment aus, und er + wartet auf eine Antwort. Ein Telefon, das nachts stumm bleibt, + ist kein Telefon. Wer nachts nicht gestoert werden will, schaltet + jetzt diese Art ab -- eine Entscheidung, die man selbst trifft, + statt einer Regel, die man nicht kennt. */ + { schluessel: "anruf", name: "Jemand ruft an", vorgabe: true }, /* DER TAGESRUF (08.09.2026, screen10). Filipe: "eine taetige meldung einmal am tag zu aktivieren wenn noch aufgaben auf sind." @@ -309,8 +334,20 @@ export async function benachrichtige(personId, art, { titel, text, ziel, merkmal if (!willHaben(personId, art)) return { verschickt: 0, grund: "abgeschaltet" }; /* Die Probe darf immer durch -- sie beantwortet die Frage "kommt - ueberhaupt etwas an?", und die stellt man sich auch um 23 Uhr. */ - if (art !== "test" && istRuhezeit()) return { verschickt: 0, grund: "ruhezeit" }; + ueberhaupt etwas an?", und die stellt man sich auch um 23 Uhr. + + UND EIN ANRUF AUCH (18.09.2026). Die Ruhezeit ist gegen + Erinnerungen gebaut: Dinge, die der Server von sich aus schickt, + weil eine Frist naeher rueckt. Ein Anruf ist das Gegenteil -- ein + Mensch drueckt gerade auf den Hoerer und wartet. + + Gefunden hat das Filipes Satz „sie bekommt nur eine + benarichtigung ... irgendwas laeuft da gewaltig schief", um 23:44 + Uhr. Serverseitig war alles in Ordnung; der Anruf wurde nur nie + ausgeliefert, weil es nach 22 Uhr war. Wer nachts seine Ruhe will, + schaltet die Art „Jemand ruft an" ab. */ + const dringend = art === "test" || art === "anruf"; + if (!dringend && istRuhezeit()) return { verschickt: 0, grund: "ruhezeit" }; /* Schon einmal geschickt? */ if (merkmal) {