Browse Source
- Add example showing how CapacitorPlatformService handles activeDid changes - Show integration with existing TimeSafari PWA activeDid change detection system - Demonstrate plugin reconfiguration when user switches identities - Include change listener system and settings synchronization - Add comprehensive guide explaining activeDid change flow - Show how to extend existing updateActiveDid method for plugin integration This ensures the DailyNotification plugin automatically adapts to activeDid changes while maintaining existing TimeSafari PWA patterns.master
2 changed files with 1226 additions and 0 deletions
@ -0,0 +1,422 @@ |
|||
# TimeSafari PWA - ActiveDid Change Integration Guide |
|||
|
|||
**Author**: Matthew Raymer |
|||
**Version**: 1.0.0 |
|||
**Created**: 2025-10-08 06:24:57 UTC |
|||
|
|||
## Overview |
|||
|
|||
This guide explains how the DailyNotification plugin integrates with the existing TimeSafari PWA activeDid change detection system. The TimeSafari PWA has a sophisticated activeDid change management system that the plugin must integrate with. |
|||
|
|||
## TimeSafari PWA ActiveDid Change System |
|||
|
|||
### How ActiveDid Changes Work |
|||
|
|||
#### 1. Database Storage |
|||
```sql |
|||
-- ActiveDid is stored in active_identity table (single source of truth) |
|||
UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1 |
|||
``` |
|||
|
|||
#### 2. PlatformServiceMixin Change Detection |
|||
```typescript |
|||
// In PlatformServiceMixin.ts |
|||
watch: { |
|||
currentActiveDid: { |
|||
handler(newDid: string | null, oldDid: string | null) { |
|||
if (newDid !== oldDid) { |
|||
logger.debug(`[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`); |
|||
// Clear caches that might be affected by the change |
|||
} |
|||
}, |
|||
immediate: true, |
|||
}, |
|||
} |
|||
``` |
|||
|
|||
#### 3. Update Method |
|||
```typescript |
|||
// In PlatformServiceMixin.ts |
|||
async $updateActiveDid(newDid: string | null): Promise<void> { |
|||
const oldDid = this._currentActiveDid; |
|||
this._currentActiveDid = newDid; |
|||
|
|||
if (newDid !== oldDid) { |
|||
// Write to active_identity table (single source of truth) |
|||
await this.$dbExec( |
|||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1", |
|||
[newDid || ""] |
|||
); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 4. Usage in Components |
|||
```typescript |
|||
// In IdentitySwitcherView.vue |
|||
async switchAccount(did?: string) { |
|||
// Update the active DID in the active_identity table |
|||
await this.$updateActiveDid(did); |
|||
|
|||
// Check if we need to load user-specific settings for the new DID |
|||
if (did) { |
|||
const newSettings = await this.$accountSettings(did); |
|||
// Update UI with new settings |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## DailyNotification Plugin Integration |
|||
|
|||
### How the Plugin Handles ActiveDid Changes |
|||
|
|||
#### 1. Enhanced updateActiveDid Method |
|||
```typescript |
|||
// In CapacitorPlatformService.ts |
|||
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; |
|||
|
|||
// Notify listeners of the change |
|||
this.notifyActiveDidChange(did, oldDid); |
|||
|
|||
// Update DailyNotification plugin if initialized |
|||
if (this.dailyNotificationInitialized) { |
|||
await this.updateDailyNotificationActiveDid(did, oldDid); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 2. Plugin Reconfiguration |
|||
```typescript |
|||
// In CapacitorPlatformService.ts |
|||
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: { |
|||
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' |
|||
}); |
|||
} |
|||
|
|||
} catch (error) { |
|||
logger.error(`[CapacitorPlatformService] Failed to update DailyNotification plugin activeDid:`, error); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### 3. Change Listener System |
|||
```typescript |
|||
// In CapacitorPlatformService.ts |
|||
private activeDidChangeListeners: Array<(newDid: string | null, oldDid: string | null) => void> = []; |
|||
|
|||
addActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { |
|||
this.activeDidChangeListeners.push(listener); |
|||
} |
|||
|
|||
removeActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { |
|||
const index = this.activeDidChangeListeners.indexOf(listener); |
|||
if (index > -1) { |
|||
this.activeDidChangeListeners.splice(index, 1); |
|||
} |
|||
} |
|||
|
|||
private notifyActiveDidChange(newDid: string | null, oldDid: string | null): void { |
|||
this.activeDidChangeListeners.forEach(listener => { |
|||
try { |
|||
listener(newDid, oldDid); |
|||
} catch (error) { |
|||
logger.error('[CapacitorPlatformService] Error in activeDid change listener:', error); |
|||
} |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
## Integration Flow |
|||
|
|||
### 1. User Switches Identity |
|||
``` |
|||
User clicks "Switch Account" → IdentitySwitcherView.switchAccount() → |
|||
$updateActiveDid() → CapacitorPlatformService.updateActiveDid() → |
|||
DailyNotification plugin reconfiguration |
|||
``` |
|||
|
|||
### 2. Plugin Reconfiguration Process |
|||
``` |
|||
1. Update database (existing TimeSafari pattern) |
|||
2. Update local tracking |
|||
3. Notify change listeners |
|||
4. Reconfigure DailyNotification plugin |
|||
5. Update TimeSafari Integration Service |
|||
6. Log the change |
|||
``` |
|||
|
|||
### 3. Settings Update Process |
|||
``` |
|||
1. Get new settings for new activeDid |
|||
2. Update plugin configuration with new settings |
|||
3. Update integration service with new activeDid |
|||
4. Clear any cached data for old activeDid |
|||
5. Initialize new activeDid-specific data |
|||
``` |
|||
|
|||
## Required Changes to Existing Code |
|||
|
|||
### File: `src/services/platforms/CapacitorPlatformService.ts` |
|||
|
|||
#### 1. Add New 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; |
|||
private activeDidChangeListeners: Array<(newDid: string | null, oldDid: string | null) => void> = []; |
|||
} |
|||
``` |
|||
|
|||
#### 2. 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; |
|||
|
|||
// Notify listeners of the change |
|||
this.notifyActiveDidChange(did, oldDid); |
|||
|
|||
// Update DailyNotification plugin if initialized |
|||
if (this.dailyNotificationInitialized) { |
|||
await this.updateDailyNotificationActiveDid(did, oldDid); |
|||
} |
|||
|
|||
logger.debug(`[CapacitorPlatformService] ActiveDid updated from ${oldDid} to ${did}`); |
|||
} |
|||
``` |
|||
|
|||
#### 3. Add New Methods |
|||
```typescript |
|||
// Add these new methods to the class |
|||
private async updateDailyNotificationActiveDid(newDid: string, oldDid: string | null): Promise<void> { /* ... */ } |
|||
private async getCurrentActiveDid(): Promise<string | null> { /* ... */ } |
|||
addActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { /* ... */ } |
|||
removeActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { /* ... */ } |
|||
private notifyActiveDidChange(newDid: string | null, oldDid: string | null): void { /* ... */ } |
|||
``` |
|||
|
|||
## PlatformServiceMixin Integration |
|||
|
|||
### File: `src/utils/PlatformServiceMixin.ts` |
|||
|
|||
#### 1. Add DailyNotification Methods |
|||
```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(); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Vue Component Integration |
|||
|
|||
### File: `src/views/HomeView.vue` (or similar) |
|||
|
|||
#### 1. Add DailyNotification Initialization |
|||
```typescript |
|||
export default defineComponent({ |
|||
name: 'HomeView', |
|||
|
|||
mixins: [PlatformServiceMixin], |
|||
|
|||
async mounted() { |
|||
// Initialize DailyNotification (only on Capacitor) |
|||
if (this.isCapacitor) { |
|||
await this.$initializeDailyNotification(); |
|||
} |
|||
}, |
|||
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
## Key Benefits |
|||
|
|||
### 1. Seamless ActiveDid Change Handling |
|||
- **Automatic Reconfiguration**: Plugin automatically reconfigures when activeDid changes |
|||
- **Settings Synchronization**: New activeDid settings are automatically loaded and applied |
|||
- **Error Handling**: Robust error handling for activeDid change failures |
|||
- **Logging**: Comprehensive logging for debugging activeDid changes |
|||
|
|||
### 2. Integration with Existing Patterns |
|||
- **Uses Existing Methods**: Leverages existing `updateActiveDid` method |
|||
- **Database Consistency**: Uses existing `active_identity` table pattern |
|||
- **Change Detection**: Integrates with existing change detection system |
|||
- **Settings Management**: Uses existing settings retrieval patterns |
|||
|
|||
### 3. Enhanced Functionality |
|||
- **Background Fetching**: Continues to work with new activeDid |
|||
- **Notification Scheduling**: Updates notifications for new activeDid |
|||
- **Storage Management**: Manages storage for multiple activeDids |
|||
- **Observability**: Tracks activeDid changes in logs and metrics |
|||
|
|||
## Testing Strategy |
|||
|
|||
### 1. ActiveDid Change Testing |
|||
```typescript |
|||
// Test activeDid change handling |
|||
const testActiveDidChange = async () => { |
|||
// Switch to new activeDid |
|||
await platformService.updateActiveDid('new-did-123'); |
|||
|
|||
// Verify plugin is reconfigured |
|||
const status = await platformService.getDailyNotificationStatus(); |
|||
assert.equal(status.currentActiveDid, 'new-did-123'); |
|||
|
|||
// Test starred projects fetch with new activeDid |
|||
const result = await platformService.loadNewStarredProjectChanges(); |
|||
assert.ok(result.data !== undefined); |
|||
}; |
|||
``` |
|||
|
|||
### 2. Settings Synchronization Testing |
|||
```typescript |
|||
// Test settings synchronization |
|||
const testSettingsSynchronization = async () => { |
|||
// Switch activeDid |
|||
await platformService.updateActiveDid('new-did-123'); |
|||
|
|||
// Verify settings are loaded for new activeDid |
|||
const settings = await platformService.getTimeSafariSettings(); |
|||
assert.equal(settings.activeDid, 'new-did-123'); |
|||
|
|||
// Verify plugin configuration is updated |
|||
const config = await DailyNotification.getConfiguration(); |
|||
assert.equal(config.timesafariConfig.activeDid, 'new-did-123'); |
|||
}; |
|||
``` |
|||
|
|||
### 3. Error Handling Testing |
|||
```typescript |
|||
// Test error handling during activeDid changes |
|||
const testErrorHandling = async () => { |
|||
try { |
|||
// Switch to invalid activeDid |
|||
await platformService.updateActiveDid('invalid-did'); |
|||
|
|||
// Verify error is handled gracefully |
|||
const status = await platformService.getDailyNotificationStatus(); |
|||
assert.ok(status.initialized); // Plugin should remain initialized |
|||
|
|||
} catch (error) { |
|||
// Verify error is logged |
|||
assert.ok(error.message.includes('activeDid')); |
|||
} |
|||
}; |
|||
``` |
|||
|
|||
## Common Issues and Solutions |
|||
|
|||
### Issue 1: Plugin Not Updating on ActiveDid Change |
|||
**Solution**: Ensure `updateDailyNotificationActiveDid` is called in the `updateActiveDid` method |
|||
|
|||
### Issue 2: Settings Not Loading for New ActiveDid |
|||
**Solution**: Verify `getTimeSafariSettings` uses the current activeDid from database |
|||
|
|||
### Issue 3: Cached Data from Old ActiveDid |
|||
**Solution**: Clear plugin caches when activeDid changes |
|||
|
|||
### Issue 4: Background Fetching with Wrong ActiveDid |
|||
**Solution**: Ensure plugin reconfiguration includes new activeDid in all requests |
|||
|
|||
## Conclusion |
|||
|
|||
The DailyNotification plugin integrates seamlessly with the existing TimeSafari PWA activeDid change system by: |
|||
|
|||
- **Extending the existing `updateActiveDid` method** to handle plugin reconfiguration |
|||
- **Using the existing change detection patterns** from PlatformServiceMixin |
|||
- **Maintaining database consistency** with the existing `active_identity` table |
|||
- **Providing robust error handling** for activeDid change failures |
|||
- **Supporting multiple activeDids** with proper isolation and cleanup |
|||
|
|||
The integration ensures that the plugin automatically adapts to activeDid changes while maintaining the same interface and behavior as the existing TimeSafari PWA code. |
|||
|
|||
--- |
|||
|
|||
**Key Takeaway**: The CapacitorPlatformService **does know** when activeDid changes through the existing `updateActiveDid` method, and the DailyNotification plugin integrates with this system to automatically reconfigure when the user switches identities. |
@ -0,0 +1,804 @@ |
|||
/** |
|||
* TimeSafari PWA - CapacitorPlatformService ActiveDid Integration Example |
|||
* |
|||
* This example shows how to extend the existing CapacitorPlatformService |
|||
* to handle activeDid changes and integrate with the DailyNotification plugin. |
|||
* |
|||
* This represents the ACTUAL CHANGES needed to handle activeDid changes |
|||
* in the existing TimeSafari PWA CapacitorPlatformService. |
|||
* |
|||
* @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 and ActiveDid Integration |
|||
* |
|||
* This shows the ACTUAL CHANGES needed to the existing TimeSafari PWA |
|||
* CapacitorPlatformService class to handle activeDid changes and integrate |
|||
* with the DailyNotification plugin. |
|||
*/ |
|||
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; |
|||
private activeDidChangeListeners: Array<(newDid: string | null, oldDid: string | null) => void> = []; |
|||
|
|||
// =================================================
|
|||
// 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; |
|||
|
|||
// Notify listeners of the change
|
|||
this.notifyActiveDidChange(did, oldDid); |
|||
|
|||
// 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 |
|||
* |
|||
* This method reconfigures the DailyNotification plugin with the new |
|||
* activeDid and updates all related settings. |
|||
*/ |
|||
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: ActiveDid Change Listener Management
|
|||
// =================================================
|
|||
|
|||
/** |
|||
* Add listener for activeDid changes |
|||
* |
|||
* This method allows components to register listeners that will be |
|||
* notified when the activeDid changes. |
|||
*/ |
|||
addActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { |
|||
this.activeDidChangeListeners.push(listener); |
|||
} |
|||
|
|||
/** |
|||
* Remove listener for activeDid changes |
|||
*/ |
|||
removeActiveDidChangeListener(listener: (newDid: string | null, oldDid: string | null) => void): void { |
|||
const index = this.activeDidChangeListeners.indexOf(listener); |
|||
if (index > -1) { |
|||
this.activeDidChangeListeners.splice(index, 1); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Notify all listeners of activeDid change |
|||
*/ |
|||
private notifyActiveDidChange(newDid: string | null, oldDid: string | null): void { |
|||
this.activeDidChangeListeners.forEach(listener => { |
|||
try { |
|||
listener(newDid, oldDid); |
|||
} catch (error) { |
|||
logger.error('[CapacitorPlatformService] Error in activeDid change listener:', error); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// =================================================
|
|||
// 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(); |
|||
|
|||
// 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 |
|||
* |
|||
* This method retrieves the current activeDid from the active_identity table |
|||
* using the existing TimeSafari pattern. |
|||
*/ |
|||
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 |
|||
* |
|||
* 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 { |
|||
// 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 |
|||
* |
|||
* 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 |
|||
*/ |
|||
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)
|
|||
} |
Loading…
Reference in new issue