Browse Source

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.
master
Matthew Raymer 3 days ago
parent
commit
9a679cd69b
  1. 8
      src/definitions.ts
  2. 13
      src/index.ts
  3. 330
      src/timesafari-storage-adapter.ts
  4. 207
      src/vite-plugin.ts
  5. 601
      src/web/index.ts
  6. 3
      types-checksum.txt
  7. 0
      www/index.html

8
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;

13
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<DailyNotificationPlugin>('DailyNotification', {
web: async () => new DailyNotificationWeb(),
});
const DailyNotification = registerPlugin<DailyNotificationPlugin>('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 };

330
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<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}`);
}
}
}

207
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<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;

601
src/web/index.ts

@ -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
}
}

3
types-checksum.txt

@ -0,0 +1,3 @@
1b8e9ec8f48f956e8c971b5c5451603a53b524b082ca6be768cca3cd03dfa54f
# Generated on 2025-10-08T03:25:29.479Z
# Files: dist/esm/**/*.d.ts

0
www/index.html

Loading…
Cancel
Save