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