import * as Device from 'expo-device'; import * as Notifications from 'expo-notifications'; import { Platform } from 'react-native'; import apiClient from '../api/client'; import Constants from 'expo-constants'; // Define the structure of the push token data expected by the backend interface PushTokenData { token: string; device_name?: string; token_type: 'expo'; // Indicate the type of token } // --- Android Notification Channel Setup --- async function setupNotificationChannelsAndroid() { if (Platform.OS === 'android') { await Notifications.setNotificationChannelAsync('default', { name: 'Default', importance: Notifications.AndroidImportance.MAX, vibrationPattern: [0, 250, 250, 250], lightColor: '#FF231F7C', }); console.log('[Notifications] Default Android channel set up.'); } } // --- Request Permissions and Get Token --- export async function registerForPushNotificationsAsync(): Promise { if (Platform.OS !== 'android' && Platform.OS !== 'ios') { console.warn('[Notifications] Push notifications are only supported on Android and iOS.'); return null; } let token: string | null = null; if (!Device.isDevice) { console.warn('[Notifications] Push notifications require a physical device.'); alert('Must use physical device for Push Notifications'); return null; } // 1. Setup Android Channels await setupNotificationChannelsAndroid(); // 2. Request Permissions const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; if (existingStatus !== 'granted') { console.log('[Notifications] Requesting notification permissions...'); const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } if (finalStatus !== 'granted') { console.warn('[Notifications] Failed to get push token: Permission not granted.'); alert('Failed to get push token for push notification!'); return null; } // 3. Get Expo Push Token try { // Use the default experience ID const projectId = process.env.EXPO_PROJECT_ID || Constants.expoConfig?.extra?.eas?.projectId; if (!projectId) { console.error('[Notifications] EAS project ID not found in app config. Cannot get push token.'); alert('Configuration error: Project ID missing. Cannot get push token.'); return null; } console.log(`[Notifications] Getting Expo push token with projectId: ${projectId}`); const expoPushToken = await Notifications.getExpoPushTokenAsync({ projectId }); token = expoPushToken.data; console.log('[Notifications] Received Expo Push Token:', token); } catch (error) { console.error('[Notifications] Error getting Expo push token:', error); alert(`Error getting push token: ${error instanceof Error ? error.message : String(error)}`); return null; } return token; } // --- Send Token to Backend --- export async function sendPushTokenToBackend(expoPushToken: string): Promise { if (!expoPushToken) { console.warn('[Notifications] No push token provided to send to backend.'); return false; } const tokenData: PushTokenData = { token: expoPushToken, device_name: Device.deviceName ?? undefined, token_type: 'expo', }; try { console.log('[Notifications] Sending push token to backend:', tokenData); const response = await apiClient.post('/user/push-token', tokenData); if (response.status === 200 || response.status === 201) { console.log('[Notifications] Push token successfully sent to backend.'); return true; } else { console.warn(`[Notifications] Backend returned status ${response.status} when sending push token.`); return false; } } catch (error: any) { console.error('[Notifications] Error sending push token to backend:', error.response?.data || error.message); return false; } } // --- Notification Handling Setup --- export function setupNotificationHandlers() { // Handle notifications that arrive while the app is foregrounded Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: false, }), }); // Handle user interaction with notifications (tapping) when app is foregrounded/backgrounded const foregroundInteractionSubscription = Notifications.addNotificationResponseReceivedListener(response => { console.log('[Notifications] User interacted with notification (foreground/background):', response.notification.request.content); // const data = response.notification.request.content.data; // if (data?.screen) { // navigation.navigate(data.screen); // } }); // Handle user interaction with notifications (tapping) when app was killed/not running // This requires careful setup, potentially using Linking or initial URL handling // Notifications.getLastNotificationResponseAsync().then(response => { // if (response) { // console.log('[Notifications] User opened app via notification (killed state):', response.notification.request.content); // // Handle navigation or action based on response.notification.request.content.data // } // }); console.log('[Notifications] Notification handlers set up.'); // Return cleanup function for useEffect return () => { console.log('[Notifications] Removing notification listeners.'); Notifications.removeNotificationSubscription(foregroundInteractionSubscription); }; }