Files
digital-pilates/services/pushNotificationManager.ts
richarjiang 3ad0e08d58 perf(app): 添加登录状态检查并优化性能
- 在多个页面添加 isLoggedIn 检查,防止未登录时进行不必要的数据获取
- 使用 React.memo 和 useMemo 优化个人页面徽章渲染性能
- 为 badges API 添加节流机制,避免频繁请求
- 优化图片缓存策略和字符串处理
- 移除调试日志并改进推送通知的认证检查
2025-11-25 15:35:30 +08:00

364 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { getAuthToken } from '@/services/api';
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<boolean> {
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.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<boolean> {
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<string | null> {
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<void> {
try {
const storedToken = await pushNotificationService.getStoredDeviceToken();
const isRegistered = await pushNotificationService.isTokenRegistered();
// 如果令牌已改变或未注册,则重新注册
if (!isRegistered || storedToken !== token) {
await this.registerDeviceToken(token);
} else {
// 令牌已注册且未变化
// 只有在用户已登录的情况下才更新用户ID绑定关系
const authToken = await getAuthToken();
if (authToken) {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
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;
}
}
/**
* 获取当前设备令牌
*/
getCurrentToken(): string | null {
return this.currentToken;
}
/**
* 获取令牌状态
*/
async getTokenStatus(): Promise<TokenStatus> {
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<void> {
try {
await pushNotificationService.clearLocalTokenData();
this.currentToken = null;
this.isInitialized = false;
console.log('推送通知管理器数据已清除');
} catch (error) {
console.error('清除推送通知管理器数据失败:', error);
}
}
}
// 导出单例实例
export const pushNotificationManager = PushNotificationManager.getInstance();