DER BEFUND
Im naechtlichen Lauf standen dreizehn Pruefungen mit derselben Zeile
rot:
[uncaughtException] Error: spawnSync openssl ENOENT
Kein Programmfehler. Die Pruefungen brauchen einen https-Vorbau (der
Sitzungskeks des Hauses ist `secure` und kommt ueber http nicht an),
und dafuer erzeugen sie ein Wegwerf-Zertifikat mit openssl.
GEMESSEN, NICHT VERMUTET
openssl liegt hier: C:\Program Files\Git\mingw64\bin\openssl.exe
C:\Program Files\Git\usr\bin\openssl.exe
im System-PATH steht: C:\Program Files\Git\cmd (nur git.exe)
im Benutzer-PATH: nichts mit Git
Wer aus Git Bash startet, erbt die mingw-Pfade und merkt nie etwas.
Die Aufgabenplanung startet `node.exe` DIREKT, ohne Shell -- und
bekommt sie nicht. Bei mir gruen, nachts rot, und der Grund steht
nicht im Code.
Das Schlimmste daran ist nicht der Ausfall: Dreizehn dauerhaft rote
Zeilen in einer Notiz, die Filipe morgens liest, gewoehnen einem das
Hinsehen ab -- und decken dabei die echten Befunde zu.
DIE REPARATUR
`server/helfer-openssl.mjs` sucht openssl erst im PATH (ohne `where`
oder `which` -- beides sind selbst Programme und koennen genauso
fehlen), danach an den Orten, an denen es auf einem Windows-Rechner
mit Git wirklich liegt.
Dazu `zertifikatBauen(schluessel, zertifikat, host)`. Die acht
Aufrufformen im Haus waren identisch bis auf die Variablennamen
(schl/zert, schluesselDatei/zertDatei, zKey/zCrt, sk/zt, key/crt) --
eine Stelle traegt alle neunundzwanzig. Zwei Tage zuvor war eine
davon um ein `-addext` aermer als die anderen; gemerkt hat es
niemand, weil der Browser den fehlenden Alternativnamen erst bei
einer Weiterleitung anmahnt. Eine Stelle kann nicht von sich selbst
abweichen.
KEIN EINTRAG IN DEN SYSTEM-PATH. `mingw64\bin` enthaelt rund hundert
Programme mit Unix-Namen (`find`, `sort`, `link`), die gleichnamige
Windows-Befehle verdecken. Das fuer eine Pruefung zu aendern haette
an ganz anderen Stellen Fehler verursacht, die niemand hierher
zurueckverfolgt.
GEGENPROBE IN BEIDE RICHTUNGEN, mit dem PATH des Nachtlaufs:
vorher (direkter Aufruf): ABSTURZ: spawnSync openssl ENOENT
nachher (ueber den Helfer): Zertifikat gebaut, 1236 Bytes
Und eine echte Pruefung unter denselben Bedingungen:
PATH ohne mingw64 -> pruef-chat-neu-stelle
63 Pruefungen, 0 Fehler, 0 Abstuerze
Genau diese Datei stand im Nachtlauf mit ENOENT rot.
EINE WACHE DAGEGEN
pruef-struktur prueft ab jetzt, dass niemand openssl wieder direkt
ruft -- der naechste merkt es dort und nicht erst in einem
Nachtlauf, den niemand liest. Drei Gegenproben.
Beim ersten Anlauf schlug sie auf ihre EIGENEN Probetexte an: Sie
liest alle Serverdateien, und dazu gehoert sie selbst. Die Texte
werden jetzt zusammengesetzt. Derselbe Selbsttreffer ist mir heute
schon zweimal passiert.
DER DRITTE AUSGANG BLEIBT, WO ER HINGEHOERT
`helfer-kachel-echtfarbe.mjs` faengt den Fehler weiterhin ab und
meldet `moeglich: false` mit Grund, statt abzubrechen. Eine
Sicherung abzuschaffen, weil ihr Anlass gerade behoben ist, ist der
Anfang des naechsten stillen Fehlschlags.
NEBENBEI: pruef-community-sicht
Sie war rot mit "ZU VIEL: reaktion.html" -- mein eigener Rueckstand
vom 28.09. Die Reaction steht als Kachel im Community-Bereich; die
Liste war nicht nachgezogen. Jetzt 10 / 0.
Die Liste bleibt bewusst von Hand gepflegt: Sie ist eine ABSICHT,
keine Ableitung. Aus rechte.js gelesen verglichen sich zwei Kopien
derselben Quelle -- immer gruen, nie ein Beweis.
GEPRUEFT
pruef-struktur ALLES IN ORDNUNG (mit der neuen Wache)
pruef-community-sicht 10 / 0
pruef-chat-neu-stelle 63 / 0 (mit dem PATH des Nachtlaufs)
pruef-notizen, -teamlage-karten, -werdegang: EXIT 0, keine Abstuerze
mess-quer EXIT 0
29 Dateien: node --check auf allen, kein direkter Aufruf mehr
442 lines
19 KiB
JavaScript
442 lines
19 KiB
JavaScript
/* =====================================================================
|
|
DIE ZWEI QUELLEN FUER OBS (28.09.2026)
|
|
|
|
Gemessen wird die Behauptung, auf der alles steht: Beide Seiten
|
|
arbeiten OHNE ANMELDUNG, nur mit einem Schluessel in der Adresse.
|
|
Dafuer wird ein Browserfenster ohne jeden Keks geoeffnet.
|
|
|
|
Dazu: Der falsche Schluessel fuehrt zu nichts, der Grund dafuer
|
|
steht im Bild, und eine bestaetigte Spende laeuft als Karte auf
|
|
der durchsichtigen Tafel.
|
|
|
|
Aufrufen mit: node server/mess-buehne.mjs
|
|
===================================================================== */
|
|
/* =====================================================================
|
|
WIE DIE REACTION AUSSIEHT (28.09.2026)
|
|
|
|
Die Pruefung nebenan misst, was die Schnittstelle antwortet. Das
|
|
sagt nichts darueber, ob ein Mensch damit einen Abend verbringen
|
|
moechte. Dieses Werkzeug oeffnet die Seite in einem echten Browser,
|
|
schaltet sie durch alle drei Zustaende und legt Bilder ab.
|
|
|
|
Gemessen wird dabei auch, was man sonst uebersieht:
|
|
- laeuft etwas in die Konsole?
|
|
- ragt etwas seitlich hinaus?
|
|
- ist der Saal so hoch wie das Fenster, oder muss man scrollen?
|
|
|
|
NIE DIE ECHTE DATENBANK. Eigener Ordner, eigener Port, eigenes
|
|
Zertifikat -- und am Ende ist alles weg.
|
|
|
|
Aufrufen mit: node server/mess-reaktion.mjs
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { request } from "node:http";
|
|
import { execFileSync } from "node:child_process";
|
|
import { createServer as httpsServer } from "node:https";
|
|
import { zertifikatBauen } from "./helfer-openssl.mjs";
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const ordner = mkdtempSync(join(tmpdir(), "mess-buehne-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
const PORT = 5483;
|
|
const HTTPS_PORT = PORT + 1;
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
for (const art of ["uncaughtException", "unhandledRejection"]) {
|
|
process.on(art, (f) => {
|
|
console.error(`\nABBRUCH (${art}): ${f?.stack || f}`);
|
|
process.exit(8);
|
|
});
|
|
}
|
|
|
|
const express = (await import("express")).default;
|
|
const ec = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
|
|
await import("./index.js");
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
|
|
/* ---------- Leute ------------------------------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const kenn = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", kenn, new Date().toISOString());
|
|
const ALT = new Date(Date.now() - 200 * 86400000).toISOString();
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salt, 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,"
|
|
+ " code_kennung, aktiv, erstellt) VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", kenn).update(code).digest("hex"), ALT);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const CODES = { admin: "CODE-M00-000001", gast: "CODE-M01-000001", gast2: "CODE-M02-000001" };
|
|
const IDS = {
|
|
admin: anlegen("DogFather", "admin", CODES.admin),
|
|
gast: anlegen("Nachtfalter", "gast", CODES.gast),
|
|
gast2: anlegen("Lenny", "gast", CODES.gast2),
|
|
};
|
|
d.close();
|
|
|
|
function roh(pfad, { method = "GET", keks = "", rumpf = null } = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const daten = rumpf === null ? null : Buffer.from(JSON.stringify(rumpf));
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method,
|
|
headers: {
|
|
Host: CREW, Origin: `https://${CREW}`,
|
|
...(keks ? { Cookie: keks } : {}),
|
|
...(daten ? { "Content-Type": "application/json", "Content-Length": daten.length } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
const teile = [];
|
|
antwort.on("data", (s) => teile.push(s));
|
|
antwort.on("end", () => {
|
|
const text = Buffer.concat(teile).toString("utf8");
|
|
let j = null; try { j = JSON.parse(text); } catch { /* kein JSON */ }
|
|
fertig({ code: antwort.statusCode, text, j, kopf: antwort.headers });
|
|
});
|
|
});
|
|
a.on("error", schief);
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
async function anmelden(rolle) {
|
|
const a = await roh("/workspace/api/anmelden", {
|
|
method: "POST",
|
|
rumpf: { rolle: rolle === "gast2" ? "gast" : rolle, code: CODES[rolle], alter_ok: true },
|
|
});
|
|
return [].concat(a.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
const k = {
|
|
admin: await anmelden("admin"),
|
|
gast: await anmelden("gast"),
|
|
gast2: await anmelden("gast2"),
|
|
};
|
|
|
|
/* ---------- Die Buehne fuellen ------------------------------------------ */
|
|
await roh("/workspace/api/reaktion", {
|
|
method: "PUT", keks: k.admin,
|
|
rumpf: {
|
|
titel: "Wir schauen den neuen Trailer",
|
|
video: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
beginnt_am: new Date(Date.now() + 7 * 60_000).toISOString(),
|
|
},
|
|
});
|
|
|
|
/* ZWEI STUECKE IN DIE WARTESCHLANGE. Eine leere Liste sieht auf
|
|
einem Bildschirmfoto genauso aus wie eine kaputte -- beides ist
|
|
ein Kasten ohne Zeilen. Zwei echte Eintraege zeigen, ob Titel,
|
|
Vorschaubild und die vier Knoepfe je Zeile wirklich ankommen. */
|
|
for (const adresse of ["https://youtu.be/9bZkp7q19f0",
|
|
"https://www.youtube.com/watch?v=kJQP7kiw5Fk"]) {
|
|
await roh("/workspace/api/reaktion/liste", {
|
|
method: "POST", keks: k.admin, rumpf: { video: adresse },
|
|
});
|
|
}
|
|
|
|
const REDEN = [
|
|
["gast", "Bin schon daaa 🐺"],
|
|
["gast2", "Ich auch! Hab extra Popcorn geholt"],
|
|
["gast", "Wie lange noch?"],
|
|
["admin", "Zwei Minuten, ich richte noch die Kamera"],
|
|
["gast2", "Kein Stress, wir sitzen bequem"],
|
|
["gast", "Letztes Mal war so gut, ich hab geheult"],
|
|
];
|
|
|
|
/* ---------- Der Vorbau -------------------------------------------------- */
|
|
const schluesselDatei = join(ordner, "p.key");
|
|
const zertDatei = join(ordner, "p.crt");
|
|
zertifikatBauen(schluesselDatei, zertDatei, CREW);
|
|
|
|
const vorbau = httpsServer({
|
|
key: readFileSync(schluesselDatei), cert: readFileSync(zertDatei),
|
|
}, (von, nach) => {
|
|
const weiter = request({
|
|
host: "127.0.0.1", port: PORT, path: von.url, method: von.method,
|
|
headers: { ...von.headers, host: `${CREW}:${HTTPS_PORT}` },
|
|
}, (antwort) => {
|
|
nach.writeHead(antwort.statusCode || 502, antwort.headers);
|
|
antwort.pipe(nach);
|
|
});
|
|
weiter.on("error", () => { try { nach.writeHead(502); nach.end(); } catch { /* egal */ } });
|
|
von.pipe(weiter);
|
|
});
|
|
await new Promise((r) => vorbau.listen(HTTPS_PORT, "127.0.0.1", r));
|
|
const BASIS = `https://${CREW}:${HTTPS_PORT}`;
|
|
|
|
const pw = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await pw.chromium.launch({
|
|
args: [`--host-resolver-rules=MAP ${CREW} 127.0.0.1`, "--ignore-certificate-errors",
|
|
/* Kamera und Mikrofon ohne Rueckfrage -- und ein bewegtes
|
|
Testbild statt eines schwarzen Rechtecks. Ohne das sieht jede
|
|
Aufnahme aus, als waere die Kamera kaputt. */
|
|
"--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream"],
|
|
});
|
|
|
|
/* JEDE GEOEFFNETE SEITE HAELT ZWEI STEHENDE VERBINDUNGEN (der
|
|
Ereignisstrom der Kopfleiste und der dieser Seite). Ein Browser
|
|
laesst je Adresse nur wenige davon zu -- wer seine Seiten nicht
|
|
schliesst, nimmt den spaeteren den Platz weg.
|
|
|
|
GEMESSEN: Die Seiten aus den ersten beiden Abschnitten blieben
|
|
offen, und danach kam Lennys Strom gar nicht mehr zustande. Die
|
|
Signale gingen ins Leere, und die Kamerafenster blieben schwarz --
|
|
ein Messfehler, der wie ein Programmfehler aussah. */
|
|
async function alsWer(rolle, breite, hoehe) {
|
|
const kontext = await browser.newContext({
|
|
viewport: { width: breite, height: hoehe },
|
|
ignoreHTTPSErrors: true,
|
|
permissions: ["camera", "microphone"],
|
|
});
|
|
await kontext.addCookies((k[rolle] || "").split("; ").filter(Boolean).map((z) => {
|
|
const [n, ...r] = z.split("=");
|
|
return { name: n, value: r.join("="), domain: CREW, path: "/" };
|
|
}));
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
const anfragen = [];
|
|
seite.on("request", (r) => { if (/\/api\//.test(r.url())) anfragen.push(r.url().replace(/^https?:\/\/[^/]+/, "")); });
|
|
seite.on("pageerror", (f) => konsole.push(String(f?.message || f)));
|
|
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
|
seite.on("response", (a) => {
|
|
if (a.status() >= 400) konsole.push(`${a.status()} ${a.url().replace(/^https?:\/\/[^/]+/, "")}`);
|
|
});
|
|
return { kontext, seite, konsole, anfragen };
|
|
}
|
|
|
|
async function bild(seite, name) {
|
|
await seite.screenshot({ path: join(HIER, `mess-reaktion-${name}.png`) });
|
|
console.log(` Bild: server/mess-reaktion-${name}.png`);
|
|
}
|
|
|
|
async function vermessen(seite, wo) {
|
|
const m = await seite.evaluate(() => ({
|
|
ueber: Math.max(0, document.documentElement.scrollWidth - document.documentElement.clientWidth),
|
|
hoch: Math.max(0, document.documentElement.scrollHeight - document.documentElement.clientHeight),
|
|
stand: document.getElementById("saal")?.dataset.stand || "?",
|
|
schild: document.getElementById("schild-wort")?.textContent || "",
|
|
}));
|
|
console.log(` ${wo}: Stand „${m.stand}", Schild „${m.schild}", `
|
|
+ `seitlich ${m.ueber} px, senkrecht ${m.hoch} px`);
|
|
return m;
|
|
}
|
|
|
|
|
|
/* ---------- Die Buehne vorbereiten -------------------------------------- */
|
|
await roh("/workspace/api/reaktion", {
|
|
method: "PUT", keks: k.admin,
|
|
rumpf: { titel: "Probe fuer OBS", video: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" },
|
|
});
|
|
await roh("/workspace/api/reaktion/stand", {
|
|
method: "POST", keks: k.admin, rumpf: { stand: "live" },
|
|
});
|
|
await roh("/workspace/api/reaktion/dabei", { method: "POST", keks: k.admin });
|
|
|
|
const adr = await roh("/workspace/api/buehne/adressen", { keks: k.admin });
|
|
console.log(` Adressen: ${adr.code}`);
|
|
let ausgang = 0;
|
|
if (adr.code !== 200) {
|
|
console.log(" ACHTUNG: Die Adressen kommen nicht.");
|
|
process.exit(1);
|
|
}
|
|
const schluessel = new URL(adr.j.video).searchParams.get("s");
|
|
console.log(` Schluessel: ${schluessel.length} Zeichen`);
|
|
if (schluessel.length !== 64) {
|
|
console.log(" ACHTUNG: Der Schluessel ist zu kurz.");
|
|
ausgang = 1;
|
|
}
|
|
|
|
/* NUR DIE LEITUNG SIEHT DIE ADRESSEN. Wer sie hat, kann mitsehen --
|
|
das ist vertretbar fuer den, der sendet, und nicht fuer jeden im
|
|
Haus. Diese Messung legt nur DogFather und zwei Zuschauer an; die
|
|
uebrigen Rollen prueft `pruef-buehne`. */
|
|
const alsGast = await roh("/workspace/api/buehne/adressen", { keks: k.gast });
|
|
console.log(` Ein Zuschauer bekommt die Adressen: ${alsGast.code}`);
|
|
if (alsGast.code !== 403) {
|
|
console.log(" ACHTUNG: Ein Zuschauer darf die Adressen sehen.");
|
|
ausgang = 1;
|
|
}
|
|
|
|
/* ---------- Ein Fenster OHNE Anmeldung ---------------------------------- */
|
|
const fremd = await browser.newContext({
|
|
viewport: { width: 1280, height: 720 },
|
|
ignoreHTTPSErrors: true,
|
|
});
|
|
const konsole = [];
|
|
const tafelSeite = await fremd.newPage();
|
|
tafelSeite.on("pageerror", (f) => konsole.push(String(f?.message || f)));
|
|
tafelSeite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
|
|
|
/* Erst der FALSCHE Schluessel. Eine Pruefung, die nur den richtigen
|
|
kennt, beweist nicht, dass der falsche abgewiesen wird. */
|
|
await tafelSeite.goto(`${BASIS}/workspace/tafel.html?s=falschfalschfalsch`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await tafelSeite.waitForTimeout(1200);
|
|
const beiFalsch = await tafelSeite.evaluate(() => ({
|
|
fehler: document.querySelector(".obs-fehler")?.textContent?.trim() || "",
|
|
karten: document.querySelectorAll(".spendenkarte").length,
|
|
}));
|
|
console.log(` Falscher Schluessel: „${beiFalsch.fehler.slice(0, 48)}…", ${beiFalsch.karten} Karten`);
|
|
if (!beiFalsch.fehler.includes("stimmt nicht") || beiFalsch.karten !== 0) {
|
|
console.log(" ACHTUNG: Ein falscher Schluessel wird nicht sauber abgewiesen.");
|
|
ausgang = 1;
|
|
}
|
|
|
|
/* Jetzt der richtige. */
|
|
await tafelSeite.goto(`${BASIS}/workspace/tafel.html?s=${schluessel}`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await tafelSeite.waitForTimeout(1500);
|
|
const kekse = await fremd.cookies();
|
|
const grund = await tafelSeite.evaluate(() => ({
|
|
hintergrund: getComputedStyle(document.body).backgroundColor,
|
|
fehler: document.querySelector(".obs-fehler")?.textContent || "",
|
|
}));
|
|
console.log(` Kekse im Fenster: ${kekse.length} Hintergrund: ${grund.hintergrund}`);
|
|
if (kekse.length !== 0) {
|
|
console.log(" ACHTUNG: Die Quelle hat sich angemeldet -- das soll sie nicht muessen.");
|
|
ausgang = 1;
|
|
}
|
|
if (!/rgba\(0, 0, 0, 0\)|transparent/.test(grund.hintergrund)) {
|
|
console.log(` ACHTUNG: Der Grund ist nicht durchsichtig (${grund.hintergrund}).`);
|
|
ausgang = 1;
|
|
}
|
|
if (grund.fehler) {
|
|
console.log(` ACHTUNG: Die Tafel meldet einen Fehler: ${grund.fehler}`);
|
|
ausgang = 1;
|
|
}
|
|
|
|
/* ---------- Laeuft eine Karte an? --------------------------------------- */
|
|
await roh("/workspace/api/spenden", {
|
|
method: "POST", keks: k.admin,
|
|
rumpf: { dogen_mille: 2500, name: "Nachtfalter", gruss: "Gruss an die Buehne" },
|
|
});
|
|
await tafelSeite.waitForFunction(
|
|
() => document.querySelector('.spendenkarte[data-da="ja"]'), null, { timeout: 12000 },
|
|
).catch(() => {});
|
|
await tafelSeite.waitForTimeout(600);
|
|
const karte = await tafelSeite.evaluate(() => {
|
|
const k2 = document.querySelector(".spendenkarte");
|
|
if (!k2) return null;
|
|
const r = k2.getBoundingClientRect();
|
|
return {
|
|
/* Zahl, Wort und Zeichen getrennt -- `textContent` klebt sie
|
|
sonst zu „2,5Dogen" zusammen, und eine Suche darin traefe
|
|
frueher oder spaeter das Falsche. */
|
|
zahl: k2.querySelector(".dogen__zahl")?.textContent?.trim(),
|
|
wort: k2.querySelector(".dogen__wort")?.textContent?.trim(),
|
|
zeichen: !!k2.querySelector(".dogen__zeichen"),
|
|
wer: k2.querySelector(".spendenkarte__wer")?.textContent?.trim(),
|
|
stufe: k2.querySelector(".spendenkarte__stufe")?.textContent?.trim(),
|
|
breite: Math.round(r.width), hoehe: Math.round(r.height),
|
|
};
|
|
});
|
|
console.log(` Karte auf der Tafel: ${JSON.stringify(karte)}`);
|
|
if (!karte || karte.zahl !== "2,5" || karte.wort !== "Dogen" || !karte.zeichen) {
|
|
console.log(" ACHTUNG: Die Karte laeuft auf der Tafel nicht an -- "
|
|
+ "oder ohne Dogen und Zeichen.");
|
|
ausgang = 1;
|
|
}
|
|
/* IM STREAM DARF KEIN GELD STEHEN. Die Tafel ist die einzige
|
|
Flaeche, die wirklich jeder sieht. */
|
|
const geldImStream = await tafelSeite.evaluate(() =>
|
|
/€|EUR|Euro/.test(document.body.textContent || ""));
|
|
console.log(` Geld im Streambild: ${geldImStream}`);
|
|
if (geldImStream) {
|
|
console.log(" ACHTUNG: Auf der Buehnentafel steht Geld.");
|
|
ausgang = 1;
|
|
}
|
|
await tafelSeite.screenshot({ path: join(HIER, "mess-buehne-1-tafel.png") });
|
|
console.log(" Bild: server/mess-buehne-1-tafel.png");
|
|
|
|
/* ---------- Das Video als Quelle ---------------------------------------- */
|
|
const videoSeite = await fremd.newPage();
|
|
videoSeite.on("pageerror", (f) => konsole.push(String(f?.message || f)));
|
|
await videoSeite.goto(`${BASIS}/workspace/buehne.html?s=${schluessel}`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await videoSeite.waitForTimeout(6000);
|
|
const video = await videoSeite.evaluate(() => ({
|
|
rahmen: !!document.querySelector("#platz iframe"),
|
|
fehler: document.querySelector(".obs-fehler")?.textContent || "",
|
|
bedienung: document.body.innerHTML.includes("controls=1"),
|
|
}));
|
|
console.log(` Video-Quelle: Rahmen=${video.rahmen} Fehler=„${video.fehler}"`);
|
|
if (!video.rahmen || video.fehler) {
|
|
console.log(" ACHTUNG: Die Video-Quelle zeigt kein Video.");
|
|
ausgang = 1;
|
|
}
|
|
await videoSeite.screenshot({ path: join(HIER, "mess-buehne-2-video.png") });
|
|
console.log(" Bild: server/mess-buehne-2-video.png");
|
|
|
|
/* ---------- Ein neuer Schluessel macht die alten Adressen tot ----------- */
|
|
const neu = await roh("/workspace/api/buehne/schluessel", { method: "POST", keks: k.admin });
|
|
const neuerSchluessel = new URL(neu.j.video).searchParams.get("s");
|
|
console.log(` Neuer Schluessel: ${neuerSchluessel !== schluessel ? "anders" : "GLEICH"}`);
|
|
const altNochGut = await roh(`/workspace/api/buehne/lage?s=${schluessel}`, {});
|
|
console.log(` Alte Adresse antwortet: ${altNochGut.code}`);
|
|
if (neuerSchluessel === schluessel || altNochGut.code !== 404) {
|
|
console.log(" ACHTUNG: Der alte Schluessel wirkt noch.");
|
|
ausgang = 1;
|
|
}
|
|
const neuGut = await roh(`/workspace/api/buehne/lage?s=${neuerSchluessel}`, {});
|
|
console.log(` Neue Adresse antwortet: ${neuGut.code}`);
|
|
if (neuGut.code !== 200) {
|
|
console.log(" ACHTUNG: Der neue Schluessel wirkt nicht.");
|
|
ausgang = 1;
|
|
}
|
|
|
|
/* ---------- Der Buehnenmodus der Reaction-Seite -------------------------- */
|
|
const hostSeite = await alsWer("admin", 1280, 720);
|
|
await hostSeite.seite.goto(`${BASIS}/workspace/reaktion.html?nur=buehne`,
|
|
{ waitUntil: "domcontentloaded" });
|
|
await hostSeite.seite.waitForTimeout(3000);
|
|
const nurBild = await hostSeite.seite.evaluate(() => {
|
|
const sicht = (w) => {
|
|
const e = document.querySelector(w);
|
|
if (!e) return false;
|
|
const r = e.getBoundingClientRect();
|
|
return r.width > 0 && r.height > 0 && getComputedStyle(e).display !== "none";
|
|
};
|
|
return {
|
|
modus: document.body.dataset.nur,
|
|
kopf: sicht(".kopfleiste"), chat: sicht(".schiene"), pult: sicht(".pult"),
|
|
schild: sicht(".buehne-schild"), leinwand: sicht(".leinwand"),
|
|
saal: Math.round(document.getElementById("saal").getBoundingClientRect().height),
|
|
fenster: window.innerHeight,
|
|
};
|
|
});
|
|
console.log(` Buehnenmodus: Kopf=${nurBild.kopf} Chat=${nurBild.chat} `
|
|
+ `Pult=${nurBild.pult} Schild=${nurBild.schild} | Leinwand=${nurBild.leinwand} `
|
|
+ `Saal ${nurBild.saal} von ${nurBild.fenster}`);
|
|
if (nurBild.kopf || nurBild.chat || nurBild.pult || nurBild.schild || !nurBild.leinwand
|
|
|| nurBild.saal < nurBild.fenster - 4) {
|
|
console.log(" ACHTUNG: Der Buehnenmodus raeumt nicht sauber auf.");
|
|
ausgang = 1;
|
|
}
|
|
await hostSeite.seite.screenshot({ path: join(HIER, "mess-buehne-3-modus.png") });
|
|
console.log(" Bild: server/mess-buehne-3-modus.png");
|
|
|
|
if (konsole.length) console.log(" KONSOLE:", konsole.slice(0, 4).join(" | "));
|
|
|
|
await fremd.close();
|
|
await hostSeite.kontext.close();
|
|
await browser.close();
|
|
await new Promise((r) => vorbau.close(r));
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
console.log(`\nFertig. Rueckgabe ${ausgang}.`);
|
|
process.exit(ausgang);
|