This repository has been archived on 2026-04-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
website/public/tools/tls-test/assets/tls-test.js
T
2026-04-19 18:54:47 +09:00

168 lines
5.9 KiB
JavaScript

(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();
})();