Add VisibilityService documentation and integration guide
- Create comprehensive documentation for the new VisibilityService, detailing its purpose, core concepts, and architecture. - Include an integration guide for module developers, outlining best practices for registering services and handling app visibility changes. - Add example code snippets for implementing visibility management in various service types, ensuring clarity and ease of use. - Introduce a troubleshooting section to address common issues and provide debugging tips for developers. - Enhance the VisibilityService integration guide with real-world examples to illustrate practical usage scenarios.
This commit is contained in:
parent
3e9c9bbdef
commit
d03a1fcd2c
3 changed files with 1139 additions and 0 deletions
429
docs/VisibilityService-Integration.md
Normal file
429
docs/VisibilityService-Integration.md
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
# VisibilityService Integration Guide for Module Developers
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Basic Service Integration
|
||||
|
||||
```typescript
|
||||
// src/modules/your-module/services/your-service.ts
|
||||
import { BaseService } from '@/core/base/BaseService'
|
||||
|
||||
export class YourService extends BaseService {
|
||||
protected readonly metadata = {
|
||||
name: 'YourService',
|
||||
version: '1.0.0',
|
||||
dependencies: ['VisibilityService'] // Optional but recommended
|
||||
}
|
||||
|
||||
private visibilityUnsubscribe?: () => void
|
||||
|
||||
protected async onInitialize(): Promise<void> {
|
||||
// Your initialization code
|
||||
await this.setupService()
|
||||
|
||||
// Register with visibility service
|
||||
this.registerWithVisibilityService()
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
private async handleAppResume(): Promise<void> {
|
||||
this.debug('App resumed - checking connections')
|
||||
|
||||
// 1. Check if reconnection is needed
|
||||
if (await this.needsReconnection()) {
|
||||
await this.reconnectService()
|
||||
}
|
||||
|
||||
// 2. Resume normal operations
|
||||
this.resumeBackgroundTasks()
|
||||
}
|
||||
|
||||
private async handleAppPause(): Promise<void> {
|
||||
this.debug('App paused - reducing activity')
|
||||
|
||||
// 1. Stop non-essential tasks
|
||||
this.pauseBackgroundTasks()
|
||||
|
||||
// 2. Prepare for potential disconnection
|
||||
await this.prepareForPause()
|
||||
}
|
||||
|
||||
protected async onDispose(): Promise<void> {
|
||||
// Always clean up registration
|
||||
if (this.visibilityUnsubscribe) {
|
||||
this.visibilityUnsubscribe()
|
||||
}
|
||||
|
||||
this.debug('Service disposed')
|
||||
}
|
||||
|
||||
// Implement these methods based on your service needs
|
||||
private async needsReconnection(): Promise<boolean> {
|
||||
// Check if your service connections are healthy
|
||||
return false
|
||||
}
|
||||
|
||||
private async reconnectService(): Promise<void> {
|
||||
// Reconnect your service
|
||||
}
|
||||
|
||||
private resumeBackgroundTasks(): void {
|
||||
// Resume periodic tasks, polling, etc.
|
||||
}
|
||||
|
||||
private pauseBackgroundTasks(): void {
|
||||
// Pause periodic tasks to save battery
|
||||
}
|
||||
|
||||
private async prepareForPause(): Promise<void> {
|
||||
// Save state, queue operations, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Patterns by Service Type
|
||||
|
||||
### Real-Time Connection Services (WebSocket, Nostr, etc.)
|
||||
|
||||
```typescript
|
||||
export class RealtimeService extends BaseService {
|
||||
private connections = new Map<string, Connection>()
|
||||
private subscriptions = new Map<string, Subscription>()
|
||||
|
||||
private async handleAppResume(): Promise<void> {
|
||||
// 1. Check connection health
|
||||
const brokenConnections = await this.checkConnectionHealth()
|
||||
|
||||
// 2. Reconnect failed connections
|
||||
for (const connectionId of brokenConnections) {
|
||||
await this.reconnectConnection(connectionId)
|
||||
}
|
||||
|
||||
// 3. Restore subscriptions
|
||||
await this.restoreSubscriptions()
|
||||
|
||||
// 4. Resume heartbeat/keepalive
|
||||
this.startHeartbeat()
|
||||
}
|
||||
|
||||
private async handleAppPause(): Promise<void> {
|
||||
// 1. Stop heartbeat to save battery
|
||||
this.stopHeartbeat()
|
||||
|
||||
// 2. Don't disconnect immediately (for quick resume)
|
||||
// Connections will be checked when app resumes
|
||||
}
|
||||
|
||||
private async checkConnectionHealth(): Promise<string[]> {
|
||||
const broken: string[] = []
|
||||
|
||||
for (const [id, connection] of this.connections) {
|
||||
if (!connection.isConnected()) {
|
||||
broken.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
return broken
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Data Sync Services
|
||||
|
||||
```typescript
|
||||
export class DataSyncService extends BaseService {
|
||||
private syncQueue: Operation[] = []
|
||||
private lastSyncTime: number = 0
|
||||
|
||||
private async handleAppResume(): Promise<void> {
|
||||
const hiddenTime = Date.now() - this.lastSyncTime
|
||||
|
||||
// If hidden for > 5 minutes, do full sync
|
||||
if (hiddenTime > 300000) {
|
||||
await this.performFullSync()
|
||||
} else {
|
||||
await this.performIncrementalSync()
|
||||
}
|
||||
|
||||
// Process queued operations
|
||||
await this.processQueue()
|
||||
|
||||
// Resume periodic sync
|
||||
this.startPeriodicSync()
|
||||
}
|
||||
|
||||
private async handleAppPause(): Promise<void> {
|
||||
// Stop periodic sync
|
||||
this.stopPeriodicSync()
|
||||
|
||||
// Save timestamp
|
||||
this.lastSyncTime = Date.now()
|
||||
|
||||
// Enable operation queueing
|
||||
this.enableQueueMode()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Background Processing Services
|
||||
|
||||
```typescript
|
||||
export class BackgroundService extends BaseService {
|
||||
private processingInterval?: number
|
||||
private taskQueue: Task[] = []
|
||||
|
||||
private async handleAppResume(): Promise<void> {
|
||||
// Resume background processing
|
||||
this.startProcessing()
|
||||
|
||||
// Process any queued tasks
|
||||
await this.processQueuedTasks()
|
||||
}
|
||||
|
||||
private async handleAppPause(): Promise<void> {
|
||||
// Stop background processing to save CPU/battery
|
||||
if (this.processingInterval) {
|
||||
clearInterval(this.processingInterval)
|
||||
this.processingInterval = undefined
|
||||
}
|
||||
|
||||
// Queue new tasks instead of processing immediately
|
||||
this.enableTaskQueueing()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Module Registration Pattern
|
||||
|
||||
### Module Index File
|
||||
|
||||
```typescript
|
||||
// src/modules/your-module/index.ts
|
||||
import type { App } from 'vue'
|
||||
import type { ModulePlugin } from '@/core/types'
|
||||
import { YourService } from './services/your-service'
|
||||
|
||||
export const yourModule: ModulePlugin = {
|
||||
name: 'your-module',
|
||||
version: '1.0.0',
|
||||
dependencies: ['base'], // base module provides VisibilityService
|
||||
|
||||
async install(app: App, options?: any) {
|
||||
console.log('🔧 Installing your module...')
|
||||
|
||||
// Create and initialize service
|
||||
const yourService = new YourService()
|
||||
|
||||
// Initialize service (this will register with VisibilityService)
|
||||
await yourService.initialize({
|
||||
waitForDependencies: true, // Wait for VisibilityService
|
||||
maxRetries: 3
|
||||
})
|
||||
|
||||
// Register service in DI container
|
||||
container.provide(YOUR_SERVICE_TOKEN, yourService)
|
||||
|
||||
console.log('✅ Your module installed successfully')
|
||||
},
|
||||
|
||||
async uninstall() {
|
||||
console.log('🗑️ Uninstalling your module...')
|
||||
// Services will auto-dispose and unregister from VisibilityService
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices Checklist
|
||||
|
||||
### ✅ Do's
|
||||
|
||||
- **Always register during `onInitialize()`**
|
||||
```typescript
|
||||
protected async onInitialize(): Promise<void> {
|
||||
await this.setupService()
|
||||
this.registerWithVisibilityService() // ✅
|
||||
}
|
||||
```
|
||||
|
||||
- **Check hidden duration before expensive operations**
|
||||
```typescript
|
||||
private async handleAppResume(): Promise<void> {
|
||||
const state = this.visibilityService.getState()
|
||||
if (state.hiddenDuration && state.hiddenDuration > 30000) {
|
||||
await this.performFullReconnect() // ✅ Only if needed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Always clean up registrations**
|
||||
```typescript
|
||||
protected async onDispose(): Promise<void> {
|
||||
if (this.visibilityUnsubscribe) {
|
||||
this.visibilityUnsubscribe() // ✅
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Use graceful pause strategies**
|
||||
```typescript
|
||||
private async handleAppPause(): Promise<void> {
|
||||
this.stopHeartbeat() // ✅ Stop periodic tasks
|
||||
// Keep connections alive for quick resume
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Don'ts
|
||||
|
||||
- **Don't immediately disconnect on pause**
|
||||
```typescript
|
||||
private async handleAppPause(): Promise<void> {
|
||||
this.disconnectAll() // ❌ Too aggressive
|
||||
}
|
||||
```
|
||||
|
||||
- **Don't ignore the service availability check**
|
||||
```typescript
|
||||
private registerWithVisibilityService(): void {
|
||||
// ❌ Missing availability check
|
||||
this.visibilityService.registerService(/*...*/)
|
||||
|
||||
// ✅ Correct
|
||||
if (!this.visibilityService) return
|
||||
this.visibilityService.registerService(/*...*/)
|
||||
}
|
||||
```
|
||||
|
||||
- **Don't forget dependencies in metadata**
|
||||
```typescript
|
||||
protected readonly metadata = {
|
||||
name: 'MyService',
|
||||
dependencies: [] // ❌ Should include 'VisibilityService'
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Connection Health Checking
|
||||
|
||||
```typescript
|
||||
private async checkConnectionHealth(): Promise<boolean> {
|
||||
try {
|
||||
// Perform a lightweight health check
|
||||
await this.ping()
|
||||
return true
|
||||
} catch (error) {
|
||||
this.debug('Connection health check failed:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subscription Restoration
|
||||
|
||||
```typescript
|
||||
private async restoreSubscriptions(): Promise<void> {
|
||||
const subscriptionsToRestore = Array.from(this.subscriptionConfigs.values())
|
||||
|
||||
for (const config of subscriptionsToRestore) {
|
||||
try {
|
||||
await this.recreateSubscription(config)
|
||||
} catch (error) {
|
||||
this.debug(`Failed to restore subscription ${config.id}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Operation Queueing
|
||||
|
||||
```typescript
|
||||
private operationQueue: Operation[] = []
|
||||
private queueingEnabled = false
|
||||
|
||||
private async executeOrQueue(operation: Operation): Promise<void> {
|
||||
if (this.queueingEnabled) {
|
||||
this.operationQueue.push(operation)
|
||||
} else {
|
||||
await operation.execute()
|
||||
}
|
||||
}
|
||||
|
||||
private async processQueue(): Promise<void> {
|
||||
const operations = this.operationQueue.splice(0) // Clear queue
|
||||
|
||||
for (const operation of operations) {
|
||||
try {
|
||||
await operation.execute()
|
||||
} catch (error) {
|
||||
this.debug('Queued operation failed:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Integration
|
||||
|
||||
### Mock VisibilityService for Tests
|
||||
|
||||
```typescript
|
||||
// tests/setup/mockVisibilityService.ts
|
||||
export const createMockVisibilityService = () => ({
|
||||
isVisible: { value: true },
|
||||
isOnline: { value: true },
|
||||
registerService: vi.fn(() => vi.fn()), // Returns unregister function
|
||||
getState: vi.fn(() => ({
|
||||
isVisible: true,
|
||||
isOnline: true,
|
||||
hiddenDuration: 0
|
||||
}))
|
||||
})
|
||||
|
||||
// In your test
|
||||
describe('YourService', () => {
|
||||
it('should register with VisibilityService', async () => {
|
||||
const mockVisibility = createMockVisibilityService()
|
||||
const service = new YourService()
|
||||
service.visibilityService = mockVisibility
|
||||
|
||||
await service.initialize()
|
||||
|
||||
expect(mockVisibility.registerService).toHaveBeenCalledWith(
|
||||
'YourService',
|
||||
expect.any(Function),
|
||||
expect.any(Function)
|
||||
)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Test Visibility Events
|
||||
|
||||
```typescript
|
||||
it('should handle app resume correctly', async () => {
|
||||
const service = new YourService()
|
||||
const reconnectSpy = vi.spyOn(service, 'reconnect')
|
||||
|
||||
// Simulate app resume after long pause
|
||||
await service.handleAppResume()
|
||||
|
||||
expect(reconnectSpy).toHaveBeenCalled()
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue