diff --git a/test-apps/daily-notification-test/src/lib/diagnostics-export.ts b/test-apps/daily-notification-test/src/lib/diagnostics-export.ts new file mode 100644 index 0000000..85823d8 --- /dev/null +++ b/test-apps/daily-notification-test/src/lib/diagnostics-export.ts @@ -0,0 +1,318 @@ +/** + * Diagnostics Export Utility + * + * Comprehensive diagnostics collection and export for the DailyNotification plugin + * Provides detailed system information for debugging and support + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { + PermissionStatus, + NotificationStatus, + ExactAlarmStatus +} from './bridge' + +export interface ComprehensiveDiagnostics { + // Basic app information + appVersion: string + platform: string + apiLevel: string + timezone: string + lastUpdated: string + + // Core status matrix fields + postNotificationsGranted: boolean + exactAlarmGranted: boolean + channelEnabled: boolean + batteryOptimizationsIgnored: boolean + canScheduleNow: boolean + lastError?: string + + // Detailed capabilities + capabilities: { + notificationStatus: NotificationStatus + permissions: PermissionStatus + exactAlarmStatus: ExactAlarmStatus + + // Browser/WebView information + userAgent: string + language: string + platform: string + cookieEnabled: boolean + onLine: boolean + hardwareConcurrency: number + maxTouchPoints: number + + // Timing information + timestamp: number + timezoneOffset: number + + // Plugin availability + pluginAvailable: boolean + + // Error context + errorContext: { + hasError: boolean + errorMessage: string | null + lastCheckTime: string + } + + // Performance metrics + performanceMetrics: { + loadTime: number + memoryUsage?: number + connectionType?: string + } + } + + // System information + systemInfo: { + screenResolution: string + colorDepth: number + pixelRatio: number + viewportSize: string + devicePixelRatio: number + } + + // Network information + networkInfo: { + connectionType: string + effectiveType?: string + downlink?: number + rtt?: number + } + + // Storage information + storageInfo: { + localStorageAvailable: boolean + sessionStorageAvailable: boolean + indexedDBAvailable: boolean + webSQLAvailable: boolean + } +} + +export class DiagnosticsExporter { + + /** + * Collect comprehensive diagnostics + */ + async collectDiagnostics( + notificationStatus: NotificationStatus, + permissions: PermissionStatus, + exactAlarmStatus: ExactAlarmStatus, + pluginAvailable: boolean + ): Promise { + + const startTime = performance.now() + + // Collect system information + const systemInfo = this.collectSystemInfo() + + // Collect network information + const networkInfo = this.collectNetworkInfo() + + // Collect storage information + const storageInfo = this.collectStorageInfo() + + // Calculate performance metrics + const loadTime = performance.now() - startTime + + return { + appVersion: '1.0.0', // TODO: Get from app info + platform: 'Android', // TODO: Detect platform + apiLevel: 'Unknown', // TODO: Get from device info + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + lastUpdated: new Date().toLocaleString(), + postNotificationsGranted: permissions.notifications === 'granted', + exactAlarmGranted: exactAlarmStatus.enabled, + channelEnabled: notificationStatus.isEnabled, + batteryOptimizationsIgnored: false, // TODO: Check battery optimization status + canScheduleNow: notificationStatus.isEnabled && permissions.notifications === 'granted', + lastError: notificationStatus.error, + capabilities: { + notificationStatus, + permissions, + exactAlarmStatus, + userAgent: navigator.userAgent, + language: navigator.language, + platform: navigator.platform, + cookieEnabled: navigator.cookieEnabled, + onLine: navigator.onLine, + hardwareConcurrency: navigator.hardwareConcurrency || 0, + maxTouchPoints: navigator.maxTouchPoints || 0, + timestamp: Date.now(), + timezoneOffset: new Date().getTimezoneOffset(), + pluginAvailable, + errorContext: { + hasError: !!notificationStatus.error, + errorMessage: notificationStatus.error || null, + lastCheckTime: new Date().toISOString() + }, + performanceMetrics: { + loadTime, + memoryUsage: this.getMemoryUsage(), + connectionType: networkInfo.connectionType + } + }, + systemInfo, + networkInfo, + storageInfo + } + } + + /** + * Collect system information + */ + private collectSystemInfo() { + return { + screenResolution: `${screen.width}x${screen.height}`, + colorDepth: screen.colorDepth, + pixelRatio: window.devicePixelRatio || 1, + viewportSize: `${window.innerWidth}x${window.innerHeight}`, + devicePixelRatio: window.devicePixelRatio || 1 + } + } + + /** + * Collect network information + */ + private collectNetworkInfo() { + const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection + + return { + connectionType: connection?.type || 'unknown', + effectiveType: connection?.effectiveType, + downlink: connection?.downlink, + rtt: connection?.rtt + } + } + + /** + * Collect storage information + */ + private collectStorageInfo() { + return { + localStorageAvailable: this.isStorageAvailable('localStorage'), + sessionStorageAvailable: this.isStorageAvailable('sessionStorage'), + indexedDBAvailable: this.isStorageAvailable('indexedDB'), + webSQLAvailable: this.isStorageAvailable('openDatabase') + } + } + + /** + * Check if storage is available + */ + private isStorageAvailable(type: string): boolean { + try { + const storage = (window as any)[type] + if (!storage) return false + + if (type === 'indexedDB') { + return !!storage + } + + if (type === 'openDatabase') { + return !!storage + } + + // Test localStorage/sessionStorage + const test = '__storage_test__' + storage.setItem(test, test) + storage.removeItem(test) + return true + } catch { + return false + } + } + + /** + * Get memory usage (if available) + */ + private getMemoryUsage(): number | undefined { + const memory = (performance as any).memory + return memory ? memory.usedJSHeapSize : undefined + } + + /** + * Export diagnostics as JSON string + */ + exportAsJSON(diagnostics: ComprehensiveDiagnostics): string { + return JSON.stringify(diagnostics, null, 2) + } + + /** + * Export diagnostics as CSV + */ + exportAsCSV(diagnostics: ComprehensiveDiagnostics): string { + const rows = [ + ['Field', 'Value'], + ['App Version', diagnostics.appVersion], + ['Platform', diagnostics.platform], + ['API Level', diagnostics.apiLevel], + ['Timezone', diagnostics.timezone], + ['Last Updated', diagnostics.lastUpdated], + ['Post Notifications Granted', diagnostics.postNotificationsGranted.toString()], + ['Exact Alarm Granted', diagnostics.exactAlarmGranted.toString()], + ['Channel Enabled', diagnostics.channelEnabled.toString()], + ['Battery Optimizations Ignored', diagnostics.batteryOptimizationsIgnored.toString()], + ['Can Schedule Now', diagnostics.canScheduleNow.toString()], + ['Last Error', diagnostics.lastError || 'None'], + ['Plugin Available', diagnostics.capabilities.pluginAvailable.toString()], + ['User Agent', diagnostics.capabilities.userAgent], + ['Language', diagnostics.capabilities.language], + ['Platform', diagnostics.capabilities.platform], + ['Online', diagnostics.capabilities.onLine.toString()], + ['Hardware Concurrency', diagnostics.capabilities.hardwareConcurrency.toString()], + ['Max Touch Points', diagnostics.capabilities.maxTouchPoints.toString()], + ['Screen Resolution', diagnostics.systemInfo.screenResolution], + ['Color Depth', diagnostics.systemInfo.colorDepth.toString()], + ['Pixel Ratio', diagnostics.systemInfo.pixelRatio.toString()], + ['Viewport Size', diagnostics.systemInfo.viewportSize], + ['Connection Type', diagnostics.networkInfo.connectionType], + ['Local Storage Available', diagnostics.storageInfo.localStorageAvailable.toString()], + ['Session Storage Available', diagnostics.storageInfo.sessionStorageAvailable.toString()], + ['IndexedDB Available', diagnostics.storageInfo.indexedDBAvailable.toString()], + ['WebSQL Available', diagnostics.storageInfo.webSQLAvailable.toString()] + ] + + return rows.map(row => row.map(cell => `"${cell}"`).join(',')).join('\n') + } + + /** + * Copy diagnostics to clipboard + */ + async copyToClipboard(diagnostics: ComprehensiveDiagnostics, format: 'json' | 'csv' = 'json'): Promise { + const content = format === 'json' + ? this.exportAsJSON(diagnostics) + : this.exportAsCSV(diagnostics) + + await navigator.clipboard.writeText(content) + } +} + +// Singleton instance +export const diagnosticsExporter = new DiagnosticsExporter() + +// Utility functions +export async function collectDiagnostics( + notificationStatus: NotificationStatus, + permissions: PermissionStatus, + exactAlarmStatus: ExactAlarmStatus, + pluginAvailable: boolean +): Promise { + return diagnosticsExporter.collectDiagnostics(notificationStatus, permissions, exactAlarmStatus, pluginAvailable) +} + +export function exportDiagnosticsAsJSON(diagnostics: ComprehensiveDiagnostics): string { + return diagnosticsExporter.exportAsJSON(diagnostics) +} + +export function exportDiagnosticsAsCSV(diagnostics: ComprehensiveDiagnostics): string { + return diagnosticsExporter.exportAsCSV(diagnostics) +} + +export async function copyDiagnosticsToClipboard(diagnostics: ComprehensiveDiagnostics, format: 'json' | 'csv' = 'json'): Promise { + return diagnosticsExporter.copyToClipboard(diagnostics, format) +} diff --git a/test-apps/daily-notification-test/src/views/StatusView.vue b/test-apps/daily-notification-test/src/views/StatusView.vue index f64f2eb..c76fea1 100644 --- a/test-apps/daily-notification-test/src/views/StatusView.vue +++ b/test-apps/daily-notification-test/src/views/StatusView.vue @@ -89,6 +89,7 @@ import { Vue, Component, toNative } from 'vue-facing-decorator' import StatusCard from '../components/cards/StatusCard.vue' import { createTypedPlugin, type PermissionStatus, type NotificationStatus, type ExactAlarmStatus } from '../lib/typed-plugin' +import { collectDiagnostics, copyDiagnosticsToClipboard, type ComprehensiveDiagnostics } from '../lib/diagnostics-export' interface StatusItem { key: string @@ -102,20 +103,8 @@ interface StatusItem { } } -interface Diagnostics { - appVersion: string - platform: string - apiLevel: string - timezone: string - lastUpdated: string - postNotificationsGranted: boolean - exactAlarmGranted: boolean - channelEnabled: boolean - batteryOptimizationsIgnored: boolean - canScheduleNow: boolean - lastError?: string - capabilities: Record -} +// Use ComprehensiveDiagnostics from diagnostics-export +type Diagnostics = ComprehensiveDiagnostics @Component({ components: { @@ -136,7 +125,40 @@ class StatusView extends Vue { channelEnabled: false, batteryOptimizationsIgnored: false, canScheduleNow: false, - capabilities: {} + lastError: undefined, + capabilities: { + notificationStatus: { isEnabled: false, isScheduled: false, pending: false }, + permissions: { notifications: 'denied', notificationsEnabled: false }, + exactAlarmStatus: { enabled: false, supported: false }, + userAgent: '', + language: '', + platform: '', + cookieEnabled: false, + onLine: false, + hardwareConcurrency: 0, + maxTouchPoints: 0, + timestamp: 0, + timezoneOffset: 0, + pluginAvailable: false, + errorContext: { hasError: false, errorMessage: null, lastCheckTime: '' }, + performanceMetrics: { loadTime: 0 } + }, + systemInfo: { + screenResolution: '0x0', + colorDepth: 0, + pixelRatio: 1, + viewportSize: '0x0', + devicePixelRatio: 1 + }, + networkInfo: { + connectionType: 'unknown' + }, + storageInfo: { + localStorageAvailable: false, + sessionStorageAvailable: false, + indexedDBAvailable: false, + webSQLAvailable: false + } } get statusItems(): StatusItem[] { @@ -228,25 +250,13 @@ class StatusView extends Vue { typedPlugin.getExactAlarmStatus() ]) - // Update diagnostics - this.diagnostics = { - appVersion: '1.0.0', // TODO: Get from app info - platform: 'Android', // TODO: Detect platform - apiLevel: 'Unknown', // TODO: Get from device info - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - lastUpdated: new Date().toLocaleString(), - postNotificationsGranted: permissions.notifications === 'granted', - exactAlarmGranted: exactAlarmStatus.enabled, - channelEnabled: notificationStatus.isEnabled, - batteryOptimizationsIgnored: false, // TODO: Check battery optimization status - canScheduleNow: notificationStatus.isEnabled && permissions.notifications === 'granted', - lastError: notificationStatus.error, - capabilities: { - notificationStatus, - permissions, - exactAlarmStatus - } - } + // Collect comprehensive diagnostics + this.diagnostics = await collectDiagnostics( + notificationStatus, + permissions, + exactAlarmStatus, + !!typedPlugin + ) console.log('✅ Status matrix refreshed successfully') @@ -299,8 +309,8 @@ class StatusView extends Vue { async exportDiagnostics() { try { - await navigator.clipboard.writeText(this.diagnosticsJson) - console.log('📋 Diagnostics copied to clipboard') + await copyDiagnosticsToClipboard(this.diagnostics, 'json') + console.log('📋 Comprehensive diagnostics copied to clipboard') // Show success feedback const button = document.querySelector('.action-button.export') as HTMLButtonElement