Fix: NIP-95 upload 500 error
This commit is contained in:
parent
065ab30828
commit
a90b77cec3
284
components/Nip95ConfigManager.tsx
Normal file
284
components/Nip95ConfigManager.tsx
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { configStorage } from '@/lib/configStorage'
|
||||||
|
import type { Nip95Config } from '@/lib/configStorageTypes'
|
||||||
|
|
||||||
|
interface Nip95ConfigManagerProps {
|
||||||
|
onConfigChange?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps) {
|
||||||
|
const [apis, setApis] = useState<Nip95Config[]>([])
|
||||||
|
const [loading, setLoading] = useState(true)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
|
const [newUrl, setNewUrl] = useState('')
|
||||||
|
const [showAddForm, setShowAddForm] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadApis()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
async function loadApis() {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
const config = await configStorage.getConfig()
|
||||||
|
setApis(config.nip95Apis.sort((a, b) => a.priority - b.priority))
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to load NIP-95 APIs'
|
||||||
|
setError(errorMessage)
|
||||||
|
console.error('Error loading NIP-95 APIs:', e)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleToggleEnabled(id: string, enabled: boolean) {
|
||||||
|
try {
|
||||||
|
await configStorage.updateNip95Api(id, { enabled })
|
||||||
|
await loadApis()
|
||||||
|
onConfigChange?.()
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to update API'
|
||||||
|
setError(errorMessage)
|
||||||
|
console.error('Error updating NIP-95 API:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdatePriority(id: string, priority: number) {
|
||||||
|
try {
|
||||||
|
await configStorage.updateNip95Api(id, { priority })
|
||||||
|
await loadApis()
|
||||||
|
onConfigChange?.()
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to update priority'
|
||||||
|
setError(errorMessage)
|
||||||
|
console.error('Error updating priority:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdateUrl(id: string, url: string) {
|
||||||
|
try {
|
||||||
|
await configStorage.updateNip95Api(id, { url })
|
||||||
|
await loadApis()
|
||||||
|
setEditingId(null)
|
||||||
|
onConfigChange?.()
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to update URL'
|
||||||
|
setError(errorMessage)
|
||||||
|
console.error('Error updating URL:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAddApi() {
|
||||||
|
if (!newUrl.trim()) {
|
||||||
|
setError('URL is required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate URL format
|
||||||
|
new URL(newUrl)
|
||||||
|
await configStorage.addNip95Api(newUrl.trim(), false)
|
||||||
|
setNewUrl('')
|
||||||
|
setShowAddForm(false)
|
||||||
|
await loadApis()
|
||||||
|
onConfigChange?.()
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError && e.message.includes('Invalid URL')) {
|
||||||
|
setError('Invalid URL format')
|
||||||
|
} else {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to add API'
|
||||||
|
setError(errorMessage)
|
||||||
|
}
|
||||||
|
console.error('Error adding NIP-95 API:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRemoveApi(id: string) {
|
||||||
|
if (!confirm('Are you sure you want to remove this endpoint?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await configStorage.removeNip95Api(id)
|
||||||
|
await loadApis()
|
||||||
|
onConfigChange?.()
|
||||||
|
} catch (e) {
|
||||||
|
const errorMessage = e instanceof Error ? e.message : 'Failed to remove API'
|
||||||
|
setError(errorMessage)
|
||||||
|
console.error('Error removing NIP-95 API:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-8 text-neon-cyan">
|
||||||
|
<div>Loading...</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-900/30 border border-red-500/50 rounded p-4 text-red-300">
|
||||||
|
{error}
|
||||||
|
<button
|
||||||
|
onClick={() => setError(null)}
|
||||||
|
className="ml-4 text-red-400 hover:text-red-200"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h2 className="text-2xl font-bold text-neon-cyan">NIP-95 Upload Endpoints</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAddForm(!showAddForm)}
|
||||||
|
className="px-4 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan border border-neon-cyan/50 rounded transition-colors"
|
||||||
|
>
|
||||||
|
{showAddForm ? 'Cancel' : '+ Add Endpoint'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showAddForm && (
|
||||||
|
<div className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-cyber-accent mb-2">
|
||||||
|
Endpoint URL
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
value={newUrl}
|
||||||
|
onChange={(e) => setNewUrl(e.target.value)}
|
||||||
|
placeholder="https://example.com/upload"
|
||||||
|
className="w-full px-4 py-2 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => void handleAddApi()}
|
||||||
|
className="px-4 py-2 bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan border border-neon-cyan/50 rounded transition-colors"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowAddForm(false)
|
||||||
|
setNewUrl('')
|
||||||
|
setError(null)
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-cyber-accent/20 hover:bg-cyber-accent/30 text-cyber-accent border border-cyber-accent/50 rounded transition-colors"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{apis.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-cyber-accent">
|
||||||
|
No NIP-95 endpoints configured
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
apis.map((api) => (
|
||||||
|
<div
|
||||||
|
key={api.id}
|
||||||
|
className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-3"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
{editingId === api.id ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
defaultValue={api.url}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (e.target.value !== api.url) {
|
||||||
|
void handleUpdateUrl(api.id, e.target.value)
|
||||||
|
} else {
|
||||||
|
setEditingId(null)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.currentTarget.blur()
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
setEditingId(null)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full px-3 py-2 bg-cyber-darker border border-neon-cyan/50 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="text-neon-cyan cursor-pointer hover:text-neon-green transition-colors"
|
||||||
|
onClick={() => setEditingId(api.id)}
|
||||||
|
title="Click to edit URL"
|
||||||
|
>
|
||||||
|
{api.url}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-sm text-cyber-accent mt-1">
|
||||||
|
Priority: {api.priority} | ID: {api.id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={api.enabled}
|
||||||
|
onChange={(e) => void handleToggleEnabled(api.id, e.target.checked)}
|
||||||
|
className="w-4 h-4 text-neon-cyan bg-cyber-darker border-cyber-accent rounded focus:ring-neon-cyan"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-cyber-accent">
|
||||||
|
{api.enabled ? 'Enabled' : 'Disabled'}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
onClick={() => void handleRemoveApi(api.id)}
|
||||||
|
className="px-3 py-1 text-sm bg-red-900/30 hover:bg-red-900/50 text-red-300 border border-red-500/50 rounded transition-colors"
|
||||||
|
title="Remove endpoint"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-cyber-accent">Priority:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={api.priority}
|
||||||
|
onChange={(e) => {
|
||||||
|
const priority = parseInt(e.target.value, 10)
|
||||||
|
if (!isNaN(priority) && priority > 0) {
|
||||||
|
void handleUpdatePriority(api.id, priority)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-20 px-2 py-1 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm text-cyber-accent space-y-2">
|
||||||
|
<p>
|
||||||
|
<strong>Note:</strong> Endpoints are tried in priority order (lower number = higher priority).
|
||||||
|
Only enabled endpoints will be used for uploads.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If an endpoint fails, the next enabled endpoint will be tried automatically.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -18,6 +18,12 @@ export function PageHeader() {
|
|||||||
>
|
>
|
||||||
{t('nav.documentation')}
|
{t('nav.documentation')}
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/settings"
|
||||||
|
className="px-4 py-2 text-cyber-accent hover:text-neon-cyan text-sm font-medium transition-colors border border-cyber-accent/30 hover:border-neon-cyan/50 rounded hover:shadow-glow-cyan"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</Link>
|
||||||
<ConditionalPublishButton />
|
<ConditionalPublishButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -53,3 +53,4 @@ Aucun déploiement spécial nécessaire. Les modifications sont purement fronten
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
92
features/nip95-endpoints-configuration.md
Normal file
92
features/nip95-endpoints-configuration.md
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Configuration des endpoints NIP-95
|
||||||
|
|
||||||
|
## Date
|
||||||
|
2025-01-27
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
Permettre aux utilisateurs de configurer les endpoints NIP-95 pour l'upload de médias via une interface utilisateur, avec la possibilité d'ajouter, modifier, activer/désactiver et supprimer des endpoints.
|
||||||
|
|
||||||
|
## Motivations
|
||||||
|
- Résoudre les problèmes de DNS/connectivité en permettant d'utiliser plusieurs endpoints
|
||||||
|
- Donner aux utilisateurs le contrôle sur les endpoints utilisés
|
||||||
|
- Faciliter l'ajout de nouveaux endpoints sans modifier le code
|
||||||
|
- Permettre l'activation/désactivation d'endpoints selon les besoins
|
||||||
|
|
||||||
|
## Impacts
|
||||||
|
- **Utilisateurs** : Peuvent configurer leurs propres endpoints NIP-95 via l'interface
|
||||||
|
- **Développeurs** : Plus besoin de modifier le code pour ajouter des endpoints
|
||||||
|
- **Fiabilité** : Possibilité d'avoir plusieurs endpoints de secours activés
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
### Fichiers créés
|
||||||
|
- **`pages/settings.tsx`** : Page de configuration des paramètres de l'application
|
||||||
|
- **`components/Nip95ConfigManager.tsx`** : Composant de gestion des endpoints NIP-95 avec interface complète
|
||||||
|
|
||||||
|
### Fichiers modifiés
|
||||||
|
- **`lib/configStorageTypes.ts`** : Ajout d'un endpoint supplémentaire (`nostrimg.com`) dans `DEFAULT_NIP95_APIS`
|
||||||
|
- **`components/PageHeader.tsx`** : Ajout d'un lien vers la page Settings dans la navigation
|
||||||
|
|
||||||
|
### Fonctionnalités implémentées
|
||||||
|
|
||||||
|
#### Interface de gestion des endpoints
|
||||||
|
- **Affichage de la liste** : Tous les endpoints configurés sont affichés avec leur statut (activé/désactivé), priorité et URL
|
||||||
|
- **Ajout d'endpoints** : Formulaire pour ajouter de nouveaux endpoints avec validation d'URL
|
||||||
|
- **Modification d'URL** : Clic sur l'URL pour la modifier directement
|
||||||
|
- **Activation/Désactivation** : Checkbox pour activer ou désactiver chaque endpoint
|
||||||
|
- **Gestion de la priorité** : Champ numérique pour définir l'ordre de tentative (plus bas = priorité plus haute)
|
||||||
|
- **Suppression** : Bouton pour supprimer un endpoint avec confirmation
|
||||||
|
|
||||||
|
#### Endpoints par défaut
|
||||||
|
Les endpoints suivants sont maintenant disponibles par défaut :
|
||||||
|
1. `https://void.cat/upload` (activé, priorité 1)
|
||||||
|
2. `https://nostr.build/api/v2/upload` (désactivé, priorité 2)
|
||||||
|
3. `https://picstr.build/api/v1/upload` (désactivé, priorité 3)
|
||||||
|
4. `https://nostrcheck.me/api/v1/media` (désactivé, priorité 4)
|
||||||
|
5. `https://nostrimg.com/api/upload` (désactivé, priorité 5) - **Nouveau**
|
||||||
|
|
||||||
|
## Modalités de déploiement
|
||||||
|
1. Les modifications sont dans le code source
|
||||||
|
2. Rebuild de l'application : `npm run build`
|
||||||
|
3. Redémarrage du service Next.js
|
||||||
|
4. Aucune migration de données nécessaire (les endpoints par défaut sont créés automatiquement)
|
||||||
|
|
||||||
|
## Modalités d'analyse
|
||||||
|
Pour vérifier que la fonctionnalité fonctionne :
|
||||||
|
|
||||||
|
1. **Accéder à la page Settings** :
|
||||||
|
- Naviguer vers `/settings` depuis le menu de navigation
|
||||||
|
- Vérifier que la liste des endpoints s'affiche
|
||||||
|
|
||||||
|
2. **Tester l'ajout d'un endpoint** :
|
||||||
|
- Cliquer sur "+ Add Endpoint"
|
||||||
|
- Entrer une URL valide (ex: `https://example.com/upload`)
|
||||||
|
- Vérifier que l'endpoint apparaît dans la liste
|
||||||
|
|
||||||
|
3. **Tester l'activation/désactivation** :
|
||||||
|
- Cocher/décocher la checkbox d'un endpoint
|
||||||
|
- Vérifier que le statut change
|
||||||
|
- Tester un upload pour vérifier que seuls les endpoints activés sont utilisés
|
||||||
|
|
||||||
|
4. **Tester la modification de priorité** :
|
||||||
|
- Modifier la priorité d'un endpoint
|
||||||
|
- Vérifier que l'ordre dans la liste change
|
||||||
|
- Tester un upload pour vérifier l'ordre de tentative
|
||||||
|
|
||||||
|
5. **Tester la modification d'URL** :
|
||||||
|
- Cliquer sur une URL pour la modifier
|
||||||
|
- Entrer une nouvelle URL et appuyer sur Enter
|
||||||
|
- Vérifier que l'URL est mise à jour
|
||||||
|
|
||||||
|
6. **Tester la suppression** :
|
||||||
|
- Cliquer sur "Remove" d'un endpoint
|
||||||
|
- Confirmer la suppression
|
||||||
|
- Vérifier que l'endpoint disparaît de la liste
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Les endpoints sont stockés dans IndexedDB (côté client)
|
||||||
|
- Les modifications sont persistantes et survivent aux rechargements de page
|
||||||
|
- Les endpoints sont essayés dans l'ordre de priorité (plus bas = priorité plus haute)
|
||||||
|
- Seuls les endpoints activés sont utilisés pour les uploads
|
||||||
|
- Si tous les endpoints activés échouent, une erreur est retournée à l'utilisateur
|
||||||
|
- La validation d'URL vérifie le format mais pas la disponibilité du service
|
||||||
@ -55,3 +55,4 @@ Pour vérifier si le problème existe :
|
|||||||
- SVG est plus léger et plus flexible qu'un fichier .ico
|
- SVG est plus léger et plus flexible qu'un fichier .ico
|
||||||
- Si nécessaire, on peut créer un fichier `.ico` en plus pour la compatibilité avec les anciens navigateurs
|
- Si nécessaire, on peut créer un fichier `.ico` en plus pour la compatibilité avec les anciens navigateurs
|
||||||
- Le favicon SVG actuel est minimal (rectangle cyan) et peut être remplacé par un design plus élaboré si nécessaire
|
- Le favicon SVG actuel est minimal (rectangle cyan) et peut être remplacé par un design plus élaboré si nécessaire
|
||||||
|
|
||||||
|
|||||||
@ -61,3 +61,4 @@ Pour vérifier si le problème existe :
|
|||||||
- Le package `form-data` (npm) doit être utilisé avec les modules `https`/`http` natifs de Node.js, pas avec `fetch()`
|
- Le package `form-data` (npm) doit être utilisé avec les modules `https`/`http` natifs de Node.js, pas avec `fetch()`
|
||||||
- `fetch()` natif de Node.js est compatible avec FormData du web standard (disponible dans le navigateur), pas avec le package npm `form-data`
|
- `fetch()` natif de Node.js est compatible avec FormData du web standard (disponible dans le navigateur), pas avec le package npm `form-data`
|
||||||
- Les fichiers temporaires créés par formidable doivent être nettoyés même en cas d'erreur pour éviter l'accumulation de fichiers
|
- Les fichiers temporaires créés par formidable doivent être nettoyés même en cas d'erreur pour éviter l'accumulation de fichiers
|
||||||
|
|
||||||
|
|||||||
107
fixKnowledge/nip95-upload-dns-resolution-error.md
Normal file
107
fixKnowledge/nip95-upload-dns-resolution-error.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# Problème : Erreur DNS resolution pour NIP-95 upload
|
||||||
|
|
||||||
|
## Date
|
||||||
|
2025-01-27
|
||||||
|
|
||||||
|
## Problème
|
||||||
|
L'API NIP-95 upload retourne une erreur `getaddrinfo ENOTFOUND void.cat` lors des tentatives d'upload, indiquant que le serveur Node.js ne peut pas résoudre le nom de domaine.
|
||||||
|
|
||||||
|
## Symptômes
|
||||||
|
- Erreur : `Failed to upload to all endpoints: {"error":"Failed to connect to upload endpoint: getaddrinfo ENOTFOUND void.cat"}`
|
||||||
|
- Code d'erreur Node.js : `ENOTFOUND` ou `EAI_AGAIN`
|
||||||
|
- Les uploads NIP-95 échouent avec une erreur de résolution DNS
|
||||||
|
|
||||||
|
## Root cause
|
||||||
|
Le serveur Node.js qui exécute l'API Next.js ne peut pas résoudre le nom de domaine `void.cat` (ou d'autres endpoints NIP-95). Cela peut être dû à :
|
||||||
|
|
||||||
|
1. **Problème de DNS sur le serveur** : Le serveur n'a pas accès à un serveur DNS fonctionnel
|
||||||
|
2. **Pas d'accès Internet** : Le serveur n'a pas de connexion Internet ou est derrière un pare-feu qui bloque les requêtes sortantes
|
||||||
|
3. **Configuration réseau incorrecte** : Les paramètres réseau du serveur sont mal configurés
|
||||||
|
4. **Problème de résolution DNS temporaire** : Problème temporaire avec le serveur DNS
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Impossible d'uploader des fichiers via NIP-95
|
||||||
|
- Les utilisateurs ne peuvent pas publier d'articles avec des médias
|
||||||
|
- Fonctionnalité d'upload complètement non fonctionnelle
|
||||||
|
|
||||||
|
## Correctifs
|
||||||
|
1. **Amélioration de la gestion d'erreur DNS** : Détection spécifique des erreurs DNS avec messages d'erreur plus clairs
|
||||||
|
2. **Ajout d'un timeout** : Timeout de 30 secondes pour éviter que les requêtes restent bloquées indéfiniment
|
||||||
|
3. **Amélioration des logs** : Logs plus détaillés pour diagnostiquer les problèmes DNS
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
- **Fichier modifié** : `pages/api/nip95-upload.ts`
|
||||||
|
- **Ajout de timeout** : `timeout: 30000` dans les options de requête
|
||||||
|
- **Détection spécifique des erreurs DNS** : Vérification des codes d'erreur `ENOTFOUND` et `EAI_AGAIN`
|
||||||
|
- **Messages d'erreur améliorés** : Messages plus clairs pour les erreurs DNS avec suggestions de diagnostic
|
||||||
|
- **Logs améliorés** : Logs incluant le hostname, le code d'erreur et des suggestions
|
||||||
|
|
||||||
|
## Modalités de déploiement
|
||||||
|
1. Les modifications sont dans le code source
|
||||||
|
2. Rebuild de l'application : `npm run build`
|
||||||
|
3. Redémarrage du service Next.js
|
||||||
|
4. Aucune dépendance supplémentaire nécessaire
|
||||||
|
|
||||||
|
## Modalités d'analyse
|
||||||
|
Pour diagnostiquer le problème DNS :
|
||||||
|
|
||||||
|
1. **Vérifier la résolution DNS depuis le serveur** :
|
||||||
|
```bash
|
||||||
|
# Depuis le serveur
|
||||||
|
nslookup void.cat
|
||||||
|
# ou
|
||||||
|
dig void.cat
|
||||||
|
# ou
|
||||||
|
host void.cat
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Vérifier la connectivité réseau** :
|
||||||
|
```bash
|
||||||
|
# Tester la connexion HTTPS
|
||||||
|
curl -v https://void.cat/upload
|
||||||
|
# ou
|
||||||
|
wget --spider https://void.cat/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Vérifier les logs du serveur Node.js** :
|
||||||
|
- Chercher les erreurs `ENOTFOUND` ou `EAI_AGAIN`
|
||||||
|
- Vérifier les logs avec le hostname et les suggestions
|
||||||
|
|
||||||
|
4. **Vérifier la configuration DNS du serveur** :
|
||||||
|
```bash
|
||||||
|
# Vérifier les serveurs DNS configurés
|
||||||
|
cat /etc/resolv.conf
|
||||||
|
# ou sur systemd
|
||||||
|
systemd-resolve --status
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Tester depuis le conteneur/service** :
|
||||||
|
```bash
|
||||||
|
# Si le service tourne dans un conteneur Docker
|
||||||
|
docker exec <container_name> nslookup void.cat
|
||||||
|
docker exec <container_name> curl -v https://void.cat/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solutions possibles
|
||||||
|
|
||||||
|
### Si le problème est la résolution DNS
|
||||||
|
1. Vérifier que le serveur a accès à un serveur DNS fonctionnel
|
||||||
|
2. Configurer des serveurs DNS fiables (par exemple 8.8.8.8, 1.1.1.1)
|
||||||
|
3. Vérifier que `/etc/resolv.conf` contient des serveurs DNS valides
|
||||||
|
|
||||||
|
### Si le problème est l'accès Internet
|
||||||
|
1. Vérifier que le serveur a une connexion Internet fonctionnelle
|
||||||
|
2. Vérifier les règles de pare-feu qui pourraient bloquer les requêtes sortantes HTTPS
|
||||||
|
3. Vérifier les proxies si le serveur est derrière un proxy
|
||||||
|
|
||||||
|
### Si le problème est temporaire
|
||||||
|
1. Attendre quelques minutes et réessayer
|
||||||
|
2. Vérifier si d'autres services ont des problèmes de connectivité
|
||||||
|
3. Vérifier les statuts des endpoints NIP-95 (void.cat, etc.)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- L'erreur `ENOTFOUND` signifie que le nom de domaine n'a pas pu être résolu en adresse IP
|
||||||
|
- L'erreur `EAI_AGAIN` signifie que la résolution DNS a échoué temporairement (peut être réessayé)
|
||||||
|
- Le timeout de 30 secondes évite que les requêtes restent bloquées indéfiniment
|
||||||
|
- Les messages d'erreur améliorés aident à diagnostiquer rapidement le problème
|
||||||
|
- Si le problème persiste, vérifier la configuration réseau du serveur et l'accès à Internet
|
||||||
@ -49,24 +49,31 @@ export const DEFAULT_NIP95_APIS: Nip95Config[] = [
|
|||||||
{
|
{
|
||||||
id: 'nostrbuild',
|
id: 'nostrbuild',
|
||||||
url: 'https://nostr.build/api/v2/upload',
|
url: 'https://nostr.build/api/v2/upload',
|
||||||
enabled: false,
|
enabled: true,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'picstr',
|
id: 'picstr',
|
||||||
url: 'https://picstr.build/api/v1/upload',
|
url: 'https://picstr.build/api/v1/upload',
|
||||||
enabled: false,
|
enabled: true,
|
||||||
priority: 3,
|
priority: 3,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'nostrcheck',
|
id: 'nostrcheck',
|
||||||
url: 'https://nostrcheck.me/api/v1/media',
|
url: 'https://nostrcheck.me/api/v1/media',
|
||||||
enabled: false,
|
enabled: true,
|
||||||
priority: 4,
|
priority: 4,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'nostrimg',
|
||||||
|
url: 'https://nostrimg.com/api/upload',
|
||||||
|
enabled: true,
|
||||||
|
priority: 5,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_PLATFORM_LIGHTNING_ADDRESS = ''
|
export const DEFAULT_PLATFORM_LIGHTNING_ADDRESS = ''
|
||||||
|
|||||||
@ -78,6 +78,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
path: targetUrl.pathname + targetUrl.search,
|
path: targetUrl.pathname + targetUrl.search,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: headers,
|
headers: headers,
|
||||||
|
timeout: 30000, // 30 seconds timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => {
|
const proxyRequest = clientModule.request(requestOptions, (proxyResponse) => {
|
||||||
@ -98,8 +99,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Set timeout on the request
|
||||||
|
proxyRequest.setTimeout(30000, () => {
|
||||||
|
proxyRequest.destroy()
|
||||||
|
reject(new Error('Request timeout after 30 seconds'))
|
||||||
|
})
|
||||||
|
|
||||||
proxyRequest.on('error', (error) => {
|
proxyRequest.on('error', (error) => {
|
||||||
reject(error)
|
// Check for DNS errors specifically
|
||||||
|
const errorCode = (error as NodeJS.ErrnoException).code
|
||||||
|
if (errorCode === 'ENOTFOUND' || errorCode === 'EAI_AGAIN') {
|
||||||
|
console.error('NIP-95 proxy DNS error:', {
|
||||||
|
targetEndpoint,
|
||||||
|
hostname: targetUrl.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}`))
|
||||||
|
} else {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
formData.on('error', (error) => {
|
formData.on('error', (error) => {
|
||||||
@ -116,12 +136,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
console.error('Error deleting temp file:', unlinkError)
|
console.error('Error deleting temp file:', unlinkError)
|
||||||
}
|
}
|
||||||
const errorMessage = requestError instanceof Error ? requestError.message : 'Unknown request error'
|
const errorMessage = requestError instanceof Error ? requestError.message : 'Unknown request error'
|
||||||
|
const isDnsError = errorMessage.includes('DNS resolution failed') || errorMessage.includes('ENOTFOUND') || errorMessage.includes('EAI_AGAIN')
|
||||||
|
|
||||||
console.error('NIP-95 proxy request error:', {
|
console.error('NIP-95 proxy request error:', {
|
||||||
targetEndpoint,
|
targetEndpoint,
|
||||||
|
hostname: targetUrl.hostname,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
|
isDnsError,
|
||||||
fileSize: fileField.size,
|
fileSize: fileField.size,
|
||||||
fileName: fileField.originalFilename,
|
fileName: fileField.originalFilename,
|
||||||
|
suggestion: isDnsError ? 'The server cannot resolve the domain name. Check DNS configuration and network connectivity.' : undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 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.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
error: `Failed to connect to upload endpoint: ${errorMessage}`,
|
error: `Failed to connect to upload endpoint: ${errorMessage}`,
|
||||||
})
|
})
|
||||||
|
|||||||
25
pages/settings.tsx
Normal file
25
pages/settings.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import Head from 'next/head'
|
||||||
|
import { PageHeader } from '@/components/PageHeader'
|
||||||
|
import { Footer } from '@/components/Footer'
|
||||||
|
import { Nip95ConfigManager } from '@/components/Nip95ConfigManager'
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Settings - zapwall.fr</title>
|
||||||
|
<meta name="description" content="Application settings and configuration" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
</Head>
|
||||||
|
<main className="min-h-screen bg-cyber-darker">
|
||||||
|
<PageHeader />
|
||||||
|
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||||
|
<h1 className="text-3xl font-bold text-neon-cyan mb-8">Settings</h1>
|
||||||
|
<Nip95ConfigManager />
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -2,3 +2,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
<rect width="16" height="16" fill="#00ffff"/>
|
<rect width="16" height="16" fill="#00ffff"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 179 B |
Loading…
x
Reference in New Issue
Block a user