@josuelmm/capacitor-background-geolocation 1.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.
Files changed (207) hide show
  1. package/JosuelmmCapacitorBackgroundGeolocation.podspec +34 -0
  2. package/LICENSE +17 -0
  3. package/NOTICE.md +32 -0
  4. package/Package.swift +45 -0
  5. package/README.md +402 -0
  6. package/android/build.gradle +79 -0
  7. package/android/proguard-rules.pro +1 -0
  8. package/android/src/main/AndroidManifest.xml +83 -0
  9. package/android/src/main/java/com/evgenii/jsevaluator/HandlerWrapper.java +18 -0
  10. package/android/src/main/java/com/evgenii/jsevaluator/JavaScriptInterface.java +22 -0
  11. package/android/src/main/java/com/evgenii/jsevaluator/JsEvaluator.java +133 -0
  12. package/android/src/main/java/com/evgenii/jsevaluator/JsFunctionCallFormatter.java +37 -0
  13. package/android/src/main/java/com/evgenii/jsevaluator/WebViewWrapper.java +71 -0
  14. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/CallJavaResultInterface.java +8 -0
  15. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/HandlerWrapperInterface.java +5 -0
  16. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsCallback.java +10 -0
  17. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsEvaluatorInterface.java +18 -0
  18. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/WebViewWrapperInterface.java +14 -0
  19. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/BackgroundGeolocationPlugin.java +898 -0
  20. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/ConfigMapper.java +303 -0
  21. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/HeadlessTaskRegistry.java +34 -0
  22. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/JsEvaluatorTaskRunner.java +63 -0
  23. package/android/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +699 -0
  24. package/android/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +103 -0
  25. package/android/src/main/java/com/marianhello/bgloc/Config.java +1155 -0
  26. package/android/src/main/java/com/marianhello/bgloc/ConnectivityListener.java +5 -0
  27. package/android/src/main/java/com/marianhello/bgloc/HttpPostService.java +362 -0
  28. package/android/src/main/java/com/marianhello/bgloc/LocationManager.java +138 -0
  29. package/android/src/main/java/com/marianhello/bgloc/PluginDelegate.java +45 -0
  30. package/android/src/main/java/com/marianhello/bgloc/PluginException.java +38 -0
  31. package/android/src/main/java/com/marianhello/bgloc/PostLocationTask.java +238 -0
  32. package/android/src/main/java/com/marianhello/bgloc/ResourceResolver.java +55 -0
  33. package/android/src/main/java/com/marianhello/bgloc/data/AbstractLocationTemplate.java +69 -0
  34. package/android/src/main/java/com/marianhello/bgloc/data/ArrayListLocationTemplate.java +88 -0
  35. package/android/src/main/java/com/marianhello/bgloc/data/BackgroundActivity.java +108 -0
  36. package/android/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +1088 -0
  37. package/android/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
  38. package/android/src/main/java/com/marianhello/bgloc/data/ConfigurationDAO.java +13 -0
  39. package/android/src/main/java/com/marianhello/bgloc/data/DAOFactory.java +17 -0
  40. package/android/src/main/java/com/marianhello/bgloc/data/HashMapLocationTemplate.java +82 -0
  41. package/android/src/main/java/com/marianhello/bgloc/data/LocationDAO.java +27 -0
  42. package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplate.java +12 -0
  43. package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +71 -0
  44. package/android/src/main/java/com/marianhello/bgloc/data/LocationTransform.java +19 -0
  45. package/android/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
  46. package/android/src/main/java/com/marianhello/bgloc/data/provider/ContentProviderLocationDAO.java +406 -0
  47. package/android/src/main/java/com/marianhello/bgloc/data/provider/LocationContentProvider.java +321 -0
  48. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +94 -0
  49. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +227 -0
  50. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +122 -0
  51. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +550 -0
  52. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +189 -0
  53. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
  54. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
  55. package/android/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
  56. package/android/src/main/java/com/marianhello/bgloc/headless/AbstractTaskRunner.java +15 -0
  57. package/android/src/main/java/com/marianhello/bgloc/headless/ActivityTask.java +48 -0
  58. package/android/src/main/java/com/marianhello/bgloc/headless/JsCallback.java +10 -0
  59. package/android/src/main/java/com/marianhello/bgloc/headless/LocationTask.java +60 -0
  60. package/android/src/main/java/com/marianhello/bgloc/headless/StationaryTask.java +25 -0
  61. package/android/src/main/java/com/marianhello/bgloc/headless/Task.java +8 -0
  62. package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunner.java +5 -0
  63. package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunnerFactory.java +8 -0
  64. package/android/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
  65. package/android/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
  66. package/android/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +218 -0
  67. package/android/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +385 -0
  68. package/android/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +685 -0
  69. package/android/src/main/java/com/marianhello/bgloc/provider/LocationProvider.java +32 -0
  70. package/android/src/main/java/com/marianhello/bgloc/provider/LocationProviderFactory.java +47 -0
  71. package/android/src/main/java/com/marianhello/bgloc/provider/ProviderDelegate.java +12 -0
  72. package/android/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +175 -0
  73. package/android/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
  74. package/android/src/main/java/com/marianhello/bgloc/service/LocationService.java +16 -0
  75. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +1531 -0
  76. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfo.java +6 -0
  77. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfoImpl.java +41 -0
  78. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceIntentBuilder.java +203 -0
  79. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +156 -0
  80. package/android/src/main/java/com/marianhello/bgloc/sync/AccountHelper.java +39 -0
  81. package/android/src/main/java/com/marianhello/bgloc/sync/Authenticator.java +68 -0
  82. package/android/src/main/java/com/marianhello/bgloc/sync/AuthenticatorService.java +28 -0
  83. package/android/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +311 -0
  84. package/android/src/main/java/com/marianhello/bgloc/sync/NotificationHelper.java +148 -0
  85. package/android/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +301 -0
  86. package/android/src/main/java/com/marianhello/bgloc/sync/SyncService.java +68 -0
  87. package/android/src/main/java/com/marianhello/logging/DBLogReader.java +208 -0
  88. package/android/src/main/java/com/marianhello/logging/LogEntry.java +99 -0
  89. package/android/src/main/java/com/marianhello/logging/LoggerManager.java +70 -0
  90. package/android/src/main/java/com/marianhello/logging/UncaughtExceptionLogger.java +36 -0
  91. package/android/src/main/java/com/marianhello/utils/CloneHelper.java +22 -0
  92. package/android/src/main/java/com/marianhello/utils/Convert.java +56 -0
  93. package/android/src/main/java/com/marianhello/utils/TextUtils.java +72 -0
  94. package/android/src/main/java/com/marianhello/utils/ToneGenerator.java +68 -0
  95. package/android/src/main/java/org/apache/commons/io/Charsets.java +153 -0
  96. package/android/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java +344 -0
  97. package/android/src/main/java/org/chromium/content/browser/ThreadUtils.java +134 -0
  98. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlExpression.java +398 -0
  99. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlSelectStatement.java +671 -0
  100. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlStatement.java +29 -0
  101. package/android/src/main/java/ru/andremoniy/utils/TextUtils.java +61 -0
  102. package/android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  103. package/android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  104. package/android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  105. package/android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  106. package/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  107. package/android/src/main/res/values/strings.xml +15 -0
  108. package/android/src/main/res/xml/authenticator.xml +7 -0
  109. package/android/src/main/res/xml/syncadapter.xml +9 -0
  110. package/dist/esm/definitions.d.ts +1052 -0
  111. package/dist/esm/definitions.js +142 -0
  112. package/dist/esm/definitions.js.map +1 -0
  113. package/dist/esm/index.d.ts +8 -0
  114. package/dist/esm/index.js +23 -0
  115. package/dist/esm/index.js.map +1 -0
  116. package/dist/esm/web.d.ts +92 -0
  117. package/dist/esm/web.js +242 -0
  118. package/dist/esm/web.js.map +1 -0
  119. package/dist/plugin.cjs.js +415 -0
  120. package/dist/plugin.cjs.js.map +1 -0
  121. package/dist/plugin.js +418 -0
  122. package/dist/plugin.js.map +1 -0
  123. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin-Bridging-Header.h +18 -0
  124. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.m +52 -0
  125. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.swift +750 -0
  126. package/ios/Tests/BackgroundGeolocationPluginTests/BackgroundGeolocationPluginTests.swift +12 -0
  127. package/ios/common/BackgroundGeolocation/CocoaLumberjack.h +1945 -0
  128. package/ios/common/BackgroundGeolocation/CocoaLumberjack.m +5255 -0
  129. package/ios/common/BackgroundGeolocation/FMDB.h +2357 -0
  130. package/ios/common/BackgroundGeolocation/FMDB.m +2672 -0
  131. package/ios/common/BackgroundGeolocation/FMDBLogger.h +42 -0
  132. package/ios/common/BackgroundGeolocation/FMDBLogger.m +264 -0
  133. package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.h +41 -0
  134. package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.m +68 -0
  135. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager+Internal.h +33 -0
  136. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.h +178 -0
  137. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.m +1025 -0
  138. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.h +103 -0
  139. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.m +238 -0
  140. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequestDefines.h +163 -0
  141. package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.h +39 -0
  142. package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.m +37 -0
  143. package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.h +51 -0
  144. package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.m +53 -0
  145. package/ios/common/BackgroundGeolocation/MAURActivity.h +23 -0
  146. package/ios/common/BackgroundGeolocation/MAURActivity.m +52 -0
  147. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.h +18 -0
  148. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +340 -0
  149. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +88 -0
  150. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +1193 -0
  151. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +46 -0
  152. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +283 -0
  153. package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.h +25 -0
  154. package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.m +105 -0
  155. package/ios/common/BackgroundGeolocation/MAURConfig.h +99 -0
  156. package/ios/common/BackgroundGeolocation/MAURConfig.m +636 -0
  157. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +53 -0
  158. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +54 -0
  159. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.h +20 -0
  160. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +550 -0
  161. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.h +17 -0
  162. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +124 -0
  163. package/ios/common/BackgroundGeolocation/MAURLocation.h +73 -0
  164. package/ios/common/BackgroundGeolocation/MAURLocation.m +392 -0
  165. package/ios/common/BackgroundGeolocation/MAURLocationContract.h +38 -0
  166. package/ios/common/BackgroundGeolocation/MAURLocationContract.m +39 -0
  167. package/ios/common/BackgroundGeolocation/MAURLocationManager.h +53 -0
  168. package/ios/common/BackgroundGeolocation/MAURLocationManager.m +305 -0
  169. package/ios/common/BackgroundGeolocation/MAURLogReader.h +26 -0
  170. package/ios/common/BackgroundGeolocation/MAURLogReader.m +122 -0
  171. package/ios/common/BackgroundGeolocation/MAURLogging.h +19 -0
  172. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +53 -0
  173. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +367 -0
  174. package/ios/common/BackgroundGeolocation/MAURProviderDelegate.h +52 -0
  175. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.h +18 -0
  176. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +138 -0
  177. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.h +26 -0
  178. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +335 -0
  179. package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.h +57 -0
  180. package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.m +93 -0
  181. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +52 -0
  182. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +520 -0
  183. package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.h +32 -0
  184. package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.m +276 -0
  185. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
  186. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
  187. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
  188. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
  189. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
  190. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
  191. package/ios/common/BackgroundGeolocation/MAURUncaughtExceptionLogger.h +20 -0
  192. package/ios/common/BackgroundGeolocation/MAURUncaughtExceptionLogger.m +62 -0
  193. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
  194. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
  195. package/ios/common/BackgroundGeolocation/Reachability.h +102 -0
  196. package/ios/common/BackgroundGeolocation/Reachability.m +475 -0
  197. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/README.md +170 -0
  198. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.h +55 -0
  199. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.m +47 -0
  200. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlDataManipulationCommand.h +27 -0
  201. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.h +250 -0
  202. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.m +259 -0
  203. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.h +360 -0
  204. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.m +427 -0
  205. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlStatement.h +37 -0
  206. package/ios/common/BackgroundGeolocation/module.modulemap +16 -0
  207. package/package.json +82 -0
@@ -0,0 +1,750 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) 2026 JosueLMM
3
+
4
+ import Foundation
5
+ import Capacitor
6
+ import CoreLocation
7
+ import CoreMotion
8
+ import UIKit
9
+ import UserNotifications
10
+ import MAURBackgroundGeolocation
11
+
12
+ @objc(BackgroundGeolocationPlugin)
13
+ public class BackgroundGeolocationPlugin: CAPPlugin, CAPBridgedPlugin, MAURProviderDelegate {
14
+ public let identifier = "BackgroundGeolocationPlugin"
15
+ public let jsName = "BackgroundGeolocation"
16
+ public let pluginMethods: [CAPPluginMethod] = [
17
+ CAPPluginMethod(name: "configure", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "start", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "stop", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "getCurrentLocation", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "getStationaryLocation", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "getLocations", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "getValidLocations", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "getValidLocationsAndDelete", returnType: CAPPluginReturnPromise),
25
+ CAPPluginMethod(name: "getConfig", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "deleteLocation", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "deleteAllLocations", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "isLocationEnabled", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "showAppSettings", returnType: CAPPluginReturnPromise),
30
+ CAPPluginMethod(name: "showLocationSettings", returnType: CAPPluginReturnPromise),
31
+ CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
32
+ CAPPluginMethod(name: "watchLocationMode", returnType: CAPPluginReturnPromise),
33
+ CAPPluginMethod(name: "stopWatchingLocationMode", returnType: CAPPluginReturnPromise),
34
+ CAPPluginMethod(name: "getLogEntries", returnType: CAPPluginReturnPromise),
35
+ CAPPluginMethod(name: "checkStatus", returnType: CAPPluginReturnPromise),
36
+ CAPPluginMethod(name: "getDiagnostics", returnType: CAPPluginReturnPromise),
37
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise),
38
+ CAPPluginMethod(name: "switchMode", returnType: CAPPluginReturnPromise),
39
+ CAPPluginMethod(name: "startTask", returnType: CAPPluginReturnPromise),
40
+ CAPPluginMethod(name: "endTask", returnType: CAPPluginReturnPromise),
41
+ CAPPluginMethod(name: "forceSync", returnType: CAPPluginReturnPromise),
42
+ CAPPluginMethod(name: "clearSync", returnType: CAPPluginReturnPromise),
43
+ CAPPluginMethod(name: "getPendingSyncCount", returnType: CAPPluginReturnPromise),
44
+ CAPPluginMethod(name: "startSession", returnType: CAPPluginReturnPromise),
45
+ CAPPluginMethod(name: "clearSession", returnType: CAPPluginReturnPromise),
46
+ CAPPluginMethod(name: "getSessionLocations", returnType: CAPPluginReturnPromise),
47
+ CAPPluginMethod(name: "getSessionLocationsCount", returnType: CAPPluginReturnPromise),
48
+ CAPPluginMethod(name: "triggerSOS", returnType: CAPPluginReturnPromise),
49
+ CAPPluginMethod(name: "isIgnoringBatteryOptimizations", returnType: CAPPluginReturnPromise),
50
+ CAPPluginMethod(name: "requestIgnoreBatteryOptimizations", returnType: CAPPluginReturnPromise),
51
+ CAPPluginMethod(name: "openBatterySettings", returnType: CAPPluginReturnPromise),
52
+ CAPPluginMethod(name: "openAutoStartSettings", returnType: CAPPluginReturnPromise),
53
+ CAPPluginMethod(name: "getManufacturerHelp", returnType: CAPPluginReturnPromise),
54
+ CAPPluginMethod(name: "requestBackgroundLocationPermission", returnType: CAPPluginReturnPromise),
55
+ CAPPluginMethod(name: "requestActivityRecognitionPermission", returnType: CAPPluginReturnPromise),
56
+ CAPPluginMethod(name: "requestNotificationPermission", returnType: CAPPluginReturnPromise),
57
+ CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
58
+ CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
59
+ CAPPluginMethod(name: "registerHeadlessTask", returnType: CAPPluginReturnPromise),
60
+ CAPPluginMethod(name: "removeAllListeners", returnType: CAPPluginReturnPromise)
61
+ ]
62
+
63
+ private static let pluginVersion = "1.0.0"
64
+
65
+ private var facade: MAURBackgroundGeolocationFacade?
66
+ private var currentConfig: MAURConfig?
67
+ private var permissionHelper: PermissionRequestHelper?
68
+ private var lastLocationAt: Date?
69
+
70
+ override public func load() {
71
+ let f = MAURBackgroundGeolocationFacade()
72
+ f.delegate = self
73
+ facade = f
74
+ NotificationCenter.default.addObserver(self, selector: #selector(onAppForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
75
+ NotificationCenter.default.addObserver(self, selector: #selector(onAppBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
76
+
77
+ // v3.5 Phase 4 — sync + heartbeat notifications.
78
+ let nc = NotificationCenter.default
79
+ nc.addObserver(self, selector: #selector(onSyncStartN(_:)), name: NSNotification.Name(rawValue: MAURBackgroundSyncDidStartNotification), object: nil)
80
+ nc.addObserver(self, selector: #selector(onSyncSuccessN(_:)), name: NSNotification.Name(rawValue: MAURBackgroundSyncDidSucceedNotification), object: nil)
81
+ nc.addObserver(self, selector: #selector(onSyncErrorN(_:)), name: NSNotification.Name(rawValue: MAURBackgroundSyncDidFailNotification), object: nil)
82
+ nc.addObserver(self, selector: #selector(onSyncProgressN(_:)), name: NSNotification.Name(rawValue: MAURBackgroundSyncDidProgressNotification), object: nil)
83
+ nc.addObserver(self, selector: #selector(onHeartbeatN(_:)), name: NSNotification.Name(rawValue: MAURHeartbeatNotification), object: nil)
84
+
85
+ // v4.0 Phase 6 — driver-insight notifications.
86
+ nc.addObserver(self, selector: #selector(onTripStartN(_:)), name: NSNotification.Name(rawValue: MAURTripStartNotification), object: nil)
87
+ nc.addObserver(self, selector: #selector(onTripEndN(_:)), name: NSNotification.Name(rawValue: MAURTripEndNotification), object: nil)
88
+ nc.addObserver(self, selector: #selector(onMovingN(_:)), name: NSNotification.Name(rawValue: MAURMovingNotification), object: nil)
89
+ nc.addObserver(self, selector: #selector(onStoppedN(_:)), name: NSNotification.Name(rawValue: MAURStoppedNotification), object: nil)
90
+ nc.addObserver(self, selector: #selector(onSpeedingN(_:)), name: NSNotification.Name(rawValue: MAURSpeedingNotification), object: nil)
91
+ nc.addObserver(self, selector: #selector(onProviderChangeN(_:)), name: NSNotification.Name(rawValue: MAURProviderChangeNotification), object: nil)
92
+ nc.addObserver(self, selector: #selector(onSOSN(_:)), name: NSNotification.Name(rawValue: MAURSOSNotification), object: nil)
93
+
94
+ // v4.1 GPS-derived sensor-like events.
95
+ nc.addObserver(self, selector: #selector(onHardBrakeN(_:)), name: NSNotification.Name(rawValue: MAURHardBrakeNotification), object: nil)
96
+ nc.addObserver(self, selector: #selector(onRapidAccelerationN(_:)), name: NSNotification.Name(rawValue: MAURRapidAccelerationNotification), object: nil)
97
+ nc.addObserver(self, selector: #selector(onSharpTurnN(_:)), name: NSNotification.Name(rawValue: MAURSharpTurnNotification), object: nil)
98
+ nc.addObserver(self, selector: #selector(onPossibleCrashN(_:)), name: NSNotification.Name(rawValue: MAURPossibleCrashNotification), object: nil)
99
+
100
+ // v4.2 sensor fusion.
101
+ nc.addObserver(self, selector: #selector(onPhoneUsageWhileDrivingN(_:)), name: NSNotification.Name(rawValue: MAURPhoneUsageWhileDrivingNotification), object: nil)
102
+ }
103
+
104
+ deinit {
105
+ NotificationCenter.default.removeObserver(self)
106
+ }
107
+
108
+ // MARK: - App lifecycle bridge
109
+
110
+ @objc private func onAppForeground() {
111
+ facade?.switchMode(MAURForegroundMode)
112
+ notifyListeners("foreground", data: [:])
113
+ }
114
+
115
+ @objc private func onAppBackground() {
116
+ facade?.switchMode(MAURBackgroundMode)
117
+ notifyListeners("background", data: [:])
118
+ }
119
+
120
+ // MARK: - Bridge methods
121
+
122
+ @objc func configure(_ call: CAPPluginCall) {
123
+ guard let facade = facade else { call.reject("facade not initialized"); return }
124
+ let opts = call.options ?? [:]
125
+ let cfg = MAURConfig.fromDictionary(opts)
126
+ currentConfig = cfg
127
+ do {
128
+ try facade.configure(cfg)
129
+ call.resolve()
130
+ } catch {
131
+ let nsErr = error as NSError
132
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
133
+ }
134
+ }
135
+
136
+ @objc func start(_ call: CAPPluginCall) {
137
+ guard let facade = facade else { call.reject("facade not initialized"); return }
138
+ do {
139
+ try facade.start()
140
+ // `start` event is emitted by MAURProviderDelegate.onLocationResume.
141
+ call.resolve()
142
+ } catch {
143
+ let nsErr = error as NSError
144
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
145
+ }
146
+ }
147
+
148
+ @objc func stop(_ call: CAPPluginCall) {
149
+ guard let facade = facade else { call.reject("facade not initialized"); return }
150
+ do {
151
+ try facade.stop()
152
+ // `stop` event is emitted by MAURProviderDelegate.onLocationPause.
153
+ call.resolve()
154
+ } catch {
155
+ let nsErr = error as NSError
156
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
157
+ }
158
+ }
159
+
160
+ @objc func getCurrentLocation(_ call: CAPPluginCall) {
161
+ guard let facade = facade else { call.reject("facade not initialized"); return }
162
+ let timeout = call.getInt("timeout") ?? Int(Int32.max)
163
+ let maximumAge = call.getInt("maximumAge").map { Int64($0) } ?? Int64.max
164
+ let highAccuracy = call.getBool("enableHighAccuracy") ?? false
165
+ do {
166
+ let location = try facade.getCurrentLocation(Int32(timeout), maximumAge: maximumAge, enableHighAccuracy: highAccuracy)
167
+ call.resolve(location.toDictionary() as? [String: Any] ?? [:])
168
+ } catch {
169
+ let nsErr = error as NSError
170
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
171
+ }
172
+ }
173
+
174
+ @objc func getStationaryLocation(_ call: CAPPluginCall) {
175
+ guard let facade = facade else { call.reject("facade not initialized"); return }
176
+ if let loc = facade.getStationaryLocation() {
177
+ call.resolve(loc.toDictionary() as? [String: Any] ?? [:])
178
+ } else {
179
+ // TS contract is `Location | null` — resolve with no payload so the
180
+ // JS bridge surfaces `null`, matching Android's `call.resolve()`.
181
+ call.resolve()
182
+ }
183
+ }
184
+
185
+ @objc func getLocations(_ call: CAPPluginCall) {
186
+ guard let facade = facade else { call.reject("facade not initialized"); return }
187
+ let locations = facade.getLocations() as? [MAURLocation] ?? []
188
+ let arr = locations.compactMap { $0.toDictionaryWithId() as? [String: Any] }
189
+ call.resolve(["locations": arr])
190
+ }
191
+
192
+ @objc func getValidLocations(_ call: CAPPluginCall) {
193
+ guard let facade = facade else { call.reject("facade not initialized"); return }
194
+ let locations = facade.getValidLocations() as? [MAURLocation] ?? []
195
+ let arr = locations.compactMap { $0.toDictionaryWithId() as? [String: Any] }
196
+ call.resolve(["locations": arr])
197
+ }
198
+
199
+ @objc func getValidLocationsAndDelete(_ call: CAPPluginCall) {
200
+ guard let facade = facade else { call.reject("facade not initialized"); return }
201
+ let locations = facade.getValidLocationsAndDelete() as? [MAURLocation] ?? []
202
+ let arr = locations.compactMap { $0.toDictionaryWithId() as? [String: Any] }
203
+ call.resolve(["locations": arr])
204
+ }
205
+
206
+ @objc func getConfig(_ call: CAPPluginCall) {
207
+ guard let facade = facade else { call.reject("facade not initialized"); return }
208
+ let cfg = facade.getConfig()
209
+ let dict = cfg?.toDictionary() as? [String: Any] ?? [:]
210
+ call.resolve(dict)
211
+ }
212
+
213
+ @objc func deleteLocation(_ call: CAPPluginCall) {
214
+ guard let facade = facade else { call.reject("facade not initialized"); return }
215
+ guard let locationId = call.getInt("locationId") else {
216
+ call.reject("locationId required"); return
217
+ }
218
+ do {
219
+ try facade.delete(NSNumber(value: locationId))
220
+ call.resolve()
221
+ } catch {
222
+ let nsErr = error as NSError
223
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
224
+ }
225
+ }
226
+
227
+ @objc func deleteAllLocations(_ call: CAPPluginCall) {
228
+ guard let facade = facade else { call.reject("facade not initialized"); return }
229
+ do {
230
+ try facade.deleteAllLocations()
231
+ call.resolve()
232
+ } catch {
233
+ let nsErr = error as NSError
234
+ call.reject(nsErr.localizedDescription, String(nsErr.code))
235
+ }
236
+ }
237
+
238
+ @objc func isLocationEnabled(_ call: CAPPluginCall) {
239
+ guard let facade = facade else { call.reject("facade not initialized"); return }
240
+ call.resolve(["enabled": facade.locationServicesEnabled()])
241
+ }
242
+
243
+ @objc func showAppSettings(_ call: CAPPluginCall) {
244
+ facade?.showAppSettings()
245
+ call.resolve()
246
+ }
247
+
248
+ @objc func showLocationSettings(_ call: CAPPluginCall) {
249
+ facade?.showLocationSettings()
250
+ call.resolve()
251
+ }
252
+
253
+ @objc func openSettings(_ call: CAPPluginCall) {
254
+ // Alias of showAppSettings — keeps parity with the cross-platform TS contract.
255
+ facade?.showAppSettings()
256
+ call.resolve()
257
+ }
258
+
259
+ @objc func watchLocationMode(_ call: CAPPluginCall) {
260
+ // iOS has no separate "mode watcher". Permission/status changes flow through
261
+ // onAuthorizationChanged -> "authorization" event. This is a no-op resolve.
262
+ call.resolve()
263
+ }
264
+
265
+ @objc func stopWatchingLocationMode(_ call: CAPPluginCall) {
266
+ call.resolve()
267
+ }
268
+
269
+ @objc func getLogEntries(_ call: CAPPluginCall) {
270
+ guard let facade = facade else { call.reject("facade not initialized"); return }
271
+ let limit = call.getInt("limit") ?? 0
272
+ let fromId = call.getInt("fromId") ?? 0
273
+ let minLevel = call.getString("minLevel") ?? "DEBUG"
274
+ let logs = facade.getLogEntries(limit, fromLogEntryId: fromId, minLogLevelFromString: minLevel) as? [Any] ?? []
275
+ call.resolve(["entries": logs])
276
+ }
277
+
278
+ @objc func checkStatus(_ call: CAPPluginCall) {
279
+ guard let facade = facade else { call.reject("facade not initialized"); return }
280
+ let isRunning = facade.isStarted()
281
+ let enabled = facade.locationServicesEnabled()
282
+ let auth = facade.authorizationStatus()
283
+ call.resolve([
284
+ "isRunning": isRunning,
285
+ "locationServicesEnabled": enabled,
286
+ "authorization": auth.rawValue
287
+ ])
288
+ }
289
+
290
+ @objc func getDiagnostics(_ call: CAPPluginCall) {
291
+ var d: [String: Any] = [:]
292
+ d["isRunning"] = facade?.isStarted() ?? false
293
+ d["locationServicesEnabled"] = facade?.locationServicesEnabled() ?? false
294
+ // iOS has no boot-time auto-start equivalent. Expose for cross-platform shape.
295
+ d["startOnBoot"] = false
296
+
297
+ // Pending sync (best-effort).
298
+ let pending = MAURSQLiteLocationDAO.sharedInstance().getLocationsForSyncCount()
299
+ d["pendingSyncCount"] = pending?.intValue ?? 0
300
+
301
+ if let last = lastLocationAt {
302
+ d["lastLocationAt"] = Int64(last.timeIntervalSince1970 * 1000)
303
+ } else {
304
+ d["lastLocationAt"] = NSNull()
305
+ }
306
+
307
+ // Precise location (iOS 14+).
308
+ if #available(iOS 14.0, *) {
309
+ let lm = CLLocationManager()
310
+ d["preciseLocationEnabled"] = (lm.accuracyAuthorization == .fullAccuracy)
311
+ } else {
312
+ d["preciseLocationEnabled"] = true
313
+ }
314
+
315
+ d["backgroundRefreshStatus"] = Self.backgroundRefreshText()
316
+ d["lowPowerModeEnabled"] = ProcessInfo.processInfo.isLowPowerModeEnabled
317
+ d["motionPermissionStatus"] = Self.motionPermissionText()
318
+ d["authorizationStatusText"] = Self.authorizationStatusText(Self.currentAuthorizationStatus())
319
+
320
+ call.resolve(d)
321
+ }
322
+
323
+ @objc func getPluginVersion(_ call: CAPPluginCall) {
324
+ call.resolve(["version": Self.pluginVersion])
325
+ }
326
+
327
+ @objc func switchMode(_ call: CAPPluginCall) {
328
+ guard let facade = facade else { call.reject("facade not initialized"); return }
329
+ let raw = call.getInt("mode") ?? Int(MAURForegroundMode.rawValue)
330
+ let mode: MAUROperationalMode = (raw == Int(MAURBackgroundMode.rawValue)) ? MAURBackgroundMode : MAURForegroundMode
331
+ facade.switchMode(mode)
332
+ call.resolve()
333
+ }
334
+
335
+ @objc func startTask(_ call: CAPPluginCall) {
336
+ let key = MAURBackgroundTaskManager.sharedTasks().beginTask()
337
+ call.resolve(["taskKey": key])
338
+ }
339
+
340
+ @objc func endTask(_ call: CAPPluginCall) {
341
+ let key = call.getInt("taskKey") ?? 0
342
+ MAURBackgroundTaskManager.sharedTasks().endTask(withKey: UInt(key))
343
+ call.resolve()
344
+ }
345
+
346
+ @objc func forceSync(_ call: CAPPluginCall) {
347
+ facade?.forceSync()
348
+ call.resolve()
349
+ }
350
+
351
+ @objc func clearSync(_ call: CAPPluginCall) {
352
+ facade?.clearSync()
353
+ call.resolve()
354
+ }
355
+
356
+ @objc func getPendingSyncCount(_ call: CAPPluginCall) {
357
+ let count = facade?.getPendingSyncCount() ?? 0
358
+ call.resolve(["count": count])
359
+ }
360
+
361
+ @objc func startSession(_ call: CAPPluginCall) {
362
+ facade?.startSession()
363
+ call.resolve()
364
+ }
365
+
366
+ @objc func clearSession(_ call: CAPPluginCall) {
367
+ facade?.clearSession()
368
+ call.resolve()
369
+ }
370
+
371
+ @objc func getSessionLocations(_ call: CAPPluginCall) {
372
+ let locations = facade?.getSessionLocations() as? [MAURLocation] ?? []
373
+ let arr = locations.compactMap { $0.toDictionaryWithId() as? [String: Any] }
374
+ call.resolve(["locations": arr])
375
+ }
376
+
377
+ @objc func getSessionLocationsCount(_ call: CAPPluginCall) {
378
+ let count = facade?.getSessionLocationsCount() ?? 0
379
+ call.resolve(["count": count])
380
+ }
381
+
382
+ @objc func triggerSOS(_ call: CAPPluginCall) {
383
+ // Facade attaches the latest location and posts MAURSOSNotification, which we
384
+ // forward via onSOSN(_:) below — keeps the payload-merge logic in a single place.
385
+ let payload = call.getObject("payload")
386
+ facade?.triggerSOS(payload)
387
+ call.resolve()
388
+ }
389
+
390
+ // MARK: - Battery / OEM helpers (iOS no-ops)
391
+
392
+ @objc func isIgnoringBatteryOptimizations(_ call: CAPPluginCall) {
393
+ call.resolve(["whitelisted": true])
394
+ }
395
+
396
+ @objc func requestIgnoreBatteryOptimizations(_ call: CAPPluginCall) {
397
+ call.resolve(["whitelisted": true])
398
+ }
399
+
400
+ @objc func openBatterySettings(_ call: CAPPluginCall) {
401
+ // iOS has no Battery Settings deeplink. Best-effort: app settings.
402
+ if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
403
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
404
+ }
405
+ call.resolve()
406
+ }
407
+
408
+ @objc func openAutoStartSettings(_ call: CAPPluginCall) {
409
+ // No per-OEM auto-start screen on iOS. Report opened=false to let JS render help.
410
+ call.resolve([
411
+ "opened": false,
412
+ "manufacturer": "apple",
413
+ "screen": ""
414
+ ])
415
+ }
416
+
417
+ @objc func getManufacturerHelp(_ call: CAPPluginCall) {
418
+ // Consumer apps will render their own copy. Return an empty list to keep the
419
+ // cross-platform shape stable.
420
+ call.resolve([
421
+ "manufacturer": "apple",
422
+ "steps": [] as [String]
423
+ ])
424
+ }
425
+
426
+ // MARK: - Runtime permission helpers (cross-platform shims)
427
+
428
+ @objc func requestBackgroundLocationPermission(_ call: CAPPluginCall) {
429
+ // iOS folds background into "Always" — there's no separate gate.
430
+ call.resolve(["granted": true, "notRequired": true])
431
+ }
432
+
433
+ @objc func requestActivityRecognitionPermission(_ call: CAPPluginCall) {
434
+ // Probe motion availability so the consumer sees a faithful "granted" flag.
435
+ var granted = true
436
+ if #available(iOS 11.0, *) {
437
+ if CMMotionActivityManager.isActivityAvailable() {
438
+ switch CMMotionActivityManager.authorizationStatus() {
439
+ case .authorized: granted = true
440
+ case .denied,
441
+ .restricted: granted = false
442
+ case .notDetermined: granted = true
443
+ @unknown default: granted = true
444
+ }
445
+ }
446
+ }
447
+ call.resolve(["granted": granted, "notRequired": true])
448
+ }
449
+
450
+ @objc func requestNotificationPermission(_ call: CAPPluginCall) {
451
+ UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
452
+ call.resolve(["granted": granted, "notRequired": false])
453
+ }
454
+ }
455
+
456
+ /// Android-only feature. iOS does not allow running JS in a killed-app
457
+ /// scenario the way Android's `JsEvaluator` does. The call resolves so
458
+ /// cross-platform code stays portable. Use the regular `addListener`
459
+ /// callbacks on iOS — they are delivered as long as the app is running
460
+ /// in the background (which on iOS is the only state where locations
461
+ /// are produced anyway).
462
+ @objc func registerHeadlessTask(_ call: CAPPluginCall) {
463
+ call.resolve()
464
+ }
465
+
466
+ @objc override public func removeAllListeners(_ call: CAPPluginCall) {
467
+ super.removeAllListeners(call)
468
+ }
469
+
470
+ // MARK: - Permissions (Capacitor standard)
471
+
472
+ @objc override public func checkPermissions(_ call: CAPPluginCall) {
473
+ let status = Self.currentAuthorizationStatus()
474
+ call.resolve(["location": Self.permissionState(for: status)])
475
+ }
476
+
477
+ @objc override public func requestPermissions(_ call: CAPPluginCall) {
478
+ let status = Self.currentAuthorizationStatus()
479
+ if status != .notDetermined {
480
+ call.resolve(["location": Self.permissionState(for: status)])
481
+ return
482
+ }
483
+ let helper = PermissionRequestHelper { [weak self] result in
484
+ call.resolve(["location": BackgroundGeolocationPlugin.permissionState(for: result)])
485
+ self?.permissionHelper = nil
486
+ }
487
+ permissionHelper = helper
488
+ helper.start()
489
+ }
490
+
491
+ private static func currentAuthorizationStatus() -> CLAuthorizationStatus {
492
+ if #available(iOS 14.0, *) {
493
+ return CLLocationManager().authorizationStatus
494
+ }
495
+ return CLLocationManager.authorizationStatus()
496
+ }
497
+
498
+ private static func permissionState(for status: CLAuthorizationStatus) -> String {
499
+ switch status {
500
+ case .notDetermined: return "prompt"
501
+ case .denied, .restricted: return "denied"
502
+ case .authorizedAlways, .authorizedWhenInUse: return "granted"
503
+ @unknown default: return "prompt"
504
+ }
505
+ }
506
+
507
+ private static func authorizationStatusText(_ status: CLAuthorizationStatus) -> String {
508
+ switch status {
509
+ case .notDetermined: return "notDetermined"
510
+ case .restricted: return "restricted"
511
+ case .denied: return "denied"
512
+ case .authorizedAlways: return "authorizedAlways"
513
+ case .authorizedWhenInUse: return "authorizedWhenInUse"
514
+ @unknown default: return "unknown"
515
+ }
516
+ }
517
+
518
+ private static func backgroundRefreshText() -> String {
519
+ switch UIApplication.shared.backgroundRefreshStatus {
520
+ case .available: return "available"
521
+ case .denied: return "denied"
522
+ case .restricted: return "restricted"
523
+ @unknown default: return "unknown"
524
+ }
525
+ }
526
+
527
+ private static func motionPermissionText() -> String {
528
+ if !CMMotionActivityManager.isActivityAvailable() { return "restricted" }
529
+ if #available(iOS 11.0, *) {
530
+ switch CMMotionActivityManager.authorizationStatus() {
531
+ case .notDetermined: return "notDetermined"
532
+ case .restricted: return "restricted"
533
+ case .denied: return "denied"
534
+ case .authorized: return "authorized"
535
+ @unknown default: return "notDetermined"
536
+ }
537
+ }
538
+ return "notDetermined"
539
+ }
540
+
541
+ // MARK: - MAURProviderDelegate
542
+
543
+ public func onAuthorizationChanged(_ authStatus: MAURLocationAuthorizationStatus) {
544
+ notifyListeners("authorization", data: ["status": authStatus.rawValue])
545
+ }
546
+
547
+ public func onLocationChanged(_ location: MAURLocation!) {
548
+ lastLocationAt = Date()
549
+ guard let dict = location.toDictionaryWithId() as? [String: Any] else { return }
550
+ notifyListeners("location", data: dict)
551
+ }
552
+
553
+ public func onStationaryChanged(_ location: MAURLocation!) {
554
+ guard let dict = location.toDictionaryWithId() as? [String: Any] else { return }
555
+ notifyListeners("stationary", data: dict)
556
+ }
557
+
558
+ public func onLocationPause() {
559
+ notifyListeners("stop", data: [:])
560
+ }
561
+
562
+ public func onLocationResume() {
563
+ notifyListeners("start", data: [:])
564
+ }
565
+
566
+ public func onActivityChanged(_ activity: MAURActivity!) {
567
+ guard let dict = activity.toDictionary() as? [String: Any] else { return }
568
+ notifyListeners("activity", data: dict)
569
+ }
570
+
571
+ public func onAbortRequested() {
572
+ notifyListeners("abort_requested", data: [:])
573
+ }
574
+
575
+ public func onHttpAuthorization() {
576
+ notifyListeners("http_authorization", data: [:])
577
+ }
578
+
579
+ public func onError(_ error: Error!) {
580
+ let nsErr = error as NSError?
581
+ notifyListeners("error", data: [
582
+ "code": nsErr?.code ?? -1,
583
+ "message": nsErr?.localizedDescription ?? "unknown error"
584
+ ])
585
+ }
586
+
587
+ // MARK: - Notification observers (sync / heartbeat / driving)
588
+
589
+ @objc private func onSyncStartN(_ note: Notification) {
590
+ notifyListeners("syncStart", data: [:])
591
+ }
592
+
593
+ @objc private func onSyncSuccessN(_ note: Notification) {
594
+ let sent = (note.userInfo?["sent"] as? NSNumber)?.intValue ?? 0
595
+ notifyListeners("syncSuccess", data: ["sent": sent])
596
+ }
597
+
598
+ @objc private func onSyncErrorN(_ note: Notification) {
599
+ let status = (note.userInfo?["httpStatus"] as? NSNumber)?.intValue ?? 0
600
+ let msg = (note.userInfo?["message"] as? String) ?? ""
601
+ notifyListeners("syncError", data: ["httpStatus": status, "message": msg])
602
+ }
603
+
604
+ @objc private func onSyncProgressN(_ note: Notification) {
605
+ let progress = (note.userInfo?["progress"] as? NSNumber)?.intValue ?? 0
606
+ notifyListeners("syncProgress", data: ["progress": progress])
607
+ }
608
+
609
+ @objc private func onHeartbeatN(_ note: Notification) {
610
+ if let loc = note.userInfo?["location"] as? MAURLocation,
611
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
612
+ notifyListeners("heartbeat", data: dict)
613
+ } else {
614
+ notifyListeners("heartbeat", data: [:])
615
+ }
616
+ }
617
+
618
+ @objc private func onTripStartN(_ note: Notification) {
619
+ if let loc = note.userInfo?["location"] as? MAURLocation,
620
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
621
+ notifyListeners("tripStart", data: dict)
622
+ } else {
623
+ notifyListeners("tripStart", data: [:])
624
+ }
625
+ }
626
+
627
+ @objc private func onTripEndN(_ note: Notification) {
628
+ var p: [String: Any] = [:]
629
+ if let loc = note.userInfo?["location"] as? MAURLocation,
630
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
631
+ p["location"] = dict
632
+ } else {
633
+ p["location"] = NSNull()
634
+ }
635
+ p["distance"] = (note.userInfo?["distance"] as? NSNumber)?.doubleValue ?? 0
636
+ p["durationMs"] = (note.userInfo?["durationMs"] as? NSNumber)?.int64Value ?? 0
637
+ notifyListeners("tripEnd", data: p)
638
+ }
639
+
640
+ @objc private func onMovingN(_ note: Notification) {
641
+ if let loc = note.userInfo?["location"] as? MAURLocation,
642
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
643
+ notifyListeners("moving", data: dict)
644
+ } else {
645
+ notifyListeners("moving", data: [:])
646
+ }
647
+ }
648
+
649
+ @objc private func onStoppedN(_ note: Notification) {
650
+ if let loc = note.userInfo?["location"] as? MAURLocation,
651
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
652
+ notifyListeners("stopped", data: dict)
653
+ } else {
654
+ notifyListeners("stopped", data: [:])
655
+ }
656
+ }
657
+
658
+ @objc private func onSpeedingN(_ note: Notification) {
659
+ var p: [String: Any] = [:]
660
+ if let loc = note.userInfo?["location"] as? MAURLocation,
661
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
662
+ p["location"] = dict
663
+ } else {
664
+ p["location"] = NSNull()
665
+ }
666
+ p["speedKmh"] = (note.userInfo?["speedKmh"] as? NSNumber)?.doubleValue ?? 0
667
+ p["limitKmh"] = (note.userInfo?["limitKmh"] as? NSNumber)?.doubleValue ?? 0
668
+ notifyListeners("speeding", data: p)
669
+ }
670
+
671
+ @objc private func onProviderChangeN(_ note: Notification) {
672
+ let provider = (note.userInfo?["provider"] as? String) ?? ""
673
+ notifyListeners("providerChange", data: ["provider": provider])
674
+ }
675
+
676
+ @objc private func onSOSN(_ note: Notification) {
677
+ var p: [String: Any] = [:]
678
+ if let userPayload = note.userInfo?["payload"] as? [String: Any] {
679
+ for (k, v) in userPayload { p[k] = v }
680
+ }
681
+ if let loc = note.userInfo?["location"] as? MAURLocation,
682
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
683
+ p["location"] = dict
684
+ } else if p["location"] == nil {
685
+ p["location"] = NSNull()
686
+ }
687
+ notifyListeners("sos", data: p)
688
+ }
689
+
690
+ private func emitDrivingEvent(_ name: String, note: Notification) {
691
+ var p: [String: Any] = [:]
692
+ if let loc = note.userInfo?["location"] as? MAURLocation,
693
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
694
+ p["location"] = dict
695
+ } else {
696
+ p["location"] = NSNull()
697
+ }
698
+ p["value"] = (note.userInfo?["value"] as? NSNumber)?.doubleValue ?? 0
699
+ if let source = note.userInfo?["source"] as? String { p["source"] = source }
700
+ notifyListeners(name, data: p)
701
+ }
702
+
703
+ @objc private func onHardBrakeN(_ note: Notification) { emitDrivingEvent("hardBrake", note: note) }
704
+ @objc private func onRapidAccelerationN(_ note: Notification) { emitDrivingEvent("rapidAcceleration", note: note) }
705
+ @objc private func onSharpTurnN(_ note: Notification) { emitDrivingEvent("sharpTurn", note: note) }
706
+ @objc private func onPossibleCrashN(_ note: Notification) { emitDrivingEvent("possibleCrash", note: note) }
707
+
708
+ @objc private func onPhoneUsageWhileDrivingN(_ note: Notification) {
709
+ if let loc = note.userInfo?["location"] as? MAURLocation,
710
+ let dict = loc.toDictionaryWithId() as? [String: Any] {
711
+ notifyListeners("phoneUsageWhileDriving", data: dict)
712
+ } else {
713
+ notifyListeners("phoneUsageWhileDriving", data: [:])
714
+ }
715
+ }
716
+ }
717
+
718
+ // MARK: - Permission helper
719
+
720
+ /// Wraps `CLLocationManager` for a single permission prompt.
721
+ private final class PermissionRequestHelper: NSObject, CLLocationManagerDelegate {
722
+ private let manager = CLLocationManager()
723
+ private let completion: (CLAuthorizationStatus) -> Void
724
+ private var done = false
725
+
726
+ init(completion: @escaping (CLAuthorizationStatus) -> Void) {
727
+ self.completion = completion
728
+ super.init()
729
+ manager.delegate = self
730
+ }
731
+
732
+ func start() {
733
+ manager.requestWhenInUseAuthorization()
734
+ }
735
+
736
+ func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
737
+ finish(with: manager.authorizationStatus)
738
+ }
739
+
740
+ // iOS 13 fallback (kept for SDK completeness; deployment target is 14+).
741
+ func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
742
+ finish(with: status)
743
+ }
744
+
745
+ private func finish(with status: CLAuthorizationStatus) {
746
+ if status == .notDetermined || done { return }
747
+ done = true
748
+ completion(status)
749
+ }
750
+ }