/** * Push Notifications Service * * Handles: * - Requesting push notification permissions * - Getting Expo Push Token * - Registering/unregistering token on server * - Handling incoming notifications */ import * as Notifications from 'expo-notifications'; import * as Device from 'expo-device'; import { Platform } from 'react-native'; import * as SecureStore from 'expo-secure-store'; import Constants from 'expo-constants'; const WELLNUO_API_URL = 'https://wellnuo.smartlaunchhub.com/api'; const PUSH_TOKEN_KEY = 'expoPushToken'; // Configure notification handler for foreground notifications Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, shouldShowBanner: true, shouldShowList: true, }), }); /** * Register for push notifications and get Expo Push Token * Returns the token or null if not available */ export async function registerForPushNotificationsAsync(): Promise { let token: string | null = null; // Must be a physical device for push notifications if (!Device.isDevice) { // For simulator, return a fake token for testing if (__DEV__) { return 'ExponentPushToken[SIMULATOR_TEST_TOKEN]'; } return null; } // Check/request permissions const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { return null; } // Get Expo Push Token try { const projectId = Constants.expoConfig?.extra?.eas?.projectId; const pushTokenResponse = await Notifications.getExpoPushTokenAsync({ projectId: projectId, }); token = pushTokenResponse.data; // Store locally await SecureStore.setItemAsync(PUSH_TOKEN_KEY, token); } catch (error) { return null; } // Android needs notification channel if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); // Emergency alerts channel await Notifications.setNotificationChannelAsync('emergency', { name: 'Emergency Alerts', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 500, 200, 500], lightColor: '#FF0000', sound: 'default', }); } return token; } /** * Register push token on WellNuo backend */ export async function registerTokenOnServer(token: string): Promise { try { const accessToken = await SecureStore.getItemAsync('accessToken'); if (!accessToken) { return false; } const response = await fetch(`${WELLNUO_API_URL}/push-tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, }, body: JSON.stringify({ token, platform: Platform.OS, deviceName: Device.deviceName || `${Device.brand} ${Device.modelName}`, }), }); if (!response.ok) { const error = await response.json(); return false; } return true; } catch (error) { return false; } } /** * Unregister push token from server (call on logout) */ export async function unregisterToken(): Promise { try { const token = await SecureStore.getItemAsync(PUSH_TOKEN_KEY); const accessToken = await SecureStore.getItemAsync('accessToken'); if (!token || !accessToken) { return true; // Nothing to unregister } const response = await fetch(`${WELLNUO_API_URL}/push-tokens`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${accessToken}`, }, body: JSON.stringify({ token }), }); // Clear local storage regardless of server response await SecureStore.deleteItemAsync(PUSH_TOKEN_KEY); if (!response.ok) { return false; } return true; } catch (error) { // Still clear local storage await SecureStore.deleteItemAsync(PUSH_TOKEN_KEY); return false; } } /** * Full registration flow: get token + register on server * Call this after successful login */ export async function setupPushNotifications(): Promise { const token = await registerForPushNotificationsAsync(); if (token) { await registerTokenOnServer(token); } return token; } /** * Get stored push token (if any) */ export async function getStoredPushToken(): Promise { try { return await SecureStore.getItemAsync(PUSH_TOKEN_KEY); } catch { return null; } } /** * Add listener for received notifications (while app is open) */ export function addNotificationReceivedListener( callback: (notification: Notifications.Notification) => void ): Notifications.EventSubscription { return Notifications.addNotificationReceivedListener(callback); } /** * Add listener for notification response (user tapped notification) */ export function addNotificationResponseListener( callback: (response: Notifications.NotificationResponse) => void ): Notifications.EventSubscription { return Notifications.addNotificationResponseReceivedListener(callback); } /** * Get last notification response (if app was opened from notification) */ export async function getLastNotificationResponse(): Promise { return await Notifications.getLastNotificationResponseAsync(); } /** * Parse notification data to determine navigation target */ export function parseNotificationData(data: Record): { type: string; deploymentId?: number; beneficiaryId?: number; alertId?: string; } { return { type: (data.type as string) || 'unknown', deploymentId: data.deploymentId as number | undefined, beneficiaryId: data.beneficiaryId as number | undefined, alertId: data.alertId as string | undefined, }; }