You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

12 KiB

TimeSafari PWA - CapacitorPlatformService Changes Summary

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

Overview

This document summarizes the exact changes needed to the existing TimeSafari PWA CapacitorPlatformService to add DailyNotification plugin functionality. These are the actual modifications required to the existing codebase.

Required Changes to Existing TimeSafari PWA Code

File: src/services/platforms/CapacitorPlatformService.ts

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

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

2. Add New Interfaces (after existing interfaces)

// 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)

export class CapacitorPlatformService implements PlatformService {
  // ... existing properties ...
  
  // ADD THESE NEW PROPERTIES
  private dailyNotificationService: DailyNotification | null = null;
  private integrationService: TimeSafariIntegrationService | null = null;
  private dailyNotificationInitialized = false;
}

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

  /**
   * 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();
      
      // Configure DailyNotification plugin with TimeSafari data
      await DailyNotification.configure({
        timesafariConfig: {
          activeDid: settings.activeDid || '',
          endpoints: {
            projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
          },
          starredProjectsConfig: {
            enabled: true,
            starredPlanHandleIds: settings.starredPlanHandleIds || [],
            lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
            fetchInterval: '0 8 * * *'
          }
        },
        networkConfig: {
          baseURL: settings.apiServer || 'https://endorser.ch',
          timeout: 30000
        }
      });
      
      // Initialize TimeSafari Integration Service
      this.integrationService = TimeSafariIntegrationService.getInstance();
      await this.integrationService.initialize({
        activeDid: settings.activeDid || '',
        storageAdapter: this.getTimeSafariStorageAdapter(),
        endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch'
      });
      
      this.dailyNotificationInitialized = true;
      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();
    
    if (!settings.activeDid || !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(
        settings.activeDid,
        settings.starredPlanHandleIds,
        settings.lastAckedStarredPlanChangesJwtId
      );
      
      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 };
    }
  }

  /**
   * Get TimeSafari settings using existing database patterns
   */
  private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
    try {
      const result = await this.dbQuery("SELECT * FROM settings WHERE id = 1");
      
      if (!result?.values?.length) {
        return {};
      }

      const settings: TimeSafariSettings = {};
      result.columns.forEach((column, index) => {
        if (column !== 'id') {
          settings[column] = result.values[0][index];
        }
      });

      // Handle JSON field parsing
      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 {
      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 handlers for DailyNotification plugin
   */
  private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
    logger.log("[CapacitorPlatformService] Starred projects success callback:", {
      count: data.data.length,
      hitLimit: data.hitLimit
    });
    
    // 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)]
    );
  }

  private async handleStarredProjectsError(error: Error): Promise<void> {
    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() })]
    );
  }

  private async handleStarredProjectsComplete(result: unknown): Promise<void> {
    logger.log("[CapacitorPlatformService] Starred projects fetch completed:", result);
  }

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

5. Modify Existing Method (update the initializeDatabase method)

  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;
    }
  }

Required Changes to PlatformServiceMixin

File: src/utils/PlatformServiceMixin.ts

1. Add New Methods (in the methods section)

  methods: {
    // ... existing methods ...
    
    /**
     * Initialize DailyNotification plugin (Capacitor only)
     */
    async $initializeDailyNotification(): Promise<void> {
      if (this.isCapacitor) {
        await this.platformService.initializeDailyNotification();
      }
    },

    /**
     * Enhanced loadNewStarredProjectChanges method
     */
    async $loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
      if (this.isCapacitor) {
        return await this.platformService.loadNewStarredProjectChanges();
      } else {
        // Fall back to existing web method
        return await this.$loadNewStarredProjectChangesWeb();
      }
    },

    /**
     * Get DailyNotification status for debugging
     */
    async $getDailyNotificationStatus(): Promise<unknown> {
      if (this.isCapacitor) {
        return await this.platformService.getDailyNotificationStatus();
      }
      return { initialized: false, platform: 'web' };
    }
  }

Required Changes to Vue Components

File: src/views/HomeView.vue (or similar)

1. Add DailyNotification Initialization (in mounted lifecycle)

export default defineComponent({
  name: 'HomeView',
  
  mixins: [PlatformServiceMixin],
  
  async mounted() {
    // ADD THIS: Initialize DailyNotification (only on Capacitor)
    if (this.isCapacitor) {
      await this.$initializeDailyNotification();
    }
    
    // ... existing mounted code ...
  },

  methods: {
    // ... existing methods ...
    
    /**
     * Enhanced loadNewStarredProjectChanges method
     */
    async loadNewStarredProjectChanges(): Promise<void> {
      if (this.isCapacitor) {
        // Use plugin-enhanced method on Capacitor
        const result = await this.$loadNewStarredProjectChanges();
        this.numNewStarredProjectChanges = result.data.length;
        this.newStarredProjectChangesHitLimit = result.hitLimit;
      } else {
        // Use existing web method in browser
        await this.loadNewStarredProjectChangesWeb();
      }
    }
  }
});

Package.json Changes

Add DailyNotification Plugin Dependency

{
  "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
    • Add new methods for plugin functionality
    • Modify existing initializeDatabase method
  2. src/utils/PlatformServiceMixin.ts

    • Add new methods for plugin integration
    • Add platform detection for plugin usage
  3. Vue Components (e.g., src/views/HomeView.vue)

    • Add plugin initialization in mounted lifecycle
    • Add enhanced methods for plugin functionality
  4. package.json

    • Add DailyNotification plugin dependency

Key Benefits:

  • Same Interface: Existing methods work exactly the same
  • Enhanced Functionality: Background fetching, structured logging, error handling
  • Platform Detection: Only initializes on Capacitor platforms
  • Gradual Migration: Can be adopted incrementally
  • No Breaking Changes: Existing code continues to work

Migration Strategy:

  1. Add the changes to existing TimeSafari PWA code
  2. Test on Capacitor platforms (Android, iOS)
  3. Verify web fallback works 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 architecture.