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[] = [
|
export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
||||||
{
|
{
|
||||||
id: 'default',
|
id: 'default',
|
||||||
url: 'https://nostr.build/api/v2/upload',
|
url: 'https://void.cat/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: false,
|
||||||
|
priority: 2,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_PLATFORM_LIGHTNING_ADDRESS = ''
|
export const DEFAULT_PLATFORM_LIGHTNING_ADDRESS = ''
|
||||||
|
|||||||
129
lib/nip95.ts
129
lib/nip95.ts
@ -1,5 +1,5 @@
|
|||||||
import type { MediaRef } from '@/types/nostr'
|
import type { MediaRef } from '@/types/nostr'
|
||||||
import { getPrimaryNip95Api } from './config'
|
import { getEnabledNip95Apis } from './config'
|
||||||
|
|
||||||
const MAX_IMAGE_BYTES = 5 * 1024 * 1024
|
const MAX_IMAGE_BYTES = 5 * 1024 * 1024
|
||||||
const MAX_VIDEO_BYTES = 45 * 1024 * 1024
|
const MAX_VIDEO_BYTES = 45 * 1024 * 1024
|
||||||
@ -29,40 +29,45 @@ function validateFile(file: File): MediaRef['type'] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload media via NIP-95.
|
* Parse upload response from different NIP-95 providers
|
||||||
* This implementation validates size/type then delegates to a pluggable uploader.
|
* Supports void.cat format: { ok: true, file: { id, url } } or { url: string }
|
||||||
* The actual upload endpoint must be provided via env/config; otherwise an error is thrown.
|
* Supports nostr.build format: { url: string }
|
||||||
*/
|
*/
|
||||||
export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
function parseUploadResponse(result: unknown, endpoint: string): string {
|
||||||
assertBrowser()
|
if (typeof result !== 'object' || result === null) {
|
||||||
const mediaType = validateFile(file)
|
throw new Error('Invalid upload response format')
|
||||||
|
|
||||||
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.'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = new FormData()
|
const obj = result as Record<string, unknown>
|
||||||
formData.append('file', file)
|
|
||||||
|
|
||||||
let response: Response
|
// void.cat format: { ok: true, file: { id, url } }
|
||||||
try {
|
if ('ok' in obj && obj.ok === true && 'file' in obj) {
|
||||||
response = await fetch(endpoint, {
|
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',
|
method: 'POST',
|
||||||
body: formData,
|
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}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let errorMessage = 'Upload failed'
|
let errorMessage = 'Upload failed'
|
||||||
@ -72,37 +77,61 @@ export async function uploadNip95Media(file: File): Promise<MediaRef> {
|
|||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
errorMessage = `HTTP ${response.status} ${response.statusText}`
|
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)
|
throw new Error(errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: { url?: string }
|
let result: unknown
|
||||||
try {
|
try {
|
||||||
result = (await response.json()) as { url?: string }
|
result = await response.json()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = e instanceof Error ? e.message : 'Invalid JSON response'
|
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}`)
|
throw new Error(`Invalid upload response: ${errorMessage}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.url) {
|
return parseUploadResponse(result, endpoint)
|
||||||
console.error('NIP-95 upload missing URL:', {
|
|
||||||
endpoint,
|
|
||||||
response: result,
|
|
||||||
})
|
|
||||||
throw new Error('Upload response missing URL')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { url: result.url, type: mediaType }
|
/**
|
||||||
|
* 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.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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