Browse Source
- Add example showing exact changes to existing TimeSafari PWA CapacitorPlatformService - Show how to extend the existing class rather than creating a new one - Provide summary of actual code changes needed (imports, properties, methods) - Include modifications to PlatformServiceMixin and Vue components - Show how to integrate with existing database and settings patterns - Provide migration strategy for gradual adoption This gives an accurate representation of the changes needed to the actual TimeSafari PWA codebase rather than creating a separate class.master
2 changed files with 1091 additions and 0 deletions
@ -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<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; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 4. 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(); |
||||
|
|
||||
|
// 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) |
||||
|
|
||||
|
```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; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 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<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) |
||||
|
|
||||
|
```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<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 |
||||
|
|
||||
|
```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.** |
@ -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<PlanSummaryAndPreviousClaim>; |
||||
|
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<void> | null = null; |
||||
|
private operationQueue: Array<QueuedOperation> = []; |
||||
|
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<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({ |
||||
|
// 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<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 |
||||
|
); |
||||
|
|
||||
|
// 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<TimeSafariSettings> { |
||||
|
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<void> { |
||||
|
// 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<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() })] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 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<void> { |
||||
|
// 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<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; |
||||
|
|
||||
|
// 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<void> { |
||||
|
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<QueryExecResult> { |
||||
|
await this.waitForInitialization(); |
||||
|
return this.queueOperation<QueryExecResult>("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)
|
||||
|
} |
Loading…
Reference in new issue