diff --git a/doc/BACKGROUND_DATA_FETCHING_PLAN.md b/doc/BACKGROUND_DATA_FETCHING_PLAN.md index 939b0c9..62beeb0 100644 --- a/doc/BACKGROUND_DATA_FETCHING_PLAN.md +++ b/doc/BACKGROUND_DATA_FETCHING_PLAN.md @@ -3,7 +3,7 @@ **Author**: Matthew Raymer **Version**: 1.0.0 **Created**: 2025-10-02 07:47:04 UTC -**Last Updated**: 2025-10-02 11:30:00 UTC +**Last Updated**: 2025-10-02 12:30:00 UTC ## Overview @@ -258,81 +258,72 @@ Based on TimeSafari's optimization patterns: #### **Database Integration Patterns** -**Android/Electron: Hybrid activeDid Approach** +**Android/Electron: Host-Provided activeDid Approach (Option A)** ```typescript -// TimeSafari integration with hybrid activeDid management +// 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: 'hybrid', - hostDbPath: 'timesafari.sqlite' // Access TimeSafari's active_identity + storageType: 'plugin-managed' // Plugin owns its storage }); -// Host provides current activeDid -const activeIdentity = await this.$getActiveIdentity(); -await plugin.setActiveDidFromHost(activeIdentity.activeDid); - -// CRITICAL: Set up activeDid change listener +// 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}`); }); - -// Enable automatic background lookup -await plugin.enableAutoActiveDidMode(); ``` -**Web: Host-Managed Database with Hybrid activeDid** +**Web: Host-Provided activeDid Approach (Option A)** ```typescript -// Host application manages absurd-sql database +// 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: 'hybrid' + storageType: 'host-managed' // Plugin delegates to host for storage }); -// 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); +// Plugin operates independently with provided activeDid +const results = await plugin.executeContentFetch(contentConfig); ``` -**iOS: Hybrid Approach with Active Identity Access** +**iOS: Host-Provided activeDid Approach (Option A)** ```typescript -// Plugin manages Core Data + reads TimeSafari's active_identity +// 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: 'hybrid', - hostDbPath: 'timesafari.sqlite' + storageType: 'plugin-managed' // Plugin owns Core Data storage }); -// Host provides activeDid initially -const activeIdentity = await this.$getActiveIdentity(); -await plugin.setActiveDidFromHost(activeIdentity.activeDid); - -// Plugin can refresh activeDid in background tasks -await plugin.enableAutoActiveDidMode(); +// Plugin operates with provided activeDid, no database sharing needed +const results = await plugin.executeBackgroundContentFetch(); ``` #### **New Plugin Methods Required** ```typescript interface EnhancedDailyNotificationPlugin { - // Database configuration - Platform-aware based on TimeSafari integration + // Database configuration - Simplified Option A approach configureDatabase(options: { platform: 'android' | 'ios' | 'web' | 'electron'; - storageType: 'plugin-managed' | 'host-managed' | 'hybrid'; + storageType: 'plugin-managed' | 'host-managed'; dbPath?: string; encryption?: boolean; - hostDbPath?: string; // Path to TimeSafari's active_identity database }): Promise; - // Hybrid activeDid Management (Option 3 Implementation) + // Host-provided activeDid Management (Option A Implementation) setActiveDidFromHost(activeDid: string): Promise; - refreshActiveDidFromDatabase(): Promise; - enableAutoActiveDidMode(): Promise; // Content fetch with database integration fetchAndStoreContent(config: ContentFetchConfig): Promise; @@ -362,15 +353,15 @@ interface EnhancedDailyNotificationPlugin { - **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()` +- **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**: Listen for `activeDidChanged` events from TimeSafari + - **Event-Based Notification**: Host dispatches `activeDidChanged` events - **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 -- **Coordinate** with TimeSafari's PlatformServiceMixin for identity changes + - **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 @@ -381,16 +372,16 @@ interface EnhancedDailyNotificationPlugin { 3. **Phase 3**: Integrate with notification scheduling 4. **Phase 4**: Add passkey authentication support -### Testing Approach with Hybrid activeDid Management +### 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 -- **Hybrid activeDid testing**: +- **Host-provided activeDid testing**: - Test `setActiveDidFromHost()` with TimeSafari PlatformServiceMixin - - Test `refreshActiveDidFromDatabase()` with background tasks - - Test `enableAutoActiveDidMode()` for automatic synchronization + - 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 @@ -494,18 +485,17 @@ GET {apiServer}/api/v2/report/offersToPlansOwnedByMe?afterId={jwtId}&beforeId={j - Create plugin configuration interfaces - Set up basic error handling -### Phase 2: Hybrid activeDid Integration & API Access (Weeks 3-4) +### Phase 2: Host-Provided activeDid Integration & API Access (Weeks 3-4) -- Implement hybrid activeDid management (Option 3 approach) -- Add `setActiveDidFromHost()` and `refreshActiveDidFromDatabase()` methods -- Implement TimeSafari active_identity table access +- 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**: 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 + - **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) @@ -550,12 +540,12 @@ GET {apiServer}/api/v2/report/offersToPlansOwnedByMe?afterId={jwtId}&beforeId={j --- -**Status**: Hybrid activeDid implementation plan - Ready for implementation +**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, 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 +**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 diff --git a/doc/DATABASE_ACCESS_CLARIFICATION.md b/doc/DATABASE_ACCESS_CLARIFICATION.md new file mode 100644 index 0000000..3d83d0e --- /dev/null +++ b/doc/DATABASE_ACCESS_CLARIFICATION.md @@ -0,0 +1,208 @@ +# Database Access Pattern Clarification + +**Author**: Matthew Raymer +**Version**: 1.0.0 +**Created**: 2025-10-02 12:15:00 UTC +**Last Updated**: 2025-10-02 12:15:00 UTC + +## Current Problem: Unclear Database Access Patterns + +The current hybrid scheme has **conflicting** database access patterns that would be difficult to implement practically. + +## Reality Check: What Actually Works + +### **Option A: Host Always Provides activeDid (Recommended)** + +**Implementation**: Host application **always** provides activeDid to plugin + +**Android/Electron:** +```typescript +// Host queries its own database +const activeIdentity = await this.$getActiveIdentity(); // Uses host's database access +await plugin.setActiveDidFromHost(activeIdentity.activeDid); + +// Plugin uses its own isolated database +await plugin.configureDatabase({ + platform: 'android', + storageType: 'plugin-managed', // Plugin owns its storage + encryption: false +}); +``` + +**Web:** +```typescript +// Host queries its absurd-sql database +const activeIdentity = await this.$getActiveIdentity(); +await plugin.setActiveDidFromHost(activeIdentity.activeDid); + +// Plugin uses host-delegated storage +await plugin.configureDatabase({ + platform: 'web', + storageType: 'host-managed' // Plugin delegates to host +}); +``` + +**iOS:** +```typescript +// Host uses its CapacitorSQLite +const activeIdentity = await this.$getActiveIdentity(); +await plugin.setActiveDidFromHost(activeIdentity.activeDid); + +// Plugin uses Core Data +await plugin.configureDatabase({ + platform: 'ios', + storageType: 'plugin-managed' // Plugin owns Core Data +}); +``` + +### **Option B: Plugin Polls Host Database (Backup Only)** + +**Implementation**: Plugin periodically checks for activeDid changes + +**Android/Electron:** +```typescript +// Plugin could theoretically access host database +await plugin.configureDatabase({ + platform: 'android', + storageType: 'hybrid', + hostDatabaseConnection: 'shared_connection', // Requires host coordination + pollingInterval: 30000 // Check every 30 seconds +}); +``` + +**Issues with Option B:** +- Requires careful database connection sharing +- Potential for database locking conflicts +- Race conditions during activeDid changes +- Security concerns with plugin accessing host data + +## **Recommended Implementation: Option A** + +### **Why Option A Works Better:** + +1. **Clear Separation**: Host owns identity management, plugin owns notification storage +2. **Security**: No shared database access between host and plugin +3. **Reliability**: Fewer moving parts, clearer failure modes +4. **Maintainability**: Easier to test and debug + +### **Implementation Details:** + +#### **Host Application Responsibilities:** +```typescript +class TimeSafariNotificationService { + async initialize(): Promise { + // 1. Host queries its own database for activeDid + const activeIdentity = await this.$getActiveIdentity(); + + // 2. Host provides activeDid to plugin immediately + await this.plugin.setActiveDidFromHost(activeIdentity.activeDid); + + // 3. Host sets up change listener + this.plugin.onActiveDidChange((newActiveDid) => { + this.handleActiveDidChange(newActiveDid); + }); + + // 4. Plugin configures its own independent storage + await this.plugin.configureDatabase({ + platform: this.getCurrentPlatform(), + storageType: 'plugin-managed' + }); + } + + // TimeSafari PlatformServiceMixin integration + async $updateActiveDid(newDid: string): Promise { + // Update TimeSafari's active_identity table + await this.$dbExec( + "UPDATE active_identity SET activeDid = ?, lastUpdated = ? WHERE id = 1", + [newDid, new Date().toISOString()] + ); + + // Immediately notify plugin of change + await this.plugin.refreshActiveDidFromHost(newDid); + } +} +``` + +#### **Plugin Responsibilities:** +```typescript +class DailyNotificationPlugin { + private currentActiveDid: string | null = null; + + async setActiveDidFromHost(activeDid: string): Promise { + this.currentActiveDid = activeDid; + await this.clearCacheForIdentityChange(); + await this.refreshAuthenticationTokens(); + } + + async onActiveDidChange(callback: (newActiveDid: string) => Promise): Promise { + this.activeDidChangeCallback = callback; + } + + private async refreshActiveDidFromHost(newActiveDid: string): Promise { + await this.refreshAuthenticationForNewIdentity(newActiveDid); + this.activeDidChangeCallback?.(newActiveDid); + } +} +``` + +### **Database Isolation:** + +**Plugin Storage (Independent):** +- Cached notification content +- Authentication tokens +- Background task state +- Plugin configuration + +**Host Storage (TimeSafari Owns):** +- active_identity table +- User settings +- Application data +- Authentication credentials + +## **Backup Pattern for Background Tasks** + +For cases where plugin needs to handle activeDid changes when app is closed: + +### **Token-Based Backup:** +```typescript +interface PluginConfig { + activeDid: string; + refreshToken: string; // Encrypted token that can decode new activeDid + fallbackExpiryTime: number; // When to stop using fallback +} + +// Plugin can validate if activeDid hasn't expired +await plugin.validateActiveDidNotExpired(config); +``` + +### **Lightweight Backup Check:** +```typescript +// Plugin could make lightweight API call to check current activeDid +async checkCurrentActiveDid(): Promise { + try { + const response = await this.fetch('/api/internal/active-identity', { + headers: { 'Authorization': 'Bearer ' + this.backgroundToken } + }); + return response.json().activeDid; + } catch (error) { + return null; // Fail safely + } +} +``` + +## **Key Takeaway** + +**The host application should ALWAYS provide activeDid to the plugin.** The plugin should never directly access TimeSafari's database unless there's a very specific architectural reason (which there isn't in this case). + +This approach: +- ✅ Maintains clear separation of concerns +- ✅ Avoids database access conflicts +- ✅ Ensures reliable activeDid change detection +- ✅ Simplifies implementation and testing +- ✅ Maintains security isolation + +--- + +**Status**: Clarification complete - Option A recommended +**Next Steps**: Update implementation plan to use host-provided activeDid only +**Impact**: Simplified architecture with clearer responsibilities