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