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 |
* DailyNotification Web Implementation |
||||
* Web platform implementation for the Daily Notification Plugin |
* |
||||
|
* 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 { DailyNotificationPlugin, NotificationOptions, NotificationResponse, NotificationStatus, NotificationSettings, BatteryStatus, PowerState, PermissionStatus } from '../definitions'; |
||||
import type { DailyNotificationPlugin, NotificationOptions, NotificationSettings, NotificationResponse, NotificationStatus, BatteryStatus, PowerState } from '../definitions'; |
|
||||
|
|
||||
export class DailyNotificationWeb extends WebPlugin implements DailyNotificationPlugin { |
export class DailyNotificationWeb implements DailyNotificationPlugin { |
||||
private nextNotificationTime: number = 0; |
private notifications: Map<string, NotificationResponse> = new Map(); |
||||
private lastNotificationTime: number = 0; |
private settings: NotificationSettings = { |
||||
private settings: NotificationSettings & { adaptiveScheduling?: boolean } = {}; |
sound: true, |
||||
|
priority: 'default', |
||||
constructor() { |
timezone: 'UTC' |
||||
super({ |
}; |
||||
name: 'DailyNotification', |
private scheduledNotifications: Set<string> = new Set(); |
||||
platforms: ['web'] |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
async initialize(options: NotificationOptions): Promise<void> { |
/** |
||||
// Web implementation - store settings
|
* Schedule a daily notification |
||||
this.settings = { ...options }; |
*/ |
||||
|
async scheduleDailyNotification(options: NotificationOptions): Promise<void> { |
||||
|
// Validate required parameters
|
||||
|
if (!options.time) { |
||||
|
throw new Error('Time parameter is required'); |
||||
} |
} |
||||
|
|
||||
async scheduleDailyNotification(options: NotificationOptions | any): Promise<void> { |
// Create notification content
|
||||
// Web implementation using browser notifications
|
const notification: NotificationResponse = { |
||||
if (!('Notification' in window)) { |
id: this.generateId(), |
||||
throw new Error('This browser does not support notifications'); |
title: options.title || 'Daily Update', |
||||
} |
body: options.body || 'Your daily notification is ready', |
||||
|
timestamp: Date.now(), |
||||
|
url: options.url |
||||
|
}; |
||||
|
|
||||
if (Notification.permission === 'granted') { |
// Store notification
|
||||
new Notification(options.title || 'Daily Update', { |
this.notifications.set(notification.id, notification); |
||||
body: options.body || 'Your daily update is ready', |
this.scheduledNotifications.add(notification.id); |
||||
icon: '/icon.png', |
|
||||
badge: '/badge.png', |
|
||||
tag: 'daily-notification' |
|
||||
}); |
|
||||
|
|
||||
this.nextNotificationTime = Date.now() + (24 * 60 * 60 * 1000); // 24 hours from now
|
// Schedule the notification using browser APIs if available
|
||||
this.lastNotificationTime = Date.now(); |
if ('Notification' in window && 'serviceWorker' in navigator) { |
||||
} else if (Notification.permission !== 'denied') { |
await this.scheduleBrowserNotification(notification, options); |
||||
const permission = await Notification.requestPermission(); |
|
||||
if (permission === 'granted') { |
|
||||
await this.scheduleDailyNotification(options); |
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
|
console.log('Web notification scheduled:', notification); |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Get the last notification |
||||
|
*/ |
||||
async getLastNotification(): Promise<NotificationResponse | null> { |
async getLastNotification(): Promise<NotificationResponse | null> { |
||||
return { |
const notifications = Array.from(this.notifications.values()); |
||||
title: 'Last Notification', |
if (notifications.length === 0) { |
||||
body: 'This was the last notification', |
return null; |
||||
timestamp: new Date(this.lastNotificationTime).toISOString() |
} |
||||
}; |
|
||||
|
// Return the most recent notification
|
||||
|
return notifications.sort((a, b) => b.timestamp - a.timestamp)[0]; |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Cancel all notifications |
||||
|
*/ |
||||
async cancelAllNotifications(): Promise<void> { |
async cancelAllNotifications(): Promise<void> { |
||||
// Web implementation - clear scheduled notifications
|
this.scheduledNotifications.clear(); |
||||
this.nextNotificationTime = 0; |
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> { |
async getNotificationStatus(): Promise<NotificationStatus> { |
||||
return { |
return { |
||||
lastNotificationTime: this.lastNotificationTime, |
isEnabled: 'Notification' in window, |
||||
nextNotificationTime: this.nextNotificationTime, |
isScheduled: this.scheduledNotifications.size > 0, |
||||
|
lastNotificationTime: this.getLastNotificationTime(), |
||||
|
nextNotificationTime: this.getNextNotificationTime(), |
||||
|
pending: this.scheduledNotifications.size, |
||||
settings: this.settings, |
settings: this.settings, |
||||
isScheduled: this.nextNotificationTime > 0 |
error: undefined |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Update notification settings |
||||
|
*/ |
||||
async updateSettings(settings: NotificationSettings): Promise<void> { |
async updateSettings(settings: NotificationSettings): Promise<void> { |
||||
this.settings = { ...this.settings, ...settings }; |
this.settings = { ...this.settings, ...settings }; |
||||
|
console.log('Web notification settings updated:', this.settings); |
||||
if (settings.time) { |
|
||||
this.nextNotificationTime = Date.now() + (24 * 60 * 60 * 1000); // 24 hours from now
|
|
||||
} |
|
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Get battery status (mock implementation) |
||||
|
*/ |
||||
async getBatteryStatus(): Promise<BatteryStatus> { |
async getBatteryStatus(): Promise<BatteryStatus> { |
||||
// Web implementation - return mock battery status
|
// Mock battery status for web
|
||||
return { |
return { |
||||
level: 100, |
level: 100, |
||||
isCharging: false, |
isCharging: false, |
||||
powerState: 1, |
powerState: 0, |
||||
isOptimizationExempt: true |
isOptimizationExempt: false |
||||
}; |
}; |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Request battery optimization exemption (web not applicable) |
||||
|
*/ |
||||
async requestBatteryOptimizationExemption(): Promise<void> { |
async requestBatteryOptimizationExemption(): Promise<void> { |
||||
// Web implementation - no-op
|
console.log('Battery optimization exemption not applicable on web'); |
||||
console.log('Battery optimization exemption requested (web platform)'); |
|
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Set adaptive scheduling (web not applicable) |
||||
|
*/ |
||||
async setAdaptiveScheduling(options: { enabled: boolean }): Promise<void> { |
async setAdaptiveScheduling(options: { enabled: boolean }): Promise<void> { |
||||
// Web implementation - store setting
|
console.log('Adaptive scheduling not applicable on web:', options); |
||||
this.settings.adaptiveScheduling = options.enabled; |
|
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Get power state (mock implementation) |
||||
|
*/ |
||||
async getPowerState(): Promise<PowerState> { |
async getPowerState(): Promise<PowerState> { |
||||
// Web implementation - return mock power state
|
|
||||
return { |
return { |
||||
powerState: 1, |
powerState: 0, |
||||
isOptimizationExempt: true |
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