# 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 { 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 { 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 { // 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; // Plugin-initiated activeDid lookup for background async refreshActiveDidFromDatabase(): Promise; // Automatic mode that uses both approaches async enableAutoActiveDidMode(): Promise; } // 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 { // 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 { 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 { // 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 { // ... 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