import { Capacitor } from '@capacitor/core'; import { DailyNotificationPlugin } from '@timesafari/daily-notification-plugin'; // Phase 4: Import TimeSafari components import { EndorserAPIClient, TIMESAFARI_ENDSORER_CONFIG } from '../../../src/typescript/EndorserAPIClient'; import { SecurityManager, TIMESAFARI_SECURITY_CONFIG } from '../../../src/typescript/SecurityManager'; import { TimeSafariNotificationManager, DEFAULT_TIMESAFARI_PREFERENCES } from '../../../src/typescript/TimeSafariNotificationManager'; import { TimeSafariUser, TimeSafariPreferences, EnhancedTimeSafariNotification, TimeSafariNotificationType } from '../../../src/definitions'; // Enhanced ConfigLoader for Phase 4 class ConfigLoader { private static instance: ConfigLoader; private config: any; static getInstance(): ConfigLoader { if (!this.instance) { this.instance = new ConfigLoader(); } return this.instance; } async loadConfig(): Promise { this.config = { timesafari: { appId: 'timesafari-android-test', appName: 'TimeSafari Android Test', version: '1.0.0' }, scheduling: { contentFetch: { schedule: '0 8 * * *' }, userNotification: { schedule: '0 9 * * *' } }, endorser: { baseUrl: 'http://10.0.2.2:3001/api/v2/report', apiKey: 'test-api-key', // Phase 4: Enhanced EndorserAPI configuration timeoutMs: 15000, maxRetries: 3, enableParallel: true, maxConcurrent: 3 }, security: { // Phase 4: Security configuration enableCryptoSigning: true, enableJWTGeneration: true, jwtExpirationMinutes: 60, signatureAlgorithm: 'ES256K', credentialStorage: 'secure_element' }, testData: { userDid: 'did:example:android-test-user', lastKnownOfferId: '0', lastKnownPlanId: '0', starredPlanIds: ['plan:test-1', 'plan:test-2'], favoritePersonIds: ['person:test-1'], favoriteItemIds: ['item:test-1'] } }; } getConfig(): any { return this.config; } getEndorserUrl(endpoint: string): string { const endpoints: { [key: string]: string } = { notificationsBundle: '/notifications/bundle', offers: '/offersToPerson', offersToPlans: '/offersToPlansOwnedByMe', plansLastUpdated: '/plansLastUpdatedBetween' }; return `${this.config.endorser.baseUrl}${endpoints[endpoint] || endpoint}`; } // Phase 4: Enhanced configuration methods getEndorserAPIConfig() { return { baseUrl: this.config.endorser.baseUrl.replace('/api/v2/report', ''), timeoutMs: this.config.endorser.timeoutMs, maxRetries: this.config.endorser.maxRetries, enableParallel: this.config.endorser.enableParallel, maxConcurrent: this.config.endorser.maxConcurrent }; } getSecurityConfig() { return { enableCryptoSigning: this.config.security.enableCryptoSigning, enableJWTGeneration: this.config.security.enableJWTGeneration, jwtExpirationMinutes: this.config.security.jwtExpirationMinutes, signatureAlgorithm: this.config.security.signatureAlgorithm, credentialStorage: this.config.security.credentialStorage }; } getTimeSafariUser(): TimeSafariUser { return { activeDid: this.config.testData.userDid, preferences: DEFAULT_TIMESAFARI_PREFERENCES, starredPlanIds: this.config.testData.starredPlanIds, favoritePersonIds: this.config.testData.favoritePersonIds, favoriteItemIds: this.config.testData.favoriteItemIds, lastKnownOfferId: this.config.testData.lastKnownOfferId, lastKnownPlanId: this.config.testData.lastKnownPlanId }; } getAuthHeaders(): any { return { 'Authorization': `Bearer ${this.config.endorser.apiKey}`, 'Content-Type': 'application/json' }; } } class MockDailyNotificationService { constructor(config: any) { this.config = config; } private config: any; async initialize(): Promise { console.log('Mock notification service initialized'); } async scheduleDualNotification(config: any): Promise { console.log('Mock dual notification scheduled:', config); } async registerCallback(name: string, callback: Function): Promise { console.log(`Mock callback registered: ${name}`); } async getDualScheduleStatus(): Promise { return { contentFetch: { enabled: true }, userNotification: { enabled: true } }; } } class TestLogger { constructor(level: string) { console.log('Mock logger initialized with level:', level); } info(message: string, data?: any) { console.log(`[INFO] ${message}`, data); } error(message: string, data?: any) { console.error(`[ERROR] ${message}`, data); } debug(message: string, data?: any) { console.log(`[DEBUG] ${message}`, data); } } // Enhanced UI components for comprehensive testing class PermissionManager { private container: HTMLElement; private dialogContainer: HTMLElement; constructor(container: HTMLElement, dialogContainer: HTMLElement) { this.container = container; this.dialogContainer = dialogContainer; } async updateStatus(): Promise { // Mock permission status for testing const mockStatus = { granted: true, notifications: 'granted' as const, backgroundRefresh: 'granted' as const }; this.renderStatus(mockStatus); } private renderStatus(status: any): void { const statusClass = status.granted ? 'status-granted' : 'status-denied'; const statusText = status.granted ? 'Granted' : 'Denied'; this.container.innerHTML = `
${status.granted ? '✓' : '✗'} ${statusText}
Notifications: ${status.notifications}
Background Refresh: ${status.backgroundRefresh}
`; } showPermissionDialog(): void { const dialog = document.createElement('div'); dialog.className = 'dialog-overlay'; dialog.innerHTML = `

Enable Daily Notifications

Get notified about new offers, projects, people, and items in your TimeSafari community.

  • New offers directed to you
  • Changes to your projects
  • Updates from favorited people
  • New items of interest
`; this.dialogContainer.appendChild(dialog); dialog.querySelector('#allow-permissions')?.addEventListener('click', () => { this.hideDialog(); this.updateStatus(); }); dialog.querySelector('#deny-permissions')?.addEventListener('click', () => { this.hideDialog(); }); dialog.querySelector('#never-permissions')?.addEventListener('click', () => { this.hideDialog(); }); } private hideDialog(): void { const dialog = this.dialogContainer.querySelector('.dialog-overlay'); if (dialog) { dialog.remove(); } } } class SettingsPanel { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } render(): void { this.container.innerHTML = `
`; } } class StatusDashboard { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } render(): void { this.container.innerHTML = `
Overall Status
Active
Next Notification
2h 15m
Last Outcome
Success
Cache Age
1h 23m

Performance

Success Rate: 95%
Error Count: 2
`; } } class ErrorDisplay { private container: HTMLElement; constructor(container: HTMLElement) { this.container = container; } showError(error: Error): void { this.container.innerHTML = `
⚠️

Something went wrong

${error.message}

Error Code: ${error.name}

`; } hide(): void { this.container.innerHTML = ''; } } // Enhanced test interface for TimeSafari Android integration with Phase 4 components class TimeSafariAndroidTestApp { private statusElement: HTMLElement; private logElement: HTMLElement; private configLoader: ConfigLoader; private notificationService: MockDailyNotificationService; private logger: TestLogger; // Phase 4: TimeSafari components private endorserAPIClient: EndorserAPIClient; private securityManager: SecurityManager; private timeSafariNotificationManager: TimeSafariNotificationManager; // UI Components private permissionManager: PermissionManager; private settingsPanel: SettingsPanel; private statusDashboard: StatusDashboard; private errorDisplay: ErrorDisplay; constructor() { this.statusElement = document.getElementById('status')!; this.logElement = document.getElementById('log')!; this.configLoader = ConfigLoader.getInstance(); this.logger = new TestLogger('debug'); this.notificationService = new MockDailyNotificationService(this.configLoader.getConfig()); // Phase 4: Initialize TimeSafari components this.endorserAPIClient = new EndorserAPIClient(this.configLoader.getEndorserAPIConfig()); this.securityManager = new SecurityManager(this.configLoader.getSecurityConfig()); this.timeSafariNotificationManager = new TimeSafariNotificationManager(); // Initialize UI components this.permissionManager = new PermissionManager( document.getElementById('permission-status-container')!, document.getElementById('permission-dialog-container')! ); this.settingsPanel = new SettingsPanel(document.getElementById('settings-container')!); this.statusDashboard = new StatusDashboard(document.getElementById('status-container')!); this.errorDisplay = new ErrorDisplay(document.getElementById('error-container')!); this.setupEventListeners(); this.initializeUI(); this.initializePhase4Components(); this.log('TimeSafari Android Test app initialized with Phase 4 components'); } private setupEventListeners() { // Original test functionality document.getElementById('configure')?.addEventListener('click', () => this.testConfigure()); document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule()); document.getElementById('endorser-api')?.addEventListener('click', () => this.testEndorserAPI()); document.getElementById('callbacks')?.addEventListener('click', () => this.testCallbacks()); document.getElementById('check-status')?.addEventListener('click', () => this.testStatus()); document.getElementById('performance')?.addEventListener('click', () => this.testPerformance()); document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog()); // Enhanced UI functionality document.getElementById('check-permissions')?.addEventListener('click', () => this.checkPermissions()); document.getElementById('request-permissions')?.addEventListener('click', () => this.requestPermissions()); document.getElementById('open-settings')?.addEventListener('click', () => this.openSettings()); document.getElementById('test-notification')?.addEventListener('click', () => this.testNotification()); document.getElementById('refresh-status')?.addEventListener('click', () => this.refreshStatus()); document.getElementById('battery-status')?.addEventListener('click', () => this.checkBatteryStatus()); document.getElementById('exact-alarm-status')?.addEventListener('click', () => this.checkExactAlarmStatus()); document.getElementById('reboot-recovery')?.addEventListener('click', () => this.checkRebootRecovery()); // Phase 4: TimeSafari component testing document.getElementById('test-security-manager')?.addEventListener('click', () => this.testSecurityManager()); document.getElementById('test-endorser-api-client')?.addEventListener('click', () => this.testEndorserAPIClient()); document.getElementById('test-notification-manager')?.addEventListener('click', () => this.testTimeSafariNotificationManager()); document.getElementById('test-phase4-integration')?.addEventListener('click', () => this.testPhase4Integration()); } private async initializeUI(): Promise { try { // Initialize UI components await this.permissionManager.updateStatus(); this.settingsPanel.render(); this.statusDashboard.render(); this.log('✅ Enhanced UI components initialized'); } catch (error) { this.log(`❌ UI initialization failed: ${error}`); this.errorDisplay.showError(error as Error); } } // Phase 4: Initialize TimeSafari components private async initializePhase4Components(): Promise { try { this.log('Initializing Phase 4 TimeSafari components...'); // Initialize SecurityManager with test DID const timeSafariUser = this.configLoader.getTimeSafariUser(); const securityInitialized = await this.securityManager.initialize(timeSafariUser.activeDid); if (securityInitialized) { this.log('✅ SecurityManager initialized successfully'); // Generate JWT token for API authentication const jwt = await this.securityManager.generateJWT({ scope: 'notifications', audience: 'endorser-api' }); if (jwt) { this.endorserAPIClient.setAuthToken(jwt); this.log('✅ JWT token generated and set for EndorserAPI'); } } else { this.log('❌ SecurityManager initialization failed'); } // Initialize TimeSafariNotificationManager const managerInitialized = await this.timeSafariNotificationManager.initialize(timeSafariUser); if (managerInitialized) { this.log('✅ TimeSafariNotificationManager initialized successfully'); } else { this.log('❌ TimeSafariNotificationManager initialization failed'); } this.log('Phase 4 components initialization completed'); } catch (error) { this.log('Error initializing Phase 4 components:', error); this.errorDisplay.showError(error as Error); } } // Enhanced UI methods private async checkPermissions(): Promise { try { this.log('Checking permissions...'); await this.permissionManager.updateStatus(); this.log('✅ Permission status updated'); } catch (error) { this.log(`❌ Permission check failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async requestPermissions(): Promise { try { this.log('Requesting permissions...'); this.permissionManager.showPermissionDialog(); this.log('✅ Permission dialog shown'); } catch (error) { this.log(`❌ Permission request failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async openSettings(): Promise { try { this.log('Opening settings...'); // Mock settings opening this.log('✅ Settings opened (mock)'); } catch (error) { this.log(`❌ Settings open failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async testNotification(): Promise { try { this.log('Sending test notification...'); // Mock test notification this.log('✅ Test notification sent (mock)'); } catch (error) { this.log(`❌ Test notification failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async refreshStatus(): Promise { try { this.log('Refreshing status...'); this.statusDashboard.render(); this.log('✅ Status refreshed'); } catch (error) { this.log(`❌ Status refresh failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async checkBatteryStatus(): Promise { try { this.log('Checking battery optimization status...'); // Mock battery status check this.log('✅ Battery optimization: Not optimized (mock)'); } catch (error) { this.log(`❌ Battery status check failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async checkExactAlarmStatus(): Promise { try { this.log('Checking exact alarm permission...'); // Mock exact alarm status this.log('✅ Exact alarm permission: Granted (mock)'); } catch (error) { this.log(`❌ Exact alarm check failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async checkRebootRecovery(): Promise { try { this.log('Checking reboot recovery status...'); // Mock reboot recovery status this.log('✅ Reboot recovery: Active (mock)'); } catch (error) { this.log(`❌ Reboot recovery check failed: ${error}`); this.errorDisplay.showError(error as Error); } } private async testConfigure() { try { this.log('Testing TimeSafari configuration...'); await this.configLoader.loadConfig(); const config = this.configLoader.getConfig(); await this.notificationService.initialize(); this.log('✅ TimeSafari configuration successful', { appId: config.timesafari.appId, appName: config.timesafari.appName, version: config.timesafari.version }); this.updateStatus('Configured'); } catch (error) { this.log(`❌ Configuration failed: ${error}`); } } private async testSchedule() { try { this.log('Testing TimeSafari community notification scheduling...'); const config = this.configLoader.getConfig(); const dualConfig = { contentFetch: { enabled: true, schedule: config.scheduling.contentFetch.schedule, url: this.configLoader.getEndorserUrl('notificationsBundle'), headers: this.configLoader.getAuthHeaders(), ttlSeconds: 3600, timeout: 30000, retryAttempts: 3, retryDelay: 5000, callbacks: { onSuccess: async (data: any) => { this.log('✅ Content fetch successful', data); await this.processEndorserNotificationBundle(data); }, onError: async (error: any) => { this.log('❌ Content fetch failed', error); } } }, userNotification: { enabled: true, schedule: config.scheduling.userNotification.schedule, title: 'TimeSafari Community Update', body: 'New offers, projects, people, and items await your attention!', sound: true, vibration: true, priority: 'high', actions: [ { id: 'view_offers', title: 'View Offers' }, { id: 'view_projects', title: 'See Projects' }, { id: 'view_people', title: 'Check People' }, { id: 'view_items', title: 'Browse Items' }, { id: 'dismiss', title: 'Dismiss' } ] }, relationship: { autoLink: true, contentTimeout: 300000, fallbackBehavior: 'show_default' } }; await this.notificationService.scheduleDualNotification(dualConfig); this.log('✅ Community notification scheduled successfully'); this.updateStatus('Scheduled'); } catch (error) { this.log(`❌ Scheduling failed: ${error}`); } } private async testEndorserAPI() { try { this.log('Testing Endorser.ch API integration...'); const config = this.configLoader.getConfig(); const testData = config.testData; // Test parallel API requests pattern const requests = [ // Offers to person fetch(`${this.configLoader.getEndorserUrl('offers')}?recipientId=${testData.userDid}&afterId=${testData.lastKnownOfferId}`, { headers: this.configLoader.getAuthHeaders() }), // Offers to user's projects fetch(`${this.configLoader.getEndorserUrl('offersToPlans')}?afterId=${testData.lastKnownOfferId}`, { headers: this.configLoader.getAuthHeaders() }), // Changes to starred projects fetch(this.configLoader.getEndorserUrl('plansLastUpdated'), { method: 'POST', headers: this.configLoader.getAuthHeaders(), body: JSON.stringify({ planIds: testData.starredPlanIds, afterId: testData.lastKnownPlanId }) }) ]; const [offersToPerson, offersToProjects, starredChanges] = await Promise.all(requests); const notificationData = { offersToPerson: await offersToPerson.json(), offersToProjects: await offersToProjects.json(), starredChanges: await starredChanges.json() }; this.log('✅ Endorser.ch API integration successful', { offersToPerson: notificationData.offersToPerson.data?.length || 0, offersToProjects: notificationData.offersToProjects.data?.length || 0, starredChanges: notificationData.starredChanges.data?.length || 0 }); this.updateStatus('API Connected'); } catch (error) { this.log(`❌ Endorser.ch API test failed: ${error}`); } } private async testCallbacks() { try { this.log('Testing TimeSafari notification callbacks...'); const config = this.configLoader.getConfig(); // Register offers callback await this.notificationService.registerCallback('offers', async (event: any) => { this.log('📨 Offers callback triggered', event); await this.handleOffersNotification(event); }); // Register projects callback await this.notificationService.registerCallback('projects', async (event: any) => { this.log('📨 Projects callback triggered', event); await this.handleProjectsNotification(event); }); // Register people callback await this.notificationService.registerCallback('people', async (event: any) => { this.log('📨 People callback triggered', event); await this.handlePeopleNotification(event); }); // Register items callback await this.notificationService.registerCallback('items', async (event: any) => { this.log('📨 Items callback triggered', event); await this.handleItemsNotification(event); }); this.log('✅ All callbacks registered successfully'); this.updateStatus('Callbacks Registered'); } catch (error) { this.log(`❌ Callback registration failed: ${error}`); } } private async testStatus() { try { this.log('Testing notification status...'); const status = await this.notificationService.getDualScheduleStatus(); this.log('📊 Notification Status:', status); this.updateStatus(`Status: ${status.contentFetch.enabled ? 'Active' : 'Inactive'}`); } catch (error) { this.log(`❌ Status check failed: ${error}`); } } private async testPerformance() { try { this.log('Testing Android performance metrics...'); const metrics = { overallScore: 85, databasePerformance: 90, memoryEfficiency: 80, batteryEfficiency: 85, objectPoolEfficiency: 90, totalDatabaseQueries: 150, averageMemoryUsage: 25.5, objectPoolHits: 45, backgroundCpuUsage: 2.3, totalNetworkRequests: 12, recommendations: ['Enable ETag support', 'Optimize memory usage'] }; this.log('📊 Android Performance Metrics:', metrics); this.updateStatus(`Performance: ${metrics.overallScore}/100`); } catch (error) { this.log(`❌ Performance check failed: ${error}`); } } /** * Process Endorser.ch notification bundle using parallel API requests */ private async processEndorserNotificationBundle(data: any): Promise { try { this.log('Processing Endorser.ch notification bundle...'); // Process each notification type if (data.offersToPerson?.data?.length > 0) { await this.handleOffersNotification(data.offersToPerson); } if (data.starredChanges?.data?.length > 0) { await this.handleProjectsNotification(data.starredChanges); } this.log('✅ Notification bundle processed successfully'); } catch (error) { this.log(`❌ Bundle processing failed: ${error}`); } } /** * Handle offers notification events from Endorser.ch API */ private async handleOffersNotification(event: any): Promise { this.log('Handling offers notification:', event); if (event.data && event.data.length > 0) { // Process OfferSummaryArrayMaybeMoreBody format event.data.forEach((offer: any) => { this.log('Processing offer:', { jwtId: offer.jwtId, handleId: offer.handleId, offeredByDid: offer.offeredByDid, recipientDid: offer.recipientDid, objectDescription: offer.objectDescription }); }); // Check if there are more offers to fetch if (event.hitLimit) { const lastOffer = event.data[event.data.length - 1]; this.log('More offers available, last JWT ID:', lastOffer.jwtId); } } } /** * Handle projects notification events from Endorser.ch API */ private async handleProjectsNotification(event: any): Promise { this.log('Handling projects notification:', event); if (event.data && event.data.length > 0) { // Process PlanSummaryAndPreviousClaimArrayMaybeMore format event.data.forEach((planData: any) => { const { plan, wrappedClaimBefore } = planData; this.log('Processing project change:', { jwtId: plan.jwtId, handleId: plan.handleId, name: plan.name, issuerDid: plan.issuerDid, hasPreviousClaim: !!wrappedClaimBefore }); }); // Check if there are more project changes to fetch if (event.hitLimit) { const lastPlan = event.data[event.data.length - 1]; this.log('More project changes available, last JWT ID:', lastPlan.plan.jwtId); } } } /** * Handle people notification events */ private async handlePeopleNotification(event: any): Promise { this.log('Handling people notification:', event); // Implementation would process people data and update local state } /** * Handle items notification events */ private async handleItemsNotification(event: any): Promise { this.log('Handling items notification:', event); // Implementation would process items data and update local state } private log(message: string, data?: any) { const timestamp = new Date().toLocaleTimeString(); const logEntry = document.createElement('div'); logEntry.innerHTML = `[${timestamp}] ${message}`; if (data) { logEntry.innerHTML += `
${JSON.stringify(data, null, 2)}
`; } this.logElement.appendChild(logEntry); this.logElement.scrollTop = this.logElement.scrollHeight; } private clearLog() { this.logElement.innerHTML = ''; this.log('Log cleared'); } private updateStatus(status: string) { this.statusElement.textContent = status; } // Phase 4: TimeSafari component test methods private async testSecurityManager(): Promise { try { this.log('🔐 Testing SecurityManager...'); const timeSafariUser = this.configLoader.getTimeSafariUser(); // Test JWT generation const jwt = await this.securityManager.generateJWT({ scope: 'test', audience: 'test-api' }); if (jwt) { this.log('✅ JWT generation successful'); this.log('JWT token:', jwt.substring(0, 50) + '...'); // Test JWT verification const claims = await this.securityManager.verifyJWT(jwt); if (claims) { this.log('✅ JWT verification successful'); this.log('Claims:', claims); } else { this.log('❌ JWT verification failed'); } } else { this.log('❌ JWT generation failed'); } // Test operation history const history = this.securityManager.getOperationHistory(); this.log(`Security operations performed: ${history.length}`); this.log('SecurityManager test completed'); } catch (error) { this.log('SecurityManager test failed:', error); this.errorDisplay.showError(error as Error); } } private async testEndorserAPIClient(): Promise { try { this.log('🌐 Testing EndorserAPIClient...'); const timeSafariUser = this.configLoader.getTimeSafariUser(); // Test offers to person this.log('Testing offers to person...'); const offersResponse = await this.endorserAPIClient.fetchOffersToPerson( timeSafariUser.activeDid, timeSafariUser.lastKnownOfferId, undefined ); this.log(`✅ Offers fetched: ${offersResponse.data.length} offers`); if (offersResponse.data.length > 0) { this.log('Sample offer:', offersResponse.data[0]); } // Test offers to projects this.log('Testing offers to projects...'); const projectsResponse = await this.endorserAPIClient.fetchOffersToProjectsOwnedByMe( timeSafariUser.lastKnownOfferId ); this.log(`✅ Project offers fetched: ${projectsResponse.data.length} offers`); // Test project updates if (timeSafariUser.starredPlanIds && timeSafariUser.starredPlanIds.length > 0) { this.log('Testing project updates...'); const updatesResponse = await this.endorserAPIClient.fetchProjectsLastUpdated( timeSafariUser.starredPlanIds, timeSafariUser.lastKnownPlanId, undefined ); this.log(`✅ Project updates fetched: ${updatesResponse.data.length} updates`); } this.log('EndorserAPIClient test completed'); } catch (error) { this.log('EndorserAPIClient test failed:', error); this.errorDisplay.showError(error as Error); } } private async testTimeSafariNotificationManager(): Promise { try { this.log('📱 Testing TimeSafariNotificationManager...'); // Test notification generation this.log('Generating TimeSafari notifications...'); const notifications = await this.timeSafariNotificationManager.generateNotifications({ forceFetch: false, includeMetadata: true, filterByPriority: true, maxNotifications: 10, cacheTtl: 300000 }); this.log(`✅ Generated ${notifications.length} notifications`); // Display notification details notifications.forEach((notification, index) => { this.log(`Notification ${index + 1}:`, { type: notification.type, subtype: notification.subtype, priority: notification.notificationPriority, timestamp: new Date(notification.timestamp).toISOString(), disabled: notification.disabled, sound: notification.sound, vibration: notification.vibration }); }); // Test statistics const stats = this.timeSafariNotificationManager.getStatistics(); this.log('Manager statistics:', stats); this.log('TimeSafariNotificationManager test completed'); } catch (error) { this.log('TimeSafariNotificationManager test failed:', error); this.errorDisplay.showError(error as Error); } } private async testPhase4Integration(): Promise { try { this.log('🚀 Testing complete Phase 4 integration...'); const timeSafariUser = this.configLoader.getTimeSafariUser(); // Test complete workflow this.log('Step 1: Security authentication...'); const jwt = await this.securityManager.generateJWT({ scope: 'notifications', audience: 'endorser-api' }); if (!jwt) { throw new Error('JWT generation failed'); } this.endorserAPIClient.setAuthToken(jwt); this.log('✅ Authentication successful'); this.log('Step 2: Fetching TimeSafari data...'); const bundle = await this.endorserAPIClient.fetchAllTimeSafariNotifications({ activeDid: timeSafariUser.activeDid, starredPlanIds: timeSafariUser.starredPlanIds || [], lastKnownOfferId: timeSafariUser.lastKnownOfferId, lastKnownPlanId: timeSafariUser.lastKnownPlanId, fetchOffersToPerson: true, fetchOffersToProjects: true, fetchProjectUpdates: true, notificationPreferences: { offers: true, projects: true, people: false, items: true } }); this.log(`✅ Data fetched successfully: ${bundle.success}`); this.log('Bundle metadata:', bundle.metadata); this.log('Step 3: Generating notifications...'); const notifications = await this.timeSafariNotificationManager.generateNotifications({ forceFetch: true, includeMetadata: true, maxNotifications: 20 }); this.log(`✅ Generated ${notifications.length} notifications`); // Summary this.log('🎉 Phase 4 integration test completed successfully!'); this.log('Summary:', { securityInitialized: this.securityManager.isInitialized(), apiClientReady: !!this.endorserAPIClient, managerInitialized: this.timeSafariNotificationManager.isInitialized(), notificationsGenerated: notifications.length, bundleSuccess: bundle.success }); } catch (error) { this.log('Phase 4 integration test failed:', error); this.errorDisplay.showError(error as Error); } } } // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', () => { new TimeSafariAndroidTestApp(); });