docs: add clean CapacitorPlatformService integration without isCapacitor flags
- Add example showing DailyNotification plugin integration ONLY in Capacitor classes - Remove all isCapacitor flags since plugin is only used on Capacitor platforms - Show actual changes needed to existing TimeSafari PWA CapacitorPlatformService - Include activeDid change handling and plugin reconfiguration - Provide clean summary of exact code changes needed - Focus on Capacitor-specific implementation without platform detection This gives a cleaner integration approach where plugin code only touches Capacitor classes and doesn't need platform detection flags.
This commit is contained in:
577
docs/capacitor-platform-service-clean-changes.md
Normal file
577
docs/capacitor-platform-service-clean-changes.md
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
# TimeSafari PWA - CapacitorPlatformService Clean Changes
|
||||||
|
|
||||||
|
**Author**: Matthew Raymer
|
||||||
|
**Version**: 1.0.0
|
||||||
|
**Created**: 2025-10-08 06:24:57 UTC
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document shows the **exact changes** needed to the existing TimeSafari PWA `CapacitorPlatformService` to add DailyNotification plugin functionality. The plugin code **ONLY touches Capacitor classes** - no `isCapacitor` flags needed.
|
||||||
|
|
||||||
|
## Required Changes to Existing TimeSafari PWA Code
|
||||||
|
|
||||||
|
### File: `src/services/platforms/CapacitorPlatformService.ts`
|
||||||
|
|
||||||
|
#### 1. Add New Imports (at the top of the file)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ADD THESE IMPORTS
|
||||||
|
import { DailyNotification } from '@timesafari/daily-notification-plugin';
|
||||||
|
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin';
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Add New Interfaces (after existing interfaces)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ADD THESE INTERFACES
|
||||||
|
interface PlanSummaryAndPreviousClaim {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
lastUpdated: string;
|
||||||
|
previousClaim?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StarredProjectsResponse {
|
||||||
|
data: Array<PlanSummaryAndPreviousClaim>;
|
||||||
|
hitLimit: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeSafariSettings {
|
||||||
|
accountDid?: string;
|
||||||
|
activeDid?: string;
|
||||||
|
apiServer?: string;
|
||||||
|
starredPlanHandleIds?: string[];
|
||||||
|
lastAckedStarredPlanChangesJwtId?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Add New Properties (in the class, after existing properties)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class CapacitorPlatformService implements PlatformService {
|
||||||
|
// ... existing properties ...
|
||||||
|
|
||||||
|
// ADD THESE NEW PROPERTIES
|
||||||
|
private dailyNotificationService: DailyNotification | null = null;
|
||||||
|
private integrationService: TimeSafariIntegrationService | null = null;
|
||||||
|
private dailyNotificationInitialized = false;
|
||||||
|
|
||||||
|
// ActiveDid change tracking
|
||||||
|
private currentActiveDid: string | null = null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. Modify Existing updateActiveDid Method
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
async updateActiveDid(did: string): Promise<void> {
|
||||||
|
const oldDid = this.currentActiveDid;
|
||||||
|
|
||||||
|
// Update the database (existing TimeSafari pattern)
|
||||||
|
await this.dbExec(
|
||||||
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||||
|
[did],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update local tracking
|
||||||
|
this.currentActiveDid = did;
|
||||||
|
|
||||||
|
// Update DailyNotification plugin if initialized
|
||||||
|
if (this.dailyNotificationInitialized) {
|
||||||
|
await this.updateDailyNotificationActiveDid(did, oldDid);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`[CapacitorPlatformService] ActiveDid updated from ${oldDid} to ${did}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. Modify Existing initializeDatabase Method
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private async initializeDatabase(): Promise<void> {
|
||||||
|
// If already initialized, return immediately
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If initialization is in progress, wait for it
|
||||||
|
if (this.initializationPromise) {
|
||||||
|
return this.initializationPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Start initialization
|
||||||
|
this.initializationPromise = this._initialize();
|
||||||
|
await this.initializationPromise;
|
||||||
|
|
||||||
|
// ADD THIS LINE: Initialize DailyNotification after database is ready
|
||||||
|
await this.initializeDailyNotification();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"[CapacitorPlatformService] Initialize database method failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
this.initializationPromise = null; // Reset on failure
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. Add New Methods (in the class, after existing methods)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* Initialize DailyNotification plugin with TimeSafari configuration
|
||||||
|
*/
|
||||||
|
async initializeDailyNotification(): Promise<void> {
|
||||||
|
if (this.dailyNotificationInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log("[CapacitorPlatformService] Initializing DailyNotification plugin...");
|
||||||
|
|
||||||
|
// Get current TimeSafari settings
|
||||||
|
const settings = await this.getTimeSafariSettings();
|
||||||
|
|
||||||
|
// Get current activeDid
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
// Configure DailyNotification plugin with TimeSafari data
|
||||||
|
await DailyNotification.configure({
|
||||||
|
// Basic plugin configuration
|
||||||
|
storage: 'tiered',
|
||||||
|
ttlSeconds: 1800,
|
||||||
|
enableETagSupport: true,
|
||||||
|
enableErrorHandling: true,
|
||||||
|
enablePerformanceOptimization: true,
|
||||||
|
|
||||||
|
// TimeSafari-specific configuration
|
||||||
|
timesafariConfig: {
|
||||||
|
// Use current activeDid
|
||||||
|
activeDid: currentActiveDid || '',
|
||||||
|
|
||||||
|
// Use existing TimeSafari API endpoints
|
||||||
|
endpoints: {
|
||||||
|
offersToPerson: `${settings.apiServer}/api/v2/offers/person`,
|
||||||
|
offersToPlans: `${settings.apiServer}/api/v2/offers/plans`,
|
||||||
|
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure starred projects fetching (matches existing TimeSafari pattern)
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *', // Daily at 8 AM
|
||||||
|
maxResults: 50,
|
||||||
|
hitLimitHandling: 'warn' // Same as existing TimeSafari error handling
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sync configuration (optimized for TimeSafari use case)
|
||||||
|
syncConfig: {
|
||||||
|
enableParallel: true,
|
||||||
|
maxConcurrent: 3,
|
||||||
|
batchSize: 10,
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error policy (matches existing TimeSafari error handling)
|
||||||
|
errorPolicy: {
|
||||||
|
maxRetries: 3,
|
||||||
|
backoffMultiplier: 2,
|
||||||
|
activeDidChangeRetries: 5, // Special retry for activeDid changes
|
||||||
|
starredProjectsRetries: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Network configuration using existing TimeSafari patterns
|
||||||
|
networkConfig: {
|
||||||
|
baseURL: settings.apiServer || 'https://endorser.ch',
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3,
|
||||||
|
retryDelay: 1000,
|
||||||
|
maxConcurrent: 5,
|
||||||
|
|
||||||
|
// Headers matching TimeSafari pattern
|
||||||
|
defaultHeaders: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'User-Agent': 'TimeSafari-PWA/1.0.0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content fetch configuration (replaces existing loadNewStarredProjectChanges)
|
||||||
|
contentFetch: {
|
||||||
|
enabled: true,
|
||||||
|
schedule: '0 8 * * *', // Daily at 8 AM
|
||||||
|
|
||||||
|
// Use existing TimeSafari request pattern
|
||||||
|
requestConfig: {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ${jwt}',
|
||||||
|
'X-User-DID': '${activeDid}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
planIds: '${starredPlanHandleIds}',
|
||||||
|
afterId: '${lastAckedJwtId}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Callbacks that match TimeSafari error handling
|
||||||
|
callbacks: {
|
||||||
|
onSuccess: this.handleStarredProjectsSuccess.bind(this),
|
||||||
|
onError: this.handleStarredProjectsError.bind(this),
|
||||||
|
onComplete: this.handleStarredProjectsComplete.bind(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize TimeSafari Integration Service
|
||||||
|
this.integrationService = TimeSafariIntegrationService.getInstance();
|
||||||
|
await this.integrationService.initialize({
|
||||||
|
activeDid: currentActiveDid || '',
|
||||||
|
storageAdapter: this.getTimeSafariStorageAdapter(),
|
||||||
|
endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch',
|
||||||
|
|
||||||
|
// Use existing TimeSafari request patterns
|
||||||
|
requestConfig: {
|
||||||
|
baseURL: settings.apiServer || 'https://endorser.ch',
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure starred projects fetching
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *',
|
||||||
|
maxResults: 50
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Schedule daily notifications
|
||||||
|
await DailyNotification.scheduleDailyNotification({
|
||||||
|
title: 'TimeSafari Community Update',
|
||||||
|
body: 'You have new offers and project updates',
|
||||||
|
time: '09:00',
|
||||||
|
channel: 'timesafari_community_updates'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dailyNotificationInitialized = true;
|
||||||
|
this.currentActiveDid = currentActiveDid;
|
||||||
|
|
||||||
|
logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method
|
||||||
|
*/
|
||||||
|
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
|
||||||
|
// Ensure DailyNotification is initialized
|
||||||
|
if (!this.dailyNotificationInitialized) {
|
||||||
|
await this.initializeDailyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await this.getTimeSafariSettings();
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
if (!currentActiveDid || !settings.starredPlanHandleIds?.length) {
|
||||||
|
return { data: [], hitLimit: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use plugin's enhanced fetching with same interface as existing TimeSafari code
|
||||||
|
const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges(
|
||||||
|
currentActiveDid,
|
||||||
|
settings.starredPlanHandleIds,
|
||||||
|
settings.lastAckedStarredPlanChangesJwtId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enhanced logging (optional)
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects loaded successfully:", {
|
||||||
|
count: starredProjectChanges.data.length,
|
||||||
|
hitLimit: starredProjectChanges.hitLimit,
|
||||||
|
planIds: settings.starredPlanHandleIds.length,
|
||||||
|
activeDid: currentActiveDid
|
||||||
|
});
|
||||||
|
|
||||||
|
return starredProjectChanges;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Same error handling as existing TimeSafari code
|
||||||
|
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
|
||||||
|
return { data: [], hitLimit: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DailyNotification plugin when activeDid changes
|
||||||
|
*/
|
||||||
|
private async updateDailyNotificationActiveDid(newDid: string, oldDid: string | null): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.log(`[CapacitorPlatformService] Updating DailyNotification plugin activeDid from ${oldDid} to ${newDid}`);
|
||||||
|
|
||||||
|
// Get new settings for the new activeDid
|
||||||
|
const newSettings = await this.getTimeSafariSettings();
|
||||||
|
|
||||||
|
// Reconfigure DailyNotification plugin with new activeDid
|
||||||
|
await DailyNotification.configure({
|
||||||
|
timesafariConfig: {
|
||||||
|
activeDid: newDid,
|
||||||
|
endpoints: {
|
||||||
|
offersToPerson: `${newSettings.apiServer}/api/v2/offers/person`,
|
||||||
|
offersToPlans: `${newSettings.apiServer}/api/v2/offers/plans`,
|
||||||
|
projectsLastUpdated: `${newSettings.apiServer}/api/v2/report/plansLastUpdatedBetween`
|
||||||
|
},
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: newSettings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: newSettings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update TimeSafari Integration Service
|
||||||
|
if (this.integrationService) {
|
||||||
|
await this.integrationService.initialize({
|
||||||
|
activeDid: newDid,
|
||||||
|
storageAdapter: this.getTimeSafariStorageAdapter(),
|
||||||
|
endorserApiBaseUrl: newSettings.apiServer || 'https://endorser.ch'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(`[CapacitorPlatformService] DailyNotification plugin updated successfully for activeDid: ${newDid}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[CapacitorPlatformService] Failed to update DailyNotification plugin activeDid:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current activeDid from the database
|
||||||
|
*/
|
||||||
|
private async getCurrentActiveDid(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT activeDid FROM active_identity WHERE id = 1"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.values?.length) {
|
||||||
|
const activeDid = result.values[0][0] as string | null;
|
||||||
|
return activeDid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Error getting current activeDid:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TimeSafari settings using existing database patterns
|
||||||
|
*/
|
||||||
|
private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
|
||||||
|
try {
|
||||||
|
// Get current activeDid
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
if (!currentActiveDid) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use existing TimeSafari settings retrieval pattern
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
|
[currentActiveDid]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result?.values?.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map database columns to values (existing TimeSafari pattern)
|
||||||
|
const settings: TimeSafariSettings = {};
|
||||||
|
result.columns.forEach((column, index) => {
|
||||||
|
if (column !== 'id') {
|
||||||
|
settings[column] = result.values[0][index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set activeDid from current value
|
||||||
|
settings.activeDid = currentActiveDid;
|
||||||
|
|
||||||
|
// Handle JSON field parsing (existing TimeSafari pattern)
|
||||||
|
if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') {
|
||||||
|
try {
|
||||||
|
settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds);
|
||||||
|
} catch {
|
||||||
|
settings.starredPlanHandleIds = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TimeSafari storage adapter using existing patterns
|
||||||
|
*/
|
||||||
|
private getTimeSafariStorageAdapter(): unknown {
|
||||||
|
// Return existing TimeSafari storage adapter
|
||||||
|
return {
|
||||||
|
// Use existing TimeSafari storage patterns
|
||||||
|
store: async (key: string, value: unknown) => {
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
[key, JSON.stringify(value)]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
retrieve: async (key: string) => {
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT data FROM temp WHERE id = ?",
|
||||||
|
[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.values?.length) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(result.values[0][0] as string);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for successful starred projects fetch
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
|
||||||
|
// Enhanced logging (optional)
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects success callback:", {
|
||||||
|
count: data.data.length,
|
||||||
|
hitLimit: data.hitLimit,
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store results in TimeSafari temp table for UI access
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
['starred_projects_latest', JSON.stringify(data)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for starred projects fetch errors
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsError(error: Error): Promise<void> {
|
||||||
|
// Same error handling as existing TimeSafari code
|
||||||
|
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
|
||||||
|
|
||||||
|
// Store error in TimeSafari temp table for UI access
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
['starred_projects_error', JSON.stringify({
|
||||||
|
error: error.message,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
})]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for starred projects fetch completion
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsComplete(result: unknown): Promise<void> {
|
||||||
|
// Handle completion
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects fetch completed:", {
|
||||||
|
result,
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get DailyNotification plugin status for debugging
|
||||||
|
*/
|
||||||
|
async getDailyNotificationStatus(): Promise<{
|
||||||
|
initialized: boolean;
|
||||||
|
platform: string;
|
||||||
|
capabilities: PlatformCapabilities;
|
||||||
|
currentActiveDid: string | null;
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
initialized: this.dailyNotificationInitialized,
|
||||||
|
platform: Capacitor.getPlatform(),
|
||||||
|
capabilities: this.getCapabilities(),
|
||||||
|
currentActiveDid: this.currentActiveDid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package.json Changes
|
||||||
|
|
||||||
|
### Add DailyNotification Plugin Dependency
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@timesafari/daily-notification-plugin": "ssh://git@173.199.124.46:222/trent_larson/daily-notification-plugin.git"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary of Changes
|
||||||
|
|
||||||
|
### Files Modified:
|
||||||
|
1. **`src/services/platforms/CapacitorPlatformService.ts`**
|
||||||
|
- Add imports for DailyNotification plugin
|
||||||
|
- Add new interfaces for plugin integration
|
||||||
|
- Add new properties for plugin state
|
||||||
|
- Modify existing `updateActiveDid` method
|
||||||
|
- Modify existing `initializeDatabase` method
|
||||||
|
- Add new methods for plugin functionality
|
||||||
|
|
||||||
|
2. **`package.json`**
|
||||||
|
- Add DailyNotification plugin dependency
|
||||||
|
|
||||||
|
### Key Benefits:
|
||||||
|
- **Same Interface**: Existing methods work exactly the same
|
||||||
|
- **Enhanced Functionality**: Background fetching, structured logging, error handling
|
||||||
|
- **ActiveDid Change Handling**: Plugin automatically reconfigures when activeDid changes
|
||||||
|
- **No Platform Flags**: Plugin code only touches Capacitor classes
|
||||||
|
- **No Breaking Changes**: Existing code continues to work
|
||||||
|
|
||||||
|
### Migration Strategy:
|
||||||
|
1. **Add the changes** to existing TimeSafari PWA CapacitorPlatformService
|
||||||
|
2. **Test on Capacitor platforms** (Android, iOS)
|
||||||
|
3. **Verify activeDid changes** work correctly
|
||||||
|
4. **Gradually migrate** individual methods to use plugin features
|
||||||
|
5. **Leverage advanced features** like background fetching and observability
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**These are the exact changes needed to integrate the DailyNotification plugin with the existing TimeSafari PWA CapacitorPlatformService. The plugin code ONLY touches Capacitor classes - no `isCapacitor` flags needed.**
|
||||||
746
examples/capacitor-platform-service-clean-integration.ts
Normal file
746
examples/capacitor-platform-service-clean-integration.ts
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
/**
|
||||||
|
* TimeSafari PWA - CapacitorPlatformService Clean Integration Example
|
||||||
|
*
|
||||||
|
* This example shows the ACTUAL CHANGES needed to the existing TimeSafari PWA
|
||||||
|
* CapacitorPlatformService to add DailyNotification plugin functionality.
|
||||||
|
*
|
||||||
|
* The plugin code ONLY touches Capacitor classes - no isCapacitor flags needed.
|
||||||
|
*
|
||||||
|
* @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 Integration
|
||||||
|
*
|
||||||
|
* This shows the ACTUAL CHANGES needed to the existing TimeSafari PWA
|
||||||
|
* CapacitorPlatformService class. The plugin code ONLY touches this class.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
// ActiveDid change tracking
|
||||||
|
private currentActiveDid: string | null = null;
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// EXISTING CONSTRUCTOR (unchanged)
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// MODIFIED METHOD: Enhanced updateActiveDid with DailyNotification Integration
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced updateActiveDid method that handles DailyNotification plugin updates
|
||||||
|
*
|
||||||
|
* This method extends the existing updateActiveDid method to also update
|
||||||
|
* the DailyNotification plugin when the activeDid changes.
|
||||||
|
*/
|
||||||
|
async updateActiveDid(did: string): Promise<void> {
|
||||||
|
const oldDid = this.currentActiveDid;
|
||||||
|
|
||||||
|
// Update the database (existing TimeSafari pattern)
|
||||||
|
await this.dbExec(
|
||||||
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||||
|
[did],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update local tracking
|
||||||
|
this.currentActiveDid = did;
|
||||||
|
|
||||||
|
// Update DailyNotification plugin if initialized
|
||||||
|
if (this.dailyNotificationInitialized) {
|
||||||
|
await this.updateDailyNotificationActiveDid(did, oldDid);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`[CapacitorPlatformService] ActiveDid updated from ${oldDid} to ${did}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Update DailyNotification Plugin ActiveDid
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update DailyNotification plugin when activeDid changes
|
||||||
|
*/
|
||||||
|
private async updateDailyNotificationActiveDid(newDid: string, oldDid: string | null): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.log(`[CapacitorPlatformService] Updating DailyNotification plugin activeDid from ${oldDid} to ${newDid}`);
|
||||||
|
|
||||||
|
// Get new settings for the new activeDid
|
||||||
|
const newSettings = await this.getTimeSafariSettings();
|
||||||
|
|
||||||
|
// Reconfigure DailyNotification plugin with new activeDid
|
||||||
|
await DailyNotification.configure({
|
||||||
|
timesafariConfig: {
|
||||||
|
activeDid: newDid,
|
||||||
|
endpoints: {
|
||||||
|
offersToPerson: `${newSettings.apiServer}/api/v2/offers/person`,
|
||||||
|
offersToPlans: `${newSettings.apiServer}/api/v2/offers/plans`,
|
||||||
|
projectsLastUpdated: `${newSettings.apiServer}/api/v2/report/plansLastUpdatedBetween`
|
||||||
|
},
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: newSettings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: newSettings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update TimeSafari Integration Service
|
||||||
|
if (this.integrationService) {
|
||||||
|
await this.integrationService.initialize({
|
||||||
|
activeDid: newDid,
|
||||||
|
storageAdapter: this.getTimeSafariStorageAdapter(),
|
||||||
|
endorserApiBaseUrl: newSettings.apiServer || 'https://endorser.ch'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(`[CapacitorPlatformService] DailyNotification plugin updated successfully for activeDid: ${newDid}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[CapacitorPlatformService] Failed to update DailyNotification plugin activeDid:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Initialize DailyNotification Plugin
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize DailyNotification plugin with TimeSafari configuration
|
||||||
|
*/
|
||||||
|
async initializeDailyNotification(): Promise<void> {
|
||||||
|
if (this.dailyNotificationInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logger.log("[CapacitorPlatformService] Initializing DailyNotification plugin...");
|
||||||
|
|
||||||
|
// Get current TimeSafari settings
|
||||||
|
const settings = await this.getTimeSafariSettings();
|
||||||
|
|
||||||
|
// Get current activeDid
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
// Configure DailyNotification plugin with TimeSafari data
|
||||||
|
await DailyNotification.configure({
|
||||||
|
// Basic plugin configuration
|
||||||
|
storage: 'tiered',
|
||||||
|
ttlSeconds: 1800,
|
||||||
|
enableETagSupport: true,
|
||||||
|
enableErrorHandling: true,
|
||||||
|
enablePerformanceOptimization: true,
|
||||||
|
|
||||||
|
// TimeSafari-specific configuration
|
||||||
|
timesafariConfig: {
|
||||||
|
// Use current activeDid
|
||||||
|
activeDid: currentActiveDid || '',
|
||||||
|
|
||||||
|
// Use existing TimeSafari API endpoints
|
||||||
|
endpoints: {
|
||||||
|
offersToPerson: `${settings.apiServer}/api/v2/offers/person`,
|
||||||
|
offersToPlans: `${settings.apiServer}/api/v2/offers/plans`,
|
||||||
|
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure starred projects fetching (matches existing TimeSafari pattern)
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *', // Daily at 8 AM
|
||||||
|
maxResults: 50,
|
||||||
|
hitLimitHandling: 'warn' // Same as existing TimeSafari error handling
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sync configuration (optimized for TimeSafari use case)
|
||||||
|
syncConfig: {
|
||||||
|
enableParallel: true,
|
||||||
|
maxConcurrent: 3,
|
||||||
|
batchSize: 10,
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error policy (matches existing TimeSafari error handling)
|
||||||
|
errorPolicy: {
|
||||||
|
maxRetries: 3,
|
||||||
|
backoffMultiplier: 2,
|
||||||
|
activeDidChangeRetries: 5, // Special retry for activeDid changes
|
||||||
|
starredProjectsRetries: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Network configuration using existing TimeSafari patterns
|
||||||
|
networkConfig: {
|
||||||
|
baseURL: settings.apiServer || 'https://endorser.ch',
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3,
|
||||||
|
retryDelay: 1000,
|
||||||
|
maxConcurrent: 5,
|
||||||
|
|
||||||
|
// Headers matching TimeSafari pattern
|
||||||
|
defaultHeaders: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'User-Agent': 'TimeSafari-PWA/1.0.0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Content fetch configuration (replaces existing loadNewStarredProjectChanges)
|
||||||
|
contentFetch: {
|
||||||
|
enabled: true,
|
||||||
|
schedule: '0 8 * * *', // Daily at 8 AM
|
||||||
|
|
||||||
|
// Use existing TimeSafari request pattern
|
||||||
|
requestConfig: {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`,
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ${jwt}',
|
||||||
|
'X-User-DID': '${activeDid}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
planIds: '${starredPlanHandleIds}',
|
||||||
|
afterId: '${lastAckedJwtId}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Callbacks that match TimeSafari error handling
|
||||||
|
callbacks: {
|
||||||
|
onSuccess: this.handleStarredProjectsSuccess.bind(this),
|
||||||
|
onError: this.handleStarredProjectsError.bind(this),
|
||||||
|
onComplete: this.handleStarredProjectsComplete.bind(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize TimeSafari Integration Service
|
||||||
|
this.integrationService = TimeSafariIntegrationService.getInstance();
|
||||||
|
await this.integrationService.initialize({
|
||||||
|
activeDid: currentActiveDid || '',
|
||||||
|
storageAdapter: this.getTimeSafariStorageAdapter(),
|
||||||
|
endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch',
|
||||||
|
|
||||||
|
// Use existing TimeSafari request patterns
|
||||||
|
requestConfig: {
|
||||||
|
baseURL: settings.apiServer || 'https://endorser.ch',
|
||||||
|
timeout: 30000,
|
||||||
|
retryAttempts: 3
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure starred projects fetching
|
||||||
|
starredProjectsConfig: {
|
||||||
|
enabled: true,
|
||||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [],
|
||||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '',
|
||||||
|
fetchInterval: '0 8 * * *',
|
||||||
|
maxResults: 50
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Schedule daily notifications
|
||||||
|
await DailyNotification.scheduleDailyNotification({
|
||||||
|
title: 'TimeSafari Community Update',
|
||||||
|
body: 'You have new offers and project updates',
|
||||||
|
time: '09:00',
|
||||||
|
channel: 'timesafari_community_updates'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dailyNotificationInitialized = true;
|
||||||
|
this.currentActiveDid = currentActiveDid;
|
||||||
|
|
||||||
|
logger.log("[CapacitorPlatformService] DailyNotification plugin initialized successfully");
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Failed to initialize DailyNotification plugin:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Get Current ActiveDid
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current activeDid from the database
|
||||||
|
*/
|
||||||
|
private async getCurrentActiveDid(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT activeDid FROM active_identity WHERE id = 1"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.values?.length) {
|
||||||
|
const activeDid = result.values[0][0] as string | null;
|
||||||
|
return activeDid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Error getting current activeDid:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> {
|
||||||
|
// Ensure DailyNotification is initialized
|
||||||
|
if (!this.dailyNotificationInitialized) {
|
||||||
|
await this.initializeDailyNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = await this.getTimeSafariSettings();
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
if (!currentActiveDid || !settings.starredPlanHandleIds?.length) {
|
||||||
|
return { data: [], hitLimit: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use plugin's enhanced fetching with same interface as existing TimeSafari code
|
||||||
|
const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges(
|
||||||
|
currentActiveDid,
|
||||||
|
settings.starredPlanHandleIds,
|
||||||
|
settings.lastAckedStarredPlanChangesJwtId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enhanced logging (optional)
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects loaded successfully:", {
|
||||||
|
count: starredProjectChanges.data.length,
|
||||||
|
hitLimit: starredProjectChanges.hitLimit,
|
||||||
|
planIds: settings.starredPlanHandleIds.length,
|
||||||
|
activeDid: currentActiveDid
|
||||||
|
});
|
||||||
|
|
||||||
|
return starredProjectChanges;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Same error handling as existing TimeSafari code
|
||||||
|
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
|
||||||
|
return { data: [], hitLimit: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Get TimeSafari Settings
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TimeSafari settings using existing database patterns
|
||||||
|
*/
|
||||||
|
private async getTimeSafariSettings(): Promise<TimeSafariSettings> {
|
||||||
|
try {
|
||||||
|
// Get current activeDid
|
||||||
|
const currentActiveDid = await this.getCurrentActiveDid();
|
||||||
|
|
||||||
|
if (!currentActiveDid) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use existing TimeSafari settings retrieval pattern
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT * FROM settings WHERE accountDid = ?",
|
||||||
|
[currentActiveDid]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result?.values?.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map database columns to values (existing TimeSafari pattern)
|
||||||
|
const settings: TimeSafariSettings = {};
|
||||||
|
result.columns.forEach((column, index) => {
|
||||||
|
if (column !== 'id') {
|
||||||
|
settings[column] = result.values[0][index];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set activeDid from current value
|
||||||
|
settings.activeDid = currentActiveDid;
|
||||||
|
|
||||||
|
// Handle JSON field parsing (existing TimeSafari pattern)
|
||||||
|
if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') {
|
||||||
|
try {
|
||||||
|
settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds);
|
||||||
|
} catch {
|
||||||
|
settings.starredPlanHandleIds = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[CapacitorPlatformService] Error getting TimeSafari settings:", error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Get TimeSafari Storage Adapter
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get TimeSafari storage adapter using existing patterns
|
||||||
|
*/
|
||||||
|
private getTimeSafariStorageAdapter(): unknown {
|
||||||
|
// Return existing TimeSafari storage adapter
|
||||||
|
return {
|
||||||
|
// Use existing TimeSafari storage patterns
|
||||||
|
store: async (key: string, value: unknown) => {
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
[key, JSON.stringify(value)]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
retrieve: async (key: string) => {
|
||||||
|
const result = await this.dbQuery(
|
||||||
|
"SELECT data FROM temp WHERE id = ?",
|
||||||
|
[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result?.values?.length) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(result.values[0][0] as string);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHODS: Callback Handlers
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for successful starred projects fetch
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> {
|
||||||
|
// Enhanced logging (optional)
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects success callback:", {
|
||||||
|
count: data.data.length,
|
||||||
|
hitLimit: data.hitLimit,
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store results in TimeSafari temp table for UI access
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
['starred_projects_latest', JSON.stringify(data)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for starred projects fetch errors
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsError(error: Error): Promise<void> {
|
||||||
|
// Same error handling as existing TimeSafari code
|
||||||
|
logger.warn("[CapacitorPlatformService] Failed to load starred project changes:", error);
|
||||||
|
|
||||||
|
// Store error in TimeSafari temp table for UI access
|
||||||
|
await this.dbExec(
|
||||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)",
|
||||||
|
['starred_projects_error', JSON.stringify({
|
||||||
|
error: error.message,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
})]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback handler for starred projects fetch completion
|
||||||
|
*/
|
||||||
|
private async handleStarredProjectsComplete(result: unknown): Promise<void> {
|
||||||
|
// Handle completion
|
||||||
|
logger.log("[CapacitorPlatformService] Starred projects fetch completed:", {
|
||||||
|
result,
|
||||||
|
activeDid: this.currentActiveDid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// NEW METHOD: Get DailyNotification Status
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get DailyNotification plugin status for debugging
|
||||||
|
*/
|
||||||
|
async getDailyNotificationStatus(): Promise<{
|
||||||
|
initialized: boolean;
|
||||||
|
platform: string;
|
||||||
|
capabilities: PlatformCapabilities;
|
||||||
|
currentActiveDid: string | null;
|
||||||
|
}> {
|
||||||
|
return {
|
||||||
|
initialized: this.dailyNotificationInitialized,
|
||||||
|
platform: Capacitor.getPlatform(),
|
||||||
|
capabilities: this.getCapabilities(),
|
||||||
|
currentActiveDid: this.currentActiveDid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// MODIFIED METHOD: Enhanced initializeDatabase
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// EXISTING METHODS (unchanged - showing key ones)
|
||||||
|
// =================================================
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user