@tsachit/react-native-geo-service 1.0.0 → 1.0.2

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.
@@ -1,5 +1,6 @@
1
1
  #import "RNGeoService.h"
2
2
  #import <React/RCTLog.h>
3
+ #import <UIKit/UIKit.h>
3
4
 
4
5
  // ---------------------------------------------------------------------------
5
6
  // CLActivityType helper
@@ -33,6 +34,9 @@ static CLLocationAccuracy accuracyFromString(NSString *accuracy) {
33
34
  @property (nonatomic, strong) CLLocationManager *locationManager;
34
35
  @property (nonatomic, strong) NSDictionary *config;
35
36
 
37
+ // Locations buffered while JS listeners are not yet attached (background relaunch)
38
+ @property (nonatomic, strong) NSMutableArray<NSDictionary *> *pendingLocations;
39
+
36
40
  @property (nonatomic, assign) BOOL isTracking;
37
41
  @property (nonatomic, assign) BOOL hasListeners;
38
42
  @property (nonatomic, assign) BOOL coarseTracking;
@@ -45,6 +49,15 @@ static CLLocationAccuracy accuracyFromString(NSString *accuracy) {
45
49
  @property (nonatomic, assign) NSInteger slowReadingCount;
46
50
  @property (nonatomic, assign) BOOL isIdle;
47
51
 
52
+ // Battery tracking
53
+ @property (nonatomic, assign) float batteryLevelAtStart;
54
+
55
+ // Session tracking metrics
56
+ @property (nonatomic, assign) NSInteger updateCount;
57
+ @property (nonatomic, strong) NSDate *trackingStartTime;
58
+ @property (nonatomic, assign) NSTimeInterval gpsActiveSeconds; // accumulated GPS-on time
59
+ @property (nonatomic, strong) NSDate *gpsActiveStart; // when current GPS-on window started
60
+
48
61
  @end
49
62
 
50
63
  @implementation RNGeoService
@@ -59,6 +72,56 @@ RCT_EXPORT_MODULE();
59
72
  return dispatch_get_main_queue();
60
73
  }
61
74
 
75
+ // ---------------------------------------------------------------------------
76
+ // Init — auto-resume tracking if app was relaunched from terminated state
77
+ //
78
+ // iOS can relaunch a terminated app when startMonitoringSignificantLocationChanges
79
+ // is active. When this happens, React Native creates a fresh module instance.
80
+ // We detect this via NSUserDefaults and immediately resume tracking so that
81
+ // location updates are not lost during the relaunch window.
82
+ // ---------------------------------------------------------------------------
83
+ - (instancetype)init {
84
+ if (self = [super init]) {
85
+ self.pendingLocations = [NSMutableArray array];
86
+
87
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
88
+ BOOL wasTracking = [defaults boolForKey:@"GeoServiceIsTracking"];
89
+
90
+ if (wasTracking) {
91
+ // Restore persisted config
92
+ NSData *configData = [defaults objectForKey:@"GeoServiceConfig"];
93
+ if (configData) {
94
+ NSDictionary *restoredConfig = [NSPropertyListSerialization
95
+ propertyListWithData:configData options:0 format:nil error:nil];
96
+ if (restoredConfig) {
97
+ self.config = restoredConfig;
98
+ self.coarseTracking = [restoredConfig[@"coarseTracking"] boolValue];
99
+ self.debugMode = [restoredConfig[@"debug"] boolValue];
100
+ self.adaptiveAccuracy = restoredConfig[@"adaptiveAccuracy"]
101
+ ? [restoredConfig[@"adaptiveAccuracy"] boolValue] : YES;
102
+ self.idleSpeedThreshold = restoredConfig[@"idleSpeedThreshold"]
103
+ ? [restoredConfig[@"idleSpeedThreshold"] floatValue] : 0.5f;
104
+ self.idleSampleCount = restoredConfig[@"idleSampleCount"]
105
+ ? [restoredConfig[@"idleSampleCount"] integerValue] : 3;
106
+ }
107
+ }
108
+
109
+ [self applyConfigToLocationManager];
110
+
111
+ // Significant changes is always running alongside standard updates —
112
+ // it is the only mechanism that can wake a terminated app and costs
113
+ // almost nothing in battery (cell towers, not GPS).
114
+ [self.locationManager startMonitoringSignificantLocationChanges];
115
+ if (!self.coarseTracking) {
116
+ [self.locationManager startUpdatingLocation];
117
+ }
118
+ self.isTracking = YES;
119
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Auto-resumed tracking after app relaunch");
120
+ }
121
+ }
122
+ return self;
123
+ }
124
+
62
125
  // ---------------------------------------------------------------------------
63
126
  // Supported events
64
127
  // ---------------------------------------------------------------------------
@@ -66,8 +129,24 @@ RCT_EXPORT_MODULE();
66
129
  return @[@"onLocation", @"onError"];
67
130
  }
68
131
 
69
- - (void)startObserving { self.hasListeners = YES; }
70
- - (void)stopObserving { self.hasListeners = NO; }
132
+ // Drain any locations that arrived before JS listeners were attached.
133
+ // This is the normal case during a background relaunch from terminated state:
134
+ // CLLocationManager fires before the React component tree has mounted.
135
+ - (void)startObserving {
136
+ self.hasListeners = YES;
137
+ if (self.pendingLocations.count > 0) {
138
+ if (self.debugMode) {
139
+ RCTLogInfo(@"[RNGeoService] Draining %lu buffered location(s) to JS",
140
+ (unsigned long)self.pendingLocations.count);
141
+ }
142
+ for (NSDictionary *loc in self.pendingLocations) {
143
+ [self sendEventWithName:@"onLocation" body:loc];
144
+ }
145
+ [self.pendingLocations removeAllObjects];
146
+ }
147
+ }
148
+
149
+ - (void)stopObserving { self.hasListeners = NO; }
71
150
 
72
151
  // ---------------------------------------------------------------------------
73
152
  // Lazy CLLocationManager
@@ -86,7 +165,7 @@ RCT_EXPORT_MODULE();
86
165
  RCT_EXPORT_METHOD(configure:(NSDictionary *)options
87
166
  resolve:(RCTPromiseResolveBlock)resolve
88
167
  reject:(RCTPromiseRejectBlock)reject) {
89
- self.config = options;
168
+ self.config = options;
90
169
  self.coarseTracking = [options[@"coarseTracking"] boolValue];
91
170
  self.debugMode = [options[@"debug"] boolValue];
92
171
  self.adaptiveAccuracy = options[@"adaptiveAccuracy"] ? [options[@"adaptiveAccuracy"] boolValue] : YES;
@@ -95,6 +174,19 @@ RCT_EXPORT_METHOD(configure:(NSDictionary *)options
95
174
  self.slowReadingCount = 0;
96
175
  self.isIdle = NO;
97
176
 
177
+ // Persist config so it survives app termination and can be restored on
178
+ // background relaunch triggered by significant location changes.
179
+ NSError *serializeError = nil;
180
+ NSData *configData = [NSPropertyListSerialization
181
+ dataWithPropertyList:options
182
+ format:NSPropertyListBinaryFormat_v1_0
183
+ options:0
184
+ error:&serializeError];
185
+ if (configData && !serializeError) {
186
+ [[NSUserDefaults standardUserDefaults] setObject:configData forKey:@"GeoServiceConfig"];
187
+ [[NSUserDefaults standardUserDefaults] synchronize];
188
+ }
189
+
98
190
  [self applyConfigToLocationManager];
99
191
 
100
192
  if (self.debugMode) RCTLogInfo(@"[RNGeoService] Config applied: %@", options);
@@ -117,7 +209,7 @@ RCT_EXPORT_METHOD(configure:(NSDictionary *)options
117
209
  self.locationManager.pausesLocationUpdatesAutomatically = autoPause;
118
210
 
119
211
  if (@available(iOS 11.0, *)) {
120
- BOOL bgIndicator = [cfg[@"showBackgroundIndicator"] boolValue];
212
+ BOOL bgIndicator = self.debugMode ? YES : [cfg[@"showBackgroundIndicator"] boolValue];
121
213
  self.locationManager.showsBackgroundLocationIndicator = bgIndicator;
122
214
  }
123
215
 
@@ -131,30 +223,44 @@ RCT_EXPORT_METHOD(start:(RCTPromiseResolveBlock)resolve
131
223
  reject:(RCTPromiseRejectBlock)reject) {
132
224
  CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
133
225
 
226
+ // If permission is denied or restricted, resolve without starting.
227
+ // The app is responsible for requesting OS permission (via react-native-permissions)
228
+ // before calling start(). If denied, the didChangeAuthorizationStatus delegate
229
+ // will handle cleanup.
134
230
  if (status == kCLAuthorizationStatusDenied ||
135
231
  status == kCLAuthorizationStatusRestricted) {
136
- reject(@"PERMISSION_DENIED", @"Location permission denied. Request 'Always' permission before calling start().", nil);
232
+ resolve(nil);
137
233
  return;
138
234
  }
139
235
 
140
- if (status == kCLAuthorizationStatusNotDetermined) {
141
- [self.locationManager requestAlwaysAuthorization];
142
- }
143
-
144
236
  [self applyConfigToLocationManager];
145
237
 
238
+ // Significant changes MUST always run alongside standard updates.
239
+ // It is the only iOS mechanism that can relaunch a terminated app —
240
+ // and it uses cell towers (not GPS), so battery cost is negligible.
241
+ [self.locationManager startMonitoringSignificantLocationChanges];
242
+
146
243
  if (self.coarseTracking) {
147
- [self.locationManager startMonitoringSignificantLocationChanges];
148
- if (self.debugMode) RCTLogInfo(@"[RNGeoService] Coarse (significant-change) tracking started");
244
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Coarse (significant-change only) tracking started");
149
245
  } else {
150
246
  [self.locationManager startUpdatingLocation];
151
- if (self.debugMode) RCTLogInfo(@"[RNGeoService] Standard tracking started");
247
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Standard tracking started (+ significant changes for background wake)");
152
248
  }
153
249
 
154
250
  self.isTracking = YES;
155
251
  [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"GeoServiceIsTracking"];
156
252
  [[NSUserDefaults standardUserDefaults] synchronize];
157
253
 
254
+ // Record battery level at tracking start for drain calculation
255
+ [UIDevice currentDevice].batteryMonitoringEnabled = YES;
256
+ self.batteryLevelAtStart = [UIDevice currentDevice].batteryLevel;
257
+
258
+ // Reset session metrics
259
+ self.updateCount = 0;
260
+ self.gpsActiveSeconds = 0;
261
+ self.trackingStartTime = [NSDate date];
262
+ self.gpsActiveStart = [NSDate date]; // GPS starts active
263
+
158
264
  resolve(nil);
159
265
  }
160
266
 
@@ -163,14 +269,12 @@ RCT_EXPORT_METHOD(start:(RCTPromiseResolveBlock)resolve
163
269
  // ---------------------------------------------------------------------------
164
270
  RCT_EXPORT_METHOD(stop:(RCTPromiseResolveBlock)resolve
165
271
  reject:(RCTPromiseRejectBlock)reject) {
166
- if (self.coarseTracking) {
167
- [self.locationManager stopMonitoringSignificantLocationChanges];
168
- } else {
169
- [self.locationManager stopUpdatingLocation];
170
- }
272
+ [self.locationManager stopUpdatingLocation];
273
+ [self.locationManager stopMonitoringSignificantLocationChanges];
171
274
 
172
275
  self.isTracking = NO;
173
276
  [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"GeoServiceIsTracking"];
277
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"GeoServiceConfig"];
174
278
  [[NSUserDefaults standardUserDefaults] synchronize];
175
279
 
176
280
  if (self.debugMode) RCTLogInfo(@"[RNGeoService] Tracking stopped");
@@ -195,6 +299,55 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
195
299
  }
196
300
  }
197
301
 
302
+ // ---------------------------------------------------------------------------
303
+ // getBatteryInfo() / setLocationIndicator()
304
+ // ---------------------------------------------------------------------------
305
+ RCT_EXPORT_METHOD(getBatteryInfo:(RCTPromiseResolveBlock)resolve
306
+ reject:(RCTPromiseRejectBlock)reject) {
307
+ [UIDevice currentDevice].batteryMonitoringEnabled = YES;
308
+ float current = [UIDevice currentDevice].batteryLevel;
309
+ UIDeviceBatteryState state = [UIDevice currentDevice].batteryState;
310
+ BOOL isCharging = state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull;
311
+ float drain = (self.batteryLevelAtStart > 0 && current > 0)
312
+ ? (self.batteryLevelAtStart - current) * 100.0f : 0.0f;
313
+
314
+ // Elapsed session time
315
+ NSTimeInterval elapsed = self.trackingStartTime
316
+ ? [[NSDate date] timeIntervalSinceDate:self.trackingStartTime] : 0;
317
+
318
+ // GPS active = accumulated time + current window (if GPS is on right now)
319
+ NSTimeInterval gpsActive = self.gpsActiveSeconds;
320
+ if (!self.isIdle && self.gpsActiveStart) {
321
+ gpsActive += [[NSDate date] timeIntervalSinceDate:self.gpsActiveStart];
322
+ }
323
+
324
+ double updatesPerMinute = (elapsed > 0)
325
+ ? (self.updateCount / (elapsed / 60.0)) : 0;
326
+ double drainRatePerHour = (elapsed > 0 && drain > 0)
327
+ ? (drain / (elapsed / 3600.0)) : 0;
328
+
329
+ resolve(@{
330
+ @"level": @(current * 100.0f),
331
+ @"isCharging": @(isCharging),
332
+ @"levelAtStart": @(self.batteryLevelAtStart * 100.0f),
333
+ @"drainSinceStart": @(MAX(drain, 0.0f)),
334
+ @"updateCount": @(self.updateCount),
335
+ @"trackingElapsedSeconds": @(elapsed),
336
+ @"gpsActiveSeconds": @(gpsActive),
337
+ @"updatesPerMinute": @(updatesPerMinute),
338
+ @"drainRatePerHour": @(MAX(drainRatePerHour, 0.0))
339
+ });
340
+ }
341
+
342
+ RCT_EXPORT_METHOD(setLocationIndicator:(BOOL)show
343
+ resolve:(RCTPromiseResolveBlock)resolve
344
+ reject:(RCTPromiseRejectBlock)reject) {
345
+ if (@available(iOS 11.0, *)) {
346
+ self.locationManager.showsBackgroundLocationIndicator = show;
347
+ }
348
+ resolve(nil);
349
+ }
350
+
198
351
  // ---------------------------------------------------------------------------
199
352
  // CLLocationManagerDelegate
200
353
  // ---------------------------------------------------------------------------
@@ -203,8 +356,11 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
203
356
  CLLocation *location = [locations lastObject];
204
357
  if (!location) return;
205
358
 
359
+ self.updateCount++;
360
+
206
361
  if (self.debugMode) {
207
- RCTLogInfo(@"[RNGeoService] Location: %f, %f (±%.0fm) speed=%.1fm/s",
362
+ RCTLogInfo(@"[RNGeoService] Location #%ld: %f, %f (±%.0fm) speed=%.1fm/s",
363
+ (long)self.updateCount,
208
364
  location.coordinate.latitude,
209
365
  location.coordinate.longitude,
210
366
  location.horizontalAccuracy,
@@ -215,8 +371,18 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
215
371
  [self evaluateMotionState:location];
216
372
  }
217
373
 
374
+ NSDictionary *locationDict = [self locationToDictionary:location];
375
+
218
376
  if (self.hasListeners) {
219
- [self sendEventWithName:@"onLocation" body:[self locationToDictionary:location]];
377
+ [self sendEventWithName:@"onLocation" body:locationDict];
378
+ } else {
379
+ // Buffer the location — JS listeners haven't attached yet.
380
+ // This is normal during background relaunch: CLLocationManager fires
381
+ // before the React component tree has had time to mount.
382
+ // Events are drained in startObserving() once a listener attaches.
383
+ if (self.pendingLocations.count < 10) {
384
+ [self.pendingLocations addObject:locationDict];
385
+ }
220
386
  }
221
387
  }
222
388
 
@@ -229,6 +395,11 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
229
395
  if (!self.isIdle && self.slowReadingCount >= self.idleSampleCount) {
230
396
  self.isIdle = YES;
231
397
  self.slowReadingCount = 0;
398
+ // Accumulate GPS-on time before going idle
399
+ if (self.gpsActiveStart) {
400
+ self.gpsActiveSeconds += [[NSDate date] timeIntervalSinceDate:self.gpsActiveStart];
401
+ self.gpsActiveStart = nil;
402
+ }
232
403
  // Reduce accuracy — CoreLocation stops requesting GPS
233
404
  self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
234
405
  self.locationManager.distanceFilter = 50.0;
@@ -238,6 +409,7 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
238
409
  if (self.isIdle) {
239
410
  self.isIdle = NO;
240
411
  self.slowReadingCount = 0;
412
+ self.gpsActiveStart = [NSDate date]; // GPS back on
241
413
  [self applyConfigToLocationManager];
242
414
  if (self.debugMode) RCTLogInfo(@"[RNGeoService] Movement detected — accuracy restored");
243
415
  } else {
@@ -248,20 +420,21 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
248
420
 
249
421
  - (void)locationManager:(CLLocationManager *)manager
250
422
  didFailWithError:(NSError *)error {
251
- // kCLErrorLocationUnknown (code 0) is transient — CoreLocation couldn't get a
252
- // fix yet but will keep trying automatically. Silently ignore it so we don't
253
- // surface a noisy error to the app before the GPS has warmed up.
423
+ // kCLErrorLocationUnknown (code 0) is transient — CoreLocation hasn't acquired
424
+ // a fix yet but will keep trying automatically. Silently ignore it.
254
425
  if ([error.domain isEqualToString:kCLErrorDomain] && error.code == kCLErrorLocationUnknown) {
255
426
  if (self.debugMode) RCTLogInfo(@"[RNGeoService] Location unknown (transient) — waiting for GPS fix");
256
427
  return;
257
428
  }
258
429
 
259
- // kCLErrorDenied means the user revoked location permission this is a real
260
- // error worth surfacing, and we should stop tracking to avoid repeated failures.
430
+ // kCLErrorDenied: user revoked permission. Stop everything and clear state.
261
431
  if ([error.domain isEqualToString:kCLErrorDomain] && error.code == kCLErrorDenied) {
262
432
  RCTLogWarn(@"[RNGeoService] Location permission denied — stopping tracking");
263
433
  [self.locationManager stopUpdatingLocation];
264
434
  [self.locationManager stopMonitoringSignificantLocationChanges];
435
+ self.isTracking = NO;
436
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"GeoServiceIsTracking"];
437
+ [[NSUserDefaults standardUserDefaults] synchronize];
265
438
  } else {
266
439
  RCTLogError(@"[RNGeoService] Location error: %@", error.localizedDescription);
267
440
  }
@@ -276,18 +449,38 @@ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
276
449
 
277
450
  - (void)locationManager:(CLLocationManager *)manager
278
451
  didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
279
- if (self.debugMode) RCTLogInfo(@"[RNGeoService] Auth status: %d", status);
452
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Auth status changed: %d", status);
453
+
454
+ // Permission was revoked while tracking — stop and notify JS
455
+ if (status == kCLAuthorizationStatusDenied || status == kCLAuthorizationStatusRestricted) {
456
+ if (self.isTracking) {
457
+ [self.locationManager stopUpdatingLocation];
458
+ [self.locationManager stopMonitoringSignificantLocationChanges];
459
+ self.isTracking = NO;
460
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"GeoServiceIsTracking"];
461
+ [[NSUserDefaults standardUserDefaults] synchronize];
462
+ if (self.hasListeners) {
463
+ [self sendEventWithName:@"onError" body:@{
464
+ @"code": @(kCLErrorDenied),
465
+ @"message": @"Location permission was revoked. Please re-enable in Settings."
466
+ }];
467
+ }
468
+ }
469
+ return;
470
+ }
280
471
 
281
- // Resume tracking after background relaunch (e.g. significant location change)
282
- if (status == kCLAuthorizationStatusAuthorizedAlways &&
472
+ // Permission granted after background relaunch resume if we were tracking before
473
+ if ((status == kCLAuthorizationStatusAuthorizedAlways ||
474
+ status == kCLAuthorizationStatusAuthorizedWhenInUse) &&
475
+ !self.isTracking &&
283
476
  [[NSUserDefaults standardUserDefaults] boolForKey:@"GeoServiceIsTracking"]) {
284
477
  [self applyConfigToLocationManager];
285
- if (self.coarseTracking) {
286
- [self.locationManager startMonitoringSignificantLocationChanges];
287
- } else {
478
+ [self.locationManager startMonitoringSignificantLocationChanges];
479
+ if (!self.coarseTracking) {
288
480
  [self.locationManager startUpdatingLocation];
289
481
  }
290
482
  self.isTracking = YES;
483
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Tracking resumed after auth grant");
291
484
  }
292
485
  }
293
486
 
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ /**
3
+ * Drop this anywhere in your component tree once.
4
+ * It renders the GeoDebugPanel automatically when:
5
+ * - debug: true was passed to configure()
6
+ * - location tracking is active (implies permission was granted)
7
+ * Nothing is rendered if debug is false or tracking hasn't started.
8
+ */
9
+ export declare const GeoDebugOverlay: React.FC;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.GeoDebugOverlay = void 0;
49
+ const react_1 = __importStar(require("react"));
50
+ const index_1 = __importDefault(require("./index"));
51
+ const index_2 = require("./index");
52
+ const GeoDebugPanel_1 = require("./GeoDebugPanel");
53
+ /**
54
+ * Drop this anywhere in your component tree once.
55
+ * It renders the GeoDebugPanel automatically when:
56
+ * - debug: true was passed to configure()
57
+ * - location tracking is active (implies permission was granted)
58
+ * Nothing is rendered if debug is false or tracking hasn't started.
59
+ */
60
+ const GeoDebugOverlay = () => {
61
+ const [visible, setVisible] = (0, react_1.useState)(false);
62
+ (0, react_1.useEffect)(() => {
63
+ let cancelled = false;
64
+ const check = () => __awaiter(void 0, void 0, void 0, function* () {
65
+ try {
66
+ const tracking = yield index_1.default.isTracking();
67
+ if (!cancelled)
68
+ setVisible((0, index_2._isDebugMode)() && tracking);
69
+ }
70
+ catch (_) {
71
+ if (!cancelled)
72
+ setVisible(false);
73
+ }
74
+ });
75
+ check();
76
+ const id = setInterval(check, 3000);
77
+ return () => {
78
+ cancelled = true;
79
+ clearInterval(id);
80
+ };
81
+ }, []);
82
+ if (!visible)
83
+ return null;
84
+ return <GeoDebugPanel_1.GeoDebugPanel />;
85
+ };
86
+ exports.GeoDebugOverlay = GeoDebugOverlay;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ /** Refresh interval in ms. Default: 30000 */
4
+ pollInterval?: number;
5
+ }
6
+ export declare const GeoDebugPanel: React.FC<Props>;
7
+ export {};