react-native-bg-geolocation 0.2.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 (142) hide show
  1. package/BgGeolocation.podspec +39 -0
  2. package/LICENSE +20 -0
  3. package/README.md +366 -0
  4. package/android/build.gradle +69 -0
  5. package/android/src/main/AndroidManifest.xml +53 -0
  6. package/android/src/main/java/com/bggeolocation/BgGeolocationActivityRecognitionReceiver.kt +116 -0
  7. package/android/src/main/java/com/bggeolocation/BgGeolocationBootReceiver.kt +44 -0
  8. package/android/src/main/java/com/bggeolocation/BgGeolocationForegroundService.kt +373 -0
  9. package/android/src/main/java/com/bggeolocation/BgGeolocationGeofenceReceiver.kt +55 -0
  10. package/android/src/main/java/com/bggeolocation/BgGeolocationHeadlessTask.kt +138 -0
  11. package/android/src/main/java/com/bggeolocation/BgGeolocationModule.kt +1030 -0
  12. package/android/src/main/java/com/bggeolocation/BgGeolocationMotionStateMachine.kt +159 -0
  13. package/android/src/main/java/com/bggeolocation/BgGeolocationPackage.kt +31 -0
  14. package/android/src/main/res/drawable/bg_geo_notification.xml +9 -0
  15. package/ios/BgGeolocation.h +14 -0
  16. package/ios/BgGeolocation.mm +709 -0
  17. package/ios/engine/AtomicBoolean.swift +48 -0
  18. package/ios/engine/BGActivityChangeEvent.swift +20 -0
  19. package/ios/engine/BGActivityConfig.swift +71 -0
  20. package/ios/engine/BGAppConfig.swift +92 -0
  21. package/ios/engine/BGAppState.swift +147 -0
  22. package/ios/engine/BGAuthorization.swift +85 -0
  23. package/ios/engine/BGAuthorizationAlertPresenter.swift +39 -0
  24. package/ios/engine/BGAuthorizationConfig.swift +50 -0
  25. package/ios/engine/BGAuthorizationEvent.swift +40 -0
  26. package/ios/engine/BGBackgroundTaskManager.swift +143 -0
  27. package/ios/engine/BGCLRouter.swift +101 -0
  28. package/ios/engine/BGCallback.swift +19 -0
  29. package/ios/engine/BGConfig.swift +440 -0
  30. package/ios/engine/BGConfigModuleBase.swift +180 -0
  31. package/ios/engine/BGConfigOLD.swift +582 -0
  32. package/ios/engine/BGConnectivityChangeEvent.swift +15 -0
  33. package/ios/engine/BGCrashDetector.swift +122 -0
  34. package/ios/engine/BGCurrentPositionRequest.swift +87 -0
  35. package/ios/engine/BGDataStore.swift +75 -0
  36. package/ios/engine/BGDatabase.swift +677 -0
  37. package/ios/engine/BGDatabasePool.swift +220 -0
  38. package/ios/engine/BGDatabaseQueue.swift +215 -0
  39. package/ios/engine/BGDateUtils.swift +26 -0
  40. package/ios/engine/BGDeviceInfo.swift +54 -0
  41. package/ios/engine/BGDeviceManager.swift +65 -0
  42. package/ios/engine/BGEnabledChangeEvent.swift +11 -0
  43. package/ios/engine/BGEnv.swift +17 -0
  44. package/ios/engine/BGEventBus.swift +83 -0
  45. package/ios/engine/BGEventManager.swift +169 -0
  46. package/ios/engine/BGEventNames.swift +51 -0
  47. package/ios/engine/BGGeofence.swift +233 -0
  48. package/ios/engine/BGGeofenceDAO.swift +152 -0
  49. package/ios/engine/BGGeofenceEvent.swift +42 -0
  50. package/ios/engine/BGGeofenceLocationRequest.swift +94 -0
  51. package/ios/engine/BGGeofenceManager.swift +315 -0
  52. package/ios/engine/BGGeofenceTransition.swift +97 -0
  53. package/ios/engine/BGGeofencesChangeEvent.swift +26 -0
  54. package/ios/engine/BGGeolocationConfig.swift +136 -0
  55. package/ios/engine/BGHeartbeatEvent.swift +31 -0
  56. package/ios/engine/BGHeartbeatService.swift +51 -0
  57. package/ios/engine/BGHttpConfig.swift +105 -0
  58. package/ios/engine/BGHttpErrorCodes.swift +63 -0
  59. package/ios/engine/BGHttpEvent.swift +34 -0
  60. package/ios/engine/BGHttpRequest.swift +126 -0
  61. package/ios/engine/BGHttpResponse.swift +93 -0
  62. package/ios/engine/BGHttpService.swift +428 -0
  63. package/ios/engine/BGKalmanFilter.swift +105 -0
  64. package/ios/engine/BGLMActionNames.swift +55 -0
  65. package/ios/engine/BGLicenseManager.swift +26 -0
  66. package/ios/engine/BGLiveActivityManager.swift +327 -0
  67. package/ios/engine/BGLocation.swift +311 -0
  68. package/ios/engine/BGLocationAuthorization.swift +427 -0
  69. package/ios/engine/BGLocationDAO.swift +252 -0
  70. package/ios/engine/BGLocationErrors.swift +28 -0
  71. package/ios/engine/BGLocationEvent.swift +43 -0
  72. package/ios/engine/BGLocationFilter.swift +82 -0
  73. package/ios/engine/BGLocationFilterConfig.swift +57 -0
  74. package/ios/engine/BGLocationHelper.swift +54 -0
  75. package/ios/engine/BGLocationManager.swift +662 -0
  76. package/ios/engine/BGLocationMetricsEngine.swift +116 -0
  77. package/ios/engine/BGLocationRequestService.swift +459 -0
  78. package/ios/engine/BGLocationSatisfier.swift +14 -0
  79. package/ios/engine/BGLocationStreamEvent.swift +27 -0
  80. package/ios/engine/BGLog.swift +337 -0
  81. package/ios/engine/BGLogLevel.swift +26 -0
  82. package/ios/engine/BGLoggerConfig.swift +60 -0
  83. package/ios/engine/BGMotionActivity.swift +31 -0
  84. package/ios/engine/BGMotionActivityClassifier.swift +108 -0
  85. package/ios/engine/BGMotionActivityManagerAdapter.swift +40 -0
  86. package/ios/engine/BGMotionActivitySource.swift +46 -0
  87. package/ios/engine/BGMotionDetector.swift +377 -0
  88. package/ios/engine/BGMotionPermissionManager.swift +50 -0
  89. package/ios/engine/BGNativeLogger.swift +48 -0
  90. package/ios/engine/BGNotificaitons.swift +37 -0
  91. package/ios/engine/BGOdometer.swift +66 -0
  92. package/ios/engine/BGPersistenceConfig.swift +29 -0
  93. package/ios/engine/BGPolygonStreamRequest.swift +48 -0
  94. package/ios/engine/BGPowerSaveChangeEvent.swift +12 -0
  95. package/ios/engine/BGPropertySpec.swift +29 -0
  96. package/ios/engine/BGProviderChangeEvent.swift +31 -0
  97. package/ios/engine/BGQueue.swift +50 -0
  98. package/ios/engine/BGRPC.swift +194 -0
  99. package/ios/engine/BGReachability.swift +58 -0
  100. package/ios/engine/BGResultSet.swift +157 -0
  101. package/ios/engine/BGSchedule.swift +228 -0
  102. package/ios/engine/BGScheduleEvent.swift +13 -0
  103. package/ios/engine/BGScheduler.swift +116 -0
  104. package/ios/engine/BGSingleLocationRequest.swift +49 -0
  105. package/ios/engine/BGStreamLocationRequest.swift +42 -0
  106. package/ios/engine/BGTemplate.swift +54 -0
  107. package/ios/engine/BGTimerService.swift +46 -0
  108. package/ios/engine/BGTrackingAudioManager.swift +286 -0
  109. package/ios/engine/BGTrackingService.swift +879 -0
  110. package/ios/engine/BGWatchPositionRequest.swift +63 -0
  111. package/ios/engine/DatabaseQueue.swift +47 -0
  112. package/ios/engine/LogQuery.swift +10 -0
  113. package/ios/engine/SQLQuery.swift +65 -0
  114. package/ios/engine/TransistorAuthorizationToken.swift +182 -0
  115. package/ios/liveactivity/BGLiveTrackingAttributes.swift +52 -0
  116. package/ios/locationpush/BGLocationPushDeliverer.swift +260 -0
  117. package/ios/locationpush/BGLocationPushService.swift +161 -0
  118. package/ios/locationpush/BGLocationPushShared.swift +98 -0
  119. package/ios/locationpush/BGLocationPushSocketClient.swift +198 -0
  120. package/lib/module/NativeBgGeolocation.js +5 -0
  121. package/lib/module/NativeBgGeolocation.js.map +1 -0
  122. package/lib/module/events.js +20 -0
  123. package/lib/module/events.js.map +1 -0
  124. package/lib/module/index.js +706 -0
  125. package/lib/module/index.js.map +1 -0
  126. package/lib/module/package.json +1 -0
  127. package/lib/module/types.js +2 -0
  128. package/lib/module/types.js.map +1 -0
  129. package/lib/typescript/package.json +1 -0
  130. package/lib/typescript/src/NativeBgGeolocation.d.ts +57 -0
  131. package/lib/typescript/src/NativeBgGeolocation.d.ts.map +1 -0
  132. package/lib/typescript/src/events.d.ts +18 -0
  133. package/lib/typescript/src/events.d.ts.map +1 -0
  134. package/lib/typescript/src/index.d.ts +238 -0
  135. package/lib/typescript/src/index.d.ts.map +1 -0
  136. package/lib/typescript/src/types.d.ts +229 -0
  137. package/lib/typescript/src/types.d.ts.map +1 -0
  138. package/package.json +141 -0
  139. package/src/NativeBgGeolocation.ts +236 -0
  140. package/src/events.ts +17 -0
  141. package/src/index.tsx +935 -0
  142. package/src/types.ts +254 -0
@@ -0,0 +1,709 @@
1
+ //
2
+ // BgGeolocation.mm
3
+ //
4
+ // ObjC++ TurboModule — delegates to our Swift engine (ios/engine/).
5
+ // No binary dependency, no billing, works in DEBUG and RELEASE.
6
+ //
7
+ #import "BgGeolocation.h"
8
+ // Spec import MUST be in the .mm (C++), not in the .h (which Swift module scan reads as plain C).
9
+ #import <BgGeolocationSpec/BgGeolocationSpec.h>
10
+ #import <Foundation/Foundation.h>
11
+ #import <CoreLocation/CoreLocation.h>
12
+ #import <CoreMotion/CoreMotion.h>
13
+ #import <UIKit/UIKit.h>
14
+ // These must come BEFORE BgGeolocation-Swift.h so the generated header
15
+ // can reference MFMailCompose and UNUserNotification types.
16
+ #import <MessageUI/MessageUI.h>
17
+ #import <UserNotifications/UserNotifications.h>
18
+ #import <AudioToolbox/AudioToolbox.h>
19
+
20
+ // Auto-generated ObjC bridge for all @objc Swift classes in this pod.
21
+ #import "BgGeolocation-Swift.h"
22
+
23
+ #ifdef RCT_NEW_ARCH_ENABLED
24
+ #import <React/RCTBridge+Private.h>
25
+ #import <React/RCTUtils.h>
26
+ using namespace facebook;
27
+ using namespace facebook::react;
28
+ #endif
29
+
30
+ // Declare NativeBgGeolocationSpec conformance here so it stays out of the header.
31
+ @interface BgGeolocation () <NativeBgGeolocationSpec>
32
+ @end
33
+
34
+ // ─── Event name constants (match src/events.ts) ──────────────────────────────
35
+ static NSString *const EVENT_LOCATION = @"location";
36
+ static NSString *const EVENT_WATCHPOSITION = @"watchposition";
37
+ static NSString *const EVENT_PROVIDERCHANGE = @"providerchange";
38
+ static NSString *const EVENT_MOTIONCHANGE = @"motionchange";
39
+ static NSString *const EVENT_ACTIVITYCHANGE = @"activitychange";
40
+ static NSString *const EVENT_GEOFENCESCHANGE = @"geofenceschange";
41
+ static NSString *const EVENT_HTTP = @"http";
42
+ static NSString *const EVENT_SCHEDULE = @"schedule";
43
+ static NSString *const EVENT_GEOFENCE = @"geofence";
44
+ static NSString *const EVENT_HEARTBEAT = @"heartbeat";
45
+ static NSString *const EVENT_POWERSAVECHANGE = @"powersavechange";
46
+ static NSString *const EVENT_CONNECTIVITYCHANGE = @"connectivitychange";
47
+ static NSString *const EVENT_ENABLEDCHANGE = @"enabledchange";
48
+ static NSString *const EVENT_NOTIFICATIONACTION = @"notificationaction";
49
+ static NSString *const EVENT_AUTHORIZATION = @"authorization";
50
+ static NSString *const EVENT_LOCATIONPUSH = @"locationpush";
51
+
52
+ // Bridges the app's background-push handler (AppDelegate) to JS and back.
53
+ // AppDelegate posts BACKGROUND with userInfo {requestId, locationQueryId};
54
+ // the module emits the JS "locationpush" event. When JS calls
55
+ // finishLocationPush(requestId) the module posts FINISHED so AppDelegate can
56
+ // invoke the stored UIBackgroundFetchResult completion handler.
57
+ static NSString *const NOTIF_LOCATIONPUSH_BACKGROUND = @"BGLocationPushBackground";
58
+ static NSString *const NOTIF_LOCATIONPUSH_FINISHED = @"BGLocationPushFinished";
59
+
60
+ @implementation BgGeolocation {
61
+ BOOL _ready;
62
+ NSInteger _watchId;
63
+ BGLocationManager *_engine; // our Swift engine instance
64
+ }
65
+
66
+ RCT_EXPORT_MODULE();
67
+
68
+ + (BOOL)requiresMainQueueSetup { return YES; }
69
+
70
+ - (instancetype)init {
71
+ self = [super init];
72
+ if (self) {
73
+ _ready = NO;
74
+ _watchId = -1;
75
+ _engine = [BGLocationManager sharedInstance];
76
+ }
77
+ return self;
78
+ }
79
+
80
+ - (NSArray<NSString *> *)supportedEvents {
81
+ return @[
82
+ EVENT_LOCATION, EVENT_WATCHPOSITION, EVENT_PROVIDERCHANGE, EVENT_MOTIONCHANGE,
83
+ EVENT_ACTIVITYCHANGE, EVENT_GEOFENCESCHANGE, EVENT_HTTP, EVENT_SCHEDULE,
84
+ EVENT_GEOFENCE, EVENT_HEARTBEAT, EVENT_POWERSAVECHANGE, EVENT_CONNECTIVITYCHANGE,
85
+ EVENT_ENABLEDCHANGE, EVENT_NOTIFICATIONACTION, EVENT_AUTHORIZATION,
86
+ EVENT_LOCATIONPUSH,
87
+ ];
88
+ }
89
+
90
+ // ─── Event listeners ──────────────────────────────────────────────────────────
91
+
92
+ - (void)registerEventListeners {
93
+ __typeof(self) __weak me = self;
94
+
95
+ [_engine onLocation:^(BGLocation *location) {
96
+ [me sendEventWithName:EVENT_LOCATION body:[location toDictionary]];
97
+ } failure:^(NSError *error) {
98
+ [me sendEventWithName:EVENT_LOCATION body:@{@"error": @(error.code)}];
99
+ }];
100
+
101
+ [_engine onMotionChange:^(BGLocation *location) {
102
+ [me sendEventWithName:EVENT_MOTIONCHANGE body:@{
103
+ @"isMoving": @(location.isMoving),
104
+ @"location": [location toDictionary]
105
+ }];
106
+ } failure:nil];
107
+
108
+ [_engine onActivityChange:^(BGMotionActivity *activity) {
109
+ [me sendEventWithName:EVENT_ACTIVITYCHANGE body:@{
110
+ @"activity": activity.name ?: @"unknown",
111
+ @"confidence": @(activity.confidence)
112
+ }];
113
+ }];
114
+
115
+ [_engine onHeartbeat:^(BGHeartbeatEvent *event) {
116
+ [me sendEventWithName:EVENT_HEARTBEAT body:[event toDictionary]];
117
+ }];
118
+
119
+ [_engine onGeofence:^(BGGeofenceEvent *event) {
120
+ [me sendEventWithName:EVENT_GEOFENCE body:[event toDictionary]];
121
+ }];
122
+
123
+ [_engine onHttp:^(NSDictionary *response) {
124
+ [me sendEventWithName:EVENT_HTTP body:response];
125
+ }];
126
+
127
+ [_engine onProviderChange:^(BGProviderChangeEvent *event) {
128
+ [me sendEventWithName:EVENT_PROVIDERCHANGE body:[event toDictionary]];
129
+ }];
130
+
131
+ [_engine onSchedule:^(BGScheduleEvent *event) {
132
+ // BGScheduleEvent.state is Any? — safely cast to dict or send an empty marker
133
+ NSDictionary *body = [event.state isKindOfClass:[NSDictionary class]]
134
+ ? (NSDictionary *)event.state : @{};
135
+ [me sendEventWithName:EVENT_SCHEDULE body:body];
136
+ }];
137
+
138
+ [_engine onPowerSaveChange:^(BOOL isPowerSave) {
139
+ [me sendEventWithName:EVENT_POWERSAVECHANGE body:@(isPowerSave)];
140
+ }];
141
+
142
+ [_engine onConnectivityChange:^(BOOL connected) {
143
+ [me sendEventWithName:EVENT_CONNECTIVITYCHANGE body:@{@"connected": @(connected)}];
144
+ }];
145
+
146
+ [_engine onEnabledChange:^(BOOL enabled) {
147
+ [me sendEventWithName:EVENT_ENABLEDCHANGE body:@(enabled)];
148
+ }];
149
+
150
+ [_engine onAuthorization:^(BGAuthorizationEvent *event) {
151
+ [me sendEventWithName:EVENT_AUTHORIZATION body:[event toDictionary]];
152
+ }];
153
+
154
+ // Relay AppDelegate background location-pushes to JS. Registered once.
155
+ // Native captures the location with our engine, then hands it to JS — JS owns
156
+ // delivery (socket/REST). Only runs while the app process is alive; kill-state
157
+ // pushes are handled entirely by the LocationPushExtension.
158
+ static dispatch_once_t onceToken;
159
+ dispatch_once(&onceToken, ^{
160
+ [[NSNotificationCenter defaultCenter]
161
+ addObserverForName:NOTIF_LOCATIONPUSH_BACKGROUND
162
+ object:nil
163
+ queue:[NSOperationQueue mainQueue]
164
+ usingBlock:^(NSNotification *note) {
165
+ __strong __typeof(me) strongMe = me;
166
+ if (!strongMe) return;
167
+ NSString *requestId = note.userInfo[@"requestId"] ?: @"";
168
+ id queryId = note.userInfo[@"locationQueryId"] ?: [NSNull null];
169
+
170
+ BGCurrentPositionRequest *request =
171
+ [BGCurrentPositionRequest requestWithSuccess:^(id locationObj) {
172
+ BGLocation *tsLocation = (BGLocation *)locationObj;
173
+ CLLocation *loc = tsLocation.location;
174
+ NSDictionary *dict = [tsLocation toDictionary];
175
+
176
+ // Deliver NATIVELY (socket → REST) so it works even when the RN bridge
177
+ // isn't alive on a background/kill-state wake. JS is notified after,
178
+ // with delivered=YES so the host app does NOT re-send.
179
+ void (^afterDeliver)(BOOL) = ^(BOOL delivered) {
180
+ dispatch_async(dispatch_get_main_queue(), ^{
181
+ [strongMe sendEventWithName:EVENT_LOCATIONPUSH body:@{
182
+ @"requestId": requestId,
183
+ @"locationQueryId": queryId,
184
+ @"location": dict ?: [NSNull null],
185
+ @"delivered": @(delivered),
186
+ }];
187
+ // Release the app without depending on JS calling finishLocationPush.
188
+ [[NSNotificationCenter defaultCenter]
189
+ postNotificationName:NOTIF_LOCATIONPUSH_FINISHED
190
+ object:nil
191
+ userInfo:@{@"requestId": requestId}];
192
+ });
193
+ };
194
+
195
+ if (loc != nil) {
196
+ CLLocationCoordinate2D c = loc.coordinate;
197
+ NSString *ts = [strongMe iso8601StringFromDate:loc.timestamp];
198
+ [BGLocationPushDeliverer deliverWithLatitude:c.latitude
199
+ longitude:c.longitude
200
+ accuracy:MAX(loc.horizontalAccuracy, 0)
201
+ speed:loc.speed
202
+ heading:loc.course
203
+ altitude:loc.altitude
204
+ timestampISO:ts
205
+ queryId:(queryId == [NSNull null] ? @"" : queryId)
206
+ completion:^(BOOL ok) { afterDeliver(ok); }];
207
+ } else {
208
+ afterDeliver(NO);
209
+ }
210
+ } failure:^(NSInteger code) {
211
+ [strongMe sendEventWithName:EVENT_LOCATIONPUSH body:@{
212
+ @"requestId": requestId,
213
+ @"locationQueryId": queryId,
214
+ @"error": @(code),
215
+ @"delivered": @(NO),
216
+ }];
217
+ [[NSNotificationCenter defaultCenter]
218
+ postNotificationName:NOTIF_LOCATIONPUSH_FINISHED
219
+ object:nil
220
+ userInfo:@{@"requestId": requestId}];
221
+ }];
222
+ request.timeout = 20;
223
+ request.samples = 1;
224
+ request.desiredAccuracy = 10;
225
+ request.persist = NO;
226
+ [strongMe->_engine getCurrentPosition:request];
227
+ }];
228
+ });
229
+ }
230
+
231
+ #pragma mark - Core
232
+
233
+ - (void)ready:(NSDictionary *)config success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
234
+ if (_ready) {
235
+ BOOL resetFlag = config[@"reset"] ? [config[@"reset"] boolValue] : YES;
236
+ if (resetFlag) [[BGConfig sharedInstance] updateWithDictionary:config];
237
+ success(@[[BGConfig sharedInstance].toDictionary]);
238
+ return;
239
+ }
240
+ _ready = YES;
241
+
242
+ dispatch_async(dispatch_get_main_queue(), ^{
243
+ // Set view controller now that RN window is ready
244
+ UIViewController *root =
245
+ [[[[UIApplication sharedApplication] delegate] window] rootViewController];
246
+ if (root) { self->_engine.viewController = root; }
247
+
248
+ @try {
249
+ BGConfig *cfg = [BGConfig sharedInstance];
250
+ BOOL resetFlag = config[@"reset"] ? [config[@"reset"] boolValue] : YES;
251
+
252
+ if (cfg.isFirstBoot) {
253
+ [cfg updateWithDictionary:config];
254
+ } else if (resetFlag) {
255
+ [cfg resetConfig:@{}];
256
+ [cfg updateWithDictionary:config];
257
+ } else {
258
+ [cfg updateWithDictionary:config];
259
+ }
260
+
261
+ [self registerEventListeners];
262
+ [self->_engine ready];
263
+ success(@[cfg.toDictionary]);
264
+ } @catch (NSException *e) {
265
+ failure(@[e.reason ?: @"ready_error"]);
266
+ }
267
+ });
268
+ }
269
+
270
+ - (void)configure:(NSDictionary *)config success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
271
+ [self ready:config success:success failure:failure];
272
+ }
273
+
274
+ - (void)reset:(NSDictionary *)config success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
275
+ BGConfig *cfg = [BGConfig sharedInstance];
276
+ @try {
277
+ if (config.count > 0) { [cfg resetConfig:@{}]; [cfg updateWithDictionary:config]; }
278
+ else { [cfg reset]; }
279
+ success(@[cfg.toDictionary]);
280
+ } @catch (NSException *e) { failure(@[e.reason ?: @"reset_error"]); }
281
+ }
282
+
283
+ - (void)setConfig:(NSDictionary *)config success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
284
+ [[BGConfig sharedInstance] updateWithDictionary:config];
285
+ success(@[[BGConfig sharedInstance].toDictionary]);
286
+ }
287
+
288
+ - (void)getState:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
289
+ success(@[[_engine getState]]);
290
+ }
291
+
292
+ #pragma mark - Lifecycle
293
+
294
+ - (void)start:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
295
+ dispatch_async(dispatch_get_main_queue(), ^{
296
+ [self->_engine start];
297
+ success(@[[self->_engine getState]]);
298
+ });
299
+ }
300
+
301
+ - (void)stop:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
302
+ [_engine stop];
303
+ success(@[[_engine getState]]);
304
+ }
305
+
306
+ - (void)startSchedule:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
307
+ dispatch_async(dispatch_get_main_queue(), ^{
308
+ [self->_engine startSchedule];
309
+ success(@[[self->_engine getState]]);
310
+ });
311
+ }
312
+
313
+ - (void)stopSchedule:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
314
+ dispatch_async(dispatch_get_main_queue(), ^{
315
+ [self->_engine stopSchedule];
316
+ success(@[[self->_engine getState]]);
317
+ });
318
+ }
319
+
320
+ - (void)startGeofences:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
321
+ dispatch_async(dispatch_get_main_queue(), ^{
322
+ [self->_engine startGeofences];
323
+ success(@[[BGConfig sharedInstance].toDictionary]);
324
+ });
325
+ }
326
+
327
+ #pragma mark - Background task
328
+
329
+ - (void)beginBackgroundTask:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
330
+ success(@[@([_engine createBackgroundTask])]);
331
+ }
332
+
333
+ - (void)finish:(double)taskId success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
334
+ [_engine stopBackgroundTask:(NSUInteger)taskId];
335
+ success(@[@(taskId)]);
336
+ }
337
+
338
+ #pragma mark - Motion / Location
339
+
340
+ - (void)changePace:(BOOL)isMoving success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
341
+ [_engine changePace:isMoving];
342
+ success(@[]);
343
+ }
344
+
345
+ - (void)getCurrentPosition:(NSDictionary *)options success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
346
+ BGCurrentPositionRequest *request = [BGCurrentPositionRequest requestWithSuccess:^(id location) {
347
+ success(@[[(BGLocation *)location toDictionary]]);
348
+ } failure:^(NSInteger code) {
349
+ failure(@[@(code)]);
350
+ }];
351
+ if (options[@"timeout"]) request.timeout = [options[@"timeout"] doubleValue];
352
+ if (options[@"maximumAge"]) request.maximumAge = [options[@"maximumAge"] doubleValue];
353
+ if (options[@"persist"]) request.persist = [options[@"persist"] boolValue];
354
+ if (options[@"samples"]) request.samples = [options[@"samples"] intValue];
355
+ if (options[@"desiredAccuracy"]) request.desiredAccuracy = [options[@"desiredAccuracy"] doubleValue];
356
+ if (options[@"extras"]) request.extras = options[@"extras"];
357
+ [_engine getCurrentPosition:request];
358
+ }
359
+
360
+ - (void)watchPosition:(NSDictionary *)options success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
361
+ // interval is private(set) — use the factory that accepts it up front
362
+ double interval = options[@"interval"] ? [options[@"interval"] doubleValue] : 60000.0;
363
+ BGWatchPositionRequest *request = [BGWatchPositionRequest requestWithInterval:interval
364
+ success:^(id location) {
365
+ [self sendEventWithName:EVENT_WATCHPOSITION body:[(BGLocation *)location toDictionary]];
366
+ } failure:^(NSInteger code) {}];
367
+ if (options[@"desiredAccuracy"]) request.desiredAccuracy = [options[@"desiredAccuracy"] doubleValue];
368
+ if (options[@"persist"]) request.persist = [options[@"persist"] boolValue];
369
+ if (options[@"extras"]) request.extras = options[@"extras"];
370
+ if (options[@"timeout"]) request.timeout = [options[@"timeout"] doubleValue];
371
+ [_engine watchPosition:request]; _watchId = 0;
372
+ success(@[]);
373
+ }
374
+
375
+ - (void)stopWatchPosition:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
376
+ if (_watchId >= 0) { [_engine stopWatchPosition:_watchId]; _watchId = -1; }
377
+ success(@[]);
378
+ }
379
+
380
+ #pragma mark - Permissions
381
+
382
+ - (void)requestPermission:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
383
+ __typeof(self) __weak weakSelf = self;
384
+ [_engine requestPermission:^{
385
+ __typeof(self) __strong strongSelf = weakSelf;
386
+ NSDictionary *state = [strongSelf->_engine getProviderState];
387
+ success(@[state[@"status"] ?: @(3)]);
388
+ } failure:^(NSError *error) {
389
+ failure(@[@(error.code)]);
390
+ }];
391
+ }
392
+
393
+ - (void)requestMotionPermission:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
394
+ CMMotionActivityManager *motionManager = [CMMotionActivityManager new];
395
+ [motionManager queryActivityStartingFromDate:[NSDate date]
396
+ toDate:[NSDate date]
397
+ toQueue:NSOperationQueue.mainQueue
398
+ withHandler:^(__unused NSArray<CMMotionActivity *> *activities, __unused NSError *error) {
399
+ success(@[@([CMMotionActivityManager authorizationStatus])]);
400
+ }];
401
+ }
402
+
403
+ - (void)requestTemporaryFullAccuracy:(NSString *)purpose success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
404
+ [_engine requestTemporaryFullAccuracy:purpose success:^{
405
+ success(@[@(2)]); // 2 = full accuracy
406
+ } failure:^(NSError *error) {
407
+ failure(@[error.localizedDescription ?: @"accuracy_error"]);
408
+ }];
409
+ }
410
+
411
+ - (void)getProviderState:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
412
+ success(@[[_engine getProviderState]]);
413
+ }
414
+
415
+ #pragma mark - HTTP & Persistence
416
+
417
+ - (void)getLocations:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
418
+ [_engine getLocations:^(NSArray *records) { success(@[records]); }
419
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"get_locations_error"]); }];
420
+ }
421
+
422
+ - (void)getCount:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
423
+ success(@[@([_engine getCount])]);
424
+ }
425
+
426
+ - (void)destroyLocations:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
427
+ [_engine destroyLocations];
428
+ success(@[]);
429
+ }
430
+
431
+ - (void)destroyLocation:(NSString *)uuid success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
432
+ [_engine destroyLocation:uuid success:^{ success(@[]); }
433
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"destroy_error"]); }];
434
+ }
435
+
436
+ - (void)insertLocation:(NSDictionary *)location success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
437
+ NSDictionary *coords = location[@"coords"] ?: @{};
438
+ CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(
439
+ [coords[@"latitude"] doubleValue], [coords[@"longitude"] doubleValue]);
440
+ CLLocation *cl = [[CLLocation alloc] initWithCoordinate:coord
441
+ altitude:[coords[@"altitude"] doubleValue]
442
+ horizontalAccuracy:[coords[@"accuracy"] doubleValue]
443
+ verticalAccuracy:[coords[@"altitudeAccuracy"] doubleValue]
444
+ timestamp:[NSDate date]];
445
+ BGLocation *loc = [[BGLocation alloc] initWithLocation:cl type:@"manual" extras:location[@"extras"]];
446
+ [_engine insertLocation:loc success:^(BGLocation *l) { success(@[[l uuid]]); }
447
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"insert_error"]); }];
448
+ }
449
+
450
+ - (void)sync:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
451
+ [_engine sync:^(NSArray *records) { success(@[records]); }
452
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"sync_error"]); }];
453
+ }
454
+
455
+ #pragma mark - Odometer
456
+
457
+ - (void)getOdometer:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
458
+ success(@[@([_engine getOdometer])]);
459
+ }
460
+
461
+ - (void)setOdometer:(double)value success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
462
+ BGCurrentPositionRequest *request = [BGCurrentPositionRequest requestWithSuccess:^(id location) {
463
+ success(@[[(BGLocation *)location toDictionary]]);
464
+ } failure:^(NSInteger code) {
465
+ failure(@[@(code)]);
466
+ }];
467
+ [_engine setOdometer:value request:request];
468
+ }
469
+
470
+ #pragma mark - Geofences
471
+
472
+ - (void)addGeofence:(NSDictionary *)config success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
473
+ BGGeofence *gf = [self buildGeofence:config];
474
+ if (!gf) { failure(@[@"Invalid geofence data"]); return; }
475
+ [_engine addGeofence:gf success:^{ success(@[]); }
476
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"geofence_error"]); }];
477
+ }
478
+
479
+ - (void)addGeofences:(NSArray *)geofences success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
480
+ NSMutableArray *list = [NSMutableArray new];
481
+ for (NSDictionary *params in geofences) {
482
+ BGGeofence *gf = [self buildGeofence:params];
483
+ if (!gf) { failure(@[@"Invalid geofence data"]); return; }
484
+ [list addObject:gf];
485
+ }
486
+ [_engine addGeofences:list success:^{ success(@[]); }
487
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"geofences_error"]); }];
488
+ }
489
+
490
+ - (BGGeofence *)buildGeofence:(NSDictionary *)params {
491
+ if (!params[@"identifier"]) return nil;
492
+ return [BGGeofence circleWithIdentifier:params[@"identifier"]
493
+ radius:[params[@"radius"] doubleValue]
494
+ latitude:[params[@"latitude"] doubleValue]
495
+ longitude:[params[@"longitude"] doubleValue]
496
+ notifyOnEntry:params[@"notifyOnEntry"] ? [params[@"notifyOnEntry"] boolValue] : YES
497
+ notifyOnExit:params[@"notifyOnExit"] ? [params[@"notifyOnExit"] boolValue] : YES
498
+ notifyOnDwell:params[@"notifyOnDwell"] ? [params[@"notifyOnDwell"] boolValue] : NO
499
+ loiteringDelay:params[@"loiteringDelay"] ? [params[@"loiteringDelay"] doubleValue] : 0
500
+ extras:params[@"extras"]];
501
+ }
502
+
503
+ - (void)removeGeofence:(NSString *)identifier success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
504
+ [_engine removeGeofence:identifier success:^{ success(@[]); }
505
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"remove_geofence_error"]); }];
506
+ }
507
+
508
+ - (void)removeGeofences:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
509
+ [_engine removeGeofences:@[] success:^{ success(@[]); }
510
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"remove_geofences_error"]); }];
511
+ }
512
+
513
+ - (void)getGeofences:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
514
+ [_engine getGeofences:^(NSArray *geofences) {
515
+ NSMutableArray *result = [NSMutableArray new];
516
+ for (BGGeofence *g in geofences) [result addObject:[g toDictionary]];
517
+ success(@[result]);
518
+ } failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"get_geofences_error"]); }];
519
+ }
520
+
521
+ - (void)getGeofence:(NSString *)identifier success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
522
+ [_engine getGeofence:identifier success:^(BGGeofence *g) { success(@[[g toDictionary]]); }
523
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"get_geofence_error"]); }];
524
+ }
525
+
526
+ - (void)geofenceExists:(NSString *)identifier callback:(RCTResponseSenderBlock)callback {
527
+ [_engine geofenceExists:identifier callback:^(BOOL exists) { callback(@[@(exists)]); }];
528
+ }
529
+
530
+ #pragma mark - Logging
531
+
532
+ - (void)log:(NSString *)level message:(NSString *)message {
533
+ [_engine log:level message:message];
534
+ }
535
+
536
+ - (void)setLogLevel:(double)value success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
537
+ [[BGConfig sharedInstance] updateWithDictionary:@{@"logLevel": @(value)}];
538
+ success(@[[BGConfig sharedInstance].toDictionary]);
539
+ }
540
+
541
+ - (void)getLog:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
542
+ LogQuery *query = [[LogQuery alloc] init];
543
+ [_engine getLog:query success:^(NSDictionary *result) {
544
+ // Convert log entries array to JSON string for JS compatibility
545
+ NSArray *entries = result[@"log"] ?: @[];
546
+ NSData *data = [NSJSONSerialization dataWithJSONObject:entries options:0 error:nil];
547
+ NSString *json = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : @"[]";
548
+ success(@[json]);
549
+ } failure:^(NSError *error) {
550
+ failure(@[error.localizedDescription ?: @"get_log_error"]);
551
+ }];
552
+ }
553
+
554
+ - (void)destroyLog:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
555
+ [_engine destroyLog];
556
+ success(@[]);
557
+ }
558
+
559
+ - (void)emailLog:(NSString *)email success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
560
+ [_engine emailLog:email success:^{ success(@[]); }
561
+ failure:^(NSError *error) { failure(@[error.localizedDescription ?: @"email_log_error"]); }];
562
+ }
563
+
564
+ #pragma mark - Utility
565
+
566
+ - (void)isPowerSaveMode:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
567
+ success(@[@([_engine isPowerSaveMode])]);
568
+ }
569
+
570
+ - (void)getSensors:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
571
+ success(@[@{
572
+ @"platform": @"ios",
573
+ @"accelerometer": @([_engine isAccelerometerAvailable]),
574
+ @"gyroscope": @([_engine isGyroAvailable]),
575
+ @"magnetometer": @([_engine isMagnetometerAvailable]),
576
+ @"motionHardware": @([_engine isMotionHardwareAvailable]),
577
+ @"motionAuthorizationStatus": @([CMMotionActivityManager authorizationStatus]),
578
+ }]);
579
+ }
580
+
581
+ - (void)getDeviceInfo:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
582
+ success(@[@{
583
+ @"platform": @"ios",
584
+ @"manufacturer": @"Apple",
585
+ @"model": [[UIDevice currentDevice] model],
586
+ @"version": [[UIDevice currentDevice] systemVersion],
587
+ @"framework": @"react-native",
588
+ }]);
589
+ }
590
+
591
+ - (void)getLocationPushToken:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
592
+ NSString *token = [[NSUserDefaults standardUserDefaults]
593
+ stringForKey:@"BGLocationManager_locationPushToken"];
594
+ success(@[token ?: [NSNull null]]);
595
+ }
596
+
597
+ - (void)getApnsDeviceToken:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure {
598
+ NSString *token = [[NSUserDefaults standardUserDefaults]
599
+ stringForKey:@"BGLocationManager_apnsDeviceToken"];
600
+ success(@[token ?: [NSNull null]]);
601
+ }
602
+
603
+ - (void)setLocationPushConfig:(NSDictionary *)config
604
+ success:(RCTResponseSenderBlock)success
605
+ failure:(RCTResponseSenderBlock)failure {
606
+ [_engine setLocationPushConfig:(config ?: @{})];
607
+ success(@[]);
608
+ }
609
+
610
+ - (NSString *)iso8601StringFromDate:(NSDate *)date {
611
+ static NSISO8601DateFormatter *formatter;
612
+ static dispatch_once_t once;
613
+ dispatch_once(&once, ^{
614
+ formatter = [[NSISO8601DateFormatter alloc] init];
615
+ formatter.formatOptions = NSISO8601DateFormatWithInternetDateTime |
616
+ NSISO8601DateFormatWithFractionalSeconds;
617
+ });
618
+ return [formatter stringFromDate:(date ?: [NSDate date])];
619
+ }
620
+
621
+ - (void)finishLocationPush:(NSString *)requestId
622
+ success:(RCTResponseSenderBlock)success
623
+ failure:(RCTResponseSenderBlock)failure {
624
+ [[NSNotificationCenter defaultCenter]
625
+ postNotificationName:NOTIF_LOCATIONPUSH_FINISHED
626
+ object:nil
627
+ userInfo:@{@"requestId": requestId ?: @""}];
628
+ success(@[]);
629
+ }
630
+
631
+ - (void)playSound:(double)soundId {
632
+ [_engine playSound:(SystemSoundID)soundId];
633
+ }
634
+
635
+ #pragma mark - Lifecycle
636
+
637
+ - (void)dealloc {
638
+ [_engine removeListeners];
639
+ _engine = nil;
640
+ }
641
+
642
+ #ifdef RCT_NEW_ARCH_ENABLED
643
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
644
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
645
+ return std::make_shared<facebook::react::NativeBgGeolocationSpecJSI>(params);
646
+ }
647
+ #endif
648
+
649
+ @end
650
+
651
+ // ─── Native kill-state / background launch bootstrap ─────────────────────────
652
+ // A TurboModule is instantiated LAZILY — only when JS first touches it. On an
653
+ // iOS location relaunch after system termination (significant-location-change
654
+ // or stationary-region exit), the OS launches the app in the background and expects the queued Core
655
+ // Location event to be delivered to a CLLocationManager that, at that instant,
656
+ // does not exist yet. Waiting for the React Native bridge + JS bundle to boot
657
+ // and import this module often loses the brief background wake window.
658
+ //
659
+ // This standalone class eagerly constructs the engine at class-load time and
660
+ // also observes UIApplicationDidFinishLaunchingNotification — recreating the
661
+ // CLLocationManager, installing the BGCLRouter delegate, and (via auto-resume)
662
+ // re-arming SLC/region + native HTTP delivery — independently of React Native.
663
+ // (It lives in its own class because BgGeolocation's +load is already provided
664
+ // by the RCT_EXPORT_MODULE() macro, so a second +load there would collide.)
665
+ @interface BGLaunchBootstrap : NSObject
666
+ @end
667
+
668
+ @implementation BGLaunchBootstrap
669
+
670
+ + (void)bootstrapFromNotification:(NSNotification *)note phase:(NSString *)phase {
671
+ BOOL launchedForLocation =
672
+ note.userInfo[UIApplicationLaunchOptionsLocationKey] != nil;
673
+ BOOL inBackground =
674
+ [UIApplication sharedApplication].applicationState == UIApplicationStateBackground;
675
+ if (launchedForLocation || inBackground) {
676
+ [[NSUserDefaults standardUserDefaults] setBool:YES
677
+ forKey:@"BGLocationManager_didLaunchInBackground"];
678
+ [[NSUserDefaults standardUserDefaults] synchronize];
679
+ }
680
+ // Diagnostic marker — visible in the iOS unified log (`log show` /
681
+ // `log collect`) even for a location-triggered kill-state relaunch with no JS.
682
+ NSLog(@"[BGGEO] %@Launching: launchedForLocation=%d inBackground=%d",
683
+ phase, launchedForLocation, inBackground);
684
+ // Bootstrap the engine now, before/independent of the RN bridge. The
685
+ // singleton's setupCoreLocation runs auto-resume when tracking was persisted
686
+ // enabled; on a normal foreground launch with tracking disabled this just
687
+ // creates + configures the (idle) manager, which is harmless.
688
+ BGLocationManager *manager = [BGLocationManager sharedInstance];
689
+ if (launchedForLocation || inBackground) {
690
+ [manager ready];
691
+ }
692
+ }
693
+
694
+ + (void)load {
695
+ // For a previously-enabled tracker, persisted config is enough to resume the
696
+ // engine before React Native or the application delegate has finished booting.
697
+ // CLLocationManager is created on the main thread by BGLocationManager.
698
+ (void)[BGLocationManager sharedInstance];
699
+
700
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
701
+ [center addObserverForName:UIApplicationDidFinishLaunchingNotification
702
+ object:nil
703
+ queue:[NSOperationQueue mainQueue]
704
+ usingBlock:^(NSNotification *note) {
705
+ [self bootstrapFromNotification:note phase:@"didFinish"];
706
+ }];
707
+ }
708
+
709
+ @end