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[] = [
|
export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
||||||
{
|
{
|
||||||
id: 'voidcat',
|
id: 'nostrimg',
|
||||||
url: 'https://void.cat/upload',
|
url: 'https://nostrimg.com/api/upload',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
priority: 1,
|
priority: 1,
|
||||||
createdAt: Date.now(),
|
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',
|
id: 'nostrcheck',
|
||||||
url: 'https://nostrcheck.me/api/v1/media',
|
url: 'https://nostrcheck.me/api/v1/media',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
priority: 4,
|
priority: 2,
|
||||||
createdAt: Date.now(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'nostrimg',
|
|
||||||
url: 'https://nostrimg.com/api/upload',
|
|
||||||
enabled: true,
|
|
||||||
priority: 5,
|
|
||||||
createdAt: Date.now(),
|
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' })
|
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
|
// Forward to target endpoint using https/http native modules
|
||||||
const targetUrl = new URL(targetEndpoint)
|
// Support redirects (301, 302, 307, 308)
|
||||||
const isHttps = targetUrl.protocol === 'https:'
|
const currentUrl = new URL(targetEndpoint)
|
||||||
const clientModule = isHttps ? https : http
|
const MAX_REDIRECTS = 5
|
||||||
|
|
||||||
let response: { statusCode: number; statusMessage: string; body: string }
|
let response: { statusCode: number; statusMessage: string; body: string }
|
||||||
try {
|
try {
|
||||||
response = await new Promise<{ statusCode: number; statusMessage: string; body: string }>((resolve, reject) => {
|
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 = {
|
const requestOptions = {
|
||||||
hostname: targetUrl.hostname,
|
hostname: url.hostname,
|
||||||
port: targetUrl.port || (isHttps ? 443 : 80),
|
port: url.port || (isHttps ? 443 : 80),
|
||||||
path: targetUrl.pathname + targetUrl.search,
|
path: url.pathname + url.search,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: headers,
|
headers: headers,
|
||||||
timeout: 30000, // 30 seconds timeout
|
timeout: 30000, // 30 seconds timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => {
|
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 = ''
|
let body = ''
|
||||||
proxyResponse.setEncoding('utf8')
|
proxyResponse.setEncoding('utf8')
|
||||||
proxyResponse.on('data', (chunk) => {
|
proxyResponse.on('data', (chunk) => {
|
||||||
@ -89,7 +126,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
})
|
})
|
||||||
proxyResponse.on('end', () => {
|
proxyResponse.on('end', () => {
|
||||||
resolve({
|
resolve({
|
||||||
statusCode: proxyResponse.statusCode || 500,
|
statusCode: statusCode,
|
||||||
statusMessage: proxyResponse.statusMessage || 'Internal Server Error',
|
statusMessage: proxyResponse.statusMessage || 'Internal Server Error',
|
||||||
body: body,
|
body: body,
|
||||||
})
|
})
|
||||||
@ -111,22 +148,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
if (errorCode === 'ENOTFOUND' || errorCode === 'EAI_AGAIN') {
|
if (errorCode === 'ENOTFOUND' || errorCode === 'EAI_AGAIN') {
|
||||||
console.error('NIP-95 proxy DNS error:', {
|
console.error('NIP-95 proxy DNS error:', {
|
||||||
targetEndpoint,
|
targetEndpoint,
|
||||||
hostname: targetUrl.hostname,
|
hostname: url.hostname,
|
||||||
errorCode,
|
errorCode,
|
||||||
errorMessage: error.message,
|
errorMessage: error.message,
|
||||||
suggestion: 'Check DNS resolution or network connectivity on the server',
|
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 {
|
} else {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
formData.on('error', (error) => {
|
requestFormData.on('error', (error) => {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
|
|
||||||
formData.pipe(proxyRequest)
|
requestFormData.pipe(proxyRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
makeRequest(currentUrl, 0, fileField)
|
||||||
})
|
})
|
||||||
} catch (requestError) {
|
} catch (requestError) {
|
||||||
// Clean up temporary file before returning error
|
// 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:', {
|
console.error('NIP-95 proxy request error:', {
|
||||||
targetEndpoint,
|
targetEndpoint,
|
||||||
hostname: targetUrl.hostname,
|
hostname: currentUrl.hostname,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
isDnsError,
|
isDnsError,
|
||||||
fileSize: fileField.size,
|
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
|
// Return a more specific error message for DNS issues
|
||||||
if (isDnsError) {
|
if (isDnsError) {
|
||||||
return res.status(500).json({
|
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
|
const errorText = response.body.substring(0, 200) // Limit log size
|
||||||
console.error('NIP-95 proxy response error:', {
|
console.error('NIP-95 proxy response error:', {
|
||||||
targetEndpoint,
|
targetEndpoint,
|
||||||
|
finalUrl: currentUrl.toString(),
|
||||||
status: response.statusCode,
|
status: response.statusCode,
|
||||||
statusText: response.statusMessage,
|
statusText: response.statusMessage,
|
||||||
errorText: errorText,
|
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({
|
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