From ca6226e53edca98f94d6560c39f3fcc633080c60 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Jan 2026 12:52:50 +0000 Subject: [PATCH] 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. --- src/components/BlossomUploadDialog.tsx | 42 ++++++++++++++++++++------ src/components/ChatViewer.tsx | 15 +++++++++ src/hooks/useBlossomUpload.tsx | 12 +++++++- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/components/BlossomUploadDialog.tsx b/src/components/BlossomUploadDialog.tsx index c28ef9e..4d6401e 100644 --- a/src/components/BlossomUploadDialog.tsx +++ b/src/components/BlossomUploadDialog.tsx @@ -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(() => { diff --git a/src/components/ChatViewer.tsx b/src/components/ChatViewer.tsx index ae492f6..6452d7e 100644 --- a/src/components/ChatViewer.tsx +++ b/src/components/ChatViewer.tsx @@ -446,9 +446,24 @@ export function ChatViewer({ // Ref to MentionEditor for programmatic submission const editorRef = useRef(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 diff --git a/src/hooks/useBlossomUpload.tsx b/src/hooks/useBlossomUpload.tsx index aee53a5..effc7e7 100644 --- a/src/hooks/useBlossomUpload.tsx +++ b/src/hooks/useBlossomUpload.tsx @@ -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 };