19 KiB
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 11:30:00 UTC
Overview
This document outlines the implementation plan for background data fetching in the Capacitor Daily Notification Plugin, replacing web push implementations with native platform solutions. The plan covers Android, iOS, and cross-platform considerations for API data fetching with authentication.
Platform Architecture Overview
JavaScript Layer → Native Bridge → Native Background Executor
↓
HTTP Client + Auth
↓
API Server Response
↓
Parse & Cache Data
↓
Trigger Notifications
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
// 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
// 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
// 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
// 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
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
Integration Points
Enhanced Plugin Interface for Host Application Integration
Database Integration Patterns
Android/Electron: Hybrid activeDid Approach
// TimeSafari integration with hybrid activeDid management
await plugin.configureDatabase({
platform: 'android',
storageType: 'hybrid',
hostDbPath: 'timesafari.sqlite' // Access TimeSafari's active_identity
});
// Host provides current activeDid
const activeIdentity = await this.$getActiveIdentity();
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// CRITICAL: Set up activeDid change listener
plugin.onActiveDidChange(async (newActiveDid) => {
await plugin.clearCacheForNewIdentity();
await plugin.refreshAuthenticationForNewIdentity(newActiveDid);
logger.info(`[TimeSafari] ActiveDid changed to: ${newActiveDid}`);
});
// Enable automatic background lookup
await plugin.enableAutoActiveDidMode();
Web: Host-Managed Database with Hybrid activeDid
// Host application manages absurd-sql database
await plugin.configureDatabase({
platform: 'web',
storageType: 'hybrid'
});
// Host provides activeDid (plugin doesn't access web database)
const activeIdentity = await this.$getActiveIdentity();
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// Plugin delegates SQL operations to host
const query = await plugin.getContentFetchQuery(apiEndpoint, activeDid);
const results = await hostDatabase.exec(query);
iOS: Hybrid Approach with Active Identity Access
// Plugin manages Core Data + reads TimeSafari's active_identity
await plugin.configureDatabase({
platform: 'ios',
storageType: 'hybrid',
hostDbPath: 'timesafari.sqlite'
});
// Host provides activeDid initially
const activeIdentity = await this.$getActiveIdentity();
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
// Plugin can refresh activeDid in background tasks
await plugin.enableAutoActiveDidMode();
New Plugin Methods Required
interface EnhancedDailyNotificationPlugin {
// Database configuration - Platform-aware based on TimeSafari integration
configureDatabase(options: {
platform: 'android' | 'ios' | 'web' | 'electron';
storageType: 'plugin-managed' | 'host-managed' | 'hybrid';
dbPath?: string;
encryption?: boolean;
hostDbPath?: string; // Path to TimeSafari's active_identity database
}): Promise<void>;
// Hybrid activeDid Management (Option 3 Implementation)
setActiveDidFromHost(activeDid: string): Promise<void>;
refreshActiveDidFromDatabase(): Promise<string>;
enableAutoActiveDidMode(): Promise<void>;
// Content fetch with database integration
fetchAndStoreContent(config: ContentFetchConfig): Promise<ContentFetchResult>;
// Data access for host application
getStoredContent(query: string, params?: any[]): Promise<any[]>;
clearStoredContent(options?: { olderThan?: number }): Promise<void>;
// Background task coordination
getBackgroundTaskStatus(): Promise<BackgroundTaskStatus>;
pauseBackgroundTasks(): Promise<void>;
resumeBackgroundTasks(): Promise<void>;
// TimeSafari Integration Methods
initializeFromTimeSafari(): Promise<void>;
listenForActiveDidChanges(): Promise<void>;
// Critical: ActiveDid Change Handling
onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): void;
clearCacheForNewIdentity(): Promise<void>;
refreshAuthenticationForNewIdentity(activeDid: string): Promise<void>;
}
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 dual-mode activeDid access:
- Foreground: Host provides activeDid via
setActiveDidFromHost()
- Background: Plugin looks up activeDid via
refreshActiveDidFromDatabase()
- Foreground: Host provides activeDid via
- Critical: Plugin MUST know when activeDid changes for:
- Event-Based Notification: Listen for
activeDidChanged
events from TimeSafari - Cache Invalidation: Clear cached content when user switches identity
- Token Refresh: Generate new JWT tokens with updated activeDid
- Background Task Coordination: Stop/restart tasks with new identity context
- Event-Based Notification: Listen for
- Coordinate with TimeSafari's PlatformServiceMixin for identity changes
Migration & Testing Strategy
Gradual Migration
- Phase 1: Implement basic HTTP + JWT authentication
- Phase 2: Add caching and state management
- Phase 3: Integrate with notification scheduling
- Phase 4: Add passkey authentication support
Testing Approach with Hybrid activeDid Management
- Unit tests for JWT generation and HTTP clients with activeDid
- Integration tests for API endpoint interactions using TimeSafari active_identity patterns
- Hybrid activeDid testing:
- Test
setActiveDidFromHost()
with TimeSafari PlatformServiceMixin - Test
refreshActiveDidFromDatabase()
with background tasks - Test
enableAutoActiveDidMode()
for automatic synchronization - Critical: Test
onActiveDidChange()
listener with identity switches - Test cache invalidation and token refresh during activeDid changes
- Test
- 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:
{
"data": Array<OfferSummaryRecord>,
"hitLimit": boolean
}
Offers to User Projects Endpoint
GET {apiServer}/api/v2/report/offersToPlansOwnedByMe?afterId={jwtId}&beforeId={jwtId}
Response Structure:
{
"data": Array<OfferToPlanSummaryRecord>,
"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: Hybrid activeDid Integration & API Access (Weeks 3-4)
- Implement hybrid activeDid management (Option 3 approach)
- Add
setActiveDidFromHost()
andrefreshActiveDidFromDatabase()
methods - Implement TimeSafari active_identity table access
- Integrate API endpoint calls with activeDid-based authentication
- Add response parsing and validation
- Implement platform-specific database integration:
- Android/Electron: Direct @capacitor-community/sqlite integration with active_identity access
- Web: Plugin delegation pattern with host-provided activeDid
- iOS: Core Data background thread integration with TimeSafari database access
- Add
enableAutoActiveDidMode()
for automatic identity synchronization
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: Hybrid activeDid implementation plan - 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, TimeSafari active_identity table access
Hybrid Features:
- Option 3 Implementation: Dual-mode activeDid access (host-provided + background lookup)
- TimeSafari Integration: PlatformServiceMixin coordination and active_identity table access
- Cross-Platform: Android/Electron SQLite access, Web host delegation, iOS Core Data hybrid
- Authentication: DID-based JWT with activeDid from TimeSafari's identity system
- Background Tasks: Automatic activeDid synchronization across app lifecycle states