Add fallback public Blossom servers for users without server list

- Add well-known public servers as fallbacks (blossom.primal.net, nostr.download, files.v0l.io)
- Use fallbacks when user has no kind 10063 server list configured
- Show "Public Servers" label with Globe icon when using fallbacks
- Inform user that no server list was found
- Select first fallback server by default (vs all user servers)
This commit is contained in:
Claude
2026-01-13 15:40:21 +00:00
parent 5c53438718
commit d82ce4b955

View File

@@ -12,6 +12,7 @@ import {
Archive,
CheckCircle,
XCircle,
Globe,
} from "lucide-react";
import {
Dialog,
@@ -34,6 +35,16 @@ import {
} from "@/services/blossom";
import type { Subscription } from "rxjs";
/**
* Well-known public Blossom servers that can be used as fallbacks
* when the user doesn't have their own server list configured
*/
const FALLBACK_SERVERS = [
"https://blossom.primal.net",
"https://nostr.download",
"https://files.v0l.io",
];
interface BlossomUploadDialogProps {
/** Whether the dialog is open */
open: boolean;
@@ -75,6 +86,7 @@ export function BlossomUploadDialog({
new Set(),
);
const [loadingServers, setLoadingServers] = useState(true);
const [usingFallback, setUsingFallback] = useState(false);
const [uploading, setUploading] = useState(false);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
@@ -93,26 +105,46 @@ export function BlossomUploadDialog({
setUploadResults([]);
setUploadErrors([]);
setUploading(false);
setUsingFallback(false);
}
}, [open]);
// Helper to set fallback servers
const useFallbackServers = useCallback(() => {
setServers(FALLBACK_SERVERS);
setSelectedServers(new Set([FALLBACK_SERVERS[0]])); // Select first by default
setUsingFallback(true);
setLoadingServers(false);
}, []);
// Fetch servers when dialog opens
useEffect(() => {
if (!open || !pubkey) {
if (!open) {
setLoadingServers(false);
return;
}
// If no pubkey (not logged in), use fallback servers
if (!pubkey) {
useFallbackServers();
return;
}
setLoadingServers(true);
setUsingFallback(false);
let subscription: Subscription | null = null;
let foundUserServers = false;
// Check existing event first
const event = eventStore.getReplaceable(USER_SERVER_LIST_KIND, pubkey, "");
if (event) {
const s = getServersFromEvent(event);
setServers(s);
setSelectedServers(new Set(s)); // Select all by default
setLoadingServers(false);
if (s.length > 0) {
setServers(s);
setSelectedServers(new Set(s)); // Select all by default
setLoadingServers(false);
foundUserServers = true;
}
}
// Also fetch from network
@@ -125,21 +157,31 @@ export function BlossomUploadDialog({
const e = eventStore.getReplaceable(USER_SERVER_LIST_KIND, pubkey, "");
if (e) {
const s = getServersFromEvent(e);
setServers(s);
setSelectedServers((prev) => (prev.size === 0 ? new Set(s) : prev));
if (s.length > 0) {
setServers(s);
setSelectedServers((prev) => (prev.size === 0 ? new Set(s) : prev));
setUsingFallback(false);
foundUserServers = true;
}
}
setLoadingServers(false);
},
error: () => setLoadingServers(false),
});
const timeout = setTimeout(() => setLoadingServers(false), 3000);
// After timeout, use fallbacks if no user servers found
const timeout = setTimeout(() => {
setLoadingServers(false);
if (!foundUserServers) {
useFallbackServers();
}
}, 3000);
return () => {
subscription?.unsubscribe();
clearTimeout(timeout);
};
}, [open, pubkey, eventStore]);
}, [open, pubkey, eventStore, useFallbackServers]);
// Create preview URL for selected file
useEffect(() => {
@@ -341,12 +383,20 @@ export function BlossomUploadDialog({
<div className="flex items-center justify-center py-4">
<Loader2 className="size-5 animate-spin text-muted-foreground" />
</div>
) : servers.length > 0 ? (
) : (
<div className="border rounded-lg p-3">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">
Servers ({selectedServers.size}/{servers.length})
</span>
<div className="flex items-center gap-2">
{usingFallback ? (
<Globe className="size-3.5 text-muted-foreground" />
) : (
<HardDrive className="size-3.5 text-muted-foreground" />
)}
<span className="text-sm font-medium">
{usingFallback ? "Public Servers" : "Your Servers"} (
{selectedServers.size}/{servers.length})
</span>
</div>
<div className="flex gap-1">
<Button
variant="ghost"
@@ -368,6 +418,11 @@ export function BlossomUploadDialog({
</Button>
</div>
</div>
{usingFallback && (
<p className="text-xs text-muted-foreground mb-2">
No server list found. Using public servers.
</p>
)}
<div className="space-y-1 max-h-32 overflow-y-auto">
{servers.map((server) => (
<label
@@ -379,7 +434,11 @@ export function BlossomUploadDialog({
onCheckedChange={() => toggleServer(server)}
disabled={uploading}
/>
<HardDrive className="size-3.5 text-muted-foreground flex-shrink-0" />
{usingFallback ? (
<Globe className="size-3.5 text-muted-foreground flex-shrink-0" />
) : (
<HardDrive className="size-3.5 text-muted-foreground flex-shrink-0" />
)}
<span className="font-mono text-xs truncate flex-1">
{server}
</span>
@@ -387,16 +446,6 @@ export function BlossomUploadDialog({
))}
</div>
</div>
) : (
<div className="border rounded-lg p-4 text-center">
<HardDrive className="size-8 mx-auto mb-2 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
No Blossom servers configured
</p>
<p className="text-xs text-muted-foreground mt-1">
Publish a kind 10063 server list to use this feature
</p>
</div>
)}
{/* Upload Results */}