Enhance VisibilityService integration for WebSocket and chat services

- Add detailed examples for WebSocket connection recovery and chat message synchronization in the VisibilityService documentation.
- Refactor ChatService to register with VisibilityService, enabling automatic handling of app visibility changes and missed message synchronization.
- Implement connection recovery logic in NostrclientHub and ChatService to ensure seamless user experience during app backgrounding.
- Update base module to ensure proper initialization of services with VisibilityService dependencies, enhancing overall connection management.
This commit is contained in:
padreug 2025-09-05 15:57:02 +02:00
parent d03a1fcd2c
commit ef7333e68e
5 changed files with 756 additions and 121 deletions

View file

@ -56,6 +56,10 @@ export const baseModule: ModulePlugin = {
waitForDependencies: false, // VisibilityService has no dependencies
maxRetries: 1
})
await nostrclientHub.initialize({
waitForDependencies: true, // NostrClientHub depends on VisibilityService
maxRetries: 3
})
console.log('✅ Base module installed successfully')
},
@ -63,9 +67,12 @@ export const baseModule: ModulePlugin = {
async uninstall() {
console.log('🗑️ Uninstalling base module...')
// Cleanup Nostr connections
relayHub.disconnect()
nostrclientHub.disconnect?.()
// Cleanup services
await relayHub.dispose()
await nostrclientHub.dispose()
await auth.dispose()
await paymentService.dispose()
await visibilityService.dispose()
console.log('✅ Base module uninstalled')
},

View file

@ -1,30 +1,6 @@
import type { Filter, Event } from 'nostr-tools'
import { BaseService } from '@/core/base/BaseService'
// Simple EventEmitter for browser compatibility
class EventEmitter {
private events: { [key: string]: Function[] } = {}
on(event: string, listener: Function) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(listener)
}
emit(event: string, ...args: any[]) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(...args))
}
}
removeAllListeners(event?: string) {
if (event) {
delete this.events[event]
} else {
this.events = {}
}
}
}
export interface NostrclientConfig {
url: string
@ -46,7 +22,18 @@ export interface RelayStatus {
error?: string
}
export class NostrclientHub extends EventEmitter {
export class NostrclientHub extends BaseService {
// Service metadata
protected readonly metadata = {
name: 'NostrclientHub',
version: '1.0.0',
dependencies: ['VisibilityService']
}
// EventEmitter functionality
private events: { [key: string]: Function[] } = {}
// Service state
private ws: WebSocket | null = null
private config: NostrclientConfig
private subscriptions: Map<string, SubscriptionConfig> = new Map()
@ -54,6 +41,7 @@ export class NostrclientHub extends EventEmitter {
private reconnectAttempts = 0
private readonly maxReconnectAttempts = 5
private readonly reconnectDelay = 5000
private visibilityUnsubscribe?: () => void
// Connection state
private _isConnected = false
@ -64,6 +52,42 @@ export class NostrclientHub extends EventEmitter {
this.config = config
}
// EventEmitter methods
on(event: string, listener: Function) {
if (!this.events[event]) {
this.events[event] = []
}
this.events[event].push(listener)
}
emit(event: string, ...args: any[]) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(...args))
}
}
removeAllListeners(event?: string) {
if (event) {
delete this.events[event]
} else {
this.events = {}
}
}
/**
* Service-specific initialization (called by BaseService)
*/
protected async onInitialize(): Promise<void> {
// Connect to WebSocket
console.log('🔧 NostrclientHub: Initializing connection to', this.config.url)
await this.connect()
// Register with visibility service
this.registerWithVisibilityService()
this.debug('NostrclientHub initialized')
}
get isConnected(): boolean {
return this._isConnected
}
@ -83,13 +107,6 @@ export class NostrclientHub extends EventEmitter {
}))
}
/**
* Initialize and connect to nostrclient WebSocket
*/
async initialize(): Promise<void> {
console.log('🔧 NostrclientHub: Initializing connection to', this.config.url)
await this.connect()
}
/**
* Connect to the nostrclient WebSocket
@ -351,6 +368,69 @@ export class NostrclientHub extends EventEmitter {
await this.connect()
}, delay) as unknown as number
}
/**
* Register with VisibilityService for connection management
*/
private registerWithVisibilityService(): void {
if (!this.visibilityService) {
this.debug('VisibilityService not available')
return
}
this.visibilityUnsubscribe = this.visibilityService.registerService(
this.metadata.name,
async () => this.handleAppResume(),
async () => this.handleAppPause()
)
this.debug('Registered with VisibilityService')
}
/**
* Handle app resuming from visibility change
*/
private async handleAppResume(): Promise<void> {
this.debug('App resumed - checking nostrclient WebSocket connection')
// Check if we need to reconnect
if (!this.isConnected && !this._isConnecting) {
this.debug('WebSocket disconnected, attempting to reconnect...')
await this.connect()
} else if (this.isConnected) {
// Connection is alive, resubscribe to ensure all subscriptions are active
this.resubscribeAll()
}
}
/**
* Handle app pausing from visibility change
*/
private async handleAppPause(): Promise<void> {
this.debug('App paused - WebSocket connection will be maintained for quick resume')
// Don't immediately disconnect - WebSocket will be checked on resume
// This allows for quick resume without full reconnection overhead
}
/**
* Cleanup when service is disposed (overrides BaseService)
*/
protected async onDispose(): Promise<void> {
// Unregister from visibility service
if (this.visibilityUnsubscribe) {
this.visibilityUnsubscribe()
this.visibilityUnsubscribe = undefined
}
// Disconnect WebSocket
this.disconnect()
// Clear all event listeners
this.removeAllListeners()
this.debug('NostrclientHub disposed')
}
}
// Export singleton instance