export interface TunnelErrorPageOptions { kind: "access-denied" | "upstream-unavailable"; incidentId: string; timestamp: string; tunnelName: string; method: string; path: string; clientIp: string; cloudflareRay: string; target?: string; errorCode?: string; errorMessage?: string; } function escapeHtml(value: string): string { return value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function diagnosticText(options: TunnelErrorPageOptions): string { const lines = [ `Tunnel incident: ${options.incidentId}`, `Time: ${options.timestamp}`, `Problem: ${options.kind}`, `Tunnel: ${options.tunnelName}`, `Request: ${options.method} ${options.path}`, `Client IP: ${options.clientIp || "(not provided by proxy)"}`, `Cloudflare Ray: ${options.cloudflareRay || "(not provided)"}`, ]; if (options.target) lines.push(`Upstream: ${options.target}`); if (options.errorCode) lines.push(`Error code: ${options.errorCode}`); if (options.errorMessage) lines.push(`Error: ${options.errorMessage}`); return lines.join("\n"); } export function renderTunnelErrorPage(options: TunnelErrorPageOptions): string { const denied = options.kind === "access-denied"; const title = denied ? "This device is not allowed yet" : "The preview is temporarily offline"; const summary = denied ? "The tunnel is running, but this device's public IP is not on its access list." : "The public tunnel and access check are working, but the local preview server is not responding."; const nextStep = denied ? "Copy the diagnostic below and send it to the person running the tunnel. It includes the public IP that needs to be allowed." : "Copy the diagnostic below and send it to the person running the tunnel. The incident ID can be matched to the server log."; const diagnostic = diagnosticText(options); const safeDiagnosticForScript = JSON.stringify(diagnostic).replaceAll("<", "\\u003c"); return ` ${escapeHtml(title)}

Tunnel diagnostic

${escapeHtml(title)}

${escapeHtml(summary)}

${escapeHtml(nextStep)}

${escapeHtml(diagnostic)}

Incident ${escapeHtml(options.incidentId)}

`; }