diff --git a/docs/capacitor-platform-service-changes-summary.md b/docs/capacitor-platform-service-changes-summary.md new file mode 100644 index 0000000..575ccae --- /dev/null +++ b/docs/capacitor-platform-service-changes-summary.md @@ -0,0 +1,427 @@ +# 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) + +```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; + 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; +} +``` + +#### 4. Add New Methods (in the class, after existing methods) + +```typescript + /** + * Initialize DailyNotification plugin with TimeSafari configuration + */ + async initializeDailyNotification(): Promise { + 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 { + // 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 { + 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 { + 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 { + 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 { + 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) + +```typescript + private async initializeDatabase(): Promise { + // 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) + +```typescript + methods: { + // ... existing methods ... + + /** + * Initialize DailyNotification plugin (Capacitor only) + */ + async $initializeDailyNotification(): Promise { + if (this.isCapacitor) { + await this.platformService.initializeDailyNotification(); + } + }, + + /** + * Enhanced loadNewStarredProjectChanges method + */ + async $loadNewStarredProjectChanges(): Promise { + 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 { + 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) + +```typescript +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 { + 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 + +```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 + - 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.** diff --git a/examples/capacitor-platform-service-extension-example.ts b/examples/capacitor-platform-service-extension-example.ts new file mode 100644 index 0000000..761e29d --- /dev/null +++ b/examples/capacitor-platform-service-extension-example.ts @@ -0,0 +1,664 @@ +/** + * TimeSafari PWA - CapacitorPlatformService Extension Example + * + * This example shows how to extend the existing CapacitorPlatformService + * in the TimeSafari PWA to add DailyNotification plugin functionality. + * + * This represents the ACTUAL CHANGES needed to the existing TimeSafari PWA code. + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +// ================================================= +// EXISTING TIMESAFARI PWA CODE (unchanged) +// ================================================= + +import { Filesystem, Directory, Encoding } from "@capacitor/filesystem"; +import { + Camera, + CameraResultType, + CameraSource, + CameraDirection, +} from "@capacitor/camera"; +import { Capacitor } from "@capacitor/core"; +import { Share } from "@capacitor/share"; +import { + SQLiteConnection, + SQLiteDBConnection, + CapacitorSQLite, + DBSQLiteValues, +} from "@capacitor-community/sqlite"; + +import { runMigrations } from "@/db-sql/migration"; +import { QueryExecResult } from "@/interfaces/database"; +import { + ImageResult, + PlatformService, + PlatformCapabilities, +} from "../PlatformService"; +import { logger } from "../../utils/logger"; + +// ================================================= +// NEW IMPORTS FOR DAILYNOTIFICATION PLUGIN +// ================================================= + +import { DailyNotification } from '@timesafari/daily-notification-plugin'; +import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; + +// ================================================= +// EXISTING INTERFACES (unchanged) +// ================================================= + +interface QueuedOperation { + type: "run" | "query" | "rawQuery"; + sql: string; + params: unknown[]; + resolve: (value: unknown) => void; + reject: (reason: unknown) => void; +} + +// ================================================= +// NEW INTERFACES FOR DAILYNOTIFICATION PLUGIN +// ================================================= + +interface PlanSummaryAndPreviousClaim { + id: string; + title: string; + description: string; + lastUpdated: string; + previousClaim?: unknown; +} + +interface StarredProjectsResponse { + data: Array; + hitLimit: boolean; +} + +interface TimeSafariSettings { + accountDid?: string; + activeDid?: string; + apiServer?: string; + starredPlanHandleIds?: string[]; + lastAckedStarredPlanChangesJwtId?: string; + [key: string]: unknown; +} + +/** + * EXTENDED CapacitorPlatformService with DailyNotification functionality + * + * This shows the ACTUAL CHANGES needed to the existing TimeSafari PWA + * CapacitorPlatformService class to add DailyNotification plugin support. + */ +export class CapacitorPlatformService implements PlatformService { + // ================================================= + // EXISTING PROPERTIES (unchanged) + // ================================================= + + /** Current camera direction */ + private currentDirection: CameraDirection = CameraDirection.Rear; + + private sqlite: SQLiteConnection; + private db: SQLiteDBConnection | null = null; + private dbName = "timesafari.sqlite"; + private initialized = false; + private initializationPromise: Promise | null = null; + private operationQueue: Array = []; + private isProcessingQueue: boolean = false; + + // ================================================= + // NEW PROPERTIES FOR DAILYNOTIFICATION PLUGIN + // ================================================= + + private dailyNotificationService: DailyNotification | null = null; + private integrationService: TimeSafariIntegrationService | null = null; + private dailyNotificationInitialized = false; + + // ================================================= + // EXISTING CONSTRUCTOR (unchanged) + // ================================================= + + constructor() { + this.sqlite = new SQLiteConnection(CapacitorSQLite); + } + + // ================================================= + // NEW METHOD: Initialize DailyNotification Plugin + // ================================================= + + /** + * Initialize DailyNotification plugin with TimeSafari configuration + * + * This method should be called after the database is initialized + * to set up the DailyNotification plugin with TimeSafari-specific settings. + */ + async initializeDailyNotification(): Promise { + 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({ + // Basic plugin configuration + storage: 'tiered', + ttlSeconds: 1800, + enableETagSupport: true, + enableErrorHandling: true, + enablePerformanceOptimization: true, + + // TimeSafari-specific configuration + timesafariConfig: { + // Use existing TimeSafari activeDid + activeDid: settings.activeDid || '', + + // 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, + 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) + } + }, + + // Authentication configuration + authentication: { + jwt: { + secret: process.env.JWT_SECRET || 'timesafari-jwt-secret', + algorithm: 'HS256', + expirationMinutes: 60, + refreshThresholdMinutes: 10 + } + }, + + // Observability configuration + logging: { + level: 'INFO', + enableRequestLogging: true, + enableResponseLogging: true, + enableErrorLogging: true, + redactSensitiveData: true + }, + + // Security configuration + security: { + certificatePinning: { + enabled: true, + pins: [ + { + hostname: 'endorser.ch', + pins: ['sha256/YOUR_PIN_HERE'] + } + ] + } + } + }); + + // Initialize TimeSafari Integration Service + this.integrationService = TimeSafariIntegrationService.getInstance(); + await this.integrationService.initialize({ + activeDid: settings.activeDid || '', + 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; + logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully"); + + } catch (error) { + logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error); + throw error; + } + } + + // ================================================= + // NEW METHOD: Enhanced loadNewStarredProjectChanges + // ================================================= + + /** + * Enhanced version of existing TimeSafari loadNewStarredProjectChanges method + * + * This method replaces the existing TimeSafari PWA method with plugin-enhanced + * functionality while maintaining the same interface and behavior. + * + * This is the ACTUAL METHOD that would replace the existing TimeSafari method. + */ + async loadNewStarredProjectChanges(): Promise { + // 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 + ); + + // Enhanced logging (optional) + logger.log("[CapacitorPlatformService] Starred projects loaded successfully:", { + count: starredProjectChanges.data.length, + hitLimit: starredProjectChanges.hitLimit, + planIds: settings.starredPlanHandleIds.length + }); + + 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 }; + } + } + + // ================================================= + // NEW METHOD: Get TimeSafari Settings + // ================================================= + + /** + * Get TimeSafari settings using existing database patterns + * + * This method uses the existing TimeSafari database patterns to retrieve + * settings that are needed for the DailyNotification plugin configuration. + */ + private async getTimeSafariSettings(): Promise { + try { + // Use existing TimeSafari settings retrieval pattern + const result = await this.dbQuery( + "SELECT * FROM settings WHERE id = 1" + ); + + 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]; + } + }); + + // 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 {}; + } + } + + // ================================================= + // NEW METHOD: Get TimeSafari Storage Adapter + // ================================================= + + /** + * Get TimeSafari storage adapter using existing patterns + * + * This method creates a storage adapter that uses the existing TimeSafari + * database patterns for the DailyNotification plugin. + */ + 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; + } + }; + } + + // ================================================= + // NEW METHODS: Callback Handlers + // ================================================= + + /** + * Callback handler for successful starred projects fetch + * + * This method handles successful starred projects fetches and stores + * the results in the existing TimeSafari database for UI access. + */ + private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise { + // Enhanced logging (optional) + 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)] + ); + } + + /** + * Callback handler for starred projects fetch errors + * + * This method handles starred projects fetch errors and stores + * the error information in the existing TimeSafari database for UI access. + */ + private async handleStarredProjectsError(error: Error): Promise { + // 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() })] + ); + } + + /** + * Callback handler for starred projects fetch completion + * + * This method handles the completion of starred projects fetches + * and provides additional logging and cleanup. + */ + private async handleStarredProjectsComplete(result: unknown): Promise { + // Handle completion + logger.log("[CapacitorPlatformService] Starred projects fetch completed:", result); + } + + // ================================================= + // NEW METHOD: Get DailyNotification Status + // ================================================= + + /** + * Get DailyNotification plugin status for debugging + * + * This method provides status information about the DailyNotification + * plugin for debugging and monitoring purposes. + */ + async getDailyNotificationStatus(): Promise<{ + initialized: boolean; + platform: string; + capabilities: PlatformCapabilities; + }> { + return { + initialized: this.dailyNotificationInitialized, + platform: Capacitor.getPlatform(), + capabilities: this.getCapabilities() + }; + } + + // ================================================= + // EXISTING METHODS (unchanged - showing key ones) + // ================================================= + + private async initializeDatabase(): Promise { + // 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; + + // NEW: 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; + } + } + + private async _initialize(): Promise { + if (this.initialized) { + return; + } + + try { + // Create/Open database + this.db = await this.sqlite.createConnection( + this.dbName, + false, + "no-encryption", + 1, + false, + ); + + await this.db.open(); + + // Set journal mode to WAL for better performance + // await this.db.execute("PRAGMA journal_mode=WAL;"); + + // Run migrations + await this.runCapacitorMigrations(); + + this.initialized = true; + logger.log( + "[CapacitorPlatformService] SQLite database initialized successfully", + ); + + // Start processing the queue after initialization + this.processQueue(); + } catch (error) { + logger.error( + "[CapacitorPlatformService] Error initializing SQLite database:", + error, + ); + throw new Error( + "[CapacitorPlatformService] Failed to initialize database", + ); + } + } + + // ... (all other existing methods remain unchanged) + + /** + * Gets the capabilities of the Capacitor platform + * @returns Platform capabilities object + */ + getCapabilities(): PlatformCapabilities { + const platform = Capacitor.getPlatform(); + + return { + hasFileSystem: true, + hasCamera: true, + isMobile: true, // Capacitor is always mobile + isIOS: platform === "ios", + hasFileDownload: false, // Mobile platforms need sharing + needsFileHandlingInstructions: true, // Mobile needs instructions + isNativeApp: true, + }; + } + + /** + * @see PlatformService.dbQuery + */ + async dbQuery(sql: string, params?: unknown[]): Promise { + await this.waitForInitialization(); + return this.queueOperation("query", sql, params || []); + } + + /** + * @see PlatformService.dbExec + */ + async dbExec( + sql: string, + params?: unknown[], + ): Promise<{ changes: number; lastId?: number }> { + await this.waitForInitialization(); + return this.queueOperation<{ changes: number; lastId?: number }>( + "run", + sql, + params || [], + ); + } + + // ... (all other existing methods remain unchanged) + + /** + * Checks if running on Capacitor platform. + * @returns true, as this is the Capacitor implementation + */ + isCapacitor(): boolean { + return true; + } + + /** + * Checks if running on Electron platform. + * @returns false, as this is Capacitor, not Electron + */ + isElectron(): boolean { + return false; + } + + /** + * Checks if running on web platform. + * @returns false, as this is not web + */ + isWeb(): boolean { + return false; + } + + // ... (all other existing methods remain unchanged) +}