338 lines
8.5 KiB
TypeScript
338 lines
8.5 KiB
TypeScript
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<string> {
|
|
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<number> {
|
|
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();
|