Add BaseService and refactor ChatService for improved dependency management

- Introduce BaseService as a foundational class for services, providing standardized dependency injection and initialization logic.
- Refactor ChatService to extend BaseService, enhancing its initialization process and dependency handling.
- Implement service metadata and structured initialization in ChatService, allowing for better tracking and error handling during service setup.
- Update chat module to initialize ChatService with dependency management, ensuring readiness before use.
This commit is contained in:
padreug 2025-09-05 06:19:08 +02:00
parent c7fcd51990
commit 8d4c389f71
4 changed files with 398 additions and 66 deletions

View file

@ -35,6 +35,15 @@ export const chatModule: ModulePlugin = {
const chatService = new ChatService(config)
container.provide(CHAT_SERVICE_TOKEN, chatService)
// Initialize the service (will handle dependency injection)
await chatService.initialize({
waitForDependencies: true,
maxRetries: 3
}).catch(error => {
console.warn('💬 Chat service initialization deferred:', error)
// Service will auto-initialize when dependencies are available
})
// Also make it globally available for other modules (like market)
;(globalThis as any).chatService = chatService

View file

@ -1,6 +1,6 @@
import { ref, computed } from 'vue'
import { eventBus } from '@/core/event-bus'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import { BaseService } from '@/core/base/BaseService'
import { nip04, finalizeEvent, type Event, type EventTemplate } from 'nostr-tools'
import type { ChatMessage, ChatPeer, UnreadMessageData, ChatConfig } from '../types'
import { getAuthToken } from '@/lib/config/lnbits'
@ -9,20 +9,25 @@ import { config } from '@/lib/config'
const UNREAD_MESSAGES_KEY = 'nostr-chat-unread-messages'
const PEERS_KEY = 'nostr-chat-peers'
export class ChatService {
export class ChatService extends BaseService {
// Service metadata
protected readonly metadata = {
name: 'ChatService',
version: '1.0.0',
dependencies: ['RelayHub', 'AuthService']
}
// Service-specific state
private messages = ref<Map<string, ChatMessage[]>>(new Map())
private peers = ref<Map<string, ChatPeer>>(new Map())
private config: ChatConfig
private subscriptionUnsubscriber?: () => void
private isInitialized = ref(false)
private marketMessageHandler?: (event: any) => Promise<void>
constructor(config: ChatConfig) {
super()
this.config = config
this.loadPeersFromStorage()
// Defer initialization until services are available
this.deferredInitialization()
}
// Register market message handler for forwarding market-related DMs
@ -30,47 +35,43 @@ export class ChatService {
this.marketMessageHandler = handler
}
// Defer initialization until services are ready
private deferredInitialization(): void {
// Try initialization immediately
this.tryInitialization()
// Also listen for auth events to re-initialize when user logs in
eventBus.on('auth:login', () => {
console.log('💬 Auth login detected, initializing chat...')
this.tryInitialization()
})
}
// Try to initialize services if they're available
private async tryInitialization(): Promise<void> {
try {
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
/**
* 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')
if (!relayHub || !authService?.user?.value?.pubkey) {
console.log('💬 Services not ready yet, will retry when auth completes...')
return
}
console.log('💬 Services ready, initializing chat functionality...')
// Load peers from API
await this.loadPeersFromAPI().catch(error => {
console.warn('Failed to load peers from API:', error)
// 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()
// Re-inject dependencies and complete initialization
await this.waitForDependencies()
await this.completeInitialization()
})
// Initialize message handling (subscription + history loading)
await this.initializeMessageHandling()
// Mark as initialized
this.isInitialized.value = true
console.log('💬 Chat service fully initialized and ready!')
} catch (error) {
console.error('💬 Failed to initialize chat:', error)
this.isInitialized.value = false
return
}
await this.completeInitialization()
}
/**
* Complete the initialization once all dependencies are available
*/
private async completeInitialization(): Promise<void> {
// Load peers from API
await this.loadPeersFromAPI().catch(error => {
console.warn('Failed to load peers from API:', error)
})
// Initialize message handling (subscription + history loading)
await this.initializeMessageHandling()
this.debug('Chat service fully initialized and ready!')
}
// Initialize message handling (subscription + history loading)
@ -95,7 +96,7 @@ export class ChatService {
}
get isReady() {
return computed(() => this.isInitialized.value)
return this.isInitialized
}
// Get messages for a specific peer
@ -197,18 +198,17 @@ export class ChatService {
// Check if services are available for messaging
private checkServicesAvailable(): { relayHub: any; authService: any } | null {
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
// Dependencies are already injected by BaseService
if (!relayHub || !authService?.user?.value?.prvkey) {
if (!this.relayHub || !this.authService?.user?.value?.prvkey) {
return null
}
if (!relayHub.isConnected) {
if (!this.relayHub.isConnected) {
return null
}
return { relayHub, authService }
return { relayHub: this.relayHub, authService: this.authService }
}
// Send a message
@ -390,16 +390,15 @@ export class ChatService {
// Load message history for known peers
private async loadMessageHistory(): Promise<void> {
try {
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
// Dependencies are already injected by BaseService
if (!relayHub || !authService?.user?.value?.pubkey) {
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
console.warn('Cannot load message history: missing services')
return
}
const userPubkey = authService.user.value.pubkey
const userPrivkey = authService.user.value.prvkey
const userPubkey = this.authService.user.value.pubkey
const userPrivkey = this.authService.user.value.prvkey
const peerPubkeys = Array.from(this.peers.value.keys())
if (peerPubkeys.length === 0) {
@ -411,7 +410,7 @@ export class ChatService {
// Query historical messages (kind 4) to/from known peers
// We need separate queries for sent vs received messages due to different tagging
const receivedEvents = await relayHub.queryEvents([
const receivedEvents = await this.relayHub.queryEvents([
{
kinds: [4],
authors: peerPubkeys, // Messages from peers
@ -420,7 +419,7 @@ export class ChatService {
}
])
const sentEvents = await relayHub.queryEvents([
const sentEvents = await this.relayHub.queryEvents([
{
kinds: [4],
authors: [userPubkey], // Messages from us
@ -474,29 +473,28 @@ export class ChatService {
// Setup subscription for incoming messages
private setupMessageSubscription(): void {
try {
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
// Dependencies are already injected by BaseService
if (!relayHub || !authService?.user?.value?.pubkey) {
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
console.warn('💬 Cannot setup message subscription: missing services')
return
}
if (!relayHub.isConnected) {
if (!this.relayHub.isConnected) {
console.warn('💬 RelayHub not connected, waiting for connection...')
// Listen for connection event
relayHub.on('connected', () => {
this.relayHub.on('connected', () => {
console.log('💬 RelayHub connected, setting up message subscription...')
this.setupMessageSubscription()
})
return
}
const userPubkey = authService.user.value.pubkey
const userPrivkey = authService.user.value.prvkey
const userPubkey = this.authService.user.value.pubkey
const userPrivkey = this.authService.user.value.prvkey
// Subscribe to encrypted direct messages (kind 4) addressed to this user
this.subscriptionUnsubscriber = relayHub.subscribe({
this.subscriptionUnsubscriber = this.relayHub.subscribe({
id: 'chat-messages',
filters: [
{
@ -573,14 +571,26 @@ export class ChatService {
}
}
// Cleanup
destroy(): void {
/**
* Cleanup when service is disposed (overrides BaseService)
*/
protected async onDispose(): Promise<void> {
// Unsubscribe from message subscription
if (this.subscriptionUnsubscriber) {
this.subscriptionUnsubscriber()
this.subscriptionUnsubscriber = undefined
}
this.messages.value.clear()
this.peers.value.clear()
this.debug('Chat service disposed')
}
/**
* Legacy destroy method for backward compatibility
*/
destroy(): void {
this.dispose()
}
}