// src/services/browserNotificationService.js /** * 浏览器原生通知服务 * 提供系统级通知功能(Web Notifications API) */ import { logger } from '../utils/logger'; class BrowserNotificationService { constructor() { this.permission = this.isSupported() ? Notification.permission : 'denied'; this.activeNotifications = new Map(); // 存储活跃的通知 } /** * 检查浏览器是否支持通知 API */ isSupported() { return 'Notification' in window; } /** * 获取当前权限状态 * @returns {string} 'granted' | 'denied' | 'default' */ getPermissionStatus() { if (!this.isSupported()) { return 'denied'; } return Notification.permission; } /** * 请求通知权限 * @returns {Promise} 权限状态 */ async requestPermission() { if (!this.isSupported()) { logger.warn('browserNotificationService', 'Notifications not supported'); return 'denied'; } if (this.permission === 'granted') { logger.info('browserNotificationService', 'Permission already granted'); return 'granted'; } try { const permission = await Notification.requestPermission(); this.permission = permission; logger.info('browserNotificationService', `Permission ${permission}`); return permission; } catch (error) { logger.error('browserNotificationService', 'requestPermission', error); return 'denied'; } } /** * 发送浏览器通知 * @param {Object} options 通知选项 * @param {string} options.title 标题 * @param {string} options.body 内容 * @param {string} options.icon 图标路径 * @param {string} options.tag 标签(防止重复) * @param {boolean} options.requireInteraction 是否需要用户交互才关闭 * @param {Object} options.data 自定义数据(如跳转链接) * @param {number} options.autoClose 自动关闭时间(毫秒) * @returns {Notification|null} 通知对象 */ sendNotification({ title, body, icon = '/logo192.png', tag, requireInteraction = false, data = {}, autoClose = 0, }) { if (!this.isSupported()) { logger.warn('browserNotificationService', 'Notifications not supported'); return null; } if (this.permission !== 'granted') { logger.warn('browserNotificationService', 'Permission not granted'); return null; } try { // 关闭相同 tag 的旧通知 if (tag && this.activeNotifications.has(tag)) { const oldNotification = this.activeNotifications.get(tag); oldNotification.close(); } // 创建通知 const notification = new Notification(title, { body, icon, badge: '/badge.png', tag: tag || `notification_${Date.now()}`, requireInteraction, data, silent: false, // 允许声音 }); // 存储通知引用 if (tag) { this.activeNotifications.set(tag, notification); } // 自动关闭 if (autoClose > 0 && !requireInteraction) { setTimeout(() => { notification.close(); }, autoClose); } // 通知关闭时清理引用 notification.onclose = () => { if (tag) { this.activeNotifications.delete(tag); } }; logger.info('browserNotificationService', 'Notification sent', { title, tag }); return notification; } catch (error) { logger.error('browserNotificationService', 'sendNotification', error); return null; } } /** * 设置通知点击处理 * @param {Notification} notification 通知对象 * @param {Function} navigate React Router navigate 函数 */ setupClickHandler(notification, navigate) { if (!notification) return; notification.onclick = (event) => { event.preventDefault(); // 聚焦窗口 window.focus(); // 跳转链接 if (notification.data?.link) { navigate(notification.data.link); } // 关闭通知 notification.close(); logger.info('browserNotificationService', 'Notification clicked', notification.data); }; } /** * 关闭所有活跃通知 */ closeAll() { this.activeNotifications.forEach(notification => { notification.close(); }); this.activeNotifications.clear(); logger.info('browserNotificationService', 'All notifications closed'); } /** * 根据通知数据发送浏览器通知 * @param {Object} notificationData 通知数据 * @param {Function} navigate React Router navigate 函数 */ sendFromNotificationData(notificationData, navigate) { const { type, priority, title, content, link, extra } = notificationData; // 生成唯一 tag const tag = `${type}_${Date.now()}`; // 判断是否需要用户交互(紧急通知不自动关闭) const requireInteraction = priority === 'urgent'; // 发送通知 const notification = this.sendNotification({ title: title || '新通知', body: content || '', tag, requireInteraction, data: { link, ...extra }, autoClose: requireInteraction ? 0 : 8000, // 紧急通知不自动关闭 }); // 设置点击处理 if (notification && navigate) { this.setupClickHandler(notification, navigate); } return notification; } } // 导出单例 export const browserNotificationService = new BrowserNotificationService(); export default browserNotificationService;