From 54478b1c97e55c5d57d257db3e274da0da759d03 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 15 Oct 2025 09:07:30 +0000 Subject: [PATCH] fix(android-test): remove old index.ts that was blocking Vue 3 app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 🐛 Root Cause - Old `index.ts` file was creating static interface with Permission Management - This file was loading instead of Vue 3 app (`main.ts`) - Caused old interface to display without header navigation ## ✅ Solution - Deleted `test-apps/android-test/src/index.ts` - Re-registered DailyNotification plugin in capacitor.plugins.json - Rebuilt and reinstalled app ## 🎯 Expected Result Vue 3 app should now load with: - ✅ Header navigation bar (Home, Schedule, Notifications, Status, History, Logs, Settings) - ✅ Modern gradient background - ✅ Router-based navigation - ✅ Copy to clipboard in LogsView --- test-apps/android-test/src/index.ts | 1495 --------------------------- 1 file changed, 1495 deletions(-) delete mode 100644 test-apps/android-test/src/index.ts diff --git a/test-apps/android-test/src/index.ts b/test-apps/android-test/src/index.ts deleted file mode 100644 index de798db..0000000 --- a/test-apps/android-test/src/index.ts +++ /dev/null @@ -1,1495 +0,0 @@ -// import { Capacitor } from '@capacitor/core'; -// import { DailyNotificationPlugin } from '@timesafari/daily-notification-plugin'; - -// Phase 4: Import TimeSafari components -import { EndorserAPIClient } from '../shared/typescript/EndorserAPIClient'; -import { SecurityManager } from '../shared/typescript/SecurityManager'; -import { TimeSafariNotificationManager } from '../shared/typescript/TimeSafariNotificationManager'; -// import { -// TimeSafariUser, -// TimeSafariPreferences, -// EnhancedTimeSafariNotification, -// TimeSafariNotificationType -// } from '../../../src/definitions'; - -// Generic Polling Interface -import { - GenericPollingRequest, - PollingScheduleConfig, - PollingResult, - StarredProjectsRequest, - StarredProjectsResponse, - calculateBackoffDelay, - createDefaultOutboxPressureManager -} from '../../../packages/polling-contracts/src'; - -// Enhanced ConfigLoader for Phase 4 -class ConfigLoader { - private static instance: ConfigLoader; - private config: Record; - - 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(): Record { - 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(): Record { - 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(): Record { - 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(): Record { - return { - 'Authorization': `Bearer ${this.config.endorser.apiKey}`, - 'Content-Type': 'application/json' - }; - } -} - -class MockDailyNotificationService { - constructor(config: Record) { - this.config = config; - } - - private config: Record; - - async initialize(): Promise { - // console.log('Mock notification service initialized'); - } - - async scheduleDualNotification(_config: Record): Promise { - // console.log('Mock dual notification scheduled:', config); - } - - async registerCallback(_name: string, _callback: (...args: unknown[]) => void): Promise { - // console.log(`Mock callback registered: ${name}`); - } - - async getDualScheduleStatus(): Promise> { - return { - contentFetch: { enabled: true }, - userNotification: { enabled: true } - }; - } -} - -class TestLogger { - constructor(level: string) { - // eslint-disable-next-line no-console - console.log('Mock logger initialized with level:', level); - } - - info(message: string, data?: Record): void { - // eslint-disable-next-line no-console - console.log(`[INFO] ${message}`, data); - } - - error(message: string, data?: Record): void { - // eslint-disable-next-line no-console - console.error(`[ERROR] ${message}`, data); - } - - debug(message: string, data?: Record): void { - // eslint-disable-next-line no-console - 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: Record): 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() { - const statusElement = document.getElementById('status'); - const logElement = document.getElementById('log'); - if (!statusElement || !logElement) { - throw new Error('Required DOM elements not found'); - } - this.statusElement = statusElement; - this.logElement = logElement; - 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 - const permissionStatusContainer = document.getElementById('permission-status-container'); - const permissionDialogContainer = document.getElementById('permission-dialog-container'); - const settingsContainer = document.getElementById('settings-container'); - const statusContainer = document.getElementById('status-container'); - const errorContainer = document.getElementById('error-container'); - - if (!permissionStatusContainer || !permissionDialogContainer || !settingsContainer || !statusContainer || !errorContainer) { - throw new Error('Required UI containers not found'); - } - - this.permissionManager = new PermissionManager( - permissionStatusContainer, - permissionDialogContainer - ); - this.settingsPanel = new SettingsPanel(settingsContainer); - this.statusDashboard = new StatusDashboard(statusContainer); - this.errorDisplay = new ErrorDisplay(errorContainer); - - this.setupEventListeners(); - this.initializeUI(); - this.initializePhase4Components(); - this.log('TimeSafari Android Test app initialized with Phase 4 components'); - } - - private setupEventListeners(): void { - // 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()); - - // Generic Polling Interface testing - document.getElementById('test-generic-polling')?.addEventListener('click', () => this.testGenericPolling()); - document.getElementById('test-polling-schedule')?.addEventListener('click', () => this.testPollingSchedule()); - document.getElementById('test-polling-results')?.addEventListener('click', () => this.testPollingResults()); - - // Static Daily Reminder event listeners - document.getElementById('schedule-reminder')?.addEventListener('click', () => this.scheduleDailyReminder()); - document.getElementById('cancel-reminder')?.addEventListener('click', () => this.cancelDailyReminder()); - document.getElementById('get-reminders')?.addEventListener('click', () => this.getScheduledReminders()); - document.getElementById('update-reminder')?.addEventListener('click', () => this.updateDailyReminder()); - } - - 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(): Promise { - 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(): Promise { - 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: Record): Promise => { - this.log('✅ Content fetch successful', data); - await this.processEndorserNotificationBundle(data); - }, - onError: async (error: Record): Promise => { - 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(): Promise { - 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(): Promise { - try { - this.log('Testing TimeSafari notification callbacks...'); - // const config = this.configLoader.getConfig(); - - // Register offers callback - await this.notificationService.registerCallback('offers', async (event: Record) => { - this.log('📨 Offers callback triggered', event); - await this.handleOffersNotification(event); - }); - - // Register projects callback - await this.notificationService.registerCallback('projects', async (event: Record) => { - this.log('📨 Projects callback triggered', event); - await this.handleProjectsNotification(event); - }); - - // Register people callback - await this.notificationService.registerCallback('people', async (event: Record) => { - this.log('📨 People callback triggered', event); - await this.handlePeopleNotification(event); - }); - - // Register items callback - await this.notificationService.registerCallback('items', async (event: Record) => { - 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(): Promise { - 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(): Promise { - 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: Record): 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: Record): Promise { - this.log('Handling offers notification:', event); - - if (event.data && event.data.length > 0) { - // Process OfferSummaryArrayMaybeMoreBody format - event.data.forEach((offer: Record) => { - 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: Record): Promise { - this.log('Handling projects notification:', event); - - if (event.data && event.data.length > 0) { - // Process PlanSummaryAndPreviousClaimArrayMaybeMore format - event.data.forEach((planData: Record) => { - 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: Record): Promise { - this.log('Handling people notification:', event); - // Implementation would process people data and update local state - } - - /** - * Handle items notification events - */ - private async handleItemsNotification(event: Record): Promise { - this.log('Handling items notification:', event); - // Implementation would process items data and update local state - } - - private log(message: string, data?: Record): void { - 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(): void { - this.logElement.innerHTML = ''; - this.log('Log cleared'); - } - - private updateStatus(status: string): void { - 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); - } - } - - // Generic Polling Interface Test Methods - private async testGenericPolling(): Promise { - try { - this.log('🔄 Testing Generic Polling Interface (Android)...'); - - // Create a starred projects polling request - const starredProjectsRequest: GenericPollingRequest = { - endpoint: '/api/v2/report/plansLastUpdatedBetween', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'TimeSafari-DailyNotificationPlugin/1.0.0', - 'Authorization': `Bearer ${await this.getJwtToken()}` - }, - body: { - planIds: ['plan:test-1', 'plan:test-2'], - afterId: '1704067200_abc123_12345678', - limit: 100 - }, - responseSchema: { - validate: (data: unknown): data is StarredProjectsResponse => { - return data && - Array.isArray(data.data) && - typeof data.hitLimit === 'boolean' && - data.pagination && - typeof data.pagination.hasMore === 'boolean'; - }, - transformError: (error: unknown) => ({ - code: 'VALIDATION_ERROR', - message: error.message || 'Validation failed', - retryable: false - }) - }, - retryConfig: { - maxAttempts: 3, - backoffStrategy: 'exponential', - baseDelayMs: 1000 - }, - timeoutMs: 30000 - }; - - this.log('✅ Generic polling request created successfully'); - this.log('Request details:', { - endpoint: starredProjectsRequest.endpoint, - method: starredProjectsRequest.method, - planIds: starredProjectsRequest.body.planIds, - afterId: starredProjectsRequest.body.afterId - }); - - // Test backoff calculation - const backoffDelay = calculateBackoffDelay(1, starredProjectsRequest.retryConfig); - this.log(`✅ Backoff delay calculated: ${backoffDelay}ms`); - - // Test outbox pressure manager - const pressureManager = createDefaultOutboxPressureManager(); - const pressureStatus = await pressureManager.checkStoragePressure(50); - this.log('✅ Outbox pressure check:', pressureStatus); - - this.log('Generic Polling Interface test completed successfully'); - - } catch (error) { - this.log('Generic Polling Interface test failed:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async testPollingSchedule(): Promise { - try { - this.log('📅 Testing Polling Schedule Configuration (Android)...'); - - const timeSafariUser = this.configLoader.getTimeSafariUser(); - - // Create schedule configuration - const scheduleConfig: PollingScheduleConfig = { - request: { - endpoint: '/api/v2/report/plansLastUpdatedBetween', - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'TimeSafari-DailyNotificationPlugin/1.0.0', - 'Authorization': `Bearer ${await this.getJwtToken()}` - }, - body: { - planIds: timeSafariUser.starredPlanIds || [], - afterId: timeSafariUser.lastKnownPlanId, - limit: 100 - }, - responseSchema: { - validate: (data: unknown): data is StarredProjectsResponse => { - return data && Array.isArray(data.data); - }, - transformError: (error: unknown) => ({ - code: 'VALIDATION_ERROR', - message: error.message, - retryable: false - }) - }, - retryConfig: { - maxAttempts: 3, - backoffStrategy: 'exponential', - baseDelayMs: 1000 - }, - timeoutMs: 30000 - }, - schedule: { - cronExpression: '0 10,16 * * *', // 10 AM and 4 PM daily - timezone: 'UTC', - maxConcurrentPolls: 1 - }, - notificationConfig: { - enabled: true, - templates: { - singleUpdate: '{projectName} has been updated', - multipleUpdates: 'You have {count} new updates in your starred projects' - }, - groupingRules: { - maxGroupSize: 5, - timeWindowMinutes: 5 - } - }, - stateConfig: { - watermarkKey: 'lastAckedStarredPlanChangesJwtId', - storageAdapter: 'timesafari' - } - }; - - this.log('✅ Polling schedule configuration created successfully'); - this.log('Schedule details:', { - cronExpression: scheduleConfig.schedule.cronExpression, - timezone: scheduleConfig.schedule.timezone, - maxConcurrentPolls: scheduleConfig.schedule.maxConcurrentPolls, - watermarkKey: scheduleConfig.stateConfig.watermarkKey - }); - - // Mock scheduling the poll - this.log('📅 Scheduling polling (mock)...'); - const scheduleId = `android-poll-${Date.now()}`; - this.log(`✅ Polling scheduled with ID: ${scheduleId}`); - - this.log('Polling Schedule test completed successfully'); - - } catch (error) { - this.log('Polling Schedule test failed:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async testPollingResults(): Promise { - try { - this.log('📊 Testing Polling Results Handling (Android)...'); - - // Mock polling result - const mockResult: PollingResult = { - success: true, - data: { - data: [ - { - planSummary: { - jwtId: '1704153600_mno345_87654321', - handleId: 'test-project-1', - name: 'Test Project 1', - issuerDid: 'did:example:test-issuer', - locLat: 40.7128, - locLon: -74.0060, - url: 'https://test-project-1.com', - version: '1.0.0' - }, - previousClaim: { - jwtId: '1704067200_abc123_12345678', - claimType: 'project_update', - claimData: { - status: 'in_progress', - progress: 0.75 - }, - metadata: { - createdAt: '2025-01-01T10:00:00Z', - updatedAt: '2025-01-01T12:00:00Z' - } - } - } - ], - hitLimit: false, - pagination: { - hasMore: false, - nextAfterId: null - } - }, - metadata: { - requestId: 'req-android-test-123', - timestamp: new Date().toISOString(), - duration: 1250, - attempt: 1 - } - }; - - this.log('✅ Mock polling result created'); - this.log('Result details:', { - success: mockResult.success, - dataCount: mockResult.data?.data.length || 0, - hitLimit: mockResult.data?.hitLimit, - hasMore: mockResult.data?.pagination.hasMore, - duration: mockResult.metadata?.duration - }); - - // Test result processing - if (mockResult.success && mockResult.data) { - const changes = mockResult.data.data; - - if (changes.length > 0) { - this.log('📝 Processing polling results...'); - - // Generate notifications - this.log(`✅ Generated notifications for ${changes.length} changes`); - - // Update watermark with CAS - const latestJwtId = changes[changes.length - 1].planSummary.jwtId; - this.log(`✅ Updated watermark to: ${latestJwtId}`); - - // Acknowledge changes with server - const jwtIds = changes.map(c => c.planSummary.jwtId); - this.log(`✅ Acknowledged ${jwtIds.length} changes with server`); - } - } - - this.log('Polling Results test completed successfully'); - - } catch (error) { - this.log('Polling Results test failed:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async getJwtToken(): Promise { - // Mock JWT token generation - return 'mock-jwt-token-android-test'; - } - - // Static Daily Reminder Methods - private async scheduleDailyReminder(): Promise { - try { - this.log('Scheduling daily reminder...'); - - const reminderOptions = { - id: (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin', - title: (document.getElementById('reminder-title') as HTMLInputElement)?.value || 'Good Morning!', - body: (document.getElementById('reminder-body') as HTMLInputElement)?.value || 'Time to check your TimeSafari community updates', - time: (document.getElementById('reminder-time') as HTMLInputElement)?.value || '09:00', - sound: (document.getElementById('reminder-sound') as HTMLInputElement)?.checked ?? true, - vibration: (document.getElementById('reminder-vibration') as HTMLInputElement)?.checked ?? true, - priority: (document.getElementById('reminder-priority') as HTMLSelectElement)?.value || 'normal', - repeatDaily: (document.getElementById('reminder-repeat') as HTMLInputElement)?.checked ?? true - }; - - this.log('Reminder options:', reminderOptions); - - await this.plugin.scheduleDailyReminder(reminderOptions); - this.log('✅ Daily reminder scheduled successfully'); - this.errorDisplay.showSuccess('Daily reminder scheduled successfully!'); - - } catch (error) { - this.log('❌ Failed to schedule daily reminder:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async cancelDailyReminder(): Promise { - try { - this.log('Cancelling daily reminder...'); - - const reminderId = (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin'; - - await this.plugin.cancelDailyReminder(reminderId); - this.log('✅ Daily reminder cancelled successfully'); - this.errorDisplay.showSuccess('Daily reminder cancelled successfully!'); - - } catch (error) { - this.log('❌ Failed to cancel daily reminder:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async getScheduledReminders(): Promise { - try { - this.log('Getting scheduled reminders...'); - - const result = await this.plugin.getScheduledReminders(); - this.log('✅ Scheduled reminders retrieved:', result); - - if (result.reminders && result.reminders.length > 0) { - this.errorDisplay.showSuccess(`Found ${result.reminders.length} scheduled reminders`); - // eslint-disable-next-line no-console - console.table(result.reminders); - } else { - this.errorDisplay.showInfo('No scheduled reminders found'); - } - - } catch (error) { - this.log('❌ Failed to get scheduled reminders:', error); - this.errorDisplay.showError(error as Error); - } - } - - private async updateDailyReminder(): Promise { - try { - this.log('Updating daily reminder...'); - - const reminderId = (document.getElementById('reminder-id') as HTMLInputElement)?.value || 'morning_checkin'; - - const updateOptions = { - title: (document.getElementById('reminder-title') as HTMLInputElement)?.value, - body: (document.getElementById('reminder-body') as HTMLInputElement)?.value, - time: (document.getElementById('reminder-time') as HTMLInputElement)?.value, - sound: (document.getElementById('reminder-sound') as HTMLInputElement)?.checked, - vibration: (document.getElementById('reminder-vibration') as HTMLInputElement)?.checked, - priority: (document.getElementById('reminder-priority') as HTMLSelectElement)?.value, - repeatDaily: (document.getElementById('reminder-repeat') as HTMLInputElement)?.checked - }; - - this.log('Update options:', updateOptions); - - await this.plugin.updateDailyReminder(reminderId, updateOptions); - this.log('✅ Daily reminder updated successfully'); - this.errorDisplay.showSuccess('Daily reminder updated successfully!'); - - } catch (error) { - this.log('❌ Failed to update daily reminder:', error); - this.errorDisplay.showError(error as Error); - } - } -} - -// Initialize app when DOM is ready -document.addEventListener('DOMContentLoaded', () => { - new TimeSafariAndroidTestApp(); -});