Fix: NIP-95 upload 500 error
This commit is contained in:
parent
a90b77cec3
commit
d8311078bc
@ -40,38 +40,17 @@ export const DEFAULT_RELAYS: RelayConfig[] = [
|
||||
|
||||
export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
||||
{
|
||||
id: 'voidcat',
|
||||
url: 'https://void.cat/upload',
|
||||
id: 'nostrimg',
|
||||
url: 'https://nostrimg.com/api/upload',
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'nostrbuild',
|
||||
url: 'https://nostr.build/api/v2/upload',
|
||||
enabled: true,
|
||||
priority: 2,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'picstr',
|
||||
url: 'https://picstr.build/api/v1/upload',
|
||||
enabled: true,
|
||||
priority: 3,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'nostrcheck',
|
||||
url: 'https://nostrcheck.me/api/v1/media',
|
||||
enabled: true,
|
||||
priority: 4,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'nostrimg',
|
||||
url: 'https://nostrimg.com/api/upload',
|
||||
enabled: true,
|
||||
priority: 5,
|
||||
priority: 2,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
@ -55,33 +55,70 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ error: 'No file provided' })
|
||||
}
|
||||
|
||||
// Create FormData for the target endpoint
|
||||
const formData = new FormData()
|
||||
const fileStream = fs.createReadStream(fileField.filepath)
|
||||
formData.append('file', fileStream, {
|
||||
filename: fileField.originalFilename || fileField.newFilename || 'upload',
|
||||
contentType: fileField.mimetype || 'application/octet-stream',
|
||||
})
|
||||
|
||||
// Forward to target endpoint using https/http native modules
|
||||
const targetUrl = new URL(targetEndpoint)
|
||||
const isHttps = targetUrl.protocol === 'https:'
|
||||
const clientModule = isHttps ? https : http
|
||||
// Support redirects (301, 302, 307, 308)
|
||||
const currentUrl = new URL(targetEndpoint)
|
||||
const MAX_REDIRECTS = 5
|
||||
|
||||
let response: { statusCode: number; statusMessage: string; body: string }
|
||||
try {
|
||||
response = await new Promise<{ statusCode: number; statusMessage: string; body: string }>((resolve, reject) => {
|
||||
const headers = formData.getHeaders()
|
||||
function makeRequest(url: URL, redirectCount: number, fileField: FormidableFile): void {
|
||||
if (redirectCount > MAX_REDIRECTS) {
|
||||
reject(new Error(`Too many redirects (max ${MAX_REDIRECTS})`))
|
||||
return
|
||||
}
|
||||
|
||||
// Recreate FormData for each request (needed for redirects)
|
||||
const requestFormData = new FormData()
|
||||
const fileStream = fs.createReadStream(fileField.filepath)
|
||||
requestFormData.append('file', fileStream, {
|
||||
filename: fileField.originalFilename || fileField.newFilename || 'upload',
|
||||
contentType: fileField.mimetype || 'application/octet-stream',
|
||||
})
|
||||
|
||||
const isHttps = url.protocol === 'https:'
|
||||
const clientModule = isHttps ? https : http
|
||||
const headers = requestFormData.getHeaders()
|
||||
const requestOptions = {
|
||||
hostname: targetUrl.hostname,
|
||||
port: targetUrl.port || (isHttps ? 443 : 80),
|
||||
path: targetUrl.pathname + targetUrl.search,
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
timeout: 30000, // 30 seconds timeout
|
||||
}
|
||||
|
||||
const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => {
|
||||
// Handle redirects (301, 302, 307, 308)
|
||||
const statusCode = proxyResponse.statusCode || 500
|
||||
if ((statusCode === 301 || statusCode === 302 || statusCode === 307 || statusCode === 308) && proxyResponse.headers.location) {
|
||||
const location = proxyResponse.headers.location
|
||||
let redirectUrl: URL
|
||||
try {
|
||||
// Handle relative and absolute URLs
|
||||
redirectUrl = new URL(location, url.toString())
|
||||
console.log('NIP-95 proxy redirect:', {
|
||||
from: url.toString(),
|
||||
to: redirectUrl.toString(),
|
||||
statusCode,
|
||||
redirectCount: redirectCount + 1,
|
||||
})
|
||||
// Drain the response before redirecting
|
||||
proxyResponse.resume()
|
||||
// Make new request to redirect location
|
||||
makeRequest(redirectUrl, redirectCount + 1, fileField)
|
||||
return
|
||||
} catch (urlError) {
|
||||
console.error('NIP-95 proxy invalid redirect URL:', {
|
||||
location,
|
||||
error: urlError instanceof Error ? urlError.message : 'Unknown error',
|
||||
})
|
||||
reject(new Error(`Invalid redirect URL: ${location}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let body = ''
|
||||
proxyResponse.setEncoding('utf8')
|
||||
proxyResponse.on('data', (chunk) => {
|
||||
@ -89,7 +126,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
})
|
||||
proxyResponse.on('end', () => {
|
||||
resolve({
|
||||
statusCode: proxyResponse.statusCode || 500,
|
||||
statusCode: statusCode,
|
||||
statusMessage: proxyResponse.statusMessage || 'Internal Server Error',
|
||||
body: body,
|
||||
})
|
||||
@ -111,22 +148,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
if (errorCode === 'ENOTFOUND' || errorCode === 'EAI_AGAIN') {
|
||||
console.error('NIP-95 proxy DNS error:', {
|
||||
targetEndpoint,
|
||||
hostname: targetUrl.hostname,
|
||||
hostname: url.hostname,
|
||||
errorCode,
|
||||
errorMessage: error.message,
|
||||
suggestion: 'Check DNS resolution or network connectivity on the server',
|
||||
})
|
||||
reject(new Error(`DNS resolution failed for ${targetUrl.hostname}: ${error.message}`))
|
||||
reject(new Error(`DNS resolution failed for ${url.hostname}: ${error.message}`))
|
||||
} else {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
formData.on('error', (error) => {
|
||||
requestFormData.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
formData.pipe(proxyRequest)
|
||||
requestFormData.pipe(proxyRequest)
|
||||
}
|
||||
|
||||
makeRequest(currentUrl, 0, fileField)
|
||||
})
|
||||
} catch (requestError) {
|
||||
// Clean up temporary file before returning error
|
||||
@ -140,7 +180,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
console.error('NIP-95 proxy request error:', {
|
||||
targetEndpoint,
|
||||
hostname: targetUrl.hostname,
|
||||
hostname: currentUrl.hostname,
|
||||
error: errorMessage,
|
||||
isDnsError,
|
||||
fileSize: fileField.size,
|
||||
@ -151,7 +191,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
// Return a more specific error message for DNS issues
|
||||
if (isDnsError) {
|
||||
return res.status(500).json({
|
||||
error: `DNS resolution failed for ${targetUrl.hostname}. The server cannot resolve the domain name. Please check DNS configuration and network connectivity.`,
|
||||
error: `DNS resolution failed for ${currentUrl.hostname}. The server cannot resolve the domain name. Please check DNS configuration and network connectivity.`,
|
||||
})
|
||||
}
|
||||
|
||||
@ -171,12 +211,28 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const errorText = response.body.substring(0, 200) // Limit log size
|
||||
console.error('NIP-95 proxy response error:', {
|
||||
targetEndpoint,
|
||||
finalUrl: currentUrl.toString(),
|
||||
status: response.statusCode,
|
||||
statusText: response.statusMessage,
|
||||
errorText: errorText,
|
||||
})
|
||||
|
||||
// Provide more specific error messages for common HTTP status codes
|
||||
let userFriendlyError = errorText || `Upload failed: ${response.statusCode} ${response.statusMessage}`
|
||||
if (response.statusCode === 401) {
|
||||
userFriendlyError = 'Authentication required. This endpoint requires authorization headers.'
|
||||
} else if (response.statusCode === 403) {
|
||||
userFriendlyError = 'Access forbidden. This endpoint may require authentication or have restrictions.'
|
||||
} else if (response.statusCode === 405) {
|
||||
userFriendlyError = 'Method not allowed. This endpoint may not support POST requests or the URL may be incorrect.'
|
||||
} else if (response.statusCode === 413) {
|
||||
userFriendlyError = 'File too large. The file exceeds the maximum size allowed by this endpoint.'
|
||||
} else if (response.statusCode >= 500) {
|
||||
userFriendlyError = `Server error (${response.statusCode}). The endpoint server encountered an error.`
|
||||
}
|
||||
|
||||
return res.status(response.statusCode).json({
|
||||
error: errorText || `Upload failed: ${response.statusCode} ${response.statusMessage}`,
|
||||
error: userFriendlyError,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user