(function () { const init = window.__TLS_INIT__; if (!init) return; const phaseEl = document.getElementById("tls-phase"); const barEl = document.getElementById("tls-progress-bar"); const logEl = document.getElementById("tls-log"); const verbEl = document.getElementById("tls-status-verb"); // Colors for the severity label tag. Order Good→Bad maps to // bright-{green,blue,yellow,red,purple}. Matches SEVERITY_COLORS in // schemas.py so the live log and results page render identically. const SEV_COLOR = { good: "bright-green", normal: "bright-blue", notgood: "bright-yellow", bad: "bright-red", serious: "bright-purple", info: "tx-alt", }; const SEV_LABEL = { good: "GOOD", normal: "NORMAL", notgood: "NOT GOOD", bad: "BAD", serious: "SERIOUS", info: "INFO", }; let reconnectAttempts = 0; const MAX_RECONNECTS = 3; let ws = null; let closedByDone = false; function normalizeStep(s) { // "handshake_sim" → "handshake-sim" for display consistency with the // server-rendered results page. return String(s || "info").replace(/_/g, "-").toLowerCase(); } function appendRow(category, body, detail, severity) { if (!logEl) return; const row = document.createElement("div"); row.className = "tls-log-row"; const cat = document.createElement("span"); cat.className = "tls-log-cat text-tx-alt"; cat.textContent = "[" + normalizeStep(category) + "]"; row.appendChild(cat); const msg = document.createElement("span"); msg.className = "tls-log-msg"; if (severity && SEV_LABEL[severity] && severity !== "info") { const sev = document.createElement("span"); sev.className = "text-" + (SEV_COLOR[severity] || "tx") + " font-bold"; sev.textContent = SEV_LABEL[severity]; msg.appendChild(sev); msg.appendChild(document.createTextNode(" ")); } // Title is always in the default text color (white). Severity is // communicated by the coloured label tag, not by tinting the title. const msgText = document.createElement("span"); msgText.className = "text-tx"; msgText.textContent = body || ""; msg.appendChild(msgText); row.appendChild(msg); const det = document.createElement("span"); det.className = "tls-log-detail text-tx-alt"; det.textContent = detail || ""; row.appendChild(det); logEl.appendChild(row); logEl.scrollTop = logEl.scrollHeight; } function setProgress(value, phaseText) { if (barEl) barEl.style.width = `${Math.max(0, Math.min(1, value)) * 100}%`; if (phaseEl && phaseText) phaseEl.textContent = phaseText; } function markDone(rank, score) { if (verbEl) verbEl.textContent = "完了"; if (barEl) barEl.style.width = "100%"; if (phaseEl) phaseEl.textContent = `完了 (ランク ${rank}, スコア ${score})`; } function connect() { try { ws = new WebSocket(init.wsUrl); } catch (e) { scheduleReconnect(); return; } ws.onmessage = (ev) => { let msg; try { msg = JSON.parse(ev.data); } catch (_) { return; } if (msg.type === "history") { (msg.entries || []).forEach((e) => appendRow(e.phase, e.detail, "", e.severity)); if (msg.status === "done") { closedByDone = true; location.replace(init.resultsUrl); } return; } if (msg.type === "progress") { // Phase-level status: single body text with no detail appendRow(msg.phase, msg.detail, "", msg.severity); if (typeof msg.progress === "number") { setProgress(msg.progress, msg.detail || msg.phase); } return; } if (msg.type === "finding") { const f = msg.finding || {}; // Findings: show severity tag + title as the body, and detail in column 3. // The "[step]" prefix uses the phase slug (e.g. "handshake-sim") // rather than the coarser group ("vulnerabilities"). const step = f.step || f.group || f.category || "info"; appendRow(step, f.title || "", f.detail || "", f.severity || "info"); return; } if (msg.type === "done") { closedByDone = true; markDone(msg.rank, msg.score); setTimeout(() => location.replace(msg.redirect || init.resultsUrl), 300); return; } if (msg.type === "error") { appendRow("error", msg.message || "engine failed", "", "serious"); return; } if (msg.type === "started") { appendRow("init", msg.target || "", "", "info"); return; } }; ws.onclose = () => { if (!closedByDone) scheduleReconnect(); }; ws.onerror = () => { try { ws.close(); } catch (_) {} }; } function scheduleReconnect() { if (closedByDone) return; if (reconnectAttempts >= MAX_RECONNECTS) { appendRow("ws", "WebSocket 接続が切断されました。ページをリロードしてください。", "", "bad"); return; } const delay = Math.min(10_000, 1000 * Math.pow(2, reconnectAttempts)); reconnectAttempts += 1; setTimeout(connect, delay); } window.addEventListener("beforeunload", () => { closedByDone = true; try { ws && ws.close(); } catch (_) {} }); connect(); })();