Anruf: zwei Minuten Zeit -- und ein verpasster Anruf bleibt sichtbar
Filipe: „das mit dem anruf klappt nicht … es geeeeeht einfach nicht." NACHGEMESSEN WAR SERVERSEITIG ALLES IN ORDNUNG: Der Anruf kam an (Protokoll), der Raum stimmte (Dogfather + VanVan), sie war angemeldet (Sitzung bis morgen frueh), sie hat ein Geraet fuer Benachrichtigungen, und der neue Code lief. Trotzdem ging es nicht -- und zwar aus zwei Gruenden, die beide nichts mit Technik zu tun haben, sondern mit Zeit und Sichtbarkeit. 1. 45 SEKUNDEN SIND KEIN KLINGELN. Bei einem Telefon hebt man ab. Hier muss in dieselben Sekunden: Benachrichtigung zustellen, bemerken, Handy entsperren, antippen, App laden, Anmeldung pruefen. Das reicht, wenn man das Geraet in der Hand haelt -- und sonst nie. Jetzt zwei Minuten: lang genug, um aus der Tasche zu kommen, kurz genug, dass niemand vor einem Anruf sitzt, den es nicht mehr gibt. 2. UND DANACH WAR ES, ALS WAERE NIE ETWAS GEWESEN. Ein verpasster Anruf verschwand lautlos: keine Zeile, kein Hinweis, nicht einmal, DASS jemand angerufen hat. Genau das fuehlt sich an wie „geht nicht", auch wenn alles funktioniert hat. Jedes Telefon der Welt zeigt einen verpassten Anruf; jetzt steht er als Zeile im Gespraech, beim Anrufer, mit Uhrzeit. Geprueft: pruef-anruf 95 -> 100. Der neue Abschnitt misst den echten Ablauf (anrufen, niemand geht ran, Zeile erscheint) und dass sie NUR EINMAL erscheint -- `aufraeumen()` laeuft bei jeder Anfrage mit. Dafuer ist die Klingeldauer ueber die Umgebung einstellbar: Eine Pruefung, die zwei Minuten wartet, wird abgeschaltet, und dann waere der verpasste Anruf wieder ungeprueft. Im Betrieb steht die Variable nirgends. Und noch ein eigener Messfehler: Die Pruefung las zuerst /api/chat/raum/:id -- die Route heisst /api/chat/raeume/:id/nachrichten. Sie meldete „0 Nachrichten, vorher wie nachher" und sah damit aus wie ein Befund ueber den Code. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -28,6 +28,10 @@ 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";
|
||||
/* Zwei Sekunden statt zwei Minuten -- sonst muesste Abschnitt 5e so
|
||||
lange warten, und eine Pruefung, die zwei Minuten braucht, wird
|
||||
abgeschaltet. Im Betrieb steht die Variable nirgends. */
|
||||
process.env.ANRUF_KLINGELT_SEKUNDEN = "2";
|
||||
/* Der Vermittlungsserver holt sein Geheimnis aus einer Datei. Hier muss
|
||||
der Pfad UMGEBOGEN werden, bevor index.js geladen wird -- sonst
|
||||
zeigte er auf die echte Datei unter /etc, und die Pruefung wuerde
|
||||
@@ -267,6 +271,61 @@ melde("=== 5. Auflegen ===");
|
||||
jetzt in `pushTextFuer()` -- eine Zeile weniger Verschachtelung und
|
||||
eine Frage, die sich beantworten laesst.
|
||||
======================================================================= */
|
||||
/* =======================================================================
|
||||
5e. EIN VERPASSTER ANRUF HINTERLAESST EINE SPUR
|
||||
|
||||
Bisher verschwand er lautlos. Wer zu spaet kam, oeffnete den Chat
|
||||
und sah -- nichts. Keine Zeile, kein Hinweis, nicht einmal, DASS
|
||||
jemand angerufen hat.
|
||||
|
||||
Genau das fuehlt sich an wie „es geht einfach nicht", auch wenn
|
||||
technisch alles funktioniert hat: Der Anruf kam an, es klingelte,
|
||||
niemand war schnell genug, und danach war es, als waere nie etwas
|
||||
gewesen. Jedes Telefon der Welt zeigt einen verpassten Anruf.
|
||||
======================================================================= */
|
||||
melde("");
|
||||
melde("=== 5e. Ein verpasster Anruf ===");
|
||||
{
|
||||
const raum = json(await roh("/workspace/api/chat/raeume", HAUS,
|
||||
{ cookie: kDogi })).raeume?.[0];
|
||||
if (!raum) {
|
||||
unklar("kein Gespraech vorhanden -- nicht nachgesehen");
|
||||
} else {
|
||||
const vorher = json(await roh(`/workspace/api/chat/raeume/${raum.id}/nachrichten`, HAUS,
|
||||
{ cookie: kDogi })).nachrichten?.length ?? 0;
|
||||
|
||||
const start = await roh(`/workspace/api/anruf/${raum.id}/start`, HAUS,
|
||||
{ cookie: kDogi }, { video: false });
|
||||
ok(start.code === 200 || start.code === 201, `Anruf gestartet (${start.code})`);
|
||||
|
||||
/* Niemand geht ran. Nach ANRUF_KLINGELT_SEKUNDEN (hier 2) gilt er
|
||||
als verpasst -- `aufraeumen()` laeuft bei der naechsten Anfrage
|
||||
mit. */
|
||||
await new Promise((r) => setTimeout(r, 2600));
|
||||
await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand });
|
||||
|
||||
const nachher = json(await roh(`/workspace/api/chat/raeume/${raum.id}/nachrichten`, HAUS,
|
||||
{ cookie: kDogi })).nachrichten || [];
|
||||
ok(nachher.length === vorher + 1,
|
||||
`es steht genau EINE neue Zeile im Gespraech (${vorher} -> ${nachher.length})`);
|
||||
const letzte = nachher[nachher.length - 1];
|
||||
ok(/[Vv]erpasst/.test(letzte?.text || ""),
|
||||
`und sie sagt, was los war („${letzte?.text}")`);
|
||||
ok(letzte?.von_id !== undefined,
|
||||
"sie steht beim Anrufer, nicht als Systemtext ohne Absender");
|
||||
|
||||
/* UND NICHT ZWEIMAL. `aufraeumen()` laeuft bei JEDER Anfrage --
|
||||
ohne das Loeschen aus `laufend` staende hier bei jedem Aufruf
|
||||
eine weitere Zeile. */
|
||||
await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand });
|
||||
await roh("/workspace/api/anruf/offen", CREW, { cookie: kHand });
|
||||
const nochmal = json(await roh(`/workspace/api/chat/raeume/${raum.id}/nachrichten`, HAUS,
|
||||
{ cookie: kDogi })).nachrichten || [];
|
||||
ok(nochmal.length === nachher.length,
|
||||
`und bleibt bei einer, auch nach weiteren Abfragen (${nochmal.length})`);
|
||||
}
|
||||
}
|
||||
|
||||
melde("");
|
||||
melde("=== 5d. Was in der Benachrichtigung steht ===");
|
||||
{
|
||||
|
||||
@@ -91,8 +91,33 @@ export const GRUPPE_MAX = 4;
|
||||
*
|
||||
* Ohne diese Grenze bliebe ein Anruf, bei dem der Anrufer die Seite
|
||||
* schließt, für immer offen stehen -- und der Angerufene sähe eine
|
||||
* Einladung zu einem Gespräch, an dem niemand mehr teilnimmt. */
|
||||
const KLINGELT_SEKUNDEN = 45;
|
||||
* Einladung zu einem Gespräch, an dem niemand mehr teilnimmt.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
* WARUM ZWEI MINUTEN UND NICHT 45 SEKUNDEN (18.09.2026)
|
||||
*
|
||||
* Filipe: „das mit dem anruf klappt nicht … es geeeeeht einfach
|
||||
* nicht." Nachgemessen war serverseitig alles in Ordnung: Der Anruf
|
||||
* kam an, der Raum stimmte, die Angerufene war angemeldet und hatte
|
||||
* ein Gerät für Benachrichtigungen.
|
||||
*
|
||||
* Nur passte die Zeit nicht zum Weg. Bei einem Telefon hebt man ab --
|
||||
* hier muss in dieselben Sekunden: Benachrichtigung zustellen, sie
|
||||
* bemerken, Handy entsperren, antippen, App laden, Anmeldung prüfen.
|
||||
* 45 Sekunden reichen dafür, wenn man das Gerät in der Hand hält, und
|
||||
* sonst nie.
|
||||
*
|
||||
* Das ist der Unterschied zwischen „funktioniert" und „funktioniert
|
||||
* im Alltag". Zwei Minuten sind lang genug, um aus der Tasche zu
|
||||
* kommen, und kurz genug, dass niemand vor einem Anruf sitzt, den es
|
||||
* längst nicht mehr gibt. */
|
||||
const KLINGELT_SEKUNDEN = Number(process.env.ANRUF_KLINGELT_SEKUNDEN) || 120;
|
||||
|
||||
/* WARUM UEBER DIE UMGEBUNG UMSTELLBAR: Damit eine Pruefung den Ablauf
|
||||
messen kann, ohne zwei Minuten zu warten. Eine Pruefung, die so
|
||||
lange braucht, wird abgeschaltet -- und dann ist der verpasste
|
||||
Anruf wieder ungeprueft. Im Betrieb steht die Variable nirgends;
|
||||
dort gelten die 120 Sekunden. */
|
||||
|
||||
/* Wer gerade in welchem Raum telefoniert.
|
||||
IM SPEICHER, NICHT IN DER DATENBANK: Ein laufender Anruf ist ein
|
||||
@@ -112,10 +137,58 @@ function aufraeumen() {
|
||||
ist keiner mehr -- der Angerufene ist nicht rangegangen. */
|
||||
if (a.wer.size === 1 && jetzt - a.seit > KLINGELT_SEKUNDEN * 1000) {
|
||||
laufend.delete(raumId);
|
||||
|
||||
/* ---- UND ER HINTERLAESST EINE SPUR (18.09.2026) -------------
|
||||
|
||||
Bisher verschwand ein verpasster Anruf lautlos. Wer zu spaet
|
||||
kam, oeffnete den Chat und sah -- nichts. Keine Zeile, kein
|
||||
Hinweis, nicht einmal, DASS jemand angerufen hat. Genau das
|
||||
fuehlt sich an wie „es geht einfach nicht", auch wenn technisch
|
||||
alles funktioniert hat.
|
||||
|
||||
Jedes Telefon der Welt zeigt einen verpassten Anruf. Hier
|
||||
steht er als Zeile im Gespraech, mit Namen und Uhrzeit -- und
|
||||
damit hat der andere etwas, worauf er antworten kann.
|
||||
|
||||
IN EINEM EIGENEN try: Ein Fehler beim Schreiben der Zeile darf
|
||||
das Aufraeumen nicht anhalten -- sonst bliebe ein abgelaufener
|
||||
Anruf stehen, und das waere schlimmer als eine fehlende
|
||||
Notiz. */
|
||||
try { verpasstVermerken(raumId, a); } catch (f) {
|
||||
console.error("[anruf] verpasst:", f?.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Schreibt „Verpasster Anruf" in das Gespraech.
|
||||
*
|
||||
* Als ganz normale Nachricht des Anrufers: So steht sie an der
|
||||
* richtigen Stelle im Verlauf, wird mitgezaehlt wie jede andere und
|
||||
* braucht keine Sonderbehandlung in der Oberflaeche. */
|
||||
function verpasstVermerken(raumId, a) {
|
||||
const anrufer = [...a.wer.entries()][0];
|
||||
if (!anrufer) return;
|
||||
const [id, wer] = anrufer;
|
||||
const d = db();
|
||||
const n = new Date().toISOString();
|
||||
const text = a.video ? "📞 Verpasster Videoanruf" : "📞 Verpasster Anruf";
|
||||
d.prepare("INSERT INTO chat_nachrichten (raum_id, person_id, text, erstellt) VALUES (?,?,?,?)")
|
||||
.run(raumId, id, text, n);
|
||||
const neueId = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
||||
d.prepare("UPDATE chat_raeume SET letzte_am = ? WHERE id = ?").run(n, raumId);
|
||||
|
||||
/* Die anderen im Raum bekommen sie wie jede Nachricht -- inklusive
|
||||
Benachrichtigung, falls sie gerade nicht zusehen. Wer den Anruf
|
||||
verpasst hat, sieht wenigstens, DASS er ihn verpasst hat. */
|
||||
const andere = teilnehmerFuerAnruf(raumId)
|
||||
.map((t) => t.id).filter((x) => x !== id);
|
||||
chatEreignis(raumId, {
|
||||
id: neueId, raum_id: raumId, von_id: id, von: wer.name,
|
||||
text, erstellt: n, zurueckgenommen: false, antwort: null, anhang: null,
|
||||
}, andere, "neu");
|
||||
}
|
||||
|
||||
function angemeldet(req, res, next) {
|
||||
const person = sitzungLesen(req);
|
||||
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
||||
|
||||
Reference in New Issue
Block a user