react-native-background-geolocation 5.5.0 → 5.7.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 (33) hide show
  1. package/RNBackgroundGeolocation.podspec +1 -1
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/transistorsoft/rnbackgroundgeolocation/HeadlessTask.java +10 -1
  4. package/android/src/main/java/com/transistorsoft/rnbackgroundgeolocation/HeadlessTaskManager.java +6 -15
  5. package/android/src/main/java/com/transistorsoft/rnbackgroundgeolocation/RNBackgroundGeolocationModule.java +87 -35
  6. package/expo/plugin/tsconfig.tsbuildinfo +1 -1
  7. package/ios/RNBackgroundGeolocation/RNBackgroundGeolocation.mm +30 -12
  8. package/package.json +14 -9
  9. package/src/NativeModule.js +7 -4
  10. package/src/TransistorAuthorizationService.js +4 -1
  11. package/src/index.js +71 -9
  12. package/src/native/specs/NativeBackgroundGeolocation.ts +1 -1
  13. package/src/specs/NativeRNBackgroundGeolocation.js +3 -3
  14. package/.github/ISSUE_TEMPLATE/1-bug.yml +0 -80
  15. package/.github/ISSUE_TEMPLATE/2-help-wanted.yml +0 -82
  16. package/.github/workflows/stale.yaml +0 -25
  17. package/babel.config.js +0 -5
  18. package/jest.config.js +0 -14
  19. package/mocks/react-native.js +0 -55
  20. package/tests/enums-bridge-test.js +0 -16
  21. package/tests/enums.test.js +0 -23
  22. package/tests/insert-location.test.js +0 -51
  23. package/tests/location-filter-event.test.js +0 -30
  24. package/tests/ready-compound.test.js +0 -28
  25. package/tests/ready.test.js +0 -6
  26. package/tests-types/api-surface.ts +0 -0
  27. package/tests-types/config-surface.ts +0 -38
  28. package/tests-types/enums-smoke-test.ts +0 -35
  29. package/tests-types/events-typing.ts +0 -23
  30. package/tests-types/flat-config.ts +0 -0
  31. package/tests-types/ok.ts +0 -20
  32. package/tests-types/tsconfig.json +0 -14
  33. package/typedoc.json +0 -49
@@ -26,7 +26,7 @@ Pod::Spec.new do |s|
26
26
  s.source_files = 'ios/RNBackgroundGeolocation/*.{h,m,mm}'
27
27
  s.preserve_paths = 'docs', 'CHANGELOG.md', 'LICENSE', 'package.json', 'RNBackgroundGeolocation.ios.js'
28
28
 
29
- tslm_version = ENV['TSLOCATIONMANAGER_VERSION'] || '~> 4.5.0'
29
+ tslm_version = ENV['TSLOCATIONMANAGER_VERSION'] || '~> 4.7.1'
30
30
  s.dependency 'TSLocationManager', tslm_version
31
31
 
32
32
  s.libraries = 'sqlite3', 'z', 'stdc++'
@@ -9,7 +9,7 @@ def DEFAULT_TARGET_SDK_VERSION = 35
9
9
 
10
10
  // Plugin dependencies
11
11
  def DEFAULT_PLAY_SERVICES_LOCATION_VERSION = "21.3.0"
12
- def DEFAULT_TSLOCATIONMANAGER_VERSION = "4.5.+"
12
+ def DEFAULT_TSLOCATIONMANAGER_VERSION = "4.6.+"
13
13
  def DEFAULT_OK_HTTP_VERSION = "4.12.0"
14
14
  def DEFAULT_ANDROID_PERMISSIONS_VERSION = "2.1.6"
15
15
  def DEFAULT_EVENTBUS_VERSION = "3.3.1"
@@ -27,7 +27,7 @@ import java.util.Map;
27
27
 
28
28
  public class HeadlessTask {
29
29
  private static final String HEADLESS_TASK_NAME = "BackgroundGeolocation";
30
- // Hard-coded time-limit for headless-tasks is 60000 @todo configurable?
30
+ // Hard-coded time-limit for headless-tasks is 120s @todo configurable?
31
31
  private static final int TASK_TIMEOUT = 60000 * 2;
32
32
 
33
33
  /**
@@ -49,6 +49,15 @@ public class HeadlessTask {
49
49
  params = config.toMap(false);
50
50
  } else if (name.equals(EventName.LOCATION)) {
51
51
  params = event.getLocationEvent().toMap();
52
+ } else if (name.equals(EventName.LOCATION_ERROR)) {
53
+ // EventManager normalizes LocationErrorEvent to its Integer error-code — the same value
54
+ // the foreground onLocation failure-callback receives.
55
+ Object error = event.getEvent();
56
+ if (error instanceof Integer) {
57
+ clientEvent.putInt("params", (Integer) error);
58
+ } else {
59
+ clientEvent.putNull("params");
60
+ }
52
61
  } else if (name.equals(EventName.MOTIONCHANGE)) {
53
62
  params = event.getMotionChangeEvent().toMap();
54
63
  } else if (name.equals(EventName.HTTP)) {
@@ -43,23 +43,14 @@ import java.util.concurrent.atomic.AtomicInteger;
43
43
  * might build up in the queue before the Host is finally launched, when we drain the queue (see #drainTaskQueue).
44
44
  *
45
45
  * For finally sending events to the client, we wrap the RN HeadlessJsTaskConfig with our own TaskConfig class. This class
46
- * adds our own auto-incremented "taskId" field to maintain a mapping between our taskId and RN's. See #invokeStartTask.
47
- * This class appends our custom taskId into the the event params sent to Javascript, for the following purpose:
46
+ * adds our own auto-incremented "_transistorHeadlessTaskId" field to maintain a mapping between our taskId and RN's. See #invokeStartTask.
48
47
  *
49
- * ```javascript
50
- * const BackgroundGeolocationHeadlessTask = async (event) => {
51
- * console.log('[HeadlessTask] taskId: ', event.taskId); // <-- here's our custom taskId.
48
+ * The client never sees it: the wrapper installed by BackgroundGeolocation.registerHeadlessTask (src/index.js) removes
49
+ * _transistorHeadlessTaskId from the event before calling the developer's task, then, once the task's Promise settles,
50
+ * signals back to this native HeadlessTask instance that the JS task is complete: BackgroundGeolocation.finishHeadlessTask(taskId).
51
+ * This is a pretty easy task to do via EventBus -- Just create a new instance of FinishHeadlessTaskEvent(taskId);
52
52
  *
53
- * await doWork(); // <-- perform some arbitrarily long process (eg: http request).
54
- *
55
- * BackgroundGeolocation.finishHeadlessTask(event.taskId); // <-- $$$ Here's the money $$$
56
- * }
57
- * ```
58
- *
59
- * See "Here's the $money" above: We want to signal back to this native HeadlessTask instance , that our JS task is now complete.
60
- * This is a pretty easy task to do via EventBus -- Just create a new instance of FinishHeadlessTaskEvent(params.taskId);
61
- *
62
- * The code then looks up a TaskConfig from mEventQueue using the given event.taskId.
53
+ * The code then looks up a TaskConfig from mEventQueue using the given taskId.
63
54
  *
64
55
  * All this extra fussing with taking care to finish our RN HeadlessTasks seems to be more important with RN's "new architecture", where before,
65
56
  * RN seemed to automatically complete its tasks seemingly when the Javascript function stopped executing. This is why it was always so
@@ -4,6 +4,8 @@ import com.transistorsoft.rnbackgroundgeolocation.NativeRNBackgroundGeolocationS
4
4
 
5
5
  import android.Manifest;
6
6
  import android.app.Activity;
7
+
8
+ import androidx.annotation.Nullable;
7
9
  import android.content.Context;
8
10
  import android.content.Intent;
9
11
  import android.os.Build;
@@ -100,6 +102,10 @@ public class RNBackgroundGeolocationModule
100
102
  private boolean mInitialized = false;
101
103
  private boolean mReady = false;
102
104
 
105
+ // Every SDK callback this module registered, so a re-registration can close the previous one.
106
+ private final List<AutoCloseable> mSubscriptions = new ArrayList<>();
107
+ private AutoCloseable mPlayServicesSubscription;
108
+
103
109
  // Map of event listener-counts
104
110
  private final HashMap<String, Integer> mListeners = new HashMap<>();
105
111
  private List<String> mEvents = new ArrayList<>();
@@ -146,21 +152,40 @@ public class RNBackgroundGeolocationModule
146
152
  private void registerEvents() {
147
153
  BackgroundGeolocation adapter = getAdapter();
148
154
 
149
- adapter.onLocation(new LocationCallback());
150
- adapter.onMotionChange(new MotionChangeCallback());
151
- adapter.onActivityChange(new ActivityChangeCallback());
152
- adapter.onLocationFilter(new LocationFilterCallback());
153
- adapter.onLocationProviderChange(new LocationProviderChangeCallback());
154
- adapter.onGeofencesChange(new GeofencesChangeCallback());
155
- adapter.onGeofence(new GeofenceCallback());
156
- adapter.onHeartbeat(new HeartbeatCallback());
157
- adapter.onHttp(new HttpResponseCallback());
158
- adapter.onSchedule(new ScheduleCallback());
159
- adapter.onPowerSaveChange(new PowerSaveChangeCallack());
160
- adapter.onConnectivityChange(new ConnectivityChangeCallback());
161
- adapter.onEnabledChange(new EnabledChangeCallback());
162
- adapter.onNotificationAction(new NotificationActionCallback());
163
- HttpService.getInstance(getReactApplicationContext()).onAuthorization(new AuthorizationCallback());
155
+ // onHostDestroy() clears mReady, so an Activity recreated for a configuration change calls
156
+ // ready() again — and the SDK's callbacks now survive that recreation, having stopped
157
+ // taking the termination path that used to clear them. addListener does not dedupe, so
158
+ // close the previous registrations or every event is delivered once more per recreation.
159
+ // Close only what this method registered: clearing them all would take the play-services
160
+ // listener with it, which only initializeLocationManager() re-registers.
161
+ for (AutoCloseable subscription : mSubscriptions) {
162
+ closeSubscription(subscription);
163
+ }
164
+ mSubscriptions.clear();
165
+
166
+ mSubscriptions.add(adapter.onLocation(new LocationCallback()));
167
+ mSubscriptions.add(adapter.onMotionChange(new MotionChangeCallback()));
168
+ mSubscriptions.add(adapter.onActivityChange(new ActivityChangeCallback()));
169
+ mSubscriptions.add(adapter.onLocationFilter(new LocationFilterCallback()));
170
+ mSubscriptions.add(adapter.onLocationProviderChange(new LocationProviderChangeCallback()));
171
+ mSubscriptions.add(adapter.onGeofencesChange(new GeofencesChangeCallback()));
172
+ mSubscriptions.add(adapter.onGeofence(new GeofenceCallback()));
173
+ mSubscriptions.add(adapter.onHeartbeat(new HeartbeatCallback()));
174
+ mSubscriptions.add(adapter.onHttp(new HttpResponseCallback()));
175
+ mSubscriptions.add(adapter.onSchedule(new ScheduleCallback()));
176
+ mSubscriptions.add(adapter.onPowerSaveChange(new PowerSaveChangeCallack()));
177
+ mSubscriptions.add(adapter.onConnectivityChange(new ConnectivityChangeCallback()));
178
+ mSubscriptions.add(adapter.onEnabledChange(new EnabledChangeCallback()));
179
+ mSubscriptions.add(adapter.onNotificationAction(new NotificationActionCallback()));
180
+ mSubscriptions.add(HttpService.getInstance(getReactApplicationContext()).onAuthorization(new AuthorizationCallback()));
181
+ }
182
+
183
+ private void closeSubscription(AutoCloseable subscription) {
184
+ try {
185
+ subscription.close();
186
+ } catch (Exception e) {
187
+ TSLog.logger.warn(TSLog.warn(e.getMessage()));
188
+ }
164
189
  }
165
190
 
166
191
  /**
@@ -389,8 +414,10 @@ public class RNBackgroundGeolocationModule
389
414
  config.updateWithJSONObject(mapToJson(setHeadlessJobService(params)));
390
415
  } else {
391
416
  if (reset) {
392
- config.reset();
393
- config.updateWithJSONObject(mapToJson(setHeadlessJobService(params)));
417
+ // One commit: a separate reset() exposed the SDK's config listeners to the
418
+ // defaults in between while it was still configured — onHostDestroy() clears
419
+ // mReady, so an Activity recreation runs this path again.
420
+ config.reset(mapToJson(setHeadlessJobService(params)));
394
421
  } else if (params.hasKey(TSAuthorization.NAME)) {
395
422
  ReadableMap readableMap = params.getMap(TSAuthorization.NAME);
396
423
  if (readableMap != null) {
@@ -413,8 +440,7 @@ public class RNBackgroundGeolocationModule
413
440
  @ReactMethod
414
441
  public void configure(ReadableMap params, final Promise response){
415
442
  final TSConfig config = TSConfig.getInstance(getReactApplicationContext());
416
- config.reset();
417
- config.updateWithJSONObject(mapToJson(setHeadlessJobService(params)));
443
+ config.reset(mapToJson(setHeadlessJobService(params)));
418
444
 
419
445
  getAdapter().ready(new TSCallback() {
420
446
  @Override public void onSuccess() { response.resolve(getState()); }
@@ -432,8 +458,7 @@ public class RNBackgroundGeolocationModule
432
458
  @ReactMethod
433
459
  public void reset(ReadableMap defaultConfig, final Promise response) {
434
460
  TSConfig config = TSConfig.getInstance(getReactApplicationContext());
435
- config.reset();
436
- config.updateWithJSONObject(mapToJson(setHeadlessJobService(defaultConfig)));
461
+ config.reset(mapToJson(setHeadlessJobService(defaultConfig)));
437
462
  response.resolve(getState());
438
463
  }
439
464
 
@@ -479,7 +504,9 @@ public class RNBackgroundGeolocationModule
479
504
  @ReactMethod
480
505
  public void changePace(final boolean moving, final Promise response) {
481
506
  getAdapter().changePace(moving, new TSCallback() {
482
- @Override public void onSuccess() { response.resolve(moving); }
507
+ // (WO-033) The State the types declare, read once the adapter reports success — the
508
+ // same helper start() resolves with. This echoed the caller's own argument until now.
509
+ @Override public void onSuccess() { response.resolve(getState()); }
483
510
  @Override public void onFailure(String error) { response.reject(error); }
484
511
  });
485
512
  }
@@ -734,10 +761,20 @@ public class RNBackgroundGeolocationModule
734
761
  }
735
762
 
736
763
  @ReactMethod
737
- public void removeGeofences(final Promise response) {
738
- // TODO allow JS api to delete a list-of-geofences.
739
- // TODO accept WritableArray geofences from Client js API, allowing to remove a set of geofences
764
+ public void removeGeofences(ReadableArray data, final Promise response) {
765
+ // (WO-047) An empty list means "remove all" to the core: a bad argument rejects, never becomes [].
766
+ if (data == null) {
767
+ response.reject("removeGeofences: identifiers must be an Array");
768
+ return;
769
+ }
740
770
  List<String> identifiers = new ArrayList<>();
771
+ for (int n=0;n<data.size();n++) {
772
+ if (data.getType(n) != ReadableType.String) {
773
+ response.reject("removeGeofences: identifier at index " + n + " is not a String");
774
+ return;
775
+ }
776
+ identifiers.add(data.getString(n));
777
+ }
741
778
  getAdapter().removeGeofences(identifiers, new TSCallback() {
742
779
  @Override public void onSuccess() {
743
780
  response.resolve(true);
@@ -829,10 +866,8 @@ public class RNBackgroundGeolocationModule
829
866
  response.resolve(mapToWritableMap(token.toMap()));
830
867
  }
831
868
  @Override public void onFailure(String error) {
832
- WritableMap params = new WritableNativeMap();
833
- params.putString("status", error);
834
- params.putString("message", error);
835
- response.reject(error);
869
+ // (WO-073) The core's status ("403") as the code: a rejection cannot carry a `status` key.
870
+ response.reject(error, error);
836
871
  }
837
872
  });
838
873
  }
@@ -990,15 +1025,27 @@ public class RNBackgroundGeolocationModule
990
1025
  }
991
1026
 
992
1027
  @ReactMethod
993
- public void requestPermission(final Promise response) {
994
- getAdapter().requestPermission(new TSRequestPermissionCallback() {
1028
+ public void requestPermission(@Nullable final String permission, final Promise response) {
1029
+ TSRequestPermissionCallback callback = new TSRequestPermissionCallback() {
995
1030
  @Override public void onSuccess(int status) {
996
1031
  response.resolve(status);
997
1032
  }
998
1033
  @Override public void onFailure(int status) {
999
- response.reject("Permission request failed with status: " + status);
1034
+ // The error code carries the bare AuthorizationStatus — the JavaScript
1035
+ // layer normalizes the rejection to that value (cross-platform contract).
1036
+ response.reject(String.valueOf(status), String.valueOf(status));
1000
1037
  }
1001
- });
1038
+ };
1039
+ // (WO-007) permission ∈ "location" | "motion" | null (null = everything).
1040
+ // Null MUST route to the historical no-argument overload here in the bridge:
1041
+ // the string overload's own null-guard exists only in tslocationmanager >=
1042
+ // the WO-007 release — against an older AAR, handing it null would NPE inside
1043
+ // the adapter and crash every legacy requestPermission() call.
1044
+ if (permission == null) {
1045
+ getAdapter().requestPermission(callback);
1046
+ } else {
1047
+ getAdapter().requestPermission(permission, callback);
1048
+ }
1002
1049
  }
1003
1050
 
1004
1051
  @ReactMethod
@@ -1259,8 +1306,13 @@ public class RNBackgroundGeolocationModule
1259
1306
  }
1260
1307
  BackgroundGeolocation adapter = getAdapter();
1261
1308
  adapter.setActivity(activity);
1262
- // Handle play-services connect errors.
1263
- adapter.onPlayServicesConnectError((new TSPlayServicesConnectErrorCallback() {
1309
+ // Handle play-services connect errors. onHostDestroy() clears mInitialized, so a
1310
+ // recreated Activity runs this again: close the previous registration, or the
1311
+ // resolution dialog is raised once per recreation.
1312
+ if (mPlayServicesSubscription != null) {
1313
+ closeSubscription(mPlayServicesSubscription);
1314
+ }
1315
+ mPlayServicesSubscription = adapter.onPlayServicesConnectError((new TSPlayServicesConnectErrorCallback() {
1264
1316
  @Override
1265
1317
  public void onPlayServicesConnectError(int errorCode) {
1266
1318
  handlePlayServicesConnectError(errorCode);
@@ -1 +1 @@
1
- {"root":["./src/androidplugin.ts","./src/iosplugin.ts","./src/index.ts"],"version":"5.9.3"}
1
+ {"root":["./src/androidPlugin.ts","./src/iOSPlugin.ts","./src/index.ts"],"version":"5.9.3"}
@@ -163,8 +163,7 @@ RCT_EXPORT_METHOD(reset:(NSDictionary*)params
163
163
  TSConfig *config = [TSConfig sharedInstance];
164
164
  @try {
165
165
  if ([[params allKeys] count] > 0) {
166
- [config resetConfig:YES];
167
- [config updateWithDictionary:params];
166
+ [config resetWithDictionary:params]; // (WO-039) one commit, as in ready()
168
167
  } else {
169
168
  [config reset];
170
169
  }
@@ -208,8 +207,9 @@ RCT_EXPORT_METHOD(ready:(NSDictionary*)params
208
207
  [config updateWithDictionary:params];
209
208
  } else {
210
209
  if (resetFlag) {
211
- [config resetConfig:YES];
212
- [config updateWithDictionary:params];
210
+ // (WO-039) ONE commit: -resetConfig:YES then -updateWithDictionary: diffed against the
211
+ // defaults — unchanged keys re-fired every launch, omitted keys reverted with no event.
212
+ [config resetWithDictionary:params];
213
213
  } else if ([params objectForKey:@"authorization"]) {
214
214
  [config batchUpdate:^(TSConfig * _Nonnull cfg) {
215
215
  [cfg.authorization updateWithDictionary:[params objectForKey:@"authorization"]];
@@ -312,7 +312,10 @@ RCT_EXPORT_METHOD(startGeofences:(RCTPromiseResolveBlock)resolve reject:(RCTProm
312
312
  RCT_EXPORT_METHOD(changePace:(BOOL)moving resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
313
313
  {
314
314
  [locationManager changePace:moving];
315
- resolve(@(YES));
315
+ // (WO-033) The State the types declare, read after the void core call — the shape start()
316
+ // uses at :262. This resolved @(YES) until now: `true` even for changePace(false). No
317
+ // main-queue hop is added: -changePace: is not -start:, and the read is the statement after.
318
+ resolve([locationManager getState]);
316
319
  }
317
320
 
318
321
  RCT_EXPORT_METHOD(beginBackgroundTask:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
@@ -497,10 +500,21 @@ RCT_EXPORT_METHOD(removeGeofence:(NSString*)identifier resolve:(RCTPromiseResolv
497
500
  }];
498
501
  }
499
502
 
500
- RCT_EXPORT_METHOD(removeGeofences:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
503
+ RCT_EXPORT_METHOD(removeGeofences:(NSArray*)identifiers resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
501
504
  {
502
- NSArray *geofences = @[];
503
- [locationManager removeGeofences:geofences success:^{
505
+ // (WO-047) An empty list means "remove all" to the core: a bad argument rejects, never reaches it.
506
+ // In DEBUG, RCTConvert turns a non-array into nil, so nil must reject too (index.js always sends an array).
507
+ if (![identifiers isKindOfClass:[NSArray class]]) {
508
+ reject(@"remove_geofences_error", @"removeGeofences: identifiers must be an Array", nil);
509
+ return;
510
+ }
511
+ for (NSUInteger n = 0; n < identifiers.count; n++) {
512
+ if (![identifiers[n] isKindOfClass:[NSString class]]) {
513
+ reject(@"remove_geofences_error", [NSString stringWithFormat:@"removeGeofences: identifier at index %lu is not a String", (unsigned long)n], nil);
514
+ return;
515
+ }
516
+ }
517
+ [locationManager removeGeofences:identifiers success:^{
504
518
  resolve(@(YES));
505
519
  } failure:^(NSString* error) {
506
520
  reject(@"remove_geofences_error", error, nil);
@@ -658,12 +672,15 @@ RCT_EXPORT_METHOD(getProviderState:(RCTPromiseResolveBlock)resolve reject:(RCTPr
658
672
  resolve([event toDictionary]);
659
673
  }
660
674
 
661
- RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
675
+ RCT_EXPORT_METHOD(requestPermission:(NSString * _Nullable)permission resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
662
676
  {
663
- [locationManager requestPermission:^(NSNumber *status) {
677
+ // (WO-007) permission ∈ @"location" | @"motion" | nil (nil = everything).
678
+ [locationManager requestPermission:permission success:^(NSNumber *status) {
664
679
  resolve(status);
665
680
  } failure:^(NSNumber *status) {
666
- reject(@"request_permission_error", [status stringValue], nil);
681
+ // The error code carries the bare AuthorizationStatus — the JavaScript layer
682
+ // normalizes the rejection to that value (cross-platform contract).
683
+ reject([status stringValue], [status stringValue], nil);
667
684
  }];
668
685
  }
669
686
 
@@ -689,7 +706,8 @@ RCT_EXPORT_METHOD(getTransistorToken:(NSString*)orgname
689
706
  success:^(TransistorAuthorizationToken *token) {
690
707
  resolve([token toDictionary]);
691
708
  } failure:^(NSError *error) {
692
- reject(@"get_transistor_token_error", error.localizedDescription, error);
709
+ // (WO-073) The HTTP status as the code, so the JavaScript can tell a 403 refusal from any other failure.
710
+ reject([NSString stringWithFormat:@"%ld", (long)error.code], error.localizedDescription, error);
693
711
  }];
694
712
  }
695
713
 
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
-
3
- "name": "react-native-background-geolocation",
4
- "version": "5.5.0",
2
+ "name": "react-native-background-geolocation",
3
+ "version": "5.7.0",
5
4
  "description": "The most sophisticated cross-platform background location-tracking & geofencing module with battery-conscious motion-detection intelligence",
6
5
  "scripts": {
7
- "build": "yarn run build:expo",
8
- "test": "echo \"Error: no test specified\" && exit 1",
6
+ "build": "pnpm run build:expo",
7
+ "test": "jest && tsc -p tests-types --noEmit",
9
8
  "build:expo": "rimraf expo/plugin/build && tsc --build ./expo/plugin",
10
9
  "docs": "node ./scripts/generate-docs.mjs",
11
10
  "docs:publish": "./scripts/publish-docs.sh",
12
- "prepublishOnly": "npm run build"
11
+ "prepublishOnly": "pnpm run build",
12
+ "preflight": "scripts/preflight.sh",
13
+ "release": "scripts/release.sh"
13
14
  },
14
15
  "codegenConfig": {
15
16
  "name": "RNBackgroundGeolocation",
@@ -21,12 +22,12 @@
21
22
  "ios": {
22
23
  "swiftModuleName": "RNBackgroundGeolocation",
23
24
  "packageName": "RNBackgroundGeolocation"
24
- }
25
+ }
25
26
  },
26
27
  "main": "src/index.js",
27
28
  "repository": {
28
29
  "type": "git",
29
- "url": "git+ssh://git@github.com/transistorsoft/react-native-background-geolocation.git"
30
+ "url": "git+https://github.com/transistorsoft/react-native-background-geolocation.git"
30
31
  },
31
32
  "keywords": [
32
33
  "react-native",
@@ -41,7 +42,7 @@
41
42
  ],
42
43
  "dependencies": {
43
44
  "@babel/runtime": "^7.26.0",
44
- "@transistorsoft/background-geolocation-types": "^5.2.2",
45
+ "@transistorsoft/background-geolocation-types": "^5.3.8",
45
46
  "tslib": "^2.6.3"
46
47
  },
47
48
  "author": "Chris Scott <chris@transistorsoft.com>",
@@ -51,10 +52,14 @@
51
52
  },
52
53
  "homepage": "https://www.transistorsoft.com/shop/products/react-native-background-geolocation",
53
54
  "devDependencies": {
55
+ "@babel/core": "^8.0.1",
56
+ "@babel/preset-env": "^8.0.2",
54
57
  "@expo/config-plugins": "^4.1.0",
55
58
  "@types/node": "^18.11.5",
59
+ "babel-jest": "^30.5.0",
56
60
  "chalk": "^4.1.0",
57
61
  "fs-extra": "^9.1.0",
62
+ "jest": "^30.5.0",
58
63
  "path": "^0.12.7",
59
64
  "rimraf": "^5.0.0",
60
65
  "typescript": "^5.6.0"
@@ -170,8 +170,11 @@ export default class NativeModule {
170
170
  return RNBackgroundGeolocation.configure(validateConfig(config));
171
171
  }
172
172
 
173
- static requestPermission() {
174
- return RNBackgroundGeolocation.requestPermission();
173
+ static requestPermission(permission) {
174
+ // (WO-007) permission ∈ 'location' | 'motion' | null (null = everything, the
175
+ // historical behaviour). Always pass the argument explicitly — both the
176
+ // TurboModule spec and the old-architecture bridge expect it.
177
+ return RNBackgroundGeolocation.requestPermission((typeof permission === 'string') ? permission : null);
175
178
  }
176
179
 
177
180
  static requestTemporaryFullAccuracy(purpose) {
@@ -370,8 +373,8 @@ export default class NativeModule {
370
373
  return RNBackgroundGeolocation.addGeofences(geofences);
371
374
  }
372
375
 
373
- static removeGeofences() {
374
- return RNBackgroundGeolocation.removeGeofences();
376
+ static removeGeofences(identifiers) {
377
+ return RNBackgroundGeolocation.removeGeofences(identifiers);
375
378
  }
376
379
 
377
380
  static getGeofences() {
@@ -24,7 +24,10 @@ export default class TransistorAuthorizationService {
24
24
  console.warn('[TransistorAuthorizationService findOrCreate] ERROR: ', error);
25
25
  // Return a dummy token on error. this is a weird thing to do but it probably failed due to no network connection to demo server.
26
26
  // Once app will request the token once again after restarting one's app.
27
- if (error.status == '403') {
27
+ // (WO-073) A React Native rejection carries only code and message: the natives put the HTTP status in
28
+ // `code`, and `status` is set here so a refusal reads as it does on Capacitor, Cordova and React Native 4.x.
29
+ if (error && error.code != null && String(error.code) == '403') {
30
+ error.status = String(error.code);
28
31
  reject(error);
29
32
  return;
30
33
  }
package/src/index.js CHANGED
@@ -24,9 +24,47 @@ import {
24
24
  HttpMethod,
25
25
  TriggerActivity,
26
26
  ActivityType,
27
- Event
27
+ Event,
28
+ Permission,
29
+ MotionActivityType,
30
+ TrackingMode,
31
+ LogLevelName,
32
+ GeofenceAction,
33
+ LocationError,
34
+ LocationFilterReason,
35
+ SQLQueryOrder
28
36
  } from '@transistorsoft/background-geolocation-types';
29
37
 
38
+ // (WO-007) Named RUNTIME exports — every const-enum value the shared types package
39
+ // exports. index.d.ts has always re-exported the types package, but these objects
40
+ // only existed (partially) as statics on the default export — named value imports
41
+ // like `import { AuthorizationStatus }` were undefined at runtime. The jest parity
42
+ // test asserts this list stays complete against the installed types package.
43
+ export {
44
+ LogLevel,
45
+ DesiredAccuracy,
46
+ PersistMode,
47
+ AuthorizationStatus,
48
+ AccuracyAuthorization,
49
+ LocationRequest,
50
+ AuthorizationStrategy,
51
+ LocationFilterPolicy,
52
+ KalmanProfile,
53
+ NotificationPriority,
54
+ HttpMethod,
55
+ TriggerActivity,
56
+ ActivityType,
57
+ Event,
58
+ Permission,
59
+ MotionActivityType,
60
+ TrackingMode,
61
+ LogLevelName,
62
+ GeofenceAction,
63
+ LocationError,
64
+ LocationFilterReason,
65
+ SQLQueryOrder
66
+ };
67
+
30
68
  // Build a lookup table of allowed event names (runtime)
31
69
  const VALID_EVENT_NAMES = new Set(Object.values(Event));
32
70
 
@@ -160,6 +198,10 @@ export default class BackgroundGeolocation {
160
198
  return AuthorizationStatus;
161
199
  }
162
200
 
201
+ static get Permission() {
202
+ return Permission;
203
+ }
204
+
163
205
  static get AccuracyAuthorization() {
164
206
  return AccuracyAuthorization;
165
207
  }
@@ -226,8 +268,13 @@ export default class BackgroundGeolocation {
226
268
  return NativeModule.configure(config);
227
269
  }
228
270
 
229
- static requestPermission() {
230
- return NativeModule.requestPermission();
271
+ static requestPermission(permission) {
272
+ return NativeModule.requestPermission(permission).catch((error) => {
273
+ // (WO-007) Cross-platform contract: reject with the bare AuthorizationStatus
274
+ // value (the natives carry it in the error code).
275
+ const status = parseInt(error.code, 10);
276
+ return Promise.reject(Number.isNaN(status) ? error : status);
277
+ });
231
278
  }
232
279
 
233
280
  static requestTemporaryFullAccuracy(purpose) {
@@ -531,12 +578,27 @@ export default class BackgroundGeolocation {
531
578
  * Remove geofences. You may either supply an array of identifiers or nothing to destroy all geofences.
532
579
  * 1. removeGeofences() <-- Promise
533
580
  * 2. removeGeofences(['foo']) <-- Promise
534
- *
535
- * 3. removeGeofences(success, [failure])
536
- * 4. removeGeofences(['foo'], success, [failure])
537
- */
538
- static removeGeofences() {
539
- return NativeModule.removeGeofences();
581
+ * 3. removeGeofences(success, [failure]) <-- v4 callback form: remove all
582
+ */
583
+ static removeGeofences(identifiers, failure) {
584
+ if (typeof(identifiers) == 'function') {
585
+ // (WO-047) The v4 callback form removes all, exactly as v4 did.
586
+ const success = identifiers;
587
+ NativeModule.removeGeofences([]).then(success).catch(failure);
588
+ return;
589
+ }
590
+ if (identifiers == null) identifiers = [];
591
+ // (WO-047) An empty list means "remove all" in both cores: anything malformed must reject,
592
+ // never fall through to []. An index loop, not .some(), which skips the holes of a sparse list.
593
+ // Covered by tests/remove-geofences.test.js.
594
+ let valid = Array.isArray(identifiers);
595
+ for (let n = 0; valid && n < identifiers.length; n++) {
596
+ valid = typeof(identifiers[n]) == 'string';
597
+ }
598
+ if (!valid) {
599
+ return Promise.reject(new Error("BackgroundGeolocation#removeGeofences must be provided an {Array} of {String} identifiers, or nothing to remove all geofences."));
600
+ }
601
+ return NativeModule.removeGeofences(identifiers);
540
602
  }
541
603
 
542
604
  /**
@@ -31,7 +31,7 @@ export interface Spec extends TurboModule {
31
31
  getDeviceInfo(): Promise<Object>;
32
32
  isPowerSaveMode(): Promise<boolean>;
33
33
  isIgnoringBatteryOptimizations(): Promise<boolean>;
34
- requestPermission(): Promise<number>;
34
+ requestPermission(permission: string | null): Promise<number>;
35
35
  requestTemporaryFullAccuracy(purpose: string): Promise<number>;
36
36
 
37
37
  // Transistor token helpers
@@ -24,7 +24,7 @@ export interface Spec extends TurboModule {
24
24
  +startGeofences: () => Promise<Object>;
25
25
  +stop: () => Promise<Object>;
26
26
 
27
- +changePace: (moving: boolean) => Promise<boolean>;
27
+ +changePace: (moving: boolean) => Promise<Object>; // (WO-033) State, like +start
28
28
  +getState: () => Promise<Object>;
29
29
 
30
30
  // Locations / persistence
@@ -53,7 +53,7 @@ export interface Spec extends TurboModule {
53
53
  +addGeofence: (config: Object) => Promise<boolean>;
54
54
  +addGeofences: (geofences: Array<Object>) => Promise<boolean>;
55
55
  +removeGeofence: (identifier: string) => Promise<boolean>;
56
- +removeGeofences: () => Promise<boolean>;
56
+ +removeGeofences: (identifiers: Array<string>) => Promise<boolean>;
57
57
  +getGeofences: () => Promise<Array<Object>>;
58
58
  +getGeofence: (identifier: string) => Promise<Object>;
59
59
  +geofenceExists: (identifier: string) => Promise<boolean>;
@@ -90,7 +90,7 @@ export interface Spec extends TurboModule {
90
90
  +showSettings: (args: Object) => Promise<boolean>;
91
91
 
92
92
  +getProviderState: () => Promise<Object>;
93
- +requestPermission: () => Promise<Int32>;
93
+ +requestPermission: (permission: ?string) => Promise<Int32>;
94
94
  +requestTemporaryFullAccuracy: (purpose: string) => Promise<Int32>;
95
95
 
96
96
  // Required by NativeEventEmitter
@@ -1,80 +0,0 @@
1
- name: Bug Report
2
- description: File a bug report.
3
- title: "[Bug]: "
4
- labels: ["bug", "triage"]
5
- body:
6
- - type: markdown
7
- attributes:
8
- value: |
9
- Thanks for taking the time to fill out this support request.
10
- - type: checkboxes
11
- id: terms
12
- attributes:
13
- label: Required Reading
14
- description: "- I have read the Wikis [Philosophy of Operation](./wiki/Philosophy-of-Operation) and [Debugging](./wiki/Debugging)\n- I am aware of the [API Documentation](https://transistorsoft.github.io/react-native-background-geolocation/)\n- I am aware of the [CHANGELOG](./blob/master/CHANGELOG.md)"
15
- options:
16
- - label: Confirmed
17
- required: true
18
- - type: input
19
- id: version
20
- attributes:
21
- label: Plugin Version
22
- description: What version of our software are you running?
23
- placeholder: "Consult your package.json (do not enter 'latest')"
24
- validations:
25
- required: true
26
- - type: checkboxes
27
- id: os
28
- attributes:
29
- label: Mobile operating-system(s)
30
- description: Which mobile operating-system(s) is this issue reported upon?
31
- options:
32
- - label: iOS
33
- - label: Android
34
- - type: input
35
- id: device-info
36
- attributes:
37
- label: Device Manufacturer(s) and Model(s)
38
- description: What is the device model(s) and manufacturer(s)
39
- placeholder: eg Google Pixel 6, iPhone 13 Pro
40
- validations:
41
- required: true
42
- - type: input
43
- id: device-os
44
- attributes:
45
- label: Device operating-systems(s)
46
- description: Affected device operating system
47
- placeholder: eg iOS 18.1, Android 13
48
- validations:
49
- required: true
50
- - type: input
51
- id: framework-version
52
- attributes:
53
- label: React Native / Expo version
54
- description: What version React Native / Expo are you using
55
- placeholder: "eg: 0.76.6, Expo 52 (consult your package.json)"
56
- - type: textarea
57
- id: what-happened
58
- attributes:
59
- label: What happened?
60
- description: Also tell us, what did you expect to happen?
61
- placeholder: Tell us what happened and what did you expect to happen.
62
- validations:
63
- required: true
64
- - type: textarea
65
- id: config
66
- attributes:
67
- label: Plugin Code and/or Config
68
- description: Provide the Config you're using with the `BackgroundGeolocation.ready(config)` method along with any other relevant code.
69
- placeholder: "Paste your Config or code here, eg: BackgroundGeolocation.ready(config).\n\nYour code will be automatically syntax-highlighted"
70
- render: typescript
71
- validations:
72
- required: true
73
- - type: textarea
74
- id: logs
75
- attributes:
76
- label: Relevant log output
77
- description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
78
- placeholder: "include iOS / Android logs\n- $ adb logcat -s TSLocationManager:V\n- ios XCode logs\n- Logs from BackgroundGeolocation.emailLog(address)"
79
- render: shell
80
-
@@ -1,82 +0,0 @@
1
- name: Help Wanted
2
- description: I need assistance
3
- title: "[Help Wanted]: "
4
- labels: ["triage"]
5
- body:
6
- - type: markdown
7
- attributes:
8
- value: |
9
- Thanks for taking the time to fill out this support request.
10
- - type: checkboxes
11
- id: terms
12
- attributes:
13
- label: Required Reading
14
- description: "- I have read the Wikis [Philosophy of Operation](./wiki/Philosophy-of-Operation) and [Debugging](./wiki/Debugging)\n- I am aware of the [API Documentation](https://transistorsoft.github.io/react-native-background-geolocation/)\n- I am aware of the [CHANGELOG](./blob/master/CHANGELOG.md)"
15
- options:
16
- - label: Confirmed
17
- required: true
18
- - type: input
19
- id: version
20
- attributes:
21
- label: Plugin Version
22
- description: What version of our software are you running?
23
- placeholder: "Consult your package.json (do not enter 'latest')"
24
- validations:
25
- required: true
26
- - type: checkboxes
27
- id: os
28
- attributes:
29
- label: Mobile operating-system(s)
30
- description: Which mobile operating-system(s) is this about?
31
- options:
32
- - label: iOS
33
- - label: Android
34
- validations:
35
- required: true
36
- - type: input
37
- id: device-info
38
- attributes:
39
- label: Device Manufacturer(s) and Model(s)
40
- description: What is the device model(s) and manufacturer(s)
41
- placeholder: eg Google Pixel 6, iPhone 13 Pro
42
- validations:
43
- required: true
44
- - type: input
45
- id: device-os
46
- attributes:
47
- label: Device operating-systems(s)
48
- description: Affected device operating system
49
- placeholder: eg iOS 18.1, Android 13
50
- validations:
51
- required: true
52
- - type: input
53
- id: framework-version
54
- attributes:
55
- label: React Native / Expo version
56
- description: What version React Native / Expo are you using
57
- placeholder: "eg: 0.76.6, Expo 52 (consult your package.json)"
58
- - type: textarea
59
- id: what-happened
60
- attributes:
61
- label: What do you require assistance about?
62
- validations:
63
- required: true
64
- - type: textarea
65
- id: config
66
- attributes:
67
- label: "[Optional] Plugin Code and/or Config"
68
- description: Provide the Config you're using with the `BackgroundGeolocation.ready(config)` method along with any other relevant code.
69
- placeholder: "Paste your Config or code here, eg: BackgroundGeolocation.ready(config).\n\nYour code will be automatically syntax-highlighted"
70
- render: typescript
71
- validations:
72
- required: false
73
- - type: textarea
74
- id: logs
75
- attributes:
76
- label: "[Optional] Relevant log output"
77
- description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
78
- placeholder: "include iOS / Android logs\n- $ adb logcat -s TSLocationManager:V\n- ios XCode logs\n- Logs from BackgroundGeolocation.emailLog(address)"
79
- render: shell
80
- validations:
81
- required: false
82
-
@@ -1,25 +0,0 @@
1
- name: Close inactive issues
2
- on:
3
- schedule:
4
- - cron: "30 1 * * *"
5
-
6
- jobs:
7
- close-issues:
8
- runs-on: ubuntu-latest
9
- permissions:
10
- issues: write
11
- pull-requests: write
12
- steps:
13
- - uses: actions/stale@v5
14
- with:
15
- operations-per-run: 100
16
- days-before-issue-stale: 30
17
- days-before-issue-close: 14
18
- stale-issue-label: "stale"
19
- stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
20
- close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
21
- days-before-pr-stale: -1
22
- days-before-pr-close: -1
23
- repo-token: ${{ secrets.GITHUB_TOKEN }}
24
-
25
-
package/babel.config.js DELETED
@@ -1,5 +0,0 @@
1
- module.exports = {
2
- presets: [
3
- ['@babel/preset-env', { targets: { node: 'current' } }]
4
- ],
5
- };
package/jest.config.js DELETED
@@ -1,14 +0,0 @@
1
- /** @type {import('jest').Config} */
2
-
3
- module.exports = {
4
- testEnvironment: 'node',
5
- roots: ['<rootDir>/src', '<rootDir>/tests'],
6
- transform: {
7
- '^.+\\.[jt]sx?$': 'babel-jest'
8
- },
9
- moduleNameMapper: {
10
- '^react-native$': '<rootDir>/mocks/react-native'
11
- },
12
- testPathIgnorePatterns: ['/node_modules/', '/dist/'],
13
- transformIgnorePatterns: ['/node_modules/']
14
- };
@@ -1,55 +0,0 @@
1
- const listeners = new Set();
2
-
3
- class NativeEventEmitter {
4
- addListener(event, cb) {
5
- const sub = { remove: () => listeners.delete(sub) };
6
- listeners.add(sub);
7
- return sub;
8
- }
9
- removeAllListeners() { listeners.clear(); }
10
- }
11
-
12
- // The native module is a TurboModule: every data method returns a Promise (not the pre-migration
13
- // callback convention). NativeModule.js returns these calls directly, so the mocks must resolve a
14
- // Promise. `log`/`playSound` are fire-and-forget (void).
15
- const RNBackgroundGeolocation = {
16
- // Minimal surface your JS calls in tests:
17
- ready: () => Promise.resolve({ enabled: false }),
18
- configure: () => Promise.resolve({}),
19
- setConfig: () => Promise.resolve({}),
20
- reset: () => Promise.resolve({ enabled: false }),
21
- getState: () => Promise.resolve({ enabled: false }),
22
- beginBackgroundTask: () => Promise.resolve(1),
23
- finish: (id) => Promise.resolve(id),
24
- addGeofence: () => Promise.resolve(),
25
- addGeofences: () => Promise.resolve(),
26
- removeGeofence: () => Promise.resolve(),
27
- removeGeofences: () => Promise.resolve(),
28
- getGeofences: () => Promise.resolve([]),
29
- getGeofence: () => Promise.resolve(null),
30
- geofenceExists: () => Promise.resolve(false),
31
- changePace: () => Promise.resolve(),
32
- getLog: () => Promise.resolve(''),
33
- destroyLog: () => Promise.resolve(),
34
- emailLog: () => Promise.resolve(),
35
- getOdometer: () => Promise.resolve(0),
36
- setOdometer: () => Promise.resolve({}),
37
- getLocations: () => Promise.resolve([]),
38
- getCount: () => Promise.resolve(0),
39
- destroyLocations: () => Promise.resolve(),
40
- insertLocation: () => Promise.resolve('00000000-0000-0000-0000-000000000000'),
41
- sync: () => Promise.resolve({ success: true }),
42
- getProviderState: () => Promise.resolve({ enabled: true }),
43
- requestPermission: () => Promise.resolve(1),
44
- requestTemporaryFullAccuracy: () => Promise.resolve(1),
45
- log: () => {},
46
- getDeviceInfo: () => Promise.resolve({ model: 'mock' }),
47
- playSound: () => {},
48
- };
49
-
50
- module.exports = {
51
- NativeModules: { RNBackgroundGeolocation },
52
- NativeEventEmitter,
53
- Platform: { OS: 'ios' },
54
- AppRegistry: { registerHeadlessTask: () => {} },
55
- };
@@ -1,16 +0,0 @@
1
- const BG = require('../src/index.js').default;
2
- const Shared = require('@transistorsoft/background-geolocation-types');
3
-
4
- test('RN enum namespaces mirror shared enum values', () => {
5
- // Spot-check full object equality for safety
6
- expect(BG.LogLevel).toEqual(Shared.LogLevel);
7
- expect(BG.DesiredAccuracy).toEqual(Shared.DesiredAccuracy);
8
- expect(BG.PersistMode).toEqual(Shared.PersistMode);
9
- expect(BG.AuthorizationStrategy).toEqual(Shared.AuthorizationStrategy);
10
- expect(BG.LocationFilterPolicy).toEqual(Shared.LocationFilterPolicy);
11
- expect(BG.KalmanProfile).toEqual(Shared.KalmanProfile);
12
- expect(BG.NotificationPriority).toEqual(Shared.NotificationPriority);
13
- expect(BG.HttpMethod).toEqual(Shared.HttpMethod);
14
- expect(BG.TriggerActivity).toEqual(Shared.TriggerActivity);
15
- expect(BG.ActivityType).toEqual(Shared.ActivityType);
16
- });
@@ -1,23 +0,0 @@
1
- const BG = require('../src/index.js').default;
2
-
3
- test('exposes attached enum namespaces', () => {
4
- const BG = require('../src/index.js').default;
5
-
6
- // Core enums
7
- expect(BG.LogLevel.Verbose).toBeDefined();
8
- expect(BG.DesiredAccuracy.High).toBeDefined();
9
- expect(BG.PersistMode.All).toBeDefined();
10
- expect(BG.AuthorizationStrategy.Jwt).toBe('jwt');
11
- expect(BG.LocationFilterPolicy.Adjust).toBeDefined();
12
- expect(BG.KalmanProfile.Default).toBeDefined();
13
-
14
- // Notification priority & activity
15
- expect(BG.NotificationPriority.Default).toBeDefined();
16
- expect(BG.NotificationPriority.Min).toBeDefined();
17
- expect(BG.ActivityType.Other).toBeDefined();
18
- expect(BG.ActivityType.AutomotiveNavigation).toBeDefined();
19
-
20
- // HTTP + trigger activity
21
- expect(BG.HttpMethod.Post).toBeDefined();
22
- expect(BG.TriggerActivity.Walking).toBeDefined();
23
- });
@@ -1,51 +0,0 @@
1
- // JS-side marshaling test for insertLocation: does the JavaScript API layer forward the caller's
2
- // location to the native bridge boundary *unchanged*, and pass the returned uuid back — with no
3
- // real SDK and no device? The native module is a jest spy standing in for the bridge; we assert on
4
- // what it receives (the args that crossed JS -> native) and what the caller ultimately gets back.
5
- //
6
- // This is the JS half of the two-halves bridge-marshaling strategy. The native half (ReadableMap ->
7
- // JSONObject) is covered by the Android Robolectric test `RNBackgroundGeolocationModuleTest`.
8
-
9
- const { NativeModules } = require('react-native'); // mapped to mocks/react-native.js via jest config
10
- const BG = require('../src/index.js').default;
11
-
12
- const native = NativeModules.RNBackgroundGeolocation;
13
-
14
- describe('insertLocation — JS -> native bridge marshaling', () => {
15
- afterEach(() => {
16
- jest.restoreAllMocks();
17
- });
18
-
19
- test('forwards the location to the native module unchanged and resolves its uuid', async () => {
20
- const insert = jest.spyOn(native, 'insertLocation').mockResolvedValue('uuid-abc');
21
-
22
- const location = {
23
- coords: { latitude: 45.5152, longitude: -73.6104 },
24
- timestamp: 1705314600000, // numeric epoch
25
- extras: { source: 'import' },
26
- };
27
-
28
- const uuid = await BG.insertLocation(location);
29
-
30
- // The exact object reaches the native boundary — no mangling by index.js / NativeModule.
31
- expect(insert).toHaveBeenCalledTimes(1);
32
- expect(insert).toHaveBeenCalledWith(location);
33
-
34
- // Type/shape preserved across the JS layers: numeric timestamp stays a number, coords/extras nested.
35
- const [passed] = insert.mock.calls[0];
36
- expect(typeof passed.timestamp).toBe('number');
37
- expect(passed.coords).toEqual({ latitude: 45.5152, longitude: -73.6104 });
38
- expect(passed.extras).toEqual({ source: 'import' });
39
-
40
- // The uuid string flows back out through NativeModule + index.js.
41
- expect(uuid).toBe('uuid-abc');
42
- });
43
-
44
- test('propagates a native rejection to the caller', async () => {
45
- jest.spyOn(native, 'insertLocation').mockRejectedValue(new Error('insert_location_error'));
46
-
47
- await expect(
48
- BG.insertLocation({ coords: { latitude: 0, longitude: 0 }, timestamp: 0 })
49
- ).rejects.toThrow('insert_location_error');
50
- });
51
- });
@@ -1,30 +0,0 @@
1
- jest.mock('../src/NativeModule', () => ({
2
- addListener: jest.fn(() => ({ remove: jest.fn() })),
3
- }));
4
-
5
- const NativeModule = require('../src/NativeModule');
6
- const BG = require('../src/index.js').default;
7
- const Shared = require('@transistorsoft/background-geolocation-types');
8
-
9
- // onLocationFilter event wiring (issue #2585).
10
- test('exposes the locationfilter event name + subscription method', () => {
11
- // Shared enum carries the new event name.
12
- expect(Shared.Event.LocationFilter).toBe('locationfilter');
13
-
14
- // Backward-compat static getter mirrors the shared enum.
15
- expect(BG.EVENT_LOCATIONFILTER).toBe('locationfilter');
16
-
17
- // Subscription method is exposed.
18
- expect(typeof BG.onLocationFilter).toBe('function');
19
- });
20
-
21
- test('onLocationFilter routes through the native bridge with the locationfilter event', () => {
22
- NativeModule.addListener.mockClear();
23
- const cb = jest.fn();
24
-
25
- const subscription = BG.onLocationFilter(cb);
26
-
27
- expect(NativeModule.addListener).toHaveBeenCalledWith('locationfilter', cb);
28
- expect(subscription).toBeDefined();
29
- expect(typeof subscription.remove).toBe('function');
30
- });
@@ -1,28 +0,0 @@
1
- jest.mock('../src/NativeModule', () => ({
2
- ready: jest.fn().mockResolvedValue({ enabled: false })
3
- }));
4
-
5
- const NativeModule = require('../src/NativeModule');
6
- const BG = require('../src/index.js').default;
7
-
8
- test('ready forwards compound config to native', async () => {
9
- const cfg = {
10
- logger: { logLevel: BG.LogLevel.Verbose },
11
- geolocation: {
12
- desiredAccuracy: BG.DesiredAccuracy.High,
13
- distanceFilter: 10,
14
- },
15
- http: {
16
- url: 'https://example.com',
17
- autoSync: true,
18
- },
19
- };
20
-
21
- await BG.ready(cfg);
22
-
23
- expect(NativeModule.ready).toHaveBeenCalledTimes(1);
24
- const [arg] = NativeModule.ready.mock.calls[0];
25
-
26
- // Same object shape (or deep-equal)
27
- expect(arg).toMatchObject(cfg);
28
- });
@@ -1,6 +0,0 @@
1
- const BG = require('../src/index.js').default;
2
-
3
- test('ready resolves and returns state', async () => {
4
- const state = await BG.ready({ logger: { logLevel: BG.LogLevel.Verbose } });
5
- expect(state.enabled).toBe(false);
6
- });
File without changes
@@ -1,38 +0,0 @@
1
- import BackgroundGeolocation, {
2
- type Config as RNConfig,
3
- type State
4
- } from 'react-native-background-geolocation';
5
-
6
- const config: RNConfig = {
7
- logger: {
8
- logLevel: BackgroundGeolocation.LogLevel.Verbose,
9
- },
10
- geolocation: {
11
- desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
12
- distanceFilter: 10,
13
- },
14
- http: {
15
- url: 'https://example.com',
16
- autoSync: true,
17
- },
18
- persistence: {
19
- persistMode: BackgroundGeolocation.PersistMode.All,
20
- maxDaysToPersist: 7,
21
- },
22
- app: {
23
- stopOnTerminate: false,
24
- startOnBoot: true,
25
- },
26
- activity: {
27
- disableStopDetection: false,
28
- },
29
- };
30
-
31
- // Ensure `ready` accepts RNConfig and returns a Promise-like thing
32
- BackgroundGeolocation.ready(config).then((state: State) => {
33
- const enabled: boolean = state.enabled;
34
- void enabled;
35
- });
36
-
37
- // no runtime; compile-only
38
- export {};
@@ -1,35 +0,0 @@
1
- // Type-only smoke test to ensure static enum members on BackgroundGeolocation
2
- // align with the shared @transistorsoft/background-geolocation-types definitions.
3
- // This test runs via `pnpm run test:types` using tsc --noEmit.
4
-
5
- import BackgroundGeolocation from 'react-native-background-geolocation';
6
-
7
- // Accessors — ensure all enum namespaces exist and have valid members.
8
- const logLevelVerbose = BackgroundGeolocation.LogLevel.Verbose;
9
- const desiredAccuracyHigh = BackgroundGeolocation.DesiredAccuracy.High;
10
- const persistModeAll = BackgroundGeolocation.PersistMode.All;
11
- const authJwt = BackgroundGeolocation.AuthorizationStrategy.Jwt;
12
- const policyAdjust = BackgroundGeolocation.LocationFilterPolicy.Adjust;
13
- const kalmanDefault = BackgroundGeolocation.KalmanProfile.Default;
14
- const httpPost = BackgroundGeolocation.HttpMethod.Post;
15
- const triggerWalking = BackgroundGeolocation.TriggerActivity.Walking;
16
- const notificationDefault = BackgroundGeolocation.NotificationPriority.Default;
17
- const activityOther = BackgroundGeolocation.ActivityType.Other;
18
-
19
- // Assignments to typed constants to ensure they match the exported type shapes.
20
- let testLogLevel: typeof BackgroundGeolocation.LogLevel = BackgroundGeolocation.LogLevel;
21
- let testDesiredAccuracy: typeof BackgroundGeolocation.DesiredAccuracy = BackgroundGeolocation.DesiredAccuracy;
22
- let testPersistMode: typeof BackgroundGeolocation.PersistMode = BackgroundGeolocation.PersistMode;
23
- let testAuthorizationStrategy: typeof BackgroundGeolocation.AuthorizationStrategy = BackgroundGeolocation.AuthorizationStrategy;
24
- let testLocationFilterPolicy: typeof BackgroundGeolocation.LocationFilterPolicy = BackgroundGeolocation.LocationFilterPolicy;
25
- let testKalmanProfile: typeof BackgroundGeolocation.KalmanProfile = BackgroundGeolocation.KalmanProfile;
26
- let testHttpMethod: typeof BackgroundGeolocation.HttpMethod = BackgroundGeolocation.HttpMethod;
27
- let testTriggerActivity: typeof BackgroundGeolocation.TriggerActivity = BackgroundGeolocation.TriggerActivity;
28
-
29
- let testNotificationPriority: typeof BackgroundGeolocation.NotificationPriority =
30
- BackgroundGeolocation.NotificationPriority;
31
- let testActivityType: typeof BackgroundGeolocation.ActivityType =
32
- BackgroundGeolocation.ActivityType;
33
-
34
- // No runtime code — this is a static type check only.
35
- export {};
@@ -1,23 +0,0 @@
1
- import BackgroundGeolocation, {
2
- type Location,
3
- type GeofenceEvent
4
- } from 'react-native-background-geolocation';
5
-
6
-
7
- // callbacks should see the bridged types
8
- BackgroundGeolocation.onLocation((location: Location) => {
9
- const loc: Location = location;
10
- console.log(loc.coords.latitude, loc.is_moving);
11
- });
12
-
13
- BackgroundGeolocation.onGeofence((event: GeofenceEvent) => {
14
- const gfEvent: GeofenceEvent = event;
15
- console.log(gfEvent.identifier, gfEvent.location.coords.longitude);
16
- });
17
-
18
- // Ensure that invalid usage fails if uncommented:
19
- // BackgroundGeolocation.onLocation((location) => {
20
- // const bad: string = location; // should be type error
21
- // });
22
-
23
- export {};
File without changes
package/tests-types/ok.ts DELETED
@@ -1,20 +0,0 @@
1
- import BackgroundGeolocation, { type Geofence } from 'react-native-background-geolocation';
2
- import type { Config as SharedConfig } from '@transistorsoft/background-geolocation-types';
3
-
4
- const gf: Geofence = {
5
- identifier: 'home',
6
- latitude: 1,
7
- longitude: 2,
8
- radius: 150,
9
- notifyOnEntry: true,
10
- };
11
-
12
- const compoundConfig: SharedConfig = {
13
- logger: { logLevel: BackgroundGeolocation.LogLevel.Verbose },
14
- geolocation: {
15
- desiredAccuracy: BackgroundGeolocation.DesiredAccuracy.High,
16
- distanceFilter: 10,
17
- },
18
- };
19
-
20
- BackgroundGeolocation.ready(compoundConfig);
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "strict": true,
4
- "target": "ES2020",
5
- "module": "ESNext",
6
- "moduleResolution": "Node",
7
- "skipLibCheck": true,
8
- "baseUrl": "..",
9
- "paths": {
10
- "react-native-background-geolocation": ["src/index.d.ts"]
11
- }
12
- },
13
- "include": ["./**/*.ts"]
14
- }
package/typedoc.json DELETED
@@ -1,49 +0,0 @@
1
- {
2
- "$schema": "https://typedoc.org/schema.json",
3
- "entryPoints": [
4
- "../../background-geolocation-types/src/docs-entry.ts"
5
- ],
6
- "tsconfig": "../../background-geolocation-types/tsconfig.json",
7
-
8
- "name": "react-native-background-geolocation",
9
- "favicon": "../../background-geolocation-types/assets/images/favicon.ico",
10
- "includeVersion": false,
11
- "out": "docs",
12
- "readme": "none",
13
- "disableSources": true,
14
-
15
- "sort": ["source-order"],
16
- "highlightLanguages": [
17
- "ts",
18
- "js",
19
- "json",
20
- "xml",
21
- "bash",
22
- "console",
23
- "erb",
24
- "java"
25
- ],
26
-
27
- "categoryOrder": [
28
- "Primary API",
29
- "Config",
30
- "Events",
31
- "Data",
32
- "Logger",
33
- "Device",
34
- "*"
35
- ],
36
- "categorizeByGroup": false,
37
- "useFirstParagraphOfCommentAsSummary": true,
38
- "excludeInternal": true,
39
- "excludePrivate": true,
40
- "excludeProtected": true,
41
-
42
- "gitRevision": "main",
43
-
44
- "plugin": [
45
- "../../background-geolocation-types/tools/typedoc-plugin-gitlink/index.cjs",
46
- "../../background-geolocation-types/tools/typedoc-plugin-mediaplayer/index.cjs",
47
- "../../background-geolocation-types/tools/typedoc-plugin-site/index.cjs"
48
- ]
49
- }