import messaging from '@react-native-firebase/messaging'; import notifee, { AndroidImportance, EventType } from '@notifee/react-native'; import { Platform } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import APIClient from '@aethex/core'; interface NotificationData { type: 'message' | 'call' | 'friend_request' | 'voice_channel'; conversationId?: string; callId?: string; userId?: string; title: string; body: string; imageUrl?: string; actions?: Array<{ action: string; title: string }>; } class PushNotificationService { private apiClient: APIClient | null = null; async initialize(apiClient: APIClient) { this.apiClient = apiClient; // Request permissions const authStatus = await messaging().requestPermission(); const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED || authStatus === messaging.AuthorizationStatus.PROVISIONAL; if (enabled) { console.log('Push notification permission granted'); await this.getFCMToken(); } // Create notification channels (Android) if (Platform.OS === 'android') { await this.createChannels(); } // Handle foreground messages messaging().onMessage(async (remoteMessage) => { await this.displayNotification(remoteMessage); }); // Handle background messages messaging().setBackgroundMessageHandler(async (remoteMessage) => { console.log('Background message:', remoteMessage); await this.displayNotification(remoteMessage); }); // Handle notification actions notifee.onBackgroundEvent(async ({ type, detail }) => { await this.handleNotificationEvent(type, detail); }); notifee.onForegroundEvent(async ({ type, detail }) => { await this.handleNotificationEvent(type, detail); }); // Handle token refresh messaging().onTokenRefresh(async (token) => { await this.updateFCMToken(token); }); } async getFCMToken(): Promise { const token = await messaging().getToken(); console.log('FCM Token:', token); await this.updateFCMToken(token); return token; } private async updateFCMToken(token: string) { try { // Save locally await AsyncStorage.setItem('fcm_token', token); // Send to server if (this.apiClient) { await this.apiClient.request({ method: 'POST', url: '/api/users/fcm-token', data: { token, platform: Platform.OS }, }); } } catch (error) { console.error('Error updating FCM token:', error); } } async createChannels() { // Messages channel await notifee.createChannel({ id: 'messages', name: 'Messages', importance: AndroidImportance.HIGH, sound: 'message_sound', vibration: true, lights: true, lightColor: '#667eea', }); // Calls channel await notifee.createChannel({ id: 'calls', name: 'Calls', importance: AndroidImportance.HIGH, sound: 'call_sound', vibration: true, vibrationPattern: [300, 500], }); // Voice channel await notifee.createChannel({ id: 'voice_channel', name: 'Voice Channel', importance: AndroidImportance.LOW, sound: false, }); // Friend requests await notifee.createChannel({ id: 'social', name: 'Social', importance: AndroidImportance.DEFAULT, sound: 'default', }); } async displayNotification(remoteMessage: any) { const { notification, data } = remoteMessage; const notifData: NotificationData = data as NotificationData; const channelId = this.getChannelId(notifData.type); const notificationConfig: any = { title: notification?.title || notifData.title, body: notification?.body || notifData.body, android: { channelId: channelId, smallIcon: 'ic_notification', color: '#667eea', pressAction: { id: 'default', launchActivity: 'default', }, actions: this.getActions(notifData.type, notifData), }, ios: { categoryId: notifData.type.toUpperCase(), attachments: notifData.imageUrl ? [ { url: notifData.imageUrl, thumbnailHidden: false, }, ] : undefined, sound: this.getSound(notifData.type), }, data: data, }; await notifee.displayNotification(notificationConfig); } private getChannelId(type: string): string { switch (type) { case 'call': return 'calls'; case 'voice_channel': return 'voice_channel'; case 'friend_request': return 'social'; case 'message': default: return 'messages'; } } private getActions(type: string, data: NotificationData) { if (Platform.OS !== 'android') return undefined; switch (type) { case 'message': return [ { title: 'Reply', pressAction: { id: 'reply' }, input: { placeholder: 'Type a message...', allowFreeFormInput: true, }, }, { title: 'Mark as Read', pressAction: { id: 'mark_read' }, }, ]; case 'call': return [ { title: 'Answer', pressAction: { id: 'answer_call' }, }, { title: 'Decline', pressAction: { id: 'decline_call' }, }, ]; case 'friend_request': return [ { title: 'Accept', pressAction: { id: 'accept_friend' }, }, { title: 'Decline', pressAction: { id: 'decline_friend' }, }, ]; default: return undefined; } } private getSound(type: string): string { switch (type) { case 'call': return 'call_sound.wav'; case 'message': return 'message_sound.wav'; default: return 'default'; } } private async handleNotificationEvent(type: EventType, detail: any) { const { notification, pressAction, input } = detail; if (type === EventType.PRESS) { // User tapped notification const data = notification?.data; console.log('Notification pressed:', data); // Navigate to appropriate screen } if (type === EventType.ACTION_PRESS && pressAction) { await this.handleAction(pressAction.id, notification?.data, input); } if (type === EventType.DISMISSED) { console.log('Notification dismissed'); } } private async handleAction(actionId: string, data: any, input?: string) { if (!this.apiClient) return; try { switch (actionId) { case 'reply': if (input && data?.conversationId) { await this.apiClient.sendMessage(data.conversationId, input); } break; case 'mark_read': if (data?.conversationId) { await this.apiClient.request({ method: 'POST', url: `/api/conversations/${data.conversationId}/read`, }); } break; case 'answer_call': if (data?.callId) { await this.apiClient.joinCall(data.callId); } break; case 'decline_call': if (data?.callId) { await this.apiClient.request({ method: 'POST', url: `/api/calls/${data.callId}/decline`, }); } break; case 'accept_friend': if (data?.requestId) { await this.apiClient.acceptFriendRequest(data.requestId); } break; case 'decline_friend': if (data?.requestId) { await this.apiClient.request({ method: 'POST', url: `/api/nexus/friends/reject/${data.requestId}`, }); } break; } } catch (error) { console.error('Error handling notification action:', error); } } async cancelNotification(notificationId: string) { await notifee.cancelNotification(notificationId); } async cancelAllNotifications() { await notifee.cancelAllNotifications(); } async getBadgeCount(): Promise { if (Platform.OS === 'ios') { return await notifee.getBadgeCount(); } return 0; } async setBadgeCount(count: number) { if (Platform.OS === 'ios') { await notifee.setBadgeCount(count); } } } export default new PushNotificationService();