Browse Source

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<string, unknown> 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
master
Matthew Raymer 9 hours ago
parent
commit
be632b2f0e
  1. 3
      BUILDING.md
  2. 26
      test-apps/daily-notification-test/src/lib/diagnostics-export.ts
  3. 22
      test-apps/daily-notification-test/src/lib/error-handling.ts
  4. 73
      test-apps/daily-notification-test/src/lib/schema-validation.ts
  5. 44
      test-apps/daily-notification-test/src/lib/typed-plugin.ts
  6. 49
      test-apps/daily-notification-test/src/views/HomeView.vue
  7. 2
      test-apps/daily-notification-test/src/views/StatusView.vue

3
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

26
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<string, unknown>)[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
}

22
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)
}

73
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<string, unknown>
// 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<string, unknown>
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<string, unknown>
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<string, unknown>
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<string, unknown>) {
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)
}

44
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<ScheduleResponse> }).scheduleDailyNotification(request)
// Validate response
if (result && typeof result.success === 'boolean') {
@ -75,7 +75,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async checkPermissions(): Promise<PermissionStatus> {
try {
const result = await this.plugin.checkPermissions()
const result = await (this.plugin as { checkPermissions: () => Promise<PermissionStatus> }).checkPermissions()
// Ensure response has required fields
return {
@ -97,7 +97,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async getNotificationStatus(): Promise<NotificationStatus> {
try {
const result = await this.plugin.getNotificationStatus()
const result = await (this.plugin as { getNotificationStatus: () => Promise<NotificationStatus> }).getNotificationStatus()
// Ensure response has required fields
return {
@ -125,7 +125,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async getExactAlarmStatus(): Promise<ExactAlarmStatus> {
try {
const result = await this.plugin.getExactAlarmStatus()
const result = await (this.plugin as { getExactAlarmStatus: () => Promise<ExactAlarmStatus> }).getExactAlarmStatus()
// Ensure response has required fields
return {
@ -147,11 +147,11 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async requestPermissions(): Promise<PermissionResult> {
try {
const result = await this.plugin.requestPermissions()
const result = await (this.plugin as { requestPermissions: () => Promise<PermissionStatus> }).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<void> {
try {
await this.plugin.openExactAlarmSettings()
await (this.plugin as { openExactAlarmSettings: () => Promise<void> }).openExactAlarmSettings()
} catch (error) {
logError(error, 'openExactAlarmSettings')
throw error
@ -183,7 +183,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async openChannelSettings(): Promise<void> {
try {
await this.plugin.openChannelSettings()
await (this.plugin as { openChannelSettings: () => Promise<void> }).openChannelSettings()
} catch (error) {
logError(error, 'openChannelSettings')
throw error
@ -195,7 +195,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async requestBatteryOptimizationExemption(): Promise<void> {
try {
await this.plugin.requestBatteryOptimizationExemption()
await (this.plugin as { requestBatteryOptimizationExemption: () => Promise<void> }).requestBatteryOptimizationExemption()
} catch (error) {
logError(error, 'requestBatteryOptimizationExemption')
throw error
@ -207,7 +207,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async cancelAllNotifications(): Promise<void> {
try {
await this.plugin.cancelAllNotifications()
await (this.plugin as { cancelAllNotifications: () => Promise<void> }).cancelAllNotifications()
} catch (error) {
logError(error, 'cancelAllNotifications')
throw error
@ -219,7 +219,7 @@ export class TypedDailyNotificationPlugin implements DailyNotificationBridge {
*/
async getLastNotification(): Promise<NotificationContent | null> {
try {
const result = await this.plugin.getLastNotification()
const result = await (this.plugin as { getLastNotification: () => Promise<NotificationContent | null> }).getLastNotification()
return result || null
} catch (error) {
logError(error, 'getLastNotification')

49
test-apps/daily-notification-test/src/views/HomeView.vue

@ -77,7 +77,17 @@
<!-- System Status -->
<div class="system-status">
<h2 class="section-title">System Status</h2>
<StatusCard :status="systemStatus" @refresh="refreshSystemStatus" />
<div class="status-grid">
<StatusCard
v-for="item in systemStatus"
:key="item.label"
:title="item.label"
:status="getStatusType(item.status)"
:value="item.value"
:description="getStatusDescription(item.label)"
@refresh="refreshSystemStatus"
/>
</div>
<!-- Diagnostic Actions -->
<div class="section">
@ -218,10 +228,10 @@ const checkSystemStatus = async (): Promise<void> => {
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<string, unknown>).notificationsEnabled)
console.log(' - exactAlarmEnabled:', (permissions as unknown as Record<string, unknown>).exactAlarmEnabled)
console.log(' - wakeLockEnabled:', (permissions as unknown as Record<string, unknown>).wakeLockEnabled)
console.log(' - allPermissionsGranted:', (permissions as unknown as Record<string, unknown>).allPermissionsGranted)
console.log('📊 Exact alarm status:', exactAlarmStatus)
// Map plugin response to app store format
@ -302,6 +312,35 @@ const checkSystemStatus = async (): Promise<void> => {
}
}
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<void> => {
console.log('🔄 CLICK: Refresh System Status')
await checkSystemStatus()

2
test-apps/daily-notification-test/src/views/StatusView.vue

@ -88,7 +88,7 @@
<script lang="ts">
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 { createTypedPlugin } from '../lib/typed-plugin'
import { collectDiagnostics, copyDiagnosticsToClipboard, type ComprehensiveDiagnostics } from '../lib/diagnostics-export'
interface StatusItem {

Loading…
Cancel
Save