134 lines
5.8 KiB
TypeScript
134 lines
5.8 KiB
TypeScript
import type { NextApiResponse } from 'next'
|
|
import type { ProxyUploadResponse } from './types'
|
|
|
|
export function handleProxyResponse(params: { res: NextApiResponse; response: ProxyUploadResponse; targetEndpoint: string }): void {
|
|
if (params.response.statusCode < 200 || params.response.statusCode >= 300) {
|
|
respondNonOk({ res: params.res, response: params.response, targetEndpoint: params.targetEndpoint })
|
|
return
|
|
}
|
|
if (isHtmlResponse(params.response.body)) {
|
|
respondHtml({ res: params.res, response: params.response, targetEndpoint: params.targetEndpoint })
|
|
return
|
|
}
|
|
const parseResult = parseJsonSafe(params.response.body)
|
|
if (!parseResult.ok) {
|
|
console.error('NIP-95 proxy JSON parse error:', { targetEndpoint: params.targetEndpoint, bodyPreview: params.response.body.substring(0, 100) })
|
|
params.res.status(500).json({ error: 'Invalid upload response: Invalid JSON response. The endpoint may not be a valid NIP-95 upload endpoint.' })
|
|
return
|
|
}
|
|
params.res.status(200).json(parseResult.value)
|
|
}
|
|
|
|
function respondNonOk(params: { res: NextApiResponse; response: ProxyUploadResponse; targetEndpoint: string }): void {
|
|
const errorText = params.response.body.substring(0, 200)
|
|
console.error('NIP-95 proxy response error:', {
|
|
targetEndpoint: params.targetEndpoint,
|
|
finalUrl: params.response.finalUrl,
|
|
status: params.response.statusCode,
|
|
statusText: params.response.statusMessage,
|
|
errorText,
|
|
})
|
|
params.res.status(params.response.statusCode).json({ error: buildUserFriendlyHttpError(params.response.statusCode, params.response.statusMessage, errorText) })
|
|
}
|
|
|
|
function buildUserFriendlyHttpError(statusCode: number, statusMessage: string, errorText: string): string {
|
|
if (statusCode === 401) {
|
|
return 'Authentication required. This endpoint requires authorization headers.'
|
|
}
|
|
if (statusCode === 403) {
|
|
return 'Access forbidden. This endpoint may require authentication or have restrictions.'
|
|
}
|
|
if (statusCode === 405) {
|
|
return 'Method not allowed. This endpoint may not support POST requests or the URL may be incorrect.'
|
|
}
|
|
if (statusCode === 413) {
|
|
return 'File too large. The file exceeds the maximum size allowed by this endpoint.'
|
|
}
|
|
if (statusCode >= 500) {
|
|
return `Server error (${statusCode}). The endpoint server encountered an error.`
|
|
}
|
|
return errorText || `Upload failed: ${statusCode} ${statusMessage}`
|
|
}
|
|
|
|
export function isHtmlResponse(body: string): boolean {
|
|
const trimmedBody = body.trim()
|
|
return trimmedBody.startsWith('<!DOCTYPE') || trimmedBody.startsWith('<html') || trimmedBody.startsWith('<!')
|
|
}
|
|
|
|
function respondHtml(params: { res: NextApiResponse; response: ProxyUploadResponse; targetEndpoint: string }): void {
|
|
const title = readHtmlTitle(params.response.body)
|
|
const h1 = readHtmlH1(params.response.body)
|
|
const errorText = title ?? h1 ?? 'HTML error page returned'
|
|
const flags = detectHtmlErrorFlags({ body: params.response.body, title })
|
|
console.error('NIP-95 proxy HTML response error:', {
|
|
targetEndpoint: params.targetEndpoint,
|
|
finalUrl: params.response.finalUrl,
|
|
status: params.response.statusCode,
|
|
errorText,
|
|
is404: flags.is404,
|
|
is403: flags.is403,
|
|
is500: flags.is500,
|
|
bodyPreview: params.response.body.substring(0, 500),
|
|
contentType: 'HTML (expected JSON)',
|
|
suggestion: buildHtmlErrorSuggestion(flags),
|
|
})
|
|
params.res.status(500).json({ error: buildHtmlUserMessage({ ...flags, finalUrl: params.response.finalUrl, errorText }) })
|
|
}
|
|
|
|
function buildHtmlUserMessage(params: { is404: boolean; is403: boolean; is500: boolean; finalUrl: string; errorText: string }): string {
|
|
if (params.is404) {
|
|
return `Endpoint not found (404). The URL may be incorrect: ${params.finalUrl}`
|
|
}
|
|
if (params.is403) {
|
|
return 'Access forbidden (403). The endpoint may require authentication or have restrictions.'
|
|
}
|
|
if (params.is500) {
|
|
return 'Server error (500). The endpoint server encountered an error.'
|
|
}
|
|
return `Endpoint returned an HTML error page instead of JSON. The endpoint may be unavailable, the URL may be incorrect, or specific headers may be required. Error: ${params.errorText}`
|
|
}
|
|
|
|
type JsonParseResult = { ok: true; value: unknown } | { ok: false }
|
|
|
|
function parseJsonSafe(body: string): JsonParseResult {
|
|
try {
|
|
return { ok: true, value: JSON.parse(body) as unknown }
|
|
} catch {
|
|
return { ok: false }
|
|
}
|
|
}
|
|
|
|
function readHtmlTitle(body: string): string | undefined {
|
|
return body.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]
|
|
}
|
|
|
|
function readHtmlH1(body: string): string | undefined {
|
|
return body.match(/<h1[^>]*>([^<]+)<\/h1>/i)?.[1]
|
|
}
|
|
|
|
function detectHtmlErrorFlags(params: { body: string; title: string | undefined }): { is404: boolean; is403: boolean; is500: boolean } {
|
|
const is404 = isHtmlErrorForCode({ body: params.body, title: params.title, code: '404', marker: 'Not Found' })
|
|
const is403 = isHtmlErrorForCode({ body: params.body, title: params.title, code: '403', marker: 'Forbidden' })
|
|
const is500 = isHtmlErrorForCode({ body: params.body, title: params.title, code: '500', marker: 'Internal Server Error' })
|
|
return { is404, is403, is500 }
|
|
}
|
|
|
|
function isHtmlErrorForCode(params: { body: string; title: string | undefined; code: string; marker: string }): boolean {
|
|
const titleHasCode = params.title?.includes(params.code) === true
|
|
return params.body.includes(params.code) || params.body.includes(params.marker) || titleHasCode
|
|
}
|
|
|
|
function buildHtmlErrorSuggestion(params: { is404: boolean; is403: boolean; is500: boolean }): string {
|
|
if (params.is404) {
|
|
return 'The endpoint URL may be incorrect or the endpoint does not exist'
|
|
}
|
|
if (params.is403) {
|
|
return 'The endpoint may require authentication or have restrictions'
|
|
}
|
|
if (params.is500) {
|
|
return 'The endpoint server encountered an internal error'
|
|
}
|
|
return 'The endpoint returned HTML instead of JSON; verify URL and required headers'
|
|
}
|
|
|