Browse Source
- Update interface definitions to match test requirements - Add missing methods to DailyNotificationPlugin interface - Fix ContentHandler signature compatibility - Switch Jest test environment from node to jsdom - Install required jsdom dependencies - Update mock plugin objects with all required methods - Fix timestamp type mismatches in testsmaster
11 changed files with 1486 additions and 91 deletions
File diff suppressed because it is too large
@ -1,107 +1,297 @@ |
|||
/** |
|||
* DailyNotificationWeb implementation |
|||
* Web platform implementation for the Daily Notification Plugin |
|||
* DailyNotification Web Implementation |
|||
* |
|||
* Web platform implementation with proper mock functionality |
|||
* Aligned with updated interface definitions |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 2.0.0 |
|||
*/ |
|||
|
|||
import { WebPlugin } from '@capacitor/core'; |
|||
import type { DailyNotificationPlugin, NotificationOptions, NotificationSettings, NotificationResponse, NotificationStatus, BatteryStatus, PowerState } from '../definitions'; |
|||
import { DailyNotificationPlugin, NotificationOptions, NotificationResponse, NotificationStatus, NotificationSettings, BatteryStatus, PowerState, PermissionStatus } from '../definitions'; |
|||
|
|||
export class DailyNotificationWeb extends WebPlugin implements DailyNotificationPlugin { |
|||
private nextNotificationTime: number = 0; |
|||
private lastNotificationTime: number = 0; |
|||
private settings: NotificationSettings & { adaptiveScheduling?: boolean } = {}; |
|||
export class DailyNotificationWeb implements DailyNotificationPlugin { |
|||
private notifications: Map<string, NotificationResponse> = new Map(); |
|||
private settings: NotificationSettings = { |
|||
sound: true, |
|||
priority: 'default', |
|||
timezone: 'UTC' |
|||
}; |
|||
private scheduledNotifications: Set<string> = new Set(); |
|||
|
|||
constructor() { |
|||
super({ |
|||
name: 'DailyNotification', |
|||
platforms: ['web'] |
|||
}); |
|||
} |
|||
|
|||
async initialize(options: NotificationOptions): Promise<void> { |
|||
// Web implementation - store settings
|
|||
this.settings = { ...options }; |
|||
} |
|||
|
|||
async scheduleDailyNotification(options: NotificationOptions | any): Promise<void> { |
|||
// Web implementation using browser notifications
|
|||
if (!('Notification' in window)) { |
|||
throw new Error('This browser does not support notifications'); |
|||
/** |
|||
* Schedule a daily notification |
|||
*/ |
|||
async scheduleDailyNotification(options: NotificationOptions): Promise<void> { |
|||
// Validate required parameters
|
|||
if (!options.time) { |
|||
throw new Error('Time parameter is required'); |
|||
} |
|||
|
|||
if (Notification.permission === 'granted') { |
|||
new Notification(options.title || 'Daily Update', { |
|||
body: options.body || 'Your daily update is ready', |
|||
icon: '/icon.png', |
|||
badge: '/badge.png', |
|||
tag: 'daily-notification' |
|||
}); |
|||
// Create notification content
|
|||
const notification: NotificationResponse = { |
|||
id: this.generateId(), |
|||
title: options.title || 'Daily Update', |
|||
body: options.body || 'Your daily notification is ready', |
|||
timestamp: Date.now(), |
|||
url: options.url |
|||
}; |
|||
|
|||
this.nextNotificationTime = Date.now() + (24 * 60 * 60 * 1000); // 24 hours from now
|
|||
this.lastNotificationTime = Date.now(); |
|||
} else if (Notification.permission !== 'denied') { |
|||
const permission = await Notification.requestPermission(); |
|||
if (permission === 'granted') { |
|||
await this.scheduleDailyNotification(options); |
|||
} |
|||
// Store notification
|
|||
this.notifications.set(notification.id, notification); |
|||
this.scheduledNotifications.add(notification.id); |
|||
|
|||
// Schedule the notification using browser APIs if available
|
|||
if ('Notification' in window && 'serviceWorker' in navigator) { |
|||
await this.scheduleBrowserNotification(notification, options); |
|||
} |
|||
|
|||
console.log('Web notification scheduled:', notification); |
|||
} |
|||
|
|||
/** |
|||
* Get the last notification |
|||
*/ |
|||
async getLastNotification(): Promise<NotificationResponse | null> { |
|||
return { |
|||
title: 'Last Notification', |
|||
body: 'This was the last notification', |
|||
timestamp: new Date(this.lastNotificationTime).toISOString() |
|||
}; |
|||
const notifications = Array.from(this.notifications.values()); |
|||
if (notifications.length === 0) { |
|||
return null; |
|||
} |
|||
|
|||
// Return the most recent notification
|
|||
return notifications.sort((a, b) => b.timestamp - a.timestamp)[0]; |
|||
} |
|||
|
|||
/** |
|||
* Cancel all notifications |
|||
*/ |
|||
async cancelAllNotifications(): Promise<void> { |
|||
// Web implementation - clear scheduled notifications
|
|||
this.nextNotificationTime = 0; |
|||
this.scheduledNotifications.clear(); |
|||
this.notifications.clear(); |
|||
|
|||
// Cancel browser notifications if available
|
|||
if ('Notification' in window) { |
|||
Notification.requestPermission().then(permission => { |
|||
if (permission === 'granted') { |
|||
// Clear any existing browser notifications
|
|||
console.log('Browser notifications cleared'); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Get notification status |
|||
*/ |
|||
async getNotificationStatus(): Promise<NotificationStatus> { |
|||
return { |
|||
lastNotificationTime: this.lastNotificationTime, |
|||
nextNotificationTime: this.nextNotificationTime, |
|||
isEnabled: 'Notification' in window, |
|||
isScheduled: this.scheduledNotifications.size > 0, |
|||
lastNotificationTime: this.getLastNotificationTime(), |
|||
nextNotificationTime: this.getNextNotificationTime(), |
|||
pending: this.scheduledNotifications.size, |
|||
settings: this.settings, |
|||
isScheduled: this.nextNotificationTime > 0 |
|||
error: undefined |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Update notification settings |
|||
*/ |
|||
async updateSettings(settings: NotificationSettings): Promise<void> { |
|||
this.settings = { ...this.settings, ...settings }; |
|||
|
|||
if (settings.time) { |
|||
this.nextNotificationTime = Date.now() + (24 * 60 * 60 * 1000); // 24 hours from now
|
|||
} |
|||
console.log('Web notification settings updated:', this.settings); |
|||
} |
|||
|
|||
/** |
|||
* Get battery status (mock implementation) |
|||
*/ |
|||
async getBatteryStatus(): Promise<BatteryStatus> { |
|||
// Web implementation - return mock battery status
|
|||
// Mock battery status for web
|
|||
return { |
|||
level: 100, |
|||
isCharging: false, |
|||
powerState: 1, |
|||
isOptimizationExempt: true |
|||
powerState: 0, |
|||
isOptimizationExempt: false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Request battery optimization exemption (web not applicable) |
|||
*/ |
|||
async requestBatteryOptimizationExemption(): Promise<void> { |
|||
// Web implementation - no-op
|
|||
console.log('Battery optimization exemption requested (web platform)'); |
|||
console.log('Battery optimization exemption not applicable on web'); |
|||
} |
|||
|
|||
/** |
|||
* Set adaptive scheduling (web not applicable) |
|||
*/ |
|||
async setAdaptiveScheduling(options: { enabled: boolean }): Promise<void> { |
|||
// Web implementation - store setting
|
|||
this.settings.adaptiveScheduling = options.enabled; |
|||
console.log('Adaptive scheduling not applicable on web:', options); |
|||
} |
|||
|
|||
/** |
|||
* Get power state (mock implementation) |
|||
*/ |
|||
async getPowerState(): Promise<PowerState> { |
|||
// Web implementation - return mock power state
|
|||
return { |
|||
powerState: 1, |
|||
isOptimizationExempt: true |
|||
powerState: 0, |
|||
isOptimizationExempt: false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Check permissions |
|||
*/ |
|||
async checkPermissions(): Promise<PermissionStatus> { |
|||
if (!('Notification' in window)) { |
|||
return { |
|||
status: 'denied', |
|||
granted: false, |
|||
notifications: 'denied', |
|||
alert: false, |
|||
badge: false, |
|||
sound: false, |
|||
lockScreen: false, |
|||
carPlay: false |
|||
}; |
|||
} |
|||
|
|||
const permission = Notification.permission; |
|||
return { |
|||
status: permission, |
|||
granted: permission === 'granted', |
|||
notifications: permission as any, |
|||
alert: permission === 'granted', |
|||
badge: permission === 'granted', |
|||
sound: permission === 'granted', |
|||
lockScreen: permission === 'granted', |
|||
carPlay: false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Request permissions |
|||
*/ |
|||
async requestPermissions(): Promise<PermissionStatus> { |
|||
if (!('Notification' in window)) { |
|||
throw new Error('Notifications not supported in this browser'); |
|||
} |
|||
|
|||
try { |
|||
const permission = await Notification.requestPermission(); |
|||
return { |
|||
status: permission, |
|||
granted: permission === 'granted', |
|||
notifications: permission as any, |
|||
alert: permission === 'granted', |
|||
badge: permission === 'granted', |
|||
sound: permission === 'granted', |
|||
lockScreen: permission === 'granted', |
|||
carPlay: false |
|||
}; |
|||
} catch (error) { |
|||
console.error('Error requesting notification permissions:', error); |
|||
throw error; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Schedule browser notification using native APIs |
|||
*/ |
|||
private async scheduleBrowserNotification(notification: NotificationResponse, options: NotificationOptions): Promise<void> { |
|||
if (!('Notification' in window)) { |
|||
return; |
|||
} |
|||
|
|||
const permission = await Notification.requestPermission(); |
|||
if (permission !== 'granted') { |
|||
console.warn('Notification permission not granted'); |
|||
return; |
|||
} |
|||
|
|||
// Calculate next notification time
|
|||
const nextTime = this.calculateNextNotificationTime(options.time!); |
|||
const delay = nextTime.getTime() - Date.now(); |
|||
|
|||
if (delay > 0) { |
|||
setTimeout(() => { |
|||
this.showBrowserNotification(notification, options); |
|||
}, delay); |
|||
} else { |
|||
// Show immediately if time has passed
|
|||
this.showBrowserNotification(notification, options); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Show browser notification |
|||
*/ |
|||
private showBrowserNotification(notification: NotificationResponse, options: NotificationOptions): void { |
|||
if (!('Notification' in window)) { |
|||
return; |
|||
} |
|||
|
|||
const browserNotification = new Notification(notification.title, { |
|||
body: notification.body, |
|||
icon: '/favicon.ico', |
|||
tag: notification.id, |
|||
requireInteraction: false, |
|||
silent: !options.sound |
|||
}); |
|||
|
|||
// Handle notification click
|
|||
browserNotification.onclick = () => { |
|||
if (notification.url) { |
|||
window.open(notification.url, '_blank'); |
|||
} |
|||
browserNotification.close(); |
|||
}; |
|||
|
|||
// Auto-close after 10 seconds
|
|||
setTimeout(() => { |
|||
browserNotification.close(); |
|||
}, 10000); |
|||
} |
|||
|
|||
/** |
|||
* Calculate next notification time |
|||
*/ |
|||
private calculateNextNotificationTime(timeString: string): Date { |
|||
const [hours, minutes] = timeString.split(':').map(Number); |
|||
const now = new Date(); |
|||
const next = new Date(now); |
|||
|
|||
next.setHours(hours, minutes, 0, 0); |
|||
|
|||
// If time has passed today, schedule for tomorrow
|
|||
if (next <= now) { |
|||
next.setDate(next.getDate() + 1); |
|||
} |
|||
|
|||
return next; |
|||
} |
|||
|
|||
/** |
|||
* Generate unique ID |
|||
*/ |
|||
private generateId(): string { |
|||
return `web-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; |
|||
} |
|||
|
|||
/** |
|||
* Get last notification time |
|||
*/ |
|||
private async getLastNotificationTime(): Promise<number> { |
|||
const last = await this.getLastNotification(); |
|||
return last ? last.timestamp : 0; |
|||
} |
|||
|
|||
/** |
|||
* Get next notification time |
|||
*/ |
|||
private getNextNotificationTime(): number { |
|||
// For web, return 24 hours from now as placeholder
|
|||
return Date.now() + (24 * 60 * 60 * 1000); |
|||
} |
|||
} |
Loading…
Reference in new issue