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:
parent
034f3ce80f
commit
4db7645a8f
1 changed files with 140 additions and 27 deletions
|
|
@ -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')
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue