diff --git a/server/mess-teilen.mjs b/server/mess-teilen.mjs new file mode 100644 index 00000000..fe0b3b3f --- /dev/null +++ b/server/mess-teilen.mjs @@ -0,0 +1,198 @@ +/* ===================================================================== + WAS PASSIERT BEIM TEILEN WIRKLICH? (05.10.2026) + + Filipe: "ich lade video hoch und es erscheint immer noch nicht", + dazu auf Nachfrage: die Seite bleibt haengen, es kommt KEINE + Meldung -- und auf der Crew-Adresse ist das Brett voll (49 Videos). + + Gemessen war vorher schon: Server unveraendert, Sitzung gueltig, + TikTok antwortet (auch auf Kurzlinks), und im Protokoll steht heute + kein einziges `video_eingelesen` -- die Anfrage erreicht den Server + also gar nicht. `los()` in teilen.js macht keine Netzanfrage; wenn + die Seite dort stehenbleibt, ist das Skript gestolpert. + + Diese Messung spielt genau das nach: Teilen-Seite mit einem Link + aufrufen, Browsermeldungen mitschreiben, und nachsehen, ob die + Ladezeile verschwindet. Dann denselben Weg bis zum Absenden. + + KEIN PRUEFLAUF, sondern eine Messung -- sie stellt fest, was ist, + und sagt nicht, was sein soll. + ===================================================================== */ + +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-teilen-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); + +import { notbremse } from "./helfer-notbremse.mjs"; +const { eigenerPort } = await import("./helfer-port.mjs"); +const PORT = await eigenerPort(import.meta, "mess-teilen"); +process.env.PORT = `${PORT}`; +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; + +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"); +notbremse(180_000, "mess-teilen"); +await new Promise((r) => setTimeout(r, 900)); +const BASIS = `http://127.0.0.1:${PORT}`; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); +await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "x" }), +}).catch(() => {}); +for (let i = 0; i < 60; i++) { + try { + const p = new DatabaseSync(process.env.WORKSPACE_DB); + try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); } + } catch { await new Promise((r) => setTimeout(r, 250)); } +} +{ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync("CODE-DOGI-0001", 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, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)") + .run("Filipe", "admin", hash, salt, 32768, new Date().toISOString()); + d.close(); +} + +const { chromium } = await import("playwright"); +const browser = await chromium.launch(); +const kontext = await browser.newContext({ + viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true, +}); +const seite = await kontext.newPage(); + +const klagen = []; +seite.on("console", (m) => { if (m.type() === "error") klagen.push("Konsole: " + m.text()); }); +seite.on("pageerror", (f) => klagen.push("ABSTURZ: " + String(f?.message || f))); +seite.on("requestfailed", (r) => klagen.push("Anfrage gescheitert: " + r.url().replace(BASIS, ""))); + +await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" }); +await seite.click('.rolle[data-rolle="admin"]'); +await seite.fill("#code", "CODE-DOGI-0001"); +await seite.click("button[type=submit]"); +await seite.waitForURL("**/start.html", { timeout: 20000 }); + +/* ---- Genau so, wie die App beim Teilen aufruft ---------------------- */ +const LINK = "https://vm.tiktok.com/ZN8h3hbHr/"; +await seite.goto(`${BASIS}/workspace/teilen.html?url=${encodeURIComponent(LINK)}`, + { waitUntil: "domcontentloaded" }); +await seite.waitForTimeout(1500); + +const lage = await seite.evaluate(() => { + const z = (id) => document.getElementById(id); + return { + ladezeileWeg: z("ladezeile")?.hidden === true, + wahlDa: z("wahl")?.hidden === false, + knoepfeDa: z("knoepfe")?.hidden === false, + fundDa: z("fund")?.hidden === false, + linkText: z("url-text")?.textContent || "", + knopfText: z("uebernehmen")?.textContent || "", + fehlerText: z("fehler")?.textContent || "", + }; +}); + +console.log("\n=== Die Teilen-Seite nach dem Aufruf ==="); +console.log(` Ladezeile verschwunden : ${lage.ladezeileWeg}`); +console.log(` Auswahl sichtbar : ${lage.wahlDa}`); +console.log(` Knopfreihe sichtbar : ${lage.knoepfeDa}`); +console.log(` Link erkannt : ${lage.fundDa} "${lage.linkText}"`); +console.log(` Knopf : "${lage.knopfText.trim()}"`); +console.log(` Fehlerzeile : "${lage.fehlerText.trim()}"`); +console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`); + +/* ---- Und jetzt absenden -------------------------------------------- */ +if (lage.knoepfeDa) { + const antwort = seite.waitForResponse( + (r) => /\/api\/bereich\/[a-z]+\/video$/.test(r.url()), { timeout: 20000 }).catch(() => null); + await seite.click("#uebernehmen"); + const a = await antwort; + await seite.waitForTimeout(1500); + const danach = await seite.evaluate(() => ({ + fertig: document.getElementById("fertig")?.hidden === false, + satz: document.getElementById("fertig-satz")?.textContent || "", + fehler: document.getElementById("fehler")?.textContent || "", + knopf: document.getElementById("uebernehmen")?.textContent || "", + })); + console.log("\n=== Nach dem Absenden ==="); + console.log(` Antwort vom Server : ${a ? a.status() : "KEINE -- es ging nichts hinaus"}`); + console.log(` Erfolg sichtbar : ${danach.fertig} "${danach.satz.trim()}"`); + console.log(` Fehlerzeile : "${danach.fehler.trim()}"`); + console.log(` Knopf steht auf : "${danach.knopf.trim()}"`); + console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`); + + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const n = d.prepare("SELECT COUNT(*) AS n FROM eintraege WHERE bereich = 'highlight'").get().n; + d.close(); + console.log(` Eintraege im Brett : ${n}`); +} else { + console.log("\n Die Seite ist gar nicht erst fertig geworden -- kein Absenden moeglich."); +} + +/* ===================================================================== + GEGENPROBE: Was passiert, wenn der Aufbau WIRKLICH stolpert? + + Ohne sie waere die Sicherung von heute eine Behauptung. Gepruefft + wird am haerteren Fall: Ein Element, das `los()` sofort anfasst, + wird aus der Seite entfernt, bevor das Skript laeuft. Vorher blieb + die Seite danach fuer immer auf "wird geladen" stehen -- genau + Filipes Bild. + ===================================================================== */ +{ + const s2 = await kontext.newPage(); + const klagen2 = []; + s2.on("pageerror", (f) => klagen2.push(String(f?.message || f))); + /* DIE ZERSTOERUNG MUSS SICHER ANKOMMEN (berichtigt 05.10.2026). + + Der erste Versuch tauschte die Kennung im HTML unterwegs aus -- + und kam nie an: Die Seite lud normal, der Browser meldete nichts, + und die Messung schloss daraus "die Sicherung greift nicht". + Eine Gegenprobe, die ihren eigenen Schaden nicht anrichtet, + misst gar nichts. + + Jetzt wird `document.getElementById` ersetzt, bevor irgendein + Skript laeuft -- genau das, was `$` in teilen.js benutzt. Damit + wirft `los()` zuverlaessig an derselben Stelle wie bei einem + echten Fehler. */ + await s2.addInitScript(() => { + const echt = document.getElementById.bind(document); + document.getElementById = (id) => { + if (id === "bretter") throw new Error("Probe: Element fehlt"); + return echt(id); + }; + }); + await s2.goto(`${BASIS}/workspace/teilen.html?url=${encodeURIComponent(LINK)}`, + { waitUntil: "domcontentloaded" }); + await s2.waitForTimeout(1200); + const kaputt = await s2.evaluate(() => { + const z = (id) => document.getElementById(id); + return { + ladezeileWeg: z("ladezeile")?.hidden === true, + meldung: (z("fehler")?.textContent || "").trim(), + eingabeDa: z("haendisch")?.hidden === false, + knopfDa: z("uebernehmen") !== null, + }; + }); + console.log("\n=== Gegenprobe: Seite absichtlich zerstoert ==="); + console.log(` Ladezeile verschwunden : ${kaputt.ladezeileWeg}`); + console.log(` Meldung sichtbar : "${kaputt.meldung.slice(0, 96)}"`); + console.log(` Einfuegefeld offen : ${kaputt.eingabeDa}`); + console.log(` (Browser meldete : ${klagen2.length ? klagen2[0].slice(0, 60) : "nichts"})`); + console.log(kaputt.ladezeileWeg && kaputt.meldung && kaputt.eingabeDa + ? " -> Die Seite sagt, dass etwas kaputt ist, und bietet den Weg von Hand an." + : " -> SIE HAENGT IMMER NOCH. Die Sicherung greift nicht."); + await s2.close(); +} + +await kontext.close(); +await browser.close(); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } +process.exit(0); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index f8346fe3..f50d832c 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +