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