Files
dogfather-universe/server/mess-teilen.mjs
T
DogFatherGitandClaude Opus 5 48689e28ac 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 <[email protected]>
2026-10-05 12:34:51 +02:00

199 lines
9.0 KiB
JavaScript

/* =====================================================================
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);