# TimeSafari PWA - CapacitorPlatformService Clean Changes

**Author**: Matthew Raymer  
**Version**: 1.0.0  
**Created**: 2025-10-08 06:24:57 UTC

## Overview

This document shows the **exact changes** needed to the existing TimeSafari PWA `CapacitorPlatformService` to add DailyNotification plugin functionality. The plugin code **ONLY touches Capacitor classes** - no `isCapacitor` flags needed.

## Required Changes to Existing TimeSafari PWA Code

### File: `src/services/platforms/CapacitorPlatformService.ts`

#### 1. Add New Imports (at the top of the file)

```typescript
// ADD THESE IMPORTS
import { DailyNotification } from '@timesafari/daily-notification-plugin';
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
```

#### 2. Add New Interfaces (after existing interfaces)

```typescript
// ADD THESE INTERFACES
interface PlanSummaryAndPreviousClaim {
  id: string;
  title: string;
  description: string;
  lastUpdated: string;
  previousClaim?: unknown;
}

interface StarredProjectsResponse {
  data: Array<PlanSummaryAndPreviousClaim>;
  hitLimit: boolean;
}

interface TimeSafariSettings {
  accountDid?: string;
  activeDid?: string;
  apiServer?: string;
  starredPlanHandleIds?: string[];
  lastAckedStarredPlanChangesJwtId?: string;
  [key: string]: unknown;
}
```

#### 3. Add New Properties (in the class, after existing properties)

```typescript
export class CapacitorPlatformService implements PlatformService {
  // ... existing properties ...
  
  // ADD THESE NEW PROPERTIES
  private dailyNotificationService: DailyNotification | null = null;
  private integrationService: TimeSafariIntegrationService | null = null;
  private dailyNotificationInitialized = false;
  
  // ActiveDid change tracking
  private currentActiveDid: string | null = null;
}
```

#### 4. Modify Existing updateActiveDid Method

```typescript
async updateActiveDid(did: string): Promise<void> {
  const oldDid = this.currentActiveDid;
  
  // Update the database (existing TimeSafari pattern)
  await this.dbExec(
    "UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
    [did],
  );
  
  // Update local tracking
  this.currentActiveDid = did;
  
  // Update DailyNotification plugin if initialized
  if (this.dailyNotificationInitialized) {
    await this.updateDailyNotificationActiveDid(did, oldDid);
  }
  
  logger.debug(
    `[CapacitorPlatformService] ActiveDid updated from ${oldDid} to ${did}`
  );
}
```

#### 5. Modify Existing initializeDatabase Method

```typescript
private async initializeDatabase(): Promise<void> {
  // If already initialized, return immediately
  if (this.initialized) {
    return;
  }

  // If initialization is in progress, wait for it
  if (this.initializationPromise) {
    return this.initializationPromise;
  }

  try {
    // Start initialization
    this.initializationPromise = this._initialize();
    await this.initializationPromise;
    
    // ADD THIS LINE: Initialize DailyNotification after database is ready
    await this.initializeDailyNotification();
    
  } catch (error) {
    logger.error(
      "[CapacitorPlatformService] Initialize database method failed:",
      error,
    );
    this.initializationPromise = null; // Reset on failure
    throw error;
  }
}
```

#### 6. Add New Methods (in the class, after existing methods)

```typescript
  /**
   * Initialize DailyNotification plugin with TimeSafari configuration
   */
  async initializeDailyNotification(): Promise<void> {
    if (this.dailyNotificationInitialized) {
      return;
    }

    try {
      logger.log("[CapacitorPlatformService] Initializing DailyNotification plugin...");
      
      // Get current TimeSafari settings
      const settings = await this.getTimeSafariSettings();
      
      // Get current activeDid
      const currentActiveDid = await this.getCurrentActiveDid();
      
      // Configure DailyNotification plugin with TimeSafari data
      await DailyNotification.configure({
        // Basic plugin configuration
        storage: 'tiered',
        ttlSeconds: 1800,
        enableETagSupport: true,
        enableErrorHandling: true,
        enablePerformanceOptimization: true,
        
        // TimeSafari-specific configuration
        timesafariConfig: {
          // Use current activeDid
          activeDid: currentActiveDid || '',
          
          // Use existing TimeSafari API endpoints
          endpoints: {
            offersToPerson: `${settings.apiServer}/api/v2/offers/person`,
            offersToPlans: `${settings.apiServer}/api/v2/offers/plans`,
            projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
          },
          
          // Configure starred projects fetching (matches existing TimeSafari pattern)
          starredProjectsConfig: {
            enabled: true,
            starredPlanHandleIds: settings.starredPlanHandleIds || [],
            lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
            fetchInterval: '0 8 * * *',  // Daily at 8 AM
            maxResults: 50,
            hitLimitHandling: 'warn'  // Same as existing TimeSafari error handling
          },
          
          // Sync configuration (optimized for TimeSafari use case)
          syncConfig: {
            enableParallel: true,
            maxConcurrent: 3,
            batchSize: 10,
            timeout: 30000,
            retryAttempts: 3
          },
          
          // Error policy (matches existing TimeSafari error handling)
          errorPolicy: {
            maxRetries: 3,
            backoffMultiplier: 2,
            activeDidChangeRetries: 5,  // Special retry for activeDid changes
            starredProjectsRetries: 3
          }
        },
        
        // Network configuration using existing TimeSafari patterns
        networkConfig: {
          baseURL: settings.apiServer || 'https://endorser.ch',
          timeout: 30000,
          retryAttempts: 3,
          retryDelay: 1000,
          maxConcurrent: 5,
          
          // Headers matching TimeSafari pattern
          defaultHeaders: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'TimeSafari-PWA/1.0.0'
          }
        },
        
        // Content fetch configuration (replaces existing loadNewStarredProjectChanges)
        contentFetch: {
          enabled: true,
          schedule: '0 8 * * *',  // Daily at 8 AM
          
          // Use existing TimeSafari request pattern
          requestConfig: {
            method: 'POST',
            url: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`,
            headers: {
              'Authorization': 'Bearer ${jwt}',
              'X-User-DID': '${activeDid}',
              'Content-Type': 'application/json'
            },
            body: {
              planIds: '${starredPlanHandleIds}',
              afterId: '${lastAckedJwtId}'
            }
          },
          
          // Callbacks that match TimeSafari error handling
          callbacks: {
            onSuccess: this.handleStarredProjectsSuccess.bind(this),
            onError: this.handleStarredProjectsError.bind(this),
            onComplete: this.handleStarredProjectsComplete.bind(this)
          }
        }
      });
      
      // Initialize TimeSafari Integration Service
      this.integrationService = TimeSafariIntegrationService.getInstance();
      await this.integrationService.initialize({
        activeDid: currentActiveDid || '',
        storageAdapter: this.getTimeSafariStorageAdapter(),
        endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch',
        
        // Use existing TimeSafari request patterns
        requestConfig: {
          baseURL: settings.apiServer || 'https://endorser.ch',
          timeout: 30000,
          retryAttempts: 3
        },
        
        // Configure starred projects fetching
        starredProjectsConfig: {
          enabled: true,
          starredPlanHandleIds: settings.starredPlanHandleIds || [],
          lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
          fetchInterval: '0 8 * * *',
          maxResults: 50
        }
      });
      
      // Schedule daily notifications
      await DailyNotification.scheduleDailyNotification({
        title: 'TimeSafari Community Update',
        body: 'You have new offers and project updates',
        time: '09:00',
        channel: 'timesafari_community_updates'
      });
      
      this.dailyNotificationInitialized = true;
      this.currentActiveDid = currentActiveDid;
      
      logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
      
    } catch (error) {
      logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
      throw error;
    }
  }

  /**
   * Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
   */
  async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
    // Ensure DailyNotification is initialized
    if (!this.dailyNotificationInitialized) {
      await this.initializeDailyNotification();
    }

    const settings = await this.getTimeSafariSettings();
    const currentActiveDid = await this.getCurrentActiveDid();
    
    if (!currentActiveDid || !settings.starredPlanHandleIds?.length) {
      return { data: [], hitLimit: false };
    }

    try {
      // Use plugin's enhanced fetching with same interface as existing TimeSafari code
      const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges(
        currentActiveDid,
        settings.starredPlanHandleIds,
        settings.lastAckedStarredPlanChangesJwtId
      );
      
      // Enhanced logging (optional)
      logger.log("[CapacitorPlatformService] Starred projects loaded successfully:", {
        count: starredProjectChanges.data.length,
        hitLimit: starredProjectChanges.hitLimit,
        planIds: settings.starredPlanHandleIds.length,
        activeDid: currentActiveDid
      });
      
      return starredProjectChanges;
      
    } catch (error) {
      // Same error handling as existing TimeSafari code
      logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
      return { data: [], hitLimit: false };
    }
  }

  /**
   * Update DailyNotification plugin when activeDid changes
   */
  private async updateDailyNotificationActiveDid(newDid: string, oldDid: string | null): Promise<void> {
    try {
      logger.log(`[CapacitorPlatformService] Updating DailyNotification plugin activeDid from ${oldDid} to ${newDid}`);
      
      // Get new settings for the new activeDid
      const newSettings = await this.getTimeSafariSettings();
      
      // Reconfigure DailyNotification plugin with new activeDid
      await DailyNotification.configure({
        timesafariConfig: {
          activeDid: newDid,
          endpoints: {
            offersToPerson: `${newSettings.apiServer}/api/v2/offers/person`,
            offersToPlans: `${newSettings.apiServer}/api/v2/offers/plans`,
            projectsLastUpdated: `${newSettings.apiServer}/api/v2/report/plansLastUpdatedBetween`
          },
          starredProjectsConfig: {
            enabled: true,
            starredPlanHandleIds: newSettings.starredPlanHandleIds || [],
            lastAckedJwtId: newSettings.lastAckedStarredPlanChangesJwtId || '',
            fetchInterval: '0 8 * * *'
          }
        }
      });
      
      // Update TimeSafari Integration Service
      if (this.integrationService) {
        await this.integrationService.initialize({
          activeDid: newDid,
          storageAdapter: this.getTimeSafariStorageAdapter(),
          endorserApiBaseUrl: newSettings.apiServer || 'https://endorser.ch'
        });
      }
      
      logger.log(`[CapacitorPlatformService] DailyNotification plugin updated successfully for activeDid: ${newDid}`);
      
    } catch (error) {
      logger.error(`[CapacitorPlatformService] Failed to update DailyNotification plugin activeDid:`, error);
    }
  }

  /**
   * Get current activeDid from the database
   */
  private async getCurrentActiveDid(): Promise<string | null> {
    try {
      const result = await this.dbQuery(
        "SELECT activeDid FROM active_identity WHERE id = 1"
      );
      
      if (result?.values?.length) {
        const activeDid = result.values[0][0] as string | null;
        return activeDid;
      }
      
      return null;
    } catch (error) {
      logger.error("[CapacitorPlatformService] Error getting current activeDid:", error);
      return null;
    }
  }

  /**
   * Get TimeSafari settings using existing database patterns
   */
  private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
    try {
      // Get current activeDid
      const currentActiveDid = await this.getCurrentActiveDid();
      
      if (!currentActiveDid) {
        return {};
      }

      // Use existing TimeSafari settings retrieval pattern
      const result = await this.dbQuery(
        "SELECT * FROM settings WHERE accountDid = ?",
        [currentActiveDid]
      );
      
      if (!result?.values?.length) {
        return {};
      }

      // Map database columns to values (existing TimeSafari pattern)
      const settings: TimeSafariSettings = {};
      result.columns.forEach((column, index) => {
        if (column !== 'id') {
          settings[column] = result.values[0][index];
        }
      });

      // Set activeDid from current value
      settings.activeDid = currentActiveDid;

      // Handle JSON field parsing (existing TimeSafari pattern)
      if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') {
        try {
          settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds);
        } catch {
          settings.starredPlanHandleIds = [];
        }
      }

      return settings;
    } catch (error) {
      logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
      return {};
    }
  }

  /**
   * Get TimeSafari storage adapter using existing patterns
   */
  private getTimeSafariStorageAdapter(): unknown {
    // Return existing TimeSafari storage adapter
    return {
      // Use existing TimeSafari storage patterns
      store: async (key: string, value: unknown) => {
        await this.dbExec(
          "INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
          [key, JSON.stringify(value)]
        );
      },
      
      retrieve: async (key: string) => {
        const result = await this.dbQuery(
          "SELECT data FROM temp WHERE id = ?",
          [key]
        );
        
        if (result?.values?.length) {
          try {
            return JSON.parse(result.values[0][0] as string);
          } catch {
            return null;
          }
        }
        
        return null;
      }
    };
  }

  /**
   * Callback handler for successful starred projects fetch
   */
  private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
    // Enhanced logging (optional)
    logger.log("[CapacitorPlatformService] Starred projects success callback:", {
      count: data.data.length,
      hitLimit: data.hitLimit,
      activeDid: this.currentActiveDid
    });
    
    // Store results in TimeSafari temp table for UI access
    await this.dbExec(
      "INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
      ['starred_projects_latest', JSON.stringify(data)]
    );
  }

  /**
   * Callback handler for starred projects fetch errors
   */
  private async handleStarredProjectsError(error: Error): Promise<void> {
    // Same error handling as existing TimeSafari code
    logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
    
    // Store error in TimeSafari temp table for UI access
    await this.dbExec(
      "INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
      ['starred_projects_error', JSON.stringify({ 
        error: error.message, 
        timestamp: Date.now(),
        activeDid: this.currentActiveDid
      })]
    );
  }

  /**
   * Callback handler for starred projects fetch completion
   */
  private async handleStarredProjectsComplete(result: unknown): Promise<void> {
    // Handle completion
    logger.log("[CapacitorPlatformService] Starred projects fetch completed:", {
      result,
      activeDid: this.currentActiveDid
    });
  }

  /**
   * Get DailyNotification plugin status for debugging
   */
  async getDailyNotificationStatus(): Promise<{
    initialized: boolean;
    platform: string;
    capabilities: PlatformCapabilities;
    currentActiveDid: string | null;
  }> {
    return {
      initialized: this.dailyNotificationInitialized,
      platform: Capacitor.getPlatform(),
      capabilities: this.getCapabilities(),
      currentActiveDid: this.currentActiveDid
    };
  }
```

## Package.json Changes

### Add DailyNotification Plugin Dependency

```json
{
  "dependencies": {
    "@timesafari/daily-notification-plugin": "ssh://git@173.199.124.46:222/trent_larson/daily-notification-plugin.git"
  }
}
```

## Summary of Changes

### Files Modified:
1. **`src/services/platforms/CapacitorPlatformService.ts`**
   - Add imports for DailyNotification plugin
   - Add new interfaces for plugin integration
   - Add new properties for plugin state
   - Modify existing `updateActiveDid` method
   - Modify existing `initializeDatabase` method
   - Add new methods for plugin functionality

2. **`package.json`**
   - Add DailyNotification plugin dependency

### Key Benefits:
- **Same Interface**: Existing methods work exactly the same
- **Enhanced Functionality**: Background fetching, structured logging, error handling
- **ActiveDid Change Handling**: Plugin automatically reconfigures when activeDid changes
- **No Platform Flags**: Plugin code only touches Capacitor classes
- **No Breaking Changes**: Existing code continues to work

### Migration Strategy:
1. **Add the changes** to existing TimeSafari PWA CapacitorPlatformService
2. **Test on Capacitor platforms** (Android, iOS)
3. **Verify activeDid changes** work correctly
4. **Gradually migrate** individual methods to use plugin features
5. **Leverage advanced features** like background fetching and observability

---

**These are the exact changes needed to integrate the DailyNotification plugin with the existing TimeSafari PWA CapacitorPlatformService. The plugin code ONLY touches Capacitor classes - no `isCapacitor` flags needed.**
