Removed all console.log, console.error, console.warn, console.info, and console.debug statements from the main source code to clean up production output. Changes: - Removed 400+ console statements from TypeScript/TSX files - Cleaned BLE services (BLEManager.ts, MockBLEManager.ts) - Cleaned API services, contexts, hooks, and components - Cleaned WiFi setup and sensor management screens - Preserved console statements in test files (*.test.ts, __tests__/) - TypeScript compilation verified successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
236 lines
6.1 KiB
TypeScript
236 lines
6.1 KiB
TypeScript
/**
|
|
* 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<string | null> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<string | null> {
|
|
|
|
const token = await registerForPushNotificationsAsync();
|
|
|
|
if (token) {
|
|
await registerTokenOnServer(token);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* Get stored push token (if any)
|
|
*/
|
|
export async function getStoredPushToken(): Promise<string | null> {
|
|
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<Notifications.NotificationResponse | null> {
|
|
return await Notifications.getLastNotificationResponseAsync();
|
|
}
|
|
|
|
/**
|
|
* Parse notification data to determine navigation target
|
|
*/
|
|
export function parseNotificationData(data: Record<string, unknown>): {
|
|
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,
|
|
};
|
|
}
|