diff --git a/src/App.vue b/src/App.vue index 71a03d6..fae0885 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,13 +8,15 @@ import { Toaster } from '@/components/ui/sonner' import 'vue-sonner/style.css' import { auth } from '@/composables/useAuth' import { useMarketPreloader } from '@/composables/useMarketPreloader' +import { useNostrChatPreloader } from '@/composables/useNostrChatPreloader' import { toast } from 'vue-sonner' const route = useRoute() const showLoginDialog = ref(false) -// Initialize market preloader +// Initialize preloaders const marketPreloader = useMarketPreloader() +const chatPreloader = useNostrChatPreloader() // Hide navbar on login page const showNavbar = computed(() => { @@ -25,8 +27,9 @@ function handleLoginSuccess() { showLoginDialog.value = false toast.success('Welcome back!') - // Trigger market preloading after successful login + // Trigger preloading after successful login marketPreloader.preloadMarket() + chatPreloader.preloadChat() } onMounted(async () => { @@ -38,11 +41,17 @@ onMounted(async () => { } }) -// Watch for authentication changes and trigger market preloading +// Watch for authentication changes and trigger preloading watch(() => auth.isAuthenticated.value, (isAuthenticated) => { - if (isAuthenticated && !marketPreloader.isPreloaded.value) { - console.log('User authenticated, triggering market preload...') - marketPreloader.preloadMarket() + if (isAuthenticated) { + if (!marketPreloader.isPreloaded.value) { + console.log('User authenticated, triggering market preload...') + marketPreloader.preloadMarket() + } + if (!chatPreloader.isPreloaded.value) { + console.log('User authenticated, triggering chat preload...') + chatPreloader.preloadChat() + } } }, { immediate: true }) diff --git a/src/components/layout/Navbar.vue b/src/components/layout/Navbar.vue index f16ce3d..926a833 100644 --- a/src/components/layout/Navbar.vue +++ b/src/components/layout/Navbar.vue @@ -13,6 +13,7 @@ import ProfileDialog from '@/components/auth/ProfileDialog.vue' import CurrencyDisplay from '@/components/ui/CurrencyDisplay.vue' import { auth } from '@/composables/useAuth' import { useMarketPreloader } from '@/composables/useMarketPreloader' +import { useNostrChatPreloader } from '@/composables/useNostrChatPreloader' interface NavigationItem { name: string @@ -26,6 +27,7 @@ const isOpen = ref(false) const showLoginDialog = ref(false) const showProfileDialog = ref(false) const marketPreloader = useMarketPreloader() +const chatPreloader = useNostrChatPreloader() const navigation = computed(() => [ { name: t('nav.home'), href: '/' }, @@ -43,6 +45,11 @@ const totalBalance = computed(() => { }, 0) }) +// Compute total unread messages +const totalUnreadMessages = computed(() => { + return chatPreloader.getTotalUnreadCount() +}) + const toggleMenu = () => { isOpen.value = !isOpen.value } @@ -87,13 +94,20 @@ const handleLogout = async () => { - - + +
+ + + {{ totalUnreadMessages > 99 ? '99+' : totalUnreadMessages }} + +
{{ item.name }}
([]) +const peers = computed(() => chatPreloader.peers.value) const selectedPeer = ref(null) const messageInput = ref('') -const isLoading = ref(false) +const isLoading = computed(() => chatPreloader.isPreloading.value) const showChat = ref(false) const messagesScrollArea = ref(null) const messagesContainer = ref(null) @@ -408,7 +410,6 @@ const { connect, disconnect, subscribeToPeer, - subscribeToPeerForNotifications, sendMessage: sendNostrMessage, onMessageAdded, markMessagesAsRead, @@ -499,106 +500,12 @@ const goBackToPeers = () => { } // Methods -const loadPeers = async () => { - try { - isLoading.value = true - const authToken = getAuthToken() - if (!authToken) { - console.warn('No authentication token found - cannot load peers') - return - } - const API_BASE_URL = config.api.baseUrl || 'http://localhost:5006' - const response = await fetch(`${API_BASE_URL}/api/v1/auth/nostr/pubkeys`, { - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }) - - console.log('Peers API Response status:', response.status) - - if (!response.ok) { - const errorText = await response.text() - console.error('Peers API Error:', response.status, errorText) - throw new Error(`Failed to load peers: ${response.status}`) - } - - const responseText = await response.text() - console.log('Peers API Response text:', responseText) - - try { - const data = JSON.parse(responseText) - peers.value = data.map((peer: any) => ({ - user_id: peer.user_id, - username: peer.username, - pubkey: peer.pubkey - })) - - console.log(`Loaded ${peers.value.length} peers`) - - // Note: Subscriptions will be handled by the isConnected watcher - - } catch (parseError) { - console.error('JSON Parse Error for peers:', parseError) - console.error('Response was:', responseText) - throw new Error('Invalid JSON response from peers API') - } - } catch (error) { - console.error('Failed to load peers:', error) - } finally { - isLoading.value = false - } -} -// Subscribe to all peers for notifications (without loading full message history) -const subscribeToAllPeers = async () => { - if (!peers.value.length) { - console.log('No peers to subscribe to') - return - } - - // Wait for connection to be established - if (!isConnected.value) { - console.log('Waiting for connection to be established before subscribing to peers') - // Wait a bit for connection to establish - await new Promise(resolve => setTimeout(resolve, 1000)) - - if (!isConnected.value) { - console.warn('Still not connected, skipping peer subscriptions') - return - } - } - - console.log(`Subscribing to ${peers.value.length} peers for notifications`) - console.log('Peers to subscribe to:', peers.value.map(p => ({ pubkey: p.pubkey, username: p.username }))) - - let successCount = 0 - let errorCount = 0 - - for (const peer of peers.value) { - try { - console.log(`Attempting to subscribe to peer: ${peer.pubkey} (${peer.username})`) - // Subscribe to peer for notifications only (don't load full history) - const subscription = await subscribeToPeerForNotifications(peer.pubkey) - if (subscription) { - console.log(`Successfully subscribed to notifications for peer: ${peer.pubkey}`) - successCount++ - } else { - console.warn(`Failed to create subscription for peer: ${peer.pubkey}`) - errorCount++ - } - } catch (error) { - console.warn(`Failed to subscribe to peer ${peer.pubkey}:`, error) - errorCount++ - } - } - - console.log(`Subscription summary: ${successCount} successful, ${errorCount} failed`) -} -const refreshPeers = () => { - loadPeers() +const refreshPeers = async () => { + console.log('Refreshing peers and chat data...') + await chatPreloader.preloadChat() } const selectPeer = async (peer: Peer) => { @@ -693,21 +600,22 @@ onMounted(async () => { } } - console.log('Starting connection and peer loading...') - await connect() - console.log('Connection established, loading peers...') - await loadPeers() - console.log('Peers loaded, checking if we should subscribe...') + console.log('Chat component mounted - checking if preloader has data...') - // If we're connected and have peers, subscribe to them - if (isConnected.value && peers.value.length > 0) { - console.log('Connection and peers ready, subscribing to all peers for notifications') - await subscribeToAllPeers() + // If chat is already preloaded, we're good to go + if (chatPreloader.isPreloaded.value) { + console.log('Chat data was preloaded, connecting to chat...') + await connect() + console.log('Chat connected successfully') + } else if (!chatPreloader.isPreloading.value) { + // If not preloaded and not currently preloading, trigger preload + console.log('Chat data not preloaded, triggering preload...') + await chatPreloader.preloadChat() + await connect() } else { - console.log('Not ready to subscribe yet:', { - isConnected: isConnected.value, - peerCount: peers.value.length - }) + // Currently preloading, just connect + console.log('Chat is currently preloading, just connecting...') + await connect() } }) @@ -716,13 +624,10 @@ onUnmounted(() => { disconnect() }) -// Watch for connection state changes and subscribe to peers when connected +// Watch for connection state changes watch(isConnected, async (connected, prevConnected) => { console.log('Connection state changed:', { connected, prevConnected, peerCount: peers.value.length }) - if (connected && peers.value.length > 0 && !prevConnected) { - console.log('Connection established and peers available, subscribing to peers for notifications') - await subscribeToAllPeers() - } + // Note: Peer subscriptions are handled by the preloader }) // Watch for new messages and scroll to bottom diff --git a/src/composables/useNostrChatPreloader.ts b/src/composables/useNostrChatPreloader.ts new file mode 100644 index 0000000..f8745f2 --- /dev/null +++ b/src/composables/useNostrChatPreloader.ts @@ -0,0 +1,166 @@ +import { ref, readonly } from 'vue' +import { useNostrChat } from './useNostrChat' +import { getAuthToken } from '@/lib/config/lnbits' +import { config } from '@/lib/config' + +export interface Peer { + user_id: string + username: string + pubkey: string +} + +export function useNostrChatPreloader() { + const isPreloading = ref(false) + const isPreloaded = ref(false) + const preloadError = ref(null) + const peers = ref([]) + + const chat = useNostrChat() + + const preloadChat = async () => { + // Don't preload if already done or currently preloading + if (isPreloaded.value || isPreloading.value) { + return + } + + try { + isPreloading.value = true + preloadError.value = null + + console.log('Preloading chat data...') + + // Connect to chat + await chat.connect() + + // Load peers + await loadPeers() + + // Subscribe to all peers for notifications (without loading full history) + if (peers.value.length > 0) { + console.log(`Subscribing to ${peers.value.length} peers for notifications`) + await subscribeToAllPeersForNotifications() + } + + isPreloaded.value = true + console.log('Chat data preloaded successfully') + + } catch (error) { + console.error('Failed to preload chat:', error) + preloadError.value = error instanceof Error ? error.message : 'Failed to preload chat' + // Don't throw error, let the UI handle it gracefully + } finally { + isPreloading.value = false + } + } + + const loadPeers = async () => { + try { + const authToken = getAuthToken() + if (!authToken) { + console.warn('No authentication token found - cannot load peers') + return + } + + const API_BASE_URL = config.api.baseUrl || 'http://localhost:5006' + const response = await fetch(`${API_BASE_URL}/api/v1/auth/nostr/pubkeys`, { + headers: { + 'Authorization': `Bearer ${authToken}`, + 'Content-Type': 'application/json' + } + }) + + if (!response.ok) { + const errorText = await response.text() + console.error('Peers API Error:', response.status, errorText) + throw new Error(`Failed to load peers: ${response.status}`) + } + + const responseText = await response.text() + + try { + const data = JSON.parse(responseText) + peers.value = data.map((peer: any) => ({ + user_id: peer.user_id, + username: peer.username, + pubkey: peer.pubkey + })) + + console.log(`Loaded ${peers.value.length} peers for chat preloader`) + + } catch (parseError) { + console.error('JSON Parse Error for peers:', parseError) + throw new Error('Invalid JSON response from peers API') + } + } catch (error) { + console.error('Failed to load peers in preloader:', error) + throw error + } + } + + // Subscribe to all peers for notifications (without loading full message history) + const subscribeToAllPeersForNotifications = async () => { + if (!peers.value.length) { + console.log('No peers to subscribe to') + return + } + + // Wait for connection to be established + if (!chat.isConnected.value) { + console.log('Waiting for connection to be established before subscribing to peers') + // Wait a bit for connection to establish + await new Promise(resolve => setTimeout(resolve, 1000)) + + if (!chat.isConnected.value) { + console.warn('Still not connected, skipping peer subscriptions') + return + } + } + + console.log(`Subscribing to ${peers.value.length} peers for notifications`) + + let successCount = 0 + let errorCount = 0 + + for (const peer of peers.value) { + try { + console.log(`Attempting to subscribe to peer: ${peer.pubkey} (${peer.username})`) + // Subscribe to peer for notifications only (don't load full history) + const subscription = await chat.subscribeToPeerForNotifications(peer.pubkey) + if (subscription) { + console.log(`Successfully subscribed to notifications for peer: ${peer.pubkey}`) + successCount++ + } else { + console.warn(`Failed to create subscription for peer: ${peer.pubkey}`) + errorCount++ + } + } catch (error) { + console.warn(`Failed to subscribe to peer ${peer.pubkey}:`, error) + errorCount++ + } + } + + console.log(`Chat preloader subscription summary: ${successCount} successful, ${errorCount} failed`) + } + + const resetPreload = () => { + isPreloaded.value = false + preloadError.value = null + peers.value = [] + } + + return { + isPreloading: readonly(isPreloading), + isPreloaded: readonly(isPreloaded), + preloadError: readonly(preloadError), + peers: readonly(peers), + preloadChat, + resetPreload, + + // Expose chat composable methods for global access + getTotalUnreadCount: chat.getTotalUnreadCount, + getUnreadCount: chat.getUnreadCount, + getAllUnreadCounts: chat.getAllUnreadCounts, + markMessagesAsRead: chat.markMessagesAsRead, + clearAllUnreadCounts: chat.clearAllUnreadCounts + } +} \ No newline at end of file