|
|
|
@ -2449,24 +2449,83 @@ |
|
|
|
userSigner = window.nostr; |
|
|
|
userSigner = window.nostr; |
|
|
|
console.log("Restored extension signer for import"); |
|
|
|
console.log("Restored extension signer for import"); |
|
|
|
} |
|
|
|
} |
|
|
|
headers.Authorization = await createNIP98AuthHeader( |
|
|
|
|
|
|
|
`${getApiBase()}/api/import`, |
|
|
|
// Use the actual request URL for NIP-98 auth |
|
|
|
"POST", |
|
|
|
// If behind reverse proxy, server may see HTTP instead of HTTPS |
|
|
|
); |
|
|
|
// Use the same URL we're fetching, but try HTTP if HTTPS fails |
|
|
|
|
|
|
|
const importUrl = `${getApiBase()}/api/import`; |
|
|
|
|
|
|
|
// Convert to HTTP if HTTPS (reverse proxy may forward as HTTP internally) |
|
|
|
|
|
|
|
const authUrl = importUrl.replace('https://', 'http://'); |
|
|
|
|
|
|
|
console.log("Creating NIP-98 auth for import URL:", authUrl, "(request URL:", importUrl, ")"); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
headers.Authorization = await createNIP98AuthHeader( |
|
|
|
|
|
|
|
authUrl, |
|
|
|
|
|
|
|
"POST", |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
console.log("NIP-98 auth header created successfully"); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
|
|
console.error("Failed to create NIP-98 auth header:", error); |
|
|
|
|
|
|
|
importMessage = "Failed to create authentication: " + error.message; |
|
|
|
|
|
|
|
setTimeout(() => { importMessage = ""; }, 5000); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
const formData = new FormData(); |
|
|
|
const formData = new FormData(); |
|
|
|
formData.append("file", selectedFile); |
|
|
|
formData.append("file", selectedFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("Uploading file:", { |
|
|
|
|
|
|
|
name: selectedFile.name, |
|
|
|
|
|
|
|
size: selectedFile.size, |
|
|
|
|
|
|
|
type: selectedFile.type, |
|
|
|
|
|
|
|
sizeKB: Math.round(selectedFile.size / 1024), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use AbortController for timeout handling |
|
|
|
|
|
|
|
const controller = new AbortController(); |
|
|
|
|
|
|
|
const timeoutId = setTimeout(() => { |
|
|
|
|
|
|
|
controller.abort(); |
|
|
|
|
|
|
|
importMessage = "Upload timeout: The request took too long. Please try again or contact the administrator."; |
|
|
|
|
|
|
|
setTimeout(() => { importMessage = ""; }, 10000); |
|
|
|
|
|
|
|
}, 300000); // 5 minute timeout |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("Sending import request to:", `${getApiBase()}/api/import`); |
|
|
|
|
|
|
|
console.log("Request headers:", Object.keys(headers)); |
|
|
|
|
|
|
|
console.log("File size:", selectedFile.size, "bytes"); |
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${getApiBase()}/api/import`, { |
|
|
|
const response = await fetch(`${getApiBase()}/api/import`, { |
|
|
|
method: "POST", |
|
|
|
method: "POST", |
|
|
|
headers, |
|
|
|
headers, |
|
|
|
body: formData, |
|
|
|
body: formData, |
|
|
|
|
|
|
|
signal: controller.signal, |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
clearTimeout(timeoutId); |
|
|
|
|
|
|
|
console.log("Import request completed, status:", response.status, response.statusText); |
|
|
|
|
|
|
|
|
|
|
|
if (!response.ok) { |
|
|
|
if (!response.ok) { |
|
|
|
throw new Error( |
|
|
|
// Try to get error message from response body |
|
|
|
`Import failed: ${response.status} ${response.statusText}`, |
|
|
|
let errorMsg = `Import failed: ${response.status} ${response.statusText}`; |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// Provide helpful messages for common errors |
|
|
|
|
|
|
|
if (response.status === 502) { |
|
|
|
|
|
|
|
errorMsg = "Server error (502): The file may be too large or the server is temporarily unavailable. Try a smaller file or contact the administrator."; |
|
|
|
|
|
|
|
} else if (response.status === 413) { |
|
|
|
|
|
|
|
errorMsg = "File too large: The file exceeds the server's size limit. Please split it into smaller files."; |
|
|
|
|
|
|
|
} else if (response.status === 504) { |
|
|
|
|
|
|
|
errorMsg = "Request timeout: The file upload took too long. Try a smaller file or check your connection."; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
const errorData = await response.text(); |
|
|
|
|
|
|
|
if (errorData && !errorData.includes("<!DOCTYPE")) { |
|
|
|
|
|
|
|
// Only show non-HTML error messages |
|
|
|
|
|
|
|
const shortError = errorData.substring(0, 200); |
|
|
|
|
|
|
|
errorMsg += ` - ${shortError}`; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
// Ignore error reading response |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(errorMsg); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json(); |
|
|
|
const result = await response.json(); |
|
|
|
@ -2748,20 +2807,50 @@ |
|
|
|
throw new Error("No valid signer available"); |
|
|
|
throw new Error("No valid signer available"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure URL is normalized (remove trailing slash, ensure proper format) |
|
|
|
|
|
|
|
let normalizedUrl = url.trim(); |
|
|
|
|
|
|
|
if (normalizedUrl.endsWith('/') && normalizedUrl.length > 1) { |
|
|
|
|
|
|
|
normalizedUrl = normalizedUrl.slice(0, -1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If the URL uses HTTPS but we're behind a reverse proxy that forwards as HTTP, |
|
|
|
|
|
|
|
// try using HTTP instead to match what the server sees |
|
|
|
|
|
|
|
// The server checks the request URL against the signed URL |
|
|
|
|
|
|
|
let authUrl = normalizedUrl; |
|
|
|
|
|
|
|
if (normalizedUrl.startsWith('https://')) { |
|
|
|
|
|
|
|
// Try HTTP version in case reverse proxy forwards as HTTP internally |
|
|
|
|
|
|
|
const httpUrl = normalizedUrl.replace('https://', 'http://'); |
|
|
|
|
|
|
|
// Use the same URL we're actually requesting (which should match server's view) |
|
|
|
|
|
|
|
authUrl = normalizedUrl; // Keep HTTPS for now, server should handle both |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Create NIP-98 auth event |
|
|
|
// Create NIP-98 auth event |
|
|
|
const authEvent = { |
|
|
|
const authEvent = { |
|
|
|
kind: 27235, |
|
|
|
kind: 27235, |
|
|
|
created_at: Math.floor(Date.now() / 1000), |
|
|
|
created_at: Math.floor(Date.now() / 1000), |
|
|
|
tags: [ |
|
|
|
tags: [ |
|
|
|
["u", url], |
|
|
|
["u", authUrl], |
|
|
|
["method", method.toUpperCase()], |
|
|
|
["method", method.toUpperCase()], |
|
|
|
], |
|
|
|
], |
|
|
|
content: "", |
|
|
|
content: "", |
|
|
|
pubkey: userPubkey, |
|
|
|
pubkey: userPubkey, |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("NIP-98 auth event before signing:", { |
|
|
|
|
|
|
|
kind: authEvent.kind, |
|
|
|
|
|
|
|
url: authUrl, |
|
|
|
|
|
|
|
method: method.toUpperCase(), |
|
|
|
|
|
|
|
pubkey: userPubkey, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const signedEvent = await userSigner.signEvent(authEvent); |
|
|
|
const signedEvent = await userSigner.signEvent(authEvent); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("NIP-98 auth event after signing:", { |
|
|
|
|
|
|
|
id: signedEvent.id, |
|
|
|
|
|
|
|
pubkey: signedEvent.pubkey, |
|
|
|
|
|
|
|
sig: signedEvent.sig ? signedEvent.sig.substring(0, 16) + "..." : "missing", |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Encode as base64 |
|
|
|
// Encode as base64 |
|
|
|
const eventJson = JSON.stringify(signedEvent); |
|
|
|
const eventJson = JSON.stringify(signedEvent); |
|
|
|
const base64Event = btoa(eventJson); |
|
|
|
const base64Event = btoa(eventJson); |
|
|
|
|