Browse Source
- 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.master
7 changed files with 553 additions and 609 deletions
@ -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<void> { |
|||
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<unknown> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<StorageStats> { |
|||
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<string | null>; |
|||
setItem(key: string, value: string): Promise<void>; |
|||
removeItem(key: string): Promise<void>; |
|||
getAllKeys(): Promise<string[]>; |
|||
} |
|||
|
|||
/** |
|||
* TTL Manager for managing time-to-live data |
|||
*/ |
|||
class TTLManager { |
|||
private ttlMap: Map<string, NodeJS.Timeout> = 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<string | null> { |
|||
return localStorage.getItem(key); |
|||
} |
|||
|
|||
async setItem(key: string, value: string): Promise<void> { |
|||
localStorage.setItem(key, value); |
|||
} |
|||
|
|||
async removeItem(key: string): Promise<void> { |
|||
localStorage.removeItem(key); |
|||
} |
|||
|
|||
async getAllKeys(): Promise<string[]> { |
|||
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}`); |
|||
} |
|||
} |
|||
} |
@ -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<string, number> = {}; |
|||
|
|||
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; |
@ -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<string, NotificationResponse> = new Map(); |
|||
private settings: NotificationSettings = { |
|||
sound: true, |
|||
priority: 'default', |
|||
timezone: 'UTC' |
|||
}; |
|||
private scheduledNotifications: Set<string> = new Set(); |
|||
private activeDid?: string; // Stored for future use in web implementation
|
|||
|
|||
getCurrentActiveDid(): string | undefined { |
|||
return this.activeDid; |
|||
} |
|||
|
|||
async configure(_options: Record<string, unknown>): Promise<void> { |
|||
// Web implementation placeholder
|
|||
// Configuration applied for web platform
|
|||
} |
|||
|
|||
async maintainRollingWindow(): Promise<void> { |
|||
// 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<void> { |
|||
// Request exact alarm permission called on web platform
|
|||
} |
|||
|
|||
async openExactAlarmSettings(): Promise<void> { |
|||
// 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<void> { |
|||
// 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<NotificationResponse | null> { |
|||
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> { |
|||
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<NotificationStatus> { |
|||
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<void> { |
|||
this.settings = { ...this.settings, ...settings }; |
|||
// Settings updated
|
|||
} |
|||
|
|||
/** |
|||
* Get battery status (mock implementation for web) |
|||
*/ |
|||
async getBatteryStatus(): Promise<BatteryStatus> { |
|||
// Mock implementation for web
|
|||
return { |
|||
level: 100, |
|||
isCharging: false, |
|||
powerState: 0, |
|||
isOptimizationExempt: false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Request battery optimization exemption (mock for web) |
|||
*/ |
|||
async requestBatteryOptimizationExemption(): Promise<void> { |
|||
// Battery optimization exemption requested (web mock)
|
|||
} |
|||
|
|||
/** |
|||
* Set adaptive scheduling (mock for web) |
|||
*/ |
|||
async setAdaptiveScheduling(_options: { enabled: boolean }): Promise<void> { |
|||
// Adaptive scheduling set
|
|||
} |
|||
|
|||
/** |
|||
* Get power state (mock for web) |
|||
*/ |
|||
async getPowerState(): Promise<PowerState> { |
|||
return { |
|||
powerState: 0, |
|||
isOptimizationExempt: false |
|||
}; |
|||
} |
|||
|
|||
/** |
|||
* Check permissions (web implementation) |
|||
*/ |
|||
async checkPermissions(): Promise<PermissionStatus> { |
|||
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<PermissionStatus> { |
|||
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<void> { |
|||
// 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<void> { |
|||
// 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<void> { |
|||
// Dual notification scheduled (web mock implementation)
|
|||
// Mock implementation combining content fetch and user notification
|
|||
} |
|||
|
|||
/** |
|||
* Get dual schedule status (web implementation) |
|||
*/ |
|||
async getDualScheduleStatus(): Promise<DualScheduleStatus> { |
|||
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<void> { |
|||
// Dual schedule config updated (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Cancel dual schedule (web implementation) |
|||
*/ |
|||
async cancelDualSchedule(): Promise<void> { |
|||
// Dual schedule cancelled (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Pause dual schedule (web implementation) |
|||
*/ |
|||
async pauseDualSchedule(): Promise<void> { |
|||
// Dual schedule paused (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Resume dual schedule (web implementation) |
|||
*/ |
|||
async resumeDualSchedule(): Promise<void> { |
|||
// Dual schedule resumed (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Get content cache (web implementation) |
|||
*/ |
|||
async getContentCache(): Promise<Record<string, unknown>> { |
|||
return {}; // Mock empty cache
|
|||
} |
|||
|
|||
/** |
|||
* Clear content cache (web implementation) |
|||
*/ |
|||
async clearContentCache(): Promise<void> { |
|||
// Content cache cleared (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Get content history (web implementation) |
|||
*/ |
|||
async getContentHistory(): Promise<ContentFetchResult[]> { |
|||
return []; // Mock empty history
|
|||
} |
|||
|
|||
/** |
|||
* Register callback (web implementation) |
|||
*/ |
|||
async registerCallback(_name: string, _callback: (...args: unknown[]) => void): Promise<void> { |
|||
// Callback registered (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Unregister callback (web implementation) |
|||
*/ |
|||
async unregisterCallback(_name: string): Promise<void> { |
|||
// Callback unregistered (web mock implementation)
|
|||
} |
|||
|
|||
/** |
|||
* Get registered callbacks (web implementation) |
|||
*/ |
|||
async getRegisteredCallbacks(): Promise<string[]> { |
|||
return []; // Mock empty callback list
|
|||
} |
|||
|
|||
/** |
|||
* 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
|
|||
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<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); |
|||
} |
|||
|
|||
// Phase 1: ActiveDid Management Methods Implementation
|
|||
async setActiveDidFromHost(activeDid: string): Promise<void> { |
|||
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>): 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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
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<void> { |
|||
// Schedule daily reminder called (web mock implementation)
|
|||
// Mock implementation for web
|
|||
} |
|||
|
|||
async cancelDailyReminder(_reminderId: string): Promise<void> { |
|||
// Cancel daily reminder called (web mock implementation)
|
|||
// Mock implementation for web
|
|||
} |
|||
|
|||
async getScheduledReminders(): Promise<DailyReminderInfo[]> { |
|||
// Get scheduled reminders called (web mock implementation)
|
|||
return []; // Mock empty array for web
|
|||
} |
|||
|
|||
async updateDailyReminder(_reminderId: string, _options: DailyReminderOptions): Promise<void> { |
|||
// Update daily reminder called (web mock implementation)
|
|||
// Mock implementation for web
|
|||
} |
|||
} |
@ -0,0 +1,3 @@ |
|||
1b8e9ec8f48f956e8c971b5c5451603a53b524b082ca6be768cca3cd03dfa54f |
|||
# Generated on 2025-10-08T03:25:29.479Z |
|||
# Files: dist/esm/**/*.d.ts |
Loading…
Reference in new issue