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

@ -424,6 +424,219 @@ it('should handle app resume correctly', async () => {
})
```
## WebSocket Connection Recovery Examples
### Real-World Chat Message Recovery
**Scenario**: User gets a phone call, returns to app 5 minutes later. Chat messages arrived while away.
```typescript
export class ChatService extends BaseService {
private async handleAppResume(): Promise<void> {
// Step 1: Check if subscription still exists
if (!this.subscriptionUnsubscriber) {
this.debug('Chat subscription lost during backgrounding - recreating')
this.setupMessageSubscription()
}
// Step 2: Sync missed messages from all chat peers
await this.syncMissedMessages()
}
private async syncMissedMessages(): Promise<void> {
const peers = Array.from(this.peers.value.values())
for (const peer of peers) {
try {
// Get messages from last hour for this peer
const recentEvents = await this.relayHub.queryEvents([
{
kinds: [4], // Encrypted DMs
authors: [peer.pubkey],
'#p': [this.getUserPubkey()],
since: Math.floor(Date.now() / 1000) - 3600,
limit: 20
}
])
// Process each recovered message
for (const event of recentEvents) {
await this.processIncomingMessage(event)
}
this.debug(`Recovered ${recentEvents.length} messages from ${peer.pubkey.slice(0, 8)}`)
} catch (error) {
console.warn(`Failed to recover messages from peer ${peer.pubkey.slice(0, 8)}:`, error)
}
}
}
}
```
### Nostr Relay Connection Recovery
**Scenario**: Nostr relays disconnected due to mobile browser suspension. Subscriptions need restoration.
```typescript
export class RelayHub extends BaseService {
private async handleResume(): Promise<void> {
// Step 1: Check which relays are still connected
const disconnectedRelays = this.checkDisconnectedRelays()
if (disconnectedRelays.length > 0) {
this.debug(`Found ${disconnectedRelays.length} disconnected relays`)
await this.reconnectToRelays(disconnectedRelays)
}
// Step 2: Restore all subscriptions on recovered relays
await this.restoreSubscriptions()
this.emit('connectionRecovered', {
reconnectedRelays: disconnectedRelays.length,
restoredSubscriptions: this.subscriptions.size
})
}
private async restoreSubscriptions(): Promise<void> {
if (this.subscriptions.size === 0) return
this.debug(`Restoring ${this.subscriptions.size} subscriptions`)
for (const [id, config] of this.subscriptions) {
try {
// Recreate subscription on available relays
const subscription = this.pool.subscribeMany(
this.getAvailableRelays(),
config.filters,
{
onevent: (event) => this.emit('event', { subscriptionId: id, event }),
oneose: () => this.emit('eose', { subscriptionId: id })
}
)
this.activeSubscriptions.set(id, subscription)
this.debug(`✅ Restored subscription: ${id}`)
} catch (error) {
this.debug(`❌ Failed to restore subscription ${id}:`, error)
}
}
}
}
```
### WebSocket Service Recovery
**Scenario**: Custom WebSocket service (like nostrclient-hub) needs reconnection after suspension.
```typescript
export class WebSocketService extends BaseService {
private ws: WebSocket | null = null
private subscriptions = new Map<string, any>()
private async handleAppResume(): Promise<void> {
// Step 1: Check WebSocket connection state
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
this.debug('WebSocket connection lost, reconnecting...')
await this.reconnect()
}
// Step 2: Resubscribe to all active subscriptions
if (this.ws?.readyState === WebSocket.OPEN) {
await this.resubscribeAll()
}
}
private async reconnect(): Promise<void> {
if (this.ws) {
this.ws.close()
}
return new Promise((resolve, reject) => {
this.ws = new WebSocket(this.config.url)
this.ws.onopen = () => {
this.debug('WebSocket reconnected successfully')
resolve()
}
this.ws.onerror = (error) => {
this.debug('WebSocket reconnection failed:', error)
reject(error)
}
this.ws.onmessage = (message) => {
this.handleMessage(JSON.parse(message.data))
}
})
}
private async resubscribeAll(): Promise<void> {
for (const [id, config] of this.subscriptions) {
const subscribeMessage = JSON.stringify(['REQ', id, ...config.filters])
this.ws?.send(subscribeMessage)
this.debug(`Resubscribed to: ${id}`)
}
}
}
```
## Debugging Connection Issues
### Enable Debug Logging
```typescript
// In browser console or service initialization
localStorage.setItem('debug', 'VisibilityService,RelayHub,ChatService')
// Or programmatically in your service
protected async onInitialize(): Promise<void> {
if (import.meta.env.DEV) {
this.visibilityService.on('debug', (message, data) => {
console.log(`[VisibilityService] ${message}`, data)
})
}
}
```
### Check Connection Status
```javascript
// In browser console
// Check visibility state
console.log('Visibility state:', visibilityService.getState())
// Check relay connections
console.log('Relay status:', relayHub.getConnectionStatus())
// Check active subscriptions
console.log('Active subscriptions:', relayHub.subscriptionDetails)
// Force connection check
await visibilityService.forceConnectionCheck()
```
### Monitor Recovery Events
```typescript
export class MyService extends BaseService {
protected async onInitialize(): Promise<void> {
// Listen for visibility events
this.visibilityService.on('visibilityChanged', (isVisible) => {
console.log('App visibility changed:', isVisible)
})
// Listen for reconnection events
this.relayHub.on('connectionRecovered', (data) => {
console.log('Connections recovered:', data)
})
}
}
```
---
This integration guide provides everything a module developer needs to add visibility management to their services. The patterns are battle-tested and optimize for both user experience and device battery life.
This integration guide provides everything a module developer needs to add visibility management to their services. The patterns are battle-tested and optimize for both user experience and device battery life.
**Key takeaway**: Mobile browsers WILL suspend WebSocket connections when apps lose focus. Integrating with VisibilityService ensures your real-time features work reliably across all platforms and usage patterns.