UNPKG

expo-healthkit-module

Version:

Expo module providing Apple HealthKit / Android Health Connect integration for RN / expo apps.

361 lines 12.9 kB
// Reexport the native module. On web, it will be resolved to ExpoHealthkitModule.web.ts // and on native platforms to ExpoHealthkitModule.ts import { Platform } from "react-native"; import NativeModule from "./src/ExpoHealthkitModule"; export { default } from "./src/ExpoHealthkitModule"; export * from "./src/ExpoHealthkitModule.types"; // export { default as ExpoHealthkitModuleView } from "./src/ExpoHealthkitModuleView"; // Cross-platform identifier mapping with type safety const IDENTIFIER_MAPPING = { stepCount: { ios: "HKQuantityTypeIdentifierStepCount", android: "Steps", }, heartRate: { ios: "HKQuantityTypeIdentifierHeartRate", android: "HeartRate", }, activeEnergyBurned: { ios: "HKQuantityTypeIdentifierActiveEnergyBurned", android: "ActiveCaloriesBurned", }, totalEnergyBurned: { ios: "HKQuantityTypeIdentifierBasalEnergyBurned", android: "TotalCaloriesBurned", }, distanceWalkingRunning: { ios: "HKQuantityTypeIdentifierDistanceWalkingRunning", android: "Distance", }, flightsClimbed: { ios: "HKQuantityTypeIdentifierFlightsClimbed", android: "FloorsClimbed", }, restingHeartRate: { ios: "HKQuantityTypeIdentifierRestingHeartRate", android: "RestingHeartRate", }, respiratoryRate: { ios: "HKQuantityTypeIdentifierRespiratoryRate", android: "RespiratoryRate", }, vo2Max: { ios: "HKQuantityTypeIdentifierVO2Max", android: "Vo2Max", }, sleepAnalysis: { ios: "HKCategoryTypeIdentifierSleepAnalysis", android: "SleepSession", }, workout: { ios: "HKWorkoutTypeIdentifier", android: "ExerciseSession", }, heartRateVariabilitySDNN: { ios: "HKQuantityTypeIdentifierHeartRateVariabilitySDNN", android: "HeartRateVariability", }, }; // Enhanced error handling with typed errors function createHealthDataError(code, message) { return { code, message }; } function createUnsupportedPlatformError() { return { success: false, data: [], error: createHealthDataError("permission_denied", "Health data is only available on iOS and Android."), }; } function createExceptionError(error) { const message = error instanceof Error ? error.message : "An unexpected error occurred while fetching health data."; return { success: false, data: [], error: createHealthDataError("exception", message), }; } // Core API functions with improved error handling and type safety export async function authorizeHealthKit() { if (Platform.OS === "ios") { return NativeModule.authorizeHealthKit(); } else if (Platform.OS === "android") { return NativeModule.authorizeHealthKit(); } else { return { success: false, granted: [], denied: [], error: "Health data is only available on iOS and Android.", }; } } // Open Health Connect app for manual permission granting (Android only) export async function openHealthConnectApp() { if (Platform.OS === "android") { return NativeModule.openHealthConnectApp(); } else { return { success: false, error: "Health Connect is only available on Android.", }; } } export async function getHealthData(options) { try { // Validate required parameters if (!options.identifier || !options.startDate || !options.endDate) { return { success: false, data: [], error: createHealthDataError("missing_arguments", "Missing required options: identifier, startDate, endDate."), }; } // Validate date format (basic ISO 8601 check) const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/; if (!dateRegex.test(options.startDate) || !dateRegex.test(options.endDate)) { return { success: false, data: [], error: createHealthDataError("invalid_date", "Dates must be in ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ)"), }; } if (Platform.OS === "ios") { // Use iOS identifier directly or map from cross-platform identifier const identifier = options.identifier.startsWith("HK") ? options.identifier : IDENTIFIER_MAPPING[options.identifier]?.ios || options.identifier; console.log("===GETTING HEALTH DATA FROM MODULE OPTIONS===", options); console.log("🟦 [JS] About to call NativeModule.getHealthData"); console.time("🟦 [JS] NativeModule.getHealthData"); // Filter out undefined values to prevent conversion errors const healthDataOptions = { identifier, startDate: options.startDate, endDate: options.endDate, }; // Only add optional fields if they have values if (options.aggregation !== undefined) { healthDataOptions.aggregation = options.aggregation; } if (options.limit !== undefined) { healthDataOptions.limit = options.limit; } if (options.ascending !== undefined) { healthDataOptions.ascending = options.ascending; } const res = await NativeModule.getHealthData(healthDataOptions); console.timeEnd("🟦 [JS] NativeModule.getHealthData"); console.log("🟦 [JS] NativeModule.getHealthData completed"); console.log("===GETTING HEALTH DATA FROM MODULE===", res); return res; } else if (Platform.OS === "android") { // Use Android identifier directly or map from cross-platform identifier const identifier = Object.values(IDENTIFIER_MAPPING).some((mapping) => mapping.android === options.identifier) ? options.identifier : IDENTIFIER_MAPPING[options.identifier]?.android || options.identifier; return NativeModule.getHealthData({ ...options, identifier, aggregation: options.aggregation ?? "raw", }); } else { return createUnsupportedPlatformError(); } } catch (error) { console.error("[expo-healthkit-module] getHealthData error:", error); return createExceptionError(error); } } // Background Sync Methods with enhanced error handling // CRITICAL: Register background task handler early (during app launch) export async function registerBackgroundTaskHandler() { if (Platform.OS === "ios") { return NativeModule.registerBackgroundTaskHandler(); } else if (Platform.OS === "android") { // TODO: Implement Android WorkManager registration return { success: true, error: undefined, }; } else { return { success: false, error: "Background sync is only available on iOS and Android.", }; } } export async function enableBackgroundSync(options) { if (Platform.OS === "ios") { return NativeModule.enableBackgroundSync(options); } else if (Platform.OS === "android") { return NativeModule.enableBackgroundSync(options); } else { return { success: false, error: "Background sync is only available on iOS and Android.", }; } } export async function getBackgroundSyncStatus() { if (Platform.OS === "ios") { return NativeModule.getBackgroundSyncStatus(); } else if (Platform.OS === "android") { return NativeModule.getBackgroundSyncStatus(); } else { return { enabled: false, lastSync: null, error: "Background sync is only available on iOS and Android.", }; } } export async function disableBackgroundSync() { if (Platform.OS === "ios") { return NativeModule.disableBackgroundSync(); } else if (Platform.OS === "android") { return NativeModule.disableBackgroundSync(); } else { return { success: false, error: "Background sync is only available on iOS and Android.", }; } } export async function notifyBackgroundSyncComplete(success) { if (Platform.OS === "ios") { return NativeModule.notifyBackgroundSyncComplete(success); } else if (Platform.OS === "android") { // TODO: Implement Android notification return Promise.resolve(); } else { return Promise.resolve(); } } // Helper function to get platform-specific identifier with type safety export function getPlatformIdentifier(crossPlatformId) { const mapping = IDENTIFIER_MAPPING[crossPlatformId]; if (!mapping) return crossPlatformId; return Platform.OS === "ios" ? mapping.ios : mapping.android; } // Helper function to get all supported identifiers for current platform with type safety export function getSupportedIdentifiers() { if (Platform.OS === "ios") { return [ "HKQuantityTypeIdentifierActiveEnergyBurned", "HKQuantityTypeIdentifierAppleExerciseTime", "HKQuantityTypeIdentifierAppleStandTime", "HKQuantityTypeIdentifierBasalEnergyBurned", "HKQuantityTypeIdentifierDistanceCycling", "HKQuantityTypeIdentifierDistanceWalkingRunning", "HKQuantityTypeIdentifierFlightsClimbed", "HKQuantityTypeIdentifierHeartRate", "HKQuantityTypeIdentifierHeartRateRecoveryOneMinute", "HKQuantityTypeIdentifierHeartRateVariabilitySDNN", "HKQuantityTypeIdentifierRespiratoryRate", "HKQuantityTypeIdentifierRestingHeartRate", "HKQuantityTypeIdentifierStepCount", "HKQuantityTypeIdentifierVO2Max", "HKCategoryTypeIdentifierSleepAnalysis", "HKWorkoutTypeIdentifier", ]; } else { // Return cross-platform identifiers for Android or web return [ "stepCount", "heartRate", "activeEnergyBurned", "totalEnergyBurned", "heartRateVariabilitySDNN", "distanceWalkingRunning", "flightsClimbed", "restingHeartRate", "respiratoryRate", "vo2Max", "sleepAnalysis", "workout", ]; } } // Validate health data identifier export function isValidHealthDataIdentifier(identifier) { const supportedIdentifiers = getSupportedIdentifiers(); return supportedIdentifiers.includes(identifier); } // Type guard for checking if a result has an error export function hasHealthDataError(result) { return !result.success && result.error !== undefined; } // Utility function to format dates for the API export function formatDateForHealthAPI(date) { return date.toISOString(); } // Utility function to create date range for common periods export function createDateRange(period) { const now = new Date(); const endDate = formatDateForHealthAPI(now); let startDate; switch (period) { case "today": startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); break; case "yesterday": startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); return { startDate: formatDateForHealthAPI(startDate), endDate: formatDateForHealthAPI(new Date(startDate.getTime() + 24 * 60 * 60 * 1000)), }; case "week": startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); break; case "month": startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); break; case "year": startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); break; default: startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // Default to last 24 hours } return { startDate: formatDateForHealthAPI(startDate), endDate, }; } // Export all health data (Android only - for debugging) export async function exportAllHealthData(options) { if (Platform.OS === "android") { return NativeModule.exportAllHealthData(options); } else { return { success: false, data: {}, error: { code: "unsupported_platform", message: "Export all data is only available on Android.", }, }; } } //# sourceMappingURL=index.js.map