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:
parent
c7fcd51990
commit
8d4c389f71
4 changed files with 398 additions and 66 deletions
311
src/core/base/BaseService.ts
Normal file
311
src/core/base/BaseService.ts
Normal file
|
|
@ -0,0 +1,311 @@
|
||||||
|
import { tryInjectService, SERVICE_TOKENS, type ServiceToken } from '@/core/di-container'
|
||||||
|
import { eventBus } from '@/core/event-bus'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service metadata for tracking and debugging
|
||||||
|
*/
|
||||||
|
export interface ServiceMetadata {
|
||||||
|
name: string
|
||||||
|
version?: string
|
||||||
|
dependencies?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service initialization options
|
||||||
|
*/
|
||||||
|
export interface ServiceInitOptions {
|
||||||
|
waitForDependencies?: boolean
|
||||||
|
maxRetries?: number
|
||||||
|
retryDelay?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for all services in the modular architecture
|
||||||
|
* Provides standardized dependency injection, initialization, and error handling
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* export class MyService extends BaseService {
|
||||||
|
* protected metadata = {
|
||||||
|
* name: 'MyService',
|
||||||
|
* dependencies: ['RelayHub', 'AuthService']
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* protected async onInitialize(): Promise<void> {
|
||||||
|
* // Service-specific initialization
|
||||||
|
* await this.setupSubscriptions()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export abstract class BaseService {
|
||||||
|
// Core dependencies with proper typing
|
||||||
|
protected relayHub: any = null
|
||||||
|
protected authService: any = null
|
||||||
|
protected nostrClientHub: any = null
|
||||||
|
|
||||||
|
// Service state
|
||||||
|
public readonly isInitialized: Ref<boolean> = ref(false)
|
||||||
|
public readonly isInitializing: Ref<boolean> = ref(false)
|
||||||
|
public readonly initError: Ref<Error | null> = ref(null)
|
||||||
|
|
||||||
|
// Service metadata
|
||||||
|
protected abstract readonly metadata: ServiceMetadata
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Dependencies will be injected after construction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the service with dependency injection and error handling
|
||||||
|
*/
|
||||||
|
public async initialize(options: ServiceInitOptions = {}): Promise<void> {
|
||||||
|
const {
|
||||||
|
waitForDependencies = true,
|
||||||
|
maxRetries = 3,
|
||||||
|
retryDelay = 1000
|
||||||
|
} = options
|
||||||
|
|
||||||
|
if (this.isInitialized.value) {
|
||||||
|
console.log(`✅ ${this.metadata.name} already initialized`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isInitializing.value) {
|
||||||
|
console.log(`⏳ ${this.metadata.name} is already initializing...`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitializing.value = true
|
||||||
|
this.initError.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`🚀 Initializing ${this.metadata.name}...`)
|
||||||
|
|
||||||
|
// Inject dependencies
|
||||||
|
await this.injectDependencies(waitForDependencies, maxRetries, retryDelay)
|
||||||
|
|
||||||
|
// Call service-specific initialization
|
||||||
|
await this.onInitialize()
|
||||||
|
|
||||||
|
this.isInitialized.value = true
|
||||||
|
console.log(`✅ ${this.metadata.name} initialized successfully`)
|
||||||
|
|
||||||
|
// Emit initialization event
|
||||||
|
eventBus.emit(`service:initialized`, {
|
||||||
|
service: this.metadata.name,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, this.metadata.name)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
const err = error instanceof Error ? error : new Error(String(error))
|
||||||
|
this.initError.value = err
|
||||||
|
|
||||||
|
console.error(`❌ Failed to initialize ${this.metadata.name}:`, err)
|
||||||
|
|
||||||
|
// Emit error event
|
||||||
|
eventBus.emit(`service:error`, {
|
||||||
|
service: this.metadata.name,
|
||||||
|
error: err.message,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, this.metadata.name)
|
||||||
|
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.isInitializing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject required dependencies with retry logic
|
||||||
|
*/
|
||||||
|
protected async injectDependencies(
|
||||||
|
waitForDependencies: boolean,
|
||||||
|
maxRetries: number,
|
||||||
|
retryDelay: number
|
||||||
|
): Promise<void> {
|
||||||
|
let retries = 0
|
||||||
|
|
||||||
|
while (retries < maxRetries) {
|
||||||
|
try {
|
||||||
|
// Try to inject core dependencies
|
||||||
|
this.relayHub = tryInjectService(SERVICE_TOKENS.RELAY_HUB)
|
||||||
|
this.authService = tryInjectService(SERVICE_TOKENS.AUTH_SERVICE)
|
||||||
|
this.nostrClientHub = tryInjectService(SERVICE_TOKENS.NOSTR_CLIENT_HUB)
|
||||||
|
|
||||||
|
// Check if all required dependencies are available
|
||||||
|
const missingDeps = this.getMissingDependencies()
|
||||||
|
|
||||||
|
if (missingDeps.length === 0) {
|
||||||
|
console.log(`✅ All dependencies injected for ${this.metadata.name}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!waitForDependencies) {
|
||||||
|
console.warn(`⚠️ ${this.metadata.name} starting with missing dependencies:`, missingDeps)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries < maxRetries - 1) {
|
||||||
|
console.log(`⏳ Waiting for dependencies (${missingDeps.join(', ')})... Retry ${retries + 1}/${maxRetries}`)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
||||||
|
retries++
|
||||||
|
} else {
|
||||||
|
throw new Error(`Missing required dependencies: ${missingDeps.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (retries >= maxRetries - 1) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
retries++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of missing dependencies
|
||||||
|
*/
|
||||||
|
protected getMissingDependencies(): string[] {
|
||||||
|
const missing: string[] = []
|
||||||
|
const deps = this.metadata.dependencies || []
|
||||||
|
|
||||||
|
if (deps.includes('RelayHub') && !this.relayHub) {
|
||||||
|
missing.push('RelayHub')
|
||||||
|
}
|
||||||
|
if (deps.includes('AuthService') && !this.authService) {
|
||||||
|
missing.push('AuthService')
|
||||||
|
}
|
||||||
|
if (deps.includes('NostrClientHub') && !this.nostrClientHub) {
|
||||||
|
missing.push('NostrClientHub')
|
||||||
|
}
|
||||||
|
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for specific dependencies to be available
|
||||||
|
*/
|
||||||
|
protected async waitForDependencies(timeout = 10000): Promise<void> {
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
const missingDeps = this.getMissingDependencies()
|
||||||
|
|
||||||
|
if (missingDeps.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Timeout waiting for dependencies: ${this.getMissingDependencies().join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user is authenticated (helper method)
|
||||||
|
*/
|
||||||
|
protected requireAuth(): void {
|
||||||
|
if (!this.authService?.isAuthenticated?.value) {
|
||||||
|
throw new Error('Authentication required')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user pubkey (helper method)
|
||||||
|
*/
|
||||||
|
protected getUserPubkey(): string | undefined {
|
||||||
|
return this.authService?.user?.value?.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle errors consistently across all services
|
||||||
|
*/
|
||||||
|
protected handleError(error: unknown, context: string): Error {
|
||||||
|
const err = error instanceof Error ? error : new Error(String(error))
|
||||||
|
|
||||||
|
console.error(`[${this.metadata.name}] Error in ${context}:`, err)
|
||||||
|
|
||||||
|
// Emit error event for monitoring
|
||||||
|
eventBus.emit('service:error', {
|
||||||
|
service: this.metadata.name,
|
||||||
|
context,
|
||||||
|
error: err.message,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}, this.metadata.name)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log debug information (only in development)
|
||||||
|
*/
|
||||||
|
protected debug(...args: any[]): void {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`[${this.metadata.name}]`, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract method for service-specific initialization
|
||||||
|
* Must be implemented by derived classes
|
||||||
|
*/
|
||||||
|
protected abstract onInitialize(): Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup method for service disposal
|
||||||
|
*/
|
||||||
|
public async dispose(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.onDispose()
|
||||||
|
|
||||||
|
this.isInitialized.value = false
|
||||||
|
this.relayHub = null
|
||||||
|
this.authService = null
|
||||||
|
this.nostrClientHub = null
|
||||||
|
|
||||||
|
console.log(`♻️ ${this.metadata.name} disposed`)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to dispose ${this.metadata.name}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional cleanup logic for derived classes
|
||||||
|
*/
|
||||||
|
protected async onDispose(): Promise<void> {
|
||||||
|
// Override in derived classes if cleanup is needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type-safe service class with proper dependency typing
|
||||||
|
* Use this when you need full type safety for dependencies
|
||||||
|
*/
|
||||||
|
export abstract class TypedBaseService<TDeps extends Record<string, any> = {}> extends BaseService {
|
||||||
|
protected dependencies!: TDeps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get typed dependency
|
||||||
|
*/
|
||||||
|
protected getDependency<K extends keyof TDeps>(key: K): TDeps[K] {
|
||||||
|
return this.dependencies[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject typed dependencies
|
||||||
|
*/
|
||||||
|
protected async injectTypedDependencies(tokens: Record<keyof TDeps, ServiceToken>): Promise<void> {
|
||||||
|
this.dependencies = {} as TDeps
|
||||||
|
|
||||||
|
for (const [key, token] of Object.entries(tokens)) {
|
||||||
|
const service = tryInjectService(token as ServiceToken)
|
||||||
|
if (service) {
|
||||||
|
(this.dependencies as any)[key] = service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import type { DIContainer, ServiceToken } from './types'
|
import type { DIContainer, ServiceToken } from './types'
|
||||||
|
|
||||||
|
export type { ServiceToken } from './types'
|
||||||
|
|
||||||
interface ServiceRegistration {
|
interface ServiceRegistration {
|
||||||
service: any
|
service: any
|
||||||
scope: 'singleton' | 'transient'
|
scope: 'singleton' | 'transient'
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,15 @@ export const chatModule: ModulePlugin = {
|
||||||
const chatService = new ChatService(config)
|
const chatService = new ChatService(config)
|
||||||
container.provide(CHAT_SERVICE_TOKEN, chatService)
|
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)
|
// Also make it globally available for other modules (like market)
|
||||||
;(globalThis as any).chatService = chatService
|
;(globalThis as any).chatService = chatService
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { eventBus } from '@/core/event-bus'
|
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 { nip04, finalizeEvent, type Event, type EventTemplate } from 'nostr-tools'
|
||||||
import type { ChatMessage, ChatPeer, UnreadMessageData, ChatConfig } from '../types'
|
import type { ChatMessage, ChatPeer, UnreadMessageData, ChatConfig } from '../types'
|
||||||
import { getAuthToken } from '@/lib/config/lnbits'
|
import { getAuthToken } from '@/lib/config/lnbits'
|
||||||
|
|
@ -9,20 +9,25 @@ import { config } from '@/lib/config'
|
||||||
const UNREAD_MESSAGES_KEY = 'nostr-chat-unread-messages'
|
const UNREAD_MESSAGES_KEY = 'nostr-chat-unread-messages'
|
||||||
const PEERS_KEY = 'nostr-chat-peers'
|
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 messages = ref<Map<string, ChatMessage[]>>(new Map())
|
||||||
private peers = ref<Map<string, ChatPeer>>(new Map())
|
private peers = ref<Map<string, ChatPeer>>(new Map())
|
||||||
private config: ChatConfig
|
private config: ChatConfig
|
||||||
private subscriptionUnsubscriber?: () => void
|
private subscriptionUnsubscriber?: () => void
|
||||||
private isInitialized = ref(false)
|
|
||||||
private marketMessageHandler?: (event: any) => Promise<void>
|
private marketMessageHandler?: (event: any) => Promise<void>
|
||||||
|
|
||||||
constructor(config: ChatConfig) {
|
constructor(config: ChatConfig) {
|
||||||
|
super()
|
||||||
this.config = config
|
this.config = config
|
||||||
this.loadPeersFromStorage()
|
this.loadPeersFromStorage()
|
||||||
|
|
||||||
// Defer initialization until services are available
|
|
||||||
this.deferredInitialization()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register market message handler for forwarding market-related DMs
|
// Register market message handler for forwarding market-related DMs
|
||||||
|
|
@ -30,31 +35,34 @@ export class ChatService {
|
||||||
this.marketMessageHandler = handler
|
this.marketMessageHandler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer initialization until services are ready
|
/**
|
||||||
private deferredInitialization(): void {
|
* Service-specific initialization (called by BaseService)
|
||||||
// Try initialization immediately
|
*/
|
||||||
this.tryInitialization()
|
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')
|
||||||
|
|
||||||
// Also listen for auth events to re-initialize when user logs in
|
// Listen for auth events to complete initialization when user logs in
|
||||||
eventBus.on('auth:login', () => {
|
const unsubscribe = eventBus.on('auth:login', async () => {
|
||||||
console.log('💬 Auth login detected, initializing chat...')
|
this.debug('Auth login detected, completing chat initialization...')
|
||||||
this.tryInitialization()
|
unsubscribe()
|
||||||
|
|
||||||
|
// Re-inject dependencies and complete initialization
|
||||||
|
await this.waitForDependencies()
|
||||||
|
await this.completeInitialization()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
if (!relayHub || !authService?.user?.value?.pubkey) {
|
|
||||||
console.log('💬 Services not ready yet, will retry when auth completes...')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('💬 Services ready, initializing chat functionality...')
|
await this.completeInitialization()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete the initialization once all dependencies are available
|
||||||
|
*/
|
||||||
|
private async completeInitialization(): Promise<void> {
|
||||||
// Load peers from API
|
// Load peers from API
|
||||||
await this.loadPeersFromAPI().catch(error => {
|
await this.loadPeersFromAPI().catch(error => {
|
||||||
console.warn('Failed to load peers from API:', error)
|
console.warn('Failed to load peers from API:', error)
|
||||||
|
|
@ -63,14 +71,7 @@ export class ChatService {
|
||||||
// Initialize message handling (subscription + history loading)
|
// Initialize message handling (subscription + history loading)
|
||||||
await this.initializeMessageHandling()
|
await this.initializeMessageHandling()
|
||||||
|
|
||||||
// Mark as initialized
|
this.debug('Chat service fully initialized and ready!')
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize message handling (subscription + history loading)
|
// Initialize message handling (subscription + history loading)
|
||||||
|
|
@ -95,7 +96,7 @@ export class ChatService {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isReady() {
|
get isReady() {
|
||||||
return computed(() => this.isInitialized.value)
|
return this.isInitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get messages for a specific peer
|
// Get messages for a specific peer
|
||||||
|
|
@ -197,18 +198,17 @@ export class ChatService {
|
||||||
|
|
||||||
// Check if services are available for messaging
|
// Check if services are available for messaging
|
||||||
private checkServicesAvailable(): { relayHub: any; authService: any } | null {
|
private checkServicesAvailable(): { relayHub: any; authService: any } | null {
|
||||||
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
|
// Dependencies are already injected by BaseService
|
||||||
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
|
|
||||||
|
|
||||||
if (!relayHub || !authService?.user?.value?.prvkey) {
|
if (!this.relayHub || !this.authService?.user?.value?.prvkey) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!relayHub.isConnected) {
|
if (!this.relayHub.isConnected) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return { relayHub, authService }
|
return { relayHub: this.relayHub, authService: this.authService }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
|
|
@ -390,16 +390,15 @@ export class ChatService {
|
||||||
// Load message history for known peers
|
// Load message history for known peers
|
||||||
private async loadMessageHistory(): Promise<void> {
|
private async loadMessageHistory(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
|
// Dependencies are already injected by BaseService
|
||||||
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
|
|
||||||
|
|
||||||
if (!relayHub || !authService?.user?.value?.pubkey) {
|
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
|
||||||
console.warn('Cannot load message history: missing services')
|
console.warn('Cannot load message history: missing services')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPubkey = authService.user.value.pubkey
|
const userPubkey = this.authService.user.value.pubkey
|
||||||
const userPrivkey = authService.user.value.prvkey
|
const userPrivkey = this.authService.user.value.prvkey
|
||||||
const peerPubkeys = Array.from(this.peers.value.keys())
|
const peerPubkeys = Array.from(this.peers.value.keys())
|
||||||
|
|
||||||
if (peerPubkeys.length === 0) {
|
if (peerPubkeys.length === 0) {
|
||||||
|
|
@ -411,7 +410,7 @@ export class ChatService {
|
||||||
|
|
||||||
// Query historical messages (kind 4) to/from known peers
|
// Query historical messages (kind 4) to/from known peers
|
||||||
// We need separate queries for sent vs received messages due to different tagging
|
// 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],
|
kinds: [4],
|
||||||
authors: peerPubkeys, // Messages from peers
|
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],
|
kinds: [4],
|
||||||
authors: [userPubkey], // Messages from us
|
authors: [userPubkey], // Messages from us
|
||||||
|
|
@ -474,29 +473,28 @@ export class ChatService {
|
||||||
// Setup subscription for incoming messages
|
// Setup subscription for incoming messages
|
||||||
private setupMessageSubscription(): void {
|
private setupMessageSubscription(): void {
|
||||||
try {
|
try {
|
||||||
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB) as any
|
// Dependencies are already injected by BaseService
|
||||||
const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
|
|
||||||
|
|
||||||
if (!relayHub || !authService?.user?.value?.pubkey) {
|
if (!this.relayHub || !this.authService?.user?.value?.pubkey) {
|
||||||
console.warn('💬 Cannot setup message subscription: missing services')
|
console.warn('💬 Cannot setup message subscription: missing services')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!relayHub.isConnected) {
|
if (!this.relayHub.isConnected) {
|
||||||
console.warn('💬 RelayHub not connected, waiting for connection...')
|
console.warn('💬 RelayHub not connected, waiting for connection...')
|
||||||
// Listen for connection event
|
// Listen for connection event
|
||||||
relayHub.on('connected', () => {
|
this.relayHub.on('connected', () => {
|
||||||
console.log('💬 RelayHub connected, setting up message subscription...')
|
console.log('💬 RelayHub connected, setting up message subscription...')
|
||||||
this.setupMessageSubscription()
|
this.setupMessageSubscription()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPubkey = authService.user.value.pubkey
|
const userPubkey = this.authService.user.value.pubkey
|
||||||
const userPrivkey = authService.user.value.prvkey
|
const userPrivkey = this.authService.user.value.prvkey
|
||||||
|
|
||||||
// Subscribe to encrypted direct messages (kind 4) addressed to this user
|
// Subscribe to encrypted direct messages (kind 4) addressed to this user
|
||||||
this.subscriptionUnsubscriber = relayHub.subscribe({
|
this.subscriptionUnsubscriber = this.relayHub.subscribe({
|
||||||
id: 'chat-messages',
|
id: 'chat-messages',
|
||||||
filters: [
|
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
|
// Unsubscribe from message subscription
|
||||||
if (this.subscriptionUnsubscriber) {
|
if (this.subscriptionUnsubscriber) {
|
||||||
this.subscriptionUnsubscriber()
|
this.subscriptionUnsubscriber()
|
||||||
|
this.subscriptionUnsubscriber = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messages.value.clear()
|
this.messages.value.clear()
|
||||||
this.peers.value.clear()
|
this.peers.value.clear()
|
||||||
|
|
||||||
|
this.debug('Chat service disposed')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy destroy method for backward compatibility
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
this.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue