import { limitErrorText, readErrorString } from './httpUtils'; export function formatAxureProxyErrorDetails(error: any): string { const parts: string[] = []; const message = readErrorString(error?.message); const causeMessage = readErrorString(error?.cause?.message); const code = readErrorString(error?.code) || readErrorString(error?.cause?.code); const errno = readErrorString(error?.errno) || readErrorString(error?.cause?.errno); const syscall = readErrorString(error?.syscall) || readErrorString(error?.cause?.syscall); const address = readErrorString(error?.address) || readErrorString(error?.cause?.address); const port = typeof error?.port === 'number' ? String(error.port) : typeof error?.cause?.port === 'number' ? String(error.cause.port) : ''; if (message) { parts.push(message); } if (causeMessage && causeMessage !== message) { parts.push(`cause=${causeMessage}`); } if (code) { parts.push(`code=${code}`); } if (errno && errno !== code) { parts.push(`errno=${errno}`); } if (syscall) { parts.push(`syscall=${syscall}`); } if (address) { parts.push(`address=${address}`); } if (port) { parts.push(`port=${port}`); } return parts.join('; ') || 'Unknown upstream error'; } export function normalizeAxvgPayloadText(rawBody: string): string { const source = rawBody.trim(); if (!source) { return '// axvg\n{}'; } if (source.startsWith('// axvg')) { return source; } return `// axvg\n${source}`; } export function buildAxureBridgeUnavailablePayload(params: { route: string; method: string; bridgeUrl: string; payloadBytes?: number; error?: any; status?: number; statusText?: string; responseText?: string; }) { const errorCode = readErrorString(params.error?.code) || readErrorString(params.error?.cause?.code) || undefined; const errorMessage = readErrorString(params.error?.message) || readErrorString(params.responseText) || (typeof params.status === 'number' ? `Axure Bridge unavailable (HTTP ${params.status})` : 'Axure Bridge unavailable'); const details = params.error ? formatAxureProxyErrorDetails(params.error) : limitErrorText(readErrorString(params.responseText), 800) || undefined; return { available: false, running: false, success: false, error: errorMessage, details, code: errorCode, route: params.route, method: params.method, bridgeUrl: params.bridgeUrl, payloadBytes: params.payloadBytes || undefined, status: typeof params.status === 'number' ? params.status : undefined, statusText: readErrorString(params.statusText) || undefined, }; } export function isLoopbackOrPrivateHostname(hostname: string): boolean { const normalized = String(hostname || '').trim().toLowerCase(); if (!normalized) { return true; } if ( normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '0.0.0.0' || normalized === '::1' || normalized === '[::1]' ) { return true; } if (/^127\./.test(normalized)) { return true; } if (/^10\./.test(normalized)) { return true; } if (/^192\.168\./.test(normalized)) { return true; } if (/^169\.254\./.test(normalized)) { return true; } const match172 = normalized.match(/^172\.(\d{1,3})\./); if (match172) { const secondOctet = Number(match172[1]); if (secondOctet >= 16 && secondOctet <= 31) { return true; } } return false; } export function isAllowedProxyImageUrl(rawUrl: string): boolean { let parsedUrl: URL; try { parsedUrl = new URL(rawUrl); } catch { return false; } if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { return false; } if (isLoopbackOrPrivateHostname(parsedUrl.hostname)) { return false; } return true; } export { limitErrorText };