(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 SEV_COLOR = { good: "bright-green", normal: "bright-yellow", notgood: "bright-orange", bad: "bright-red", serious: "magenta", info: "tx", }; let reconnectAttempts = 0; const MAX_RECONNECTS = 3; let ws = null; let closedByDone = false; function appendLog(phase, detail, severity) { if (!logEl) return; const row = document.createElement("div"); row.className = "tls-log-row"; const label = document.createElement("span"); label.className = "text-tx-alt font-small"; label.textContent = `[${phase}] `; const msg = document.createElement("span"); msg.className = `text-${SEV_COLOR[severity] || "tx"}`; msg.textContent = detail || ""; row.appendChild(label); row.appendChild(msg); logEl.appendChild(row); logEl.scrollTop = logEl.scrollHeight; } function setProgress(value, phase) { if (barEl) barEl.style.width = `${Math.max(0, Math.min(1, value)) * 100}%`; if (phaseEl && phase) phaseEl.textContent = phase; } 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) => appendLog(e.phase, e.detail, e.severity)); if (msg.status === "done") { closedByDone = true; location.replace(init.resultsUrl); } return; } if (msg.type === "progress") { appendLog(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 || {}; appendLog(f.category || "finding", `${f.severity_label || ""} ${f.title || ""} ${f.detail ? "— " + f.detail : ""}`.trim(), f.severity || "info"); return; } if (msg.type === "done") { closedByDone = true; setProgress(1.0, `done (rank ${msg.rank}, score ${msg.score})`); location.replace(msg.redirect || init.resultsUrl); return; } if (msg.type === "error") { appendLog("error", msg.message || "engine failed", "serious"); return; } if (msg.type === "started") { appendLog("started", msg.target || "", "info"); return; } }; ws.onclose = () => { if (!closedByDone) scheduleReconnect(); }; ws.onerror = () => { try { ws.close(); } catch (_) {} }; } function scheduleReconnect() { if (closedByDone) return; if (reconnectAttempts >= MAX_RECONNECTS) { appendLog("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(); })();