From 48689e28acb33122905c36163fc7094b1e184ba8 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Mon, 5 Oct 2026 12:34:51 +0200 Subject: [PATCH] Teilen-Seite kann nicht mehr stumm haengen bleiben Filipe: "ich lade video hoch und es erscheint immer noch nicht" -- auf Nachfrage: die Seite bleibt haengen, es kommt KEINE Meldung. WAS GEMESSEN WURDE, bevor etwas angefasst wurde: Server zwischen 08:41 und der Meldung unveraendert, ein Neustart Seine Sitzungen gueltig, Android-App TikTok-Abruf vom Server 200, auch fuer Kurzlinks Seite, Skript, alle 14 Elemente live auf beiden Adressen Service Worker speichert nichts zwischen Manifeste (beide) gueltig, share_target da Protokoll heute kein video_eingelesen Teilen-Weg nachgespielt (mess-teilen) 201, Eintrag, Erfolgssatz Aus den Caddy-Protokollen ausserdem: Die Handy-Apps laufen auf crew. (85 Android, 18 iPhone), auf der Agenturadresse fast nur Windows. Der Riegel von heute Mittag trifft ihn also nicht. DIE URSACHE IST DAMIT NICHT GEFUNDEN -- aber ein eigener Mangel schon, und der ist der Grund, warum die Suche so lange gedauert hat: `los()` baut die Seite auf und macht dabei keine einzige Netzanfrage. Es kann also nur stolpern, nicht warten. Und wenn es stolpert, wird `ladezeile.hidden = true` nie erreicht -- die Seite steht fuer immer auf "wird geladen". Kein Fehler, kein Hinweis, nichts zum Weitermelden, und von aussen nicht zu unterscheiden von "der Server antwortet nicht". Eine Seite, die nicht sagen kann, dass sie kaputt ist, kostet jeden Fehler doppelt: einmal den Fehler und einmal die Suche danach. Jetzt nennt sie den Grund im Klartext, oeffnet das Feld zum Einfuegen von Hand und haengt den Knopf daran -- eine Meldung ohne Ausweg waere nur ein Trost. Beim naechsten Versuch steht also da, woran es liegt. GEGENPROBE GEFAHREN, und sie hat zuerst mich korrigiert: 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` vor jedem Skript ersetzt, genau das, was `$` benutzt. Ergebnis: Ladezeile weg, Meldung "Probe: Element fehlt", Einfuegefeld offen. mess-teilen.mjs neu: spielt den Teilen-Weg einmal heil und einmal zerstoert durch. Co-Authored-By: Claude Opus 5 --- server/mess-teilen.mjs | 198 ++++++++++++++++++++++++++++++++ workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/js/teilen.js | 56 ++++++++- workspace/aufgaben.html | 38 +++--- workspace/automation.html | 32 +++--- workspace/befinden.html | 34 +++--- workspace/bereich.html | 44 +++---- workspace/bewerben.html | 38 +++--- workspace/bewerbungen.html | 36 +++--- workspace/buehne.html | 6 +- workspace/calls.html | 34 +++--- workspace/chat.html | 40 +++---- workspace/content.html | 34 +++--- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 +++--- workspace/entwicklung.html | 36 +++--- workspace/hilfe.html | 30 ++--- workspace/index.html | 10 +- workspace/kalender.html | 34 +++--- workspace/leistung.html | 32 +++--- workspace/manager-ziele.html | 28 ++--- workspace/material.html | 32 +++--- workspace/notizen.html | 28 ++--- workspace/personen.html | 32 +++--- workspace/profil.html | 36 +++--- workspace/reaktion.html | 38 +++--- workspace/rechte.html | 34 +++--- workspace/report.html | 36 +++--- workspace/scouting.html | 32 +++--- workspace/start.html | 32 +++--- workspace/startcheck.html | 34 +++--- workspace/steckbrief.html | 36 +++--- workspace/support.html | 28 ++--- workspace/tafel.html | 6 +- workspace/talente.html | 36 +++--- workspace/team.html | 32 +++--- workspace/teamlage.html | 34 +++--- workspace/teilen.html | 30 ++--- workspace/treff-moderation.html | 32 +++--- workspace/treff-regeln.html | 32 +++--- workspace/uebersicht.html | 32 +++--- workspace/unsere-seiten.html | 30 ++--- workspace/unterstuetzen.html | 32 +++--- workspace/werdegang.html | 32 +++--- workspace/willkommen.html | 30 ++--- workspace/wissen.html | 34 +++--- 48 files changed, 936 insertions(+), 686 deletions(-) create mode 100644 server/mess-teilen.mjs 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`. --> - - + + - +