From dc4da570a7d37e6fe4b5e8cd121381e23d9fbe60 Mon Sep 17 00:00:00 2001 From: padreug Date: Fri, 5 Sep 2025 06:41:19 +0200 Subject: [PATCH] Refactor services to extend BaseService for improved structure and dependency management - Update AuthService and RelayHub to extend BaseService, introducing standardized initialization and metadata handling. - Implement service-specific initialization methods in both services, enhancing error handling and logging. - Modify NostrmarketService to inherit from BaseService, ensuring consistent dependency management and initialization. - Refactor market module to dynamically import NostrmarketService, improving service registration and initialization flow. - Enhance debug logging across services for better traceability during initialization and operation. --- src/modules/base/auth/auth-service.ts | 44 ++++++-- src/modules/base/index.ts | 5 +- src/modules/base/nostr/relay-hub.ts | 104 ++++++++++++++---- src/modules/market/index.ts | 18 ++- .../market/services/nostrmarketService.ts | 30 +++-- 5 files changed, 151 insertions(+), 50 deletions(-) diff --git a/src/modules/base/auth/auth-service.ts b/src/modules/base/auth/auth-service.ts index 45b97c3..0cac8dc 100644 --- a/src/modules/base/auth/auth-service.ts +++ b/src/modules/base/auth/auth-service.ts @@ -1,15 +1,27 @@ // Auth service for LNbits integration import { ref } from 'vue' +import { BaseService } from '@/core/base/BaseService' import { eventBus } from '@/core/event-bus' import { lnbitsAPI, type LoginCredentials, type RegisterData } from '@/lib/api/lnbits' -export class AuthService { +export class AuthService extends BaseService { + // Service metadata + protected readonly metadata = { + name: 'AuthService', + version: '1.0.0', + dependencies: [] // Auth service has no dependencies on other services + } + + // Public state public isAuthenticated = ref(false) public user = ref(null) public isLoading = ref(false) - async initialize(): Promise { - console.log('🔑 Initializing auth service...') + /** + * Service-specific initialization (called by BaseService) + */ + protected async onInitialize(): Promise { + this.debug('Initializing auth service...') // Check for existing auth state and fetch user data await this.checkAuth() @@ -21,7 +33,7 @@ export class AuthService { async checkAuth(): Promise { if (!lnbitsAPI.isAuthenticated()) { - console.log('🔑 No auth token found - user needs to login') + this.debug('No auth token found - user needs to login') this.isAuthenticated.value = false this.user.value = null return false @@ -34,12 +46,12 @@ export class AuthService { this.user.value = userData this.isAuthenticated.value = true - console.log('🔑 User authenticated:', userData.username || userData.id, userData.pubkey?.slice(0, 8)) + this.debug(`User authenticated: ${userData.username || userData.id} (${userData.pubkey?.slice(0, 8)})`) return true } catch (error) { - console.warn('🔑 Authentication check failed:', error) + this.handleError(error, 'checkAuth') this.isAuthenticated.value = false this.user.value = null // Clear invalid token @@ -63,9 +75,9 @@ export class AuthService { eventBus.emit('auth:login', { user: userData }, 'auth-service') } catch (error) { - console.error('Login failed:', error) - eventBus.emit('auth:login-failed', { error }, 'auth-service') - throw error + const err = this.handleError(error, 'login') + eventBus.emit('auth:login-failed', { error: err }, 'auth-service') + throw err } finally { this.isLoading.value = false } @@ -84,9 +96,9 @@ export class AuthService { eventBus.emit('auth:login', { user: userData }, 'auth-service') } catch (error) { - console.error('Registration failed:', error) - eventBus.emit('auth:login-failed', { error }, 'auth-service') - throw error + const err = this.handleError(error, 'register') + eventBus.emit('auth:login-failed', { error: err }, 'auth-service') + throw err } finally { this.isLoading.value = false } @@ -104,6 +116,14 @@ export class AuthService { // Re-fetch user data from API await this.checkAuth() } + + /** + * Cleanup when service is disposed + */ + protected async onDispose(): Promise { + this.logout() + this.debug('Auth service disposed') + } } // Export singleton instance diff --git a/src/modules/base/index.ts b/src/modules/base/index.ts index d74d616..ebaafd9 100644 --- a/src/modules/base/index.ts +++ b/src/modules/base/index.ts @@ -33,7 +33,10 @@ export const baseModule: ModulePlugin = { // Initialize core services await relayHub.initialize(options?.config?.nostr?.relays || []) - await auth.initialize() + await auth.initialize({ + waitForDependencies: false, // Auth has no dependencies + maxRetries: 1 + }) console.log('✅ Base module installed successfully') }, diff --git a/src/modules/base/nostr/relay-hub.ts b/src/modules/base/nostr/relay-hub.ts index 162911e..d6be10c 100644 --- a/src/modules/base/nostr/relay-hub.ts +++ b/src/modules/base/nostr/relay-hub.ts @@ -1,4 +1,6 @@ import { SimplePool, type Filter, type Event, type Relay } from 'nostr-tools' +import { BaseService } from '@/core/base/BaseService' +import { ref } from 'vue' // Simple EventEmitter implementation for browser compatibility class EventEmitter { @@ -57,17 +59,29 @@ export interface RelayStatus { latency?: number } -export class RelayHub extends EventEmitter { +export class RelayHub extends BaseService { + // Service metadata + protected readonly metadata = { + name: 'RelayHub', + version: '1.0.0', + dependencies: [] // RelayHub has no dependencies on other services + } + + // EventEmitter functionality + private eventEmitter = new EventEmitter() + + // RelayHub specific properties private pool: SimplePool private relayConfigs: Map = new Map() private connectedRelays: Map = new Map() private subscriptions: Map = new Map() - public isInitialized = false + public isInitializedLegacy = false // Keep for backward compatibility private reconnectInterval?: number private healthCheckInterval?: number private mobileVisibilityHandler?: () => void - // Connection state + // Connection state - we need both a reactive ref for components and internal state for business logic + public isConnected = ref(false) private _isConnected = false private _connectionAttempts = 0 private readonly maxReconnectAttempts = 5 @@ -79,10 +93,24 @@ export class RelayHub extends EventEmitter { this.pool = new SimplePool() this.setupMobileVisibilityHandling() } - - get isConnected(): boolean { - return this._isConnected + + // Forward EventEmitter methods + on(event: string, listener: Function): void { + this.eventEmitter.on(event, listener) } + + off(event: string, listener: Function): void { + this.eventEmitter.off(event, listener) + } + + emit(event: string, ...args: any[]): void { + this.eventEmitter.emit(event, ...args) + } + + removeAllListeners(event?: string): void { + this.eventEmitter.removeAllListeners(event) + } + get connectedRelayCount(): number { // Return the actual size of connectedRelays map @@ -123,14 +151,38 @@ export class RelayHub extends EventEmitter { /** * Initialize the relay hub with relay configurations + * This is the public API that maintains backward compatibility */ - async initialize(relayUrls: string[]): Promise { - if (this.isInitialized) { - console.warn('RelayHub already initialized') - return + async initialize(relayUrls: string[]): Promise + async initialize(options: any): Promise + async initialize(relayUrlsOrOptions: string[] | any): Promise { + // Handle backward compatibility for relayUrls array + if (Array.isArray(relayUrlsOrOptions)) { + this.pendingRelayUrls = relayUrlsOrOptions + // Use BaseService's initialize method + await super.initialize({ + waitForDependencies: false, // RelayHub has no dependencies + maxRetries: 1 + }) + } else { + // This is a call from BaseService or other services + await super.initialize(relayUrlsOrOptions) } - - console.log('🔧 RelayHub: Initializing with URLs:', relayUrls) + } + + private pendingRelayUrls: string[] = [] + + /** + * Service-specific initialization (called by BaseService) + */ + protected async onInitialize(): Promise { + const relayUrls = this.pendingRelayUrls + + if (!relayUrls || relayUrls.length === 0) { + throw new Error('No relay URLs provided for initialization') + } + + this.debug(`Initializing with URLs: ${relayUrls.join(', ')}`) // Convert URLs to relay configs this.relayConfigs.clear() @@ -143,14 +195,14 @@ export class RelayHub extends EventEmitter { }) }) - console.log('🔧 RelayHub: Relay configs created:', Array.from(this.relayConfigs.values())) + this.debug(`Relay configs created: ${this.relayConfigs.size} configs`) // Start connection management - console.log('🔧 RelayHub: Starting connection...') + this.debug('Starting connection...') await this.connect() this.startHealthCheck() - this.isInitialized = true - console.log('🔧 RelayHub: Initialization complete') + this.isInitializedLegacy = true // Keep for backward compatibility + this.debug('Initialization complete') } /** @@ -200,6 +252,7 @@ export class RelayHub extends EventEmitter { if (successfulConnections.length > 0) { this._isConnected = true + this.isConnected.value = true this._connectionAttempts = 0 console.log('🔧 RelayHub: Connection successful, connected to', successfulConnections.length, 'relays') this.emit('connected', successfulConnections.length) @@ -210,6 +263,7 @@ export class RelayHub extends EventEmitter { } } catch (error) { this._isConnected = false + this.isConnected.value = false console.error('🔧 RelayHub: Connection failed with error:', error) this.emit('connectionError', error) @@ -228,8 +282,6 @@ export class RelayHub extends EventEmitter { * Disconnect from all relays */ disconnect(): void { - - // Clear intervals if (this.reconnectInterval) { clearTimeout(this.reconnectInterval) @@ -250,6 +302,7 @@ export class RelayHub extends EventEmitter { this.connectedRelays.clear() this._isConnected = false + this.isConnected.value = false this.emit('disconnected') } @@ -448,6 +501,7 @@ export class RelayHub extends EventEmitter { // Update connection status if (this.connectedRelays.size === 0) { this._isConnected = false + this.isConnected.value = false this.emit('allRelaysDisconnected') console.warn('All relays disconnected, attempting reconnection...') await this.connect() @@ -489,6 +543,7 @@ export class RelayHub extends EventEmitter { window.addEventListener('offline', () => { console.log('Network offline, marking as disconnected...') this._isConnected = false + this.isConnected.value = false this.emit('networkOffline') }) } @@ -498,8 +553,13 @@ export class RelayHub extends EventEmitter { * Cleanup resources */ destroy(): void { - - + this.dispose() + } + + /** + * Cleanup when service is disposed (called by BaseService) + */ + protected async onDispose(): Promise { // Remove event listeners if (this.mobileVisibilityHandler && typeof document !== 'undefined') { document.removeEventListener('visibilitychange', this.mobileVisibilityHandler) @@ -513,7 +573,9 @@ export class RelayHub extends EventEmitter { // Disconnect and cleanup this.disconnect() this.removeAllListeners() - this.isInitialized = false + this.isInitializedLegacy = false + + this.debug('RelayHub disposed') } } diff --git a/src/modules/market/index.ts b/src/modules/market/index.ts index adb4962..897f5e0 100644 --- a/src/modules/market/index.ts +++ b/src/modules/market/index.ts @@ -9,8 +9,7 @@ import MarketSettings from './components/MarketSettings.vue' import MerchantStore from './components/MerchantStore.vue' import ShoppingCart from './components/ShoppingCart.vue' -// Import services -import { NostrmarketService } from './services/nostrmarketService' +// NostrmarketService will be dynamically imported in install() // Store will be imported when needed @@ -46,8 +45,19 @@ export const marketModule: ModulePlugin = { throw new Error('Market module requires configuration') } - // Create and register services - const nostrmarketService = new NostrmarketService() + // Import the singleton instance + const { nostrmarketService } = await import('./services/nostrmarketService') + + // Initialize the service (will handle dependency injection) + await nostrmarketService.initialize({ + waitForDependencies: true, + maxRetries: 3 + }).catch(error => { + console.warn('🛒 NostrmarketService initialization deferred:', error) + // Service will auto-initialize when dependencies are available + }) + + // Register the service container.provide(NOSTRMARKET_SERVICE_TOKEN, nostrmarketService) // Register global components diff --git a/src/modules/market/services/nostrmarketService.ts b/src/modules/market/services/nostrmarketService.ts index bc9ea60..df2b4da 100644 --- a/src/modules/market/services/nostrmarketService.ts +++ b/src/modules/market/services/nostrmarketService.ts @@ -1,6 +1,5 @@ import { finalizeEvent, type EventTemplate, nip04 } from 'nostr-tools' -import { injectService, SERVICE_TOKENS } from '@/core/di-container' -import { auth } from '@/composables/useAuth' +import { BaseService } from '@/core/base/BaseService' import type { Stall, Product, Order } from '@/stores/market' export interface NostrmarketStall { @@ -67,13 +66,20 @@ export interface NostrmarketOrderStatus { shipped?: boolean } -export class NostrmarketService { - private get relayHub(): any { - const hub = injectService(SERVICE_TOKENS.RELAY_HUB) - if (!hub) { - throw new Error('RelayHub not available. Make sure base module is installed.') - } - return hub as any +export class NostrmarketService extends BaseService { + // Service metadata + protected readonly metadata = { + name: 'NostrmarketService', + version: '1.0.0', + dependencies: ['RelayHub', 'AuthService'] + } + + /** + * Service-specific initialization (called by BaseService) + */ + protected async onInitialize(): Promise { + this.debug('NostrmarketService initialized') + // Service doesn't need special initialization } /** @@ -100,12 +106,12 @@ export class NostrmarketService { } private getAuth() { - if (!auth.isAuthenticated.value || !auth.currentUser.value?.prvkey) { + if (!this.authService?.isAuthenticated?.value || !this.authService?.user?.value?.prvkey) { throw new Error('User not authenticated or private key not available') } - const pubkey = auth.currentUser.value.pubkey - const prvkey = auth.currentUser.value.prvkey + const pubkey = this.authService.user.value.pubkey + const prvkey = this.authService.user.value.prvkey if (!pubkey || !prvkey) { throw new Error('Public key or private key is missing')