@appeeky/expo-healthkit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +92 -0
  3. package/README.md +453 -0
  4. package/android/build.gradle +22 -0
  5. package/android/src/main/AndroidManifest.xml +74 -0
  6. package/android/src/main/java/expo/modules/healthkit/ExpoHealthKitModule.kt +228 -0
  7. package/android/src/main/java/expo/modules/healthkit/HealthConnectMapping.kt +129 -0
  8. package/android/src/main/java/expo/modules/healthkit/HealthConnectNutrition.kt +152 -0
  9. package/android/src/main/java/expo/modules/healthkit/HealthConnectRationaleActivity.kt +62 -0
  10. package/android/src/main/java/expo/modules/healthkit/HealthConnectService.kt +1054 -0
  11. package/android/src/main/java/expo/modules/healthkit/HealthConnectUnits.kt +133 -0
  12. package/android/src/main/java/expo/modules/healthkit/HealthConnectWorkouts.kt +105 -0
  13. package/android/src/main/java/expo/modules/healthkit/HealthKitExceptions.kt +30 -0
  14. package/app.plugin.js +1 -0
  15. package/build/ExpoHealthKitModule.d.ts +39 -0
  16. package/build/ExpoHealthKitModule.d.ts.map +1 -0
  17. package/build/ExpoHealthKitModule.js +35 -0
  18. package/build/ExpoHealthKitModule.js.map +1 -0
  19. package/build/ExpoHealthKitModule.web.d.ts +39 -0
  20. package/build/ExpoHealthKitModule.web.d.ts.map +1 -0
  21. package/build/ExpoHealthKitModule.web.js +105 -0
  22. package/build/ExpoHealthKitModule.web.js.map +1 -0
  23. package/build/dates.d.ts +6 -0
  24. package/build/dates.d.ts.map +1 -0
  25. package/build/dates.js +13 -0
  26. package/build/dates.js.map +1 -0
  27. package/build/errors.d.ts +9 -0
  28. package/build/errors.d.ts.map +1 -0
  29. package/build/errors.js +18 -0
  30. package/build/errors.js.map +1 -0
  31. package/build/identifiers.d.ts +418 -0
  32. package/build/identifiers.d.ts.map +1 -0
  33. package/build/identifiers.js +402 -0
  34. package/build/identifiers.js.map +1 -0
  35. package/build/index.d.ts +454 -0
  36. package/build/index.d.ts.map +1 -0
  37. package/build/index.js +418 -0
  38. package/build/index.js.map +1 -0
  39. package/build/types.d.ts +542 -0
  40. package/build/types.d.ts.map +1 -0
  41. package/build/types.js +2 -0
  42. package/build/types.js.map +1 -0
  43. package/docs/banner.jpg +0 -0
  44. package/expo-module.config.json +9 -0
  45. package/ios/ExpoHealthKit.podspec +30 -0
  46. package/ios/ExpoHealthKitModule.swift +155 -0
  47. package/ios/HealthKitAdvancedQueries.swift +458 -0
  48. package/ios/HealthKitExceptions.swift +109 -0
  49. package/ios/HealthKitIdentifiers.swift +183 -0
  50. package/ios/HealthKitRecords.swift +214 -0
  51. package/ios/HealthKitService.swift +626 -0
  52. package/ios/PrivacyInfo.xcprivacy +14 -0
  53. package/package.json +105 -0
  54. package/plugin/build/index.d.ts +30 -0
  55. package/plugin/build/index.js +138 -0
  56. package/src/ExpoHealthKitModule.ts +116 -0
  57. package/src/ExpoHealthKitModule.web.ts +140 -0
  58. package/src/dates.ts +17 -0
  59. package/src/errors.ts +22 -0
  60. package/src/identifiers.ts +494 -0
  61. package/src/index.ts +563 -0
  62. package/src/types.ts +625 -0
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@appeeky/expo-healthkit",
3
+ "version": "0.1.0",
4
+ "description": "Apple HealthKit and Android Health Connect for Expo. One identifier-based JavaScript API on iOS and Android.",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./build/index.d.ts",
11
+ "default": "./build/index.js"
12
+ },
13
+ "./app.plugin": "./app.plugin.js",
14
+ "./app.plugin.js": "./app.plugin.js",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "scripts": {
18
+ "build": "node internal/module_scripts/build.js",
19
+ "clean": "node internal/module_scripts/clean.js",
20
+ "lint": "eslint src plugin/src",
21
+ "typecheck": "tsc --noEmit && tsc --noEmit -p plugin",
22
+ "test": "node internal/module_scripts/test.js",
23
+ "test:coverage": "node internal/module_scripts/test.js --coverage --watchAll=false",
24
+ "prepare": "node internal/module_scripts/prepare.js",
25
+ "prepublishOnly": "node internal/module_scripts/prepare.js",
26
+ "open:ios": "node internal/module_scripts/open-ios.js",
27
+ "open:android": "node internal/module_scripts/open-android.js"
28
+ },
29
+ "keywords": [
30
+ "react-native",
31
+ "expo",
32
+ "expo-module",
33
+ "healthkit",
34
+ "apple-health",
35
+ "apple-healthkit",
36
+ "health-connect",
37
+ "google-health-connect",
38
+ "ios",
39
+ "android",
40
+ "health",
41
+ "fitness"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/appeeky/expo-healthkit.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/appeeky/expo-healthkit/issues"
49
+ },
50
+ "author": "Erencan Arica <erencan_arica@hotmail.com> (https://github.com/Eronred)",
51
+ "license": "SEE LICENSE IN LICENSE",
52
+ "homepage": "https://github.com/appeeky/expo-healthkit#readme",
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "devDependencies": {
57
+ "@babel/core": "^7.26.0",
58
+ "@types/jest": "^29.2.1",
59
+ "@types/react": "~19.1.1",
60
+ "babel-preset-expo": "~57.0.10",
61
+ "eslint": "~9.39.4",
62
+ "eslint-config-universe": "^15.0.3",
63
+ "expo": "^57.0.19",
64
+ "jest": "^29.7.0",
65
+ "prettier": "^3.0.0",
66
+ "react-native": "0.82.1",
67
+ "typescript": "^5.9.2"
68
+ },
69
+ "jest": {
70
+ "testEnvironment": "node",
71
+ "roots": [
72
+ "<rootDir>/src"
73
+ ],
74
+ "moduleFileExtensions": [
75
+ "ts",
76
+ "tsx",
77
+ "js"
78
+ ],
79
+ "transform": {
80
+ "^.+\\.(js|ts|tsx)$": "babel-jest"
81
+ },
82
+ "collectCoverageFrom": [
83
+ "src/identifiers.ts",
84
+ "src/errors.ts",
85
+ "src/dates.ts"
86
+ ],
87
+ "coverageDirectory": "coverage",
88
+ "coverageThreshold": {
89
+ "global": {
90
+ "branches": 80,
91
+ "functions": 90,
92
+ "lines": 90,
93
+ "statements": 90
94
+ }
95
+ }
96
+ },
97
+ "peerDependencies": {
98
+ "expo": ">=53.0.0",
99
+ "react": "*",
100
+ "react-native": "*"
101
+ },
102
+ "engines": {
103
+ "node": ">=18.18"
104
+ }
105
+ }
@@ -0,0 +1,30 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ export interface ExpoHealthKitPluginProps {
3
+ /**
4
+ * `NSHealthShareUsageDescription`. Pass `false` to skip writing the key.
5
+ */
6
+ healthSharePermission?: string | false;
7
+ /**
8
+ * `NSHealthUpdateUsageDescription`. Pass `false` to skip writing the key.
9
+ */
10
+ healthUpdatePermission?: string | false;
11
+ /**
12
+ * `NSHealthClinicalHealthRecordsShareUsageDescription`. Used when
13
+ * `isClinicalDataEnabled` is true.
14
+ */
15
+ healthClinicalRecordsPermission?: string | false;
16
+ /**
17
+ * Adds the HealthKit background-delivery entitlement and `healthkit` UIBackgroundModes.
18
+ */
19
+ isBackgroundDeliveryEnabled?: boolean;
20
+ /**
21
+ * Adds the `health-records` HealthKit access entitlement and clinical usage string.
22
+ */
23
+ isClinicalDataEnabled?: boolean;
24
+ /**
25
+ * Privacy policy URL shown from the Health Connect permission rationale screen.
26
+ */
27
+ healthConnectPrivacyPolicyUrl?: string;
28
+ }
29
+ declare const _default: ConfigPlugin<void | ExpoHealthKitPluginProps>;
30
+ export default _default;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const pkg = require('../../package.json');
5
+ const DEFAULT_SHARE = 'Allow $(PRODUCT_NAME) to read your health data';
6
+ const DEFAULT_UPDATE = 'Allow $(PRODUCT_NAME) to write health data';
7
+ const DEFAULT_CLINICAL = 'Allow $(PRODUCT_NAME) to read your clinical health records';
8
+ const HEALTH_CONNECT_PACKAGE = 'com.google.android.apps.healthdata';
9
+ const PRIVACY_POLICY_META = 'expo.modules.healthkit.HEALTH_CONNECT_PRIVACY_POLICY_URL';
10
+ const HEALTH_CONNECT_MIN_SDK = 26;
11
+ const HEALTH_CONNECT_PERMISSIONS = [
12
+ 'android.permission.health.READ_STEPS',
13
+ 'android.permission.health.WRITE_STEPS',
14
+ 'android.permission.health.READ_DISTANCE',
15
+ 'android.permission.health.WRITE_DISTANCE',
16
+ 'android.permission.health.READ_ACTIVE_CALORIES_BURNED',
17
+ 'android.permission.health.WRITE_ACTIVE_CALORIES_BURNED',
18
+ 'android.permission.health.READ_TOTAL_CALORIES_BURNED',
19
+ 'android.permission.health.WRITE_TOTAL_CALORIES_BURNED',
20
+ 'android.permission.health.READ_FLOORS_CLIMBED',
21
+ 'android.permission.health.WRITE_FLOORS_CLIMBED',
22
+ 'android.permission.health.READ_HEART_RATE',
23
+ 'android.permission.health.WRITE_HEART_RATE',
24
+ 'android.permission.health.READ_RESTING_HEART_RATE',
25
+ 'android.permission.health.WRITE_RESTING_HEART_RATE',
26
+ 'android.permission.health.READ_HEART_RATE_VARIABILITY',
27
+ 'android.permission.health.WRITE_HEART_RATE_VARIABILITY',
28
+ 'android.permission.health.READ_OXYGEN_SATURATION',
29
+ 'android.permission.health.WRITE_OXYGEN_SATURATION',
30
+ 'android.permission.health.READ_RESPIRATORY_RATE',
31
+ 'android.permission.health.WRITE_RESPIRATORY_RATE',
32
+ 'android.permission.health.READ_BODY_TEMPERATURE',
33
+ 'android.permission.health.WRITE_BODY_TEMPERATURE',
34
+ 'android.permission.health.READ_BLOOD_GLUCOSE',
35
+ 'android.permission.health.WRITE_BLOOD_GLUCOSE',
36
+ 'android.permission.health.READ_BLOOD_PRESSURE',
37
+ 'android.permission.health.WRITE_BLOOD_PRESSURE',
38
+ 'android.permission.health.READ_VO2_MAX',
39
+ 'android.permission.health.WRITE_VO2_MAX',
40
+ 'android.permission.health.READ_HEIGHT',
41
+ 'android.permission.health.WRITE_HEIGHT',
42
+ 'android.permission.health.READ_WEIGHT',
43
+ 'android.permission.health.WRITE_WEIGHT',
44
+ 'android.permission.health.READ_BODY_FAT',
45
+ 'android.permission.health.WRITE_BODY_FAT',
46
+ 'android.permission.health.READ_LEAN_BODY_MASS',
47
+ 'android.permission.health.WRITE_LEAN_BODY_MASS',
48
+ 'android.permission.health.READ_SLEEP',
49
+ 'android.permission.health.WRITE_SLEEP',
50
+ 'android.permission.health.READ_EXERCISE',
51
+ 'android.permission.health.WRITE_EXERCISE',
52
+ 'android.permission.health.READ_NUTRITION',
53
+ 'android.permission.health.WRITE_NUTRITION',
54
+ 'android.permission.health.READ_HYDRATION',
55
+ 'android.permission.health.WRITE_HYDRATION',
56
+ 'android.permission.health.READ_WHEELCHAIR_PUSHES',
57
+ 'android.permission.health.WRITE_WHEELCHAIR_PUSHES',
58
+ 'android.permission.health.READ_HEALTH_DATA_HISTORY',
59
+ ];
60
+ const withHealthKitEntitlements = (config, props = {}) => {
61
+ return (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
62
+ config.modResults['com.apple.developer.healthkit'] = true;
63
+ if (props.isBackgroundDeliveryEnabled) {
64
+ config.modResults['com.apple.developer.healthkit.background-delivery'] = true;
65
+ }
66
+ if (props.isClinicalDataEnabled) {
67
+ config.modResults['com.apple.developer.healthkit.access'] = ['health-records'];
68
+ }
69
+ return config;
70
+ });
71
+ };
72
+ const withHealthKitBackgroundModes = (config, props = {}) => {
73
+ if (!props.isBackgroundDeliveryEnabled) {
74
+ return config;
75
+ }
76
+ return (0, config_plugins_1.withInfoPlist)(config, (config) => {
77
+ const modes = new Set(Array.isArray(config.modResults.UIBackgroundModes) ? config.modResults.UIBackgroundModes : []);
78
+ modes.add('healthkit');
79
+ config.modResults.UIBackgroundModes = [...modes];
80
+ return config;
81
+ });
82
+ };
83
+ const withHealthKitUsageDescriptions = (config, props = {}) => {
84
+ const defaults = {
85
+ NSHealthShareUsageDescription: DEFAULT_SHARE,
86
+ NSHealthUpdateUsageDescription: DEFAULT_UPDATE,
87
+ };
88
+ const values = {
89
+ NSHealthShareUsageDescription: props.healthSharePermission,
90
+ NSHealthUpdateUsageDescription: props.healthUpdatePermission,
91
+ };
92
+ if (props.isClinicalDataEnabled) {
93
+ defaults.NSHealthClinicalHealthRecordsShareUsageDescription = DEFAULT_CLINICAL;
94
+ values.NSHealthClinicalHealthRecordsShareUsageDescription =
95
+ props.healthClinicalRecordsPermission;
96
+ }
97
+ return config_plugins_1.IOSConfig.Permissions.createPermissionsPlugin(defaults)(config, values);
98
+ };
99
+ const withHealthConnectMinSdk = (config) => {
100
+ return (0, config_plugins_1.withGradleProperties)(config, (config) => {
101
+ const existing = config.modResults.find((item) => item.type === 'property' && item.key === 'android.minSdkVersion');
102
+ const current = existing && existing.type === 'property' ? Number.parseInt(existing.value, 10) : Number.NaN;
103
+ if (!Number.isFinite(current) || current < HEALTH_CONNECT_MIN_SDK) {
104
+ config_plugins_1.AndroidConfig.BuildProperties.updateAndroidBuildProperty(config.modResults, 'android.minSdkVersion', String(HEALTH_CONNECT_MIN_SDK));
105
+ }
106
+ return config;
107
+ });
108
+ };
109
+ const withHealthConnectManifest = (config, props = {}) => {
110
+ config = config_plugins_1.AndroidConfig.Permissions.withPermissions(config, HEALTH_CONNECT_PERMISSIONS);
111
+ return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
112
+ var _a;
113
+ const manifest = config.modResults.manifest;
114
+ const queries = (_a = manifest.queries) !== null && _a !== void 0 ? _a : [];
115
+ const hasHealthConnect = queries.some((query) => { var _a; return (_a = query.package) === null || _a === void 0 ? void 0 : _a.some((item) => item.$['android:name'] === HEALTH_CONNECT_PACKAGE); });
116
+ if (!hasHealthConnect) {
117
+ queries.push({
118
+ package: [{ $: { 'android:name': HEALTH_CONNECT_PACKAGE } }],
119
+ });
120
+ manifest.queries = queries;
121
+ }
122
+ if (props.healthConnectPrivacyPolicyUrl) {
123
+ const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
124
+ config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, PRIVACY_POLICY_META, props.healthConnectPrivacyPolicyUrl);
125
+ }
126
+ return config;
127
+ });
128
+ };
129
+ const withExpoHealthKit = (config, props) => {
130
+ const pluginProps = props !== null && props !== void 0 ? props : {};
131
+ config = withHealthKitEntitlements(config, pluginProps);
132
+ config = withHealthKitBackgroundModes(config, pluginProps);
133
+ config = withHealthKitUsageDescriptions(config, pluginProps);
134
+ config = withHealthConnectMinSdk(config);
135
+ config = withHealthConnectManifest(config, pluginProps);
136
+ return config;
137
+ };
138
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withExpoHealthKit, pkg.name, pkg.version);
@@ -0,0 +1,116 @@
1
+ import { NativeModule, requireNativeModule } from 'expo';
2
+
3
+ import { HealthKitError } from './errors';
4
+
5
+ import type {
6
+ ExpoHealthKitModuleEvents,
7
+ NativeActivitySummary,
8
+ NativeActivitySummaryQueryOptions,
9
+ NativeAnchoredQueryOptions,
10
+ NativeAnchoredQueryResult,
11
+ NativeAudiogramSample,
12
+ NativeAuthorizationOptions,
13
+ NativeCategoryQueryOptions,
14
+ NativeCategorySample,
15
+ NativeCategorySampleInput,
16
+ NativeClinicalRecord,
17
+ NativeClinicalRecordQueryOptions,
18
+ NativeCorrelation,
19
+ NativeCorrelationInput,
20
+ NativeCorrelationQueryOptions,
21
+ NativeDateRangeQueryOptions,
22
+ NativeDeleteObjectsOptions,
23
+ NativeElectrocardiogramQueryOptions,
24
+ NativeElectrocardiogramSample,
25
+ NativeHeartbeatSeriesQueryOptions,
26
+ NativeHeartbeatSeriesSample,
27
+ NativeQuantityQueryOptions,
28
+ NativeQuantitySample,
29
+ NativeQuantitySampleInput,
30
+ NativeStatistics,
31
+ NativeStatisticsCollectionQueryOptions,
32
+ NativeStatisticsQueryOptions,
33
+ NativeWorkoutInput,
34
+ NativeWorkoutQueryOptions,
35
+ NativeWorkoutRoute,
36
+ NativeWorkoutRouteQueryOptions,
37
+ NativeWorkoutSample,
38
+ } from './types';
39
+
40
+ declare class ExpoHealthKitModule extends NativeModule<ExpoHealthKitModuleEvents> {
41
+ isHealthDataAvailable(): boolean;
42
+ requestAuthorization(options: NativeAuthorizationOptions): Promise<boolean>;
43
+ getAuthorizationStatus(identifier: string): Promise<number>;
44
+ getRequestStatusForAuthorization(options: NativeAuthorizationOptions): Promise<number>;
45
+ queryQuantitySamples(options: NativeQuantityQueryOptions): Promise<NativeQuantitySample[]>;
46
+ queryCategorySamples(options: NativeCategoryQueryOptions): Promise<NativeCategorySample[]>;
47
+ queryWorkouts(options: NativeWorkoutQueryOptions): Promise<NativeWorkoutSample[]>;
48
+ queryElectrocardiograms(
49
+ options: NativeElectrocardiogramQueryOptions
50
+ ): Promise<NativeElectrocardiogramSample[]>;
51
+ queryActivitySummaries(
52
+ options: NativeActivitySummaryQueryOptions
53
+ ): Promise<NativeActivitySummary[]>;
54
+ queryClinicalRecords(options: NativeClinicalRecordQueryOptions): Promise<NativeClinicalRecord[]>;
55
+ queryAudiograms(options: NativeDateRangeQueryOptions): Promise<NativeAudiogramSample[]>;
56
+ queryWorkoutRoute(options: NativeWorkoutRouteQueryOptions): Promise<NativeWorkoutRoute[]>;
57
+ queryCorrelations(options: NativeCorrelationQueryOptions): Promise<NativeCorrelation[]>;
58
+ queryHeartbeatSeries(
59
+ options: NativeHeartbeatSeriesQueryOptions
60
+ ): Promise<NativeHeartbeatSeriesSample[]>;
61
+ queryStatistics(options: NativeStatisticsQueryOptions): Promise<NativeStatistics>;
62
+ queryStatisticsCollection(
63
+ options: NativeStatisticsCollectionQueryOptions
64
+ ): Promise<NativeStatistics[]>;
65
+ queryAnchored(options: NativeAnchoredQueryOptions): Promise<NativeAnchoredQueryResult>;
66
+ saveQuantitySample(sample: NativeQuantitySampleInput): Promise<string>;
67
+ saveCategorySample(sample: NativeCategorySampleInput): Promise<string>;
68
+ saveWorkout(workout: NativeWorkoutInput): Promise<string>;
69
+ saveCorrelation(correlation: NativeCorrelationInput): Promise<string>;
70
+ deleteObjects(options: NativeDeleteObjectsOptions): Promise<number>;
71
+ getBiologicalSex(): Promise<number>;
72
+ getBloodType(): Promise<number>;
73
+ getDateOfBirth(): Promise<string | null>;
74
+ getFitzpatrickSkinType(): Promise<number>;
75
+ getWheelchairUse(): Promise<number>;
76
+ enableBackgroundDelivery(type: string, frequency: number): Promise<boolean>;
77
+ disableBackgroundDelivery(type: string): Promise<boolean>;
78
+ disableAllBackgroundDelivery(): Promise<boolean>;
79
+ observeTypes(types: string[]): Promise<void>;
80
+ clearObserverQueries(): Promise<void>;
81
+ }
82
+
83
+ const nativeModule = requireNativeModule<ExpoHealthKitModule>('ExpoHealthKit');
84
+
85
+ function rethrowAsHealthKitError(error: unknown): never {
86
+ const message = error instanceof Error ? error.message : String(error);
87
+ const code =
88
+ error && typeof error === 'object' && 'code' in error
89
+ ? String((error as { code: unknown }).code)
90
+ : 'ERR_HEALTHKIT';
91
+ const wrapped = new HealthKitError(message, code);
92
+ if (__DEV__) {
93
+ console.warn(`[expo-healthkit] ${code}: ${message}`);
94
+ }
95
+ throw wrapped;
96
+ }
97
+
98
+ export default new Proxy(nativeModule, {
99
+ get(target, property, receiver) {
100
+ const value = Reflect.get(target, property, receiver) as unknown;
101
+ if (typeof value !== 'function') {
102
+ return value;
103
+ }
104
+ return (...args: unknown[]) => {
105
+ try {
106
+ const result = (value as (...inner: unknown[]) => unknown).apply(target, args);
107
+ if (result instanceof Promise) {
108
+ return result.catch(rethrowAsHealthKitError);
109
+ }
110
+ return result;
111
+ } catch (error) {
112
+ rethrowAsHealthKitError(error);
113
+ }
114
+ };
115
+ },
116
+ }) as ExpoHealthKitModule;
@@ -0,0 +1,140 @@
1
+ import { NativeModule, registerWebModule } from 'expo';
2
+
3
+ import { HealthKitUnavailableError } from './errors';
4
+ import type { ExpoHealthKitModuleEvents } from './types';
5
+
6
+ function unavailable(): never {
7
+ throw new HealthKitUnavailableError();
8
+ }
9
+
10
+ class ExpoHealthKitModule extends NativeModule<ExpoHealthKitModuleEvents> {
11
+ isHealthDataAvailable(): boolean {
12
+ return false;
13
+ }
14
+
15
+ async requestAuthorization(): Promise<boolean> {
16
+ return unavailable();
17
+ }
18
+
19
+ async getAuthorizationStatus(): Promise<number> {
20
+ return unavailable();
21
+ }
22
+
23
+ async getRequestStatusForAuthorization(): Promise<number> {
24
+ return unavailable();
25
+ }
26
+
27
+ async queryQuantitySamples(): Promise<never> {
28
+ return unavailable();
29
+ }
30
+
31
+ async queryCategorySamples(): Promise<never> {
32
+ return unavailable();
33
+ }
34
+
35
+ async queryWorkouts(): Promise<never> {
36
+ return unavailable();
37
+ }
38
+
39
+ async queryElectrocardiograms(): Promise<never> {
40
+ return unavailable();
41
+ }
42
+
43
+ async queryActivitySummaries(): Promise<never> {
44
+ return unavailable();
45
+ }
46
+
47
+ async queryClinicalRecords(): Promise<never> {
48
+ return unavailable();
49
+ }
50
+
51
+ async queryAudiograms(): Promise<never> {
52
+ return unavailable();
53
+ }
54
+
55
+ async queryWorkoutRoute(): Promise<never> {
56
+ return unavailable();
57
+ }
58
+
59
+ async queryCorrelations(): Promise<never> {
60
+ return unavailable();
61
+ }
62
+
63
+ async queryHeartbeatSeries(): Promise<never> {
64
+ return unavailable();
65
+ }
66
+
67
+ async queryStatistics(): Promise<never> {
68
+ return unavailable();
69
+ }
70
+
71
+ async queryStatisticsCollection(): Promise<never> {
72
+ return unavailable();
73
+ }
74
+
75
+ async queryAnchored(): Promise<never> {
76
+ return unavailable();
77
+ }
78
+
79
+ async saveQuantitySample(): Promise<string> {
80
+ return unavailable();
81
+ }
82
+
83
+ async saveCategorySample(): Promise<string> {
84
+ return unavailable();
85
+ }
86
+
87
+ async saveWorkout(): Promise<string> {
88
+ return unavailable();
89
+ }
90
+
91
+ async saveCorrelation(): Promise<string> {
92
+ return unavailable();
93
+ }
94
+
95
+ async deleteObjects(): Promise<number> {
96
+ return unavailable();
97
+ }
98
+
99
+ async getBiologicalSex(): Promise<number> {
100
+ return unavailable();
101
+ }
102
+
103
+ async getBloodType(): Promise<number> {
104
+ return unavailable();
105
+ }
106
+
107
+ async getDateOfBirth(): Promise<string | null> {
108
+ return unavailable();
109
+ }
110
+
111
+ async getFitzpatrickSkinType(): Promise<number> {
112
+ return unavailable();
113
+ }
114
+
115
+ async getWheelchairUse(): Promise<number> {
116
+ return unavailable();
117
+ }
118
+
119
+ async enableBackgroundDelivery(): Promise<boolean> {
120
+ return unavailable();
121
+ }
122
+
123
+ async disableBackgroundDelivery(): Promise<boolean> {
124
+ return unavailable();
125
+ }
126
+
127
+ async disableAllBackgroundDelivery(): Promise<boolean> {
128
+ return unavailable();
129
+ }
130
+
131
+ async observeTypes(): Promise<void> {
132
+ return unavailable();
133
+ }
134
+
135
+ async clearObserverQueries(): Promise<void> {
136
+ return unavailable();
137
+ }
138
+ }
139
+
140
+ export default registerWebModule(ExpoHealthKitModule, 'ExpoHealthKitModule');
package/src/dates.ts ADDED
@@ -0,0 +1,17 @@
1
+ import type { DateInput } from './types';
2
+
3
+ export function toIso(value: DateInput): string {
4
+ return value instanceof Date ? value.toISOString() : value;
5
+ }
6
+
7
+ export function toOptionalIso(value?: DateInput): string | undefined {
8
+ return value == null ? undefined : toIso(value);
9
+ }
10
+
11
+ export function fromIso(value: string): Date {
12
+ return new Date(value);
13
+ }
14
+
15
+ export function resolveEndDate(startDate: DateInput, endDate?: DateInput): string {
16
+ return toIso(endDate ?? startDate);
17
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,22 @@
1
+ export class HealthKitUnavailableError extends Error {
2
+ readonly code = 'ERR_HEALTHKIT_UNAVAILABLE';
3
+
4
+ constructor(
5
+ message = 'Health data is not available. On iOS use a development build on a physical iPhone or the iOS Simulator. On Android install Health Connect and use a development build.'
6
+ ) {
7
+ super(message);
8
+ this.name = 'HealthKitUnavailableError';
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ }
12
+
13
+ export class HealthKitError extends Error {
14
+ readonly code: string;
15
+
16
+ constructor(message: string, code = 'ERR_HEALTHKIT') {
17
+ super(message);
18
+ this.name = 'HealthKitError';
19
+ this.code = code;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ }