| 1 | import type { HandshakeFailure } from "./handshake.js"; |
| 2 | |
| 3 | export const SHELL_ACTION_PREFIX = "reasonix://app/__shell/"; |
| 4 | |
| 5 | export type ShellAction = "open-logs" | "restart" | "quit"; |
| 6 | |
| 7 | export function shellActionFromURL(url: string): ShellAction | null { |
| 8 | if (!url.startsWith(SHELL_ACTION_PREFIX)) return null; |
| 9 | const action = url.slice(SHELL_ACTION_PREFIX.length).replace(/[?#].*$/, ""); |
| 10 | return action === "open-logs" || action === "restart" || action === "quit" ? action : null; |
| 11 | } |
| 12 | |
| 13 | function escapeHTML(value: string): string { |
| 14 | return value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] as string); |
| 15 | } |
| 16 | |
| 17 | export function renderFailurePage(failure: HandshakeFailure, logsPath: string): string { |
| 18 | const code = failure.code === null ? failure.name : `${failure.name} (${failure.code})`; |
| 19 | return `<!doctype html> |
| 20 | <html lang="en"> |
| 21 | <head> |
| 22 | <meta charset="utf-8"> |
| 23 | <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'"> |
| 24 | <title>${failure.name === "starting" ? "Reasonix is starting" : "Reasonix cannot start"}</title> |
| 25 | <style> |
| 26 | html, body { margin: 0; height: 100%; background: #1a1a2e; color: #f4f4f3; font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } |
| 27 | main { max-width: 640px; margin: 0 auto; padding: 64px 32px; } |
| 28 | h1 { font-size: 20px; margin: 0 0 12px; } |
| 29 | .code { color: #b8b8c8; font-size: 12px; margin-bottom: 20px; } |
| 30 | pre { white-space: pre-wrap; word-break: break-word; background: #11111f; border: 1px solid #2c2c48; border-radius: 8px; padding: 12px 14px; margin: 0 0 20px; } |
| 31 | .actions { display: flex; gap: 10px; flex-wrap: wrap; } |
| 32 | a { display: inline-block; padding: 8px 14px; border-radius: 8px; background: #2c2c48; color: inherit; text-decoration: none; } |
| 33 | a.primary { background: #e58a3a; color: #111214; } |
| 34 | .logs { color: #8f8fa8; font-size: 12px; margin-top: 20px; } |
| 35 | </style> |
| 36 | </head> |
| 37 | <body> |
| 38 | <main> |
| 39 | <h1>${escapeHTML(failure.title)}</h1> |
| 40 | <div class="code">${escapeHTML(code)}</div> |
| 41 | <pre>${escapeHTML(failure.detail)}</pre> |
| 42 | <div class="actions"> |
| 43 | ${failure.name === "starting" ? "" : failure.name === "build_mismatch" || failure.name === "contract_mismatch" ? '<p>Install the full Reasonix package to repair this installation. / 请安装完整包恢复启动,无需清理会话。</p>' : `<a class="primary" href="${SHELL_ACTION_PREFIX}restart">Retry / 重试</a>`} |
| 44 | <a href="${SHELL_ACTION_PREFIX}open-logs">Open logs folder</a> |
| 45 | <a href="${SHELL_ACTION_PREFIX}quit">Quit</a> |
| 46 | </div> |
| 47 | <div class="logs">Logs: ${escapeHTML(logsPath)}</div> |
| 48 | </main> |
| 49 | </body> |
| 50 | </html>`; |
| 51 | } |
| 52 |