// /public/assets/js/fakecheck/fakecheck.browser.js (function () { // Core / Namespace muss geladen sein if (!window.usbcheck) return; const { cfg, log: logLine, setStatus, setModeLabel, setProgress, setOverallStatus, formatBytes, formatMbps, formatDuration } = window.usbcheck; const root = document.getElementById("fc-root"); if (!root) { // Seite ohne Fakecheck-Tool → nichts tun return; } // --- DOM-Helper --------------------------------------------------------- const $ = (sel) => document.querySelector(sel); const $$ = (sel) => Array.from(document.querySelectorAll(sel)); const fsapiWarning = $("#fc-fsapi-warning"); const selectedPathText = $("#fc-selected-path-label"); const saveHint = $("#fc-save-hint"); const saveError = $("#fc-save-error"); const resMode = $("#fc-res-mode"); const resDuration = $("#fc-res-duration"); const resWriteSpeed = $("#fc-res-write-speed"); const resReadSpeed = $("#fc-res-read-speed"); const resWritten = $("#fc-res-written-bytes"); const resVerified = $("#fc-res-verified-bytes"); const btnPickDir = $("#fc-btn-pick-directory"); const btnClearSel = $("#fc-btn-clear-selection"); const btnStart = $("#fc-btn-start-tests"); const btnCancel = $("#fc-btn-cancel-tests"); const modeTiles = $$("#fc-mode-grid .fc-mode-tile"); // Login-Indikator if (cfg.loggedIn && saveHint) { saveHint.style.display = "block"; } // --- Test-Engine (Browser) --------------------------------------------- class UsbBrowserTester { constructor() { this.rootHandle = null; this.abortController = null; } hasFsApiSupport() { return "showDirectoryPicker" in window; } // Wird aktuell nicht mehr verwendet (Picker ist direkt im Click-Handler), // kann aber als Fallback/Refactoring-Hook bleiben. async pickDirectory() { if (!this.hasFsApiSupport()) { throw new Error("File System Access API wird von diesem Browser nicht unterstützt."); } const handle = await window.showDirectoryPicker(); this.rootHandle = handle; return handle; } async clearSelection() { this.rootHandle = null; } async runQuickCheck(report, progressCb, abortSignal) { const TEST_FILENAME = "usbcheck_quick_test.bin"; const TEST_SIZE_MB = 8; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); logLine("Quick-Check: Vorbereitung...", "info"); const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); const writable = await fileHandle.createWritable(); const totalBytes = TEST_SIZE_MB * 1024 * 1024; let writtenBytes = 0; const writeStart = performance.now(); while (writtenBytes < totalBytes) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = totalBytes - writtenBytes; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = (i + writtenBytes) % 251; } await writable.write(chunk); writtenBytes += chunkLen; progressCb((writtenBytes / totalBytes) * 100 * 0.5); } await writable.close(); const writeEnd = performance.now(); logLine("Quick-Check: Schreiben abgeschlossen. Verifiziere Daten...", "info"); const file = await fileHandle.getFile(); const readStart = performance.now(); const reader = file.stream().getReader(); let offset = 0; let verifiedBytes = 0; while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; const chunk = value; for (let i = 0; i < chunk.length; i++) { const expected = (offset + i) % 251; if (chunk[i] !== expected) { logLine(`Quick-Check: Datenfehler bei Byte ${offset + i}`, "error"); throw new Error(`Datenfehler im Quick-Check bei Byte ${offset + i}`); } } offset += chunk.length; verifiedBytes += chunk.length; progressCb(50 + (verifiedBytes / totalBytes) * 100 * 0.5); } const readEnd = performance.now(); const writeSeconds = (writeEnd - writeStart) / 1000; const readSeconds = (readEnd - readStart) / 1000; report.quick = { mode: "quick", test_file: TEST_FILENAME, size_bytes: totalBytes, write_bytes: writtenBytes, read_bytes: verifiedBytes, write_duration_s: writeSeconds, read_duration_s: readSeconds, write_mbit_s: writeSeconds ? (writtenBytes * 8 / (writeSeconds * 1e6)) : null, read_mbit_s: readSeconds ? (verifiedBytes * 8 / (readSeconds * 1e6)) : null, ok: true }; logLine("Quick-Check: Erfolgreich abgeschlossen.", "info"); } async runBenchmark(report, progressCb, abortSignal) { const TEST_FILENAME = "usbcheck_benchmark.bin"; const TEST_SIZE_MB = 32; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); logLine("Benchmark: Start – schreibe Testdatei...", "info"); const fileHandle = await dirHandle.getFileHandle(TEST_FILENAME, { create: true }); const writable = await fileHandle.createWritable(); const totalBytes = TEST_SIZE_MB * 1024 * 1024; let writtenBytes = 0; const writeStart = performance.now(); while (writtenBytes < totalBytes) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = totalBytes - writtenBytes; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = 0xaa; } await writable.write(chunk); writtenBytes += chunkLen; progressCb((writtenBytes / totalBytes) * 100 * 0.4); } await writable.close(); const writeEnd = performance.now(); logLine("Benchmark: Lesen & Timing...", "info"); const file = await fileHandle.getFile(); const readStart = performance.now(); const reader = file.stream().getReader(); let readBytes = 0; while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; readBytes += value.length; progressCb(40 + (readBytes / totalBytes) * 100 * 0.6); } const readEnd = performance.now(); const writeSeconds = (writeEnd - writeStart) / 1000; const readSeconds = (readEnd - readStart) / 1000; report.benchmark = { mode: "benchmark", test_file: TEST_FILENAME, size_bytes: totalBytes, write_bytes: writtenBytes, read_bytes: readBytes, write_duration_s: writeSeconds, read_duration_s: readSeconds, write_mbit_s: writeSeconds ? (writtenBytes * 8 / (writeSeconds * 1e6)) : null, read_mbit_s: readSeconds ? (readBytes * 8 / (readSeconds * 1e6)) : null, ok: true }; logLine("Benchmark: Erfolgreich abgeschlossen.", "info"); } async runWriteVerify(report, progressCb, abortSignal) { const BASE_FILENAME = "usbcheck_block_"; const BLOCKS = 4; const BLOCK_SIZE_MB = 32; const CHUNK_SIZE = 1024 * 1024; const dirHandle = this.rootHandle; if (!dirHandle) throw new Error("Kein Verzeichnis ausgewählt."); logLine("Write/Verify: Start – mehrere Blöcke werden getestet...", "info"); const totalBytes = BLOCKS * BLOCK_SIZE_MB * 1024 * 1024; let writtenBytes = 0; let verifiedBytes = 0; const globalStart = performance.now(); const writeDetails = []; const readDetails = []; for (let b = 0; b < BLOCKS; b++) { const filename = `${BASE_FILENAME}${String(b + 1).padStart(2, "0")}.bin`; logLine(`Write/Verify: Block ${b + 1}/${BLOCKS} – ${filename}`, "info"); const fileHandle = await dirHandle.getFileHandle(filename, { create: true }); const writable = await fileHandle.createWritable(); const blockBytes = BLOCK_SIZE_MB * 1024 * 1024; let blockWritten = 0; const blockWriteStart = performance.now(); while (blockWritten < blockBytes) { if (abortSignal.aborted) { await writable.abort(); throw new DOMException("Abgebrochen", "AbortError"); } const remaining = blockBytes - blockWritten; const chunkLen = Math.min(CHUNK_SIZE, remaining); const chunk = new Uint8Array(chunkLen); for (let i = 0; i < chunkLen; i++) { chunk[i] = (i + blockWritten + b * 13) % 251; } await writable.write(chunk); blockWritten += chunkLen; writtenBytes += chunkLen; const progress = (writtenBytes + verifiedBytes) / (totalBytes * 2) * 100; progressCb(progress); } await writable.close(); const blockWriteEnd = performance.now(); writeDetails.push({ block: b + 1, bytes: blockBytes, duration_s: (blockWriteEnd - blockWriteStart) / 1000 }); const file = await fileHandle.getFile(); const reader = file.stream().getReader(); let blockOffset = 0; const blockReadStart = performance.now(); while (true) { if (abortSignal.aborted) { reader.cancel(); throw new DOMException("Abgebrochen", "AbortError"); } const { done, value } = await reader.read(); if (done) break; const chunk = value; for (let i = 0; i < chunk.length; i++) { const expected = (i + blockOffset + b * 13) % 251; if (chunk[i] !== expected) { logLine(`Write/Verify: Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`, "error"); throw new Error(`Datenfehler in Block ${b + 1} bei Byte ${blockOffset + i}`); } } blockOffset += chunk.length; verifiedBytes += chunk.length; const progress = (writtenBytes + verifiedBytes) / (totalBytes * 2) * 100; progressCb(progress); } const blockReadEnd = performance.now(); readDetails.push({ block: b + 1, bytes: blockBytes, duration_s: (blockReadEnd - blockReadStart) / 1000 }); } const globalEnd = performance.now(); const totalDuration = (globalEnd - globalStart) / 1000; const sumWrite = writeDetails.reduce((acc, d) => acc + d.bytes, 0); const sumRead = readDetails.reduce((acc, d) => acc + d.bytes, 0); const writeSec = writeDetails.reduce((acc, d) => acc + d.duration_s, 0); const readSec = readDetails.reduce((acc, d) => acc + d.duration_s, 0); report.writeverify = { mode: "writeverify", blocks: BLOCKS, block_size_mb: BLOCK_SIZE_MB, total_bytes: totalBytes, written_bytes: writtenBytes, verified_bytes: verifiedBytes, write_duration_s: writeSec, read_duration_s: readSec, write_mbit_s: writeSec ? (sumWrite * 8 / (writeSec * 1e6)) : null, read_mbit_s: readSec ? (sumRead * 8 / (readSec * 1e6)) : null, ok: true }; report.writeverify_total_duration_s = totalDuration; logLine("Write/Verify: Alle Blöcke erfolgreich verifiziert.", "info"); } async run(mode, updateProgressCb, abortSignal) { if (!this.rootHandle) { throw new Error("Kein USB-Verzeichnis ausgewählt."); } const report = { meta: { base_url: cfg.baseUrl || "", locale: cfg.locale || "en", user_agent: navigator.userAgent, started_at: new Date().toISOString() }, tool: "usbcheck_browser", tool_version: "0.1.0", mode_requested: mode, quick: null, benchmark: null, writeverify: null, total_duration_s: null }; const t0 = performance.now(); if (mode === "quick") { await this.runQuickCheck(report, updateProgressCb, abortSignal); } else if (mode === "benchmark") { await this.runBenchmark(report, updateProgressCb, abortSignal); } else if (mode === "writeverify") { await this.runWriteVerify(report, updateProgressCb, abortSignal); } else if (mode === "all") { // Alle drei Tests nacheinander im selben Report const modes = ["quick", "benchmark", "writeverify"]; for (let i = 0; i < modes.length; i++) { const subMode = modes[i]; logLine(`All-Inclusive: Starte Teiltest "${subMode}" (${i + 1}/${modes.length})...`, "info"); const base = (i / modes.length) * 100; const span = (1 / modes.length) * 100; const mapProgress = (p) => { const mapped = base + (p / 100) * span; updateProgressCb(mapped); }; if (subMode === "quick") { await this.runQuickCheck(report, mapProgress, abortSignal); } else if (subMode === "benchmark") { await this.runBenchmark(report, mapProgress, abortSignal); } else if (subMode === "writeverify") { await this.runWriteVerify(report, mapProgress, abortSignal); } } } else { throw new Error("Unbekannter Modus: " + mode); } const t1 = performance.now(); report.total_duration_s = (t1 - t0) / 1000; report.meta.ended_at = new Date().toISOString(); return report; } } const tester = new UsbBrowserTester(); let currentMode = null; let isRunning = false; if (!tester.hasFsApiSupport()) { if (fsapiWarning) fsapiWarning.style.display = "block"; logLine( "Dein Browser unterstützt die File System Access API nicht voll. Einige Funktionen sind deaktiviert.", "warn" ); } function updateStartButtonState() { const hasDir = !!tester.rootHandle; const hasMode = !!currentMode; if (btnStart) btnStart.disabled = !(hasDir && hasMode && !isRunning); if (btnCancel) btnCancel.disabled = !isRunning; } // --- Event-Handler ------------------------------------------------------ // Wichtig: showDirectoryPicker direkt im Click-Handler per Promise, // kein async/await direkt im Listener → vermeidet "User activation is required". let isPickingDir = false; if (btnPickDir) { btnPickDir.addEventListener("click", () => { if (isPickingDir) return; isPickingDir = true; if (!("showDirectoryPicker" in window)) { logLine("File System Access API wird von diesem Browser nicht unterstützt.", "error"); if (fsapiWarning) fsapiWarning.style.display = "block"; isPickingDir = false; return; } window.showDirectoryPicker() .then((handle) => { tester.rootHandle = handle; if (selectedPathText) { selectedPathText.textContent = 'USB-Ordner ausgewählt (Name: "' + (handle.name || "Unbekannt") + '").'; } if (btnClearSel) btnClearSel.disabled = false; setStatus("USB-Verzeichnis ausgewählt. Wähle jetzt einen Testmodus."); logLine("Verzeichnis ausgewählt: " + (handle.name || "[ohne Namen]")); updateStartButtonState(); }) .catch((err) => { if (err && err.name === "AbortError") { logLine("Verzeichnisauswahl abgebrochen.", "warn"); } else if (err) { logLine("Fehler bei Verzeichnisauswahl: " + err.message, "error"); } else { logLine("Unbekannter Fehler bei Verzeichnisauswahl.", "error"); } }) .finally(() => { isPickingDir = false; }); }); } if (btnClearSel) { btnClearSel.addEventListener("click", async () => { await tester.clearSelection(); if (selectedPathText) { selectedPathText.textContent = "Noch kein Verzeichnis gewählt."; } btnClearSel.disabled = true; setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); logLine("Verzeichnisauswahl zurückgesetzt.", "info"); updateStartButtonState(); }); } modeTiles.forEach((tile) => { tile.addEventListener("click", () => { if (tile.classList.contains("disabled")) return; if (isRunning) return; modeTiles.forEach((t) => t.classList.remove("selected")); tile.classList.add("selected"); currentMode = tile.getAttribute("data-mode"); const titleEl = tile.querySelector("h4"); const label = titleEl ? titleEl.textContent : currentMode; setModeLabel(label || ""); setStatus(`Modus "${label}" ausgewählt. Du kannst den Test jetzt starten.`); logLine("Modus gewählt: " + currentMode, "info"); updateStartButtonState(); }); }); if (btnStart) { btnStart.addEventListener("click", async () => { if (!currentMode || !tester.rootHandle || isRunning) return; isRunning = true; updateStartButtonState(); if (btnCancel) btnCancel.disabled = false; if (saveError) saveError.style.display = "none"; setProgress(0); logLine("Starte Tests im Modus: " + currentMode.toUpperCase(), "info"); setStatus("Test läuft... bitte USB-Stick nicht entfernen."); setOverallStatus("warn", "Test läuft..."); const abortController = new AbortController(); tester.abortController = abortController; const updateProgressCb = (percent) => setProgress(percent); let report = null; try { report = await tester.run(currentMode, updateProgressCb, abortController.signal); setProgress(100); setStatus("Test abgeschlossen."); setOverallStatus("ok", "Browser-Test erfolgreich abgeschlossen."); applyReportToDashboard(report); console.log("USB Browser Test Report (fakecheck):", report); await saveReportToBackend(report); } catch (err) { if (err && err.name === "AbortError") { setStatus("Test wurde abgebrochen."); setOverallStatus("warn", "Test abgebrochen."); logLine("Test wurde vom Benutzer abgebrochen.", "warn"); } else if (err) { setStatus("Fehler: " + err.message); setOverallStatus("bad", "Fehler im Browser-Test."); logLine("Fehler im Test: " + err.message, "error"); } else { setStatus("Unbekannter Fehler im Test."); setOverallStatus("bad", "Fehler im Browser-Test."); logLine("Unbekannter Fehler im Test.", "error"); } } finally { isRunning = false; tester.abortController = null; updateStartButtonState(); if (btnCancel) btnCancel.disabled = true; } }); } if (btnCancel) { btnCancel.addEventListener("click", () => { if (!isRunning || !tester.abortController) return; tester.abortController.abort(); }); } // --- Dashboard-Füllung -------------------------------------------------- function applyReportToDashboard(report) { const modeLabelMap = { quick: "Quick-Check", benchmark: "Benchmark", writeverify: "Write & Verify", all: "All-Inclusive (alle Browser-Tests)" }; if (resMode) { resMode.textContent = modeLabelMap[report.mode_requested] || report.mode_requested || "–"; } if (resDuration) { resDuration.textContent = formatDuration(report.total_duration_s); } let aggWriteBytes = 0; let aggReadBytes = 0; let writeSec = 0; let readSec = 0; if (report.quick) { aggWriteBytes += report.quick.write_bytes || 0; aggReadBytes += report.quick.read_bytes || 0; writeSec += report.quick.write_duration_s || 0; readSec += report.quick.read_duration_s || 0; } if (report.benchmark) { aggWriteBytes += report.benchmark.write_bytes || 0; aggReadBytes += report.benchmark.read_bytes || 0; writeSec += report.benchmark.write_duration_s || 0; readSec += report.benchmark.read_duration_s || 0; } if (report.writeverify) { aggWriteBytes += report.writeverify.written_bytes || 0; aggReadBytes += report.writeverify.verified_bytes || 0; writeSec += report.writeverify.write_duration_s || 0; readSec += report.writeverify.read_duration_s || 0; } if (resWritten) { resWritten.textContent = aggWriteBytes ? formatBytes(aggWriteBytes) : "–"; } if (resVerified) { resVerified.textContent = aggReadBytes ? formatBytes(aggReadBytes) : "–"; } const avgWriteMbps = (aggWriteBytes && writeSec) ? formatMbps(aggWriteBytes, writeSec) : "–"; const avgReadMbps = (aggReadBytes && readSec) ? formatMbps(aggReadBytes, readSec) : "–"; if (resWriteSpeed) resWriteSpeed.textContent = avgWriteMbps; if (resReadSpeed) resReadSpeed.textContent = avgReadMbps; } // --- Backend-Speicherung ------------------------------------------------ async function saveReportToBackend(report) { const url = "/api/result/browser-quick-test.php"; try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(report), credentials: "include" }); if (!response.ok) { const msg = "HTTP " + response.status; logLine("Backend: Konnte Ergebnis nicht speichern (" + msg + ").", "warn"); if (response.status >= 500 && saveError) { saveError.style.display = "block"; } return; } const data = await response.json().catch(() => null); logLine( "Backend: Testergebnis gespeichert" + (data && data.id ? ` (ID: ${data.id})` : ""), "info" ); } catch (err) { if (saveError) saveError.style.display = "block"; logLine("Fehler beim Speichern im Backend: " + (err ? err.message : "unbekannt"), "error"); } } // --- Initialzustand ----------------------------------------------------- setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); setModeLabel("Kein Modus selektiert"); setOverallStatus("ok", "Noch kein Test durchgeführt."); logLine("USB-Browser-Test (fakecheck) geladen. Warte auf Verzeichnisauswahl und Modus."); updateStartButtonState(); })();