Browse Source
- Add Android test app with exact alarm permission testing - Add iOS test app with rolling window and BGTaskScheduler testing - Add Electron test app with mock implementations and IPC - Include automated setup scripts for each platform - Provide comprehensive testing checklist and troubleshooting guide - Follow best practices for Capacitor plugin testing Test apps include: - Plugin configuration and scheduling validation - Platform-specific feature testing (Android exact alarms, iOS rolling window) - Performance monitoring and debug information - Error handling and edge case testing - Cross-platform API consistency validation Setup: Run ./setup-*.sh scripts for automated platform setup Testing: Each app provides interactive UI for comprehensive plugin validation Files: 25+ new files across test-apps/ directoryresearch/notification-plugin-enhancement
29 changed files with 2165 additions and 37 deletions
@ -0,0 +1,208 @@ |
|||
# API Reference |
|||
|
|||
## DailyNotificationPlugin Interface |
|||
|
|||
### Configuration |
|||
|
|||
#### `configure(options: ConfigureOptions): Promise<void>` |
|||
Configure the plugin with storage, TTL, and optimization settings. |
|||
|
|||
**Parameters:** |
|||
- `options.storage`: `'shared'` | `'tiered'` - Storage mode |
|||
- `options.ttlSeconds`: `number` - TTL in seconds (default: 1800) |
|||
- `options.prefetchLeadMinutes`: `number` - Prefetch lead time (default: 15) |
|||
- `options.enableETagSupport`: `boolean` - Enable ETag conditional requests |
|||
- `options.enableErrorHandling`: `boolean` - Enable advanced error handling |
|||
- `options.enablePerformanceOptimization`: `boolean` - Enable performance optimization |
|||
|
|||
### Core Methods |
|||
|
|||
#### `scheduleDailyNotification(options: NotificationOptions): Promise<void>` |
|||
Schedule a daily notification with content fetching. |
|||
|
|||
**Parameters:** |
|||
- `options.url`: `string` - Content endpoint URL |
|||
- `options.time`: `string` - Time in HH:MM format |
|||
- `options.title`: `string` - Notification title |
|||
- `options.body`: `string` - Notification body |
|||
- `options.sound`: `boolean` - Enable sound (optional) |
|||
- `options.retryConfig`: `RetryConfiguration` - Custom retry settings (optional) |
|||
|
|||
#### `getLastNotification(): Promise<NotificationResponse | null>` |
|||
Get the last scheduled notification. |
|||
|
|||
#### `cancelAllNotifications(): Promise<void>` |
|||
Cancel all scheduled notifications. |
|||
|
|||
### Platform-Specific Methods |
|||
|
|||
#### Android Only |
|||
|
|||
##### `getExactAlarmStatus(): Promise<ExactAlarmStatus>` |
|||
Get exact alarm permission and capability status. |
|||
|
|||
##### `requestExactAlarmPermission(): Promise<void>` |
|||
Request exact alarm permission from user. |
|||
|
|||
##### `openExactAlarmSettings(): Promise<void>` |
|||
Open exact alarm settings in system preferences. |
|||
|
|||
##### `getRebootRecoveryStatus(): Promise<RecoveryStatus>` |
|||
Get reboot recovery status and statistics. |
|||
|
|||
### Management Methods |
|||
|
|||
#### `maintainRollingWindow(): Promise<void>` |
|||
Manually trigger rolling window maintenance. |
|||
|
|||
#### `getRollingWindowStats(): Promise<RollingWindowStats>` |
|||
Get rolling window statistics and status. |
|||
|
|||
### Optimization Methods |
|||
|
|||
#### `optimizeDatabase(): Promise<void>` |
|||
Optimize database performance with indexes and settings. |
|||
|
|||
#### `optimizeMemory(): Promise<void>` |
|||
Optimize memory usage and perform cleanup. |
|||
|
|||
#### `optimizeBattery(): Promise<void>` |
|||
Optimize battery usage and background CPU. |
|||
|
|||
### Metrics and Monitoring |
|||
|
|||
#### `getPerformanceMetrics(): Promise<PerformanceMetrics>` |
|||
Get comprehensive performance metrics. |
|||
|
|||
#### `getErrorMetrics(): Promise<ErrorMetrics>` |
|||
Get error handling metrics and statistics. |
|||
|
|||
#### `getNetworkMetrics(): Promise<NetworkMetrics>` |
|||
Get network efficiency metrics (ETag support). |
|||
|
|||
#### `getMemoryMetrics(): Promise<MemoryMetrics>` |
|||
Get memory usage metrics and statistics. |
|||
|
|||
#### `getObjectPoolMetrics(): Promise<ObjectPoolMetrics>` |
|||
Get object pooling efficiency metrics. |
|||
|
|||
### Utility Methods |
|||
|
|||
#### `resetPerformanceMetrics(): Promise<void>` |
|||
Reset all performance metrics to zero. |
|||
|
|||
#### `resetErrorMetrics(): Promise<void>` |
|||
Reset error handling metrics. |
|||
|
|||
#### `clearRetryStates(): Promise<void>` |
|||
Clear all retry states and operations. |
|||
|
|||
#### `cleanExpiredETags(): Promise<void>` |
|||
Clean expired ETag cache entries. |
|||
|
|||
## Data Types |
|||
|
|||
### ConfigureOptions |
|||
```typescript |
|||
interface ConfigureOptions { |
|||
storage?: 'shared' | 'tiered'; |
|||
ttlSeconds?: number; |
|||
prefetchLeadMinutes?: number; |
|||
enableETagSupport?: boolean; |
|||
enableErrorHandling?: boolean; |
|||
enablePerformanceOptimization?: boolean; |
|||
maxRetries?: number; |
|||
baseRetryDelay?: number; |
|||
maxRetryDelay?: number; |
|||
backoffMultiplier?: number; |
|||
memoryWarningThreshold?: number; |
|||
memoryCriticalThreshold?: number; |
|||
objectPoolSize?: number; |
|||
maxObjectPoolSize?: number; |
|||
} |
|||
``` |
|||
|
|||
### NotificationOptions |
|||
```typescript |
|||
interface NotificationOptions { |
|||
url: string; |
|||
time: string; |
|||
title: string; |
|||
body: string; |
|||
sound?: boolean; |
|||
retryConfig?: RetryConfiguration; |
|||
} |
|||
``` |
|||
|
|||
### ExactAlarmStatus (Android) |
|||
```typescript |
|||
interface ExactAlarmStatus { |
|||
supported: boolean; |
|||
enabled: boolean; |
|||
canSchedule: boolean; |
|||
fallbackWindow: string; |
|||
} |
|||
``` |
|||
|
|||
### PerformanceMetrics |
|||
```typescript |
|||
interface PerformanceMetrics { |
|||
overallScore: number; |
|||
databasePerformance: number; |
|||
memoryEfficiency: number; |
|||
batteryEfficiency: number; |
|||
objectPoolEfficiency: number; |
|||
totalDatabaseQueries: number; |
|||
averageMemoryUsage: number; |
|||
objectPoolHits: number; |
|||
backgroundCpuUsage: number; |
|||
totalNetworkRequests: number; |
|||
recommendations: string[]; |
|||
} |
|||
``` |
|||
|
|||
### ErrorMetrics |
|||
```typescript |
|||
interface ErrorMetrics { |
|||
totalErrors: number; |
|||
networkErrors: number; |
|||
storageErrors: number; |
|||
schedulingErrors: number; |
|||
permissionErrors: number; |
|||
configurationErrors: number; |
|||
systemErrors: number; |
|||
unknownErrors: number; |
|||
cacheHitRatio: number; |
|||
} |
|||
``` |
|||
|
|||
## Error Handling |
|||
|
|||
All methods return promises that reject with descriptive error messages. The plugin includes comprehensive error categorization and retry logic. |
|||
|
|||
### Common Error Types |
|||
- **Network Errors**: Connection timeouts, DNS failures |
|||
- **Storage Errors**: Database corruption, disk full |
|||
- **Permission Errors**: Missing exact alarm permission |
|||
- **Configuration Errors**: Invalid parameters, unsupported settings |
|||
- **System Errors**: Out of memory, platform limitations |
|||
|
|||
## Platform Differences |
|||
|
|||
### Android |
|||
- Requires `SCHEDULE_EXACT_ALARM` permission for precise timing |
|||
- Falls back to windowed alarms (±10m) if exact permission denied |
|||
- Supports reboot recovery with broadcast receivers |
|||
- Full performance optimization features |
|||
|
|||
### iOS |
|||
- Uses `BGTaskScheduler` for background prefetch |
|||
- Limited to 64 pending notifications |
|||
- Automatic background task management |
|||
- Battery optimization built-in |
|||
|
|||
### Web |
|||
- Placeholder implementations for development |
|||
- No actual notification scheduling |
|||
- All methods return mock data |
|||
- Used for testing and development |
@ -0,0 +1,181 @@ |
|||
# Daily Notification Plugin - Usage Guide |
|||
|
|||
## Quick Start |
|||
|
|||
```typescript |
|||
import { DailyNotification } from '@timesafari/daily-notification-plugin'; |
|||
|
|||
// 1. Configure the plugin |
|||
await DailyNotification.configure({ |
|||
storage: 'shared', // Use shared SQLite database |
|||
ttlSeconds: 1800, // 30 minutes TTL |
|||
prefetchLeadMinutes: 15 // Prefetch 15 minutes before delivery |
|||
}); |
|||
|
|||
// 2. Schedule a notification |
|||
await DailyNotification.scheduleDailyNotification({ |
|||
url: 'https://api.example.com/daily-content', |
|||
time: '09:00', |
|||
title: 'Daily Update', |
|||
body: 'Your daily notification is ready' |
|||
}); |
|||
``` |
|||
|
|||
## Configuration Options |
|||
|
|||
### Storage Mode |
|||
- **`'shared'`** (Recommended): Uses shared SQLite database with WAL mode |
|||
- **`'tiered'`** (Legacy): Uses SharedPreferences/UserDefaults + in-memory cache |
|||
|
|||
### TTL Settings |
|||
- **`ttlSeconds`**: Maximum age of content at delivery time (default: 1800 = 30 minutes) |
|||
- **`prefetchLeadMinutes`**: How early to prefetch content (default: 15 minutes) |
|||
|
|||
### Performance Optimization |
|||
- **`enableETagSupport`**: Use conditional requests for bandwidth savings |
|||
- **`enableErrorHandling`**: Advanced retry logic with exponential backoff |
|||
- **`enablePerformanceOptimization`**: Database indexes, memory management, object pooling |
|||
|
|||
## Platform-Specific Features |
|||
|
|||
### Android |
|||
```typescript |
|||
// Check exact alarm status |
|||
const alarmStatus = await DailyNotification.getExactAlarmStatus(); |
|||
if (!alarmStatus.canSchedule) { |
|||
// Request permission or use windowed fallback |
|||
await DailyNotification.requestExactAlarmPermission(); |
|||
} |
|||
|
|||
// Check reboot recovery status |
|||
const recoveryStatus = await DailyNotification.getRebootRecoveryStatus(); |
|||
if (recoveryStatus.recoveryNeeded) { |
|||
console.log('System may have rebooted - notifications restored'); |
|||
} |
|||
``` |
|||
|
|||
### iOS |
|||
```typescript |
|||
// Background tasks are automatically handled |
|||
// The plugin uses BGTaskScheduler for T–lead prefetch |
|||
// No additional configuration needed |
|||
``` |
|||
|
|||
## Advanced Usage |
|||
|
|||
### Error Handling |
|||
```typescript |
|||
// Configure retry behavior |
|||
await DailyNotification.configure({ |
|||
maxRetries: 3, |
|||
baseRetryDelay: 1000, // 1 second |
|||
maxRetryDelay: 30000, // 30 seconds |
|||
backoffMultiplier: 2.0 |
|||
}); |
|||
|
|||
// Monitor error metrics |
|||
const errorMetrics = await DailyNotification.getErrorMetrics(); |
|||
console.log(`Network errors: ${errorMetrics.networkErrors}`); |
|||
console.log(`Cache hit ratio: ${errorMetrics.cacheHitRatio}`); |
|||
``` |
|||
|
|||
### Performance Monitoring |
|||
```typescript |
|||
// Get performance metrics |
|||
const metrics = await DailyNotification.getPerformanceMetrics(); |
|||
console.log(`Performance score: ${metrics.overallScore}/100`); |
|||
console.log(`Memory usage: ${metrics.averageMemoryUsage}MB`); |
|||
|
|||
// Optimize if needed |
|||
if (metrics.overallScore < 70) { |
|||
await DailyNotification.optimizeMemory(); |
|||
await DailyNotification.optimizeDatabase(); |
|||
} |
|||
``` |
|||
|
|||
### Rolling Window Management |
|||
```typescript |
|||
// Manual maintenance |
|||
await DailyNotification.maintainRollingWindow(); |
|||
|
|||
// Check status |
|||
const windowStats = await DailyNotification.getRollingWindowStats(); |
|||
console.log(`Maintenance needed: ${windowStats.maintenanceNeeded}`); |
|||
console.log(`Time until next: ${windowStats.timeUntilNextMaintenance}ms`); |
|||
``` |
|||
|
|||
## Production Configuration |
|||
|
|||
```typescript |
|||
// Recommended production settings |
|||
await DailyNotification.configure({ |
|||
storage: 'shared', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true, |
|||
memoryWarningThreshold: 50, // MB |
|||
memoryCriticalThreshold: 100, // MB |
|||
objectPoolSize: 20, |
|||
maxObjectPoolSize: 100 |
|||
}); |
|||
``` |
|||
|
|||
## Troubleshooting |
|||
|
|||
### Common Issues |
|||
|
|||
1. **Notifications not firing** |
|||
- Check exact alarm permissions on Android |
|||
- Verify TTL settings aren't too restrictive |
|||
- Ensure rolling window is maintained |
|||
|
|||
2. **High memory usage** |
|||
- Enable performance optimization |
|||
- Check memory thresholds |
|||
- Monitor object pool efficiency |
|||
|
|||
3. **Network efficiency** |
|||
- Enable ETag support |
|||
- Monitor cache hit ratios |
|||
- Check error retry patterns |
|||
|
|||
### Debug Information |
|||
|
|||
```typescript |
|||
// Get comprehensive debug info |
|||
const debugInfo = await DailyNotification.getDebugInfo(); |
|||
console.log('Plugin Status:', debugInfo.status); |
|||
console.log('Configuration:', debugInfo.configuration); |
|||
console.log('Recent Errors:', debugInfo.recentErrors); |
|||
``` |
|||
|
|||
## Migration from Legacy Storage |
|||
|
|||
```typescript |
|||
// The plugin automatically migrates from tiered to shared storage |
|||
// No manual migration needed - just configure with storage: 'shared' |
|||
await DailyNotification.configure({ |
|||
storage: 'shared' // Triggers automatic migration |
|||
}); |
|||
``` |
|||
|
|||
## Best Practices |
|||
|
|||
1. **Always configure before scheduling** - Set up storage, TTL, and optimization features |
|||
2. **Monitor performance metrics** - Use built-in monitoring to optimize settings |
|||
3. **Handle errors gracefully** - Implement retry logic and fallback mechanisms |
|||
4. **Test on both platforms** - Android and iOS have different capabilities and limitations |
|||
5. **Use production settings** - Enable all optimization features for production use |
|||
|
|||
## API Reference |
|||
|
|||
See `src/definitions.ts` for complete TypeScript interface definitions. |
|||
|
|||
## Examples |
|||
|
|||
- **Basic Usage**: `examples/usage.ts` |
|||
- **Phase-by-Phase**: `examples/phase1-*.ts`, `examples/phase2-*.ts`, `examples/phase3-*.ts` |
|||
- **Advanced Scenarios**: `examples/advanced-usage.ts` |
|||
- **Enterprise Features**: `examples/enterprise-usage.ts` |
@ -0,0 +1,160 @@ |
|||
# Test Apps Setup Guide |
|||
|
|||
## Overview |
|||
|
|||
This guide creates minimal Capacitor test apps for validating the Daily Notification Plugin across all target platforms. |
|||
|
|||
## Directory Structure |
|||
|
|||
``` |
|||
test-apps/ |
|||
├── android-test/ # Android test app |
|||
├── ios-test/ # iOS test app |
|||
├── electron-test/ # Electron test app |
|||
├── setup-android.sh # Android setup script |
|||
├── setup-ios.sh # iOS setup script |
|||
├── setup-electron.sh # Electron setup script |
|||
└── README.md # This guide |
|||
``` |
|||
|
|||
## Prerequisites |
|||
|
|||
- Node.js 18+ |
|||
- Capacitor CLI: `npm install -g @capacitor/cli` |
|||
- Android Studio (for Android) |
|||
- Xcode (for iOS) |
|||
- Platform-specific SDKs |
|||
|
|||
## Quick Start |
|||
|
|||
### Option 1: Automated Setup (Recommended) |
|||
```bash |
|||
# Setup all platforms |
|||
./setup-android.sh |
|||
./setup-ios.sh |
|||
./setup-electron.sh |
|||
``` |
|||
|
|||
### Option 2: Manual Setup |
|||
```bash |
|||
# Android |
|||
cd android-test |
|||
npm install |
|||
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest" |
|||
npx cap add android |
|||
npm run build |
|||
npx cap sync android |
|||
|
|||
# iOS |
|||
cd ios-test |
|||
npm install |
|||
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest" |
|||
npx cap add ios |
|||
npm run build |
|||
npx cap sync ios |
|||
|
|||
# Electron |
|||
cd electron-test |
|||
npm install |
|||
npm run build-web |
|||
``` |
|||
|
|||
## Test App Features |
|||
|
|||
Each test app includes: |
|||
- **Plugin Configuration**: Test shared SQLite, TTL, prefetch settings |
|||
- **Notification Scheduling**: Basic daily notification setup |
|||
- **Platform-Specific Features**: |
|||
- Android: Exact alarm permissions, reboot recovery |
|||
- iOS: Rolling window management, BGTaskScheduler |
|||
- Electron: Mock implementations, IPC communication |
|||
- **Performance Monitoring**: Metrics collection and display |
|||
- **Error Handling**: Comprehensive error testing |
|||
- **Debug Information**: Platform-specific debug data |
|||
|
|||
## Platform-Specific Testing |
|||
|
|||
### Android Test App |
|||
- **Exact Alarm Status**: Check permission and capability |
|||
- **Permission Requests**: Test exact alarm permission flow |
|||
- **Performance Metrics**: Monitor Android-specific optimizations |
|||
- **Reboot Recovery**: Validate system restart handling |
|||
|
|||
### iOS Test App |
|||
- **Rolling Window**: Test notification limit management |
|||
- **Background Tasks**: Validate BGTaskScheduler integration |
|||
- **Performance Metrics**: Monitor iOS-specific optimizations |
|||
- **Memory Management**: Test object pooling and cleanup |
|||
|
|||
### Electron Test App |
|||
- **Mock Implementations**: Test web platform compatibility |
|||
- **IPC Communication**: Validate Electron-specific APIs |
|||
- **Development Workflow**: Test plugin integration |
|||
- **Debug Information**: Platform-specific status display |
|||
|
|||
## Running the Test Apps |
|||
|
|||
### Android |
|||
```bash |
|||
cd android-test |
|||
npm run dev # Web development server |
|||
npx cap open android # Open in Android Studio |
|||
npx cap run android # Run on device/emulator |
|||
``` |
|||
|
|||
### iOS |
|||
```bash |
|||
cd ios-test |
|||
npm run dev # Web development server |
|||
npx cap open ios # Open in Xcode |
|||
npx cap run ios # Run on device/simulator |
|||
``` |
|||
|
|||
### Electron |
|||
```bash |
|||
cd electron-test |
|||
npm start # Run Electron app |
|||
npm run dev # Run in development mode |
|||
``` |
|||
|
|||
## Testing Checklist |
|||
|
|||
### Core Functionality |
|||
- [ ] Plugin configuration works |
|||
- [ ] Notification scheduling succeeds |
|||
- [ ] Error handling functions properly |
|||
- [ ] Performance metrics are accurate |
|||
|
|||
### Platform-Specific |
|||
- [ ] Android exact alarm permissions |
|||
- [ ] iOS rolling window management |
|||
- [ ] Electron mock implementations |
|||
- [ ] Cross-platform API consistency |
|||
|
|||
### Integration |
|||
- [ ] Plugin loads without errors |
|||
- [ ] Configuration persists across sessions |
|||
- [ ] Performance optimizations active |
|||
- [ ] Debug information accessible |
|||
|
|||
## Troubleshooting |
|||
|
|||
### Common Issues |
|||
1. **Build Failures**: Ensure all dependencies installed |
|||
2. **Platform Errors**: Check platform-specific SDKs installed |
|||
3. **Permission Issues**: Verify platform permissions configured |
|||
4. **Sync Problems**: Run `npx cap sync` after changes |
|||
|
|||
### Development Tips |
|||
- Use `npm run dev` for web testing |
|||
- Use platform-specific tools for native testing |
|||
- Check console logs for detailed error information |
|||
- Test on both physical devices and simulators |
|||
|
|||
## Next Steps |
|||
|
|||
1. **Run Setup Scripts**: Execute platform-specific setup |
|||
2. **Test Core Features**: Validate basic functionality |
|||
3. **Test Platform Features**: Verify platform-specific capabilities |
|||
4. **Integration Testing**: Test with actual plugin implementation |
|||
5. **Performance Validation**: Monitor metrics and optimizations |
@ -0,0 +1,105 @@ |
|||
# Dependencies |
|||
node_modules/ |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# Build outputs |
|||
dist/ |
|||
build/ |
|||
*.tsbuildinfo |
|||
|
|||
# Capacitor |
|||
android/ |
|||
ios/ |
|||
.capacitor/ |
|||
|
|||
# IDE and editor files |
|||
.vscode/ |
|||
.idea/ |
|||
*.swp |
|||
*.swo |
|||
*~ |
|||
|
|||
# OS generated files |
|||
.DS_Store |
|||
.DS_Store? |
|||
._* |
|||
.Spotlight-V100 |
|||
.Trashes |
|||
ehthumbs.db |
|||
Thumbs.db |
|||
|
|||
# Logs |
|||
logs |
|||
*.log |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage/ |
|||
*.lcov |
|||
|
|||
# nyc test coverage |
|||
.nyc_output |
|||
|
|||
# Dependency directories |
|||
jspm_packages/ |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional eslint cache |
|||
.eslintcache |
|||
|
|||
# Microbundle cache |
|||
.rpt2_cache/ |
|||
.rts2_cache_cjs/ |
|||
.rts2_cache_es/ |
|||
.rts2_cache_umd/ |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variables file |
|||
.env |
|||
.env.test |
|||
.env.local |
|||
.env.development.local |
|||
.env.test.local |
|||
.env.production.local |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# next.js build output |
|||
.next |
|||
|
|||
# nuxt.js build output |
|||
.nuxt |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# Serverless directories |
|||
.serverless/ |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
@ -0,0 +1,25 @@ |
|||
import { CapacitorConfig } from '@capacitor/cli'; |
|||
|
|||
const config: CapacitorConfig = { |
|||
appId: 'com.timesafari.dailynotification.androidtest', |
|||
appName: 'Daily Notification Android Test', |
|||
webDir: 'dist', |
|||
server: { |
|||
androidScheme: 'https' |
|||
}, |
|||
plugins: { |
|||
DailyNotification: { |
|||
storage: 'shared', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true |
|||
} |
|||
}, |
|||
android: { |
|||
allowMixedContent: true |
|||
} |
|||
}; |
|||
|
|||
export default config; |
@ -0,0 +1,29 @@ |
|||
{ |
|||
"name": "daily-notification-android-test", |
|||
"version": "1.0.0", |
|||
"description": "Minimal Android test app for Daily Notification Plugin", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"build": "webpack --mode=production", |
|||
"dev": "webpack serve --mode=development", |
|||
"android": "npx cap run android", |
|||
"sync": "npx cap sync android", |
|||
"open": "npx cap open android" |
|||
}, |
|||
"keywords": ["capacitor", "android", "notifications", "test"], |
|||
"author": "Matthew Raymer", |
|||
"license": "MIT", |
|||
"dependencies": { |
|||
"@capacitor/core": "^5.0.0", |
|||
"@capacitor/android": "^5.0.0", |
|||
"@capacitor/cli": "^5.0.0" |
|||
}, |
|||
"devDependencies": { |
|||
"webpack": "^5.88.0", |
|||
"webpack-cli": "^5.1.0", |
|||
"webpack-dev-server": "^4.15.0", |
|||
"html-webpack-plugin": "^5.5.0", |
|||
"typescript": "^5.0.0", |
|||
"ts-loader": "^9.4.0" |
|||
} |
|||
} |
@ -0,0 +1,108 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Daily Notification - Android Test</title> |
|||
<style> |
|||
body { |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|||
margin: 0; |
|||
padding: 20px; |
|||
background-color: #f5f5f5; |
|||
} |
|||
.container { |
|||
max-width: 600px; |
|||
margin: 0 auto; |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|||
} |
|||
h1 { |
|||
color: #333; |
|||
text-align: center; |
|||
margin-bottom: 30px; |
|||
} |
|||
.status { |
|||
background: #e3f2fd; |
|||
padding: 15px; |
|||
border-radius: 8px; |
|||
margin-bottom: 20px; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
color: #1976d2; |
|||
} |
|||
.button-grid { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
gap: 10px; |
|||
margin-bottom: 20px; |
|||
} |
|||
button { |
|||
background: #1976d2; |
|||
color: white; |
|||
border: none; |
|||
padding: 12px 16px; |
|||
border-radius: 6px; |
|||
cursor: pointer; |
|||
font-size: 14px; |
|||
transition: background-color 0.2s; |
|||
} |
|||
button:hover { |
|||
background: #1565c0; |
|||
} |
|||
button:disabled { |
|||
background: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
.log-container { |
|||
background: #f8f9fa; |
|||
border: 1px solid #dee2e6; |
|||
border-radius: 6px; |
|||
padding: 15px; |
|||
height: 300px; |
|||
overflow-y: auto; |
|||
font-family: 'Courier New', monospace; |
|||
font-size: 12px; |
|||
} |
|||
.timestamp { |
|||
color: #666; |
|||
font-weight: bold; |
|||
} |
|||
pre { |
|||
background: #e9ecef; |
|||
padding: 8px; |
|||
border-radius: 4px; |
|||
margin: 5px 0; |
|||
overflow-x: auto; |
|||
} |
|||
.clear-button { |
|||
background: #dc3545; |
|||
margin-top: 10px; |
|||
width: 100%; |
|||
} |
|||
.clear-button:hover { |
|||
background: #c82333; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="container"> |
|||
<h1>📱 Daily Notification Plugin - Android Test</h1> |
|||
|
|||
<div class="status" id="status">Ready</div> |
|||
|
|||
<div class="button-grid"> |
|||
<button id="configure">Configure Plugin</button> |
|||
<button id="schedule">Schedule Notification</button> |
|||
<button id="alarm-status">Check Alarm Status</button> |
|||
<button id="request-permission">Request Permission</button> |
|||
<button id="performance">Performance Metrics</button> |
|||
</div> |
|||
|
|||
<div class="log-container" id="log"></div> |
|||
<button class="clear-button" id="clear-log">Clear Log</button> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,154 @@ |
|||
import { Capacitor } from '@capacitor/core'; |
|||
|
|||
// Mock plugin for development
|
|||
const DailyNotification = { |
|||
async configure(options: any) { |
|||
console.log('Configure called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async scheduleDailyNotification(options: any) { |
|||
console.log('Schedule called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async getExactAlarmStatus() { |
|||
return Promise.resolve({ |
|||
supported: true, |
|||
enabled: false, |
|||
canSchedule: false, |
|||
fallbackWindow: '±10 minutes' |
|||
}); |
|||
}, |
|||
async requestExactAlarmPermission() { |
|||
console.log('Request exact alarm permission'); |
|||
return Promise.resolve(); |
|||
}, |
|||
async getPerformanceMetrics() { |
|||
return Promise.resolve({ |
|||
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'] |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
// Test interface
|
|||
class TestApp { |
|||
private statusElement: HTMLElement; |
|||
private logElement: HTMLElement; |
|||
|
|||
constructor() { |
|||
this.statusElement = document.getElementById('status')!; |
|||
this.logElement = document.getElementById('log')!; |
|||
this.setupEventListeners(); |
|||
this.log('Test app initialized'); |
|||
} |
|||
|
|||
private setupEventListeners() { |
|||
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure()); |
|||
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule()); |
|||
document.getElementById('alarm-status')?.addEventListener('click', () => this.testAlarmStatus()); |
|||
document.getElementById('request-permission')?.addEventListener('click', () => this.testRequestPermission()); |
|||
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance()); |
|||
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog()); |
|||
} |
|||
|
|||
private async testConfigure() { |
|||
try { |
|||
this.log('Testing configuration...'); |
|||
await DailyNotification.configure({ |
|||
storage: 'shared', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true |
|||
}); |
|||
this.log('✅ Configuration successful'); |
|||
this.updateStatus('Configured'); |
|||
} catch (error) { |
|||
this.log(`❌ Configuration failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testSchedule() { |
|||
try { |
|||
this.log('Testing notification scheduling...'); |
|||
await DailyNotification.scheduleDailyNotification({ |
|||
url: 'https://api.example.com/daily-content', |
|||
time: '09:00', |
|||
title: 'Daily Test Notification', |
|||
body: 'This is a test notification from the Android test app' |
|||
}); |
|||
this.log('✅ Notification scheduled successfully'); |
|||
this.updateStatus('Scheduled'); |
|||
} catch (error) { |
|||
this.log(`❌ Scheduling failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testAlarmStatus() { |
|||
try { |
|||
this.log('Testing exact alarm status...'); |
|||
const status = await DailyNotification.getExactAlarmStatus(); |
|||
this.log(`📱 Alarm Status:`, status); |
|||
this.updateStatus(`Alarm: ${status.canSchedule ? 'Enabled' : 'Disabled'}`); |
|||
} catch (error) { |
|||
this.log(`❌ Alarm status check failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testRequestPermission() { |
|||
try { |
|||
this.log('Testing permission request...'); |
|||
await DailyNotification.requestExactAlarmPermission(); |
|||
this.log('✅ Permission request sent'); |
|||
this.updateStatus('Permission Requested'); |
|||
} catch (error) { |
|||
this.log(`❌ Permission request failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testPerformance() { |
|||
try { |
|||
this.log('Testing performance metrics...'); |
|||
const metrics = await DailyNotification.getPerformanceMetrics(); |
|||
this.log(`📊 Performance Metrics:`, metrics); |
|||
this.updateStatus(`Performance: ${metrics.overallScore}/100`); |
|||
} catch (error) { |
|||
this.log(`❌ Performance check failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private log(message: string, data?: any) { |
|||
const timestamp = new Date().toLocaleTimeString(); |
|||
const logEntry = document.createElement('div'); |
|||
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`; |
|||
if (data) { |
|||
logEntry.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`; |
|||
} |
|||
this.logElement.appendChild(logEntry); |
|||
this.logElement.scrollTop = this.logElement.scrollHeight; |
|||
} |
|||
|
|||
private clearLog() { |
|||
this.logElement.innerHTML = ''; |
|||
this.log('Log cleared'); |
|||
} |
|||
|
|||
private updateStatus(status: string) { |
|||
this.statusElement.textContent = status; |
|||
} |
|||
} |
|||
|
|||
// Initialize app when DOM is ready
|
|||
document.addEventListener('DOMContentLoaded', () => { |
|||
new TestApp(); |
|||
}); |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"target": "ES2020", |
|||
"module": "ES2020", |
|||
"moduleResolution": "node", |
|||
"strict": true, |
|||
"esModuleInterop": true, |
|||
"skipLibCheck": true, |
|||
"forceConsistentCasingInFileNames": true, |
|||
"outDir": "./dist", |
|||
"rootDir": "./src" |
|||
}, |
|||
"include": ["src/**/*"], |
|||
"exclude": ["node_modules", "dist"] |
|||
} |
@ -0,0 +1,33 @@ |
|||
const path = require('path'); |
|||
const HtmlWebpackPlugin = require('html-webpack-plugin'); |
|||
|
|||
module.exports = { |
|||
entry: './src/index.ts', |
|||
module: { |
|||
rules: [ |
|||
{ |
|||
test: /\.ts$/, |
|||
use: 'ts-loader', |
|||
exclude: /node_modules/, |
|||
}, |
|||
], |
|||
}, |
|||
resolve: { |
|||
extensions: ['.ts', '.js'], |
|||
}, |
|||
output: { |
|||
filename: 'bundle.js', |
|||
path: path.resolve(__dirname, 'dist'), |
|||
clean: true, |
|||
}, |
|||
plugins: [ |
|||
new HtmlWebpackPlugin({ |
|||
template: './src/index.html', |
|||
}), |
|||
], |
|||
devServer: { |
|||
static: './dist', |
|||
port: 3000, |
|||
hot: true, |
|||
}, |
|||
}; |
@ -0,0 +1,114 @@ |
|||
# Dependencies |
|||
node_modules/ |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# Build outputs |
|||
dist/ |
|||
build/ |
|||
*.tsbuildinfo |
|||
|
|||
# Electron |
|||
out/ |
|||
app/ |
|||
packages/ |
|||
|
|||
# IDE and editor files |
|||
.vscode/ |
|||
.idea/ |
|||
*.swp |
|||
*.swo |
|||
*~ |
|||
|
|||
# OS generated files |
|||
.DS_Store |
|||
.DS_Store? |
|||
._* |
|||
.Spotlight-V100 |
|||
.Trashes |
|||
ehthumbs.db |
|||
Thumbs.db |
|||
|
|||
# Logs |
|||
logs |
|||
*.log |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage/ |
|||
*.lcov |
|||
|
|||
# nyc test coverage |
|||
.nyc_output |
|||
|
|||
# Dependency directories |
|||
jspm_packages/ |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional eslint cache |
|||
.eslintcache |
|||
|
|||
# Microbundle cache |
|||
.rpt2_cache/ |
|||
.rts2_cache_cjs/ |
|||
.rts2_cache_es/ |
|||
.rts2_cache_umd/ |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variables file |
|||
.env |
|||
.env.test |
|||
.env.local |
|||
.env.development.local |
|||
.env.test.local |
|||
.env.production.local |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# next.js build output |
|||
.next |
|||
|
|||
# nuxt.js build output |
|||
.nuxt |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# Serverless directories |
|||
.serverless/ |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
|||
|
|||
# Electron specific |
|||
*.app |
|||
*.dmg |
|||
*.exe |
|||
*.deb |
|||
*.rpm |
|||
*.AppImage |
|||
*.snap |
@ -0,0 +1,117 @@ |
|||
const { app, BrowserWindow, ipcMain } = require('electron'); |
|||
const path = require('path'); |
|||
|
|||
// Mock plugin for Electron development
|
|||
const DailyNotification = { |
|||
async configure(options) { |
|||
console.log('Electron Configure called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async scheduleDailyNotification(options) { |
|||
console.log('Electron Schedule called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async getDebugInfo() { |
|||
return Promise.resolve({ |
|||
status: 'Electron Mock Mode', |
|||
configuration: { |
|||
storage: 'mock', |
|||
platform: 'electron', |
|||
version: '1.0.0' |
|||
}, |
|||
recentErrors: [], |
|||
performance: { |
|||
overallScore: 95, |
|||
memoryUsage: 15.2, |
|||
cpuUsage: 1.2 |
|||
} |
|||
}); |
|||
}, |
|||
async getPerformanceMetrics() { |
|||
return Promise.resolve({ |
|||
overallScore: 95, |
|||
databasePerformance: 100, |
|||
memoryEfficiency: 95, |
|||
batteryEfficiency: 100, |
|||
objectPoolEfficiency: 100, |
|||
totalDatabaseQueries: 0, |
|||
averageMemoryUsage: 15.2, |
|||
objectPoolHits: 0, |
|||
backgroundCpuUsage: 0.5, |
|||
totalNetworkRequests: 0, |
|||
recommendations: ['Electron mock mode - no optimizations needed'] |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
// IPC handlers for Electron
|
|||
ipcMain.handle('configure-plugin', async (event, options) => { |
|||
try { |
|||
await DailyNotification.configure(options); |
|||
return { success: true, message: 'Configuration successful' }; |
|||
} catch (error) { |
|||
return { success: false, error: error.message }; |
|||
} |
|||
}); |
|||
|
|||
ipcMain.handle('schedule-notification', async (event, options) => { |
|||
try { |
|||
await DailyNotification.scheduleDailyNotification(options); |
|||
return { success: true, message: 'Notification scheduled' }; |
|||
} catch (error) { |
|||
return { success: false, error: error.message }; |
|||
} |
|||
}); |
|||
|
|||
ipcMain.handle('get-debug-info', async () => { |
|||
try { |
|||
const info = await DailyNotification.getDebugInfo(); |
|||
return { success: true, data: info }; |
|||
} catch (error) { |
|||
return { success: false, error: error.message }; |
|||
} |
|||
}); |
|||
|
|||
ipcMain.handle('get-performance-metrics', async () => { |
|||
try { |
|||
const metrics = await DailyNotification.getPerformanceMetrics(); |
|||
return { success: true, data: metrics }; |
|||
} catch (error) { |
|||
return { success: false, error: error.message }; |
|||
} |
|||
}); |
|||
|
|||
function createWindow() { |
|||
const mainWindow = new BrowserWindow({ |
|||
width: 800, |
|||
height: 600, |
|||
webPreferences: { |
|||
nodeIntegration: false, |
|||
contextIsolation: true, |
|||
preload: path.join(__dirname, 'preload.js') |
|||
}, |
|||
title: 'Daily Notification - Electron Test' |
|||
}); |
|||
|
|||
// Load the web app
|
|||
mainWindow.loadFile('dist/index.html'); |
|||
|
|||
// Open DevTools in development
|
|||
if (process.argv.includes('--dev')) { |
|||
mainWindow.webContents.openDevTools(); |
|||
} |
|||
} |
|||
|
|||
app.whenReady().then(createWindow); |
|||
|
|||
app.on('window-all-closed', () => { |
|||
if (process.platform !== 'darwin') { |
|||
app.quit(); |
|||
} |
|||
}); |
|||
|
|||
app.on('activate', () => { |
|||
if (BrowserWindow.getAllWindows().length === 0) { |
|||
createWindow(); |
|||
} |
|||
}); |
@ -0,0 +1,28 @@ |
|||
{ |
|||
"name": "daily-notification-electron-test", |
|||
"version": "1.0.0", |
|||
"description": "Minimal Electron test app for Daily Notification Plugin", |
|||
"main": "main.js", |
|||
"scripts": { |
|||
"start": "electron .", |
|||
"dev": "electron . --dev", |
|||
"build": "webpack --mode=production", |
|||
"build-web": "webpack --mode=production", |
|||
"electron": "npm run build-web && electron ." |
|||
}, |
|||
"keywords": ["capacitor", "electron", "notifications", "test"], |
|||
"author": "Matthew Raymer", |
|||
"license": "MIT", |
|||
"dependencies": { |
|||
"@capacitor/core": "^5.0.0", |
|||
"@capacitor/cli": "^5.0.0", |
|||
"electron": "^25.0.0" |
|||
}, |
|||
"devDependencies": { |
|||
"webpack": "^5.88.0", |
|||
"webpack-cli": "^5.1.0", |
|||
"html-webpack-plugin": "^5.5.0", |
|||
"typescript": "^5.0.0", |
|||
"ts-loader": "^9.4.0" |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
const { contextBridge, ipcRenderer } = require('electron'); |
|||
|
|||
// Expose protected methods that allow the renderer process to use
|
|||
// the ipcRenderer without exposing the entire object
|
|||
contextBridge.exposeInMainWorld('electronAPI', { |
|||
configurePlugin: (options) => ipcRenderer.invoke('configure-plugin', options), |
|||
scheduleNotification: (options) => ipcRenderer.invoke('schedule-notification', options), |
|||
getDebugInfo: () => ipcRenderer.invoke('get-debug-info'), |
|||
getPerformanceMetrics: () => ipcRenderer.invoke('get-performance-metrics') |
|||
}); |
@ -0,0 +1,107 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Daily Notification - Electron Test</title> |
|||
<style> |
|||
body { |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|||
margin: 0; |
|||
padding: 20px; |
|||
background-color: #f5f5f5; |
|||
} |
|||
.container { |
|||
max-width: 600px; |
|||
margin: 0 auto; |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|||
} |
|||
h1 { |
|||
color: #333; |
|||
text-align: center; |
|||
margin-bottom: 30px; |
|||
} |
|||
.status { |
|||
background: #fff3e0; |
|||
padding: 15px; |
|||
border-radius: 8px; |
|||
margin-bottom: 20px; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
color: #f57c00; |
|||
} |
|||
.button-grid { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
gap: 10px; |
|||
margin-bottom: 20px; |
|||
} |
|||
button { |
|||
background: #f57c00; |
|||
color: white; |
|||
border: none; |
|||
padding: 12px 16px; |
|||
border-radius: 6px; |
|||
cursor: pointer; |
|||
font-size: 14px; |
|||
transition: background-color 0.2s; |
|||
} |
|||
button:hover { |
|||
background: #ef6c00; |
|||
} |
|||
button:disabled { |
|||
background: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
.log-container { |
|||
background: #f8f9fa; |
|||
border: 1px solid #dee2e6; |
|||
border-radius: 6px; |
|||
padding: 15px; |
|||
height: 300px; |
|||
overflow-y: auto; |
|||
font-family: 'Courier New', monospace; |
|||
font-size: 12px; |
|||
} |
|||
.timestamp { |
|||
color: #666; |
|||
font-weight: bold; |
|||
} |
|||
pre { |
|||
background: #e9ecef; |
|||
padding: 8px; |
|||
border-radius: 4px; |
|||
margin: 5px 0; |
|||
overflow-x: auto; |
|||
} |
|||
.clear-button { |
|||
background: #dc3545; |
|||
margin-top: 10px; |
|||
width: 100%; |
|||
} |
|||
.clear-button:hover { |
|||
background: #c82333; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="container"> |
|||
<h1>⚡ Daily Notification Plugin - Electron Test</h1> |
|||
|
|||
<div class="status" id="status">Ready</div> |
|||
|
|||
<div class="button-grid"> |
|||
<button id="configure">Configure Plugin</button> |
|||
<button id="schedule">Schedule Notification</button> |
|||
<button id="debug-info">Debug Info</button> |
|||
<button id="performance">Performance Metrics</button> |
|||
</div> |
|||
|
|||
<div class="log-container" id="log"></div> |
|||
<button class="clear-button" id="clear-log">Clear Log</button> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,121 @@ |
|||
// Electron test interface
|
|||
class TestApp { |
|||
private statusElement: HTMLElement; |
|||
private logElement: HTMLElement; |
|||
|
|||
constructor() { |
|||
this.statusElement = document.getElementById('status')!; |
|||
this.logElement = document.getElementById('log')!; |
|||
this.setupEventListeners(); |
|||
this.log('Electron Test app initialized'); |
|||
} |
|||
|
|||
private setupEventListeners() { |
|||
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure()); |
|||
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule()); |
|||
document.getElementById('debug-info')?.addEventListener('click', () => this.testDebugInfo()); |
|||
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance()); |
|||
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog()); |
|||
} |
|||
|
|||
private async testConfigure() { |
|||
try { |
|||
this.log('Testing Electron configuration...'); |
|||
const result = await (window as any).electronAPI.configurePlugin({ |
|||
storage: 'mock', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true |
|||
}); |
|||
|
|||
if (result.success) { |
|||
this.log('✅ Electron Configuration successful'); |
|||
this.updateStatus('Configured'); |
|||
} else { |
|||
this.log(`❌ Configuration failed: ${result.error}`); |
|||
} |
|||
} catch (error) { |
|||
this.log(`❌ Configuration error: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testSchedule() { |
|||
try { |
|||
this.log('Testing Electron notification scheduling...'); |
|||
const result = await (window as any).electronAPI.scheduleNotification({ |
|||
url: 'https://api.example.com/daily-content', |
|||
time: '09:00', |
|||
title: 'Daily Electron Test Notification', |
|||
body: 'This is a test notification from the Electron test app' |
|||
}); |
|||
|
|||
if (result.success) { |
|||
this.log('✅ Electron Notification scheduled successfully'); |
|||
this.updateStatus('Scheduled'); |
|||
} else { |
|||
this.log(`❌ Scheduling failed: ${result.error}`); |
|||
} |
|||
} catch (error) { |
|||
this.log(`❌ Scheduling error: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testDebugInfo() { |
|||
try { |
|||
this.log('Testing Electron debug info...'); |
|||
const result = await (window as any).electronAPI.getDebugInfo(); |
|||
|
|||
if (result.success) { |
|||
this.log('🔍 Electron Debug Info:', result.data); |
|||
this.updateStatus(`Debug: ${result.data.status}`); |
|||
} else { |
|||
this.log(`❌ Debug info failed: ${result.error}`); |
|||
} |
|||
} catch (error) { |
|||
this.log(`❌ Debug info error: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testPerformance() { |
|||
try { |
|||
this.log('Testing Electron performance metrics...'); |
|||
const result = await (window as any).electronAPI.getPerformanceMetrics(); |
|||
|
|||
if (result.success) { |
|||
this.log('📊 Electron Performance Metrics:', result.data); |
|||
this.updateStatus(`Performance: ${result.data.overallScore}/100`); |
|||
} else { |
|||
this.log(`❌ Performance check failed: ${result.error}`); |
|||
} |
|||
} catch (error) { |
|||
this.log(`❌ Performance error: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private log(message: string, data?: any) { |
|||
const timestamp = new Date().toLocaleTimeString(); |
|||
const logEntry = document.createElement('div'); |
|||
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`; |
|||
if (data) { |
|||
logEntry.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`; |
|||
} |
|||
this.logElement.appendChild(logEntry); |
|||
this.logElement.scrollTop = this.logElement.scrollHeight; |
|||
} |
|||
|
|||
private clearLog() { |
|||
this.logElement.innerHTML = ''; |
|||
this.log('Log cleared'); |
|||
} |
|||
|
|||
private updateStatus(status: string) { |
|||
this.statusElement.textContent = status; |
|||
} |
|||
} |
|||
|
|||
// Initialize app when DOM is ready
|
|||
document.addEventListener('DOMContentLoaded', () => { |
|||
new TestApp(); |
|||
}); |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"target": "ES2020", |
|||
"module": "ES2020", |
|||
"moduleResolution": "node", |
|||
"strict": true, |
|||
"esModuleInterop": true, |
|||
"skipLibCheck": true, |
|||
"forceConsistentCasingInFileNames": true, |
|||
"outDir": "./dist", |
|||
"rootDir": "./src" |
|||
}, |
|||
"include": ["src/**/*"], |
|||
"exclude": ["node_modules", "dist"] |
|||
} |
@ -0,0 +1,28 @@ |
|||
const path = require('path'); |
|||
const HtmlWebpackPlugin = require('html-webpack-plugin'); |
|||
|
|||
module.exports = { |
|||
entry: './src/index.ts', |
|||
module: { |
|||
rules: [ |
|||
{ |
|||
test: /\.ts$/, |
|||
use: 'ts-loader', |
|||
exclude: /node_modules/, |
|||
}, |
|||
], |
|||
}, |
|||
resolve: { |
|||
extensions: ['.ts', '.js'], |
|||
}, |
|||
output: { |
|||
filename: 'bundle.js', |
|||
path: path.resolve(__dirname, 'dist'), |
|||
clean: true, |
|||
}, |
|||
plugins: [ |
|||
new HtmlWebpackPlugin({ |
|||
template: './src/index.html', |
|||
}), |
|||
], |
|||
}; |
@ -0,0 +1,105 @@ |
|||
# Dependencies |
|||
node_modules/ |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# Build outputs |
|||
dist/ |
|||
build/ |
|||
*.tsbuildinfo |
|||
|
|||
# Capacitor |
|||
android/ |
|||
ios/ |
|||
.capacitor/ |
|||
|
|||
# IDE and editor files |
|||
.vscode/ |
|||
.idea/ |
|||
*.swp |
|||
*.swo |
|||
*~ |
|||
|
|||
# OS generated files |
|||
.DS_Store |
|||
.DS_Store? |
|||
._* |
|||
.Spotlight-V100 |
|||
.Trashes |
|||
ehthumbs.db |
|||
Thumbs.db |
|||
|
|||
# Logs |
|||
logs |
|||
*.log |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage/ |
|||
*.lcov |
|||
|
|||
# nyc test coverage |
|||
.nyc_output |
|||
|
|||
# Dependency directories |
|||
jspm_packages/ |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional eslint cache |
|||
.eslintcache |
|||
|
|||
# Microbundle cache |
|||
.rpt2_cache/ |
|||
.rts2_cache_cjs/ |
|||
.rts2_cache_es/ |
|||
.rts2_cache_umd/ |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variables file |
|||
.env |
|||
.env.test |
|||
.env.local |
|||
.env.development.local |
|||
.env.test.local |
|||
.env.production.local |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# next.js build output |
|||
.next |
|||
|
|||
# nuxt.js build output |
|||
.nuxt |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# Serverless directories |
|||
.serverless/ |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
@ -0,0 +1,25 @@ |
|||
import { CapacitorConfig } from '@capacitor/cli'; |
|||
|
|||
const config: CapacitorConfig = { |
|||
appId: 'com.timesafari.dailynotification.iostest', |
|||
appName: 'Daily Notification iOS Test', |
|||
webDir: 'dist', |
|||
server: { |
|||
iosScheme: 'capacitor' |
|||
}, |
|||
plugins: { |
|||
DailyNotification: { |
|||
storage: 'shared', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true |
|||
} |
|||
}, |
|||
ios: { |
|||
scheme: 'Daily Notification iOS Test' |
|||
} |
|||
}; |
|||
|
|||
export default config; |
@ -0,0 +1,29 @@ |
|||
{ |
|||
"name": "daily-notification-ios-test", |
|||
"version": "1.0.0", |
|||
"description": "Minimal iOS test app for Daily Notification Plugin", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"build": "webpack --mode=production", |
|||
"dev": "webpack serve --mode=development", |
|||
"ios": "npx cap run ios", |
|||
"sync": "npx cap sync ios", |
|||
"open": "npx cap open ios" |
|||
}, |
|||
"keywords": ["capacitor", "ios", "notifications", "test"], |
|||
"author": "Matthew Raymer", |
|||
"license": "MIT", |
|||
"dependencies": { |
|||
"@capacitor/core": "^5.0.0", |
|||
"@capacitor/ios": "^5.0.0", |
|||
"@capacitor/cli": "^5.0.0" |
|||
}, |
|||
"devDependencies": { |
|||
"webpack": "^5.88.0", |
|||
"webpack-cli": "^5.1.0", |
|||
"webpack-dev-server": "^4.15.0", |
|||
"html-webpack-plugin": "^5.5.0", |
|||
"typescript": "^5.0.0", |
|||
"ts-loader": "^9.4.0" |
|||
} |
|||
} |
@ -0,0 +1,108 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>Daily Notification - iOS Test</title> |
|||
<style> |
|||
body { |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|||
margin: 0; |
|||
padding: 20px; |
|||
background-color: #f5f5f5; |
|||
} |
|||
.container { |
|||
max-width: 600px; |
|||
margin: 0 auto; |
|||
background: white; |
|||
border-radius: 12px; |
|||
padding: 20px; |
|||
box-shadow: 0 2px 10px rgba(0,0,0,0.1); |
|||
} |
|||
h1 { |
|||
color: #333; |
|||
text-align: center; |
|||
margin-bottom: 30px; |
|||
} |
|||
.status { |
|||
background: #e8f5e8; |
|||
padding: 15px; |
|||
border-radius: 8px; |
|||
margin-bottom: 20px; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
color: #2e7d32; |
|||
} |
|||
.button-grid { |
|||
display: grid; |
|||
grid-template-columns: 1fr 1fr; |
|||
gap: 10px; |
|||
margin-bottom: 20px; |
|||
} |
|||
button { |
|||
background: #2e7d32; |
|||
color: white; |
|||
border: none; |
|||
padding: 12px 16px; |
|||
border-radius: 6px; |
|||
cursor: pointer; |
|||
font-size: 14px; |
|||
transition: background-color 0.2s; |
|||
} |
|||
button:hover { |
|||
background: #1b5e20; |
|||
} |
|||
button:disabled { |
|||
background: #ccc; |
|||
cursor: not-allowed; |
|||
} |
|||
.log-container { |
|||
background: #f8f9fa; |
|||
border: 1px solid #dee2e6; |
|||
border-radius: 6px; |
|||
padding: 15px; |
|||
height: 300px; |
|||
overflow-y: auto; |
|||
font-family: 'Courier New', monospace; |
|||
font-size: 12px; |
|||
} |
|||
.timestamp { |
|||
color: #666; |
|||
font-weight: bold; |
|||
} |
|||
pre { |
|||
background: #e9ecef; |
|||
padding: 8px; |
|||
border-radius: 4px; |
|||
margin: 5px 0; |
|||
overflow-x: auto; |
|||
} |
|||
.clear-button { |
|||
background: #dc3545; |
|||
margin-top: 10px; |
|||
width: 100%; |
|||
} |
|||
.clear-button:hover { |
|||
background: #c82333; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="container"> |
|||
<h1>🍎 Daily Notification Plugin - iOS Test</h1> |
|||
|
|||
<div class="status" id="status">Ready</div> |
|||
|
|||
<div class="button-grid"> |
|||
<button id="configure">Configure Plugin</button> |
|||
<button id="schedule">Schedule Notification</button> |
|||
<button id="rolling-window">Maintain Window</button> |
|||
<button id="window-stats">Window Stats</button> |
|||
<button id="performance">Performance Metrics</button> |
|||
</div> |
|||
|
|||
<div class="log-container" id="log"></div> |
|||
<button class="clear-button" id="clear-log">Clear Log</button> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,153 @@ |
|||
import { Capacitor } from '@capacitor/core'; |
|||
|
|||
// Mock plugin for development
|
|||
const DailyNotification = { |
|||
async configure(options: any) { |
|||
console.log('Configure called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async scheduleDailyNotification(options: any) { |
|||
console.log('Schedule called:', options); |
|||
return Promise.resolve(); |
|||
}, |
|||
async maintainRollingWindow() { |
|||
console.log('Maintain rolling window called'); |
|||
return Promise.resolve(); |
|||
}, |
|||
async getRollingWindowStats() { |
|||
return Promise.resolve({ |
|||
stats: '64 pending notifications, 20 daily limit', |
|||
maintenanceNeeded: false, |
|||
timeUntilNextMaintenance: 900000 |
|||
}); |
|||
}, |
|||
async getPerformanceMetrics() { |
|||
return Promise.resolve({ |
|||
overallScore: 88, |
|||
databasePerformance: 92, |
|||
memoryEfficiency: 85, |
|||
batteryEfficiency: 90, |
|||
objectPoolEfficiency: 88, |
|||
totalDatabaseQueries: 120, |
|||
averageMemoryUsage: 22.3, |
|||
objectPoolHits: 38, |
|||
backgroundCpuUsage: 1.8, |
|||
totalNetworkRequests: 8, |
|||
recommendations: ['Enable background tasks', 'Optimize memory usage'] |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
// Test interface
|
|||
class TestApp { |
|||
private statusElement: HTMLElement; |
|||
private logElement: HTMLElement; |
|||
|
|||
constructor() { |
|||
this.statusElement = document.getElementById('status')!; |
|||
this.logElement = document.getElementById('log')!; |
|||
this.setupEventListeners(); |
|||
this.log('iOS Test app initialized'); |
|||
} |
|||
|
|||
private setupEventListeners() { |
|||
document.getElementById('configure')?.addEventListener('click', () => this.testConfigure()); |
|||
document.getElementById('schedule')?.addEventListener('click', () => this.testSchedule()); |
|||
document.getElementById('rolling-window')?.addEventListener('click', () => this.testRollingWindow()); |
|||
document.getElementById('window-stats')?.addEventListener('click', () => this.testWindowStats()); |
|||
document.getElementById('performance')?.addEventListener('click', () => this.testPerformance()); |
|||
document.getElementById('clear-log')?.addEventListener('click', () => this.clearLog()); |
|||
} |
|||
|
|||
private async testConfigure() { |
|||
try { |
|||
this.log('Testing iOS configuration...'); |
|||
await DailyNotification.configure({ |
|||
storage: 'shared', |
|||
ttlSeconds: 1800, |
|||
prefetchLeadMinutes: 15, |
|||
enableETagSupport: true, |
|||
enableErrorHandling: true, |
|||
enablePerformanceOptimization: true |
|||
}); |
|||
this.log('✅ iOS Configuration successful'); |
|||
this.updateStatus('Configured'); |
|||
} catch (error) { |
|||
this.log(`❌ Configuration failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testSchedule() { |
|||
try { |
|||
this.log('Testing iOS notification scheduling...'); |
|||
await DailyNotification.scheduleDailyNotification({ |
|||
url: 'https://api.example.com/daily-content', |
|||
time: '09:00', |
|||
title: 'Daily iOS Test Notification', |
|||
body: 'This is a test notification from the iOS test app' |
|||
}); |
|||
this.log('✅ iOS Notification scheduled successfully'); |
|||
this.updateStatus('Scheduled'); |
|||
} catch (error) { |
|||
this.log(`❌ iOS Scheduling failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testRollingWindow() { |
|||
try { |
|||
this.log('Testing iOS rolling window maintenance...'); |
|||
await DailyNotification.maintainRollingWindow(); |
|||
this.log('✅ Rolling window maintenance completed'); |
|||
this.updateStatus('Rolling Window Maintained'); |
|||
} catch (error) { |
|||
this.log(`❌ Rolling window maintenance failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testWindowStats() { |
|||
try { |
|||
this.log('Testing iOS rolling window stats...'); |
|||
const stats = await DailyNotification.getRollingWindowStats(); |
|||
this.log(`📊 Rolling Window Stats:`, stats); |
|||
this.updateStatus(`Window: ${stats.maintenanceNeeded ? 'Needs Maintenance' : 'OK'}`); |
|||
} catch (error) { |
|||
this.log(`❌ Window stats check failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private async testPerformance() { |
|||
try { |
|||
this.log('Testing iOS performance metrics...'); |
|||
const metrics = await DailyNotification.getPerformanceMetrics(); |
|||
this.log(`📊 iOS Performance Metrics:`, metrics); |
|||
this.updateStatus(`Performance: ${metrics.overallScore}/100`); |
|||
} catch (error) { |
|||
this.log(`❌ Performance check failed: ${error}`); |
|||
} |
|||
} |
|||
|
|||
private log(message: string, data?: any) { |
|||
const timestamp = new Date().toLocaleTimeString(); |
|||
const logEntry = document.createElement('div'); |
|||
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`; |
|||
if (data) { |
|||
logEntry.innerHTML += `<pre>${JSON.stringify(data, null, 2)}</pre>`; |
|||
} |
|||
this.logElement.appendChild(logEntry); |
|||
this.logElement.scrollTop = this.logElement.scrollHeight; |
|||
} |
|||
|
|||
private clearLog() { |
|||
this.logElement.innerHTML = ''; |
|||
this.log('Log cleared'); |
|||
} |
|||
|
|||
private updateStatus(status: string) { |
|||
this.statusElement.textContent = status; |
|||
} |
|||
} |
|||
|
|||
// Initialize app when DOM is ready
|
|||
document.addEventListener('DOMContentLoaded', () => { |
|||
new TestApp(); |
|||
}); |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"compilerOptions": { |
|||
"target": "ES2020", |
|||
"module": "ES2020", |
|||
"moduleResolution": "node", |
|||
"strict": true, |
|||
"esModuleInterop": true, |
|||
"skipLibCheck": true, |
|||
"forceConsistentCasingInFileNames": true, |
|||
"outDir": "./dist", |
|||
"rootDir": "./src" |
|||
}, |
|||
"include": ["src/**/*"], |
|||
"exclude": ["node_modules", "dist"] |
|||
} |
@ -0,0 +1,33 @@ |
|||
const path = require('path'); |
|||
const HtmlWebpackPlugin = require('html-webpack-plugin'); |
|||
|
|||
module.exports = { |
|||
entry: './src/index.ts', |
|||
module: { |
|||
rules: [ |
|||
{ |
|||
test: /\.ts$/, |
|||
use: 'ts-loader', |
|||
exclude: /node_modules/, |
|||
}, |
|||
], |
|||
}, |
|||
resolve: { |
|||
extensions: ['.ts', '.js'], |
|||
}, |
|||
output: { |
|||
filename: 'bundle.js', |
|||
path: path.resolve(__dirname, 'dist'), |
|||
clean: true, |
|||
}, |
|||
plugins: [ |
|||
new HtmlWebpackPlugin({ |
|||
template: './src/index.html', |
|||
}), |
|||
], |
|||
devServer: { |
|||
static: './dist', |
|||
port: 3001, |
|||
hot: true, |
|||
}, |
|||
}; |
@ -0,0 +1,39 @@ |
|||
#!/bin/bash |
|||
|
|||
# Android Test App Setup Script |
|||
echo "🚀 Setting up Android Test App..." |
|||
|
|||
cd android-test |
|||
|
|||
# Install dependencies |
|||
echo "📦 Installing dependencies..." |
|||
npm install |
|||
|
|||
# Install Capacitor CLI globally if not present |
|||
if ! command -v cap &> /dev/null; then |
|||
echo "🔧 Installing Capacitor CLI globally..." |
|||
npm install -g @capacitor/cli |
|||
fi |
|||
|
|||
# Initialize Capacitor |
|||
echo "⚡ Initializing Capacitor..." |
|||
npx cap init "Daily Notification Android Test" "com.timesafari.dailynotification.androidtest" |
|||
|
|||
# Add Android platform |
|||
echo "📱 Adding Android platform..." |
|||
npx cap add android |
|||
|
|||
# Build web assets |
|||
echo "🔨 Building web assets..." |
|||
npm run build |
|||
|
|||
# Sync to native |
|||
echo "🔄 Syncing to native..." |
|||
npx cap sync android |
|||
|
|||
echo "✅ Android test app setup complete!" |
|||
echo "" |
|||
echo "Next steps:" |
|||
echo "1. Open Android Studio: npx cap open android" |
|||
echo "2. Run on device/emulator: npx cap run android" |
|||
echo "3. Or build web version: npm run dev" |
@ -0,0 +1,21 @@ |
|||
#!/bin/bash |
|||
|
|||
# Electron Test App Setup Script |
|||
echo "🚀 Setting up Electron Test App..." |
|||
|
|||
cd electron-test |
|||
|
|||
# Install dependencies |
|||
echo "📦 Installing dependencies..." |
|||
npm install |
|||
|
|||
# Build web assets |
|||
echo "🔨 Building web assets..." |
|||
npm run build-web |
|||
|
|||
echo "✅ Electron test app setup complete!" |
|||
echo "" |
|||
echo "Next steps:" |
|||
echo "1. Run Electron app: npm start" |
|||
echo "2. Run in dev mode: npm run dev" |
|||
echo "3. Build and run: npm run electron" |
@ -0,0 +1,39 @@ |
|||
#!/bin/bash |
|||
|
|||
# iOS Test App Setup Script |
|||
echo "🚀 Setting up iOS Test App..." |
|||
|
|||
cd ios-test |
|||
|
|||
# Install dependencies |
|||
echo "📦 Installing dependencies..." |
|||
npm install |
|||
|
|||
# Install Capacitor CLI globally if not present |
|||
if ! command -v cap &> /dev/null; then |
|||
echo "🔧 Installing Capacitor CLI globally..." |
|||
npm install -g @capacitor/cli |
|||
fi |
|||
|
|||
# Initialize Capacitor |
|||
echo "⚡ Initializing Capacitor..." |
|||
npx cap init "Daily Notification iOS Test" "com.timesafari.dailynotification.iostest" |
|||
|
|||
# Add iOS platform |
|||
echo "🍎 Adding iOS platform..." |
|||
npx cap add ios |
|||
|
|||
# Build web assets |
|||
echo "🔨 Building web assets..." |
|||
npm run build |
|||
|
|||
# Sync to native |
|||
echo "🔄 Syncing to native..." |
|||
npx cap sync ios |
|||
|
|||
echo "✅ iOS test app setup complete!" |
|||
echo "" |
|||
echo "Next steps:" |
|||
echo "1. Open Xcode: npx cap open ios" |
|||
echo "2. Run on device/simulator: npx cap run ios" |
|||
echo "3. Or build web version: npm run dev" |
Loading…
Reference in new issue