Browse Source
- 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
2 changed files with 264 additions and 66 deletions
@ -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…
Reference in new issue