Browse Source
- Consolidated DATABASE_ACCESS_CLARIFICATION.md content into main plan - Consolidated ACTIVE_DID_CHANGE_REQUIREMENTS.md content into main plan - Consolidated TIMESAFARI_INTEGRATION_ANALYSIS.md content into main plan - Enhanced main document with Option A architecture overview - Added comprehensive TimeSafari integration patterns section - Added critical requirement section for activeDid change detection - Added event-based solution implementation details - Updated README.md to reference single consolidated document - Eliminated unnecessary document proliferation as requested The BACKGROUND_DATA_FETCHING_PLAN.md now serves as the single source of truth for all implementation guidance, containing Option A architecture, TimeSafari integration patterns, activeDid change management, and platform-specific details.research/notification-plugin-enhancement
5 changed files with 107 additions and 762 deletions
@ -1,204 +0,0 @@ |
|||
# ActiveDid Change Requirements |
|||
|
|||
**Author**: Matthew Raymer |
|||
**Version**: 1.0.0 |
|||
**Created**: 2025-10-02 12:00:00 UTC |
|||
**Last Updated**: 2025-10-02 12:00:00 UTC |
|||
|
|||
## Critical Requirement: Plugin Must Know When activeDid Changes |
|||
|
|||
**CONFIRMED**: The plugin **must** be notified when activeDid changes to maintain data integrity and security. |
|||
|
|||
## Why This Is Critical |
|||
|
|||
### **Security Implications** |
|||
- **Authentication Isolation**: Each activeDid has different authentication tokens |
|||
- **Data Privacy**: Cached content belongs to specific user identity |
|||
- **Authorization**: API calls must use correct activeDid for proper authorization |
|||
|
|||
### **Data Integrity Issues** |
|||
- **Cache Pollution**: Wrong user's data could leak between identities |
|||
- **Stale Authentication**: Old JWT tokens become invalid for new identity |
|||
- **Background Tasks**: Tasks running with wrong identity context |
|||
|
|||
### **User Experience Problems** |
|||
```typescript |
|||
// PROBLEMATIC SCENARIO WITHOUT CHANGE NOTIFICATION: |
|||
// 1. User A schedules notification for "my offers" |
|||
// 2. User switches to User B |
|||
// 3. Plugin doesn't know about change |
|||
// 4. Plugin delivers User A's personal offer data to User B ❌ |
|||
``` |
|||
|
|||
## Solution: Event-Based Change Notification |
|||
|
|||
### **TimeSafari Host Application** |
|||
```typescript |
|||
// In PlatformServiceMixin.ts - CRITICAL PATTERN |
|||
async $updateActiveDid(newDid: string | null): Promise<void> { |
|||
// ... existing activeDid update logic ... |
|||
|
|||
// MUST notify Daily Notification Plugin |
|||
await this.$notifyDailyNotificationService(newDid); |
|||
} |
|||
|
|||
async $notifyDailyNotificationService(newActiveDid: string): Promise<void> { |
|||
const event = new CustomEvent('activeDidChanged', { |
|||
detail: { activeDid: newActiveDid } |
|||
}); |
|||
document.dispatchEvent(event); |
|||
} |
|||
``` |
|||
|
|||
### **Plugin Side Implementation** |
|||
```typescript |
|||
// In Daily Notification Plugin - MANDATORY LISTENING |
|||
export class EnhancedDailyNotificationPlugin { |
|||
private currentActiveDid: string | null = null; |
|||
|
|||
constructor() { |
|||
this.setupActiveDidChangeListener(); |
|||
} |
|||
|
|||
private setupActiveDidChangeListener(): void { |
|||
document.addEventListener('activeDidChanged', (event: CustomEvent) => { |
|||
this.handleActiveDidChange(event.detail.activeDid); |
|||
}); |
|||
} |
|||
|
|||
private async handleActiveDidChange(newActiveDid: string): Promise<void> { |
|||
if (newActiveDid !== this.currentActiveDid) { |
|||
logger.info(`[Plugin] ActiveDid changing from ${this.currentActiveDid} to ${newActiveDid}`); |
|||
|
|||
// CRITICAL ACTIONS REQUIRED: |
|||
await this.clearCacheForNewIdentity(); |
|||
await this.refreshAuthenticationForNewIdentity(newActiveDid); |
|||
await this.updateBackgroundTaskIdentity(newActiveDid); |
|||
|
|||
this.currentActiveDid = newActiveDid; |
|||
} |
|||
} |
|||
|
|||
async clearCacheForNewIdentity(): Promise<void> { |
|||
// Clear all cached content to prevent data leakage |
|||
await this.clearStoredContent(); |
|||
await this.clearAuthenticationTokens(); |
|||
} |
|||
|
|||
async refreshAuthenticationForNewIdentity(activeDid: string): Promise<void> { |
|||
// Generate new JWT tokens with correct activeDid |
|||
const newJwt = await this.generateJWT(activeDid); |
|||
this.currentAuthToken = newJwt; |
|||
} |
|||
|
|||
async updateBackgroundTaskIdentity(activeDid: string): Promise<void> { |
|||
// Update background tasks to use new identity |
|||
await this.pauseBackgroundTasks(); |
|||
await this.configureBackgroundTasksForIdentity(activeDid); |
|||
await this.resumeBackgroundTasks(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Implementation Requirements |
|||
|
|||
### **Host Application Responsibilities** |
|||
1. **Event Dispatch**: Must dispatch `activeDidChanged` events |
|||
2. **Timing**: Dispatch immediately after updating active_identity table |
|||
3. **Reliability**: Event must reach all plugin listeners |
|||
|
|||
### **Plugin Responsibilities** |
|||
1. **Event Listening**: Must listen for `activeDidChanged` events |
|||
2. **Cache Clearing**: Must clear all cached content for new identity |
|||
3. **Authentication Refresh**: Must generate new tokens with updated activeDid |
|||
4. **Background Task Update**: Must restart background tasks with new context |
|||
5. **Error Handling**: Must handle failures gracefully during identity transition |
|||
|
|||
## Testing Requirements |
|||
|
|||
### **Integration Tests** |
|||
```typescript |
|||
describe('ActiveDid Change Handling', () => { |
|||
it('should clear cache when activeDid changes', async () => { |
|||
// Set up initial identity |
|||
await plugin.setActiveDidFromHost('did:alice:123'); |
|||
await plugin.cacheContentData({ /* Alice's data */ }); |
|||
|
|||
// Simulate identity change |
|||
const event = new CustomEvent('activeDidChanged', { |
|||
detail: { activeDid: 'did:bob:456' } |
|||
}); |
|||
document.dispatchEvent(event); |
|||
|
|||
// Verify cache is cleared |
|||
expect(await plugin.getCachedContent()).toBeNull(); |
|||
}); |
|||
|
|||
it('should refresh authentication tokens', async () => { |
|||
// Set initial identity |
|||
const aliceToken = await plugin.generateJWT('did:alice:123'); |
|||
|
|||
// Change identity |
|||
document.dispatchEvent(new CustomEvent('activeDidChanged', { |
|||
detail: { activeDid: 'did:bob:456' } |
|||
})); |
|||
|
|||
// Verify new token with correct identity |
|||
const bobToken = await plugin.getCurrentAuthToken(); |
|||
expect(bobToken.payload.sub).toBe('did:bob:456'); |
|||
}); |
|||
|
|||
it('should handle multiple rapid identity changes', async () => { |
|||
// Test rapid switching between identities |
|||
const identities = ['did:alice:123', 'did:bob:456', 'did:alice:123']; |
|||
|
|||
for (const identity of identities) { |
|||
document.dispatchEvent(new CustomEvent('activeDidChanged', { |
|||
detail: { activeDid: identity } |
|||
})); |
|||
await new Promise(resolve => setTimeout(resolve, 100)); // Brief pause |
|||
} |
|||
|
|||
// Ensure plugin ends up in correct state |
|||
expect(plugin.currentActiveDid).toBe('did:alice:123'); |
|||
}); |
|||
}); |
|||
``` |
|||
|
|||
### **Edge Case Testing** |
|||
- **Network Failure**: Identity changes during network failures |
|||
- **Background Mode**: Identity changes when app is backgrounded |
|||
- **Rapid Switches**: Multiple identity changes in quick succession |
|||
- **Invalid activeDid**: Change to empty or invalid activeDid |
|||
|
|||
## Platform-Specific Considerations |
|||
|
|||
### **Android/Electron** |
|||
- Plugin can detect activeDid changes in background via database polling |
|||
- Backup mechanism if event-based notification fails |
|||
|
|||
### **Web** |
|||
- Event-based notification is primary mechanism |
|||
- Fallback to periodic activeDid checking |
|||
|
|||
### **iOS** |
|||
- Background tasks can check activeDid changes |
|||
- Event-based notification for foreground changes |
|||
|
|||
## Performance Considerations |
|||
|
|||
### **Change Detection Optimization** |
|||
- Debounce rapid identity changes |
|||
- Batch cache clearing operations |
|||
- Minimize background task restarts |
|||
|
|||
### **Resource Cleanup** |
|||
- Clear authentication tokens immediately |
|||
- Clean up cached content efficiently |
|||
- Avoid memory leaks during transitions |
|||
|
|||
--- |
|||
|
|||
**Status**: Critical requirement documented - Implementation required |
|||
**Next Steps**: Add activeDid change listeners to plugin implementation |
|||
**Security Impact**: HIGH - Prevents data leakage between user identities |
@ -1,208 +0,0 @@ |
|||
# 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 |
@ -1,343 +0,0 @@ |
|||
# TimeSafari Daily Notification Plugin Integration Analysis |
|||
|
|||
**Author**: Matthew Raymer |
|||
**Version**: 1.0.0 |
|||
**Created**: 2025-10-02 11:00:00 UTC |
|||
**Last Updated**: 2025-10-02 11:00:00 UTC |
|||
|
|||
## Overview |
|||
|
|||
This document analyzes how the Daily Notification Plugin would integrate with TimeSafari, focusing on `activeDid` access and plugin hosting architecture. |
|||
|
|||
## ActiveDid Management in TimeSafari |
|||
|
|||
### Database Schema: `active_identity` Table |
|||
|
|||
**Table Structure:** |
|||
```sql |
|||
CREATE TABLE active_identity ( |
|||
id INTEGER PRIMARY KEY DEFAULT 1, |
|||
activeDid TEXT NOT NULL, |
|||
lastUpdated DATETIME DEFAULT (datetime('now')) |
|||
); |
|||
``` |
|||
|
|||
**Key Characteristics:** |
|||
- Single row table with `id = 1` always |
|||
- `activeDid` contains the currently active user's DID |
|||
- `lastUpdated` tracks when the active identity was switched |
|||
- Single source of truth for current user identity |
|||
|
|||
### Access Methods |
|||
|
|||
**1. Platform Service Level:** |
|||
```typescript |
|||
// In CapacitorPlatformService, WebPlatformService, ElectronPlatformService |
|||
async updateActiveDid(did: string): Promise<void> { |
|||
await this.dbExec( |
|||
"INSERT OR REPLACE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, ?, ?)", |
|||
[did, new Date().toISOString()] |
|||
); |
|||
} |
|||
|
|||
async getActiveIdentity(): Promise<{ activeDid: string }> { |
|||
const result = await this.dbQuery( |
|||
"SELECT activeDid FROM active_identity WHERE id = 1" |
|||
); |
|||
return { |
|||
activeDid: (result?.values?.[0]?.[0] as string) || "" |
|||
}; |
|||
} |
|||
``` |
|||
|
|||
**2. Vue Mixin Level:** |
|||
```typescript |
|||
// In PlatformServiceMixin.ts |
|||
async $updateActiveDid(newDid: string | null): Promise<void> { |
|||
await this.$dbExec( |
|||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", |
|||
[newDid || ""] |
|||
); |
|||
} |
|||
|
|||
async $getActiveIdentity(): Promise<{ activeDid: string }> { |
|||
const result = await this.$dbExec( |
|||
"SELECT activeDid FROM active_identity WHERE id = 1" |
|||
); |
|||
return { activeDid: result.activeDid || "" }; |
|||
} |
|||
``` |
|||
|
|||
**3. Component Level Usage:** |
|||
```typescript |
|||
// In Vue components |
|||
const activeIdentity = await this.$getActiveIdentity(); |
|||
const activeDid = activeIdentity.activeDid || ""; |
|||
``` |
|||
|
|||
## Plugin Integration Options |
|||
|
|||
### Option 1: Host Application Manages activeDid |
|||
|
|||
**Pros:** |
|||
- Simple integration |
|||
- Plugin doesn't need database access |
|||
- Clear separation of concerns |
|||
|
|||
**Implementation:** |
|||
```typescript |
|||
// In TimeSafari application |
|||
import { DailyNotification } from '@timesafari/daily-notification-plugin'; |
|||
|
|||
// On app initialization or user switch |
|||
async function initializeNotifications() { |
|||
const activeIdentity = await this.$getActiveIdentity(); |
|||
const activeDid = activeIdentity.activeDid; |
|||
|
|||
if (activeDid) { |
|||
await DailyNotification.setActiveDid(activeDid); |
|||
await DailyNotification.configure({ |
|||
apiServer: 'https://endorser.ch', |
|||
activeDid: activeDid, |
|||
// ... other config |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**When to Use:** |
|||
- Host application already has database access |
|||
- Plugin should be stateless regarding identity |
|||
- Simple notification scheduling only |
|||
|
|||
### Option 2: Plugin Looks Up activeDid |
|||
|
|||
**Pros:** |
|||
- Plugin manages its own identity context |
|||
- Automatic activeDid synchronization |
|||
- Background tasks have direct access |
|||
|
|||
**Implementation:** |
|||
```typescript |
|||
// Plugin has database access via @capacitor-community/sqlite |
|||
interface EnhancedDailyNotificationPlugin { |
|||
async initializeFromTimeSafari(): Promise<void> { |
|||
// Plugin accesses TimeSafari's active_identity table |
|||
const result = await this.dbQuery( |
|||
"SELECT activeDid FROM active_identity WHERE id = 1" |
|||
); |
|||
const activeDid = result?.values?.[0]?.[0] as string; |
|||
|
|||
await this.setActiveDid(activeDid); |
|||
await this.scheduleBackgroundTasks(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**When to Use:** |
|||
- Plugin needs autonomous background operation |
|||
- Host application integration should be minimal |
|||
- Plugin operates independently of app lifecycle |
|||
|
|||
### Option 3: Hybrid Approach (Recommended) |
|||
|
|||
**Implementation:** |
|||
```typescript |
|||
// Host provides activeDid when app is active |
|||
// Plugin looks up activeDid for background operations |
|||
|
|||
interface HybridDailyNotificationPlugin { |
|||
// Host-initiated activeDid setting |
|||
async setActiveDidFromHost(activeDid: string): Promise<void>; |
|||
|
|||
// Plugin-initiated activeDid lookup for background |
|||
async refreshActiveDidFromDatabase(): Promise<string>; |
|||
|
|||
// Automatic mode that uses both approaches |
|||
async enableAutoActiveDidMode(): Promise<void>; |
|||
} |
|||
|
|||
// In TimeSafari app |
|||
async function initializeNotifications() { |
|||
const activeIdentity = await this.$getActiveIdentity(); |
|||
const activeDid = activeIdentity.activeDid; |
|||
|
|||
if (activeDid) { |
|||
await DailyNotification.setActiveDidFromHost(activeDid); |
|||
await DailyNotification.enableAutoActiveDidMode(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Recommended Plugin Hosting Architecture |
|||
|
|||
### Service Layer Integration |
|||
|
|||
**Location**: `src/services/DailyNotificationService.ts` |
|||
|
|||
```typescript |
|||
// src/services/DailyNotificationService.ts |
|||
import { DailyNotification } from '@timesafari/daily-notification-plugin'; |
|||
import { PlatformServiceFactory } from './PlatformServiceFactory'; |
|||
|
|||
export class DailyNotificationService { |
|||
private plugin: DailyNotification; |
|||
private platform = PlatformServiceFactory.getInstance(); |
|||
private currentActiveDid: string | null = null; |
|||
|
|||
async initialize(): Promise<void> { |
|||
// Initialize plugin with current active identity |
|||
const activeIdentity = await this.platform.getActiveIdentity(); |
|||
this.currentActiveDid = activeIdentity.activeDid; |
|||
|
|||
if (this.currentActiveDid) { |
|||
await this.plugin.setActiveDid(this.currentActiveDid); |
|||
await this.plugin.configure({ |
|||
apiServer: import.meta.env.VITE_ENDORSER_API_SERVER, |
|||
storageType: 'plugin-managed', |
|||
notifications: { |
|||
offers: true, |
|||
projects: true, |
|||
people: true, |
|||
items: true |
|||
} |
|||
}); |
|||
|
|||
// Listen for identity changes |
|||
this.setupIdentityChangeListener(); |
|||
} |
|||
} |
|||
|
|||
private setupIdentityChangeListener(): void { |
|||
// Subscribe to identity changes in TimeSafari |
|||
document.addEventListener('activeDidChanged', (event: CustomEvent) => { |
|||
this.handleActiveDidChange(event.detail.activeDid); |
|||
}); |
|||
} |
|||
|
|||
private async handleActiveDidChange(newActiveDid: string): Promise<void> { |
|||
if (newActiveDid !== this.currentActiveDid) { |
|||
this.currentActiveDid = newActiveDid; |
|||
await this.plugin.setActiveDid(newActiveDid); |
|||
logger.info(`[DailyNotificationService] ActiveDid updated to: ${newActiveDid}`); |
|||
|
|||
``` |
|||
|
|||
### PlatformServiceMixin Integration |
|||
|
|||
**Location**: `src/utils/PlatformServiceMixin.ts` |
|||
|
|||
```typescript |
|||
// Add to PlatformServiceMixin.ts |
|||
async $notifyDailyNotificationService(): Promise<void> { |
|||
// Trigger notification service update when activeDid changes |
|||
const event = new CustomEvent('activeDidChanged', { |
|||
detail: { activeDid: this.currentActiveDid } |
|||
}); |
|||
document.dispatchEvent(event); |
|||
} |
|||
|
|||
// Modify existing $updateActiveDid method |
|||
async $updateActiveDid(newDid: string | null): Promise<void> { |
|||
// ... existing logic ... |
|||
|
|||
// Notify DailyNotification service |
|||
await this.$notifyDailyNotificationService(); |
|||
} |
|||
``` |
|||
|
|||
### App Initialization Integration |
|||
|
|||
**Location**: `src/main.ts` or App component |
|||
|
|||
```typescript |
|||
// src/main.ts |
|||
import { DailyNotificationService } from '@/services/DailyNotificationService'; |
|||
|
|||
async function initializeNotifications() { |
|||
try { |
|||
const notificationService = new DailyNotificationService(); |
|||
await notificationService.initialize(); |
|||
|
|||
logger.info('[App] Daily notification service initialized successfully'); |
|||
} catch (error) { |
|||
logger.error('[App] Failed to initialize notification service:', error); |
|||
} |
|||
} |
|||
|
|||
// Call after platform service initialization |
|||
initializePlatform().then(async () => { |
|||
await initializeNotifications(); |
|||
// ... rest of app initialization |
|||
}); |
|||
``` |
|||
|
|||
## Cross-Platform Considerations |
|||
|
|||
### Android (Capacitor) |
|||
- **Database Access**: Plugin uses `@capacitor-community/sqlite` |
|||
- **identity Access**: Can read TimeSafari's active_identity table directly |
|||
- **Background Tasks**: WorkManager can query active_identity independently |
|||
|
|||
### Web (absurd-sql) |
|||
- **Database Access**: Plugin delegates to host application |
|||
- **identity Access**: Host provides activeDid, plugin doesn't access database |
|||
- **Background Tasks**: Service Worker coordination with host database |
|||
|
|||
### iOS (Capacitor) |
|||
- **Database Access**: Plugin uses CapacitorSQLite |
|||
- **identity Access**: Can read TimeSafari's active_identity table |
|||
- **Background Tasks**: BGTaskScheduler coordination |
|||
|
|||
### Electron |
|||
- **Database Access**: Plugin uses `@capacitor-community/sqlite` + filesystem |
|||
- **identity Access**: Direct file-based database access |
|||
- **Background Tasks**: Electron main process coordination |
|||
|
|||
## Security Considerations |
|||
|
|||
### activeDid Isolation |
|||
- Plugin should not modify active_identity table |
|||
- Plugin should only read current activeDid |
|||
- Identity changes should be initiated by TimeSafari host application |
|||
|
|||
### Token Management |
|||
- JWT tokens should use activeDid for both `iss` and `sub` |
|||
- Token generation should happen in plugin context |
|||
- Host application should not handle authentication tokens |
|||
|
|||
### Background Execution |
|||
- Background tasks should validate activeDid hasn't changed |
|||
- Implement cache invalidation when activeDid changes |
|||
- Handle edge cases where activeDid becomes empty/invalid |
|||
|
|||
## Implementation Priority |
|||
|
|||
### Phase 1: Basic Integration |
|||
1. Host-managed activeDid (Option 1) |
|||
2. Service layer integration |
|||
3. Basic notification scheduling |
|||
|
|||
### Phase 2: Enhanced Integration |
|||
1. Hybrid activeDid management (Option 3) |
|||
2. Background database access |
|||
3. Automatic identity synchronization |
|||
|
|||
### Phase 3: Advanced Features |
|||
1. Cross-platform optimization |
|||
2. Advanced background task coordination |
|||
3. Performance monitoring and analytics |
|||
|
|||
## Success Criteria |
|||
|
|||
- [ ] Plugin correctly receives activeDid from TimeSafari host |
|||
- [ ] Background notifications work across identity changes |
|||
- [] Cross-platform consistency maintained |
|||
- [ ] Minimal impact on TimeSafari performance |
|||
- [ ] Secure isolation between plugin and host database operations |
|||
|
|||
--- |
|||
|
|||
**Status**: Architectural analysis complete - Ready for implementation planning |
|||
**Next Steps**: Choose integration approach and begin Phase 1 implementation |
|||
**Dependencies**: TimeSafari active_identity table access, PlatformServiceMixin integration |
Loading…
Reference in new issue