feat: Add communikey blossom servers to upload dialog

Integrates communikey blossom servers into the file upload dialog,
allowing users to upload attachments to blossom servers specified in
the communikey's kind 10222 configuration.

Changes:
- Added communikeyServers prop to BlossomUploadDialog component
- Updated server loading logic to merge user servers with communikey
  servers, removing duplicates
- If user has no server list, communikey servers are used instead of
  falling back to public servers immediately
- Added communikeyServers option to useBlossomUpload hook
- ChatViewer extracts blossom servers from communikey metadata and
  passes them to the upload hook

Users can now upload to:
1. Their own servers (kind 10063)
2. Communikey blossom servers (from kind 10222)
3. Public fallback servers (if no other options)

The servers are merged and deduplicated, with all available servers
selected by default for redundancy.
This commit is contained in:
Claude
2026-01-16 12:52:50 +00:00
parent 8fa76601ef
commit ca6226e53e
3 changed files with 58 additions and 11 deletions

View File

@@ -57,6 +57,8 @@ interface BlossomUploadDialogProps {
onError?: (error: Error) => void;
/** File types to accept (e.g., "image/*,video/*,audio/*") */
accept?: string;
/** Additional blossom servers from communikey (NIP-CC) */
communikeyServers?: string[];
}
/**
@@ -75,6 +77,7 @@ export function BlossomUploadDialog({
onCancel,
onError,
accept = "image/*,video/*,audio/*",
communikeyServers = [],
}: BlossomUploadDialogProps) {
const eventStore = useEventStore();
const activeAccount = use$(accountManager.active$);
@@ -134,18 +137,34 @@ export function BlossomUploadDialog({
let subscription: Subscription | null = null;
let foundUserServers = false;
// Helper to merge user servers with communikey servers
const mergeServers = (userServers: string[]) => {
// Combine user servers + communikey servers, removing duplicates
const allServers = [...new Set([...userServers, ...communikeyServers])];
return allServers;
};
// Check existing event first
const event = eventStore.getReplaceable(USER_SERVER_LIST_KIND, pubkey, "");
if (event) {
const s = getServersFromEvent(event);
if (s.length > 0) {
setServers(s);
setSelectedServers(new Set(s)); // Select all by default
const userServers = getServersFromEvent(event);
if (userServers.length > 0) {
const allServers = mergeServers(userServers);
setServers(allServers);
setSelectedServers(new Set(allServers)); // Select all by default
setLoadingServers(false);
foundUserServers = true;
}
}
// If we have communikey servers but no user servers yet, use them
if (!foundUserServers && communikeyServers.length > 0) {
setServers(communikeyServers);
setSelectedServers(new Set(communikeyServers));
setLoadingServers(false);
setUsingFallback(false);
}
// Also fetch from network
subscription = addressLoader({
kind: USER_SERVER_LIST_KIND,
@@ -155,10 +174,13 @@ export function BlossomUploadDialog({
next: () => {
const e = eventStore.getReplaceable(USER_SERVER_LIST_KIND, pubkey, "");
if (e) {
const s = getServersFromEvent(e);
if (s.length > 0) {
setServers(s);
setSelectedServers((prev) => (prev.size === 0 ? new Set(s) : prev));
const userServers = getServersFromEvent(e);
if (userServers.length > 0) {
const allServers = mergeServers(userServers);
setServers(allServers);
setSelectedServers((prev) =>
prev.size === 0 ? new Set(allServers) : prev,
);
setUsingFallback(false);
foundUserServers = true;
}
@@ -171,7 +193,7 @@ export function BlossomUploadDialog({
// After timeout, use fallbacks if no user servers found
const timeout = setTimeout(() => {
setLoadingServers(false);
if (!foundUserServers) {
if (!foundUserServers && communikeyServers.length === 0) {
applyFallbackServers();
}
}, 3000);
@@ -180,7 +202,7 @@ export function BlossomUploadDialog({
subscription?.unsubscribe();
clearTimeout(timeout);
};
}, [open, pubkey, eventStore, applyFallbackServers]);
}, [open, pubkey, eventStore, applyFallbackServers, communikeyServers]);
// Create preview URL for selected file
useEffect(() => {

View File

@@ -446,9 +446,24 @@ export function ChatViewer({
// Ref to MentionEditor for programmatic submission
const editorRef = useRef<MentionEditorHandle>(null);
// Extract communikey blossom servers if available
const communikeyServers = useMemo(() => {
if (
conversationResult.status === "success" &&
conversationResult.conversation.protocol === "communikeys"
) {
return (
conversationResult.conversation.metadata?.communikeyConfig
?.blossomServers || []
);
}
return [];
}, [conversationResult]);
// Blossom upload hook for file attachments
const { open: openUpload, dialog: uploadDialog } = useBlossomUpload({
accept: "image/*,video/*,audio/*",
communikeyServers,
onSuccess: (results) => {
if (results.length > 0 && editorRef.current) {
// Insert the first successful upload as a blob attachment with metadata

View File

@@ -11,6 +11,8 @@ export interface UseBlossomUploadOptions {
onError?: (error: Error) => void;
/** File types to accept (e.g., "image/*,video/*,audio/*") */
accept?: string;
/** Additional blossom servers from communikey (NIP-CC) */
communikeyServers?: string[];
}
export interface UseBlossomUploadReturn {
@@ -84,9 +86,17 @@ export function useBlossomUpload(
onCancel={handleCancel}
onError={handleError}
accept={options.accept}
communikeyServers={options.communikeyServers}
/>
),
[isOpen, handleSuccess, handleCancel, handleError, options.accept],
[
isOpen,
handleSuccess,
handleCancel,
handleError,
options.accept,
options.communikeyServers,
],
);
return { open, close, isOpen, dialog };