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