Browse Source

refactor: Switch to Option A host-provided activeDid approach

- Implemented Option A from DATABASE_ACCESS_CLARIFICATION.md
- Simplified architecture: Host ALWAYS provides activeDid to plugin
- Removed database sharing complexity and hostDbPath requirements
- Updated all platform integrations:
  * Android/Electron: Plugin-managed storage, no host database access
  * Web: Host-managed storage delegation, plugin doesn't access absurd-sql
  * iOS: Plugin-managed Core Data, no host database access
- Streamlined plugin interface to remove hybrid complexity
- Enhanced separation of concerns:
  * Host: Owns active_identity table and user management
  * Plugin: Owns notification caching and background tasks
- Updated testing strategy to verify database isolation
- Simplified implementation phases and dependencies

This approach eliminates database access conflicts and provides clearer
architectural boundaries between TimeSafari host and notification plugin.
research/notification-plugin-enhancement
Matthew Raymer 18 hours ago
parent
commit
514ca38768
  1. 122
      doc/BACKGROUND_DATA_FETCHING_PLAN.md
  2. 208
      doc/DATABASE_ACCESS_CLARIFICATION.md

122
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<void>;
// Hybrid activeDid Management (Option 3 Implementation)
// Host-provided activeDid Management (Option A Implementation)
setActiveDidFromHost(activeDid: string): Promise<void>;
refreshActiveDidFromDatabase(): Promise<string>;
enableAutoActiveDidMode(): Promise<void>;
// Content fetch with database integration
fetchAndStoreContent(config: ContentFetchConfig): Promise<ContentFetchResult>;
@ -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

208
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<void> {
// 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<void> {
// 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<void> {
this.currentActiveDid = activeDid;
await this.clearCacheForIdentityChange();
await this.refreshAuthenticationTokens();
}
async onActiveDidChange(callback: (newActiveDid: string) => Promise<void>): Promise<void> {
this.activeDidChangeCallback = callback;
}
private async refreshActiveDidFromHost(newActiveDid: string): Promise<void> {
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<string | null> {
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
Loading…
Cancel
Save