# Background Data Fetching Implementation Plan **Author**: Matthew Raymer **Version**: 1.0.0 **Created**: 2025-10-02 07:47:04 UTC **Last Updated**: 2025-10-02 12:45:00 UTC ## Overview This document outlines the **complete implementation plan** for background data fetching in the Capacitor Daily Notification Plugin, replacing web push implementations with native platform solutions. This consolidated plan includes: - **Option A Architecture**: Host always provides activeDid approach - **TimeSafari Integration**: PlatformServiceMixin coordination patterns - **Cross-Platform Implementation**: Android/Electron, iOS, Web specifications - **Database Access Patterns**: Clear separation between host and plugin storage - **ActiveDid Change Management**: Event-based notification and cache invalidation - **Security Requirements**: Data isolation and authentication patterns This document consolidates all previous analysis documents to provide a single source of truth for implementation. ## Consolidated Architecture: Option A Platform Overview ``` TimeSafari Host App ↓ (provides activeDid) Daily Notification Plugin → Native Background Executor ↓ HTTP Client + Auth ↓ API Server Response ↓ Parse & Cache Data (plugin storage) ↓ Trigger Notifications ``` ### **Option A: Host Always Provides activeDid** **Core Principle**: Host application queries its own database and provides activeDid to plugin. ### **Why Option A Is Superior:** 1. **Clear Separation**: Host owns identity management, plugin owns notifications 2. **No Database Conflicts**: Zero shared database access between host and plugin 3. **Security Isolation**: Plugin data physically separated from user data 4. **Platform Independence**: Works consistently regardless of host's database technology 5. **Simplified Implementation**: Fewer moving parts, clearer debugging ## Android Implementation Strategy ### A. Background Execution Framework - **Use WorkManager** for reliable background HTTP requests - **Replace axios** with native Android HTTP clients: - OkHttp for synchronous requests - Retrofit for type-safe API interfaces - **Handle Android-specific constraints**: Doze mode, app standby, battery optimization ### B. Authentication Implementation ```kotlin // JWT Generation in Android - Enhanced with DID support class JWTHelper { fun generateJWT(userDid: String, expiresInSeconds: Int): String { val payload = mapOf( "exp" to (System.currentTimeMillis() / 1000 + expiresInSeconds), "iat" to (System.currentTimeMillis() / 1000), "iss" to userDid, // Include DID-specific claims for verification "aud" to "timesafari.notifications", "sub" to userDid ) return signWithDID(payload, userDid) } // Enhanced authentication with Passkey support fun generateJWANT(userDid: String, biometricData: ByteArray): String { val payload = mapOf( "exp" to (System.currentTimeMillis() / 1000 + 3600), // 1 hour "iat" to (System.currentTimeMillis() / 1000), "iss" to userDid, "aud" to "timesafari.notifications", "sub" to userDid, "auth_data" to android.util.Base64.encodeToString(biometricData, android.util.Base64.NO_WRAP) ) return signWithDIDPasskey(payload, userDid, biometricData) } } ### C. HTTP Request Implementation ```kotlin // Background HTTP Worker class DataFetchWorker : CoroutineWorker { suspend fun doWork(): Result { val jwt = generateJWT(activeDid, 60) val headers = mapOf( "Authorization" to "Bearer $jwt", "Content-Type" to "application/json" ) val offersResponse = httpClient.get("$apiServer/api/v2/report/offers") { headers { headers.forEach { append(it.key, it.value) } } parameter("recipientId", activeDid) // Use activeDid for recipient filtering parameter("afterId", lastKnownOfferId) } return storeAndScheduleNotification(offersResponse.body()) } } ``` ## iOS Implementation Strategy ### A. Background Execution Framework - **Use BGTaskScheduler** for background HTTP requests - **Replace axios** with native iOS HTTP clients: - URLSession for HTTP requests - Combine framework for async/await patterns ### B. Authentication Implementation ```swift // JWT Generation in iOS class JWTHelper { func generateJWT(userDid: String, expiresInSeconds: Int) -> String { let payload: [String: Any] = [ "exp": Int(Date().timeIntervalSince1970) + expiresInSeconds, "iat": Int(Date().timeIntervalSince1970), "iss": userDid ] return signWithDID(payload, userDid) } } ``` ### C. HTTP Request Implementation ```swift // Background HTTP Task class DataFetchTask { func fetchData() async { let jwt = generateJWT(userDid: activeDid, expiresInSeconds: 60) var request = URLRequest(url: apiURL) request.setValue("Bearer \(jwt)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") do { let (data, _) = try await URLSession.shared.data(for: request) let offersResponse = try JSONDecoder().decode(OffersResponse.self, from: data) await scheduleNotification(with: offersResponse.data) } catch { // Handle errors } } } ``` ## Data Models & Type Safety ### Shared TypeScript Interfaces ```typescript // Definitions for native bridge interface OffersResponse { data: OfferSummaryRecord[]; hitLimit: boolean; } interface OfferSummaryRecord { jwtId: string; handleId: string; issuedAt: string; offeredByDid: string; recipientDid: string; unit: string; amount: number; // ... other fields } ``` ### Native Implementations - **Kotlin sealed classes** for type-safe responses - **Swift Codable structs** for JSON parsing - **Shared error handling** patterns ## Configuration Management ### Plugin Configuration ```typescript interface PluginConfig { apiServer: string; jwtExpirationSeconds: number; requestTimeoutMs: number; retryAttempts: number; activeDid: string; // Simplified to single active DID lastKnownOfferId?: string; lastKnownPlanId?: string; } ``` ### Platform-Specific Settings - **Android**: Manage API keys in `AndroidManifest.xml`, use SharedPreferences for runtime config - **iOS**: Use `Info.plist` for static config, UserDefaults for runtime settings ## Error Handling & Resilience ### Network Error Handling - **Connectivity checks** before making requests - **Exponential backoff** for retry scenarios - **Circuit breaker pattern** for API failures - **Graceful degradation** when offline ### Authentication Error Handling - **Token refresh** mechanisms - **Fallback to anonymous** requests when authentication fails - **Secure credential storage** using platform keychains ## Cache & State Management ### Data Persistence #### **Platform-Specific Storage Architecture** **Android/Electron Platforms:** - **@capacitor-community/sqlite** plugin integration for native SQLite access - **Shared plugin database** - Plugin manages its own SQLite database instance - **Direct SQL execution** via plugin's `dbExec()` methods for complex queries - **Background worker integration** for database operations during content fetch **Web Platform:** - **absurd-sql** for SQLite support in browser (managed by host application) - **Plugin delegation pattern** - Plugin provides SQL queries, host executes them - **IndexedDB fallback** for basic caching when SQLite unavailable **iOS Platform:** - **Core Data integration** via native Swift implementation - **Background task compatibility** with iOS background refresh constraints ### State Synchronization - **JavaScript → Native** configuration updates - **Native → JavaScript** status reporting - **Cross-platform state consistency** - **Background ↔ Foreground** state synchronization - **Database logging** for audit trails and debugging ### Enhanced Caching Strategy Based on TimeSafari's optimization patterns: - **Batch-oriented processing** for API requests to reduce overhead - **Intelligent batching** with configurable timing (max 100ms wait, max 10 items) - **Memory-optimized caching** with automatic cleanup (keep last 1000 log entries) - **Request deduplication** to prevent redundant API calls - **Performance monitoring** with operation timing and metrics collection ## Performance Optimizations ### Request Optimization - **Deduplication** of identical requests - **Batch requests** when possible - **Intelligent polling** based on user activity ### Memory Management - **Background memory limits** enforcement - **Cache cleanup** on memory pressure - **Resource lifecycle** management ## Critical Requirement: Plugin Must Know When activeDid Changes ### **Security Implications of Missing ActiveDid Change Detection** **Without immediate activeDid change detection, the plugin faces severe risks:** - **Cross-User Data Exposure**: Plugin fetches notifications for wrong user after identity switch - **Unauthorized API Access**: JWT tokens valid for incorrect user context - **Background Task Errors**: Content fetching operates with wrong identity ### **Event-Based Solution** **Host Application Responsibility**: Dispatch `activeDidChanged` event ```typescript // TimeSafari PlatformServiceMixin modification async $updateActiveDid(newDid: string | null): Promise { // Update TimeSafari's active_identity table (existing logic) await this.$dbExec( "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", [newDid || ""] ); // CRITICAL: Notify plugin of change IMMEDIATELY document.dispatchEvent(new CustomEvent('activeDidChanged', { detail: { activeDid: newDid } })); } ``` **Plugin Responsibility**: Listen and respond to changes ```typescript // Plugin service layer implementation plugin.onActiveDidChange(async (newActiveDid) => { // 1. Clear all cached content for previous identity await plugin.clearCacheForNewIdentity(); // 2. Refresh authentication tokens with new activeDid await plugin.refreshAuthenticationForNewIdentity(newActiveDid); // 3. Restart background tasks with correct identity await plugin.updateBackgroundTaskIdentity(newActiveDid); logger.info(`[DailyNotificationService] ActiveDid updated to: ${newActiveDid}`); }); ``` ## TimeSafari Integration Patterns ### **ActiveDid Management Analysis** **TimeSafari's Database Architecture:** - **Table**: `active_identity` (single row with `id = 1`) - **Content**: `activeDid TEXT`, `lastUpdated DATETIME` - **Purpose**: Single source of truth for active user identity **Access via PlatformServiceMixin:** ```typescript // Retrieving activeDid in TimeSafari components const activeIdentity = await this.$getActiveIdentity(); const activeDid = activeIdentity.activeDid; // Updating activeDid via PlatformServiceMixin async $updateActiveDid(newDid: string | null): Promise { await this.$dbExec( "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", [newDid || ""] ); } ``` ### **Plugin Hosting Strategy** **Existing Plugin Usage**: TimeSafari already uses Capacitor plugins extensively via `CapacitorPlatformService.ts` **Recommended Integration Architecture:** - Plugin integrates as standard Capacitor plugin - Host provides activeDid via event-driven pattern - Plugin manages own isolated storage - Clear separation of responsibilities maintained ## Integration Points ### Enhanced Plugin Interface for Host Application Integration #### **Database Integration Patterns** **Android/Electron: Host-Provided activeDid Approach (Option A)** ```typescript // Host queries its own database and provides activeDid const activeIdentity = await this.$getActiveIdentity(); // Uses host's CapacitorSQLite await plugin.setActiveDidFromHost(activeIdentity.activeDid); // Plugin configures its own isolated database await plugin.configureDatabase({ platform: 'android', storageType: 'plugin-managed' // Plugin owns its storage }); // Set up activeDid change listener for future changes plugin.onActiveDidChange(async (newActiveDid) => { await plugin.clearCacheForNewIdentity(); await plugin.refreshAuthenticationForNewIdentity(newActiveDid); logger.info(`[TimeSafari] ActiveDid changed to: ${newActiveDid}`); }); ``` **Web: Host-Provided activeDid Approach (Option A)** ```typescript // Host queries its absurd-sql database and provides activeDid const activeIdentity = await this.$getActiveIdentity(); // Uses host's absurd-sql await plugin.setActiveDidFromHost(activeIdentity.activeDid); // Plugin uses host-delegated storage for its own data await plugin.configureDatabase({ platform: 'web', storageType: 'host-managed' // Plugin delegates to host for storage }); // Plugin operates independently with provided activeDid const results = await plugin.executeContentFetch(contentConfig); ``` **iOS: Host-Provided activeDid Approach (Option A)** ```typescript // Host queries its CapacitorSQLite database and provides activeDid const activeIdentity = await this.$getActiveIdentity(); // Uses host's CapacitorSQLite await plugin.setActiveDidFromHost(activeIdentity.activeDid); // Plugin configures its own Core Data storage await plugin.configureDatabase({ platform: 'ios', storageType: 'plugin-managed' // Plugin owns Core Data storage }); // Plugin operates with provided activeDid, no database sharing needed const results = await plugin.executeBackgroundContentFetch(); ``` #### **New Plugin Methods Required** ```typescript interface EnhancedDailyNotificationPlugin { // Database configuration - Simplified Option A approach configureDatabase(options: { platform: 'android' | 'ios' | 'web' | 'electron'; storageType: 'plugin-managed' | 'host-managed'; dbPath?: string; encryption?: boolean; }): Promise; // Host-provided activeDid Management (Option A Implementation) setActiveDidFromHost(activeDid: string): Promise; // Content fetch with database integration fetchAndStoreContent(config: ContentFetchConfig): Promise; // Data access for host application getStoredContent(query: string, params?: any[]): Promise; clearStoredContent(options?: { olderThan?: number }): Promise; // Background task coordination getBackgroundTaskStatus(): Promise; pauseBackgroundTasks(): Promise; resumeBackgroundTasks(): Promise; // TimeSafari Integration Methods initializeFromTimeSafari(): Promise; listenForActiveDidChanges(): Promise; // Critical: ActiveDid Change Handling onActiveDidChange(callback: (newActiveDid: string) => Promise): void; clearCacheForNewIdentity(): Promise; refreshAuthenticationForNewIdentity(activeDid: string): Promise; } ``` ### Background Scheduling with Hybrid activeDid Management - **Integrate** with existing WorkManager/BGTaskScheduler - **Coordinate** API fetch timing with notification schedules - **Handle** app lifecycle events (background/foreground) - **Implement** host-provided activeDid access (Option A): - **Always**: Host provides activeDid via `setActiveDidFromHost()` - **No Database Sharing**: Plugin never accesses TimeSafari's active_identity table - **Critical**: Plugin **MUST** know when activeDid changes for: - **Event-Based Notification**: Host dispatches `activeDidChanged` events - **Cache Invalidation**: Clear cached content when user switches identity - **Token Refresh**: Generate new JWT tokens with updated active Did - **Background Task Coordination**: Restart tasks with new identity context - **Maintain** clear separation: Host owns identity management, plugin owns notifications ## Migration & Testing Strategy ### Gradual Migration 1. **Phase 1**: Implement basic HTTP + JWT authentication 2. **Phase 2**: Add caching and state management 3. **Phase 3**: Integrate with notification scheduling 4. **Phase 4**: Add passkey authentication support ### Testing Approach with Host-Provided activeDid Management - **Unit tests** for JWT generation and HTTP clients with activeDid - **Integration tests** for API endpoint interactions using TimeSafari active_identity patterns - **Host-provided activeDid testing**: - Test `setActiveDidFromHost()` with TimeSafari PlatformServiceMixin - Test host event dispatch and plugin event listening - **Critical**: Test `onActiveDidChange()` listener with identity switches - Test cache invalidation and token refresh during activeDid changes - Verify database isolation between host and plugin - **Background testing** on real devices (doze mode, app backgrounding) - **Authentication testing** with actual DID credentials from TimeSafari active_identity table - **Cross-platform testing** for Android/Electron (SQLite access) vs Web (host delegation) patterns ## API Endpoints to Support ### Offers to User Endpoint ``` GET {apiServer}/api/v2/report/offers?recipientDid={userDid}&afterId={jwtId}&beforeId={jwtId} ``` **Response Structure:** ```json { "data": Array, "hitLimit": boolean } ``` ### Offers to User Projects Endpoint ``` GET {apiServer}/api/v2/report/offersToPlansOwnedByMe?afterId={jwtId}&beforeId={jwtId} ``` **Response Structure:** ```json { "data": Array, "hitLimit": boolean } ``` ## Authentication Implementation Strategy ### Option 1: Simple DID Authentication (Basic) - Generate traditional JWT using DID signing - Short-lived tokens (60 seconds) - Suitable for basic notification data fetching - Use `did-jwt` library for token generation and verification - Based on TimeSafari's existing JWT implementation patterns ### Option 2: Enhanced Passkey Authentication (Advanced) - Leverage device biometrics/security keys - Longer-lived tokens with automatic refresh - Support for cached authentication state - Better user experience for frequent polling - Integrate with SimpleWebAuthn for cross-platform biometric support - Support JWANT tokens (JWT + WebAuthn) for enhanced security ## Platform-Specific Considerations ### Android Considerations - Use OkHttp or native Android HTTP clients - Handle certificate pinning if required - Support Android Keystore for secure key storage - Handle biometric prompt integration for passkeys ### iOS Considerations - Use URLSession for HTTP requests - Support iOS Keychain for authentication tokens - Handle Face ID/Touch ID integration for passkeys - Support certificate pinning if required - Use BGTaskScheduler for reliable background execution - Handle iOS-specific background refresh restrictions - Support Core Data for notification metadata persistence ## Data Flow Integration Points ### Token Generation - Accept activeDid as input - Generate JWT authentication token using DID signing - Include activeDid as both issuer (`iss`) and subject (`sub`) - Return token for immediate use or caching ### Request Execution - Construct full API URLs with query parameters - Apply authentication headers - Execute HTTP requests with proper error handling - Return structured response data ### Caching Strategy - Support token caching with expiration management - Implement request deduplication for same endpoints - Support cache invalidation for authentication failures ## Implementation Phases ### Phase 1: Core Infrastructure (Weeks 1-2) - Set up native HTTP clients for Android/iOS - Implement basic JWT generation - Create plugin configuration interfaces - Set up basic error handling ### Phase 2: Host-Provided activeDid Integration & API Access (Weeks 3-4) - Implement host-provided activeDid management (Option A approach) - Add `setActiveDidFromHost()` method (no database access needed) - Remove database sharing complexity - host always provides activeDid - Integrate API endpoint calls with activeDid-based authentication - Add response parsing and validation - Implement platform-specific database integration: - **Android/Electron**: Plugin-managed @capacitor-community/sqlite (no host database access) - **Web**: Host-managed storage delegation (plugin doesn't access absurd-sql directly) - **iOS**: Plugin-managed Core Data (no host database access) ### Phase 3: Background Integration (Weeks 5-6) - Integrate with WorkManager/BGTaskScheduler - Coordinate with notification scheduling - Handle app lifecycle events - Implement state synchronization ### Phase 4: Advanced Features (Weeks 7-8) - Add passkey authentication support - Implement advanced caching strategies - Optimize performance and memory usage - Add comprehensive testing ## Success Criteria - [ ] **Functional Requirements**: API data fetching works reliably in background - [ ] **Performance Requirements**: Requests complete within 30 seconds - [ ] **Security Requirements**: Secure credential storage and token management - [ ] **Reliability Requirements**: Handles network failures and offline scenarios - [ ] **Integration Requirements**: Seamless integration with existing plugin APIs - [ ] **Testing Requirements**: Comprehensive test coverage for all platforms - [ ] **Authentication Requirements**: Support both DID-based JWT and Passkey JWANT tokens - [ ] **Optimization Requirements**: Implement batch processing with sub-100ms delays - [ ] **Logging Requirements**: Structured logging with database persistence for debugging - [ ] **Cross-Platform Requirements**: Unified SQLite/IndexedDB storage across platforms ## Risks & Mitigation ### Technical Risks - **Background execution limits**: Mitigated by using platform-specific background task systems - **Authentication complexity**: Mitigated by implementing gradual migration path - **Cross-platform consistency**: Mitigated by shared interfaces and careful API design ### Timeline Risks - **Platform-specific complexity**: Mitigated by prioritizing Android first, then iOS - **Testing complexity**: Mitigated by automated testing and CI/CD integration - **Integration challenges**: Mitigated by maintaining backward compatibility --- **Status**: Host-provided activeDid implementation plan (Option A) - Ready for implementation **Next Steps**: Begin Phase 1 implementation with Android HTTP client setup **Dependencies**: Android Studio, Xcode, Capacitor CLI, existing plugin infrastructure, @capacitor-community/sqlite **Option A Features**: - **Simplified Architecture**: Host always provides activeDid, no database sharing - **TimeSafari Integration**: PlatformServiceMixin coordination without database access - **Cross-Platform**: Android/Electron plugin-managed SQLite, Web host-managed storage, iOS plugin-managed Core Data - **Authentication**: DID-based JWT with host-provided activeDid - **Background Tasks**: Event-based activeDid changes, clear separation of concerns