| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- /**
- * @fileoverview Desktop notification utilities for cross-platform notifications
- * @author GrabZilla Development Team
- * @version 2.1.0
- * @since 2024-01-01
- */
- /**
- * DESKTOP NOTIFICATION UTILITIES
- *
- * Provides cross-platform desktop notifications with fallbacks
- * for different operating systems and notification systems.
- */
- /**
- * Notification types with default configurations
- */
- const NOTIFICATION_TYPES = {
- SUCCESS: {
- type: 'success',
- icon: 'assets/icons/download.svg',
- sound: true,
- timeout: 5000,
- color: '#00a63e'
- },
- ERROR: {
- type: 'error',
- icon: 'assets/icons/close.svg',
- sound: true,
- timeout: 8000,
- color: '#e7000b'
- },
- WARNING: {
- type: 'warning',
- icon: 'assets/icons/clock.svg',
- sound: false,
- timeout: 6000,
- color: '#ff9500'
- },
- INFO: {
- type: 'info',
- icon: 'assets/icons/logo.svg',
- sound: false,
- timeout: 4000,
- color: '#155dfc'
- },
- PROGRESS: {
- type: 'progress',
- icon: 'assets/icons/download.svg',
- sound: false,
- timeout: 0, // Persistent until updated
- color: '#155dfc'
- }
- };
- /**
- * Desktop Notification Manager
- *
- * Handles desktop notifications with cross-platform support
- * and intelligent fallbacks for different environments.
- */
- class DesktopNotificationManager {
- constructor() {
- this.activeNotifications = new Map();
- this.notificationQueue = [];
- this.isElectronAvailable = typeof window !== 'undefined' &&
- window.electronAPI &&
- typeof window.electronAPI === 'object';
- this.isBrowserNotificationSupported = typeof Notification !== 'undefined';
- this.maxActiveNotifications = 5;
-
- // Initialize notification permissions
- this.initializePermissions();
- }
- /**
- * Initialize notification permissions
- */
- async initializePermissions() {
- if (this.isBrowserNotificationSupported && !this.isElectronAvailable) {
- try {
- if (Notification.permission === 'default') {
- await Notification.requestPermission();
- }
- } catch (error) {
- console.warn('Failed to request notification permission:', error);
- }
- }
- }
- /**
- * Show desktop notification with automatic fallback
- * @param {Object} options - Notification options
- * @returns {Promise<Object>} Notification result
- */
- async showNotification(options = {}) {
- const config = this.prepareNotificationConfig(options);
-
- try {
- // Try Electron native notifications first
- if (this.isElectronAvailable) {
- return await this.showElectronNotification(config);
- }
-
- // Fallback to browser notifications
- if (this.isBrowserNotificationSupported) {
- return await this.showBrowserNotification(config);
- }
-
- // Final fallback to in-app notification
- return this.showInAppNotification(config);
-
- } catch (error) {
- console.error('Failed to show notification:', error);
- // Always fallback to in-app notification
- return this.showInAppNotification(config);
- }
- }
- /**
- * Show success notification for completed downloads
- * @param {string} filename - Downloaded filename
- * @param {Object} options - Additional options
- */
- async showDownloadSuccess(filename, options = {}) {
- const config = {
- type: NOTIFICATION_TYPES.SUCCESS,
- title: 'Download Complete',
- message: `Successfully downloaded: ${filename}`,
- ...options
- };
- return this.showNotification(config);
- }
- /**
- * Show error notification for failed downloads
- * @param {string} filename - Failed filename or URL
- * @param {string} error - Error message
- * @param {Object} options - Additional options
- */
- async showDownloadError(filename, error, options = {}) {
- const config = {
- type: NOTIFICATION_TYPES.ERROR,
- title: 'Download Failed',
- message: `Failed to download ${filename}: ${error}`,
- ...options
- };
- return this.showNotification(config);
- }
- /**
- * Show progress notification for ongoing downloads
- * @param {string} filename - Downloading filename
- * @param {number} progress - Progress percentage (0-100)
- * @param {Object} options - Additional options
- */
- async showDownloadProgress(filename, progress, options = {}) {
- const config = {
- type: NOTIFICATION_TYPES.PROGRESS,
- title: 'Downloading...',
- message: `${filename} - ${progress}% complete`,
- id: `progress_${filename}`,
- persistent: true,
- ...options
- };
- return this.showNotification(config);
- }
- /**
- * Show conversion progress notification
- * @param {string} filename - Converting filename
- * @param {number} progress - Progress percentage (0-100)
- * @param {Object} options - Additional options
- */
- async showConversionProgress(filename, progress, options = {}) {
- const config = {
- type: NOTIFICATION_TYPES.PROGRESS,
- title: 'Converting...',
- message: `${filename} - ${progress}% converted`,
- id: `conversion_${filename}`,
- persistent: true,
- ...options
- };
- return this.showNotification(config);
- }
- /**
- * Show dependency missing notification
- * @param {string} dependency - Missing dependency name
- * @param {Object} options - Additional options
- */
- async showDependencyMissing(dependency, options = {}) {
- const config = {
- type: NOTIFICATION_TYPES.ERROR,
- title: 'Missing Dependency',
- message: `${dependency} is required but not found. Please check the application setup.`,
- timeout: 10000, // Show longer for critical errors
- ...options
- };
- return this.showNotification(config);
- }
- /**
- * Prepare notification configuration with defaults
- * @param {Object} options - User options
- * @returns {Object} Complete configuration
- */
- prepareNotificationConfig(options) {
- const typeConfig = options.type || NOTIFICATION_TYPES.INFO;
-
- return {
- id: options.id || this.generateNotificationId(),
- title: options.title || 'GrabZilla',
- message: options.message || '',
- icon: options.icon || typeConfig.icon,
- sound: options.sound !== undefined ? options.sound : typeConfig.sound,
- timeout: options.timeout !== undefined ? options.timeout : typeConfig.timeout,
- persistent: options.persistent || false,
- onClick: options.onClick || null,
- onClose: options.onClose || null,
- type: typeConfig,
- timestamp: new Date()
- };
- }
- /**
- * Show notification using Electron's native system
- * @param {Object} config - Notification configuration
- * @returns {Promise<Object>} Result
- */
- async showElectronNotification(config) {
- try {
- const result = await window.electronAPI.showNotification({
- title: config.title,
- message: config.message,
- icon: config.icon,
- sound: config.sound,
- timeout: config.timeout / 1000, // Convert to seconds
- wait: config.persistent
- });
- if (result.success) {
- this.trackNotification(config);
- }
- return {
- success: result.success,
- method: 'electron',
- id: config.id,
- error: result.error
- };
- } catch (error) {
- console.error('Electron notification failed:', error);
- throw error;
- }
- }
- /**
- * Show notification using browser's Notification API
- * @param {Object} config - Notification configuration
- * @returns {Promise<Object>} Result
- */
- async showBrowserNotification(config) {
- try {
- if (Notification.permission !== 'granted') {
- throw new Error('Notification permission not granted');
- }
- const notification = new Notification(config.title, {
- body: config.message,
- icon: config.icon,
- silent: !config.sound,
- tag: config.id // Prevents duplicate notifications
- });
- // Handle events
- if (config.onClick) {
- notification.onclick = config.onClick;
- }
- if (config.onClose) {
- notification.onclose = config.onClose;
- }
- // Auto-close if not persistent
- if (!config.persistent && config.timeout > 0) {
- setTimeout(() => {
- notification.close();
- }, config.timeout);
- }
- this.trackNotification(config, notification);
- return {
- success: true,
- method: 'browser',
- id: config.id,
- notification
- };
- } catch (error) {
- console.error('Browser notification failed:', error);
- throw error;
- }
- }
- /**
- * Show in-app notification as final fallback
- * @param {Object} config - Notification configuration
- * @returns {Object} Result
- */
- showInAppNotification(config) {
- try {
- // Dispatch custom event for UI components to handle
- const notificationEvent = new CustomEvent('app-notification', {
- detail: {
- id: config.id,
- title: config.title,
- message: config.message,
- type: config.type.type,
- icon: config.icon,
- timeout: config.timeout,
- persistent: config.persistent,
- timestamp: config.timestamp
- }
- });
- document.dispatchEvent(notificationEvent);
- this.trackNotification(config);
- return {
- success: true,
- method: 'in-app',
- id: config.id
- };
- } catch (error) {
- console.error('In-app notification failed:', error);
- return {
- success: false,
- method: 'in-app',
- id: config.id,
- error: error.message
- };
- }
- }
- /**
- * Update existing notification (for progress updates)
- * @param {string} id - Notification ID
- * @param {Object} updates - Updates to apply
- */
- async updateNotification(id, updates) {
- const existing = this.activeNotifications.get(id);
- if (!existing) {
- // Create new notification if doesn't exist
- return this.showNotification({ id, ...updates });
- }
- const updatedConfig = { ...existing.config, ...updates };
-
- // Close existing and show updated
- this.closeNotification(id);
- return this.showNotification(updatedConfig);
- }
- /**
- * Close specific notification
- * @param {string} id - Notification ID
- */
- closeNotification(id) {
- const notification = this.activeNotifications.get(id);
- if (notification) {
- if (notification.instance && notification.instance.close) {
- notification.instance.close();
- }
- this.activeNotifications.delete(id);
- }
- }
- /**
- * Close all active notifications
- */
- closeAllNotifications() {
- for (const [id] of this.activeNotifications) {
- this.closeNotification(id);
- }
- }
- /**
- * Track active notification
- * @param {Object} config - Notification configuration
- * @param {Object} instance - Notification instance (if available)
- */
- trackNotification(config, instance = null) {
- this.activeNotifications.set(config.id, {
- config,
- instance,
- timestamp: config.timestamp
- });
- // Clean up old notifications
- this.cleanupOldNotifications();
- }
- /**
- * Clean up old notifications to prevent memory leaks
- */
- cleanupOldNotifications() {
- const maxAge = 5 * 60 * 1000; // 5 minutes
- const now = new Date();
- for (const [id, notification] of this.activeNotifications) {
- if (now - notification.timestamp > maxAge) {
- this.closeNotification(id);
- }
- }
- // Limit total active notifications
- if (this.activeNotifications.size > this.maxActiveNotifications) {
- const oldest = Array.from(this.activeNotifications.entries())
- .sort((a, b) => a[1].timestamp - b[1].timestamp)
- .slice(0, this.activeNotifications.size - this.maxActiveNotifications);
- oldest.forEach(([id]) => this.closeNotification(id));
- }
- }
- /**
- * Generate unique notification ID
- * @returns {string} Unique ID
- */
- generateNotificationId() {
- return `notification_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
- }
- /**
- * Get notification statistics
- * @returns {Object} Statistics
- */
- getStats() {
- return {
- active: this.activeNotifications.size,
- electronAvailable: this.isElectronAvailable,
- browserSupported: this.isBrowserNotificationSupported,
- permission: this.isBrowserNotificationSupported ? Notification.permission : 'unknown'
- };
- }
- /**
- * Test notification system
- * @returns {Promise<Object>} Test results
- */
- async testNotifications() {
- const results = {
- electron: false,
- browser: false,
- inApp: false,
- errors: []
- };
- // Test Electron notifications
- if (this.isElectronAvailable) {
- try {
- const result = await this.showElectronNotification({
- id: 'test_electron',
- title: 'Test Notification',
- message: 'Electron notifications are working',
- timeout: 2000
- });
- results.electron = result.success;
- } catch (error) {
- results.errors.push(`Electron: ${error.message}`);
- }
- }
- // Test browser notifications
- if (this.isBrowserNotificationSupported) {
- try {
- const result = await this.showBrowserNotification({
- id: 'test_browser',
- title: 'Test Notification',
- message: 'Browser notifications are working',
- timeout: 2000
- });
- results.browser = result.success;
- } catch (error) {
- results.errors.push(`Browser: ${error.message}`);
- }
- }
- // Test in-app notifications
- try {
- const result = this.showInAppNotification({
- id: 'test_inapp',
- title: 'Test Notification',
- message: 'In-app notifications are working',
- timeout: 2000
- });
- results.inApp = result.success;
- } catch (error) {
- results.errors.push(`In-app: ${error.message}`);
- }
- return results;
- }
- }
- // Create global notification manager instance
- const notificationManager = new DesktopNotificationManager();
- // Export for use in other modules
- if (typeof module !== 'undefined' && module.exports) {
- module.exports = {
- DesktopNotificationManager,
- notificationManager,
- NOTIFICATION_TYPES
- };
- } else {
- // Browser environment - attach to window
- window.DesktopNotificationManager = DesktopNotificationManager;
- window.notificationManager = notificationManager;
- window.NOTIFICATION_TYPES = NOTIFICATION_TYPES;
- }
|