From 9a679cd69bf94bc6d01f62830775be0b4f38e928 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Wed, 8 Oct 2025 06:17:24 +0000 Subject: [PATCH] refactor: remove web support for native-first architecture - Remove IndexedDB storage implementation (~90 lines) - Remove 'web' platform from type definitions - Remove web platform registration from plugin - Update storage factory to exclude web platform - Remove web-specific SSR safety checks from vite-plugin - Delete web implementation files (src/web/, www/) BREAKING CHANGE: Web (PWA) platform support removed. Plugin now supports Android, iOS, and Electron platforms only. --- src/definitions.ts | 8 +- src/index.ts | 13 +- src/timesafari-storage-adapter.ts | 330 ++++++++++++++++ src/vite-plugin.ts | 207 ++++++++++ src/web/index.ts | 601 ------------------------------ types-checksum.txt | 3 + www/index.html | 0 7 files changed, 553 insertions(+), 609 deletions(-) create mode 100644 src/timesafari-storage-adapter.ts create mode 100644 src/vite-plugin.ts delete mode 100644 src/web/index.ts create mode 100644 types-checksum.txt delete mode 100644 www/index.html diff --git a/src/definitions.ts b/src/definitions.ts index 6f35b18..5b42429 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -172,7 +172,7 @@ export interface ConfigureOptions { retentionDays?: number; // Phase 2: TimeSafari ActiveDid Integration Enhancement activeDidIntegration?: { - platform: 'android' | 'ios' | 'web' | 'electron'; + platform: 'android' | 'ios' | 'electron'; storageType: 'plugin-managed' | 'host-managed'; jwtExpirationSeconds?: number; apiServer?: string; @@ -554,7 +554,7 @@ export interface ActiveDidChangeEventEnhanced extends ActiveDidChangeEvent { // TimeSafari-specific Platform Configuration export interface TimeSafariPlatformConfig { - platform: 'android' | 'ios' | 'web' | 'electron'; + platform: 'android' | 'ios' | 'electron'; storageType: 'plugin-managed' | 'host-managed'; syncStrategy: 'immediate' | 'batched' | 'scheduled'; permissions: { @@ -571,7 +571,7 @@ export interface TimeSafariPlatformConfig { } export interface ActiveDidIntegrationConfig { - platform: 'android' | 'ios' | 'web' | 'electron'; + platform: 'android' | 'ios' | 'electron'; storageType: 'plugin-managed' | 'host-managed'; jwtExpirationSeconds?: number; apiServer?: string; @@ -615,7 +615,7 @@ export type AppLifecycleEvent = * Phase 3: Coordination status for debugging and monitoring */ export interface CoordinationStatus { - platform: 'android' | 'ios' | 'web' | 'electron'; + platform: 'android' | 'ios' | 'electron'; coordinationActive: boolean; coordinationPaused: boolean; autoSync?: boolean; diff --git a/src/index.ts b/src/index.ts index 589b450..7b56abc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,16 +5,21 @@ import { registerPlugin } from '@capacitor/core'; import type { DailyNotificationPlugin } from './definitions'; -import { DailyNotificationWeb } from './web'; import { observability, EVENT_CODES } from './observability'; -const DailyNotification = registerPlugin('DailyNotification', { - web: async () => new DailyNotificationWeb(), -}); +const DailyNotification = registerPlugin('DailyNotification'); // Initialize observability observability.logEvent('INFO', EVENT_CODES.FETCH_START, 'Daily Notification Plugin initialized'); export * from './definitions'; export * from './observability'; +export * from './timesafari-integration'; +export * from './timesafari-storage-adapter'; +export * from './timesafari-community-integration'; +export * from './android/timesafari-android-config'; +export * from './ios/timesafari-ios-config'; +export * from './services/DailyNotificationService'; +export * from './services/DatabaseIntegrationService'; +export * from './utils/PlatformServiceMixin'; export { DailyNotification }; diff --git a/src/timesafari-storage-adapter.ts b/src/timesafari-storage-adapter.ts new file mode 100644 index 0000000..26a0796 --- /dev/null +++ b/src/timesafari-storage-adapter.ts @@ -0,0 +1,330 @@ +/** + * TimeSafari Storage Adapter + * + * Implements storage adapter pattern for TimeSafari database integration. + * Supports SQLite (native) and fallback storage. + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import { TimeSafariStorageAdapter } from './timesafari-integration'; + +/** + * TimeSafari Storage Adapter Implementation + * + * Provides unified storage interface across Android (SQLite) and iOS (SQLite) + * platforms with TimeSafari-specific patterns. + */ +export class TimeSafariStorageAdapterImpl implements TimeSafariStorageAdapter { + private storage: StorageInterface; + private prefix: string; + private ttlManager: TTLManager; + + constructor(storage: StorageInterface, prefix = 'timesafari_notifications') { + this.storage = storage; + this.prefix = prefix; + this.ttlManager = new TTLManager(); + } + + /** + * Store data with TTL support + * + * @param key - Storage key + * @param value - Value to store + * @param ttlSeconds - Optional TTL in seconds + */ + async store(key: string, value: unknown, ttlSeconds?: number): Promise { + try { + const prefixedKey = this.getPrefixedKey(key); + const storageValue = { + data: value, + timestamp: Date.now(), + ttl: ttlSeconds ? Date.now() + (ttlSeconds * 1000) : null + }; + + await this.storage.setItem(prefixedKey, JSON.stringify(storageValue)); + + // Register TTL if provided + if (ttlSeconds) { + this.ttlManager.registerTTL(prefixedKey, ttlSeconds); + } + + // Successfully stored key + } catch (error) { + throw new Error(`Failed to store key: ${key}`); + } + } + + /** + * Retrieve data with TTL validation + * + * @param key - Storage key + * @returns Stored value or null if not found/expired + */ + async retrieve(key: string): Promise { + try { + const prefixedKey = this.getPrefixedKey(key); + const stored = await this.storage.getItem(prefixedKey); + + if (!stored) { + return null; + } + + const storageValue = JSON.parse(stored); + + // Check TTL + if (storageValue.ttl && Date.now() > storageValue.ttl) { + // Key expired, cleaning up + await this.remove(key); // Clean up expired data + return null; + } + + // Successfully retrieved key + return storageValue.data; + } catch (error) { + // Failed to retrieve key, returning null + return null; + } + } + + /** + * Remove data from storage + * + * @param key - Storage key + */ + async remove(key: string): Promise { + try { + const prefixedKey = this.getPrefixedKey(key); + await this.storage.removeItem(prefixedKey); + this.ttlManager.unregisterTTL(prefixedKey); + + // Successfully removed key + } catch (error) { + throw new Error(`Failed to remove key: ${key}`); + } + } + + /** + * Clear all data with prefix + */ + async clear(): Promise { + try { + const keys = await this.storage.getAllKeys(); + const prefixedKeys = keys.filter(key => key.startsWith(this.prefix)); + + for (const key of prefixedKeys) { + await this.storage.removeItem(key); + this.ttlManager.unregisterTTL(key); + } + + // Successfully cleared storage + } catch (error) { + throw new Error('Failed to clear storage'); + } + } + + /** + * Get prefixed key + * + * @param key - Original key + * @returns Prefixed key + */ + private getPrefixedKey(key: string): string { + return `${this.prefix}_${key}`; + } + + /** + * Clean up expired data + */ + async cleanupExpired(): Promise { + try { + const keys = await this.storage.getAllKeys(); + const prefixedKeys = keys.filter(key => key.startsWith(this.prefix)); + + for (const key of prefixedKeys) { + const stored = await this.storage.getItem(key); + if (stored) { + const storageValue = JSON.parse(stored); + if (storageValue.ttl && Date.now() > storageValue.ttl) { + await this.storage.removeItem(key); + this.ttlManager.unregisterTTL(key); + // Cleaned up expired key + } + } + } + } catch (error) { + // Failed to cleanup expired data, continuing + } + } + + /** + * Get storage statistics + * + * @returns Storage statistics + */ + async getStats(): Promise { + try { + const keys = await this.storage.getAllKeys(); + const prefixedKeys = keys.filter(key => key.startsWith(this.prefix)); + + let totalSize = 0; + let expiredCount = 0; + let validCount = 0; + + for (const key of prefixedKeys) { + const stored = await this.storage.getItem(key); + if (stored) { + totalSize += stored.length; + const storageValue = JSON.parse(stored); + if (storageValue.ttl && Date.now() > storageValue.ttl) { + expiredCount++; + } else { + validCount++; + } + } + } + + return { + totalKeys: prefixedKeys.length, + validKeys: validCount, + expiredKeys: expiredCount, + totalSizeBytes: totalSize, + prefix: this.prefix + }; + } catch (error) { + // Failed to get stats, returning default + return { + totalKeys: 0, + validKeys: 0, + expiredKeys: 0, + totalSizeBytes: 0, + prefix: this.prefix + }; + } + } +} + +/** + * Storage Interface + */ +export interface StorageInterface { + getItem(key: string): Promise; + setItem(key: string, value: string): Promise; + removeItem(key: string): Promise; + getAllKeys(): Promise; +} + +/** + * TTL Manager for managing time-to-live data + */ +class TTLManager { + private ttlMap: Map = new Map(); + + /** + * Register TTL for a key + * + * @param key - Storage key + * @param ttlSeconds - TTL in seconds + */ + registerTTL(key: string, ttlSeconds: number): void { + // Clear existing TTL if any + this.unregisterTTL(key); + + // Set new TTL + const timeout = setTimeout(() => { + // TTL expired for key + this.ttlMap.delete(key); + }, ttlSeconds * 1000); + + this.ttlMap.set(key, timeout); + } + + /** + * Unregister TTL for a key + * + * @param key - Storage key + */ + unregisterTTL(key: string): void { + const timeout = this.ttlMap.get(key); + if (timeout) { + clearTimeout(timeout); + this.ttlMap.delete(key); + } + } + + /** + * Clear all TTLs + */ + clearAll(): void { + for (const timeout of this.ttlMap.values()) { + clearTimeout(timeout); + } + this.ttlMap.clear(); + } +} + +/** + * Storage Statistics + */ +export interface StorageStats { + totalKeys: number; + validKeys: number; + expiredKeys: number; + totalSizeBytes: number; + prefix: string; +} + + +/** + * LocalStorage Storage Implementation for Web (fallback) + */ +export class LocalStorageStorage implements StorageInterface { + async getItem(key: string): Promise { + return localStorage.getItem(key); + } + + async setItem(key: string, value: string): Promise { + localStorage.setItem(key, value); + } + + async removeItem(key: string): Promise { + localStorage.removeItem(key); + } + + async getAllKeys(): Promise { + const keys: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) keys.push(key); + } + return keys; + } +} + +/** + * Storage Factory + */ +export class StorageFactory { + /** + * Create appropriate storage implementation + * + * @param platform - Platform type + * @returns Storage implementation + */ + static createStorage(platform: 'android' | 'ios' | 'electron'): StorageInterface { + switch (platform) { + case 'android': + case 'ios': + // For native platforms, this would integrate with SQLite + // For now, return localStorage as placeholder + return new LocalStorageStorage(); + case 'electron': + // Electron can use either SQLite or localStorage + // For now, return localStorage as placeholder + return new LocalStorageStorage(); + default: + throw new Error(`Unsupported platform: ${platform}`); + } + } +} diff --git a/src/vite-plugin.ts b/src/vite-plugin.ts new file mode 100644 index 0000000..08c20a8 --- /dev/null +++ b/src/vite-plugin.ts @@ -0,0 +1,207 @@ +/** + * Vite Plugin for TimeSafari Daily Notification Plugin + * + * Provides Vite-specific optimizations and integrations for the TimeSafari PWA. + * Handles tree-shaking, SSR safety, and platform-specific builds. + * + * @author Matthew Raymer + * @version 1.0.0 + */ + +import type { Plugin } from 'vite'; + +export interface TimeSafariPluginOptions { + /** + * Enable SSR safety checks + */ + ssrSafe?: boolean; + + /** + * Enable tree-shaking optimizations + */ + treeShaking?: boolean; + + /** + * Platform-specific builds + */ + platforms?: ('android' | 'ios' | 'electron')[]; + + /** + * Bundle size budget in KB + */ + bundleSizeBudget?: number; + + /** + * Enable development mode optimizations + */ + devMode?: boolean; +} + +/** + * TimeSafari Vite Plugin + * + * Provides optimizations and integrations for TimeSafari PWA compatibility. + */ +export function timeSafariPlugin(options: TimeSafariPluginOptions = {}): Plugin { + const { + ssrSafe = true, + treeShaking = true, + platforms = ['android', 'ios'], + bundleSizeBudget = 35, + devMode = false + } = options; + + return { + name: 'timesafari-daily-notification', + + // Plugin configuration + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config(config, { command }): any { + const isDev = command === 'serve'; + + return { + // Build optimizations + build: { + ...config.build, + // Enable tree-shaking + rollupOptions: { + ...config.build?.rollupOptions, + treeshake: treeShaking ? { + moduleSideEffects: false, + propertyReadSideEffects: false, + unknownGlobalSideEffects: false + } : false + } + }, + + // Define constants + define: { + ...config.define, + __TIMESAFARI_SSR_SAFE__: ssrSafe, + __TIMESAFARI_PLATFORMS__: JSON.stringify(platforms), + __TIMESAFARI_BUNDLE_BUDGET__: bundleSizeBudget, + __TIMESAFARI_DEV_MODE__: devMode || isDev + } + }; + }, + + // Transform code for SSR safety + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transform(code, _id): any { + if (!ssrSafe) return null; + + // Check for SSR-unsafe code patterns + const ssrUnsafePatterns = [ + /window\./g, + /document\./g, + /navigator\./g, + /localStorage\./g, + /sessionStorage\./g, + ]; + + let hasUnsafeCode = false; + for (const pattern of ssrUnsafePatterns) { + if (pattern.test(code)) { + hasUnsafeCode = true; + break; + } + } + + if (hasUnsafeCode) { + // Wrap unsafe code in platform checks + const wrappedCode = ` +// SSR-safe wrapper for TimeSafari Daily Notification Plugin +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + ${code} +} else { + // SSR fallback - return mock implementation + // Running in SSR environment, using fallback implementation +} +`; + + return { + code: wrappedCode, + map: null + }; + } + + return null; + }, + + // Generate platform-specific builds + generateBundle(_options, bundle): void { + // Remove any web-specific code (not applicable in native-first architecture) + Object.keys(bundle).forEach(fileName => { + if (fileName.includes('web') || fileName.includes('browser')) { + delete bundle[fileName]; + } + }); + + if (!platforms.includes('android')) { + // Remove Android-specific code + Object.keys(bundle).forEach(fileName => { + if (fileName.includes('android')) { + delete bundle[fileName]; + } + }); + } + + if (!platforms.includes('ios')) { + // Remove iOS-specific code + Object.keys(bundle).forEach(fileName => { + if (fileName.includes('ios')) { + delete bundle[fileName]; + } + }); + } + }, + + // Bundle size analysis + writeBundle(_options, bundle): void { + if (devMode) return; + + let totalSize = 0; + const fileSizes: Record = {}; + + Object.entries(bundle).forEach(([fileName, chunk]) => { + if (chunk.type === 'chunk') { + const size = Buffer.byteLength(chunk.code, 'utf8'); + fileSizes[fileName] = size; + totalSize += size; + } + }); + + const totalSizeKB = totalSize / 1024; + + if (totalSizeKB > bundleSizeBudget) { + // Bundle size exceeds budget + + // Log largest files for debugging (available in fileSizes) + } else { + // Bundle size within budget + } + }, + + // Development server middleware + configureServer(server): void { + if (!devMode) return; + + // Add development-specific middleware + server.middlewares.use('/timesafari-plugin', (_req, res, _next) => { + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + name: 'TimeSafari Daily Notification Plugin', + version: process.env.npm_package_version || '1.0.0', + platforms: platforms, + ssrSafe: ssrSafe, + treeShaking: treeShaking + })); + }); + } + }; +} + +/** + * Default export for easy usage + */ +export default timeSafariPlugin; diff --git a/src/web/index.ts b/src/web/index.ts deleted file mode 100644 index 04eb115..0000000 --- a/src/web/index.ts +++ /dev/null @@ -1,601 +0,0 @@ -/** - * DailyNotification Web Implementation - * - * Web platform implementation with proper mock functionality - * Aligned with updated interface definitions - * - * @author Matthew Raymer - * @version 2.0.0 - */ - -import { - ContentFetchConfig, - UserNotificationConfig, - DualScheduleConfiguration, - DualScheduleStatus, - ContentFetchResult, - DailyReminderOptions, - DailyReminderInfo -} from '../definitions'; - -import { DailyNotificationPlugin, NotificationOptions, NotificationResponse, NotificationStatus, NotificationSettings, BatteryStatus, PowerState, PermissionStatus } from '../definitions'; - -export class DailyNotificationWeb implements DailyNotificationPlugin { - private notifications: Map = new Map(); - private settings: NotificationSettings = { - sound: true, - priority: 'default', - timezone: 'UTC' - }; - private scheduledNotifications: Set = new Set(); - private activeDid?: string; // Stored for future use in web implementation - - getCurrentActiveDid(): string | undefined { - return this.activeDid; - } - - async configure(_options: Record): Promise { - // Web implementation placeholder - // Configuration applied for web platform - } - - async maintainRollingWindow(): Promise { - // Rolling window maintenance for web platform - } - - async getRollingWindowStats(): Promise<{ - stats: string; - maintenanceNeeded: boolean; - timeUntilNextMaintenance: number; - }> { - // Get rolling window stats for web platform - return { - stats: 'Web platform - rolling window not applicable', - maintenanceNeeded: false, - timeUntilNextMaintenance: 0 - }; - } - - async getExactAlarmStatus(): Promise<{ - supported: boolean; - enabled: boolean; - canSchedule: boolean; - fallbackWindow: string; - }> { - // Get exact alarm status for web platform - return { - supported: false, - enabled: false, - canSchedule: false, - fallbackWindow: 'Not applicable on web' - }; - } - - async requestExactAlarmPermission(): Promise { - // Request exact alarm permission called on web platform - } - - async openExactAlarmSettings(): Promise { - // Open exact alarm settings called on web platform - } - - async getRebootRecoveryStatus(): Promise<{ - inProgress: boolean; - lastRecoveryTime: number; - timeSinceLastRecovery: number; - recoveryNeeded: boolean; - }> { - // Get reboot recovery status called on web platform - return { - inProgress: false, - lastRecoveryTime: 0, - timeSinceLastRecovery: 0, - recoveryNeeded: false - }; - } - - /** - * Schedule a daily notification - */ - async scheduleDailyNotification(options: NotificationOptions): Promise { - // Validate required parameters - if (!options.time) { - throw new Error('Time parameter is required'); - } - - // 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 - }; - - // 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); - } - - // Web notification scheduled - } - - /** - * Get the last notification - */ - async getLastNotification(): Promise { - 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 { - 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 - // Browser notifications cleared - } - }); - } - } - - /** - * Get notification status - */ - async getNotificationStatus(): Promise { - return { - isEnabled: 'Notification' in window, - isScheduled: this.scheduledNotifications.size > 0, - lastNotificationTime: this.getLastNotificationTime(), - nextNotificationTime: this.getNextNotificationTime(), - pending: this.scheduledNotifications.size, - settings: this.settings, - error: undefined - }; - } - - /** - * Update notification settings - */ - async updateSettings(settings: NotificationSettings): Promise { - this.settings = { ...this.settings, ...settings }; - // Settings updated - } - - /** - * Get battery status (mock implementation for web) - */ - async getBatteryStatus(): Promise { - // Mock implementation for web - return { - level: 100, - isCharging: false, - powerState: 0, - isOptimizationExempt: false - }; - } - - /** - * Request battery optimization exemption (mock for web) - */ - async requestBatteryOptimizationExemption(): Promise { - // Battery optimization exemption requested (web mock) - } - - /** - * Set adaptive scheduling (mock for web) - */ - async setAdaptiveScheduling(_options: { enabled: boolean }): Promise { - // Adaptive scheduling set - } - - /** - * Get power state (mock for web) - */ - async getPowerState(): Promise { - return { - powerState: 0, - isOptimizationExempt: false - }; - } - - /** - * Check permissions (web implementation) - */ - async checkPermissions(): Promise { - if (!('Notification' in window)) { - return { - notifications: 'denied', - alert: false, - badge: false, - sound: false, - lockScreen: false, - carPlay: false - }; - } - - const permission = Notification.permission; - return { - status: permission, - granted: permission === 'granted', - notifications: permission === 'granted' ? 'granted' : - permission === 'denied' ? 'denied' : 'prompt', - alert: permission === 'granted', - badge: permission === 'granted', - sound: permission === 'granted', - lockScreen: permission === 'granted', - carPlay: false - }; - } - - /** - * Request permissions (web implementation) - */ - async requestPermissions(): Promise { - if (!('Notification' in window)) { - throw new Error('Notifications not supported in this browser'); - } - - await Notification.requestPermission(); - return this.checkPermissions(); - } - - // Dual Scheduling Methods Implementation - - /** - * Schedule content fetch (web implementation) - */ - async scheduleContentFetch(_config: ContentFetchConfig): Promise { - // Content fetch scheduled (web mock implementation) - // Mock implementation - in real app would use Service Worker - } - - /** - * Schedule user notification (web implementation) - */ - async scheduleUserNotification(_config: UserNotificationConfig): Promise { - // User notification scheduled (web mock implementation) - // Mock implementation - in real app would use browser notifications - } - - /** - * Schedule dual notification (web implementation) - */ - async scheduleDualNotification(_config: DualScheduleConfiguration): Promise { - // Dual notification scheduled (web mock implementation) - // Mock implementation combining content fetch and user notification - } - - /** - * Get dual schedule status (web implementation) - */ - async getDualScheduleStatus(): Promise { - return { - contentFetch: { - isEnabled: false, - isScheduled: false, - pendingFetches: 0 - }, - userNotification: { - isEnabled: false, - isScheduled: false, - pendingNotifications: 0 - }, - relationship: { - isLinked: false, - contentAvailable: false - }, - overall: { - isActive: false, - lastActivity: Date.now(), - errorCount: 0, - successRate: 1.0 - } - }; - } - - /** - * Update dual schedule configuration (web implementation) - */ - async updateDualScheduleConfig(_config: DualScheduleConfiguration): Promise { - // Dual schedule config updated (web mock implementation) - } - - /** - * Cancel dual schedule (web implementation) - */ - async cancelDualSchedule(): Promise { - // Dual schedule cancelled (web mock implementation) - } - - /** - * Pause dual schedule (web implementation) - */ - async pauseDualSchedule(): Promise { - // Dual schedule paused (web mock implementation) - } - - /** - * Resume dual schedule (web implementation) - */ - async resumeDualSchedule(): Promise { - // Dual schedule resumed (web mock implementation) - } - - /** - * Get content cache (web implementation) - */ - async getContentCache(): Promise> { - return {}; // Mock empty cache - } - - /** - * Clear content cache (web implementation) - */ - async clearContentCache(): Promise { - // Content cache cleared (web mock implementation) - } - - /** - * Get content history (web implementation) - */ - async getContentHistory(): Promise { - return []; // Mock empty history - } - - /** - * Register callback (web implementation) - */ - async registerCallback(_name: string, _callback: (...args: unknown[]) => void): Promise { - // Callback registered (web mock implementation) - } - - /** - * Unregister callback (web implementation) - */ - async unregisterCallback(_name: string): Promise { - // Callback unregistered (web mock implementation) - } - - /** - * Get registered callbacks (web implementation) - */ - async getRegisteredCallbacks(): Promise { - return []; // Mock empty callback list - } - - /** - * Schedule browser notification using native APIs - */ - private async scheduleBrowserNotification(notification: NotificationResponse, options: NotificationOptions): Promise { - if (!('Notification' in window)) { - return; - } - - const permission = await Notification.requestPermission(); - if (permission !== 'granted') { - console.warn('Notification permission not granted'); - return; - } - - // Calculate next notification time - if (!options.time) { - throw new Error('Time parameter is required for scheduling'); - } - 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 = (): void => { - 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 { - 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); - } - - // Phase 1: ActiveDid Management Methods Implementation - async setActiveDidFromHost(activeDid: string): Promise { - try { - // Setting activeDid from host - - // Store activeDid for future use - this.activeDid = activeDid; - - // ActiveDid set successfully - - } catch (error) { - console.error('DNP-WEB-INDEX: Error setting activeDid from host:', error); - throw error; - } - } - - onActiveDidChange(callback: (newActiveDid: string) => Promise): void { - try { - // Setting up activeDid change listener - - // Set up event listener for activeDidChanged events - document.addEventListener('activeDidChanged', async (event: Event) => { - try { - const eventDetail = (event as CustomEvent).detail; - if (eventDetail && eventDetail.activeDid) { - // ActiveDid changed to new value - - // Clear current cached content - await this.clearCacheForNewIdentity(); - - // Update authentication for new identity - await this.refreshAuthenticationForNewIdentity(eventDetail.activeDid); - - // Call the provided callback - await callback(eventDetail.activeDid); - - // ActiveDid changed processed - } - } catch (error) { - console.error('DNP-WEB-INDEX: Error processing activeDid change:', error); - } - }); - - // ActiveDid change listener configured - - } catch (error) { - console.error('DNP-WEB-INDEX: Error setting up activeDid change listener:', error); - throw error; - } - } - - async refreshAuthenticationForNewIdentity(activeDid: string): Promise { - try { - // Refreshing authentication for activeDid - - // Update current activeDid - this.activeDid = activeDid; - - // Authentication refreshed successfully - - } catch (error) { - console.error('DNP-WEB-INDEX: Error refreshing authentication:', error); - throw error; - } - } - - async clearCacheForNewIdentity(): Promise { - try { - // Clearing cache for new identity - - // Clear content cache - await this.clearContentCache(); - - // Cache cleared successfully - - } catch (error) { - console.error('DNP-WEB-INDEX: Error clearing cache for new identity:', error); - throw error; - } - } - - async updateBackgroundTaskIdentity(activeDid: string): Promise { - try { - // Updating background task identity - - // Update current activeDid - this.activeDid = activeDid; - - // Background task identity updated successfully - - } catch (error) { - console.error('DNP-WEB-INDEX: Error updating background task identity:', error); - throw error; - } - } - - // Static Daily Reminder Methods - async scheduleDailyReminder(_options: DailyReminderOptions): Promise { - // Schedule daily reminder called (web mock implementation) - // Mock implementation for web - } - - async cancelDailyReminder(_reminderId: string): Promise { - // Cancel daily reminder called (web mock implementation) - // Mock implementation for web - } - - async getScheduledReminders(): Promise { - // Get scheduled reminders called (web mock implementation) - return []; // Mock empty array for web - } - - async updateDailyReminder(_reminderId: string, _options: DailyReminderOptions): Promise { - // Update daily reminder called (web mock implementation) - // Mock implementation for web - } -} \ No newline at end of file diff --git a/types-checksum.txt b/types-checksum.txt new file mode 100644 index 0000000..8ad7d23 --- /dev/null +++ b/types-checksum.txt @@ -0,0 +1,3 @@ +1b8e9ec8f48f956e8c971b5c5451603a53b524b082ca6be768cca3cd03dfa54f +# Generated on 2025-10-08T03:25:29.479Z +# Files: dist/esm/**/*.d.ts diff --git a/www/index.html b/www/index.html deleted file mode 100644 index e69de29..0000000