diff --git a/server/pruef-anruf.mjs b/server/pruef-anruf.mjs index 0fdb38b2..c5e9a35a 100644 --- a/server/pruef-anruf.mjs +++ b/server/pruef-anruf.mjs @@ -16,7 +16,7 @@ Aufrufen mit: node server/pruef-anruf.mjs ===================================================================== */ -import { mkdtempSync, rmSync, writeFileSync, unlinkSync } from "node:fs"; +import { mkdtempSync, rmSync, writeFileSync, unlinkSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { request as httpAnfrage } from "node:http"; @@ -1224,6 +1224,59 @@ melde("=== 8. Zu dritt ==="); } } +/* ===================================================================== + EIN turn: OHNE ZUGANGSDATEN DARF DEN BROWSER NICHT ERREICHEN + (25.09.2026) + + Am selben Tag beim Bau des Podcast-Studios im echten Browser + nachgemessen: + + Failed to construct 'RTCPeerConnection': Both username and + credential are required when the URL scheme is "turn" or "turns". + + `new RTCPeerConnection(...)` WIRFT. Die Folge ist nicht "kein + Vermittlungsserver", sondern KEIN ANRUF -- auch nicht der, der direkt + zustande gekommen waere. Aus einem Randproblem (etwa jeder Fuenfte + braucht Vermittlung) wuerde ein Totalausfall fuer alle, ausgeloest + davon, dass eine Datei auf dem Server unlesbar ist. + + Der Server laesst solche Eintraege bewusst stehen, damit der Grund + nicht verschwindet -- das ist dort richtig. Aussortiert wird im + Browser, in `workspace/assets/js/anruf.js`. + + DIESE PRUEFUNG LIEST KEINEN TEXT, SONDERN FUEHRT DEN ECHTEN AUSDRUCK + AUS. Eine Prüfung, die nur nachsieht, ob irgendwo das Wort "filter" + steht, waere auch dann gruen, wenn der Filter das Falsche tut. + ===================================================================== */ +{ + const quelle = readFileSync(new URL("../workspace/assets/js/anruf.js", import.meta.url), "utf8"); + const t = /adressen = \(d\.adressen \|\| \[\]\)(\.filter\([\s\S]*?\n {8}\}\);)/.exec(quelle); + ok(!!t, "in anruf.js wird die Adressliste ueberhaupt gefiltert"); + if (t) { + /* Genau der Ausdruck aus der ausgelieferten Datei, ausgefuehrt. */ + const filtern = new Function("d", "return (d.adressen || [])" + t[1] + ";"); + const rein = { + adressen: [ + { urls: "stun:1.2.3.4:3478" }, + { urls: "turn:1.2.3.4:3478?transport=udp", username: "1:a", credential: "x" }, + { urls: "turn:1.2.3.4:3478?transport=tcp" }, + { urls: "turns:1.2.3.4:5349", username: "1:a" }, + ], + }; + const raus = filtern(rein); + ok(raus.length === 2, `es bleiben genau die zwei brauchbaren uebrig (${raus.length})`); + ok(raus.some((a) => a.urls.startsWith("stun:")), + "STUN bleibt drin -- es kennt gar keine Anmeldung und reicht meistens"); + ok(raus.every((a) => !/^turns?:/.test(a.urls) || (a.username && a.credential)), + "kein turn: ohne Benutzer UND Passwort geht hinaus"); + /* GEGENPROBE: Wuerde der Filter alles durchlassen, waere die Zeile + oben auch gruen. Hier wird bewiesen, dass er wirklich etwas + wegnimmt. */ + ok(raus.length < rein.adressen.length, + "Gegenprobe: der Filter nimmt tatsaechlich etwas weg"); + } +} + /* ------------------------------------------------------------------- */ melde(""); melde(`${geprueft} Pruefungen, ${fehler} Fehler` diff --git a/workspace/assets/js/anruf.js b/workspace/assets/js/anruf.js index 55311c7b..70950855 100644 --- a/workspace/assets/js/anruf.js +++ b/workspace/assets/js/anruf.js @@ -1259,8 +1259,25 @@ immer kommt. */ let telefonGehtHier = true; + /* Der Grund, den der Server zum Vermittlungsserver mitschickt -- + leer heisst "alles in Ordnung". Er steht hier oben, weil ihn + `adressenFrisch()` braucht. */ + let turnGrund = ''; + function adressenFrisch() { if (!adressenGeholt) return false; + /* FEHLT DAS GEHEIMNIS, GILT DIE LISTE NIE ALS FRISCH. + + Sonst waere sie es fuer immer: Ohne Zugangsdaten bleibt nach dem + Aussortieren nur STUN uebrig, und ein STUN-Eintrag hat keinen + Ablaufzeitpunkt -- `every()` sagt dann "alles noch gueltig" und + es wird nie wieder gefragt. Waere die Datei auf dem Server + zwischenzeitlich repariert, telefonierte trotzdem niemand mehr + mit Vermittlung, bis er die Seite neu laedt. + + (Nebenbei die zweite Falle derselben Zeile: `every()` auf einer + LEEREN Liste ist `true`. Auch dagegen hilft diese Abfrage.) */ + if (turnGrund) return false; const jetzt = Math.floor(Date.now() / 1000); return adressen.every((a) => { const m = /^(\d{10,}):/.exec(a && a.username || ''); @@ -1277,15 +1294,47 @@ if (a.status === 404) { telefonGehtHier = false; return; } if (a.ok) { const d = await a.json(); - adressen = d.adressen || []; + /* ═══════════════════════════════════════════════════════════ + EIN turn: OHNE ZUGANGSDATEN DARF NICHT IN DIE LISTE. + + Am 25.09.2026 im echten Browser nachgemessen (beim Bau des + Podcast-Studios, das dieselbe Rechnung benutzt): + + Failed to construct 'RTCPeerConnection': Both username and + credential are required when the URL scheme is "turn" or + "turns". + + `new RTCPeerConnection(...)` WIRFT. Die Folge ist nicht etwa + "kein Vermittlungsserver", sondern KEIN ANRUF -- auch nicht + der, der direkt zustande gekommen waere. Aus einem + Randproblem (etwa jeder fuenfte braucht Vermittlung) wuerde + ein Totalausfall fuer alle. + + Der Server laesst solche Eintraege bewusst stehen, damit der + Grund nicht verschwindet (server/workspace-turn.js, Absatz + "KEIN STILLES WEGLASSEN"). Das ist dort richtig -- aber es + heisst, dass HIER aussortiert werden muss, bevor gebaut wird. + Der Grund geht nicht verloren: Er steht weiter unten in der + Konsole. + + STUN bleibt selbstverstaendlich drin -- STUN kennt gar keine + Anmeldung, und es ist der Weg, der in den meisten Faellen + reicht. + ═══════════════════════════════════════════════════════════ */ + adressen = (d.adressen || []).filter((e) => { + if (!/^turns?:/.test(String(e && e.urls))) return true; + return !!(e.username && e.credential); + }); nurGleichesNetz = !!d.nur_gleiches_netz; gruppeMax = d.gruppe_max || 4; adressenGeholt = true; /* Ein eingetragener Vermittlungsserver, der niemanden durchlässt, ist schlimmer als keiner -- er sieht aus, als wäre alles eingerichtet. Deshalb steht er in der Konsole. */ - if (d.turn_grund) { - console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', d.turn_grund); + turnGrund = d.turn_grund || ''; + if (turnGrund) { + console.warn('[anruf] Vermittlungsserver ohne Zugangsdaten:', turnGrund, + '- Anrufe gehen nur noch direkt. Auf dem Server nachsehen: /etc/coturn-workspace.geheimnis'); } } } catch { /* dann eben ohne -- im selben Netz geht es trotzdem */ }