Fix CORS issue by changing default NIP-95 endpoint to void.cat and adding fallback to try multiple endpoints
This commit is contained in:
parent
e5a95bc627
commit
333c8e4d7f
@ -41,11 +41,18 @@ export const DEFAULT_RELAYS: RelayConfig[] = [
|
||||
export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
||||
{
|
||||
id: 'default',
|
||||
url: 'https://nostr.build/api/v2/upload',
|
||||
url: 'https://void.cat/upload',
|
||||
enabled: true,
|
||||
priority: 1,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
{
|
||||
id: 'nostrbuild',
|
||||
url: 'https://nostr.build/api/v2/upload',
|
||||
enabled: false,
|
||||
priority: 2,
|
||||
createdAt: Date.now(),
|
||||
},
|
||||
]
|
||||
|
||||
export const DEFAULT_PLATFORM_LIGHTNING_ADDRESS = ''
|
||||
|
||||
133
lib/nip95.ts
133
lib/nip95.ts
@ -1,5 +1,5 @@
|
||||
import type { MediaRef } from '@/types/nostr'
|
||||
import { getPrimaryNip95Api } from './config'
|
||||
import { getEnabledNip95Apis } from './config'
|
||||
|
||||
const MAX_IMAGE_BYTES = 5 * 1024 * 1024
|
||||
const MAX_VIDEO_BYTES = 45 * 1024 * 1024
|
||||
@ -29,41 +29,46 @@ function validateFile(file: File): MediaRef['type'] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload media via NIP-95.
|
||||
* This implementation validates size/type then delegates to a pluggable uploader.
|
||||
* The actual upload endpoint must be provided via env/config; otherwise an error is thrown.
|
||||
* Parse upload response from different NIP-95 providers
|
||||
* Supports void.cat format: { ok: true, file: { id, url } } or { url: string }
|
||||
* Supports nostr.build format: { url: string }
|
||||
*/
|
||||
export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
||||
assertBrowser()
|
||||
const mediaType = validateFile(file)
|
||||
|
||||
const endpoint = await getPrimaryNip95Api()
|
||||
if (!endpoint) {
|
||||
throw new Error(
|
||||
'NIP-95 upload endpoint is not configured. Please configure a NIP-95 API endpoint in the application settings.'
|
||||
)
|
||||
function parseUploadResponse(result: unknown, endpoint: string): string {
|
||||
if (typeof result !== 'object' || result === null) {
|
||||
throw new Error('Invalid upload response format')
|
||||
}
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const obj = result as Record<string, unknown>
|
||||
|
||||
let response: Response
|
||||
try {
|
||||
response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Network error'
|
||||
console.error('NIP-95 upload fetch error:', {
|
||||
endpoint,
|
||||
error: errorMessage,
|
||||
fileSize: file.size,
|
||||
fileType: file.type,
|
||||
})
|
||||
throw new Error(`Failed to fetch upload endpoint: ${errorMessage}`)
|
||||
// void.cat format: { ok: true, file: { id, url } }
|
||||
if ('ok' in obj && obj.ok === true && 'file' in obj) {
|
||||
const file = obj.file as Record<string, unknown>
|
||||
if (typeof file.url === 'string') {
|
||||
return file.url
|
||||
}
|
||||
}
|
||||
|
||||
// Standard format: { url: string }
|
||||
if ('url' in obj && typeof obj.url === 'string') {
|
||||
return obj.url
|
||||
}
|
||||
|
||||
console.error('NIP-95 upload missing URL:', {
|
||||
endpoint,
|
||||
response: result,
|
||||
})
|
||||
throw new Error('Upload response missing URL')
|
||||
}
|
||||
|
||||
/**
|
||||
* Try uploading to a single endpoint
|
||||
*/
|
||||
async function tryUploadEndpoint(endpoint: string, formData: FormData): Promise<string> {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = 'Upload failed'
|
||||
try {
|
||||
@ -72,37 +77,61 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
||||
} catch (_e) {
|
||||
errorMessage = `HTTP ${response.status} ${response.statusText}`
|
||||
}
|
||||
console.error('NIP-95 upload response error:', {
|
||||
endpoint,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
errorMessage,
|
||||
fileSize: file.size,
|
||||
fileType: file.type,
|
||||
})
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
let result: { url?: string }
|
||||
let result: unknown
|
||||
try {
|
||||
result = (await response.json()) as { url?: string }
|
||||
result = await response.json()
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Invalid JSON response'
|
||||
console.error('NIP-95 upload JSON parse error:', {
|
||||
endpoint,
|
||||
error: errorMessage,
|
||||
status: response.status,
|
||||
})
|
||||
throw new Error(`Invalid upload response: ${errorMessage}`)
|
||||
}
|
||||
|
||||
if (!result.url) {
|
||||
console.error('NIP-95 upload missing URL:', {
|
||||
endpoint,
|
||||
response: result,
|
||||
})
|
||||
throw new Error('Upload response missing URL')
|
||||
return parseUploadResponse(result, endpoint)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload media via NIP-95.
|
||||
* Tries all enabled endpoints in order until one succeeds.
|
||||
* This implementation validates size/type then delegates to a pluggable uploader.
|
||||
*/
|
||||
export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
||||
assertBrowser()
|
||||
const mediaType = validateFile(file)
|
||||
|
||||
const endpoints = await getEnabledNip95Apis()
|
||||
if (endpoints.length === 0) {
|
||||
throw new Error(
|
||||
'NIP-95 upload endpoint is not configured. Please configure a NIP-95 API endpoint in the application settings.'
|
||||
)
|
||||
}
|
||||
|
||||
return { url: result.url, type: mediaType }
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
let lastError: Error | null = null
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
const url = await tryUploadEndpoint(endpoint, formData)
|
||||
return { url, type: mediaType }
|
||||
} catch (e) {
|
||||
const error = e instanceof Error ? e : new Error(String(e))
|
||||
const errorMessage = error.message
|
||||
console.error('NIP-95 upload endpoint error:', {
|
||||
endpoint,
|
||||
error: errorMessage,
|
||||
fileSize: file.size,
|
||||
fileType: file.type,
|
||||
})
|
||||
lastError = error
|
||||
// Continue to next endpoint
|
||||
}
|
||||
}
|
||||
|
||||
// All endpoints failed
|
||||
if (lastError) {
|
||||
throw new Error(`Failed to upload to all endpoints: ${lastError.message}`)
|
||||
}
|
||||
throw new Error('Failed to upload: no endpoints available')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user