refactor: Replace Nostr chat preloader with a singleton pattern for improved state management
- Remove the useNostrChatPreloader composable and integrate its functionality into the useNostrChat composable, streamlining chat data handling. - Update App.vue and ChatComponent to utilize the new singleton instance for managing chat connections and peer subscriptions. - Enhance Navbar and ChatComponent to reflect changes in unread message tracking and peer management, improving user experience. - Ensure proper error handling and logging during chat connection and peer loading processes.
This commit is contained in:
parent
855a003962
commit
2dec184c42
5 changed files with 160 additions and 209 deletions
|
|
@ -77,6 +77,9 @@ export function useNostrChat() {
|
|||
|
||||
// Track latest message timestamp for each peer (for sorting)
|
||||
const latestMessageTimestamps = ref<Map<string, number>>(new Map())
|
||||
|
||||
// Store peers globally
|
||||
const peers = ref<any[]>([])
|
||||
|
||||
// Computed
|
||||
const isLoggedIn = computed(() => !!currentUser.value)
|
||||
|
|
@ -100,6 +103,15 @@ export function useNostrChat() {
|
|||
return total
|
||||
}
|
||||
|
||||
// Reactive computed total unread count
|
||||
const totalUnreadCount = computed(() => {
|
||||
let total = 0
|
||||
for (const count of unreadCounts.value.values()) {
|
||||
total += count
|
||||
}
|
||||
return total
|
||||
})
|
||||
|
||||
// Get latest message timestamp for a peer
|
||||
const getLatestMessageTimestamp = (peerPubkey: string): number => {
|
||||
return latestMessageTimestamps.value.get(peerPubkey) || 0
|
||||
|
|
@ -798,11 +810,87 @@ export function useNostrChat() {
|
|||
messages.value.delete(peerPubkey)
|
||||
}
|
||||
|
||||
// Load peers from API
|
||||
const loadPeers = async () => {
|
||||
try {
|
||||
const authToken = getAuthToken()
|
||||
if (!authToken) {
|
||||
throw new Error('No authentication token found')
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new Error(`Failed to load peers: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const loadedPeers = data.map((peer: any) => ({
|
||||
user_id: peer.user_id,
|
||||
username: peer.username,
|
||||
pubkey: peer.pubkey
|
||||
}))
|
||||
|
||||
// Store peers in the singleton state
|
||||
peers.value = loadedPeers
|
||||
console.log(`Loaded ${loadedPeers.length} peers`)
|
||||
|
||||
return loadedPeers
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load peers:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to all peers for notifications (without loading full message history)
|
||||
const subscribeToAllPeersForNotifications = async (peers: any[]) => {
|
||||
if (!peers.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.length} peers for notifications`)
|
||||
|
||||
// Subscribe to each peer for notifications
|
||||
for (const peer of peers) {
|
||||
try {
|
||||
await subscribeToPeerForNotifications(peer.pubkey)
|
||||
} catch (error) {
|
||||
console.error(`Failed to subscribe to peer ${peer.username} (${peer.pubkey}):`, error)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Successfully subscribed to ${peers.length} peers for notifications`)
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
isConnected: readonly(isConnected),
|
||||
messages: readonly(messages),
|
||||
isLoggedIn: readonly(isLoggedIn),
|
||||
peers: readonly(peers),
|
||||
|
||||
// Reactive computed properties
|
||||
totalUnreadCount: readonly(totalUnreadCount),
|
||||
|
||||
// Methods
|
||||
connect,
|
||||
|
|
@ -825,6 +913,13 @@ export function useNostrChat() {
|
|||
|
||||
// Timestamp methods (for sorting)
|
||||
getLatestMessageTimestamp,
|
||||
getAllLatestMessageTimestamps
|
||||
getAllLatestMessageTimestamps,
|
||||
|
||||
// Peer management methods
|
||||
loadPeers,
|
||||
subscribeToAllPeersForNotifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance for global state
|
||||
export const nostrChat = useNostrChat()
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
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<string | null>(null)
|
||||
const peers = ref<Peer[]>([])
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue