@capgo/capacitor-health 7.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst Health = registerPlugin('Health', {\n web: () => import('./web').then((m) => new m.HealthWeb()),\n});\nexport * from './definitions';\nexport { Health };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class HealthWeb extends WebPlugin {\n async isAvailable() {\n return {\n available: false,\n platform: 'web',\n reason: 'Native health APIs are not accessible in a browser environment.',\n };\n }\n async requestAuthorization(_options) {\n throw this.unimplemented('Health permissions are only available on native platforms.');\n }\n async checkAuthorization(_options) {\n throw this.unimplemented('Health permissions are only available on native platforms.');\n }\n async readSamples(_options) {\n throw this.unimplemented('Reading health data is only available on native platforms.');\n }\n async saveSample(_options) {\n throw this.unimplemented('Writing health data is only available on native platforms.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,MAAM,GAAGA,mBAAc,CAAC,QAAQ,EAAE;IACxC,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC7D,CAAC;;ICFM,MAAM,SAAS,SAASC,cAAS,CAAC;IACzC,IAAI,MAAM,WAAW,GAAG;IACxB,QAAQ,OAAO;IACf,YAAY,SAAS,EAAE,KAAK;IAC5B,YAAY,QAAQ,EAAE,KAAK;IAC3B,YAAY,MAAM,EAAE,iEAAiE;IACrF,SAAS;IACT,IAAI;IACJ,IAAI,MAAM,oBAAoB,CAAC,QAAQ,EAAE;IACzC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ,IAAI,MAAM,kBAAkB,CAAC,QAAQ,EAAE;IACvC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ,IAAI,MAAM,WAAW,CAAC,QAAQ,EAAE;IAChC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;IAC/B,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,4DAA4D,CAAC;IAC9F,IAAI;IACJ;;;;;;;;;;;;;;;"}
@@ -0,0 +1,420 @@
1
+ import Foundation
2
+ import HealthKit
3
+
4
+ enum HealthManagerError: LocalizedError {
5
+ case healthDataUnavailable
6
+ case invalidDataType(String)
7
+ case invalidDate(String)
8
+ case dataTypeUnavailable(String)
9
+ case invalidDateRange
10
+ case operationFailed(String)
11
+
12
+ var errorDescription: String? {
13
+ switch self {
14
+ case .healthDataUnavailable:
15
+ return "Health data is not available on this device."
16
+ case let .invalidDataType(identifier):
17
+ return "Unsupported health data type: \(identifier)."
18
+ case let .invalidDate(dateString):
19
+ return "Invalid ISO 8601 date value: \(dateString)."
20
+ case let .dataTypeUnavailable(identifier):
21
+ return "The health data type \(identifier) is not available on this device."
22
+ case .invalidDateRange:
23
+ return "endDate must be greater than or equal to startDate."
24
+ case let .operationFailed(message):
25
+ return message
26
+ }
27
+ }
28
+ }
29
+
30
+ enum HealthDataType: String, CaseIterable {
31
+ case steps
32
+ case distance
33
+ case calories
34
+ case heartRate
35
+ case weight
36
+
37
+ func sampleType() throws -> HKQuantityType {
38
+ let identifier: HKQuantityTypeIdentifier
39
+ switch self {
40
+ case .steps:
41
+ identifier = .stepCount
42
+ case .distance:
43
+ identifier = .distanceWalkingRunning
44
+ case .calories:
45
+ identifier = .activeEnergyBurned
46
+ case .heartRate:
47
+ identifier = .heartRate
48
+ case .weight:
49
+ identifier = .bodyMass
50
+ }
51
+
52
+ guard let type = HKObjectType.quantityType(forIdentifier: identifier) else {
53
+ throw HealthManagerError.dataTypeUnavailable(rawValue)
54
+ }
55
+ return type
56
+ }
57
+
58
+ var defaultUnit: HKUnit {
59
+ switch self {
60
+ case .steps:
61
+ return HKUnit.count()
62
+ case .distance:
63
+ return HKUnit.meter()
64
+ case .calories:
65
+ return HKUnit.kilocalorie()
66
+ case .heartRate:
67
+ return HKUnit.count().unitDivided(by: HKUnit.minute())
68
+ case .weight:
69
+ return HKUnit.gramUnit(with: .kilo)
70
+ }
71
+ }
72
+
73
+ var unitIdentifier: String {
74
+ switch self {
75
+ case .steps:
76
+ return "count"
77
+ case .distance:
78
+ return "meter"
79
+ case .calories:
80
+ return "kilocalorie"
81
+ case .heartRate:
82
+ return "bpm"
83
+ case .weight:
84
+ return "kilogram"
85
+ }
86
+ }
87
+
88
+ static func parseMany(_ identifiers: [String]) throws -> [HealthDataType] {
89
+ try identifiers.map { identifier in
90
+ guard let type = HealthDataType(rawValue: identifier) else {
91
+ throw HealthManagerError.invalidDataType(identifier)
92
+ }
93
+ return type
94
+ }
95
+ }
96
+ }
97
+
98
+ struct AuthorizationStatusPayload {
99
+ let readAuthorized: [HealthDataType]
100
+ let readDenied: [HealthDataType]
101
+ let writeAuthorized: [HealthDataType]
102
+ let writeDenied: [HealthDataType]
103
+
104
+ func toDictionary() -> [String: Any] {
105
+ return [
106
+ "readAuthorized": readAuthorized.map { $0.rawValue },
107
+ "readDenied": readDenied.map { $0.rawValue },
108
+ "writeAuthorized": writeAuthorized.map { $0.rawValue },
109
+ "writeDenied": writeDenied.map { $0.rawValue }
110
+ ]
111
+ }
112
+ }
113
+
114
+ final class Health {
115
+ private let healthStore = HKHealthStore()
116
+ private let isoFormatter: ISO8601DateFormatter
117
+
118
+ init() {
119
+ let formatter = ISO8601DateFormatter()
120
+ formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
121
+ isoFormatter = formatter
122
+ }
123
+
124
+ func availabilityPayload() -> [String: Any] {
125
+ let available = HKHealthStore.isHealthDataAvailable()
126
+ if available {
127
+ return [
128
+ "available": true,
129
+ "platform": "ios"
130
+ ]
131
+ }
132
+
133
+ return [
134
+ "available": false,
135
+ "platform": "ios",
136
+ "reason": "Health data is not available on this device."
137
+ ]
138
+ }
139
+
140
+ func requestAuthorization(readIdentifiers: [String], writeIdentifiers: [String], completion: @escaping (Result<AuthorizationStatusPayload, Error>) -> Void) {
141
+ guard HKHealthStore.isHealthDataAvailable() else {
142
+ completion(.failure(HealthManagerError.healthDataUnavailable))
143
+ return
144
+ }
145
+
146
+ do {
147
+ let readTypes = try HealthDataType.parseMany(readIdentifiers)
148
+ let writeTypes = try HealthDataType.parseMany(writeIdentifiers)
149
+
150
+ let readObjectTypes = try objectTypes(for: readTypes)
151
+ let writeSampleTypes = try sampleTypes(for: writeTypes)
152
+
153
+ healthStore.requestAuthorization(toShare: writeSampleTypes, read: readObjectTypes) { [weak self] success, error in
154
+ guard let self = self else { return }
155
+
156
+ if let error = error {
157
+ completion(.failure(error))
158
+ return
159
+ }
160
+
161
+ if success {
162
+ self.evaluateAuthorizationStatus(readTypes: readTypes, writeTypes: writeTypes) { result in
163
+ completion(.success(result))
164
+ }
165
+ } else {
166
+ completion(.failure(HealthManagerError.operationFailed("Authorization request was not granted.")))
167
+ }
168
+ }
169
+ } catch {
170
+ completion(.failure(error))
171
+ }
172
+ }
173
+
174
+ func checkAuthorization(readIdentifiers: [String], writeIdentifiers: [String], completion: @escaping (Result<AuthorizationStatusPayload, Error>) -> Void) {
175
+ do {
176
+ let readTypes = try HealthDataType.parseMany(readIdentifiers)
177
+ let writeTypes = try HealthDataType.parseMany(writeIdentifiers)
178
+
179
+ evaluateAuthorizationStatus(readTypes: readTypes, writeTypes: writeTypes) { payload in
180
+ completion(.success(payload))
181
+ }
182
+ } catch {
183
+ completion(.failure(error))
184
+ }
185
+ }
186
+
187
+ func readSamples(dataTypeIdentifier: String, startDateString: String?, endDateString: String?, limit: Int?, ascending: Bool, completion: @escaping (Result<[[String: Any]], Error>) -> Void) throws {
188
+ let dataType = try parseDataType(identifier: dataTypeIdentifier)
189
+ let sampleType = try dataType.sampleType()
190
+
191
+ let startDate = try parseDate(startDateString, defaultValue: Date().addingTimeInterval(-86400))
192
+ let endDate = try parseDate(endDateString, defaultValue: Date())
193
+
194
+ guard endDate >= startDate else {
195
+ throw HealthManagerError.invalidDateRange
196
+ }
197
+
198
+ let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
199
+ let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
200
+ let queryLimit = limit ?? 100
201
+
202
+ let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
203
+ guard let self = self else { return }
204
+
205
+ if let error = error {
206
+ completion(.failure(error))
207
+ return
208
+ }
209
+
210
+ guard let quantitySamples = samples as? [HKQuantitySample] else {
211
+ completion(.success([]))
212
+ return
213
+ }
214
+
215
+ let results = quantitySamples.map { sample -> [String: Any] in
216
+ let value = sample.quantity.doubleValue(for: dataType.defaultUnit)
217
+ var payload: [String: Any] = [
218
+ "dataType": dataType.rawValue,
219
+ "value": value,
220
+ "unit": dataType.unitIdentifier,
221
+ "startDate": self.isoFormatter.string(from: sample.startDate),
222
+ "endDate": self.isoFormatter.string(from: sample.endDate)
223
+ ]
224
+
225
+ let source = sample.sourceRevision.source
226
+ payload["sourceName"] = source.name
227
+ payload["sourceId"] = source.bundleIdentifier
228
+
229
+ return payload
230
+ }
231
+
232
+ completion(.success(results))
233
+ }
234
+
235
+ healthStore.execute(query)
236
+ }
237
+
238
+ func saveSample(dataTypeIdentifier: String, value: Double, unitIdentifier: String?, startDateString: String?, endDateString: String?, metadata: [String: String]?, completion: @escaping (Result<Void, Error>) -> Void) throws {
239
+ guard HKHealthStore.isHealthDataAvailable() else {
240
+ throw HealthManagerError.healthDataUnavailable
241
+ }
242
+
243
+ let dataType = try parseDataType(identifier: dataTypeIdentifier)
244
+ let sampleType = try dataType.sampleType()
245
+
246
+ let startDate = try parseDate(startDateString, defaultValue: Date())
247
+ let endDate = try parseDate(endDateString, defaultValue: startDate)
248
+
249
+ guard endDate >= startDate else {
250
+ throw HealthManagerError.invalidDateRange
251
+ }
252
+
253
+ let unit = unit(for: unitIdentifier, dataType: dataType)
254
+ let quantity = HKQuantity(unit: unit, doubleValue: value)
255
+
256
+ var metadataDictionary: [String: Any]?
257
+ if let metadata = metadata, !metadata.isEmpty {
258
+ metadataDictionary = metadata.reduce(into: [String: Any]()) { result, entry in
259
+ result[entry.key] = entry.value
260
+ }
261
+ }
262
+
263
+ let sample = HKQuantitySample(type: sampleType, quantity: quantity, start: startDate, end: endDate, metadata: metadataDictionary)
264
+
265
+ healthStore.save(sample) { success, error in
266
+ if let error = error {
267
+ completion(.failure(error))
268
+ return
269
+ }
270
+
271
+ if success {
272
+ completion(.success(()))
273
+ } else {
274
+ completion(.failure(HealthManagerError.operationFailed("Failed to save the sample.")))
275
+ }
276
+ }
277
+ }
278
+
279
+ private func evaluateAuthorizationStatus(readTypes: [HealthDataType], writeTypes: [HealthDataType], completion: @escaping (AuthorizationStatusPayload) -> Void) {
280
+ let writeStatus = writeAuthorizationStatus(for: writeTypes)
281
+
282
+ readAuthorizationStatus(for: readTypes) { readAuthorized, readDenied in
283
+ let payload = AuthorizationStatusPayload(
284
+ readAuthorized: readAuthorized,
285
+ readDenied: readDenied,
286
+ writeAuthorized: writeStatus.authorized,
287
+ writeDenied: writeStatus.denied
288
+ )
289
+ completion(payload)
290
+ }
291
+ }
292
+
293
+ private func writeAuthorizationStatus(for types: [HealthDataType]) -> (authorized: [HealthDataType], denied: [HealthDataType]) {
294
+ var authorized: [HealthDataType] = []
295
+ var denied: [HealthDataType] = []
296
+
297
+ for type in types {
298
+ guard let sampleType = try? type.sampleType() else {
299
+ denied.append(type)
300
+ continue
301
+ }
302
+
303
+ switch healthStore.authorizationStatus(for: sampleType) {
304
+ case .sharingAuthorized:
305
+ authorized.append(type)
306
+ case .sharingDenied, .notDetermined:
307
+ denied.append(type)
308
+ @unknown default:
309
+ denied.append(type)
310
+ }
311
+ }
312
+
313
+ return (authorized, denied)
314
+ }
315
+
316
+ private func readAuthorizationStatus(for types: [HealthDataType], completion: @escaping ([HealthDataType], [HealthDataType]) -> Void) {
317
+ guard !types.isEmpty else {
318
+ completion([], [])
319
+ return
320
+ }
321
+
322
+ if #available(iOS 12.0, *) {
323
+ let group = DispatchGroup()
324
+ let lock = NSLock()
325
+ var authorized: [HealthDataType] = []
326
+ var denied: [HealthDataType] = []
327
+
328
+ for type in types {
329
+ guard let objectType = try? type.sampleType() else {
330
+ denied.append(type)
331
+ continue
332
+ }
333
+
334
+ group.enter()
335
+ let readSet = Set<HKObjectType>([objectType])
336
+ healthStore.getRequestStatusForAuthorization(toShare: Set<HKSampleType>(), read: readSet) { status, error in
337
+ defer { group.leave() }
338
+
339
+ if error != nil {
340
+ lock.lock(); denied.append(type); lock.unlock()
341
+ return
342
+ }
343
+
344
+ switch status {
345
+ case .unnecessary:
346
+ lock.lock(); authorized.append(type); lock.unlock()
347
+ case .shouldRequest, .unknown:
348
+ lock.lock(); denied.append(type); lock.unlock()
349
+ @unknown default:
350
+ lock.lock(); denied.append(type); lock.unlock()
351
+ }
352
+ }
353
+ }
354
+
355
+ group.notify(queue: .main) {
356
+ completion(authorized, denied)
357
+ }
358
+ } else {
359
+ completion(types, [])
360
+ }
361
+ }
362
+
363
+ private func parseDataType(identifier: String) throws -> HealthDataType {
364
+ guard let type = HealthDataType(rawValue: identifier) else {
365
+ throw HealthManagerError.invalidDataType(identifier)
366
+ }
367
+ return type
368
+ }
369
+
370
+ private func parseDate(_ string: String?, defaultValue: Date) throws -> Date {
371
+ guard let value = string else {
372
+ return defaultValue
373
+ }
374
+
375
+ if let date = isoFormatter.date(from: value) {
376
+ return date
377
+ }
378
+
379
+ throw HealthManagerError.invalidDate(value)
380
+ }
381
+
382
+ private func unit(for identifier: String?, dataType: HealthDataType) -> HKUnit {
383
+ guard let identifier = identifier else {
384
+ return dataType.defaultUnit
385
+ }
386
+
387
+ switch identifier {
388
+ case "count":
389
+ return HKUnit.count()
390
+ case "meter":
391
+ return HKUnit.meter()
392
+ case "kilocalorie":
393
+ return HKUnit.kilocalorie()
394
+ case "bpm":
395
+ return HKUnit.count().unitDivided(by: HKUnit.minute())
396
+ case "kilogram":
397
+ return HKUnit.gramUnit(with: .kilo)
398
+ default:
399
+ return dataType.defaultUnit
400
+ }
401
+ }
402
+
403
+ private func objectTypes(for dataTypes: [HealthDataType]) throws -> Set<HKObjectType> {
404
+ var set = Set<HKObjectType>()
405
+ for dataType in dataTypes {
406
+ let type = try dataType.sampleType()
407
+ set.insert(type)
408
+ }
409
+ return set
410
+ }
411
+
412
+ private func sampleTypes(for dataTypes: [HealthDataType]) throws -> Set<HKSampleType> {
413
+ var set = Set<HKSampleType>()
414
+ for dataType in dataTypes {
415
+ let type = try dataType.sampleType() as HKSampleType
416
+ set.insert(type)
417
+ }
418
+ return set
419
+ }
420
+ }
@@ -0,0 +1,130 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(HealthPlugin)
5
+ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "HealthPlugin"
7
+ public let jsName = "Health"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise),
10
+ CAPPluginMethod(name: "requestAuthorization", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "checkAuthorization", returnType: CAPPluginReturnPromise),
12
+ CAPPluginMethod(name: "readSamples", returnType: CAPPluginReturnPromise),
13
+ CAPPluginMethod(name: "saveSample", returnType: CAPPluginReturnPromise)
14
+ ]
15
+
16
+ private let implementation = Health()
17
+
18
+ @objc func isAvailable(_ call: CAPPluginCall) {
19
+ call.resolve(implementation.availabilityPayload())
20
+ }
21
+
22
+ @objc func requestAuthorization(_ call: CAPPluginCall) {
23
+ let read = (call.getArray("read") as? [String]) ?? []
24
+ let write = (call.getArray("write") as? [String]) ?? []
25
+
26
+ implementation.requestAuthorization(readIdentifiers: read, writeIdentifiers: write) { result in
27
+ DispatchQueue.main.async {
28
+ switch result {
29
+ case let .success(payload):
30
+ call.resolve(payload.toDictionary())
31
+ case let .failure(error):
32
+ call.reject(error.localizedDescription, nil, error)
33
+ }
34
+ }
35
+ }
36
+ }
37
+
38
+ @objc func checkAuthorization(_ call: CAPPluginCall) {
39
+ let read = (call.getArray("read") as? [String]) ?? []
40
+ let write = (call.getArray("write") as? [String]) ?? []
41
+
42
+ implementation.checkAuthorization(readIdentifiers: read, writeIdentifiers: write) { result in
43
+ DispatchQueue.main.async {
44
+ switch result {
45
+ case let .success(payload):
46
+ call.resolve(payload.toDictionary())
47
+ case let .failure(error):
48
+ call.reject(error.localizedDescription, nil, error)
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ @objc func readSamples(_ call: CAPPluginCall) {
55
+ guard let dataType = call.getString("dataType") else {
56
+ call.reject("dataType is required")
57
+ return
58
+ }
59
+
60
+ let startDate = call.getString("startDate")
61
+ let endDate = call.getString("endDate")
62
+ let limit = call.getInt("limit")
63
+ let ascending = call.getBool("ascending") ?? false
64
+
65
+ do {
66
+ try implementation.readSamples(
67
+ dataTypeIdentifier: dataType,
68
+ startDateString: startDate,
69
+ endDateString: endDate,
70
+ limit: limit,
71
+ ascending: ascending
72
+ ) { result in
73
+ DispatchQueue.main.async {
74
+ switch result {
75
+ case let .success(samples):
76
+ call.resolve(["samples": samples])
77
+ case let .failure(error):
78
+ call.reject(error.localizedDescription, nil, error)
79
+ }
80
+ }
81
+ }
82
+ } catch {
83
+ call.reject(error.localizedDescription, nil, error)
84
+ }
85
+ }
86
+
87
+ @objc func saveSample(_ call: CAPPluginCall) {
88
+ guard let dataType = call.getString("dataType") else {
89
+ call.reject("dataType is required")
90
+ return
91
+ }
92
+
93
+ guard let value = call.getDouble("value") else {
94
+ call.reject("value is required")
95
+ return
96
+ }
97
+
98
+ let unit = call.getString("unit")
99
+ let startDate = call.getString("startDate")
100
+ let endDate = call.getString("endDate")
101
+ let metadataAny = call.getObject("metadata") as? [String: Any]
102
+ let metadata = metadataAny?.reduce(into: [String: String]()) { result, entry in
103
+ if let stringValue = entry.value as? String {
104
+ result[entry.key] = stringValue
105
+ }
106
+ }
107
+
108
+ do {
109
+ try implementation.saveSample(
110
+ dataTypeIdentifier: dataType,
111
+ value: value,
112
+ unitIdentifier: unit,
113
+ startDateString: startDate,
114
+ endDateString: endDate,
115
+ metadata: metadata
116
+ ) { result in
117
+ DispatchQueue.main.async {
118
+ switch result {
119
+ case .success:
120
+ call.resolve()
121
+ case let .failure(error):
122
+ call.reject(error.localizedDescription, nil, error)
123
+ }
124
+ }
125
+ }
126
+ } catch {
127
+ call.reject(error.localizedDescription, nil, error)
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import HealthPlugin
3
+
4
+ class HealthTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = Health()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@capgo/capacitor-health",
3
+ "version": "7.0.0",
4
+ "description": "Capacitor plugin to interact with data from Apple HealthKit and Health Connect",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapgoCapacitorHealth.podspec"
17
+ ],
18
+ "author": "Martin Donadieu <martin@capgo.app>",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Cap-go/capacitor-health.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Cap-go/capacitor-health/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CapgoCapacitorHealth -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api HealthPlugin --output-readme README.md --output-json dist/docs.json",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "@capacitor/android": "^7.0.0",
50
+ "@capacitor/core": "^7.0.0",
51
+ "@capacitor/docgen": "^0.3.0",
52
+ "@capacitor/ios": "^7.0.0",
53
+ "@ionic/eslint-config": "^0.4.0",
54
+ "@ionic/prettier-config": "^4.0.0",
55
+ "@ionic/swiftlint-config": "^2.0.0",
56
+ "eslint": "^8.57.0",
57
+ "prettier": "^3.4.2",
58
+ "prettier-plugin-java": "^2.6.6",
59
+ "rimraf": "^6.0.1",
60
+ "rollup": "^4.30.1",
61
+ "swiftlint": "^2.0.0",
62
+ "typescript": "~4.1.5"
63
+ },
64
+ "peerDependencies": {
65
+ "@capacitor/core": ">=7.0.0"
66
+ },
67
+ "prettier": "@ionic/prettier-config",
68
+ "swiftlint": "@ionic/swiftlint-config",
69
+ "eslintConfig": {
70
+ "extends": "@ionic/eslint-config/recommended"
71
+ },
72
+ "capacitor": {
73
+ "ios": {
74
+ "src": "ios"
75
+ },
76
+ "android": {
77
+ "src": "android"
78
+ }
79
+ }
80
+ }