import type { ApiErrorResponse, CheckFailure, CheckResult, HealthResponse, RuntimeMode } from "../shared/api"; import type { StoredCheckResult } from "./checker/types"; export function createApiError(error: string, status: number): ApiErrorResponse { return { error, status }; } export function createHeaders(mode: RuntimeMode, init: HeadersInit): Headers { const headers = new Headers(init); if (mode === "production") { headers.set("X-Content-Type-Options", "nosniff"); headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); } return headers; } export function jsonResponse( body: unknown, options: { method?: string; mode: RuntimeMode; status?: number; headers?: HeadersInit }, ): Response { const headers = createHeaders(options.mode, { "Content-Type": "application/json; charset=utf-8", ...options.headers, }); const responseBody = options.method === "HEAD" ? null : JSON.stringify(body); return new Response(responseBody, { status: options.status, headers, }); } export function methodNotAllowedResponse(allow: string[], mode: RuntimeMode): Response { return jsonResponse(createApiError("Method not allowed", 405), { mode, status: 405, headers: { Allow: allow.join(", ") }, }); } export function allowsGetHead(method: string): boolean { return method === "GET" || method === "HEAD"; } export function formatDuration(ms: number): string { if (ms >= 60000 && ms % 60000 === 0) return `${ms / 60000}m`; if (ms >= 1000 && ms % 1000 === 0) return `${ms / 1000}s`; return `${ms}ms`; } export function mapCheckResult(row: StoredCheckResult): CheckResult { let failure: CheckFailure | null = null; if (row.failure) { try { failure = JSON.parse(row.failure) as CheckFailure; } catch { console.warn(`无法解析 failure 数据: target_id=${row.target_id}, timestamp=${row.timestamp}`); failure = null; } } return { timestamp: row.timestamp, matched: row.matched === 1, durationMs: row.duration_ms, statusDetail: row.status_detail, failure, }; } export function createHealthResponse(): HealthResponse { return { ok: true, service: "dial-server", timestamp: new Date().toISOString(), }; }