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.
This commit is contained in:
@@ -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
|
||||
await plugin.configureDatabase({
|
||||
platform: 'android',
|
||||
storageType: 'hybrid',
|
||||
hostDbPath: 'timesafari.sqlite' // Access TimeSafari's active_identity
|
||||
});
|
||||
|
||||
// Host provides current activeDid
|
||||
const activeIdentity = await this.$getActiveIdentity();
|
||||
// Host queries its own database and provides activeDid
|
||||
const activeIdentity = await this.$getActiveIdentity(); // Uses host's CapacitorSQLite
|
||||
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
|
||||
|
||||
// CRITICAL: Set up activeDid change listener
|
||||
// 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}`);
|
||||
});
|
||||
|
||||
// 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
|
||||
await plugin.configureDatabase({
|
||||
platform: 'ios',
|
||||
storageType: 'hybrid',
|
||||
hostDbPath: 'timesafari.sqlite'
|
||||
});
|
||||
|
||||
// Host provides activeDid initially
|
||||
const activeIdentity = await this.$getActiveIdentity();
|
||||
// Host queries its CapacitorSQLite database and provides activeDid
|
||||
const activeIdentity = await this.$getActiveIdentity(); // Uses host's CapacitorSQLite
|
||||
await plugin.setActiveDidFromHost(activeIdentity.activeDid);
|
||||
|
||||
// Plugin can refresh activeDid in background tasks
|
||||
await plugin.enableAutoActiveDidMode();
|
||||
// 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 - 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
Normal file
208
doc/DATABASE_ACCESS_CLARIFICATION.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user