/** * TimeSafari PWA Integration Example * * This example shows how to directly adopt the existing TimeSafari PWA * request patterns (like loadNewStarredProjectChanges) into the Daily * Notification Plugin configuration. * * @author Matthew Raymer * @version 1.0.0 */ import { DailyNotification } from '@timesafari/daily-notification-plugin'; import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; import { AxiosInstance } from 'axios'; // TimeSafari PWA existing interfaces (for reference) interface PlanSummaryAndPreviousClaim { id: string; title: string; description: string; lastUpdated: string; previousClaim?: unknown; } interface StarredProjectsResponse { data: Array; hitLimit: boolean; } // TimeSafari PWA existing class (for reference) class TimeSafariHomeView { // Existing TimeSafari PWA properties activeDid: string = ''; starredPlanHandleIds: string[] = []; lastAckedStarredPlanChangesJwtId: string = ''; numNewStarredProjectChanges: number = 0; newStarredProjectChangesHitLimit: boolean = false; apiServer: string = 'https://endorser.ch'; axios: AxiosInstance; // Plugin integration properties private dailyNotificationService: DailyNotification | null = null; private integrationService: TimeSafariIntegrationService | null = null; constructor(axiosInstance: AxiosInstance) { this.axios = axiosInstance; } /** * Initialize Daily Notification Plugin with existing TimeSafari patterns */ async initializeDailyNotifications(): Promise { try { // Configure plugin with existing TimeSafari configuration await DailyNotification.configure({ // TimeSafari-specific configuration timesafariConfig: { activeDid: this.activeDid, // Use existing TimeSafari API endpoints endpoints: { offersToPerson: `${this.apiServer}/api/v2/offers/person`, offersToPlans: `${this.apiServer}/api/v2/offers/plans`, projectsLastUpdated: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween` }, // Configure starred projects fetching (matches existing TimeSafari pattern) starredProjectsConfig: { enabled: true, starredPlanHandleIds: this.starredPlanHandleIds, lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId, fetchInterval: '0 8 * * *', // Daily at 8 AM maxResults: 50, hitLimitHandling: 'warn' // 'warn' | 'error' | 'ignore' }, // Sync configuration matching TimeSafari patterns syncConfig: { enableParallel: true, maxConcurrent: 3, batchSize: 10, timeout: 30000, retryAttempts: 3 } }, // Network configuration using existing TimeSafari patterns networkConfig: { // Use existing axios instance httpClient: this.axios, baseURL: this.apiServer, timeout: 30000, retryAttempts: 3, retryDelay: 1000, // Headers matching TimeSafari pattern defaultHeaders: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }, // Content fetch configuration contentFetch: { enabled: true, schedule: '0 8 * * *', // Daily at 8 AM // Use existing TimeSafari request pattern requestConfig: { method: 'POST', url: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween`, headers: { 'Authorization': 'Bearer ${jwt}', 'X-User-DID': '${activeDid}' }, body: { planIds: '${starredPlanHandleIds}', afterId: '${lastAckedJwtId}' } }, // Callbacks matching 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: this.activeDid, storageAdapter: this.getTimeSafariStorageAdapter(), endorserApiBaseUrl: this.apiServer, // Use existing TimeSafari request patterns requestConfig: { httpClient: this.axios, baseURL: this.apiServer, timeout: 30000, retryAttempts: 3 }, // Configure starred projects fetching starredProjectsConfig: { enabled: true, starredPlanHandleIds: this.starredPlanHandleIds, lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId, fetchInterval: '0 8 * * *', maxResults: 50 } }); console.log('Daily notifications initialized successfully with TimeSafari patterns'); } catch (error) { console.error('Failed to initialize daily notifications:', error); throw error; } } /** * 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 { if (this.activeDid && this.starredPlanHandleIds.length > 0) { try { // Use plugin's enhanced fetching with same interface as TimeSafari PWA const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges( this.activeDid, this.starredPlanHandleIds, this.lastAckedStarredPlanChangesJwtId ); // Same handling as original TimeSafari PWA this.numNewStarredProjectChanges = starredProjectChanges.data.length; this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit; // Enhanced logging with structured observability console.log('Starred projects loaded successfully:', { count: this.numNewStarredProjectChanges, hitLimit: this.newStarredProjectChangesHitLimit, planIds: this.starredPlanHandleIds.length }); } catch (error) { // Same error handling as original TimeSafari PWA console.warn('[HomeView] Failed to load starred project changes:', error); this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; } } else { this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; } } /** * Callback handlers matching TimeSafari patterns */ async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise { // Same handling as TimeSafari PWA this.numNewStarredProjectChanges = data.data.length; this.newStarredProjectChangesHitLimit = data.hitLimit; // Update UI (existing TimeSafari method) this.updateStarredProjectsUI(data); // Enhanced logging console.log('Starred projects success callback:', { count: data.data.length, hitLimit: data.hitLimit }); } async handleStarredProjectsError(error: Error): Promise { // Same error handling as TimeSafari PWA console.warn('[HomeView] Failed to load starred project changes:', error); this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; // Enhanced error handling with structured logging console.error('Starred projects error callback:', { error: error.message, activeDid: this.activeDid, planCount: this.starredPlanHandleIds.length }); } async handleStarredProjectsComplete(result: unknown): Promise { // Handle completion console.log('Starred projects fetch completed:', result); } /** * Existing TimeSafari methods (unchanged) */ private updateStarredProjectsUI(data: StarredProjectsResponse): void { // Existing TimeSafari UI update logic console.log('Updating UI with starred projects data:', data); } private getTimeSafariStorageAdapter(): unknown { // Return existing TimeSafari storage adapter return { // TimeSafari storage adapter implementation }; } } // Usage example export async function initializeTimeSafariWithPlugin(axiosInstance: AxiosInstance): Promise { const homeView = new TimeSafariHomeView(axiosInstance); // Set up TimeSafari data (existing pattern) homeView.activeDid = 'did:example:123'; homeView.starredPlanHandleIds = ['plan1', 'plan2', 'plan3']; homeView.lastAckedStarredPlanChangesJwtId = 'jwt123'; // Initialize plugin with TimeSafari patterns await homeView.initializeDailyNotifications(); // Test the enhanced method await homeView.loadNewStarredProjectChanges(); return homeView; } // Comparison example: Original vs Plugin-enhanced export class TimeSafariComparisonExample { private axios: AxiosInstance; constructor(axiosInstance: AxiosInstance) { this.axios = axiosInstance; } /** * Original TimeSafari PWA method (for comparison) */ async loadNewStarredProjectChangesOriginal( activeDid: string, starredPlanHandleIds: string[], lastAckedJwtId: string ): Promise { if (!starredPlanHandleIds || starredPlanHandleIds.length === 0) { return { data: [], hitLimit: false }; } if (!lastAckedJwtId) { return { data: [], hitLimit: false }; } const url = `https://endorser.ch/api/v2/report/plansLastUpdatedBetween`; const headers = await this.getHeaders(activeDid); const requestBody = { planIds: starredPlanHandleIds, afterId: lastAckedJwtId, }; const response = await this.axios.post(url, requestBody, { headers }); return response.data; } /** * Plugin-enhanced version (same interface, enhanced functionality) */ async loadNewStarredProjectChangesEnhanced( activeDid: string, starredPlanHandleIds: string[], lastAckedJwtId: string ): Promise { // Use plugin's enhanced integration service const integrationService = TimeSafariIntegrationService.getInstance(); return await integrationService.getStarredProjectsWithChanges( activeDid, starredPlanHandleIds, lastAckedJwtId ); } /** * Test both implementations in parallel */ async testBothImplementations( activeDid: string, starredPlanHandleIds: string[], lastAckedJwtId: string ): Promise { console.log('Testing both implementations in parallel...'); const start = Date.now(); // Test original implementation const originalResult = await this.loadNewStarredProjectChangesOriginal( activeDid, starredPlanHandleIds, lastAckedJwtId ); const originalTime = Date.now() - start; const pluginStart = Date.now(); // Test plugin-enhanced implementation const pluginResult = await this.loadNewStarredProjectChangesEnhanced( activeDid, starredPlanHandleIds, lastAckedJwtId ); const pluginTime = Date.now() - pluginStart; // Compare results console.log('Comparison Results:', { original: { time: originalTime, count: originalResult.data.length, hitLimit: originalResult.hitLimit }, plugin: { time: pluginTime, count: pluginResult.data.length, hitLimit: pluginResult.hitLimit }, performance: { improvement: ((originalTime - pluginTime) / originalTime * 100).toFixed(2) + '%' } }); // Verify results match if (JSON.stringify(originalResult) === JSON.stringify(pluginResult)) { console.log('✅ Results match perfectly!'); } else { console.log('❌ Results differ:', { original: originalResult, plugin: pluginResult }); } } private async getHeaders(activeDid: string): Promise> { // Existing TimeSafari header generation return { 'Authorization': `Bearer ${await this.getJWTToken(activeDid)}`, 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-User-DID': activeDid }; } private async getJWTToken(activeDid: string): Promise { // Existing TimeSafari JWT token generation return 'jwt-token-placeholder'; } } // Export for use in TimeSafari PWA export { TimeSafariHomeView, TimeSafariComparisonExample };