import { logger } from '@/utils/logger'; import Constants from 'expo-constants'; import * as Notifications from 'expo-notifications'; import { Platform } from 'react-native'; import { DeviceTokenRequest, pushNotificationService } from './pushNotifications'; // 设备令牌管理状态 export enum TokenStatus { UNKNOWN = 'unknown', GRANTED = 'granted', DENIED = 'denied', REGISTERED = 'registered', FAILED = 'failed', } // 推送通知管理器配置 export interface PushNotificationConfig { onTokenReceived?: (token: string) => void; onTokenRefresh?: (token: string) => void; onError?: (error: Error) => void; } /** * 推送通知管理器类 * 负责iOS推送通知权限请求和设备令牌管理 */ export class PushNotificationManager { private static instance: PushNotificationManager; private isInitialized = false; private isInitializing = false; private config: PushNotificationConfig = {}; private currentToken: string | null = null; private constructor() { } public static getInstance(): PushNotificationManager { if (!PushNotificationManager.instance) { PushNotificationManager.instance = new PushNotificationManager(); } return PushNotificationManager.instance; } /** * 初始化推送通知管理器 */ async initialize(config: PushNotificationConfig = {}): Promise { if (this.isInitialized || this.isInitializing) { return this.isInitialized; } try { this.isInitializing = true; this.config = config; console.log('初始化推送通知管理器...'); // 检查设备是否支持推送通知 // 在Expo Go环境中,某些推送功能可能受限 if (Platform.OS === 'web') { console.warn('Web平台不支持推送通知功能'); this.config.onError?.(new Error('Web平台不支持推送通知功能')); return false; } // 配置推送通知处理方式 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldPlaySound: true, shouldSetBadge: true, shouldShowBanner: true, shouldShowList: true, }), }); // 请求推送通知权限 const hasPermission = await this.requestPermissions(); if (!hasPermission) { console.warn('推送通知权限未授予'); return false; } // 获取设备令牌 const token = await this.getDeviceToken(); if (!token) { console.error('获取设备令牌失败'); return false; } // 检查是否需要注册令牌 await this.checkAndRegisterToken(token); // 设置令牌刷新监听器 this.setupTokenRefreshListener(); this.isInitialized = true; console.log('推送通知管理器初始化成功'); return true; } catch (error) { console.error('推送通知管理器初始化失败:', error); this.config.onError?.(error as Error); return false; } finally { this.isInitializing = false; } } /** * 请求推送通知权限 */ async requestPermissions(): Promise { try { const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.warn('推送通知权限未授予:', finalStatus); return false; } console.log('推送通知权限已授予'); return true; } catch (error) { console.error('请求推送通知权限失败:', error); this.config.onError?.(error as Error); return false; } } /** * 获取设备令牌 */ async getDeviceToken(): Promise { try { if (Platform.OS === 'ios') { // iOS使用APNs令牌 const token = await Notifications.getDevicePushTokenAsync(); this.currentToken = token.data; console.log('获取到iOS设备令牌:', token.data.substring(0, 20) + '...'); return token.data; } else { // Android使用FCM令牌(虽然项目只支持iOS,但保留代码完整性) const token = await Notifications.getDevicePushTokenAsync(); this.currentToken = token.data; console.log('获取到Android设备令牌:', token.data.substring(0, 20) + '...'); return token.data; } } catch (error) { console.error('获取设备令牌失败:', error); this.config.onError?.(error as Error); return null; } } /** * 检查并注册设备令牌 * 每次应用启动都会上报token,更新用户ID绑定关系 */ private async checkAndRegisterToken(token: string): Promise { try { const storedToken = await pushNotificationService.getStoredDeviceToken(); const isRegistered = await pushNotificationService.isTokenRegistered(); // 如果令牌已改变或未注册,则重新注册 if (!isRegistered || storedToken !== token) { await this.registerDeviceToken(token); } else { // 令牌已注册且未变化,更新用户ID绑定关系 await this.updateTokenUserId(token); } } catch (error) { console.error('检查和注册设备令牌失败:', error); this.config.onError?.(error as Error); } } /** * 更新设备令牌的用户ID绑定关系 * 用户ID从服务端通过header中的token解析获取 */ private async updateTokenUserId(token: string): Promise { try { logger.info('更新设备令牌用户ID绑定:', token.substring(0, 20) + '...'); // 调用服务端API更新token用户ID绑定关系 const response = await pushNotificationService.updateTokenUserId(token); console.log('设备令牌用户ID绑定更新成功:', response.tokenId); return true; } catch (error) { console.error('更新设备令牌用户ID绑定失败:', error); // 如果更新用户ID绑定失败,尝试重新注册token console.log('尝试重新注册设备令牌...'); return this.registerDeviceToken(token); } } /** * 用户登录成功后调用此方法更新token用户ID绑定 */ async onUserLogin(): Promise { try { const token = this.currentToken || await this.getDeviceToken(); if (!token) { console.warn('未找到设备令牌,无法更新用户ID绑定'); return false; } return await this.updateTokenUserId(token); } catch (error) { console.error('用户登录后更新token用户ID绑定失败:', error); this.config.onError?.(error as Error); return false; } } /** * 注册设备令牌到后端 */ async registerDeviceToken(token?: string): Promise { try { const deviceToken = token || this.currentToken || await this.getDeviceToken(); if (!deviceToken) { throw new Error('设备令牌为空'); } // 构建设备信息 const deviceInfo: DeviceTokenRequest = { deviceToken, deviceType: Platform.OS === 'ios' ? 'IOS' : 'ANDROID', appVersion: Constants.expoConfig?.version || '1.0.0', osVersion: `${Platform.OS} ${Platform.Version}`, deviceName: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device', }; logger.info('设备信息:', deviceInfo); // 注册到后端 const response = await pushNotificationService.registerDeviceToken(deviceInfo); console.log('设备令牌注册成功:', response.tokenId); this.config.onTokenReceived?.(deviceToken); return true; } catch (error) { console.error('注册设备令牌失败:', error); this.config.onError?.(error as Error); return false; } } /** * 更新设备令牌 */ async updateDeviceToken(newToken: string): Promise { try { const oldToken = await pushNotificationService.getStoredDeviceToken(); if (!oldToken) { console.warn('未找到旧令牌,将执行新注册'); return this.registerDeviceToken(newToken); } const updateRequest = { currentDeviceToken: oldToken, newDeviceToken: newToken, appVersion: Constants.expoConfig?.version || '1.0.0', osVersion: `${Platform.OS} ${Platform.Version}`, }; const response = await pushNotificationService.updateDeviceToken(updateRequest); this.currentToken = newToken; console.log('设备令牌更新成功:', response.tokenId); this.config.onTokenRefresh?.(newToken); return true; } catch (error) { console.error('更新设备令牌失败:', error); this.config.onError?.(error as Error); return false; } } /** * 注销设备令牌 */ async unregisterDeviceToken(): Promise { try { const token = this.currentToken || await pushNotificationService.getStoredDeviceToken(); if (!token) { console.warn('未找到设备令牌,无需注销'); return true; } await pushNotificationService.unregisterDeviceToken(token); this.currentToken = null; console.log('设备令牌注销成功'); return true; } catch (error) { console.error('注销设备令牌失败:', error); this.config.onError?.(error as Error); return false; } } /** * 设置令牌刷新监听器 */ private setupTokenRefreshListener(): void { // 监听令牌变化(iOS上通常不会频繁变化) Notifications.addNotificationResponseReceivedListener((response) => { console.log('收到推送通知响应:', response); }); } /** * 获取当前设备令牌 */ getCurrentToken(): string | null { return this.currentToken; } /** * 获取令牌状态 */ async getTokenStatus(): Promise { try { const hasPermission = await this.requestPermissions(); if (!hasPermission) { return TokenStatus.DENIED; } const isRegistered = await pushNotificationService.isTokenRegistered(); if (isRegistered) { return TokenStatus.REGISTERED; } const token = await this.getDeviceToken(); return token ? TokenStatus.GRANTED : TokenStatus.FAILED; } catch (error) { console.error('获取令牌状态失败:', error); return TokenStatus.FAILED; } } /** * 清除所有本地数据 */ async clearAllData(): Promise { try { await pushNotificationService.clearLocalTokenData(); this.currentToken = null; this.isInitialized = false; console.log('推送通知管理器数据已清除'); } catch (error) { console.error('清除推送通知管理器数据失败:', error); } } } // 导出单例实例 export const pushNotificationManager = PushNotificationManager.getInstance();