From be632b2f0e68f4d588c3b2f9491f74fd216a9fd5 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 24 Oct 2025 12:11:13 +0000 Subject: [PATCH] fix: resolve TypeScript and ESLint errors, fix Android build TypeScript Import Fixes: - Use type-only imports for interfaces in all lib files - Fix import statements in schema-validation.ts, error-handling.ts, typed-plugin.ts, diagnostics-export.ts, StatusView.vue ESLint Error Fixes: - Replace all 'any' types with proper type annotations - Use 'unknown' for unvalidated inputs with proper type guards - Use Record for object properties - Add proper type casting for Performance API and Navigator properties - Fix deprecated Vue filter by replacing type assertion with function StatusCard Component Fixes: - Fix prop type mismatch by changing template structure - Add getStatusType() function for type-safe status conversion - Add getStatusDescription() function for descriptive text - Update HomeView.vue to use multiple StatusCard components in grid Android Build Fix: - Fix capacitor.settings.gradle plugin path from 'android' to 'android/plugin' - Resolve Gradle dependency resolution issue - Enable successful Android APK generation Key improvements: - Full type safety with proper TypeScript interfaces - ESLint compliance with no remaining errors - Successful web and Android builds - Better error handling with typed error objects - Improved developer experience with IntelliSense support --- BUILDING.md | 3 + .../src/lib/diagnostics-export.ts | 26 ++++--- .../src/lib/error-handling.ts | 22 +++--- .../src/lib/schema-validation.ts | 73 +++++++++++-------- .../src/lib/typed-plugin.ts | 44 +++++------ .../src/views/HomeView.vue | 49 +++++++++++-- .../src/views/StatusView.vue | 2 +- 7 files changed, 139 insertions(+), 80 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 811536a..c0778e7 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -227,12 +227,14 @@ android/ ### Important Distinctions #### Plugin Module (`android/plugin/`) + - **Purpose**: Contains the actual plugin code - **No MainActivity** - This is a library, not an app - **No UI Components** - Plugins provide functionality to host apps - **Output**: AAR library files #### Test App Module (`android/app/`) + - **Purpose**: Test application for the plugin - **Has MainActivity** - Full Capacitor app with BridgeActivity - **Has UI Components** - HTML/JS interface for testing @@ -392,6 +394,7 @@ npx cap run ios ``` **Test App Features:** + - Interactive plugin testing interface - Plugin diagnostics and status checking - Notification scheduling and management diff --git a/test-apps/daily-notification-test/src/lib/diagnostics-export.ts b/test-apps/daily-notification-test/src/lib/diagnostics-export.ts index 85823d8..b982a0e 100644 --- a/test-apps/daily-notification-test/src/lib/diagnostics-export.ts +++ b/test-apps/daily-notification-test/src/lib/diagnostics-export.ts @@ -9,9 +9,9 @@ */ import { - PermissionStatus, - NotificationStatus, - ExactAlarmStatus + type PermissionStatus, + type NotificationStatus, + type ExactAlarmStatus } from './bridge' export interface ComprehensiveDiagnostics { @@ -179,13 +179,15 @@ export class DiagnosticsExporter { * Collect network information */ private collectNetworkInfo() { - const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection + const connection = (navigator as Navigator & { connection?: unknown }).connection || + (navigator as Navigator & { mozConnection?: unknown }).mozConnection || + (navigator as Navigator & { webkitConnection?: unknown }).webkitConnection return { - connectionType: connection?.type || 'unknown', - effectiveType: connection?.effectiveType, - downlink: connection?.downlink, - rtt: connection?.rtt + connectionType: (connection as { type?: string })?.type || 'unknown', + effectiveType: (connection as { effectiveType?: string })?.effectiveType, + downlink: (connection as { downlink?: number })?.downlink, + rtt: (connection as { rtt?: number })?.rtt } } @@ -206,7 +208,7 @@ export class DiagnosticsExporter { */ private isStorageAvailable(type: string): boolean { try { - const storage = (window as any)[type] + const storage = (window as unknown as Record)[type] if (!storage) return false if (type === 'indexedDB') { @@ -219,8 +221,8 @@ export class DiagnosticsExporter { // Test localStorage/sessionStorage const test = '__storage_test__' - storage.setItem(test, test) - storage.removeItem(test) + ;(storage as { setItem: (key: string, value: string) => void }).setItem(test, test) + ;(storage as { removeItem: (key: string) => void }).removeItem(test) return true } catch { return false @@ -231,7 +233,7 @@ export class DiagnosticsExporter { * Get memory usage (if available) */ private getMemoryUsage(): number | undefined { - const memory = (performance as any).memory + const memory = (performance as Performance & { memory?: { usedJSHeapSize: number } }).memory return memory ? memory.usedJSHeapSize : undefined } diff --git a/test-apps/daily-notification-test/src/lib/error-handling.ts b/test-apps/daily-notification-test/src/lib/error-handling.ts index b360c15..46d8076 100644 --- a/test-apps/daily-notification-test/src/lib/error-handling.ts +++ b/test-apps/daily-notification-test/src/lib/error-handling.ts @@ -8,15 +8,17 @@ * @version 1.0.0 */ -import { ErrorCode, ErrorInfo } from './bridge' +import { ErrorCode, type ErrorInfo } from './bridge' export class ErrorHandler { /** * Map native error to canonical error */ - mapNativeError(error: any): ErrorInfo { - const errorMessage = error?.message || error?.toString() || 'Unknown error' + mapNativeError(error: unknown): ErrorInfo { + const errorMessage = (error as { message?: string })?.message || + (error as { toString?: () => string })?.toString?.() || + 'Unknown error' // Map common error patterns if (errorMessage.includes('Permission denied')) { @@ -123,18 +125,18 @@ export class ErrorHandler { /** * Log error with context */ - logError(error: any, context: string = 'DailyNotification') { + logError(error: unknown, context: string = 'DailyNotification') { console.error(`[${context}] Error:`, error) - if (error?.stack) { - console.error(`[${context}] Stack:`, error.stack) + if ((error as { stack?: string })?.stack) { + console.error(`[${context}] Stack:`, (error as { stack: string }).stack) } } /** * Handle plugin method error */ - handlePluginError(error: any, method: string): ErrorInfo { + handlePluginError(error: unknown, method: string): ErrorInfo { this.logError(error, `Plugin.${method}`) return this.mapNativeError(error) } @@ -144,7 +146,7 @@ export class ErrorHandler { export const errorHandler = new ErrorHandler() // Utility functions -export function mapNativeError(error: any): ErrorInfo { +export function mapNativeError(error: unknown): ErrorInfo { return errorHandler.mapNativeError(error) } @@ -152,10 +154,10 @@ export function createUserMessage(error: ErrorInfo): string { return errorHandler.createUserMessage(error) } -export function logError(error: any, context?: string): void { +export function logError(error: unknown, context?: string): void { errorHandler.logError(error, context) } -export function handlePluginError(error: any, method: string): ErrorInfo { +export function handlePluginError(error: unknown, method: string): ErrorInfo { return errorHandler.handlePluginError(error, method) } diff --git a/test-apps/daily-notification-test/src/lib/schema-validation.ts b/test-apps/daily-notification-test/src/lib/schema-validation.ts index 0a8600f..3762f7c 100644 --- a/test-apps/daily-notification-test/src/lib/schema-validation.ts +++ b/test-apps/daily-notification-test/src/lib/schema-validation.ts @@ -9,11 +9,10 @@ */ import { - ScheduleRequest, - ValidationResult, + type ValidationResult, ErrorCode, - PriorityType, - PermissionType + type PriorityType, + type PermissionType } from './bridge' export class SchemaValidator { @@ -21,37 +20,48 @@ export class SchemaValidator { /** * Validate schedule request input */ - validateScheduleRequest(request: any): ValidationResult { + validateScheduleRequest(request: unknown): ValidationResult { const errors: string[] = [] + // Type guard: ensure request is an object + if (!request || typeof request !== 'object') { + return { + isValid: false, + errors: ['Request must be an object'], + message: 'Request must be an object' + } + } + + const req = request as Record + // Validate time format (HH:mm) - if (!this.isValidTimeFormat(request.time)) { + if (!this.isValidTimeFormat(req.time as string)) { errors.push('Time must be in HH:mm format (24-hour)') } // Validate title length (enforce exactly: title ≤ 100 chars) - if (request.title && request.title.length > 100) { + if (req.title && typeof req.title === 'string' && req.title.length > 100) { errors.push('Title must be 100 characters or less') } // Validate body length (enforce exactly: body ≤ 500 chars) - if (request.body && request.body.length > 500) { + if (req.body && typeof req.body === 'string' && req.body.length > 500) { errors.push('Body must be 500 characters or less') } // Validate boolean fields - if (typeof request.sound !== 'boolean') { + if (typeof req.sound !== 'boolean') { errors.push('Sound must be a boolean') } // Validate priority - if (!this.isValidPriority(request.priority)) { + if (!this.isValidPriority(req.priority)) { errors.push('Priority must be low, default, or high') } // Reject unknown fields const allowedFields = ['time', 'title', 'body', 'sound', 'priority'] - const unknownFields = Object.keys(request).filter(key => !allowedFields.includes(key)) + const unknownFields = Object.keys(req).filter(key => !allowedFields.includes(key)) if (unknownFields.length > 0) { errors.push(`Unknown fields: ${unknownFields.join(', ')}`) } @@ -66,14 +76,15 @@ export class SchemaValidator { /** * Validate permission status response */ - validatePermissionStatus(status: any): ValidationResult { + validatePermissionStatus(status: unknown): ValidationResult { const errors: string[] = [] + const statusObj = status as Record - if (!this.isValidPermissionType(status.notifications)) { + if (!this.isValidPermissionType(statusObj.notifications)) { errors.push('Notifications permission must be granted or denied') } - if (typeof status.notificationsEnabled !== 'boolean') { + if (typeof statusObj.notificationsEnabled !== 'boolean') { errors.push('NotificationsEnabled must be a boolean') } @@ -87,18 +98,19 @@ export class SchemaValidator { /** * Validate notification status response */ - validateNotificationStatus(status: any): ValidationResult { + validateNotificationStatus(status: unknown): ValidationResult { const errors: string[] = [] + const statusObj = status as Record - if (typeof status.isEnabled !== 'boolean') { + if (typeof statusObj.isEnabled !== 'boolean') { errors.push('IsEnabled must be a boolean') } - if (typeof status.isScheduled !== 'boolean') { + if (typeof statusObj.isScheduled !== 'boolean') { errors.push('IsScheduled must be a boolean') } - if (typeof status.pending !== 'boolean') { + if (typeof statusObj.pending !== 'boolean') { errors.push('Pending must be a boolean') } @@ -112,14 +124,15 @@ export class SchemaValidator { /** * Validate exact alarm status response */ - validateExactAlarmStatus(status: any): ValidationResult { + validateExactAlarmStatus(status: unknown): ValidationResult { const errors: string[] = [] + const statusObj = status as Record - if (typeof status.enabled !== 'boolean') { + if (typeof statusObj.enabled !== 'boolean') { errors.push('Enabled must be a boolean') } - if (typeof status.supported !== 'boolean') { + if (typeof statusObj.supported !== 'boolean') { errors.push('Supported must be a boolean') } @@ -143,15 +156,15 @@ export class SchemaValidator { /** * Check if priority is valid */ - private isValidPriority(priority: any): priority is PriorityType { - return ['low', 'default', 'high'].includes(priority) + private isValidPriority(priority: unknown): priority is PriorityType { + return typeof priority === 'string' && ['low', 'default', 'high'].includes(priority) } /** * Check if permission type is valid */ - private isValidPermissionType(permission: any): permission is PermissionType { - return ['granted', 'denied'].includes(permission) + private isValidPermissionType(permission: unknown): permission is PermissionType { + return typeof permission === 'string' && ['granted', 'denied'].includes(permission) } /** @@ -171,7 +184,7 @@ export class SchemaValidator { /** * Create success response */ - createSuccessResponse(data?: any) { + createSuccessResponse(data?: Record) { return { success: true, ...data @@ -183,18 +196,18 @@ export class SchemaValidator { export const schemaValidator = new SchemaValidator() // Utility functions -export function validateScheduleRequest(request: any): ValidationResult { +export function validateScheduleRequest(request: unknown): ValidationResult { return schemaValidator.validateScheduleRequest(request) } -export function validatePermissionStatus(status: any): ValidationResult { +export function validatePermissionStatus(status: unknown): ValidationResult { return schemaValidator.validatePermissionStatus(status) } -export function validateNotificationStatus(status: any): ValidationResult { +export function validateNotificationStatus(status: unknown): ValidationResult { return schemaValidator.validateNotificationStatus(status) } -export function validateExactAlarmStatus(status: any): ValidationResult { +export function validateExactAlarmStatus(status: unknown): ValidationResult { return schemaValidator.validateExactAlarmStatus(status) } diff --git a/test-apps/daily-notification-test/src/lib/typed-plugin.ts b/test-apps/daily-notification-test/src/lib/typed-plugin.ts index 763c906..7dda576 100644 --- a/test-apps/daily-notification-test/src/lib/typed-plugin.ts +++ b/test-apps/daily-notification-test/src/lib/typed-plugin.ts @@ -9,23 +9,23 @@ */ import { - DailyNotificationBridge, - ScheduleRequest, - ScheduleResponse, - PermissionStatus, - NotificationStatus, - ExactAlarmStatus, - PermissionResult, - NotificationContent + type DailyNotificationBridge, + type ScheduleRequest, + type ScheduleResponse, + type PermissionStatus, + type NotificationStatus, + type ExactAlarmStatus, + type PermissionResult, + type NotificationContent } from './bridge' import { validateScheduleRequest } from './schema-validation' import { handlePluginError, logError } from './error-handling' export class TypedDailyNotificationPlugin implements DailyNotificationBridge { - private plugin: any + private plugin: unknown - constructor(plugin: any) { + constructor(plugin: unknown) { this.plugin = plugin } @@ -48,7 +48,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { } // Call native plugin - const result = await this.plugin.scheduleDailyNotification(request) + const result = await (this.plugin as { scheduleDailyNotification: (req: ScheduleRequest) => Promise }).scheduleDailyNotification(request) // Validate response if (result && typeof result.success === 'boolean') { @@ -75,7 +75,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async checkPermissions(): Promise { try { - const result = await this.plugin.checkPermissions() + const result = await (this.plugin as { checkPermissions: () => Promise }).checkPermissions() // Ensure response has required fields return { @@ -97,7 +97,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async getNotificationStatus(): Promise { try { - const result = await this.plugin.getNotificationStatus() + const result = await (this.plugin as { getNotificationStatus: () => Promise }).getNotificationStatus() // Ensure response has required fields return { @@ -125,7 +125,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async getExactAlarmStatus(): Promise { try { - const result = await this.plugin.getExactAlarmStatus() + const result = await (this.plugin as { getExactAlarmStatus: () => Promise }).getExactAlarmStatus() // Ensure response has required fields return { @@ -147,11 +147,11 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async requestPermissions(): Promise { try { - const result = await this.plugin.requestPermissions() + const result = await (this.plugin as { requestPermissions: () => Promise }).requestPermissions() return { - granted: Boolean(result.granted), - permissions: await this.checkPermissions() + granted: result.notifications === 'granted', + permissions: result } } catch (error) { @@ -171,7 +171,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async openExactAlarmSettings(): Promise { try { - await this.plugin.openExactAlarmSettings() + await (this.plugin as { openExactAlarmSettings: () => Promise }).openExactAlarmSettings() } catch (error) { logError(error, 'openExactAlarmSettings') throw error @@ -183,7 +183,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async openChannelSettings(): Promise { try { - await this.plugin.openChannelSettings() + await (this.plugin as { openChannelSettings: () => Promise }).openChannelSettings() } catch (error) { logError(error, 'openChannelSettings') throw error @@ -195,7 +195,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async requestBatteryOptimizationExemption(): Promise { try { - await this.plugin.requestBatteryOptimizationExemption() + await (this.plugin as { requestBatteryOptimizationExemption: () => Promise }).requestBatteryOptimizationExemption() } catch (error) { logError(error, 'requestBatteryOptimizationExemption') throw error @@ -207,7 +207,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async cancelAllNotifications(): Promise { try { - await this.plugin.cancelAllNotifications() + await (this.plugin as { cancelAllNotifications: () => Promise }).cancelAllNotifications() } catch (error) { logError(error, 'cancelAllNotifications') throw error @@ -219,7 +219,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge { */ async getLastNotification(): Promise { try { - const result = await this.plugin.getLastNotification() + const result = await (this.plugin as { getLastNotification: () => Promise }).getLastNotification() return result || null } catch (error) { logError(error, 'getLastNotification') diff --git a/test-apps/daily-notification-test/src/views/HomeView.vue b/test-apps/daily-notification-test/src/views/HomeView.vue index 2025a37..0703891 100644 --- a/test-apps/daily-notification-test/src/views/HomeView.vue +++ b/test-apps/daily-notification-test/src/views/HomeView.vue @@ -77,7 +77,17 @@

System Status

- +
+ +
@@ -218,10 +228,10 @@ const checkSystemStatus = async (): Promise => { console.log('📊 Plugin permissions:', permissions) console.log('📊 Permissions details:') console.log(' - notifications:', permissions.notifications) - console.log(' - notificationsEnabled:', (permissions as any).notificationsEnabled) - console.log(' - exactAlarmEnabled:', (permissions as any).exactAlarmEnabled) - console.log(' - wakeLockEnabled:', (permissions as any).wakeLockEnabled) - console.log(' - allPermissionsGranted:', (permissions as any).allPermissionsGranted) + console.log(' - notificationsEnabled:', (permissions as unknown as Record).notificationsEnabled) + console.log(' - exactAlarmEnabled:', (permissions as unknown as Record).exactAlarmEnabled) + console.log(' - wakeLockEnabled:', (permissions as unknown as Record).wakeLockEnabled) + console.log(' - allPermissionsGranted:', (permissions as unknown as Record).allPermissionsGranted) console.log('📊 Exact alarm status:', exactAlarmStatus) // Map plugin response to app store format @@ -302,6 +312,35 @@ const checkSystemStatus = async (): Promise => { } } +const getStatusType = (status: string): 'success' | 'warning' | 'error' | 'info' => { + switch (status) { + case 'success': + case 'warning': + case 'error': + case 'info': + return status + default: + return 'info' + } +} + +const getStatusDescription = (label: string): string => { + switch (label) { + case 'Platform': + return 'Current platform information' + case 'Plugin': + return 'DailyNotification plugin availability' + case 'Permissions': + return 'Notification permission status' + case 'Can Schedule': + return 'Ready to schedule notifications' + case 'Next Scheduled': + return 'Next scheduled notification time' + default: + return 'System status information' + } +} + const refreshSystemStatus = async (): Promise => { console.log('🔄 CLICK: Refresh System Status') await checkSystemStatus() diff --git a/test-apps/daily-notification-test/src/views/StatusView.vue b/test-apps/daily-notification-test/src/views/StatusView.vue index c76fea1..4406fe5 100644 --- a/test-apps/daily-notification-test/src/views/StatusView.vue +++ b/test-apps/daily-notification-test/src/views/StatusView.vue @@ -88,7 +88,7 @@