/** * DailyNotification Setup for TimeSafari PWA * * This example shows exactly how to configure the DailyNotification plugin * to work with your existing TimeSafari PWA request patterns, specifically * the loadNewStarredProjectChanges() and getStarredProjectsWithChanges() methods. * * @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'; // Your existing TimeSafari PWA interfaces interface PlanSummaryAndPreviousClaim { id: string; title: string; description: string; lastUpdated: string; previousClaim?: unknown; } interface StarredProjectsResponse { data: Array; hitLimit: boolean; } // Your existing TimeSafari PWA class structure class TimeSafariHomeView { // Your existing properties activeDid: string = ''; starredPlanHandleIds: string[] = []; lastAckedStarredPlanChangesJwtId: string = ''; numNewStarredProjectChanges: number = 0; newStarredProjectChangesHitLimit: boolean = false; apiServer: string = 'https://endorser.ch'; axios: AxiosInstance; // Plugin integration private dailyNotificationService: DailyNotification | null = null; private integrationService: TimeSafariIntegrationService | null = null; constructor(axiosInstance: AxiosInstance) { this.axios = axiosInstance; } /** * DailyNotification Setup - This is what you need to add to your TimeSafari PWA */ async setupDailyNotification(): Promise { try { console.log('Setting up DailyNotification for TimeSafari PWA...'); // Step 1: Configure the DailyNotification plugin await DailyNotification.configure({ // Basic plugin configuration storage: 'tiered', ttlSeconds: 1800, enableETagSupport: true, enableErrorHandling: true, enablePerformanceOptimization: true, // TimeSafari-specific configuration timesafariConfig: { // Required: Your existing activeDid activeDid: this.activeDid, // Your existing 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 your existing pattern) starredProjectsConfig: { enabled: true, starredPlanHandleIds: this.starredPlanHandleIds, lastAckedJwtId: this.lastAckedStarredPlanChangesJwtId, fetchInterval: '0 8 * * *', // Daily at 8 AM (same as your existing schedule) maxResults: 50, hitLimitHandling: 'warn' // Same as your existing error handling }, // Sync configuration (optimized for your use case) syncConfig: { enableParallel: true, maxConcurrent: 3, batchSize: 10, timeout: 30000, retryAttempts: 3 }, // Error policy (matches your existing error handling) errorPolicy: { maxRetries: 3, backoffMultiplier: 2, activeDidChangeRetries: 5, starredProjectsRetries: 3 } }, // Network configuration using your existing axios instance networkConfig: { // Use your existing axios instance httpClient: this.axios, baseURL: this.apiServer, timeout: 30000, retryAttempts: 3, retryDelay: 1000, maxConcurrent: 5, // Headers matching your existing pattern defaultHeaders: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'TimeSafari-PWA/1.0.0' } }, // Content fetch configuration (replaces your existing loadNewStarredProjectChanges) contentFetch: { enabled: true, schedule: '0 8 * * *', // Daily at 8 AM // Your existing request pattern requestConfig: { method: 'POST', url: `${this.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 your existing 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 || 'your-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'] } ] } } }); // Step 2: Initialize TimeSafari Integration Service this.integrationService = TimeSafariIntegrationService.getInstance(); await this.integrationService.initialize({ activeDid: this.activeDid, storageAdapter: this.getTimeSafariStorageAdapter(), endorserApiBaseUrl: this.apiServer, // Use your existing 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 } }); // Step 3: 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' }); console.log('DailyNotification setup completed successfully!'); } catch (error) { console.error('Failed to setup DailyNotification:', error); throw error; } } /** * Enhanced version of your existing loadNewStarredProjectChanges method * * This replaces your existing 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 your existing code const starredProjectChanges = await this.integrationService!.getStarredProjectsWithChanges( this.activeDid, this.starredPlanHandleIds, this.lastAckedStarredPlanChangesJwtId ); // Same handling as your existing code this.numNewStarredProjectChanges = starredProjectChanges.data.length; this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit; // Enhanced logging (optional) console.log('Starred projects loaded successfully:', { count: this.numNewStarredProjectChanges, hitLimit: this.newStarredProjectChangesHitLimit, planIds: this.starredPlanHandleIds.length }); } catch (error) { // Same error handling as your existing code 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 that match your existing error handling patterns */ async handleStarredProjectsSuccess(data: StarredProjectsResponse): Promise { // Same handling as your existing code this.numNewStarredProjectChanges = data.data.length; this.newStarredProjectChangesHitLimit = data.hitLimit; // Update UI (your existing method) this.updateStarredProjectsUI(data); // Enhanced logging (optional) console.log('Starred projects success callback:', { count: data.data.length, hitLimit: data.hitLimit }); } async handleStarredProjectsError(error: Error): Promise { // Same error handling as your existing code console.warn('[HomeView] Failed to load starred project changes:', error); this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; // Enhanced error handling (optional) 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); } /** * Your existing methods (unchanged) */ private updateStarredProjectsUI(data: StarredProjectsResponse): void { // Your existing UI update logic console.log('Updating UI with starred projects data:', data); } private getTimeSafariStorageAdapter(): unknown { // Return your existing TimeSafari storage adapter return { // Your existing storage adapter implementation }; } } // Usage example - This is what you add to your TimeSafari PWA export async function setupDailyNotificationForTimeSafari( axiosInstance: AxiosInstance, activeDid: string, starredPlanHandleIds: string[], lastAckedJwtId: string, apiServer: string = 'https://endorser.ch' ): Promise { console.log('Setting up DailyNotification for TimeSafari PWA...'); // Create your existing HomeView instance const homeView = new TimeSafariHomeView(axiosInstance); // Set up your existing TimeSafari data homeView.activeDid = activeDid; homeView.starredPlanHandleIds = starredPlanHandleIds; homeView.lastAckedStarredPlanChangesJwtId = lastAckedJwtId; homeView.apiServer = apiServer; // Setup DailyNotification plugin await homeView.setupDailyNotification(); // Test the enhanced method await homeView.loadNewStarredProjectChanges(); console.log('DailyNotification setup completed successfully!'); return homeView; } // Vue.js component integration example export const TimeSafariDailyNotificationMixin = { data() { return { // Your existing data activeDid: '', starredPlanHandleIds: [] as string[], lastAckedStarredPlanChangesJwtId: '', numNewStarredProjectChanges: 0, newStarredProjectChangesHitLimit: false, // Plugin integration dailyNotificationService: null as DailyNotification | null, integrationService: null as TimeSafariIntegrationService | null }; }, async mounted() { // Setup DailyNotification when component mounts await this.setupDailyNotification(); }, methods: { async setupDailyNotification() { try { // Configure DailyNotification plugin 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.getTimeSafariStorageAdapter(), endorserApiBaseUrl: this.apiServer }); } catch (error) { console.error('Failed to setup DailyNotification:', error); } }, // Your existing methods (enhanced) async loadNewStarredProjectChanges() { if (this.activeDid && this.starredPlanHandleIds.length > 0) { try { const starredProjectChanges = await this.integrationService.getStarredProjectsWithChanges( this.activeDid, this.starredPlanHandleIds, this.lastAckedStarredPlanChangesJwtId ); this.numNewStarredProjectChanges = starredProjectChanges.data.length; this.newStarredProjectChangesHitLimit = starredProjectChanges.hitLimit; } catch (error) { console.warn('[HomeView] Failed to load starred project changes:', error); this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; } } else { this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; } }, handleStarredProjectsSuccess(data: StarredProjectsResponse) { this.numNewStarredProjectChanges = data.data.length; this.newStarredProjectChangesHitLimit = data.hitLimit; this.updateStarredProjectsUI(data); }, handleStarredProjectsError(error: Error) { console.warn('[HomeView] Failed to load starred project changes:', error); this.numNewStarredProjectChanges = 0; this.newStarredProjectChangesHitLimit = false; }, // Your existing methods updateStarredProjectsUI(data: StarredProjectsResponse) { // Your existing UI update logic }, getTimeSafariStorageAdapter() { // Your existing storage adapter return {}; } } }; // Export for use in your TimeSafari PWA export { TimeSafariHomeView };