diff --git a/server/pruef-anruf.mjs b/server/pruef-anruf.mjs index cf802d49..027a2300 100644 --- a/server/pruef-anruf.mjs +++ b/server/pruef-anruf.mjs @@ -229,6 +229,119 @@ melde("=== 5. Auflegen ==="); Sie enthalten Zugangsdaten (TURN braucht Benutzer und Passwort). Deshalb darf nur DogFather sie setzen. ======================================================================= */ +/* ======================================================================= + 5c. WER ERST DURCH DIE BENACHRICHTIGUNG KOMMT + + Filipe, 18.09.2026: „wenn ich vanvan anrufe kriegt sie nur eine + benarichtigung aber keinen anruf was soll das." + + Das war kein Missverstaendnis, sondern ein Loch. Das Klingeln lief + ausschliesslich ueber den offenen Ereignisstrom: Wer zusieht, hoert + es. Wer die Seite NICHT offen hat, bekommt die Push-Nachricht, + tippt darauf, die Seite laedt -- und bleibt still. Das Ereignis war + vorbei, bevor sie da war. + + Der Anruf funktionierte damit ausgerechnet fuer die nicht, fuer die + die Benachrichtigung gebaut wurde. + + DIESE PRUEFUNG BILDET GENAU DAS AB: Erst anrufen, DANN die Seite + oeffnen. Ohne sie beweist der Dreier-Test unten nur, dass es + funktioniert, wenn alle schon zusehen. + ======================================================================= */ +/* ======================================================================= + 5d. WAS IN DER BENACHRICHTIGUNG STEHT + + Filipe: „wenn ich vanvan anrufe kriegt sie nur eine benarichtigung + aber keinen anruf was soll das." + + Gemessen kam bei ihr an: „Nachricht von [object Object]", ohne Text. + + Zwei Fehler in einer Zeile. `von` ist beim Klingeln ein OBJEKT und + keine Zeichenkette; und einen `text` gibt es bei einem Anruf gar + nicht. Moeglich wurde beides, weil die ART des Ereignisses die + Benachrichtigung nie erreichte -- jedes Ereignis galt dort als + Chatnachricht. + + DIESE PRUEFUNG WAR VORHER NICHT MOEGLICH. Der Text entstand tief in + einer Funktion, die nur der Push-Weg aufruft. Er steht deshalb + jetzt in `pushTextFuer()` -- eine Zeile weniger Verschachtelung und + eine Frage, die sich beantworten laesst. + ======================================================================= */ +melde(""); +melde("=== 5d. Was in der Benachrichtigung steht ==="); +{ + const { pushTextFuer } = await import("./workspace-chat.js"); + + const a = pushTextFuer("anruf", { von: { id: 7, name: "Filipe" }, video: false }, 3); + ok(a.titel === "Filipe ruft an", `beim Anruf steht der Name und „ruft an" (${a.titel})`); + ok(!/\[object/i.test(a.titel + a.text), + "und nirgends [object Object] -- genau das kam vorher an"); + ok(a.text.includes("Rangehen"), `der Text sagt, was zu tun ist (${a.text})`); + ok(a.ziel === "/workspace/chat.html?raum=3", + `und es fuehrt in das richtige Gespraech (${a.ziel})`); + + const v = pushTextFuer("anruf", { von: { id: 7, name: "Filipe" }, video: true }, 3); + ok(v.text.toLowerCase().includes("video"), + `ein Videoanruf sagt das auch (${v.text})`); + + /* UND EINE NORMALE NACHRICHT BLEIBT EINE NORMALE NACHRICHT. */ + const n = pushTextFuer("neu", { von: "Rieke", text: "Hallo!" }, 3); + ok(n.titel === "Nachricht von Rieke" && n.text === "Hallo!", + `eine Nachricht meldet sich weiter als Nachricht (${n.titel})`); + + /* Und wenn dort doch einmal ein Objekt steht -- dieselbe Falle, + nur an der anderen Stelle -- kommt trotzdem ein Name heraus. */ + const o = pushTextFuer("neu", { von: { name: "Rieke" }, text: "Hallo!" }, 3); + ok(!/\[object/i.test(o.titel), `auch mit einem Objekt als Absender (${o.titel})`); +} + +melde(""); +melde("=== 5c. Wer erst durch die Benachrichtigung kommt ==="); +{ + const raum = json(await roh("/workspace/api/chat/raeume", HAUS, + { cookie: kDogi })).raeume?.[0]; + if (!raum) { + unklar("kein Gespraech vorhanden -- nicht nachgesehen"); + } else { + /* Erst pruefen, dass nichts offen ist -- sonst misst der Rest + einen Rest von vorher. */ + const vorher = json(await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand })); + ok(Array.isArray(vorher.anrufe) && vorher.anrufe.length === 0, + `vorher klingelt nichts (${vorher.anrufe?.length})`); + + const start = await roh(`/workspace/api/anruf/${raum.id}/start`, HAUS, + { cookie: kDogi }, { video: false }); + ok(start.code === 200 || start.code === 201, `DogFather ruft an (${start.code})`); + + /* JETZT fragt die rechte Hand -- so, wie ihre Seite es beim Laden + tut, nachdem sie auf die Benachrichtigung getippt hat. */ + const offen = json(await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand })); + ok(offen.anrufe?.length === 1, + `sie erfaehrt beim Laden, dass es klingelt (${offen.anrufe?.length})`); + ok(offen.anrufe?.[0]?.von?.name && offen.anrufe[0].von.id !== undefined, + `und von wem (${offen.anrufe?.[0]?.von?.name})`); + ok(offen.anrufe?.[0]?.raum_id === raum.id, + "und in welchem Gespraech"); + + /* DER ANRUFER SELBST DARF NICHT KLINGELN. Er hat den Anruf + gestartet; ein zweites Fenster von ihm wuerde sonst mitlaeuten. */ + const beimAnrufer = json(await roh("/workspace/api/anruf/offen", HAUS, { cookie: kDogi })); + ok(beimAnrufer.anrufe?.length === 0, + `beim Anrufer selbst klingelt nichts (${beimAnrufer.anrufe?.length})`); + + /* UND WER NICHT IN DEN RAUM GEHOERT, ERFAEHRT NICHTS. Dieselbe + Frage wie ueberall sonst -- eine neue Route ist eine neue Tuer. */ + const fremd = json(await roh("/workspace/api/anruf/offen", HAUS, { cookie: kFremd })); + ok(fremd.anrufe?.length === 0, + `ein Fremder erfaehrt nichts davon (${fremd.anrufe?.length})`); + + await roh(`/workspace/api/anruf/${raum.id}/ende`, HAUS, { cookie: kDogi }, {}); + const danach = json(await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand })); + ok(danach.anrufe?.length === 0, + `nach dem Auflegen klingelt es nicht mehr (${danach.anrufe?.length})`); + } +} + melde(""); melde("=== 6. Die Verbindungsadressen ==="); { diff --git a/server/workspace-anruf.js b/server/workspace-anruf.js index 92362392..b48d5e9a 100644 --- a/server/workspace-anruf.js +++ b/server/workspace-anruf.js @@ -398,6 +398,65 @@ anrufRouter.post("/workspace/api/anruf/:raum/signal", gleicheHerkunft, }); /** Läuft hier gerade ein Anruf? Für den, der die Seite frisch öffnet. */ +/* ===================================================================== + KLINGELT GERADE ETWAS FUER MICH? (18.09.2026) + + Filipe: „wenn ich vanvan anrufe kriegt sie nur eine benarichtigung + aber keinen anruf was soll das." + + DAS WAR DER FEHLER, UND ER WAR GRUNDSAETZLICH. Das Klingeln lief + ausschliesslich ueber `chatEreignis` -- also ueber den offenen + Ereignisstrom. Wer die Seite gerade ansieht, hoert es. Wer sie + NICHT offen hat, bekommt die Push-Benachrichtigung, tippt darauf, + die Seite laedt -- und dann passiert nichts mehr. Das Ereignis war + in dem Moment vorbei, in dem es geschickt wurde. + + Damit funktionierte der Anruf ausgerechnet fuer die nicht, fuer die + die Benachrichtigung gebaut wurde. Und es sah aus wie ein Fehler + beim Angerufenen („sie kriegt nur eine Benachrichtigung"), obwohl + nichts kaputt war -- es fehlte schlicht der Weg zurueck. + + Diese Route ist dieser Weg: Beim Laden fragt die Seite einmal nach, + ob in einem ihrer Raeume gerade jemand wartet. Ein laufender Anruf + steht im Speicher (`laufend`), die Frage kostet also nichts. + + NUR WAS NOCH KLINGELT: Anrufe, die laenger als KLINGELT_SEKUNDEN + her sind, gelten als verpasst -- sonst klingelte es beim Oeffnen + noch, obwohl der andere laengst aufgelegt hat. Und nur Raeume, in + denen die Person auch wirklich drin ist; `istDrinFuerAnruf` ist + dieselbe Pruefung wie ueberall sonst. + ===================================================================== */ +anrufRouter.get("/workspace/api/anruf/offen", (req, res) => { + try { + aufraeumen(); + const jetzt = Date.now(); + const offen = []; + for (const [raumId, a] of laufend) { + /* Schon dabei? Dann klingelt es nicht mehr, dann telefoniert + man. */ + if (a.wer.has(req.person.id)) continue; + if (jetzt - a.seit > KLINGELT_SEKUNDEN * 1000) continue; + /* DIE PERSON, NICHT IHRE NUMMER -- istDrin() wirft sonst einen + TypeError, und zwar ausdruecklich: Die Sicherung steht dort + genau fuer diesen Fall. Hier stand zuerst `.id`. */ + if (!istDrinFuerAnruf(raumId, req.person)) continue; + const ersterAndere = [...a.wer.entries()][0]; + offen.push({ + raum_id: raumId, + video: !!a.video, + von: ersterAndere + ? { id: ersterAndere[0], name: ersterAndere[1].name } + : null, + laeuft_bis: a.seit + KLINGELT_SEKUNDEN * 1000, + }); + } + res.json({ anrufe: offen }); + } catch (f) { + console.error("[anruf] Offene:", f?.message); + res.status(503).json({ fehler: "nicht_verfuegbar" }); + } +}); + anrufRouter.get("/workspace/api/anruf/:raum", (req, res) => { try { aufraeumen(); diff --git a/server/workspace-chat.js b/server/workspace-chat.js index c428c0a6..3a6bda50 100644 --- a/server/workspace-chat.js +++ b/server/workspace-chat.js @@ -1791,25 +1791,100 @@ function chatEreignis(raumId, nachricht, benachrichtigen, art = "neu") { Benachrichtigungen abschaltet. */ for (const personId of benachrichtigen) { if ((zuschauer.get(personId) || []).length) continue; - void meldeNeueNachricht(personId, raumId, nachricht); + /* 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. + Bei einem Anruf kam dabei woertlich „Nachricht von [object + Object]" heraus. */ + void meldeNeueNachricht(personId, raumId, nachricht, art); } } /** Die Push-Benachrichtigung. Absichtlich träge geladen: Der Chat soll * auch dann laufen, wenn am Push-Teil etwas klemmt. */ -async function meldeNeueNachricht(personId, raumId, nachricht) { +/** Was in der Benachrichtigung steht. + * + * ALS EIGENE FUNKTION UND EXPORTIERT, damit eine Pruefung sie lesen + * kann, ohne einen Push-Dienst nachzubauen. Genau hier stand der + * Fehler, den Filipe am 18.09.2026 gemeldet hat -- und er war von + * aussen nicht messbar, weil der Text tief in einer Funktion entstand, + * die nur der Push-Weg aufruft. + * + * @param art "anruf" oder alles andere + * @returns {{titel: string, text: string, ziel: string}} + */ +export function pushTextFuer(art, nachricht, raumId) { + const ziel = `/workspace/chat.html?raum=${raumId}`; + + /* ---- EIN ANRUF IST KEINE NACHRICHT ---------------------------- + + Filipe: „wenn ich vanvan anrufe kriegt sie nur eine benarichtigung + aber keinen anruf was soll das." + + Gemessen kam bei ihr an: + + Nachricht von [object Object] + (kein Text) + + Zwei Fehler auf einmal. `nachricht.von` ist beim Klingeln ein + OBJEKT (`{id, name}`) und keine Zeichenkette -- aus der Vorlage + wurde deshalb „[object Object]". Und einen `text` gibt es bei + einem Anruf gar nicht, also blieb die Zeile leer. + + Dass es so weit kam, lag daran, dass die ART des Ereignisses hier + nie ankam: Jedes Ereignis wurde als Chatnachricht behandelt, auch + das Klingeln. */ + if (art === "anruf") { + const wer = nachricht.von?.name || "Jemand"; + return { + titel: `${wer} ruft an`, + text: nachricht.video ? "Videoanruf – tippen zum Rangehen" + : "Tippen zum Rangehen", + ziel, + }; + } + + return { + titel: `Nachricht von ${typeof nachricht.von === "string" + ? nachricht.von : (nachricht.von?.name || "jemandem")}`, + /* Der Anfang der Nachricht steht in der Meldung -- eine + Benachrichtigung, die nur "Neue Nachricht" sagt, zwingt zum + Öffnen, um zu erfahren, ob es wichtig war. */ + text: String(nachricht.text || "").slice(0, 120) + || (nachricht.anhang?.art === "bild" ? "Foto" + : nachricht.anhang?.art === "pdf" ? "PDF" : ""), + ziel, + }; +} + +async function meldeNeueNachricht(personId, raumId, nachricht, art = "neu") { try { const { benachrichtige } = await import("./workspace-push.js"); - await benachrichtige(personId, "chat_nachricht", { - titel: `Nachricht von ${nachricht.von}`, - /* Der Anfang der Nachricht steht in der Meldung -- eine - Benachrichtigung, die nur "Neue Nachricht" sagt, zwingt zum - Öffnen, um zu erfahren, ob es wichtig war. */ - text: String(nachricht.text || "").slice(0, 120) - || (nachricht.anhang?.art === "bild" ? "Foto" - : nachricht.anhang?.art === "pdf" ? "PDF" : ""), - ziel: `/workspace/chat.html?raum=${raumId}`, - }); + + /* ---- EIN ANRUF IST KEINE NACHRICHT (18.09.2026) -------------- + + Filipe: „wenn ich vanvan anrufe kriegt sie nur eine + benarichtigung aber keinen anruf was soll das." + + Gemessen kam bei ihr an: + + Nachricht von [object Object] + (kein Text) + + Zwei Fehler auf einmal. `nachricht.von` ist beim Klingeln ein + OBJEKT (`{id, name}`) und keine Zeichenkette -- aus der Vorlage + wurde deshalb „[object Object]". Und einen `text` gibt es bei + einem Anruf gar nicht, also blieb die Zeile leer. + + Dass es ueberhaupt so weit kam, lag daran, dass die ART des + Ereignisses hier nie ankam: Jedes Ereignis wurde als + Chatnachricht behandelt, auch das Klingeln. + + 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", + pushTextFuer(art, nachricht, raumId)); } catch (fehler) { console.error("[chat] Benachrichtigung:", fehler?.message); } diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 5fb5eef7..51957fe9 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -22,25 +22,25 @@ }, "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609182214", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609182254", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609182214", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609182254", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609182214", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609182254", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609182214", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609182254", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/js/anruf.js b/workspace/assets/js/anruf.js index ed5a6e77..5230f1ed 100644 --- a/workspace/assets/js/anruf.js +++ b/workspace/assets/js/anruf.js @@ -678,6 +678,34 @@ const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' }); if (i.ok) ichId = (await i.json())?.id ?? null; } catch { /* der Chat holt es ohnehin auch */ } + + /* ---- KLINGELT GERADE ETWAS? (18.09.2026) ---------------------- + + Filipe: „wenn ich vanvan anrufe kriegt sie nur eine + benarichtigung aber keinen anruf." + + Genau so war es: Das Klingeln kam ausschliesslich ueber den + offenen Ereignisstrom. Wer die Seite nicht offen hatte, bekam + die Benachrichtigung, tippte darauf -- und landete auf einer + stillen Seite. Das Ereignis war vorbei, bevor sie geladen war. + + Der Anruf funktionierte damit fuer alle ausser denen, fuer die + die Benachrichtigung ueberhaupt gedacht ist. + + ERST NACH `ichId`: Die Klingel prueft, ob der Anruf vom eigenen + Konto kommt. Ohne die Nummer wuerde sie beim eigenen Anruf in + einem zweiten Fenster mitlaeuten. */ + try { + const a = await fetch('/workspace/api/anruf/offen', { credentials: 'same-origin' }); + if (a.ok) { + for (const r of ((await a.json())?.anrufe || [])) { + if (!r.von || r.von.id === ichId) continue; + if (anruf) break; /* telefoniert schon */ + klingelnZeigen(r.raum_id, r.von, r.video); + break; /* einer reicht */ + } + } + } catch { /* dann eben nur ueber den Ereignisstrom */ } void gruppeMax; })(); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index e86c2bda..a51e7dae 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - +
@@ -291,10 +291,10 @@ - - - - - + + + + +