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