Eine Arbeitsdatei war mitgewandert
tools/.messkopf.txt ist ein Zwischenstand beim Bauen der Messung -- sie gehoert nicht in die Geschichte. Die uebrigen Zwischenstaende stehen schon in .gitignore; dieser Name fehlte.
This commit is contained in:
@@ -1,226 +0,0 @@
|
||||
/* =====================================================================
|
||||
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";
|
||||
|
||||
const HIER = dirname(fileURLToPath(import.meta.url));
|
||||
const ordner = mkdtempSync(join(tmpdir(), "mess-reakt-"));
|
||||
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");
|
||||
execFileSync("openssl", ["req", "-x509", "-newkey", "rsa:2048", "-nodes",
|
||||
"-keyout", schluesselDatei, "-out", zertDatei, "-days", "2",
|
||||
"-subj", `/CN=${CREW}`, "-addext", `subjectAltName=DNS:${CREW}`], { stdio: "ignore" });
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user