@capgo/background-geolocation 8.0.32 → 8.0.34
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.
- package/README.md +298 -14
- package/android/build.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +17 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/BackgroundGeolocation.java +281 -2
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceBootReceiver.java +58 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceBroadcastReceiver.java +83 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceStore.java +255 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/GeofenceTransitionWorker.java +35 -0
- package/dist/docs.json +740 -4
- package/dist/esm/definitions.d.ts +317 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +17 -1
- package/dist/esm/web.js +108 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +108 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +108 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoBackgroundGeolocationPlugin/CapgoCapacitorBackgroundGeolocationPlugin.swift +425 -4
- package/ios/Tests/CapgoBackgroundGeolocationPluginTests/CapgoBackgroundGeolocationPluginTests.swift +5 -0
- package/package.json +17 -10
package/ios/Sources/CapgoBackgroundGeolocationPlugin/CapgoCapacitorBackgroundGeolocationPlugin.swift
CHANGED
|
@@ -38,7 +38,7 @@ func formatLocation(_ location: CLLocation) -> PluginCallResultData {
|
|
|
38
38
|
@objc(BackgroundGeolocation)
|
|
39
39
|
// swiftlint:disable:next type_body_length
|
|
40
40
|
public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBridgedPlugin {
|
|
41
|
-
private let pluginVersion: String = "8.0.
|
|
41
|
+
private let pluginVersion: String = "8.0.34"
|
|
42
42
|
public let identifier = "BackgroundGeolocationPlugin"
|
|
43
43
|
public let jsName = "BackgroundGeolocation"
|
|
44
44
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -46,9 +46,15 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
46
46
|
CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
|
|
47
47
|
CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
|
|
48
48
|
CAPPluginMethod(name: "setPlannedRoute", returnType: CAPPluginReturnPromise),
|
|
49
|
+
CAPPluginMethod(name: "setupGeofencing", returnType: CAPPluginReturnPromise),
|
|
50
|
+
CAPPluginMethod(name: "addGeofence", returnType: CAPPluginReturnPromise),
|
|
51
|
+
CAPPluginMethod(name: "removeGeofence", returnType: CAPPluginReturnPromise),
|
|
52
|
+
CAPPluginMethod(name: "removeAllGeofences", returnType: CAPPluginReturnPromise),
|
|
53
|
+
CAPPluginMethod(name: "getMonitoredGeofences", returnType: CAPPluginReturnPromise),
|
|
49
54
|
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
50
55
|
]
|
|
51
56
|
private var locationManager: CLLocationManager?
|
|
57
|
+
private var geofenceLocationManager: CLLocationManager?
|
|
52
58
|
private var created: Date?
|
|
53
59
|
private var allowStale: Bool = false
|
|
54
60
|
private var isUpdatingLocation: Bool = false
|
|
@@ -57,12 +63,31 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
57
63
|
private var plannedRoute: [[Double]] = []
|
|
58
64
|
private var isOffRoute: Bool = true
|
|
59
65
|
private var distanceThreshold: Double = 50.0 // Default distance threshold in meters
|
|
66
|
+
private var geofenceBackendUrl: URL?
|
|
67
|
+
private var geofenceNotifyOnEntry: Bool = true
|
|
68
|
+
private var geofenceNotifyOnExit: Bool = true
|
|
69
|
+
private var geofencePayload: [String: Any] = [:]
|
|
70
|
+
private var pendingGeofenceSetupCall: CAPPluginCall?
|
|
71
|
+
private var pendingGeofenceSetupTimeout: DispatchWorkItem?
|
|
72
|
+
private var pendingGeofenceAddCalls: [String: CAPPluginCall] = [:]
|
|
73
|
+
private var pendingGeofenceRegions: [String: (region: CLCircularRegion, payload: [String: Any])] = [:]
|
|
74
|
+
private var lastGeofenceTransition: [String: String] = [:]
|
|
75
|
+
|
|
76
|
+
private let geofenceUrlKey = "CapgoBackgroundGeolocation.geofence.url"
|
|
77
|
+
private let geofenceNotifyOnEntryKey = "CapgoBackgroundGeolocation.geofence.notifyOnEntry"
|
|
78
|
+
private let geofenceNotifyOnExitKey = "CapgoBackgroundGeolocation.geofence.notifyOnExit"
|
|
79
|
+
private let geofencePayloadKey = "CapgoBackgroundGeolocation.geofence.payload"
|
|
80
|
+
private let geofenceRegionPrefix = "CapgoBackgroundGeolocation.geofence.region."
|
|
60
81
|
|
|
61
82
|
// Earth radius in meters for distance calculations
|
|
62
83
|
private static let earthRadiusMeters: Double = 6371000.0
|
|
63
84
|
|
|
64
85
|
@objc override public func load() {
|
|
65
86
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
87
|
+
restoreGeofenceConfiguration()
|
|
88
|
+
DispatchQueue.main.async {
|
|
89
|
+
_ = self.ensureGeofenceLocationManager()
|
|
90
|
+
}
|
|
66
91
|
}
|
|
67
92
|
|
|
68
93
|
@objc func start(_ call: CAPPluginCall) {
|
|
@@ -225,6 +250,338 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
225
250
|
}
|
|
226
251
|
}
|
|
227
252
|
|
|
253
|
+
private func requestGeofenceAlwaysAuthorization(_ call: CAPPluginCall, manager: CLLocationManager, status: CLAuthorizationStatus) {
|
|
254
|
+
pendingGeofenceSetupCall = call
|
|
255
|
+
pendingGeofenceSetupTimeout?.cancel()
|
|
256
|
+
let timeout = DispatchWorkItem { [weak self] in
|
|
257
|
+
guard let self = self, let pendingCall = self.pendingGeofenceSetupCall else { return }
|
|
258
|
+
self.pendingGeofenceSetupCall = nil
|
|
259
|
+
self.pendingGeofenceSetupTimeout = nil
|
|
260
|
+
if manager.authorizationStatus == .authorizedAlways {
|
|
261
|
+
pendingCall.resolve()
|
|
262
|
+
} else {
|
|
263
|
+
pendingCall.reject(
|
|
264
|
+
"Always location permission is required for geofencing",
|
|
265
|
+
"NOT_AUTHORIZED"
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
pendingGeofenceSetupTimeout = timeout
|
|
270
|
+
manager.requestAlwaysAuthorization()
|
|
271
|
+
if status == .authorizedWhenInUse {
|
|
272
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 30, execute: timeout)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@objc func setupGeofencing(_ call: CAPPluginCall) {
|
|
277
|
+
DispatchQueue.main.async {
|
|
278
|
+
if self.pendingGeofenceSetupCall != nil {
|
|
279
|
+
return call.reject("A geofence permission request is already in progress", "PERMISSION_REQUEST_IN_PROGRESS")
|
|
280
|
+
}
|
|
281
|
+
guard CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) else {
|
|
282
|
+
return call.reject("Geofencing is not available on this device", "NOT_AVAILABLE")
|
|
283
|
+
}
|
|
284
|
+
var backendUrl: URL?
|
|
285
|
+
if let urlString = call.getString("url"), !urlString.isEmpty {
|
|
286
|
+
guard let url = URL(string: urlString),
|
|
287
|
+
let scheme = url.scheme?.lowercased(),
|
|
288
|
+
["http", "https"].contains(scheme) else {
|
|
289
|
+
return call.reject("Given url is not valid")
|
|
290
|
+
}
|
|
291
|
+
backendUrl = url
|
|
292
|
+
}
|
|
293
|
+
let payload = call.getObject("payload") ?? [:]
|
|
294
|
+
guard JSONSerialization.isValidJSONObject(payload) else {
|
|
295
|
+
return call.reject("Payload must be valid JSON")
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
self.geofenceBackendUrl = backendUrl
|
|
299
|
+
self.geofenceNotifyOnEntry = call.getBool("notifyOnEntry") ?? true
|
|
300
|
+
self.geofenceNotifyOnExit = call.getBool("notifyOnExit") ?? true
|
|
301
|
+
self.geofencePayload = payload
|
|
302
|
+
self.persistGeofenceConfiguration()
|
|
303
|
+
|
|
304
|
+
let manager = self.ensureGeofenceLocationManager()
|
|
305
|
+
let status = manager.authorizationStatus
|
|
306
|
+
if status == .authorizedAlways {
|
|
307
|
+
return call.resolve()
|
|
308
|
+
}
|
|
309
|
+
if call.getBool("requestPermissions") == false {
|
|
310
|
+
return call.reject("Always location permission is required for geofencing", "NOT_AUTHORIZED")
|
|
311
|
+
}
|
|
312
|
+
if [.denied, .restricted].contains(status) {
|
|
313
|
+
return call.reject("Always location permission is required for geofencing", "NOT_AUTHORIZED")
|
|
314
|
+
}
|
|
315
|
+
self.requestGeofenceAlwaysAuthorization(call, manager: manager, status: status)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@objc func addGeofence(_ call: CAPPluginCall) {
|
|
320
|
+
DispatchQueue.main.async {
|
|
321
|
+
let manager = self.ensureGeofenceLocationManager()
|
|
322
|
+
guard self.geofenceAvailable(manager) else {
|
|
323
|
+
return call.reject("Always location permission is required for geofencing", "NOT_AUTHORIZED")
|
|
324
|
+
}
|
|
325
|
+
guard let latitude = call.getDouble("latitude") else {
|
|
326
|
+
return call.reject("Latitude is required")
|
|
327
|
+
}
|
|
328
|
+
guard let longitude = call.getDouble("longitude") else {
|
|
329
|
+
return call.reject("Longitude is required")
|
|
330
|
+
}
|
|
331
|
+
guard let identifier = call.getString("identifier"), !identifier.isEmpty else {
|
|
332
|
+
return call.reject("Identifier is required")
|
|
333
|
+
}
|
|
334
|
+
let radius = call.getDouble("radius") ?? 50.0
|
|
335
|
+
guard CLLocationCoordinate2DIsValid(CLLocationCoordinate2D(latitude: latitude, longitude: longitude)) else {
|
|
336
|
+
return call.reject("Invalid latitude or longitude")
|
|
337
|
+
}
|
|
338
|
+
guard radius > 0 else {
|
|
339
|
+
return call.reject("Radius must be greater than 0")
|
|
340
|
+
}
|
|
341
|
+
let maximumDistance = manager.maximumRegionMonitoringDistance
|
|
342
|
+
guard maximumDistance <= 0 || radius <= maximumDistance else {
|
|
343
|
+
return call.reject("Radius exceeds the maximum supported region monitoring distance")
|
|
344
|
+
}
|
|
345
|
+
let notifyOnEntry = call.getBool("notifyOnEntry") ?? self.geofenceNotifyOnEntry
|
|
346
|
+
let notifyOnExit = call.getBool("notifyOnExit") ?? self.geofenceNotifyOnExit
|
|
347
|
+
guard notifyOnEntry || notifyOnExit else {
|
|
348
|
+
return call.reject("At least one transition must be enabled")
|
|
349
|
+
}
|
|
350
|
+
let payload = call.getObject("payload") ?? [:]
|
|
351
|
+
guard JSONSerialization.isValidJSONObject(payload) else {
|
|
352
|
+
return call.reject("Payload must be valid JSON")
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
guard self.pendingGeofenceAddCalls[identifier] == nil else {
|
|
356
|
+
return call.reject("A geofence with that identifier is already being added", "PENDING")
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let center = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
|
|
360
|
+
let region = CLCircularRegion(center: center, radius: radius, identifier: identifier)
|
|
361
|
+
region.notifyOnEntry = notifyOnEntry
|
|
362
|
+
region.notifyOnExit = notifyOnExit
|
|
363
|
+
self.pendingGeofenceAddCalls[identifier] = call
|
|
364
|
+
self.pendingGeofenceRegions[identifier] = (region, payload)
|
|
365
|
+
manager.startMonitoring(for: region)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@objc func removeGeofence(_ call: CAPPluginCall) {
|
|
370
|
+
DispatchQueue.main.async {
|
|
371
|
+
guard let identifier = call.getString("identifier"), !identifier.isEmpty else {
|
|
372
|
+
return call.reject("Identifier is required")
|
|
373
|
+
}
|
|
374
|
+
let manager = self.ensureGeofenceLocationManager()
|
|
375
|
+
if let pending = self.pendingGeofenceRegions.removeValue(forKey: identifier) {
|
|
376
|
+
manager.stopMonitoring(for: pending.region)
|
|
377
|
+
self.pendingGeofenceAddCalls.removeValue(forKey: identifier)?.reject(
|
|
378
|
+
"Geofence was removed before monitoring started",
|
|
379
|
+
"CANCELLED"
|
|
380
|
+
)
|
|
381
|
+
self.removePersistedGeofenceRegion(identifier)
|
|
382
|
+
self.lastGeofenceTransition.removeValue(forKey: identifier)
|
|
383
|
+
return call.resolve()
|
|
384
|
+
}
|
|
385
|
+
guard self.persistedGeofenceRegionIds().contains(identifier) else {
|
|
386
|
+
return call.reject("Could not find a region with that identifier", "NOT_FOUND")
|
|
387
|
+
}
|
|
388
|
+
guard let region = manager.monitoredRegions.first(where: { $0.identifier == identifier && $0 is CLCircularRegion }) else {
|
|
389
|
+
self.pendingGeofenceAddCalls.removeValue(forKey: identifier)?.reject("Geofence was removed before monitoring started", "CANCELLED")
|
|
390
|
+
self.pendingGeofenceRegions.removeValue(forKey: identifier)
|
|
391
|
+
self.removePersistedGeofenceRegion(identifier)
|
|
392
|
+
self.lastGeofenceTransition.removeValue(forKey: identifier)
|
|
393
|
+
return call.resolve()
|
|
394
|
+
}
|
|
395
|
+
manager.stopMonitoring(for: region)
|
|
396
|
+
self.pendingGeofenceAddCalls.removeValue(forKey: identifier)?.reject("Geofence was removed before monitoring started", "CANCELLED")
|
|
397
|
+
self.pendingGeofenceRegions.removeValue(forKey: identifier)
|
|
398
|
+
self.removePersistedGeofenceRegion(identifier)
|
|
399
|
+
self.lastGeofenceTransition.removeValue(forKey: identifier)
|
|
400
|
+
call.resolve()
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
@objc func removeAllGeofences(_ call: CAPPluginCall) {
|
|
405
|
+
DispatchQueue.main.async {
|
|
406
|
+
let manager = self.ensureGeofenceLocationManager()
|
|
407
|
+
let identifiers = self.persistedGeofenceRegionIds()
|
|
408
|
+
for region in manager.monitoredRegions where region is CLCircularRegion && identifiers.contains(region.identifier) {
|
|
409
|
+
manager.stopMonitoring(for: region)
|
|
410
|
+
}
|
|
411
|
+
for identifier in identifiers {
|
|
412
|
+
self.removePersistedGeofenceRegion(identifier)
|
|
413
|
+
self.lastGeofenceTransition.removeValue(forKey: identifier)
|
|
414
|
+
}
|
|
415
|
+
for (_, pendingCall) in self.pendingGeofenceAddCalls {
|
|
416
|
+
pendingCall.reject("Geofences were removed before monitoring started", "CANCELLED")
|
|
417
|
+
}
|
|
418
|
+
self.pendingGeofenceAddCalls.removeAll()
|
|
419
|
+
self.pendingGeofenceRegions.removeAll()
|
|
420
|
+
call.resolve()
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
@objc func getMonitoredGeofences(_ call: CAPPluginCall) {
|
|
425
|
+
DispatchQueue.main.async {
|
|
426
|
+
let manager = self.ensureGeofenceLocationManager()
|
|
427
|
+
let identifiers = self.persistedGeofenceRegionIds()
|
|
428
|
+
let regions = manager.monitoredRegions.compactMap { region -> String? in
|
|
429
|
+
region is CLCircularRegion && identifiers.contains(region.identifier) ? region.identifier : nil
|
|
430
|
+
}.sorted()
|
|
431
|
+
call.resolve(["regions": regions])
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private func ensureGeofenceLocationManager() -> CLLocationManager {
|
|
436
|
+
if let manager = geofenceLocationManager {
|
|
437
|
+
return manager
|
|
438
|
+
}
|
|
439
|
+
let manager = CLLocationManager()
|
|
440
|
+
manager.delegate = self
|
|
441
|
+
manager.pausesLocationUpdatesAutomatically = false
|
|
442
|
+
geofenceLocationManager = manager
|
|
443
|
+
return manager
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private func geofenceAvailable(_ manager: CLLocationManager) -> Bool {
|
|
447
|
+
CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) && manager.authorizationStatus == .authorizedAlways
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private func persistGeofenceConfiguration() {
|
|
451
|
+
let defaults = UserDefaults.standard
|
|
452
|
+
defaults.set(geofenceBackendUrl?.absoluteString, forKey: geofenceUrlKey)
|
|
453
|
+
defaults.set(geofenceNotifyOnEntry, forKey: geofenceNotifyOnEntryKey)
|
|
454
|
+
defaults.set(geofenceNotifyOnExit, forKey: geofenceNotifyOnExitKey)
|
|
455
|
+
if JSONSerialization.isValidJSONObject(geofencePayload),
|
|
456
|
+
let data = try? JSONSerialization.data(withJSONObject: geofencePayload) {
|
|
457
|
+
defaults.set(data, forKey: geofencePayloadKey)
|
|
458
|
+
} else {
|
|
459
|
+
defaults.removeObject(forKey: geofencePayloadKey)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private func restoreGeofenceConfiguration() {
|
|
464
|
+
let defaults = UserDefaults.standard
|
|
465
|
+
if let urlString = defaults.string(forKey: geofenceUrlKey), !urlString.isEmpty {
|
|
466
|
+
geofenceBackendUrl = URL(string: urlString)
|
|
467
|
+
}
|
|
468
|
+
if defaults.object(forKey: geofenceNotifyOnEntryKey) != nil {
|
|
469
|
+
geofenceNotifyOnEntry = defaults.bool(forKey: geofenceNotifyOnEntryKey)
|
|
470
|
+
}
|
|
471
|
+
if defaults.object(forKey: geofenceNotifyOnExitKey) != nil {
|
|
472
|
+
geofenceNotifyOnExit = defaults.bool(forKey: geofenceNotifyOnExitKey)
|
|
473
|
+
}
|
|
474
|
+
if let data = defaults.data(forKey: geofencePayloadKey),
|
|
475
|
+
let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
476
|
+
geofencePayload = payload
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private func persistGeofenceRegion(_ region: CLCircularRegion, payload: [String: Any]) {
|
|
481
|
+
let data: [String: Any] = [
|
|
482
|
+
"latitude": region.center.latitude,
|
|
483
|
+
"longitude": region.center.longitude,
|
|
484
|
+
"radius": region.radius,
|
|
485
|
+
"payload": payload
|
|
486
|
+
]
|
|
487
|
+
if JSONSerialization.isValidJSONObject(data),
|
|
488
|
+
let encoded = try? JSONSerialization.data(withJSONObject: data) {
|
|
489
|
+
UserDefaults.standard.set(encoded, forKey: geofenceRegionPrefix + region.identifier)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private func persistedGeofenceRegion(_ identifier: String) -> [String: Any] {
|
|
494
|
+
guard let data = UserDefaults.standard.data(forKey: geofenceRegionPrefix + identifier),
|
|
495
|
+
let decoded = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
496
|
+
return [:]
|
|
497
|
+
}
|
|
498
|
+
return decoded
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private func removePersistedGeofenceRegion(_ identifier: String) {
|
|
502
|
+
UserDefaults.standard.removeObject(forKey: geofenceRegionPrefix + identifier)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private func persistedGeofenceRegionIds() -> Set<String> {
|
|
506
|
+
Set(UserDefaults.standard.dictionaryRepresentation().keys.compactMap { key in
|
|
507
|
+
guard key.hasPrefix(geofenceRegionPrefix) else { return nil }
|
|
508
|
+
return String(key.dropFirst(geofenceRegionPrefix.count))
|
|
509
|
+
})
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private func geofenceTransitionData(for region: CLRegion, enter: Bool) -> [String: Any] {
|
|
513
|
+
let persistedRegion = persistedGeofenceRegion(region.identifier)
|
|
514
|
+
let regionPayload = persistedRegion["payload"] as? [String: Any] ?? [:]
|
|
515
|
+
var payload = geofencePayload
|
|
516
|
+
for (key, value) in regionPayload {
|
|
517
|
+
payload[key] = value
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
var data = payload
|
|
521
|
+
data["identifier"] = region.identifier
|
|
522
|
+
data["transition"] = enter ? "enter" : "exit"
|
|
523
|
+
data["enter"] = enter
|
|
524
|
+
if let circularRegion = region as? CLCircularRegion {
|
|
525
|
+
data["latitude"] = circularRegion.center.latitude
|
|
526
|
+
data["longitude"] = circularRegion.center.longitude
|
|
527
|
+
data["radius"] = circularRegion.radius
|
|
528
|
+
} else {
|
|
529
|
+
data["latitude"] = persistedRegion["latitude"]
|
|
530
|
+
data["longitude"] = persistedRegion["longitude"]
|
|
531
|
+
data["radius"] = persistedRegion["radius"]
|
|
532
|
+
}
|
|
533
|
+
data["payload"] = payload
|
|
534
|
+
return data
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private func handleGeofenceTransition(for region: CLRegion, enter: Bool) {
|
|
538
|
+
if let circularRegion = region as? CLCircularRegion {
|
|
539
|
+
if enter && !circularRegion.notifyOnEntry {
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
if !enter && !circularRegion.notifyOnExit {
|
|
543
|
+
return
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let transition = enter ? "enter" : "exit"
|
|
548
|
+
if lastGeofenceTransition[region.identifier] == transition {
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
lastGeofenceTransition[region.identifier] = transition
|
|
552
|
+
|
|
553
|
+
let data = geofenceTransitionData(for: region, enter: enter)
|
|
554
|
+
notifyListeners("geofenceTransition", data: data, retainUntilConsumed: true)
|
|
555
|
+
postGeofenceTransition(data)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private func postGeofenceTransition(_ data: [String: Any]) {
|
|
559
|
+
guard let backendUrl = geofenceBackendUrl,
|
|
560
|
+
JSONSerialization.isValidJSONObject(data),
|
|
561
|
+
let body = try? JSONSerialization.data(withJSONObject: data) else {
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
var request = URLRequest(url: backendUrl)
|
|
565
|
+
request.httpMethod = "POST"
|
|
566
|
+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
567
|
+
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
|
568
|
+
request.httpBody = body
|
|
569
|
+
|
|
570
|
+
var backgroundTask = UIBackgroundTaskIdentifier.invalid
|
|
571
|
+
backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "CapgoGeofenceTransition") {
|
|
572
|
+
if backgroundTask != .invalid {
|
|
573
|
+
UIApplication.shared.endBackgroundTask(backgroundTask)
|
|
574
|
+
backgroundTask = .invalid
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
URLSession.shared.dataTask(with: request) { _, _, _ in
|
|
578
|
+
if backgroundTask != .invalid {
|
|
579
|
+
UIApplication.shared.endBackgroundTask(backgroundTask)
|
|
580
|
+
backgroundTask = .invalid
|
|
581
|
+
}
|
|
582
|
+
}.resume()
|
|
583
|
+
}
|
|
584
|
+
|
|
228
585
|
private func startUpdatingLocation() {
|
|
229
586
|
// Avoid unnecessary calls to startUpdatingLocation, which can
|
|
230
587
|
// result in extraneous invocations of didFailWithError.
|
|
@@ -353,7 +710,8 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
353
710
|
_ manager: CLLocationManager,
|
|
354
711
|
didFailWithError error: Error
|
|
355
712
|
) {
|
|
356
|
-
guard
|
|
713
|
+
guard manager === locationManager,
|
|
714
|
+
let callbackId = activeCallbackId,
|
|
357
715
|
let call = self.bridge?.savedCall(withID: callbackId) else {
|
|
358
716
|
return
|
|
359
717
|
}
|
|
@@ -378,7 +736,8 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
378
736
|
_ manager: CLLocationManager,
|
|
379
737
|
didUpdateLocations locations: [CLLocation]
|
|
380
738
|
) {
|
|
381
|
-
guard
|
|
739
|
+
guard manager === locationManager,
|
|
740
|
+
let location = locations.last,
|
|
382
741
|
let callbackId = activeCallbackId,
|
|
383
742
|
let call = self.bridge?.savedCall(withID: callbackId) else {
|
|
384
743
|
return
|
|
@@ -394,14 +753,76 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate, CAPBri
|
|
|
394
753
|
_ manager: CLLocationManager,
|
|
395
754
|
didChangeAuthorization status: CLAuthorizationStatus
|
|
396
755
|
) {
|
|
756
|
+
if let pendingCall = pendingGeofenceSetupCall {
|
|
757
|
+
if status == .authorizedAlways {
|
|
758
|
+
pendingGeofenceSetupTimeout?.cancel()
|
|
759
|
+
pendingGeofenceSetupTimeout = nil
|
|
760
|
+
pendingGeofenceSetupCall = nil
|
|
761
|
+
pendingCall.resolve()
|
|
762
|
+
} else if status == .denied || status == .restricted || status == .authorizedWhenInUse {
|
|
763
|
+
pendingGeofenceSetupTimeout?.cancel()
|
|
764
|
+
pendingGeofenceSetupTimeout = nil
|
|
765
|
+
pendingGeofenceSetupCall = nil
|
|
766
|
+
pendingCall.reject("Always location permission is required for geofencing", "NOT_AUTHORIZED")
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
397
770
|
// If this method is called before the user decides on a permission, as
|
|
398
771
|
// it is on iOS 14 when the permissions dialog is presented, we ignore
|
|
399
772
|
// it.
|
|
400
|
-
if status != .notDetermined {
|
|
773
|
+
if manager === locationManager && status != .notDetermined {
|
|
401
774
|
startUpdatingLocation()
|
|
402
775
|
}
|
|
403
776
|
}
|
|
404
777
|
|
|
778
|
+
public func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
|
|
779
|
+
guard manager === geofenceLocationManager else { return }
|
|
780
|
+
if let pending = pendingGeofenceRegions.removeValue(forKey: region.identifier) {
|
|
781
|
+
persistGeofenceRegion(pending.region, payload: pending.payload)
|
|
782
|
+
pendingGeofenceAddCalls.removeValue(forKey: region.identifier)?.resolve()
|
|
783
|
+
}
|
|
784
|
+
manager.requestState(for: region)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
public func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
|
|
788
|
+
guard manager === geofenceLocationManager, let region = region else { return }
|
|
789
|
+
pendingGeofenceRegions.removeValue(forKey: region.identifier)
|
|
790
|
+
removePersistedGeofenceRegion(region.identifier)
|
|
791
|
+
pendingGeofenceAddCalls.removeValue(forKey: region.identifier)?.reject(
|
|
792
|
+
"Could not start monitoring the geofence",
|
|
793
|
+
"MONITORING_FAILED",
|
|
794
|
+
error
|
|
795
|
+
)
|
|
796
|
+
let nsError = error as NSError
|
|
797
|
+
notifyListeners(
|
|
798
|
+
"geofenceError",
|
|
799
|
+
data: [
|
|
800
|
+
"identifier": region.identifier,
|
|
801
|
+
"message": error.localizedDescription,
|
|
802
|
+
"code": nsError.code,
|
|
803
|
+
"domain": nsError.domain
|
|
804
|
+
],
|
|
805
|
+
retainUntilConsumed: true
|
|
806
|
+
)
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
public func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
|
|
810
|
+
guard manager === geofenceLocationManager, region is CLCircularRegion else { return }
|
|
811
|
+
handleGeofenceTransition(for: region, enter: true)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
public func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
|
|
815
|
+
guard manager === geofenceLocationManager, region is CLCircularRegion else { return }
|
|
816
|
+
handleGeofenceTransition(for: region, enter: false)
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
public func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
|
|
820
|
+
guard manager === geofenceLocationManager, region is CLCircularRegion else { return }
|
|
821
|
+
if state == .inside {
|
|
822
|
+
handleGeofenceTransition(for: region, enter: true)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
405
826
|
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
406
827
|
call.resolve(["version": self.pluginVersion])
|
|
407
828
|
}
|
package/ios/Tests/CapgoBackgroundGeolocationPluginTests/CapgoBackgroundGeolocationPluginTests.swift
CHANGED
|
@@ -39,6 +39,11 @@ class CapgoBackgroundGeolocationTests: XCTestCase {
|
|
|
39
39
|
XCTAssertTrue(methodNames.contains("stop"))
|
|
40
40
|
XCTAssertTrue(methodNames.contains("openSettings"))
|
|
41
41
|
XCTAssertTrue(methodNames.contains("setPlannedRoute"))
|
|
42
|
+
XCTAssertTrue(methodNames.contains("setupGeofencing"))
|
|
43
|
+
XCTAssertTrue(methodNames.contains("addGeofence"))
|
|
44
|
+
XCTAssertTrue(methodNames.contains("removeGeofence"))
|
|
45
|
+
XCTAssertTrue(methodNames.contains("removeAllGeofences"))
|
|
46
|
+
XCTAssertTrue(methodNames.contains("getMonitoredGeofences"))
|
|
42
47
|
XCTAssertTrue(methodNames.contains("getPluginVersion"))
|
|
43
48
|
}
|
|
44
49
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/background-geolocation",
|
|
3
|
-
"version": "8.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "8.0.34",
|
|
4
|
+
"description": "Accurate background geolocation and native geofencing for Capacitor apps on iOS and Android.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/esm/index.d.ts",
|
|
@@ -26,23 +26,23 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://capgo.app/docs/plugins/background-geolocation/",
|
|
28
28
|
"scripts": {
|
|
29
|
-
"verify": "
|
|
29
|
+
"verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
|
|
30
30
|
"verify:ios": "xcodebuild -scheme CapgoBackgroundGeolocation -destination generic/platform=iOS",
|
|
31
31
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
32
|
-
"verify:web": "
|
|
33
|
-
"test": "
|
|
32
|
+
"verify:web": "bun run build",
|
|
33
|
+
"test": "bun run test:ios && bun run test:android",
|
|
34
34
|
"test:ios": "./scripts/test-ios.sh",
|
|
35
35
|
"test:android": "cd android && ./gradlew test && cd ..",
|
|
36
|
-
"lint": "
|
|
37
|
-
"fmt": "
|
|
36
|
+
"lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
|
|
37
|
+
"fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
|
|
38
38
|
"eslint": "eslint . --ext ts",
|
|
39
39
|
"prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
40
40
|
"swiftlint": "node-swiftlint",
|
|
41
41
|
"docgen": "docgen --api BackgroundGeolocationPlugin --output-readme README.md --output-json dist/docs.json",
|
|
42
|
-
"build": "
|
|
42
|
+
"build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
|
|
43
43
|
"clean": "rimraf ./dist",
|
|
44
44
|
"watch": "tsc --watch",
|
|
45
|
-
"prepublishOnly": "
|
|
45
|
+
"prepublishOnly": "bun run build"
|
|
46
46
|
},
|
|
47
47
|
"keywords": [
|
|
48
48
|
"capacitor",
|
|
@@ -50,7 +50,14 @@
|
|
|
50
50
|
"native",
|
|
51
51
|
"accurate",
|
|
52
52
|
"background",
|
|
53
|
-
"geolocation"
|
|
53
|
+
"geolocation",
|
|
54
|
+
"geofence",
|
|
55
|
+
"geofencing",
|
|
56
|
+
"location",
|
|
57
|
+
"background-location",
|
|
58
|
+
"ios-geofencing",
|
|
59
|
+
"android-geofencing",
|
|
60
|
+
"webhook"
|
|
54
61
|
],
|
|
55
62
|
"devDependencies": {
|
|
56
63
|
"@capacitor/android": "^8.0.0",
|