diff --git a/pruef-abmelden.mjs b/pruef-abmelden.mjs new file mode 100644 index 00000000..1e3ef8c5 --- /dev/null +++ b/pruef-abmelden.mjs @@ -0,0 +1,124 @@ +/* ===================================================================== + pruef-abmelden.mjs — meldet "Abmelden" wirklich ab? + + Rückmeldung vom 23.08.2026: "der abmelde button klappt auch nicht auf + dem handy". Er tat scheinbar nichts. + + Die Ursache war nicht das Handy: Der Knopf beendete die Team-Sitzung + und lud neu -- das Merkmal der Zugangswand blieb aber stehen, und beim + Neuladen holte sich die Seite sofort wieder einen Ausweis. Man war + nach einer Zehntelsekunde erneut angemeldet. Auf dem Handy sieht man + das Aufblitzen eher, deshalb fiel es dort auf. + + Geprüft wird deshalb, was TATSÄCHLICH hinausgeht -- nicht, ob sich + etwas auf dem Schirm bewegt. + ===================================================================== */ +import { chromium, devices } from "playwright"; +import { createServer } from "node:http"; +import { readFileSync, existsSync } from "node:fs"; +import { join, extname } from "node:path"; + +const W = process.cwd(); +const T = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", + ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", + ".webp": "image/webp", ".ico": "image/x-icon", ".json": "application/json" }; +const srv = createServer((q, r) => { + const p = join(W, decodeURIComponent(q.url.split("?")[0])); + if (existsSync(p) && !p.endsWith("/")) { + r.writeHead(200, { "content-type": T[extname(p)] || "text/plain" }); + return r.end(readFileSync(p)); + } + r.writeHead(404).end(""); +}); +await new Promise((r) => srv.listen(4357, r)); + +const ergebnisse = []; +const pruefe = (n, ok, z) => { + ergebnisse.push({ n, ok }); + console.log((ok ? " ok " : " FEHL ") + n + (z !== undefined ? " -> " + z : "")); +}; + +for (const [wo, angaben] of [ + ["Computer", { viewport: { width: 1440, height: 950 } }], + ["Handy", { ...devices["iPhone 14 Pro"] }], +]) { + const browser = await chromium.launch(); + const kontext = await browser.newContext(angaben); + const seite = await kontext.newPage(); + const protokoll = []; + seite.on("pageerror", (e) => protokoll.push(e.message)); + + const gerufen = []; + await seite.route("**/*", async (route) => { + const u = new URL(route.request().url()); + const m = route.request().method(); + if (m === "POST") gerufen.push(u.pathname); + + if (u.pathname === "/webdesign/zugang-beenden") return route.fulfill({ json: { ok: true } }); + if (u.pathname.startsWith("/auth/")) return route.fulfill({ json: { ok: true } }); + if (u.pathname.includes("/webdesign/admin/uebersicht")) { + return route.fulfill({ json: { ok: true, + beiMir: { anfragen: { n: 0 }, nachrichten: { n: 0 }, wuensche: { n: 0 }, widerrufe: { n: 0 }, projekte: 0 }, + beiKunde: { projekte: 0, aufgaben: 0, zahlungen: { n: 0, summe: 0 }, zahlungenText: "0,00 €" }, + geld: { monat: { n: 0, brutto: 0, netto: 0, bruttoText: "0,00 €", nettoText: "0,00 €" }, + gesamt: { n: 0, bruttoText: "0,00 €" }, gebuehrenText: "0,00 €" }, + projekte: [], verlauf: [], bestand: {} } }); + } + if (u.pathname.includes("/webdesign/admin/")) { + return route.fulfill({ json: { ok: true, anfragen: [], kunden: [], projekte: [], zahlungen: [], verlaeufe: [] } }); + } + return route.continue(); + }); + /* Nur auf der VERWALTUNGSSEITE vorbelegen. + + addInitScript laeuft bei jeder Navigation erneut. Ohne diese + Einschraenkung setzte es den Schluessel auf der Zugangswand sofort + wieder -- und die Pruefung "der Schluessel ist weg" mass dann das + eigene Vorbereitungsskript statt des Abmeldens. Erst beim + Nachsehen aufgefallen: Zwei Zeilen meldeten Fehler, obwohl das + Abmelden voellig richtig funktionierte. */ + await seite.addInitScript(() => { + if (!location.pathname.includes("verwaltung")) return; + sessionStorage.setItem("wd-vw-token", "t"); + sessionStorage.setItem("wd-vw-token-quelle", "ausweis"); + sessionStorage.setItem("wd-sitzung-aktiv", "1"); + }); + + await seite.goto("http://127.0.0.1:4357/webdesign/verwaltung.html"); + await seite.waitForTimeout(700); + console.log("\n=== " + wo + " ==="); + + const knopf = seite.locator("#vw-abmelden"); + pruefe(wo + ": der Abmelden-Knopf ist sichtbar", await knopf.isVisible()); + const kasten = await knopf.boundingBox(); + pruefe(wo + ": er ist gross genug zum Antippen", + kasten && kasten.height >= 44, kasten && Math.round(kasten.height) + "px"); + + await knopf.click(); + await seite.waitForTimeout(900); + + /* Das Entscheidende: Beide Sitzungen muessen beendet werden. Vorher + ging nur der erste Aufruf hinaus -- und genau deshalb war man + sofort wieder drin. */ + pruefe(wo + ": die Team-Sitzung wird beendet", + gerufen.some((x) => x.includes("/auth/logout")), gerufen.join(", ")); + pruefe(wo + ": die ZUGANGSWAND wird ebenfalls beendet", + gerufen.some((x) => x === "/webdesign/zugang-beenden"), gerufen.join(", ")); + + pruefe(wo + ": der gespeicherte Schluessel ist weg", + await seite.evaluate(() => !sessionStorage.getItem("wd-vw-token"))); + pruefe(wo + ": auch dessen Herkunft", + await seite.evaluate(() => !sessionStorage.getItem("wd-vw-token-quelle"))); + + /* Und man landet an der Zugangswand, nicht wieder in der Verwaltung. */ + pruefe(wo + ": man landet an der Zugangswand", + /zugang\.html/.test(seite.url()), seite.url()); + + pruefe(wo + ": kein Skriptfehler", protokoll.length === 0, protokoll.join(" | ")); + await browser.close(); +} + +srv.close(); +const f = ergebnisse.filter((e) => !e.ok).length; +console.log(`\n===== ${ergebnisse.length - f} bestanden, ${f} fehlgeschlagen =====`); +process.exitCode = f ? 1 : 0; diff --git a/webdesign/sw.js b/webdesign/sw.js index b76c012a..c22b85f4 100644 --- a/webdesign/sw.js +++ b/webdesign/sw.js @@ -34,7 +34,7 @@ Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten Stand fest. */ -const CACHE_NAME = "dogfather-webdesign-v25"; +const CACHE_NAME = "dogfather-webdesign-v26"; /* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE HTML-Datei in dieser Liste. */ diff --git a/webdesign/verwaltung.html b/webdesign/verwaltung.html index e5eb315d..9258f928 100644 --- a/webdesign/verwaltung.html +++ b/webdesign/verwaltung.html @@ -1492,10 +1492,44 @@ } }); - document.getElementById("vw-abmelden").addEventListener("click", async function () { - try { await api("/auth/logout", { method: "POST", body: {} }); } catch (e) { /* egal */ } - token = null; tokenSchreiben(null); - location.reload(); + document.getElementById("vw-abmelden").addEventListener("click", async function (e) { + /* ABMELDEN MUSS AUCH DIE ZUGANGSWAND BEENDEN. + + Vorher tat der Knopf scheinbar nichts (Rueckmeldung 23.08.2026: + "der abmelde button klappt auch nicht auf dem handy"). Er beendete + die Team-Sitzung, loeschte den Schluessel und lud neu -- aber das + Merkmal der Zugangswand blieb im Browser stehen. Beim Neuladen holt + sich die Seite damit sofort wieder einen Ausweis und ist nach einer + Zehntelsekunde erneut angemeldet. Auf dem Handy faellt es mehr auf, + weil man das kurze Aufblitzen dort eher sieht -- der Fehler war auf + beiden Geraeten derselbe. + + Deshalb jetzt BEIDE Sitzungen beenden. Das ist auch die ehrliche + Bedeutung des Wortes: Wer sich abmeldet, will draussen sein und + nicht in einem halb offenen Zustand zurueckbleiben. + + Die Reihenfolge ist wichtig: erst die Zugangswand, dann neu laden. + Umgekehrt waere die Seite schon weg, bevor der Aufruf hinausgeht. */ + window.WD.knopfSperren(e.target, true, "Wird abgemeldet…"); + + /* Beide Aufrufe einzeln absichern: Faellt einer aus, soll der andere + trotzdem laufen. Ein halbes Abmelden waere schlimmer als keins -- + man haelte sich fuer abgemeldet und waere es nicht. */ + try { await api("/auth/logout", { method: "POST", body: {} }); } catch (f) { /* egal */ } + try { + await fetch("/webdesign/zugang-beenden", { + method: "POST", credentials: "same-origin", + headers: { "content-type": "application/json" }, body: "{}", + }); + } catch (f) { /* egal */ } + + token = null; tokenQuelle = null; tokenSchreiben(null); + try { sessionStorage.removeItem("wd-sitzung-aktiv"); } catch (f) { /* egal */ } + + /* Zur Zugangswand statt neu laden. Ein Neuladen der Verwaltung + zeigte nur die Codeeingabe -- und liesse offen, ob man nun + draussen ist oder nicht. */ + location.href = "/webdesign/zugang.html"; }); /* ---------------- Liste ---------------- */