25 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 12:45:00 UTC
Overview
This document outlines the enhancement plan for background data fetching in the Daily Notification Plugin to support TimeSafari integration with Option A architecture. This plan builds upon our existing implementation and adds:
- Option A Enhancement: ActiveDid-aware authentication on existing infrastructure
- TimeSafari Integration: PlatformServiceMixin coordination with current plugin
- Authentication Layer: JWT/DID support over existing HTTP callback system
- Database Enhancement: Extend current storage with activeDid management
- Event & Change Management: Identity change detection on existing notification system
- API Integration: Endorser.ch endpoints through current ContentFetchConfig
This document serves as the enhancement roadmap for adding TimeSafari capabilities to our existing, working plugin.
Current Implementation Baseline
✅ Already Implemented & Working
- Android Storage: SharedPreferences + SQLite with migration (
DailyNotificationStorage.java
,DailyNotificationDatabase.java
) - Web Storage: IndexedDB with comprehensive service worker (
sw.ts
,IndexedDBManager
) - Callback System: HTTP/local callbacks with circuit breaker (
callback-registry.ts
) - Configuration: Database path, TTL, retention settings (
ConfigureOptions
) - ETag Support: Conditional HTTP requests (
DailyNotificationETagManager.java
) - Dual Scheduling: Content fetch + user notification separation
- Cross-Platform API: Unified TypeScript interface (
DailyNotificationPlugin
)
⚠️ Enhancement Required for TimeSafari
- ActiveDid Integration: Add activeDid-awareness to existing authentication
- JWT Generation: Enhance HTTP layer with DID-based tokens
- Identity Change Detection: Add event listeners to existing callback system
- Endorser.ch APIs: Extend
ContentFetchConfig
with TimeSafari endpoints - Platform Auth: Add Android Keystore/iOS Keychain to existing storage
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:
- Clear Separation: Host owns identity management, plugin owns notifications
- No Database Conflicts: Zero shared database access between host and plugin
- Security Isolation: Plugin data physically separated from user data
- Platform Independence: Works consistently regardless of host's database technology
- Simplified Implementation: Fewer moving parts, clearer debugging
Android Implementation Strategy
A. Background Execution Framework
- Use WorkManager for reliable background HTTP requests
- Enhance existing Native HTTP clients (already implemented):
- Extend
DailyNotificationETagManager.java
with JWT headers - Add JWT authentication to
DailyNotificationFetcher.java
- Extend
- Handle Android-specific constraints: Doze mode, app standby, battery optimization
B. Authentication Enhancement - Extend Current Infrastructure
Goal: Enhance existing DailyNotificationETagManager.java
and DailyNotificationFetcher.java
with JWT authentication
// Enhance existing Android infrastructure with JWT authentication
class DailyNotificationJWTManager {
private val storage: DailyNotificationStorage
private val currentActiveDid: String? = null
// Add JWT generation to existing fetcher
fun generateJWTForActiveDid(activeDid: String, expiresInSeconds: Int): String {
val payload = mapOf(
"exp" to (System.currentTimeMillis() / 1000 + expiresInSeconds),
"iat" to (System.currentTimeMillis() / 1000),
"iss" to activeDid,
"aud" to "timesafari.notifications",
"sub" to activeDid
)
return signWithDID(payload, activeDid)
}
// Enhance existing HTTP client with JWT headers
fun enhanceHttpClientWithJWT(connection: HttpURLConnection, activeDid: String) {
val jwt = generateJWTForActiveDid(activeDid, 60)
connection.setRequestProperty("Authorization", "Bearer $jwt")
connection.setRequestProperty("Content-Type", "application/json")
}
}
### C. HTTP Request Enhancement - Extend Existing Fetcher
**Goal**: Enhance existing `DailyNotificationFetcher.java` with Endorser.ch API support
```kotlin
// Enhance existing DailyNotificationFetcher.java with TimeSafari APIs
class EnhancedDailyNotificationFetcher : DailyNotificationFetcher {
private val jwtManager: DailyNotificationJWTManager
suspend fun fetchEndorserOffers(activeDid: String, afterId: String?): Result {
val connection = HttpURLConnection("$apiServer/api/v2/report/offers")
// Add JWT authentication to existing connection
jwtManager.enhanceHttpClientWithJWT(connection, activeDid)
// Add Endorser.ch specific parameters
connection.setRequestProperty("recipientDid", activeDid)
if (afterId != null) {
connection.setRequestProperty("afterId", afterId)
}
// Use existing storeAndScheduleNotification method
return fetchAndStoreContent(connection)
}
}
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
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
// TimeSafari PlatformServiceMixin modification
async $updateActiveDid(newDid: string | null): Promise<void> {
// 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
// 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 withid = 1
) - Content:
activeDid TEXT
,lastUpdated DATETIME
- Purpose: Single source of truth for active user identity
Access via PlatformServiceMixin:
// Retrieving activeDid in TimeSafari components
const activeIdentity = await this.$getActiveIdentity();
const activeDid = activeIdentity.activeDid;
// Updating activeDid via PlatformServiceMixin
async $updateActiveDid(newDid: string | null): Promise<void> {
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)
// 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)
// 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)
// 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();
Enhancement Required: Extend Current Plugin Interface
Current Interface (already implemented):
interface DailyNotificationPlugin {
configure(options: ConfigureOptions): Promise<void>;
scheduleContentFetch(config: ContentFetchConfig): Promise<void>;
scheduleUserNotification(config: UserNotificationConfig): Promise<void>;
// ... existing methods (see definitions.ts)
}
Enhancement Required (add to existing interface):
interface EnhancedDailyNotificationPlugin extends DailyNotificationPlugin {
// Enhanced configuration with activeDid support
configure(options: ConfigureOptions & {
activeDidIntegration?: {
platform: 'android' | 'ios' | 'web' | 'electron';
storageType: 'plugin-managed' | 'host-managed';
};
}): Promise<void>;
// Host-provided activeDid Management (Option A Implementation)
setActiveDidFromHost(activeDid: string): Promise<void>;
// Critical: ActiveDid Change Handling
onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): 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 host-provided activeDid access (Option A):
- Always: Host provides activeDid via
setActiveDidFromHost()
- No Database Sharing: Plugin never accesses TimeSafari's active_identity table
- Always: Host provides activeDid via
- 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
- Event-Based Notification: Host dispatches
- Maintain clear separation: Host owns identity management, plugin owns notifications
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 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
- 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: Extend Core Infrastructure (Building on Existing)
- Extend existing Android
DailyNotificationFetcher.java
with JWT authentication - Enhance existing iOS implementation (when added) with authentication layer
- Add JWT generation to existing
DailyNotificationETagManager.java
- Enhance current
ConfigureOptions
with activeDid integration - Build on existing error handling (circuit breaker already implemented)
Phase 2: ActiveDid Integration & TimeSafari API Enhancement
- Add host-provided activeDid management to existing plugin interface
- Extend existing
configure()
method with activeDid options - Enhance existing
ContentFetchConfig
with Endorser.ch API endpoints - Add
setActiveDidFromHost()
andonActiveDidChange()
to existing interface - Integrate existing
callback-registry.ts
with activeDid-aware callbacks - Enhance existing platform storage:
- Android: Extend existing SQLite with activeDid-aware JWT storage
- Web: Enhance existing IndexedDB with activeDid support (no host delegation needed initially)
Phase 3: Background Enhancement & TimeSafari Coordination
- Enhance existing WorkManager integration with activeDid-aware workers
- Coordinate existing notification scheduling with TimeSafari PlatformServiceMixin
- Extend existing app lifecycle handling with activeDid change detection
- Enhance existing state synchronization with identity management
Phase 4: TimeSafari Integration & Advanced Features
- Integrate with TimeSafari's existing PlatformServiceMixin patterns
- Add Endorser.ch API endpoint support to existing
ContentFetchConfig
- Implement DID-based authentication alongside existing callback system
- Enhance existing testing with TimeSafari-specific scenarios
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: Enhancement plan for existing implementation (Option A) - Ready for implementation
Next Steps: Begin Phase 1 - enhance existing Android HTTP infrastructure with JWT authentication
Dependencies: Existing plugin infrastructure, Android Studio, Capacitor CLI, TimeSafari PlatformServiceMixin
Enhancement Approach:
- Build on Existing: Leverage current SQLite, IndexedDB, callback system, and dual scheduling
- Option A Integration: Add activeDid management to existing configuration and HTTP layers
- TimeSafari Enhancement: Extend current ContentFetchConfig with Endorser.ch API endpoints
- Authentication Layer: Add JWT/DID authentication over existing HTTP infrastructure
- Event Integration: Enhance existing callback system with activeDid change detection