Enhance ChatService initialization and authentication handling

- Introduce periodic authentication checks to ensure full initialization of the ChatService.
- Implement a flag to prevent redundant initialization.
- Update message handling and subscription methods to accommodate both injected and global authentication services.
- Improve error handling and logging for message subscription setup and processing.
- Clear authentication check intervals during service disposal to prevent memory leaks.

This commit improves the reliability and responsiveness of the chat service in handling user authentication and message subscriptions.
This commit is contained in:
padreug 2025-09-06 23:26:35 +02:00
parent 034f3ce80f
commit 4db7645a8f

View file

@ -22,6 +22,8 @@ export class ChatService extends BaseService {
private subscriptionUnsubscriber?: () => void
private marketMessageHandler?: (event: any) => Promise<void>
private visibilityUnsubscribe?: () => void
private isFullyInitialized = false
private authCheckInterval?: ReturnType<typeof setInterval>
constructor(config: ChatConfig) {
super()
@ -38,20 +40,58 @@ export class ChatService extends BaseService {
* Service-specific initialization (called by BaseService)
*/
protected async onInitialize(): Promise<void> {
// Check if we have user pubkey
if (!this.authService?.user?.value?.pubkey) {
this.debug('User not authenticated yet, deferring full initialization')
this.debug('Chat service onInitialize called')
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
this.debug('Auth detection:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth,
authServicePubkey: hasAuthService ? hasAuthService.substring(0, 10) + '...' : null,
globalAuthPubkey: hasGlobalAuth ? hasGlobalAuth.substring(0, 10) + '...' : null
})
if (!hasAuthService && !hasGlobalAuth) {
this.debug('User not authenticated yet, deferring full initialization with periodic check')
// Listen for auth events to complete initialization when user logs in
const unsubscribe = eventBus.on('auth:login', async () => {
this.debug('Auth login detected, completing chat initialization...')
unsubscribe()
if (this.authCheckInterval) {
clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined
}
// Re-inject dependencies and complete initialization
await this.waitForDependencies()
await this.completeInitialization()
})
// Also check periodically in case we missed the auth event
this.authCheckInterval = setInterval(async () => {
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
if (hasAuthService || hasGlobalAuth) {
this.debug('Auth detected via periodic check, completing initialization')
if (this.authCheckInterval) {
clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined
}
unsubscribe()
await this.waitForDependencies()
await this.completeInitialization()
}
}, 2000) // Check every 2 seconds
return
}
@ -62,6 +102,13 @@ export class ChatService extends BaseService {
* Complete the initialization once all dependencies are available
*/
private async completeInitialization(): Promise<void> {
if (this.isFullyInitialized) {
this.debug('Chat service already fully initialized, skipping')
return
}
this.debug('Completing chat service initialization...')
// Load peers from storage first
this.loadPeersFromStorage()
@ -76,13 +123,14 @@ export class ChatService extends BaseService {
// Register with visibility service
this.registerWithVisibilityService()
this.isFullyInitialized = true
this.debug('Chat service fully initialized and ready!')
}
// Initialize message handling (subscription + history loading)
async initializeMessageHandling(): Promise<void> {
// Set up real-time subscription
this.setupMessageSubscription()
await this.setupMessageSubscription()
// Load message history for known peers
await this.loadMessageHistory()
@ -198,14 +246,29 @@ export class ChatService extends BaseService {
// Refresh peers from API
async refreshPeers(): Promise<void> {
// Check if we should trigger full initialization
const { auth } = await import('@/composables/useAuth')
const hasAuth = this.authService?.user?.value?.pubkey || auth.currentUser.value?.pubkey
if (!this.isFullyInitialized && hasAuth) {
console.log('💬 Refresh peers triggered full initialization')
await this.completeInitialization()
}
return this.loadPeersFromAPI()
}
// Check if services are available for messaging
private checkServicesAvailable(): { relayHub: any; authService: any } | null {
// Dependencies are already injected by BaseService
private async checkServicesAvailable(): Promise<{ relayHub: any; authService: any; userPubkey: string; userPrivkey: string } | null> {
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.prvkey
const hasGlobalAuth = auth.currentUser.value?.prvkey
if (!this.relayHub || !this.authService?.user?.value?.prvkey) {
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
return null
}
@ -213,22 +276,24 @@ export class ChatService extends BaseService {
return null
}
return { relayHub: this.relayHub, authService: this.authService }
return {
relayHub: this.relayHub,
authService: this.authService || auth,
userPubkey: userPubkey!,
userPrivkey: userPrivkey!
}
}
// Send a message
async sendMessage(peerPubkey: string, content: string): Promise<void> {
try {
const services = this.checkServicesAvailable()
const services = await this.checkServicesAvailable()
if (!services) {
throw new Error('Chat services not ready. Please wait for connection to establish.')
}
const { relayHub, authService } = services
const userPrivkey = authService.user.value.prvkey
const userPubkey = authService.user.value.pubkey
const { relayHub, userPrivkey, userPubkey } = services
// Encrypt the message using NIP-04
const encryptedContent = await nip04.encrypt(userPrivkey, peerPubkey, content)
@ -378,15 +443,23 @@ export class ChatService extends BaseService {
// Load message history for known peers
private async loadMessageHistory(): Promise<void> {
try {
// Dependencies are already injected by BaseService
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
console.warn('Cannot load message history: missing services')
return
}
const userPubkey = this.authService.user.value.pubkey
const userPrivkey = this.authService.user.value.prvkey
if (!userPubkey || !userPrivkey) {
console.warn('Cannot load message history: missing user keys')
return
}
const peerPubkeys = Array.from(this.peers.value.keys())
if (peerPubkeys.length === 0) {
@ -459,12 +532,26 @@ export class ChatService extends BaseService {
}
// Setup subscription for incoming messages
private setupMessageSubscription(): void {
private async setupMessageSubscription(): Promise<void> {
try {
// Dependencies are already injected by BaseService
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
this.debug('Setup message subscription auth check:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth,
hasRelayHub: !!this.relayHub,
relayHubConnected: this.relayHub?.isConnected,
userPubkey: userPubkey ? userPubkey.substring(0, 10) + '...' : null
})
if (!this.relayHub || (!hasAuthService && !hasGlobalAuth)) {
console.warn('💬 Cannot setup message subscription: missing services')
// Retry after 2 seconds
setTimeout(() => this.setupMessageSubscription(), 2000)
return
}
@ -475,10 +562,16 @@ export class ChatService extends BaseService {
console.log('💬 RelayHub connected, setting up message subscription...')
this.setupMessageSubscription()
})
// Also retry after timeout in case event is missed
setTimeout(() => this.setupMessageSubscription(), 5000)
return
}
const userPubkey = this.authService.user.value.pubkey
if (!userPubkey) {
console.warn('💬 No user pubkey available for subscription')
setTimeout(() => this.setupMessageSubscription(), 2000)
return
}
// Subscribe to encrypted direct messages (kind 4) addressed to this user
this.subscriptionUnsubscriber = this.relayHub.subscribe({
@ -498,14 +591,16 @@ export class ChatService extends BaseService {
await this.processIncomingMessage(event)
},
onEose: () => {
console.log('Chat message subscription EOSE received')
console.log('💬 Chat message subscription EOSE received')
}
})
console.log('Chat message subscription set up successfully')
console.log('💬 Chat message subscription set up successfully for pubkey:', userPubkey.substring(0, 10) + '...')
} catch (error) {
console.error('Failed to setup message subscription:', error)
console.error('💬 Failed to setup message subscription:', error)
// Retry after delay
setTimeout(() => this.setupMessageSubscription(), 3000)
}
}
@ -575,8 +670,13 @@ export class ChatService extends BaseService {
*/
private async processIncomingMessage(event: any): Promise<void> {
try {
const userPubkey = this.authService?.user?.value?.pubkey
const userPrivkey = this.authService?.user?.value?.prvkey
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
const userPrivkey = hasAuthService ? this.authService.user.value.prvkey : auth.currentUser.value?.prvkey
if (!userPubkey || !userPrivkey) {
console.warn('Cannot process message: user not authenticated')
@ -637,7 +737,13 @@ export class ChatService extends BaseService {
* Load recent messages for a specific peer
*/
private async loadRecentMessagesForPeer(peerPubkey: string): Promise<void> {
const userPubkey = this.authService?.user?.value?.pubkey
// Check both injected auth service AND global auth composable
const { auth } = await import('@/composables/useAuth')
const hasAuthService = this.authService?.user?.value?.pubkey
const hasGlobalAuth = auth.currentUser.value?.pubkey
const userPubkey = hasAuthService ? this.authService.user.value.pubkey : auth.currentUser.value?.pubkey
if (!userPubkey || !this.relayHub) return
try {
@ -675,6 +781,12 @@ export class ChatService extends BaseService {
* Cleanup when service is disposed (overrides BaseService)
*/
protected async onDispose(): Promise<void> {
// Clear auth check interval
if (this.authCheckInterval) {
clearInterval(this.authCheckInterval)
this.authCheckInterval = undefined
}
// Unregister from visibility service
if (this.visibilityUnsubscribe) {
this.visibilityUnsubscribe()
@ -689,6 +801,7 @@ export class ChatService extends BaseService {
this.messages.value.clear()
this.peers.value.clear()
this.isFullyInitialized = false
this.debug('Chat service disposed')
}