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.
This commit is contained in:
@@ -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
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
Normal file
330
src/timesafari-storage-adapter.ts
Normal file
@@ -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
Normal file
207
src/vite-plugin.ts
Normal file
@@ -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
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user