diff --git a/docs/timesafari-request-adoption-guide.md b/docs/timesafari-request-adoption-guide.md new file mode 100644 index 0000000..b165f20 --- /dev/null +++ b/docs/timesafari-request-adoption-guide.md @@ -0,0 +1,682 @@ +# TimeSafari Daily Notification Plugin - Request Pattern Adoption Guide + +**Author**: Matthew Raymer +**Version**: 1.0.0 +**Created**: 2025-10-08 06:24:57 UTC + +## Overview + +This guide shows how to directly adopt the existing TimeSafari PWA request patterns (like `loadNewStarredProjectChanges()` and `getStarredProjectsWithChanges()`) into the Daily Notification Plugin configuration. The plugin is designed to work seamlessly with TimeSafari's existing axios-based request architecture. + +## Current TimeSafari PWA Pattern Analysis + +### Original TimeSafari PWA Code +```typescript +// TimeSafari PWA - HomeView.vue +private async loadNewStarredProjectChanges() { + if (this.activeDid && this.starredPlanHandleIds.length > 0) { + try { + const starredProjectChanges = await getStarredProjectsWithChanges( + this.axios, + this.apiServer, + this.activeDid, + this.starredPlanHandleIds, + this.lastAckedStarredPlanChangesJwtId, + ); + this.numNewStarredProjectChanges = starredProjectChanges.data.length; + this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit; + } catch (error) { + logger.warn("[HomeView] Failed to load starred project changes:", error); + this.numNewStarredProjectChanges = 0; + this.newStarredProjectChangesHitLimit = false; + } + } +} + +// TimeSafari PWA - API function +export async function getStarredProjectsWithChanges( + axios: Axios, + apiServer: string, + activeDid: string, + starredPlanHandleIds: string[], + afterId?: string, +): Promise<{ data: Array; hitLimit: boolean }> { + const url = `${apiServer}/api/v2/report/plansLastUpdatedBetween`; + const headers = await getHeaders(activeDid); + + const requestBody = { + planIds: starredPlanHandleIds, + afterId: afterId, + }; + + const response = await axios.post(url, requestBody, { headers }); + return response.data; +} +``` + +## Plugin Configuration Adoption + +### 1. Direct Axios Integration Pattern + +The plugin can be configured to use the host's existing axios instance and request patterns: + +```typescript +// In TimeSafari PWA - Plugin Configuration +import { DailyNotification } from '@timesafari/daily-notification-plugin'; +import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; + +// Configure plugin with existing TimeSafari patterns +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 + 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 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: '${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: async (data: { data: Array; hitLimit: boolean }) => { + // Handle successful fetch - same as TimeSafari PWA + this.numNewStarredProjectChanges = data.data.length; + this.newStarredProjectChangesHitLimit = data.hitLimit; + + // Update UI + this.updateStarredProjectsUI(data); + }, + + onError: async (error: Error) => { + // Handle error - same as TimeSafari PWA + logger.warn("[DailyNotification] Failed to load starred project changes:", error); + this.numNewStarredProjectChanges = 0; + this.newStarredProjectChangesHitLimit = false; + }, + + onComplete: async (result: ContentFetchResult) => { + // Handle completion + logger.info("[DailyNotification] Starred projects fetch completed", { + success: result.success, + dataCount: result.data?.data?.length || 0, + hitLimit: result.data?.hitLimit || false + }); + } + } + } +}); +``` + +### 2. Enhanced TimeSafari Integration Service + +The plugin provides an enhanced integration service that mirrors TimeSafari's patterns: + +```typescript +// In TimeSafari PWA - Enhanced Integration +import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; + +// Initialize with existing TimeSafari configuration +const integrationService = TimeSafariIntegrationService.getInstance(); + +await integrationService.initialize({ + activeDid: this.activeDid, + storageAdapter: this.timeSafariStorageAdapter, + 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 + } +}); + +// Use the service to fetch starred projects (same pattern as TimeSafari PWA) +const fetchStarredProjects = async () => { + try { + const starredProjectChanges = await integrationService.getStarredProjectsWithChanges( + this.activeDid, + this.starredPlanHandleIds, + this.lastAckedStarredPlanChangesJwtId + ); + + // Same handling as TimeSafari PWA + this.numNewStarredProjectChanges = starredProjectChanges.data.length; + this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit; + + } catch (error) { + // Same error handling as TimeSafari PWA + logger.warn("[DailyNotification] Failed to load starred project changes:", error); + this.numNewStarredProjectChanges = 0; + this.newStarredProjectChangesHitLimit = false; + } +}; +``` + +### 3. Vue.js Component Integration + +Direct integration into existing TimeSafari Vue components: + +```typescript +// In TimeSafari PWA - HomeView.vue (enhanced) +import { defineComponent } from 'vue'; +import { DailyNotification } from '@timesafari/daily-notification-plugin'; +import { TimeSafariIntegrationService } from '@timesafari/daily-notification-plugin'; + +export default defineComponent({ + name: 'HomeView', + + data() { + return { + // Existing TimeSafari data + activeDid: '', + starredPlanHandleIds: [] as string[], + lastAckedStarredPlanChangesJwtId: '', + numNewStarredProjectChanges: 0, + newStarredProjectChangesHitLimit: false, + + // Plugin integration + dailyNotificationService: null as DailyNotificationService | null, + integrationService: null as TimeSafariIntegrationService | null + }; + }, + + async mounted() { + // Initialize plugin with existing TimeSafari configuration + await this.initializeDailyNotifications(); + }, + + methods: { + async initializeDailyNotifications() { + try { + // Configure plugin with existing TimeSafari patterns + await DailyNotification.configure({ + timesafariConfig: { + activeDid: this.activeDid, + endpoints: { + projectsLastUpdated: `${this.apiServer}/api/v2/report/plansLastUpdatedBetween` + }, + starredProjectsConfig: { + enabled: true, + starredPlanHandleIds: this.starredPlanHandleIds, + lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId, + fetchInterval: '0 8 * * *' + } + }, + + networkConfig: { + httpClient: this.axios, + baseURL: this.apiServer, + timeout: 30000 + }, + + contentFetch: { + enabled: true, + schedule: '0 8 * * *', + callbacks: { + onSuccess: this.handleStarredProjectsSuccess, + onError: this.handleStarredProjectsError + } + } + }); + + // Initialize integration service + this.integrationService = TimeSafariIntegrationService.getInstance(); + await this.integrationService.initialize({ + activeDid: this.activeDid, + storageAdapter: this.timeSafariStorageAdapter, + endorserApiBaseUrl: this.apiServer + }); + + // Replace existing method with plugin-enhanced version + this.loadNewStarredProjectChanges = this.loadNewStarredProjectChangesEnhanced; + + } catch (error) { + logger.error("[HomeView] Failed to initialize daily notifications:", error); + } + }, + + // Enhanced version of existing method + async loadNewStarredProjectChangesEnhanced() { + if (this.activeDid && this.starredPlanHandleIds.length > 0) { + try { + // Use plugin's enhanced fetching with same interface + 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; + + } catch (error) { + // Same error handling as original TimeSafari PWA + logger.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: { data: Array; hitLimit: boolean }) { + // Same handling as TimeSafari PWA + this.numNewStarredProjectChanges = data.data.length; + this.newStarredProjectChangesHitLimit = data.hitLimit; + + // Update UI + this.updateStarredProjectsUI(data); + }, + + async handleStarredProjectsError(error: Error) { + // Same error handling as TimeSafari PWA + logger.warn("[HomeView] Failed to load starred project changes:", error); + this.numNewStarredProjectChanges = 0; + this.newStarredProjectChangesHitLimit = false; + }, + + // Existing TimeSafari methods (unchanged) + updateStarredProjectsUI(data: { data: Array; hitLimit: boolean }) { + // Existing TimeSafari UI update logic + this.$emit('starred-projects-updated', data); + } + } +}); +``` + +## Plugin Implementation Details + +### 1. Enhanced TimeSafari Integration Service + +The plugin provides an enhanced version of TimeSafari's request patterns: + +```typescript +// Plugin implementation - Enhanced TimeSafari Integration Service +export class TimeSafariIntegrationService { + private static instance: TimeSafariIntegrationService; + private httpClient: AxiosInstance | null = null; + private baseURL: string = ''; + private activeDid: string = ''; + + static getInstance(): TimeSafariIntegrationService { + if (!TimeSafariIntegrationService.instance) { + TimeSafariIntegrationService.instance = new TimeSafariIntegrationService(); + } + return TimeSafariIntegrationService.instance; + } + + async initialize(config: { + activeDid: string; + httpClient: AxiosInstance; + baseURL: string; + storageAdapter: TimeSafariStorageAdapter; + endorserApiBaseUrl: string; + }): Promise { + this.activeDid = config.activeDid; + this.httpClient = config.httpClient; + this.baseURL = config.baseURL; + + // Initialize with existing TimeSafari patterns + await this.initializeStorage(config.storageAdapter); + await this.initializeAuthentication(config.endorserApiBaseUrl); + } + + // Enhanced version of TimeSafari's getStarredProjectsWithChanges + async getStarredProjectsWithChanges( + activeDid: string, + starredPlanHandleIds: string[], + afterId?: string + ): Promise<{ data: Array; hitLimit: boolean }> { + if (!starredPlanHandleIds || starredPlanHandleIds.length === 0) { + return { data: [], hitLimit: false }; + } + + if (!afterId) { + return { data: [], hitLimit: false }; + } + + try { + // Use existing TimeSafari request pattern + const url = `${this.baseURL}/api/v2/report/plansLastUpdatedBetween`; + const headers = await this.getHeaders(activeDid); + + const requestBody = { + planIds: starredPlanHandleIds, + afterId: afterId, + }; + + // Use host's axios instance + const response = await this.httpClient!.post(url, requestBody, { headers }); + + // Log with structured logging + observability.logEvent('INFO', EVENT_CODES.FETCH_SUCCESS, 'Starred projects fetched successfully', { + activeDid, + planCount: starredPlanHandleIds.length, + resultCount: response.data.data.length, + hitLimit: response.data.hitLimit + }); + + return response.data; + + } catch (error) { + // Enhanced error handling with structured logging + observability.logEvent('ERROR', EVENT_CODES.FETCH_FAILURE, 'Failed to fetch starred projects', { + activeDid, + planCount: starredPlanHandleIds.length, + error: (error as Error).message + }); + + throw error; + } + } + + // Enhanced version of TimeSafari's getHeaders + private async getHeaders(activeDid: string): Promise> { + // Use existing TimeSafari authentication pattern + const jwt = await this.getJWTToken(activeDid); + + return { + 'Authorization': `Bearer ${jwt}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-User-DID': activeDid + }; + } + + // Enhanced JWT token management + private async getJWTToken(activeDid: string): Promise { + // Use existing TimeSafari JWT pattern + const cachedToken = await this.getCachedToken(activeDid); + if (cachedToken && !this.isTokenExpired(cachedToken)) { + return cachedToken; + } + + // Generate new token using existing TimeSafari pattern + const newToken = await this.generateJWTToken(activeDid); + await this.cacheToken(activeDid, newToken); + + return newToken; + } +} +``` + +### 2. Plugin Configuration Extensions + +The plugin extends the configuration to support TimeSafari's existing patterns: + +```typescript +// Plugin configuration extensions +export interface TimeSafariConfig { + activeDid: string; + + // Existing TimeSafari endpoints + endpoints?: { + offersToPerson?: string; + offersToPlans?: string; + projectsLastUpdated?: string; + }; + + // Enhanced starred projects configuration + starredProjectsConfig?: { + enabled: boolean; + starredPlanHandleIds: string[]; + lastAckedJwtId: string; + fetchInterval: string; // Cron expression + maxResults?: number; + hitLimitHandling?: 'warn' | 'error' | 'ignore'; + }; + + // Existing TimeSafari sync configuration + syncConfig?: { + enableParallel?: boolean; + maxConcurrent?: number; + batchSize?: number; + timeout?: number; + retryAttempts?: number; + }; + + // Enhanced error policy + errorPolicy?: { + maxRetries?: number; + backoffMultiplier?: number; + activeDidChangeRetries?: number; + starredProjectsRetries?: number; // New + }; +} + +export interface NetworkConfig { + // Use existing TimeSafari axios instance + httpClient?: AxiosInstance; + baseURL?: string; + timeout?: number; + retryAttempts?: number; + retryDelay?: number; + + // Existing TimeSafari headers + defaultHeaders?: Record; + + // Enhanced request configuration + requestConfig?: { + method: 'GET' | 'POST' | 'PUT' | 'DELETE'; + url: string; + headers?: Record; + body?: Record; + params?: Record; + }; +} +``` + +## Migration Strategy + +### Phase 1: Parallel Implementation +1. **Keep existing TimeSafari PWA code unchanged** +2. **Add plugin configuration alongside existing code** +3. **Test plugin functionality in parallel** +4. **Compare results between existing and plugin implementations** + +### Phase 2: Gradual Migration +1. **Replace individual request methods one by one** +2. **Use plugin's enhanced error handling and logging** +3. **Maintain existing UI and user experience** +4. **Add plugin-specific features (background fetching, etc.)** + +### 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** + +## Benefits of Plugin Adoption + +### 1. Enhanced Error Handling +```typescript +// Existing TimeSafari PWA +catch (error) { + logger.warn("[HomeView] Failed to load starred project changes:", error); + this.numNewStarredProjectChanges = 0; +} + +// Plugin-enhanced version +catch (error) { + // Structured logging with event IDs + observability.logEvent('WARN', EVENT_CODES.FETCH_FAILURE, 'Failed to load starred project changes', { + eventId: this.generateEventId(), + activeDid: this.activeDid, + planCount: this.starredPlanHandleIds.length, + error: error.message, + retryCount: this.retryCount + }); + + // Enhanced fallback handling + await this.handleStarredProjectsFallback(error); +} +``` + +### 2. Background Fetching +```typescript +// Plugin provides background fetching +await DailyNotification.configure({ + contentFetch: { + enabled: true, + schedule: '0 8 * * *', // Daily at 8 AM + backgroundFetch: true, // Fetch in background + cachePolicy: { + maxAge: 3600, // 1 hour cache + staleWhileRevalidate: 1800 // 30 minutes stale + } + } +}); +``` + +### 3. Enhanced Observability +```typescript +// Plugin provides comprehensive metrics +const metrics = await DailyNotification.getMetrics(); +console.log('Starred Projects Metrics:', { + fetchSuccessRate: metrics.starredProjects.fetchSuccessRate, + averageResponseTime: metrics.starredProjects.averageResponseTime, + cacheHitRate: metrics.starredProjects.cacheHitRate, + errorRate: metrics.starredProjects.errorRate +}); +``` + +## Testing Strategy + +### 1. Parallel Testing +```typescript +// Test both implementations in parallel +const testStarredProjectsFetch = async () => { + // Existing TimeSafari PWA implementation + const existingResult = await getStarredProjectsWithChanges( + this.axios, + this.apiServer, + this.activeDid, + this.starredPlanHandleIds, + this.lastAckedStarredPlanChangesJwtId + ); + + // Plugin implementation + const pluginResult = await this.integrationService.getStarredProjectsWithChanges( + this.activeDid, + this.starredPlanHandleIds, + this.lastAckedStarredPlanChangesJwtId + ); + + // Compare results + assert.deepEqual(existingResult, pluginResult); +}; +``` + +### 2. Performance Testing +```typescript +// Compare performance +const performanceTest = async () => { + const start = Date.now(); + + // Existing implementation + await getStarredProjectsWithChanges(...); + const existingTime = Date.now() - start; + + const pluginStart = Date.now(); + // Plugin implementation + await this.integrationService.getStarredProjectsWithChanges(...); + const pluginTime = Date.now() - pluginStart; + + console.log('Performance Comparison:', { + existing: existingTime, + plugin: pluginTime, + improvement: ((existingTime - pluginTime) / existingTime * 100).toFixed(2) + '%' + }); +}; +``` + +## Conclusion + +The Daily Notification Plugin is designed to seamlessly adopt TimeSafari's existing request patterns while providing enhanced functionality: + +- **Same Interface**: Plugin methods match existing TimeSafari patterns +- **Enhanced Features**: Background fetching, structured logging, metrics +- **Gradual Migration**: Can be adopted incrementally +- **Backward Compatibility**: Existing code continues to work +- **Performance Improvements**: Caching, batching, and optimization + +The plugin transforms TimeSafari's existing `loadNewStarredProjectChanges()` pattern into a more robust, observable, and efficient system while maintaining the same developer experience and user interface. + +--- + +**Next Steps**: +1. Implement parallel testing with existing TimeSafari PWA code +2. Gradually migrate individual request methods +3. Leverage plugin's advanced features for enhanced user experience diff --git a/examples/timesafari-pwa-integration-example.ts b/examples/timesafari-pwa-integration-example.ts new file mode 100644 index 0000000..46dfeb6 --- /dev/null +++ b/examples/timesafari-pwa-integration-example.ts @@ -0,0 +1,403 @@ +/** + * 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 };