diff --git a/packages/tesm-core/pyproject.toml b/packages/tesm-core/pyproject.toml index cb0018a..f4c5c4e 100644 --- a/packages/tesm-core/pyproject.toml +++ b/packages/tesm-core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm-core" -version = "2.8.6" +version = "2.8.7" description = "Gemeinsames Anwendungsgeruest (Auth, RBAC, Navigation, Design-System, Migrationen) fuer TESM und TESM-Lizenzserver" requires-python = ">=3.11" dependencies = [ diff --git a/packages/tesm-core/src/tesm_core/static/tesm_core/js/screen.js b/packages/tesm-core/src/tesm_core/static/tesm_core/js/screen.js index de12c51..99f85f8 100644 --- a/packages/tesm-core/src/tesm_core/static/tesm_core/js/screen.js +++ b/packages/tesm-core/src/tesm_core/static/tesm_core/js/screen.js @@ -23,13 +23,38 @@ import RFB from "../vendor/novnc/core/rfb.js"; // der angemeldete Weg der bessere. const OHNE_ZUGANGSDATEN = new Set([1]); -// Der Eingriff sitzt in einer privaten Methode der mitgelieferten Bibliothek. -// Das ist bewusst hier und nicht in der Bibliothek selbst: eine neue Fassung -// von noVNC laesst sich dann austauschen, ohne eine Aenderung nachzupflegen -- -// faellt die Methode weg, greift die Vorauswahl nicht mehr, und die Sitzung -// laeuft wieder wie ohne sie. Kein stiller Bruch. +// Drei Eingriffe in private Methoden der mitgelieferten Bibliothek. Bewusst +// hier und nicht in der Bibliothek selbst: eine neue Fassung von noVNC laesst +// sich dann austauschen, ohne eine Aenderung nachzupflegen -- faellt eine +// Methode weg, greift der jeweilige Eingriff nicht mehr, und die Sitzung +// laeuft wieder wie ohne ihn. Kein stiller Bruch. const istUnterstuetzt = RFB.prototype._isSupportedSecurityType; +// Den *Grund* eines Fehlschlags kennt nur ``_fail`` -- das Ereignis +// "disconnect" traegt ihn nicht, und der Weg ueber die Protokollausgabe +// funktioniert nicht: die Bibliothek bindet ``console.error`` beim Laden, also +// bevor eine Seite etwas daran aendern koennte. (Genau daran ist der erste +// Versuch gescheitert -- die Meldung blieb leer.) +let letzterGrund = ""; +const originalFail = RFB.prototype._fail; +RFB.prototype._fail = function (details) { + letzterGrund = String(details); + return originalFail.call(this, details); +}; + +// Und der Abschluss der Leitung selbst: der Schliesscode sagt, ob TESM +// abgewiesen hat (1008), ob es ein Serverfehler war (1011) -- oder ob die +// Verbindung nie zustande kam (1006). +let letzterCode = 0; +const originalSocketClose = RFB.prototype._socketClose; +RFB.prototype._socketClose = function (ereignis) { + letzterCode = (ereignis && ereignis.code) || 0; + if (ereignis && ereignis.reason) { + letzterGrund = letzterGrund || String(ereignis.reason); + } + return originalSocketClose.call(this, ereignis); +}; + const wurzel = document.getElementById("screen"); if (wurzel) { starten(wurzel); @@ -54,32 +79,16 @@ function starten(wurzel) { ); let rfb = null; - let letzterFehler = ""; function melden(text, art = "") { zeile.textContent = text; zeile.className = `screen__status${art ? " screen__status--" + art : ""}`; } - // noVNC meldet den *Grund* eines Fehlschlags ausschliesslich ins Protokoll - // der Entwicklerkonsole -- das Ereignis "disconnect" trägt ihn nicht. Ohne - // diesen Griff bliebe der Seite nur "Verbindung abgebrochen", und damit - // steht der Anwender genau dort, wo er ohne die ganze Anzeige auch stuende. - // Darum wird die Fehlerausgabe waehrend der Sitzung mitgelesen -- und - // hinterher wieder losgelassen. - const echteAusgabe = console.error.bind(console); - console.error = (...teile) => { - letzterFehler = teile.map((teil) => String(teil)).join(" "); - echteAusgabe(...teile); - }; - window.addEventListener("beforeunload", () => { - console.error = echteAusgabe; - }); - function grund() { const teile = []; - if (letzterFehler) { - teile.push(letzterFehler.replace(/^Failed (when|while) \w+: /, "")); + if (letzterGrund) { + teile.push(letzterGrund.replace(/^Failed (when|while) \w+: /, "")); } if (rfb && rfb._rfbAuthScheme > 0) { teile.push(`gewähltes Verfahren: ${rfb._rfbAuthScheme}`); @@ -87,9 +96,35 @@ function starten(wurzel) { return teile.join(" · "); } + // Steht die WebSocket-Strecke zu TESM überhaupt? Zwei sehr verschiedene + // Ursachen sehen von aussen gleich aus: der VNC-Server mag nicht, oder die + // Verbindung kommt nie zustande, weil ein Proxy zwischen Browser und TESM + // "Upgrade" nicht durchreicht. Diese Probe trennt sie. + function streckeTesten() { + return new Promise((fertig) => { + let beantwortet = false; + const pruefling = new WebSocket(`${schema}//${window.location.host}/ws/probe`); + const schliessen = (ergebnis) => { + if (beantwortet) return; + beantwortet = true; + try { + pruefling.close(); + } catch (fehler) { + /* schon zu */ + } + fertig(ergebnis); + }; + pruefling.onmessage = () => schliessen(true); + pruefling.onerror = () => schliessen(false); + pruefling.onclose = () => schliessen(false); + window.setTimeout(() => schliessen(false), 4000); + }); + } + function verbinden(zugang) { melden("Verbindung wird aufgebaut …"); - letzterFehler = ""; + letzterGrund = ""; + letzterCode = 0; // Vorauswahl nur, wenn wir nichts anzubieten haben: dann zaehlt der Weg, // der ohne Zugangsdaten auskommt -- sofern der Server ihn anbietet. @@ -136,10 +171,28 @@ function starten(wurzel) { melden( erklaerung ? `Verbindung abgebrochen – ${erklaerung}` - : "Verbindung abgebrochen, ohne dass der Client einen Grund nennt. " + - "Antwortet der VNC-Server noch?", + : "Verbindung abgebrochen. Wird geprüft, woran es liegt …", "fehler", ); + // 1006 heisst: die Leitung ging zu, ohne dass die Gegenseite einen + // Grund geschickt hat -- typisch, wenn die WebSocket-Verbindung gar + // nicht erst zustande kam. Dann sagt die Probe, wer schweigt. + if (letzterCode === 1006 || !erklaerung) { + streckeTesten().then((steht) => { + melden( + steht + ? `${erklaerung || "Verbindung abgebrochen"} – die WebSocket-Strecke zu ` + + "TESM selbst steht (Probe erfolgreich), der Abbruch kommt also von " + + "der VNC-Seite." + : "Die WebSocket-Verbindung zu TESM kommt nicht zustande – auch eine " + + "einfache Probe scheitert. Ein Proxy zwischen Browser und TESM muss " + + "die Kopfzeilen Upgrade und Connection durchreichen (nginx: " + + "location /ws/ mit proxy_set_header Upgrade $http_upgrade). Dasselbe " + + "gilt für die Browser-Konsole.", + "fehler", + ); + }); + } } rfb = null; });