@tsachit/react-native-geo-service 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.
@@ -0,0 +1,311 @@
1
+ #import "RNGeoService.h"
2
+ #import <React/RCTLog.h>
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // CLActivityType helper
6
+ // ---------------------------------------------------------------------------
7
+ static CLActivityType activityTypeFromString(NSString *type) {
8
+ if ([type isEqualToString:@"automotiveNavigation"]) return CLActivityTypeAutomotiveNavigation;
9
+ if ([type isEqualToString:@"fitness"]) return CLActivityTypeFitness;
10
+ if ([type isEqualToString:@"otherNavigation"]) return CLActivityTypeOtherNavigation;
11
+ if (@available(iOS 12.0, *)) {
12
+ if ([type isEqualToString:@"airborne"]) return CLActivityTypeAirborne;
13
+ }
14
+ return CLActivityTypeOther;
15
+ }
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // CLLocationAccuracy helper
19
+ // ---------------------------------------------------------------------------
20
+ static CLLocationAccuracy accuracyFromString(NSString *accuracy) {
21
+ if ([accuracy isEqualToString:@"navigation"]) return kCLLocationAccuracyBestForNavigation;
22
+ if ([accuracy isEqualToString:@"high"]) return kCLLocationAccuracyBest;
23
+ if ([accuracy isEqualToString:@"low"]) return kCLLocationAccuracyKilometer;
24
+ // "balanced" → nearest 100 metres — good trade-off between precision and battery
25
+ return kCLLocationAccuracyHundredMeters;
26
+ }
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // RNGeoService implementation
30
+ // ---------------------------------------------------------------------------
31
+ @interface RNGeoService ()
32
+
33
+ @property (nonatomic, strong) CLLocationManager *locationManager;
34
+ @property (nonatomic, strong) NSDictionary *config;
35
+
36
+ @property (nonatomic, assign) BOOL isTracking;
37
+ @property (nonatomic, assign) BOOL hasListeners;
38
+ @property (nonatomic, assign) BOOL coarseTracking;
39
+ @property (nonatomic, assign) BOOL debugMode;
40
+
41
+ // Adaptive accuracy state
42
+ @property (nonatomic, assign) BOOL adaptiveAccuracy;
43
+ @property (nonatomic, assign) float idleSpeedThreshold;
44
+ @property (nonatomic, assign) NSInteger idleSampleCount;
45
+ @property (nonatomic, assign) NSInteger slowReadingCount;
46
+ @property (nonatomic, assign) BOOL isIdle;
47
+
48
+ @end
49
+
50
+ @implementation RNGeoService
51
+
52
+ RCT_EXPORT_MODULE();
53
+
54
+ + (BOOL)requiresMainQueueSetup {
55
+ return YES;
56
+ }
57
+
58
+ - (dispatch_queue_t)methodQueue {
59
+ return dispatch_get_main_queue();
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Supported events
64
+ // ---------------------------------------------------------------------------
65
+ - (NSArray<NSString *> *)supportedEvents {
66
+ return @[@"onLocation", @"onError"];
67
+ }
68
+
69
+ - (void)startObserving { self.hasListeners = YES; }
70
+ - (void)stopObserving { self.hasListeners = NO; }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Lazy CLLocationManager
74
+ // ---------------------------------------------------------------------------
75
+ - (CLLocationManager *)locationManager {
76
+ if (!_locationManager) {
77
+ _locationManager = [[CLLocationManager alloc] init];
78
+ _locationManager.delegate = self;
79
+ }
80
+ return _locationManager;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // configure()
85
+ // ---------------------------------------------------------------------------
86
+ RCT_EXPORT_METHOD(configure:(NSDictionary *)options
87
+ resolve:(RCTPromiseResolveBlock)resolve
88
+ reject:(RCTPromiseRejectBlock)reject) {
89
+ self.config = options;
90
+ self.coarseTracking = [options[@"coarseTracking"] boolValue];
91
+ self.debugMode = [options[@"debug"] boolValue];
92
+ self.adaptiveAccuracy = options[@"adaptiveAccuracy"] ? [options[@"adaptiveAccuracy"] boolValue] : YES;
93
+ self.idleSpeedThreshold = options[@"idleSpeedThreshold"] ? [options[@"idleSpeedThreshold"] floatValue] : 0.5f;
94
+ self.idleSampleCount = options[@"idleSampleCount"] ? [options[@"idleSampleCount"] integerValue] : 3;
95
+ self.slowReadingCount = 0;
96
+ self.isIdle = NO;
97
+
98
+ [self applyConfigToLocationManager];
99
+
100
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Config applied: %@", options);
101
+ resolve(nil);
102
+ }
103
+
104
+ - (void)applyConfigToLocationManager {
105
+ NSDictionary *cfg = self.config ?: @{};
106
+
107
+ NSString *accuracy = cfg[@"accuracy"] ?: @"balanced";
108
+ self.locationManager.desiredAccuracy = accuracyFromString(accuracy);
109
+
110
+ double minDist = [cfg[@"minDistanceMeters"] doubleValue];
111
+ self.locationManager.distanceFilter = (minDist > 0) ? minDist : kCLDistanceFilterNone;
112
+
113
+ NSString *motionActivity = cfg[@"motionActivity"] ?: @"other";
114
+ self.locationManager.activityType = activityTypeFromString(motionActivity);
115
+
116
+ BOOL autoPause = [cfg[@"autoPauseUpdates"] boolValue];
117
+ self.locationManager.pausesLocationUpdatesAutomatically = autoPause;
118
+
119
+ if (@available(iOS 11.0, *)) {
120
+ BOOL bgIndicator = [cfg[@"showBackgroundIndicator"] boolValue];
121
+ self.locationManager.showsBackgroundLocationIndicator = bgIndicator;
122
+ }
123
+
124
+ self.locationManager.allowsBackgroundLocationUpdates = YES;
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // start()
129
+ // ---------------------------------------------------------------------------
130
+ RCT_EXPORT_METHOD(start:(RCTPromiseResolveBlock)resolve
131
+ reject:(RCTPromiseRejectBlock)reject) {
132
+ CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
133
+
134
+ if (status == kCLAuthorizationStatusDenied ||
135
+ status == kCLAuthorizationStatusRestricted) {
136
+ reject(@"PERMISSION_DENIED", @"Location permission denied. Request 'Always' permission before calling start().", nil);
137
+ return;
138
+ }
139
+
140
+ if (status == kCLAuthorizationStatusNotDetermined) {
141
+ [self.locationManager requestAlwaysAuthorization];
142
+ }
143
+
144
+ [self applyConfigToLocationManager];
145
+
146
+ if (self.coarseTracking) {
147
+ [self.locationManager startMonitoringSignificantLocationChanges];
148
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Coarse (significant-change) tracking started");
149
+ } else {
150
+ [self.locationManager startUpdatingLocation];
151
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Standard tracking started");
152
+ }
153
+
154
+ self.isTracking = YES;
155
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"GeoServiceIsTracking"];
156
+ [[NSUserDefaults standardUserDefaults] synchronize];
157
+
158
+ resolve(nil);
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // stop()
163
+ // ---------------------------------------------------------------------------
164
+ RCT_EXPORT_METHOD(stop:(RCTPromiseResolveBlock)resolve
165
+ reject:(RCTPromiseRejectBlock)reject) {
166
+ if (self.coarseTracking) {
167
+ [self.locationManager stopMonitoringSignificantLocationChanges];
168
+ } else {
169
+ [self.locationManager stopUpdatingLocation];
170
+ }
171
+
172
+ self.isTracking = NO;
173
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"GeoServiceIsTracking"];
174
+ [[NSUserDefaults standardUserDefaults] synchronize];
175
+
176
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Tracking stopped");
177
+ resolve(nil);
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // isTracking() / getCurrentLocation()
182
+ // ---------------------------------------------------------------------------
183
+ RCT_EXPORT_METHOD(isTracking:(RCTPromiseResolveBlock)resolve
184
+ reject:(RCTPromiseRejectBlock)reject) {
185
+ resolve(@(self.isTracking));
186
+ }
187
+
188
+ RCT_EXPORT_METHOD(getCurrentLocation:(RCTPromiseResolveBlock)resolve
189
+ reject:(RCTPromiseRejectBlock)reject) {
190
+ CLLocation *last = self.locationManager.location;
191
+ if (last) {
192
+ resolve([self locationToDictionary:last]);
193
+ } else {
194
+ reject(@"NO_LOCATION", @"No cached location available. Call start() first.", nil);
195
+ }
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // CLLocationManagerDelegate
200
+ // ---------------------------------------------------------------------------
201
+ - (void)locationManager:(CLLocationManager *)manager
202
+ didUpdateLocations:(NSArray<CLLocation *> *)locations {
203
+ CLLocation *location = [locations lastObject];
204
+ if (!location) return;
205
+
206
+ if (self.debugMode) {
207
+ RCTLogInfo(@"[RNGeoService] Location: %f, %f (±%.0fm) speed=%.1fm/s",
208
+ location.coordinate.latitude,
209
+ location.coordinate.longitude,
210
+ location.horizontalAccuracy,
211
+ location.speed);
212
+ }
213
+
214
+ if (self.adaptiveAccuracy && !self.coarseTracking) {
215
+ [self evaluateMotionState:location];
216
+ }
217
+
218
+ if (self.hasListeners) {
219
+ [self sendEventWithName:@"onLocation" body:[self locationToDictionary:location]];
220
+ }
221
+ }
222
+
223
+ - (void)evaluateMotionState:(CLLocation *)location {
224
+ // speed is -1 when unavailable (e.g. first fix from cache) — treat as moving
225
+ float speed = (location.speed >= 0) ? (float)location.speed : 1.0f;
226
+
227
+ if (speed < self.idleSpeedThreshold) {
228
+ self.slowReadingCount++;
229
+ if (!self.isIdle && self.slowReadingCount >= self.idleSampleCount) {
230
+ self.isIdle = YES;
231
+ self.slowReadingCount = 0;
232
+ // Reduce accuracy — CoreLocation stops requesting GPS
233
+ self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
234
+ self.locationManager.distanceFilter = 50.0;
235
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Device idle — GPS off");
236
+ }
237
+ } else {
238
+ if (self.isIdle) {
239
+ self.isIdle = NO;
240
+ self.slowReadingCount = 0;
241
+ [self applyConfigToLocationManager];
242
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Movement detected — accuracy restored");
243
+ } else {
244
+ self.slowReadingCount = 0;
245
+ }
246
+ }
247
+ }
248
+
249
+ - (void)locationManager:(CLLocationManager *)manager
250
+ 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.
254
+ if ([error.domain isEqualToString:kCLErrorDomain] && error.code == kCLErrorLocationUnknown) {
255
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Location unknown (transient) — waiting for GPS fix");
256
+ return;
257
+ }
258
+
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.
261
+ if ([error.domain isEqualToString:kCLErrorDomain] && error.code == kCLErrorDenied) {
262
+ RCTLogWarn(@"[RNGeoService] Location permission denied — stopping tracking");
263
+ [self.locationManager stopUpdatingLocation];
264
+ [self.locationManager stopMonitoringSignificantLocationChanges];
265
+ } else {
266
+ RCTLogError(@"[RNGeoService] Location error: %@", error.localizedDescription);
267
+ }
268
+
269
+ if (self.hasListeners) {
270
+ [self sendEventWithName:@"onError" body:@{
271
+ @"code": @(error.code),
272
+ @"message": error.localizedDescription ?: @"Unknown location error"
273
+ }];
274
+ }
275
+ }
276
+
277
+ - (void)locationManager:(CLLocationManager *)manager
278
+ didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
279
+ if (self.debugMode) RCTLogInfo(@"[RNGeoService] Auth status: %d", status);
280
+
281
+ // Resume tracking after background relaunch (e.g. significant location change)
282
+ if (status == kCLAuthorizationStatusAuthorizedAlways &&
283
+ [[NSUserDefaults standardUserDefaults] boolForKey:@"GeoServiceIsTracking"]) {
284
+ [self applyConfigToLocationManager];
285
+ if (self.coarseTracking) {
286
+ [self.locationManager startMonitoringSignificantLocationChanges];
287
+ } else {
288
+ [self.locationManager startUpdatingLocation];
289
+ }
290
+ self.isTracking = YES;
291
+ }
292
+ }
293
+
294
+ // ---------------------------------------------------------------------------
295
+ // Helpers
296
+ // ---------------------------------------------------------------------------
297
+ - (NSDictionary *)locationToDictionary:(CLLocation *)location {
298
+ return @{
299
+ @"latitude": @(location.coordinate.latitude),
300
+ @"longitude": @(location.coordinate.longitude),
301
+ @"accuracy": @(location.horizontalAccuracy),
302
+ @"altitude": @(location.altitude),
303
+ @"altitudeAccuracy": @(location.verticalAccuracy),
304
+ @"speed": @(location.speed),
305
+ @"bearing": @(location.course),
306
+ @"timestamp": @((long long)(location.timestamp.timeIntervalSince1970 * 1000)),
307
+ @"isStationary": @(self.isIdle)
308
+ };
309
+ }
310
+
311
+ @end
package/lib/index.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { GeoServiceConfig, GeoSubscription, Location, LocationCallback, ErrorCallback } from './types';
2
+ export * from './types';
3
+ /**
4
+ * Configure the geo service. Call this before start().
5
+ * Safe to call multiple times; subsequent calls update the config.
6
+ */
7
+ declare function configure(config: GeoServiceConfig): Promise<void>;
8
+ /**
9
+ * Start background location tracking.
10
+ * On Android, this starts a foreground service with a persistent notification.
11
+ * On iOS, this starts standard or significant-change location monitoring.
12
+ */
13
+ declare function start(): Promise<void>;
14
+ /**
15
+ * Stop background location tracking.
16
+ */
17
+ declare function stop(): Promise<void>;
18
+ /**
19
+ * Fetch the current device location as a one-time request.
20
+ */
21
+ declare function getCurrentLocation(): Promise<Location>;
22
+ /**
23
+ * Returns whether the geo service is currently tracking.
24
+ */
25
+ declare function isTracking(): Promise<boolean>;
26
+ /**
27
+ * Subscribe to location updates.
28
+ * Returns a GeoSubscription — call .remove() to unsubscribe.
29
+ *
30
+ * @example
31
+ * const sub = RNGeoService.onLocation((location) => {
32
+ * console.log(location.latitude, location.longitude);
33
+ * });
34
+ * // Later:
35
+ * sub.remove();
36
+ */
37
+ declare function onLocation(callback: LocationCallback): GeoSubscription;
38
+ /**
39
+ * Subscribe to location errors.
40
+ */
41
+ declare function onError(callback: ErrorCallback): GeoSubscription;
42
+ /**
43
+ * Register a headless task handler for Android background processing.
44
+ *
45
+ * When the app is not in the foreground, location updates are delivered via
46
+ * HeadlessJS. Register your handler here OR in your app's index.js using
47
+ * AppRegistry.registerHeadlessTask('GeoServiceHeadlessTask', ...).
48
+ *
49
+ * The handler receives a Location object and should return a Promise.
50
+ *
51
+ * @example
52
+ * // In index.js (outside the App component, at the top level):
53
+ * import { AppRegistry } from 'react-native';
54
+ * AppRegistry.registerHeadlessTask('GeoServiceHeadlessTask', () => async (location) => {
55
+ * console.log('[Headless] Location:', location);
56
+ * // Send to your server using a pre-stored auth token (e.g. SecureStore/Keychain).
57
+ * // Do not rely on in-memory app state — this context is headless and isolated.
58
+ * });
59
+ *
60
+ * @platform android
61
+ */
62
+ declare function registerHeadlessTask(handler: (location: Location) => Promise<void>): void;
63
+ declare const RNGeoService: {
64
+ configure: typeof configure;
65
+ start: typeof start;
66
+ stop: typeof stop;
67
+ getCurrentLocation: typeof getCurrentLocation;
68
+ isTracking: typeof isTracking;
69
+ onLocation: typeof onLocation;
70
+ onError: typeof onError;
71
+ registerHeadlessTask: typeof registerHeadlessTask;
72
+ };
73
+ export default RNGeoService;
package/lib/index.js ADDED
@@ -0,0 +1,155 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
17
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
18
+ return new (P || (P = Promise))(function (resolve, reject) {
19
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
22
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
23
+ });
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ const react_native_1 = require("react-native");
27
+ __exportStar(require("./types"), exports);
28
+ // The native bridge module registered as "RNGeoService" on both platforms
29
+ const nativeModule = react_native_1.NativeModules.RNGeoService;
30
+ if (!nativeModule) {
31
+ throw new Error('[react-native-geo-service] Native module not found. ' +
32
+ 'Make sure you have linked the native module correctly. ' +
33
+ 'For iOS run `pod install`, for Android rebuild the project.');
34
+ }
35
+ const eventEmitter = new react_native_1.NativeEventEmitter(nativeModule);
36
+ const DEFAULT_CONFIG = {
37
+ minDistanceMeters: 10,
38
+ accuracy: 'balanced',
39
+ stopOnAppClose: false,
40
+ restartOnBoot: false,
41
+ updateIntervalMs: 5000,
42
+ minUpdateIntervalMs: 2000,
43
+ serviceTitle: 'Location Tracking',
44
+ serviceBody: 'Your location is being tracked in the background.',
45
+ backgroundTaskName: 'GeoServiceHeadlessTask',
46
+ motionActivity: 'other',
47
+ autoPauseUpdates: false,
48
+ showBackgroundIndicator: false,
49
+ coarseTracking: false,
50
+ adaptiveAccuracy: true,
51
+ idleSpeedThreshold: 0.5,
52
+ idleSampleCount: 3,
53
+ debug: false,
54
+ };
55
+ /**
56
+ * Configure the geo service. Call this before start().
57
+ * Safe to call multiple times; subsequent calls update the config.
58
+ */
59
+ function configure(config) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ const merged = Object.assign(Object.assign({}, DEFAULT_CONFIG), config);
62
+ return nativeModule.configure(merged);
63
+ });
64
+ }
65
+ /**
66
+ * Start background location tracking.
67
+ * On Android, this starts a foreground service with a persistent notification.
68
+ * On iOS, this starts standard or significant-change location monitoring.
69
+ */
70
+ function start() {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ return nativeModule.start();
73
+ });
74
+ }
75
+ /**
76
+ * Stop background location tracking.
77
+ */
78
+ function stop() {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ return nativeModule.stop();
81
+ });
82
+ }
83
+ /**
84
+ * Fetch the current device location as a one-time request.
85
+ */
86
+ function getCurrentLocation() {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ return nativeModule.getCurrentLocation();
89
+ });
90
+ }
91
+ /**
92
+ * Returns whether the geo service is currently tracking.
93
+ */
94
+ function isTracking() {
95
+ return __awaiter(this, void 0, void 0, function* () {
96
+ return nativeModule.isTracking();
97
+ });
98
+ }
99
+ /**
100
+ * Subscribe to location updates.
101
+ * Returns a GeoSubscription — call .remove() to unsubscribe.
102
+ *
103
+ * @example
104
+ * const sub = RNGeoService.onLocation((location) => {
105
+ * console.log(location.latitude, location.longitude);
106
+ * });
107
+ * // Later:
108
+ * sub.remove();
109
+ */
110
+ function onLocation(callback) {
111
+ return eventEmitter.addListener('onLocation', callback);
112
+ }
113
+ /**
114
+ * Subscribe to location errors.
115
+ */
116
+ function onError(callback) {
117
+ return eventEmitter.addListener('onError', callback);
118
+ }
119
+ /**
120
+ * Register a headless task handler for Android background processing.
121
+ *
122
+ * When the app is not in the foreground, location updates are delivered via
123
+ * HeadlessJS. Register your handler here OR in your app's index.js using
124
+ * AppRegistry.registerHeadlessTask('GeoServiceHeadlessTask', ...).
125
+ *
126
+ * The handler receives a Location object and should return a Promise.
127
+ *
128
+ * @example
129
+ * // In index.js (outside the App component, at the top level):
130
+ * import { AppRegistry } from 'react-native';
131
+ * AppRegistry.registerHeadlessTask('GeoServiceHeadlessTask', () => async (location) => {
132
+ * console.log('[Headless] Location:', location);
133
+ * // Send to your server using a pre-stored auth token (e.g. SecureStore/Keychain).
134
+ * // Do not rely on in-memory app state — this context is headless and isolated.
135
+ * });
136
+ *
137
+ * @platform android
138
+ */
139
+ function registerHeadlessTask(handler) {
140
+ if (react_native_1.Platform.OS !== 'android')
141
+ return;
142
+ const taskName = DEFAULT_CONFIG.backgroundTaskName;
143
+ react_native_1.AppRegistry.registerHeadlessTask(taskName, () => handler);
144
+ }
145
+ const RNGeoService = {
146
+ configure,
147
+ start,
148
+ stop,
149
+ getCurrentLocation,
150
+ isTracking,
151
+ onLocation,
152
+ onError,
153
+ registerHeadlessTask,
154
+ };
155
+ exports.default = RNGeoService;
package/lib/types.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ export interface GeoServiceConfig {
2
+ /**
3
+ * Minimum distance in meters the device must move before a location update is fired.
4
+ * Higher values = fewer updates = better battery life.
5
+ * Default: 10
6
+ */
7
+ minDistanceMeters?: number;
8
+ /**
9
+ * Location accuracy mode.
10
+ * - 'navigation': GPS-level accuracy (most battery)
11
+ * - 'high': High accuracy
12
+ * - 'balanced': City-block accuracy, uses cell/WiFi (recommended for most apps)
13
+ * - 'low': Approximate location, very low battery usage
14
+ * Default: 'balanced'
15
+ */
16
+ accuracy?: 'navigation' | 'high' | 'balanced' | 'low';
17
+ /**
18
+ * Stop location tracking when the app is closed by the user.
19
+ * Set to false for always-on headless tracking.
20
+ * Default: false
21
+ */
22
+ stopOnAppClose?: boolean;
23
+ /**
24
+ * Automatically restart tracking on device reboot (Android only).
25
+ * Default: false
26
+ */
27
+ restartOnBoot?: boolean;
28
+ /**
29
+ * Target time interval between location updates in milliseconds (Android only).
30
+ * Default: 5000
31
+ */
32
+ updateIntervalMs?: number;
33
+ /**
34
+ * Minimum time between location updates in milliseconds (Android only).
35
+ * Updates will never arrive faster than this value.
36
+ * Default: 2000
37
+ */
38
+ minUpdateIntervalMs?: number;
39
+ /**
40
+ * Title of the persistent foreground service notification (Android only).
41
+ * Default: 'Location Tracking'
42
+ */
43
+ serviceTitle?: string;
44
+ /**
45
+ * Body text of the persistent foreground service notification (Android only).
46
+ * Default: 'Your location is being tracked in the background.'
47
+ */
48
+ serviceBody?: string;
49
+ /**
50
+ * Name of the HeadlessJS task to invoke when the app is not in the foreground (Android only).
51
+ * Register this task in your app's index.js using AppRegistry.registerHeadlessTask().
52
+ * Default: 'GeoServiceHeadlessTask'
53
+ */
54
+ backgroundTaskName?: string;
55
+ /**
56
+ * Hint to the OS about what kind of motion this location data is used for (iOS only).
57
+ * Allows CoreLocation to apply activity-specific power optimisations.
58
+ * Default: 'other'
59
+ */
60
+ motionActivity?: 'other' | 'automotiveNavigation' | 'fitness' | 'otherNavigation' | 'airborne';
61
+ /**
62
+ * Allow iOS to automatically pause location updates when no movement is detected.
63
+ * Set to false to always receive updates.
64
+ * Default: false
65
+ */
66
+ autoPauseUpdates?: boolean;
67
+ /**
68
+ * Show the blue location indicator in the iOS status bar when tracking in background.
69
+ * Default: false
70
+ */
71
+ showBackgroundIndicator?: boolean;
72
+ /**
73
+ * Use Significant Location Changes instead of standard location updates (iOS only).
74
+ * Much more battery efficient — only fires when the device moves ~500m.
75
+ * Wakes the app even if it was terminated.
76
+ * Default: false
77
+ */
78
+ coarseTracking?: boolean;
79
+ /**
80
+ * Automatically drop to low-power mode when the device appears stationary,
81
+ * and restore the configured accuracy the moment movement is detected again.
82
+ *
83
+ * On Android this turns the GPS chip completely off while parked.
84
+ * On iOS this reduces accuracy to kCLLocationAccuracyKilometer while still.
85
+ *
86
+ * This is the single biggest battery saving for apps that track driving/walking —
87
+ * GPS stays off while the user is parked or sitting still.
88
+ * Default: true
89
+ */
90
+ adaptiveAccuracy?: boolean;
91
+ /**
92
+ * Speed in m/s below which a reading is counted as "idle/stationary".
93
+ * Default: 0.5 (~1.8 km/h)
94
+ */
95
+ idleSpeedThreshold?: number;
96
+ /**
97
+ * Number of consecutive idle readings required before entering low-power mode.
98
+ * Higher = fewer false positives but slower to power down.
99
+ * Default: 3
100
+ */
101
+ idleSampleCount?: number;
102
+ /**
103
+ * Enable verbose native logging.
104
+ * Default: false
105
+ */
106
+ debug?: boolean;
107
+ }
108
+ export interface Location {
109
+ latitude: number;
110
+ longitude: number;
111
+ /** Horizontal accuracy in meters */
112
+ accuracy: number;
113
+ altitude: number;
114
+ /** Vertical accuracy in meters (iOS only, -1 on Android) */
115
+ altitudeAccuracy: number;
116
+ /** Speed in meters per second, -1 if unavailable */
117
+ speed: number;
118
+ /** Bearing/heading in degrees (0–360), -1 if unavailable */
119
+ bearing: number;
120
+ /** Unix timestamp in milliseconds */
121
+ timestamp: number;
122
+ /** Whether this location came from a mock provider (Android only) */
123
+ isFromMockProvider?: boolean;
124
+ /** True when adaptive accuracy has detected the device is idle and GPS is off */
125
+ isStationary?: boolean;
126
+ }
127
+ export interface LocationError {
128
+ code: number;
129
+ message: string;
130
+ }
131
+ export type LocationCallback = (location: Location) => void;
132
+ export type ErrorCallback = (error: LocationError) => void;
133
+ /**
134
+ * Returned by onLocation() and onError().
135
+ * Call .remove() to stop receiving updates and clean up the listener.
136
+ */
137
+ export interface GeoSubscription {
138
+ remove(): void;
139
+ }
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });