Browse Source
- Add comprehensive example showing DailyNotification plugin integration with existing TimeSafari PWA architecture - Show how to extend CapacitorPlatformService and PlatformServiceMixin patterns - Provide Vue.js component integration with existing TimeSafari patterns - Include settings management, database operations, and platform detection - Add migration strategy and testing approach for gradual adoption - Show how to maintain existing interfaces while adding plugin features This demonstrates how the plugin integrates with the actual TimeSafari PWA CapacitorPlatformService and PlatformServiceMixin architecture patterns.master
2 changed files with 1084 additions and 0 deletions
@ -0,0 +1,490 @@ |
|||||
|
# TimeSafari PWA - DailyNotification Plugin Integration Guide |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Version**: 1.0.0 |
||||
|
**Created**: 2025-10-08 06:24:57 UTC |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
This guide shows how to integrate the DailyNotification plugin with the existing TimeSafari PWA architecture, specifically the `CapacitorPlatformService` and `PlatformServiceMixin` patterns. The integration maintains the same interfaces and patterns while adding enhanced functionality. |
||||
|
|
||||
|
## TimeSafari PWA Architecture Analysis |
||||
|
|
||||
|
### Existing Architecture Components |
||||
|
|
||||
|
#### 1. CapacitorPlatformService |
||||
|
- **Location**: `src/services/platforms/CapacitorPlatformService.ts` |
||||
|
- **Purpose**: Provides native mobile functionality through Capacitor plugins |
||||
|
- **Features**: File system, camera, SQLite database operations, platform detection |
||||
|
- **Key Methods**: `dbQuery`, `dbExec`, `takePicture`, `writeFile`, `getCapabilities` |
||||
|
|
||||
|
#### 2. PlatformServiceFactory |
||||
|
- **Location**: `src/services/PlatformServiceFactory.ts` |
||||
|
- **Purpose**: Factory pattern for creating platform-specific service implementations |
||||
|
- **Pattern**: Singleton pattern with environment-based platform detection |
||||
|
- **Platforms**: `capacitor`, `electron`, `web` (default) |
||||
|
|
||||
|
#### 3. PlatformServiceMixin |
||||
|
- **Location**: `src/utils/PlatformServiceMixin.ts` |
||||
|
- **Purpose**: Vue.js mixin providing cached platform service access and utility methods |
||||
|
- **Features**: Database operations, settings management, contact management, logging |
||||
|
- **Key Methods**: `$db`, `$exec`, `$settings`, `$contacts`, `$saveSettings` |
||||
|
|
||||
|
### Existing TimeSafari Patterns |
||||
|
|
||||
|
#### 1. Settings Management |
||||
|
```typescript |
||||
|
// Existing TimeSafari settings pattern |
||||
|
const settings = await this.$settings(); |
||||
|
const activeDid = settings.activeDid; |
||||
|
const starredPlanHandleIds = settings.starredPlanHandleIds; |
||||
|
const apiServer = settings.apiServer; |
||||
|
``` |
||||
|
|
||||
|
#### 2. Database Operations |
||||
|
```typescript |
||||
|
// Existing TimeSafari database pattern |
||||
|
const result = await this.$dbQuery("SELECT * FROM settings WHERE id = 1"); |
||||
|
const contacts = await this.$contacts(); |
||||
|
await this.$saveSettings({ starredPlanHandleIds: newIds }); |
||||
|
``` |
||||
|
|
||||
|
#### 3. Platform Detection |
||||
|
```typescript |
||||
|
// Existing TimeSafari platform detection |
||||
|
if (this.isCapacitor) { |
||||
|
// Native mobile functionality |
||||
|
} else { |
||||
|
// Web browser functionality |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## DailyNotification Plugin Integration |
||||
|
|
||||
|
### Integration Strategy |
||||
|
|
||||
|
The DailyNotification plugin integrates with the existing TimeSafari PWA architecture by: |
||||
|
|
||||
|
1. **Extending CapacitorPlatformService**: Adding DailyNotification functionality to the existing platform service |
||||
|
2. **Using PlatformServiceMixin**: Leveraging existing database and settings patterns |
||||
|
3. **Maintaining Interfaces**: Keeping the same method signatures and behavior |
||||
|
4. **Platform Detection**: Only initializing on Capacitor platforms |
||||
|
|
||||
|
### Enhanced CapacitorPlatformService |
||||
|
|
||||
|
```typescript |
||||
|
export class EnhancedCapacitorPlatformService { |
||||
|
private platformService = PlatformServiceFactory.getInstance(); |
||||
|
private dailyNotificationService: DailyNotification | null = null; |
||||
|
private integrationService: TimeSafariIntegrationService | null = null; |
||||
|
|
||||
|
async initializeDailyNotification(): Promise<void> { |
||||
|
// Get existing TimeSafari settings |
||||
|
const settings = await this.getTimeSafariSettings(); |
||||
|
|
||||
|
// Configure plugin with TimeSafari data |
||||
|
await DailyNotification.configure({ |
||||
|
timesafariConfig: { |
||||
|
activeDid: settings.activeDid, |
||||
|
endpoints: { |
||||
|
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween` |
||||
|
}, |
||||
|
starredProjectsConfig: { |
||||
|
enabled: true, |
||||
|
starredPlanHandleIds: settings.starredPlanHandleIds, |
||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId, |
||||
|
fetchInterval: '0 8 * * *' |
||||
|
} |
||||
|
}, |
||||
|
networkConfig: { |
||||
|
baseURL: settings.apiServer, |
||||
|
timeout: 30000 |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> { |
||||
|
// Enhanced version of existing TimeSafari method |
||||
|
const settings = await this.getTimeSafariSettings(); |
||||
|
|
||||
|
if (!settings.activeDid || !settings.starredPlanHandleIds?.length) { |
||||
|
return { data: [], hitLimit: false }; |
||||
|
} |
||||
|
|
||||
|
// Use plugin's enhanced fetching |
||||
|
return await this.integrationService!.getStarredProjectsWithChanges( |
||||
|
settings.activeDid, |
||||
|
settings.starredPlanHandleIds, |
||||
|
settings.lastAckedStarredPlanChangesJwtId |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Vue.js Component Integration |
||||
|
|
||||
|
```typescript |
||||
|
export const TimeSafariDailyNotificationMixin = { |
||||
|
data() { |
||||
|
return { |
||||
|
// Existing TimeSafari data |
||||
|
activeDid: '', |
||||
|
starredPlanHandleIds: [] as string[], |
||||
|
lastAckedStarredPlanChangesJwtId: '', |
||||
|
numNewStarredProjectChanges: 0, |
||||
|
newStarredProjectChangesHitLimit: false, |
||||
|
|
||||
|
// Plugin integration |
||||
|
enhancedPlatformService: null as EnhancedCapacitorPlatformService | null |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
async mounted() { |
||||
|
// Initialize DailyNotification when component mounts (only on Capacitor) |
||||
|
if (this.isCapacitor) { |
||||
|
await this.initializeDailyNotification(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
async initializeDailyNotification(): Promise<void> { |
||||
|
this.enhancedPlatformService = new EnhancedCapacitorPlatformService(); |
||||
|
await this.enhancedPlatformService.initializeDailyNotification(); |
||||
|
}, |
||||
|
|
||||
|
async loadNewStarredProjectChanges(): Promise<void> { |
||||
|
if (this.isCapacitor && this.enhancedPlatformService) { |
||||
|
// Use plugin-enhanced method on Capacitor |
||||
|
const result = await this.enhancedPlatformService.loadNewStarredProjectChanges(); |
||||
|
this.numNewStarredProjectChanges = result.data.length; |
||||
|
this.newStarredProjectChangesHitLimit = result.hitLimit; |
||||
|
} else { |
||||
|
// Use existing web method in browser |
||||
|
await this.loadNewStarredProjectChangesWeb(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
## Integration Steps |
||||
|
|
||||
|
### Step 1: Install the Plugin |
||||
|
|
||||
|
```bash |
||||
|
# In TimeSafari PWA project |
||||
|
npm install ssh://git@173.199.124.46:222/trent_larson/daily-notification-plugin.git |
||||
|
``` |
||||
|
|
||||
|
### Step 2: Create Enhanced Platform Service |
||||
|
|
||||
|
Create `src/services/platforms/EnhancedCapacitorPlatformService.ts`: |
||||
|
|
||||
|
```typescript |
||||
|
import { DailyNotification } from '@timesafari/daily-notification-plugin'; |
||||
|
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; |
||||
|
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory'; |
||||
|
|
||||
|
export class EnhancedCapacitorPlatformService { |
||||
|
// Implementation from the example above |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Step 3: Extend PlatformServiceMixin |
||||
|
|
||||
|
Add to `src/utils/PlatformServiceMixin.ts`: |
||||
|
|
||||
|
```typescript |
||||
|
export const PlatformServiceMixin = { |
||||
|
// ... existing mixin code ... |
||||
|
|
||||
|
methods: { |
||||
|
// ... existing methods ... |
||||
|
|
||||
|
/** |
||||
|
* Initialize DailyNotification plugin (Capacitor only) |
||||
|
*/ |
||||
|
async $initializeDailyNotification(): Promise<void> { |
||||
|
if (this.isCapacitor) { |
||||
|
const enhancedService = new EnhancedCapacitorPlatformService(); |
||||
|
await enhancedService.initializeDailyNotification(); |
||||
|
this._enhancedPlatformService = enhancedService; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Enhanced loadNewStarredProjectChanges method |
||||
|
*/ |
||||
|
async $loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> { |
||||
|
if (this.isCapacitor && this._enhancedPlatformService) { |
||||
|
return await this._enhancedPlatformService.loadNewStarredProjectChanges(); |
||||
|
} else { |
||||
|
// Fall back to existing web method |
||||
|
return await this.$loadNewStarredProjectChangesWeb(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
### Step 4: Update Vue Components |
||||
|
|
||||
|
In your existing TimeSafari Vue components: |
||||
|
|
||||
|
```typescript |
||||
|
export default defineComponent({ |
||||
|
name: 'TimeSafariHomeView', |
||||
|
|
||||
|
mixins: [PlatformServiceMixin], |
||||
|
|
||||
|
async mounted() { |
||||
|
// Initialize DailyNotification (only on Capacitor) |
||||
|
if (this.isCapacitor) { |
||||
|
await this.$initializeDailyNotification(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
async loadNewStarredProjectChanges() { |
||||
|
// Use enhanced method |
||||
|
const result = await this.$loadNewStarredProjectChanges(); |
||||
|
this.numNewStarredProjectChanges = result.data.length; |
||||
|
this.newStarredProjectChangesHitLimit = result.hitLimit; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## Configuration Mapping |
||||
|
|
||||
|
### TimeSafari Settings → Plugin Configuration |
||||
|
|
||||
|
| TimeSafari Setting | Plugin Configuration | Purpose | |
||||
|
|-------------------|---------------------|---------| |
||||
|
| `settings.activeDid` | `timesafariConfig.activeDid` | User authentication | |
||||
|
| `settings.apiServer` | `networkConfig.baseURL` | API endpoint | |
||||
|
| `settings.starredPlanHandleIds` | `starredProjectsConfig.starredPlanHandleIds` | Project IDs to fetch | |
||||
|
| `settings.lastAckedStarredPlanChangesJwtId` | `starredProjectsConfig.lastAckedJwtId` | Pagination token | |
||||
|
|
||||
|
### Existing Methods → Enhanced Methods |
||||
|
|
||||
|
| Existing Method | Enhanced Method | Enhancement | |
||||
|
|----------------|----------------|-------------| |
||||
|
| `loadNewStarredProjectChanges()` | `$loadNewStarredProjectChanges()` | Background fetching, structured logging | |
||||
|
| `getStarredProjectsWithChanges()` | `integrationService.getStarredProjectsWithChanges()` | Retry logic, circuit breaker | |
||||
|
| Settings storage | Plugin storage adapter | Tiered storage, TTL management | |
||||
|
|
||||
|
## Platform-Specific Behavior |
||||
|
|
||||
|
### Capacitor (Android/iOS) |
||||
|
- **Plugin Enabled**: Full DailyNotification functionality |
||||
|
- **Background Fetching**: WorkManager (Android), BGTaskScheduler (iOS) |
||||
|
- **Notifications**: Native notification channels and categories |
||||
|
- **Storage**: SQLite with plugin's tiered storage system |
||||
|
|
||||
|
### Web Browser |
||||
|
- **Plugin Disabled**: Uses existing TimeSafari web code |
||||
|
- **No Background Fetching**: Limited to browser capabilities |
||||
|
- **No Native Notifications**: Uses web notifications API |
||||
|
- **Storage**: Existing TimeSafari storage patterns |
||||
|
|
||||
|
### Electron |
||||
|
- **Plugin Enabled**: Desktop-specific functionality |
||||
|
- **Background Tasks**: Electron main process |
||||
|
- **Desktop Notifications**: Native desktop notifications |
||||
|
- **Storage**: SQLite with plugin's tiered storage system |
||||
|
|
||||
|
## Benefits of Integration |
||||
|
|
||||
|
### 1. Same Interface, Enhanced Functionality |
||||
|
- Existing `loadNewStarredProjectChanges()` method works exactly the same |
||||
|
- Enhanced with background fetching, structured logging, and error handling |
||||
|
- No changes required to existing UI code |
||||
|
|
||||
|
### 2. Leverages Existing TimeSafari Patterns |
||||
|
- Uses existing `PlatformServiceMixin` for database operations |
||||
|
- Integrates with existing settings management |
||||
|
- Maintains existing platform detection patterns |
||||
|
|
||||
|
### 3. Platform-Optimized Performance |
||||
|
- **Capacitor**: Native mobile optimizations with WorkManager/BGTaskScheduler |
||||
|
- **Web**: Falls back to existing web code seamlessly |
||||
|
- **Electron**: Desktop-specific optimizations |
||||
|
|
||||
|
### 4. Enhanced Observability |
||||
|
- Structured logging with event IDs |
||||
|
- Performance metrics and monitoring |
||||
|
- Error tracking and analysis |
||||
|
- Health checks and status monitoring |
||||
|
|
||||
|
## Migration Strategy |
||||
|
|
||||
|
### Phase 1: Parallel Implementation |
||||
|
1. **Keep existing code unchanged** |
||||
|
2. **Add enhanced platform service alongside existing code** |
||||
|
3. **Test both implementations in parallel** |
||||
|
4. **Compare results to ensure compatibility** |
||||
|
|
||||
|
### Phase 2: Gradual Migration |
||||
|
1. **Replace individual methods one by one** |
||||
|
2. **Use enhanced error handling and logging** |
||||
|
3. **Maintain existing UI and user experience** |
||||
|
4. **Add plugin-specific features gradually** |
||||
|
|
||||
|
### Phase 3: Full Integration |
||||
|
1. **Replace all TimeSafari request patterns with plugin** |
||||
|
2. **Remove duplicate code** |
||||
|
3. **Leverage plugin's advanced features** |
||||
|
4. **Optimize performance with plugin's caching and batching** |
||||
|
|
||||
|
## Testing Strategy |
||||
|
|
||||
|
### 1. Platform-Specific Testing |
||||
|
```typescript |
||||
|
// Test on different platforms |
||||
|
const testPlatforms = async () => { |
||||
|
if (Capacitor.getPlatform() === 'android') { |
||||
|
// Test Android-specific functionality |
||||
|
} else if (Capacitor.getPlatform() === 'ios') { |
||||
|
// Test iOS-specific functionality |
||||
|
} else { |
||||
|
// Test web fallback |
||||
|
} |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
### 2. Parallel Testing |
||||
|
```typescript |
||||
|
// Test both implementations |
||||
|
const testBothImplementations = async () => { |
||||
|
// Existing TimeSafari implementation |
||||
|
const existingResult = await this.loadNewStarredProjectChangesOriginal(); |
||||
|
|
||||
|
// Plugin-enhanced implementation |
||||
|
const pluginResult = await this.$loadNewStarredProjectChanges(); |
||||
|
|
||||
|
// Compare results |
||||
|
assert.deepEqual(existingResult, pluginResult); |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
### 3. Performance Testing |
||||
|
```typescript |
||||
|
// Compare performance |
||||
|
const performanceTest = async () => { |
||||
|
const start = Date.now(); |
||||
|
await this.$loadNewStarredProjectChanges(); |
||||
|
const pluginTime = Date.now() - start; |
||||
|
|
||||
|
console.log('Plugin performance:', pluginTime); |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
## Common Integration Patterns |
||||
|
|
||||
|
### 1. Settings Integration |
||||
|
```typescript |
||||
|
// Get TimeSafari settings |
||||
|
const settings = await this.$settings(); |
||||
|
|
||||
|
// Configure plugin with settings |
||||
|
await DailyNotification.configure({ |
||||
|
timesafariConfig: { |
||||
|
activeDid: settings.activeDid, |
||||
|
starredPlanHandleIds: settings.starredPlanHandleIds |
||||
|
} |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### 2. Database Integration |
||||
|
```typescript |
||||
|
// Store plugin results in TimeSafari database |
||||
|
await this.$dbExec( |
||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)", |
||||
|
['starred_projects_latest', JSON.stringify(result)] |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
### 3. Error Handling Integration |
||||
|
```typescript |
||||
|
// Use existing TimeSafari error handling patterns |
||||
|
try { |
||||
|
const result = await this.$loadNewStarredProjectChanges(); |
||||
|
// Handle success |
||||
|
} catch (error) { |
||||
|
// Use existing TimeSafari error logging |
||||
|
await this.$logError(`Failed to load starred projects: ${error.message}`); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Troubleshooting |
||||
|
|
||||
|
### Common Issues |
||||
|
|
||||
|
#### 1. Plugin Not Initializing |
||||
|
```typescript |
||||
|
// Check platform detection |
||||
|
if (!this.isCapacitor) { |
||||
|
console.log('Plugin only works on Capacitor platforms'); |
||||
|
return; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 2. Settings Not Found |
||||
|
```typescript |
||||
|
// Ensure settings are loaded |
||||
|
const settings = await this.$settings(); |
||||
|
if (!settings.activeDid) { |
||||
|
console.log('No active DID found'); |
||||
|
return; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 3. Database Connection Issues |
||||
|
```typescript |
||||
|
// Check database connection |
||||
|
try { |
||||
|
await this.$dbQuery("SELECT 1"); |
||||
|
} catch (error) { |
||||
|
console.error('Database connection failed:', error); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Debug Methods |
||||
|
|
||||
|
#### 1. Plugin Status |
||||
|
```typescript |
||||
|
const status = await this.getDailyNotificationStatus(); |
||||
|
console.log('Plugin status:', status); |
||||
|
``` |
||||
|
|
||||
|
#### 2. Settings Debug |
||||
|
```typescript |
||||
|
await this.$debugMergedSettings(this.activeDid); |
||||
|
``` |
||||
|
|
||||
|
#### 3. Performance Debug |
||||
|
```typescript |
||||
|
const metrics = await DailyNotification.getMetrics(); |
||||
|
console.log('Plugin metrics:', metrics); |
||||
|
``` |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
The DailyNotification plugin integrates seamlessly with the existing TimeSafari PWA architecture by: |
||||
|
|
||||
|
- **Extending CapacitorPlatformService** with enhanced functionality |
||||
|
- **Using PlatformServiceMixin** for database and settings operations |
||||
|
- **Maintaining existing interfaces** and method signatures |
||||
|
- **Providing platform-specific optimizations** for Capacitor, Web, and Electron |
||||
|
- **Enhancing observability** with structured logging and metrics |
||||
|
|
||||
|
The integration maintains the same developer experience while adding powerful new features like background fetching, native notifications, and comprehensive monitoring. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Next Steps:** |
||||
|
1. Review the integration example and understand the architecture |
||||
|
2. Test the enhanced platform service with existing TimeSafari code |
||||
|
3. Gradually migrate individual methods to use the plugin |
||||
|
4. Leverage the plugin's advanced features for enhanced user experience |
@ -0,0 +1,594 @@ |
|||||
|
/** |
||||
|
* TimeSafari PWA - DailyNotification Plugin Integration Example |
||||
|
* |
||||
|
* This example shows how to integrate the DailyNotification plugin with the existing |
||||
|
* TimeSafari PWA architecture, specifically the CapacitorPlatformService and |
||||
|
* PlatformServiceMixin patterns. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
* @version 1.0.0 |
||||
|
*/ |
||||
|
|
||||
|
import { DailyNotification } from '@timesafari/daily-notification-plugin'; |
||||
|
import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; |
||||
|
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory'; |
||||
|
import { Capacitor } from '@capacitor/core'; |
||||
|
|
||||
|
// TimeSafari PWA existing interfaces (from the actual project)
|
||||
|
interface PlanSummaryAndPreviousClaim { |
||||
|
id: string; |
||||
|
title: string; |
||||
|
description: string; |
||||
|
lastUpdated: string; |
||||
|
previousClaim?: unknown; |
||||
|
} |
||||
|
|
||||
|
interface StarredProjectsResponse { |
||||
|
data: Array<PlanSummaryAndPreviousClaim>; |
||||
|
hitLimit: boolean; |
||||
|
} |
||||
|
|
||||
|
interface Settings { |
||||
|
accountDid?: string; |
||||
|
activeDid?: string; |
||||
|
apiServer?: string; |
||||
|
starredPlanHandleIds?: string[]; |
||||
|
lastAckedStarredPlanChangesJwtId?: string; |
||||
|
[key: string]: unknown; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Enhanced CapacitorPlatformService with DailyNotification integration |
||||
|
* |
||||
|
* This extends the existing TimeSafari PWA CapacitorPlatformService to include |
||||
|
* DailyNotification plugin functionality while maintaining the same interface. |
||||
|
*/ |
||||
|
export class EnhancedCapacitorPlatformService { |
||||
|
private platformService = PlatformServiceFactory.getInstance(); |
||||
|
private dailyNotificationService: DailyNotification | null = null; |
||||
|
private integrationService: TimeSafariIntegrationService | null = null; |
||||
|
private initialized = false; |
||||
|
|
||||
|
/** |
||||
|
* Initialize DailyNotification plugin with TimeSafari PWA configuration |
||||
|
*/ |
||||
|
async initializeDailyNotification(): Promise<void> { |
||||
|
if (this.initialized) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// Get current TimeSafari settings
|
||||
|
const settings = await this.getTimeSafariSettings(); |
||||
|
|
||||
|
// Configure DailyNotification plugin with TimeSafari data
|
||||
|
await DailyNotification.configure({ |
||||
|
// Basic plugin configuration
|
||||
|
storage: 'tiered', |
||||
|
ttlSeconds: 1800, |
||||
|
enableETagSupport: true, |
||||
|
enableErrorHandling: true, |
||||
|
enablePerformanceOptimization: true, |
||||
|
|
||||
|
// TimeSafari-specific configuration
|
||||
|
timesafariConfig: { |
||||
|
// Use existing TimeSafari activeDid
|
||||
|
activeDid: settings.activeDid || '', |
||||
|
|
||||
|
// Use existing TimeSafari API endpoints
|
||||
|
endpoints: { |
||||
|
offersToPerson: `${settings.apiServer}/api/v2/offers/person`, |
||||
|
offersToPlans: `${settings.apiServer}/api/v2/offers/plans`, |
||||
|
projectsLastUpdated: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween` |
||||
|
}, |
||||
|
|
||||
|
// Configure starred projects fetching (matches existing TimeSafari pattern)
|
||||
|
starredProjectsConfig: { |
||||
|
enabled: true, |
||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [], |
||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '', |
||||
|
fetchInterval: '0 8 * * *', // Daily at 8 AM
|
||||
|
maxResults: 50, |
||||
|
hitLimitHandling: 'warn' // Same as existing TimeSafari error handling
|
||||
|
}, |
||||
|
|
||||
|
// Sync configuration (optimized for TimeSafari use case)
|
||||
|
syncConfig: { |
||||
|
enableParallel: true, |
||||
|
maxConcurrent: 3, |
||||
|
batchSize: 10, |
||||
|
timeout: 30000, |
||||
|
retryAttempts: 3 |
||||
|
}, |
||||
|
|
||||
|
// Error policy (matches existing TimeSafari error handling)
|
||||
|
errorPolicy: { |
||||
|
maxRetries: 3, |
||||
|
backoffMultiplier: 2, |
||||
|
activeDidChangeRetries: 5, |
||||
|
starredProjectsRetries: 3 |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Network configuration using existing TimeSafari patterns
|
||||
|
networkConfig: { |
||||
|
// Use existing TimeSafari HTTP client (if available)
|
||||
|
baseURL: settings.apiServer || 'https://endorser.ch', |
||||
|
timeout: 30000, |
||||
|
retryAttempts: 3, |
||||
|
retryDelay: 1000, |
||||
|
maxConcurrent: 5, |
||||
|
|
||||
|
// Headers matching TimeSafari pattern
|
||||
|
defaultHeaders: { |
||||
|
'Content-Type': 'application/json', |
||||
|
'Accept': 'application/json', |
||||
|
'User-Agent': 'TimeSafari-PWA/1.0.0' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Content fetch configuration (replaces existing loadNewStarredProjectChanges)
|
||||
|
contentFetch: { |
||||
|
enabled: true, |
||||
|
schedule: '0 8 * * *', // Daily at 8 AM
|
||||
|
|
||||
|
// Use existing TimeSafari request pattern
|
||||
|
requestConfig: { |
||||
|
method: 'POST', |
||||
|
url: `${settings.apiServer}/api/v2/report/plansLastUpdatedBetween`, |
||||
|
headers: { |
||||
|
'Authorization': 'Bearer ${jwt}', |
||||
|
'X-User-DID': '${activeDid}', |
||||
|
'Content-Type': 'application/json' |
||||
|
}, |
||||
|
body: { |
||||
|
planIds: '${starredPlanHandleIds}', |
||||
|
afterId: '${lastAckedJwtId}' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Callbacks that match TimeSafari error handling
|
||||
|
callbacks: { |
||||
|
onSuccess: this.handleStarredProjectsSuccess.bind(this), |
||||
|
onError: this.handleStarredProjectsError.bind(this), |
||||
|
onComplete: this.handleStarredProjectsComplete.bind(this) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Authentication configuration
|
||||
|
authentication: { |
||||
|
jwt: { |
||||
|
secret: process.env.JWT_SECRET || 'timesafari-jwt-secret', |
||||
|
algorithm: 'HS256', |
||||
|
expirationMinutes: 60, |
||||
|
refreshThresholdMinutes: 10 |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// Observability configuration
|
||||
|
logging: { |
||||
|
level: 'INFO', |
||||
|
enableRequestLogging: true, |
||||
|
enableResponseLogging: true, |
||||
|
enableErrorLogging: true, |
||||
|
redactSensitiveData: true |
||||
|
}, |
||||
|
|
||||
|
// Security configuration
|
||||
|
security: { |
||||
|
certificatePinning: { |
||||
|
enabled: true, |
||||
|
pins: [ |
||||
|
{ |
||||
|
hostname: 'endorser.ch', |
||||
|
pins: ['sha256/YOUR_PIN_HERE'] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Initialize TimeSafari Integration Service
|
||||
|
this.integrationService = TimeSafariIntegrationService.getInstance(); |
||||
|
await this.integrationService.initialize({ |
||||
|
activeDid: settings.activeDid || '', |
||||
|
storageAdapter: this.getTimeSafariStorageAdapter(), |
||||
|
endorserApiBaseUrl: settings.apiServer || 'https://endorser.ch', |
||||
|
|
||||
|
// Use existing TimeSafari request patterns
|
||||
|
requestConfig: { |
||||
|
baseURL: settings.apiServer || 'https://endorser.ch', |
||||
|
timeout: 30000, |
||||
|
retryAttempts: 3 |
||||
|
}, |
||||
|
|
||||
|
// Configure starred projects fetching
|
||||
|
starredProjectsConfig: { |
||||
|
enabled: true, |
||||
|
starredPlanHandleIds: settings.starredPlanHandleIds || [], |
||||
|
lastAckedJwtId: settings.lastAckedStarredPlanChangesJwtId || '', |
||||
|
fetchInterval: '0 8 * * *', |
||||
|
maxResults: 50 |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Schedule daily notifications
|
||||
|
await DailyNotification.scheduleDailyNotification({ |
||||
|
title: 'TimeSafari Community Update', |
||||
|
body: 'You have new offers and project updates', |
||||
|
time: '09:00', |
||||
|
channel: 'timesafari_community_updates' |
||||
|
}); |
||||
|
|
||||
|
this.initialized = true; |
||||
|
console.log('DailyNotification plugin initialized successfully with TimeSafari PWA'); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('Failed to initialize DailyNotification plugin:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method |
||||
|
* |
||||
|
* This replaces the existing method with plugin-enhanced functionality |
||||
|
* while maintaining the same interface and behavior. |
||||
|
*/ |
||||
|
async loadNewStarredProjectChanges(): Promise<StarredProjectsResponse> { |
||||
|
if (!this.initialized) { |
||||
|
await this.initializeDailyNotification(); |
||||
|
} |
||||
|
|
||||
|
const settings = await this.getTimeSafariSettings(); |
||||
|
|
||||
|
if (!settings.activeDid || !settings.starredPlanHandleIds?.length) { |
||||
|
return { data: [], hitLimit: false }; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// Use plugin's enhanced fetching with same interface as existing TimeSafari code
|
||||
|
const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges( |
||||
|
settings.activeDid, |
||||
|
settings.starredPlanHandleIds, |
||||
|
settings.lastAckedStarredPlanChangesJwtId |
||||
|
); |
||||
|
|
||||
|
// Enhanced logging (optional)
|
||||
|
console.log('Starred projects loaded successfully:', { |
||||
|
count: starredProjectChanges.data.length, |
||||
|
hitLimit: starredProjectChanges.hitLimit, |
||||
|
planIds: settings.starredPlanHandleIds.length |
||||
|
}); |
||||
|
|
||||
|
return starredProjectChanges; |
||||
|
|
||||
|
} catch (error) { |
||||
|
// Same error handling as existing TimeSafari code
|
||||
|
console.warn('[TimeSafari] Failed to load starred project changes:', error); |
||||
|
return { data: [], hitLimit: false }; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get TimeSafari settings using existing PlatformServiceMixin pattern |
||||
|
*/ |
||||
|
private async getTimeSafariSettings(): Promise<Settings> { |
||||
|
try { |
||||
|
// Use existing TimeSafari settings retrieval pattern
|
||||
|
const result = await this.platformService.dbQuery( |
||||
|
"SELECT * FROM settings WHERE id = 1" |
||||
|
); |
||||
|
|
||||
|
if (!result?.values?.length) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
// Map database columns to values (existing TimeSafari pattern)
|
||||
|
const settings: Settings = {}; |
||||
|
result.columns.forEach((column, index) => { |
||||
|
if (column !== 'id') { |
||||
|
settings[column] = result.values[0][index]; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Handle JSON field parsing (existing TimeSafari pattern)
|
||||
|
if (settings.starredPlanHandleIds && typeof settings.starredPlanHandleIds === 'string') { |
||||
|
try { |
||||
|
settings.starredPlanHandleIds = JSON.parse(settings.starredPlanHandleIds); |
||||
|
} catch { |
||||
|
settings.starredPlanHandleIds = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return settings; |
||||
|
} catch (error) { |
||||
|
console.error('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.platformService.dbExec( |
||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)", |
||||
|
[key, JSON.stringify(value)] |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
retrieve: async (key: string) => { |
||||
|
const result = await this.platformService.dbQuery( |
||||
|
"SELECT data FROM temp WHERE id = ?", |
||||
|
[key] |
||||
|
); |
||||
|
|
||||
|
if (result?.values?.length) { |
||||
|
try { |
||||
|
return JSON.parse(result.values[0][0] as string); |
||||
|
} catch { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Callback handlers that match existing TimeSafari error handling patterns |
||||
|
*/ |
||||
|
private async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise<void> { |
||||
|
// Enhanced logging (optional)
|
||||
|
console.log('Starred projects success callback:', { |
||||
|
count: data.data.length, |
||||
|
hitLimit: data.hitLimit |
||||
|
}); |
||||
|
|
||||
|
// Store results in TimeSafari temp table for UI access
|
||||
|
await this.platformService.dbExec( |
||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)", |
||||
|
['starred_projects_latest', JSON.stringify(data)] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private async handleStarredProjectsError(error: Error): Promise<void> { |
||||
|
// Same error handling as existing TimeSafari code
|
||||
|
console.warn('[TimeSafari] Failed to load starred project changes:', error); |
||||
|
|
||||
|
// Store error in TimeSafari temp table for UI access
|
||||
|
await this.platformService.dbExec( |
||||
|
"INSERT OR REPLACE INTO temp (id, data) VALUES (?, ?)", |
||||
|
['starred_projects_error', JSON.stringify({ error: error.message, timestamp: Date.now() })] |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private async handleStarredProjectsComplete(result: unknown): Promise<void> { |
||||
|
// Handle completion
|
||||
|
console.log('Starred projects fetch completed:', result); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get plugin status for debugging |
||||
|
*/ |
||||
|
async getDailyNotificationStatus(): Promise<{ |
||||
|
initialized: boolean; |
||||
|
platform: string; |
||||
|
capabilities: unknown; |
||||
|
}> { |
||||
|
return { |
||||
|
initialized: this.initialized, |
||||
|
platform: Capacitor.getPlatform(), |
||||
|
capabilities: this.platformService.getCapabilities() |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Vue.js Mixin for DailyNotification integration with TimeSafari PWA |
||||
|
* |
||||
|
* This mixin extends the existing PlatformServiceMixin to include |
||||
|
* DailyNotification functionality while maintaining the same patterns. |
||||
|
*/ |
||||
|
export const TimeSafariDailyNotificationMixin = { |
||||
|
data() { |
||||
|
return { |
||||
|
// Existing TimeSafari data
|
||||
|
activeDid: '', |
||||
|
starredPlanHandleIds: [] as string[], |
||||
|
lastAckedStarredPlanChangesJwtId: '', |
||||
|
numNewStarredProjectChanges: 0, |
||||
|
newStarredProjectChangesHitLimit: false, |
||||
|
|
||||
|
// Plugin integration
|
||||
|
dailyNotificationService: null as DailyNotification | null, |
||||
|
integrationService: null as TimeSafariIntegrationService | null, |
||||
|
enhancedPlatformService: null as EnhancedCapacitorPlatformService | null |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
computed: { |
||||
|
// Existing TimeSafari computed properties
|
||||
|
isCapacitor(): boolean { |
||||
|
return this.platformService.isCapacitor(); |
||||
|
}, |
||||
|
|
||||
|
isWeb(): boolean { |
||||
|
return this.platformService.isWeb(); |
||||
|
}, |
||||
|
|
||||
|
isElectron(): boolean { |
||||
|
return this.platformService.isElectron(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
async mounted() { |
||||
|
// Initialize DailyNotification when component mounts (only on Capacitor)
|
||||
|
if (this.isCapacitor) { |
||||
|
await this.initializeDailyNotification(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
/** |
||||
|
* Initialize DailyNotification plugin with TimeSafari configuration |
||||
|
*/ |
||||
|
async initializeDailyNotification(): Promise<void> { |
||||
|
try { |
||||
|
// Create enhanced platform service
|
||||
|
this.enhancedPlatformService = new EnhancedCapacitorPlatformService(); |
||||
|
|
||||
|
// Initialize the plugin
|
||||
|
await this.enhancedPlatformService.initializeDailyNotification(); |
||||
|
|
||||
|
console.log('DailyNotification initialized successfully in TimeSafari PWA'); |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('Failed to initialize DailyNotification in TimeSafari PWA:', error); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Enhanced version of existing TimeSafari loadNewStarredProjectChanges method |
||||
|
*/ |
||||
|
async loadNewStarredProjectChanges(): Promise<void> { |
||||
|
if (this.isCapacitor && this.enhancedPlatformService) { |
||||
|
// Use plugin-enhanced method on Capacitor
|
||||
|
const result = await this.enhancedPlatformService.loadNewStarredProjectChanges(); |
||||
|
this.numNewStarredProjectChanges = result.data.length; |
||||
|
this.newStarredProjectChangesHitLimit = result.hitLimit; |
||||
|
} else { |
||||
|
// Use existing web method in browser
|
||||
|
await this.loadNewStarredProjectChangesWeb(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Existing web-only method (unchanged) |
||||
|
*/ |
||||
|
async loadNewStarredProjectChangesWeb(): Promise<void> { |
||||
|
// Your existing web-only implementation
|
||||
|
console.log('Using web-only method for starred projects'); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Get DailyNotification status for debugging |
||||
|
*/ |
||||
|
async getDailyNotificationStatus(): Promise<unknown> { |
||||
|
if (this.enhancedPlatformService) { |
||||
|
return await this.enhancedPlatformService.getDailyNotificationStatus(); |
||||
|
} |
||||
|
return { initialized: false, platform: 'web' }; |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Usage example in a TimeSafari PWA Vue component |
||||
|
*/ |
||||
|
export const TimeSafariHomeViewExample = { |
||||
|
name: 'TimeSafariHomeView', |
||||
|
|
||||
|
mixins: [TimeSafariDailyNotificationMixin], |
||||
|
|
||||
|
data() { |
||||
|
return { |
||||
|
// Existing TimeSafari data
|
||||
|
activeDid: '', |
||||
|
starredPlanHandleIds: [] as string[], |
||||
|
lastAckedStarredPlanChangesJwtId: '', |
||||
|
numNewStarredProjectChanges: 0, |
||||
|
newStarredProjectChangesHitLimit: false |
||||
|
}; |
||||
|
}, |
||||
|
|
||||
|
async mounted() { |
||||
|
// Load existing TimeSafari data
|
||||
|
await this.loadTimeSafariData(); |
||||
|
|
||||
|
// Initialize DailyNotification (only on Capacitor)
|
||||
|
if (this.isCapacitor) { |
||||
|
await this.initializeDailyNotification(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
/** |
||||
|
* Load existing TimeSafari data using PlatformServiceMixin |
||||
|
*/ |
||||
|
async loadTimeSafariData(): Promise<void> { |
||||
|
try { |
||||
|
// Use existing TimeSafari settings pattern
|
||||
|
const settings = await this.$settings(); |
||||
|
|
||||
|
this.activeDid = settings.activeDid || ''; |
||||
|
this.starredPlanHandleIds = settings.starredPlanHandleIds || []; |
||||
|
this.lastAckedStarredPlanChangesJwtId = settings.lastAckedStarredPlanChangesJwtId || ''; |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('Error loading TimeSafari data:', error); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Enhanced loadNewStarredProjectChanges method |
||||
|
*/ |
||||
|
async loadNewStarredProjectChanges(): Promise<void> { |
||||
|
if (this.isCapacitor && this.enhancedPlatformService) { |
||||
|
// Use plugin-enhanced method on Capacitor
|
||||
|
const result = await this.enhancedPlatformService.loadNewStarredProjectChanges(); |
||||
|
this.numNewStarredProjectChanges = result.data.length; |
||||
|
this.newStarredProjectChangesHitLimit = result.hitLimit; |
||||
|
} else { |
||||
|
// Use existing web method in browser
|
||||
|
await this.loadNewStarredProjectChangesWeb(); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Existing web-only method (unchanged) |
||||
|
*/ |
||||
|
async loadNewStarredProjectChangesWeb(): Promise<void> { |
||||
|
// Your existing web-only implementation
|
||||
|
console.log('Using web-only method for starred projects'); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Factory function to create enhanced platform service |
||||
|
*/ |
||||
|
export function createEnhancedPlatformService(): EnhancedCapacitorPlatformService { |
||||
|
return new EnhancedCapacitorPlatformService(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Utility function to check if DailyNotification should be used |
||||
|
*/ |
||||
|
export function shouldUseDailyNotification(): boolean { |
||||
|
return Capacitor.isNativePlatform(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Utility function to get platform-specific configuration |
||||
|
*/ |
||||
|
export function getPlatformConfig(): { |
||||
|
usePlugin: boolean; |
||||
|
platform: string; |
||||
|
capabilities: unknown; |
||||
|
} { |
||||
|
const platform = Capacitor.getPlatform(); |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
return { |
||||
|
usePlugin: Capacitor.isNativePlatform(), |
||||
|
platform, |
||||
|
capabilities: platformService.getCapabilities() |
||||
|
}; |
||||
|
} |
Loading…
Reference in new issue