import Constants from "expo-constants"; import * as Device from "expo-device"; import * as Notifications from "expo-notifications"; import { Platform } from "react-native"; import { apiClient } from "../api/client"; // Configure notification handling behavior Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: true, shouldShowBanner: true, shouldShowList: true, }), }); export type NotificationPermissionStatus = | "granted" | "denied" | "undetermined"; /** * Get current notification permission status */ export async function getNotificationPermissionStatus(): Promise { const { status } = await Notifications.getPermissionsAsync(); return status; } /** * Request notification permissions from the user */ export async function requestNotificationPermissions(): Promise { // Check if we're on a physical device (notifications don't work on simulator) if (!Device.isDevice) { console.log("Push notifications require a physical device"); return "denied"; } // Get existing permissions first const { status: existingStatus } = await Notifications.getPermissionsAsync(); let finalStatus = existingStatus; // Only ask if permissions have not already been determined if (existingStatus !== "granted") { const { status } = await Notifications.requestPermissionsAsync(); finalStatus = status; } return finalStatus; } /** * Get the Expo push token for this device */ export async function getExpoPushToken(): Promise { if (!Device.isDevice) { console.log("Push notifications require a physical device"); return null; } const { status } = await Notifications.getPermissionsAsync(); if (status !== "granted") { console.log("Notification permissions not granted"); return null; } try { // Get the project ID from Expo config const projectId = Constants.expoConfig?.extra?.eas?.projectId ?? Constants.easConfig?.projectId; if (!projectId) { console.error("Project ID not found in Expo config"); return null; } const tokenData = await Notifications.getExpoPushTokenAsync({ projectId, }); return tokenData.data; } catch (error) { console.error("Failed to get Expo push token:", error); return null; } } /** * Register push token with the backend */ export async function registerPushToken(): Promise { const token = await getExpoPushToken(); if (!token) { console.log("No push token available to register"); return false; } try { await apiClient.post("/parent/push-token", { token }); console.log("Push token registered successfully"); return true; } catch (error) { console.error("Failed to register push token:", error); return false; } } /** * Remove push token from the backend */ export async function unregisterPushToken(): Promise { const token = await getExpoPushToken(); if (!token) { console.log("No push token available to unregister"); return false; } try { await apiClient.delete("/parent/push-token", { token }); console.log("Push token unregistered successfully"); return true; } catch (error) { console.error("Failed to unregister push token:", error); return false; } } /** * Set up Android notification channel for alerts */ export async function setupNotificationChannels(): Promise { if (Platform.OS === "android") { await Notifications.setNotificationChannelAsync("default", { name: "Default", importance: Notifications.AndroidImportance.DEFAULT, vibrationPattern: [0, 250, 250, 250], lightColor: "#FF231F7C", }); await Notifications.setNotificationChannelAsync("alerts", { name: "Safety Alerts", description: "Important safety alerts about your child's device", importance: Notifications.AndroidImportance.HIGH, vibrationPattern: [0, 500, 250, 500], lightColor: "#FF0000", sound: "default", enableVibrate: true, enableLights: true, }); } } /** * Add a listener for received notifications (when app is foregrounded) */ export function addNotificationReceivedListener( callback: (notification: Notifications.Notification) => void ): Notifications.EventSubscription { return Notifications.addNotificationReceivedListener(callback); } /** * Add a listener for notification responses (when user taps notification) */ export function addNotificationResponseListener( callback: (response: Notifications.NotificationResponse) => void ): Notifications.EventSubscription { return Notifications.addNotificationResponseReceivedListener(callback); } /** * Get the last notification response (for handling cold start from notification) */ export async function getLastNotificationResponse(): Promise { return Notifications.getLastNotificationResponseAsync(); } /** * Initialize notifications: setup channels, request permissions, and register token * Call this when the user is authenticated */ export async function initializeNotifications(): Promise<{ permissionStatus: NotificationPermissionStatus; tokenRegistered: boolean; }> { // Setup Android channels first await setupNotificationChannels(); // Request permissions const permissionStatus = await requestNotificationPermissions(); if (permissionStatus !== "granted") { return { permissionStatus, tokenRegistered: false }; } // Register token with backend const tokenRegistered = await registerPushToken(); return { permissionStatus, tokenRegistered }; }