react-native-background-geolocation 5.5.0 → 5.6.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.
@@ -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.6.0'
30
30
  s.dependency 'TSLocationManager', tslm_version
31
31
 
32
32
  s.libraries = 'sqlite3', 'z', 'stdc++'
@@ -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;
@@ -990,15 +992,27 @@ public class RNBackgroundGeolocationModule
990
992
  }
991
993
 
992
994
  @ReactMethod
993
- public void requestPermission(final Promise response) {
994
- getAdapter().requestPermission(new TSRequestPermissionCallback() {
995
+ public void requestPermission(@Nullable final String permission, final Promise response) {
996
+ TSRequestPermissionCallback callback = new TSRequestPermissionCallback() {
995
997
  @Override public void onSuccess(int status) {
996
998
  response.resolve(status);
997
999
  }
998
1000
  @Override public void onFailure(int status) {
999
- response.reject("Permission request failed with status: " + status);
1001
+ // The error code carries the bare AuthorizationStatus — the JavaScript
1002
+ // layer normalizes the rejection to that value (cross-platform contract).
1003
+ response.reject(String.valueOf(status), String.valueOf(status));
1000
1004
  }
1001
- });
1005
+ };
1006
+ // (WO-007) permission ∈ "location" | "motion" | null (null = everything).
1007
+ // Null MUST route to the historical no-argument overload here in the bridge:
1008
+ // the string overload's own null-guard exists only in tslocationmanager >=
1009
+ // the WO-007 release — against an older AAR, handing it null would NPE inside
1010
+ // the adapter and crash every legacy requestPermission() call.
1011
+ if (permission == null) {
1012
+ getAdapter().requestPermission(callback);
1013
+ } else {
1014
+ getAdapter().requestPermission(permission, callback);
1015
+ }
1002
1016
  }
1003
1017
 
1004
1018
  @ReactMethod
@@ -658,12 +658,15 @@ RCT_EXPORT_METHOD(getProviderState:(RCTPromiseResolveBlock)resolve reject:(RCTPr
658
658
  resolve([event toDictionary]);
659
659
  }
660
660
 
661
- RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
661
+ RCT_EXPORT_METHOD(requestPermission:(NSString * _Nullable)permission resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
662
662
  {
663
- [locationManager requestPermission:^(NSNumber *status) {
663
+ // (WO-007) permission ∈ @"location" | @"motion" | nil (nil = everything).
664
+ [locationManager requestPermission:permission success:^(NSNumber *status) {
664
665
  resolve(status);
665
666
  } failure:^(NSNumber *status) {
666
- reject(@"request_permission_error", [status stringValue], nil);
667
+ // The error code carries the bare AuthorizationStatus — the JavaScript layer
668
+ // normalizes the rejection to that value (cross-platform contract).
669
+ reject([status stringValue], [status stringValue], nil);
667
670
  }];
668
671
  }
669
672
 
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
-
3
- "name": "react-native-background-geolocation",
4
- "version": "5.5.0",
2
+ "name": "react-native-background-geolocation",
3
+ "version": "5.6.0",
5
4
  "description": "The most sophisticated cross-platform background location-tracking & geofencing module with battery-conscious motion-detection intelligence",
6
5
  "scripts": {
7
6
  "build": "yarn run build:expo",
8
- "test": "echo \"Error: no test specified\" && exit 1",
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",
@@ -21,7 +20,7 @@
21
20
  "ios": {
22
21
  "swiftModuleName": "RNBackgroundGeolocation",
23
22
  "packageName": "RNBackgroundGeolocation"
24
- }
23
+ }
25
24
  },
26
25
  "main": "src/index.js",
27
26
  "repository": {
@@ -41,7 +40,7 @@
41
40
  ],
42
41
  "dependencies": {
43
42
  "@babel/runtime": "^7.26.0",
44
- "@transistorsoft/background-geolocation-types": "^5.2.2",
43
+ "@transistorsoft/background-geolocation-types": "^5.3.2",
45
44
  "tslib": "^2.6.3"
46
45
  },
47
46
  "author": "Chris Scott <chris@transistorsoft.com>",
@@ -51,10 +50,14 @@
51
50
  },
52
51
  "homepage": "https://www.transistorsoft.com/shop/products/react-native-background-geolocation",
53
52
  "devDependencies": {
53
+ "@babel/core": "^8.0.1",
54
+ "@babel/preset-env": "^8.0.2",
54
55
  "@expo/config-plugins": "^4.1.0",
55
56
  "@types/node": "^18.11.5",
57
+ "babel-jest": "^30.5.0",
56
58
  "chalk": "^4.1.0",
57
59
  "fs-extra": "^9.1.0",
60
+ "jest": "^30.5.0",
58
61
  "path": "^0.12.7",
59
62
  "rimraf": "^5.0.0",
60
63
  "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) {
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) {
@@ -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
@@ -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
@@ -0,0 +1,78 @@
1
+ // (WO-007) JS-side marshaling for the per-permission requestPermission API: does the
2
+ // JavaScript layer forward the Permission selector to the native bridge boundary, and
3
+ // normalize native rejections to the bare AuthorizationStatus value the cross-platform
4
+ // contract documents? The native module is a jest spy standing in for the bridge.
5
+
6
+ const { NativeModules } = require('react-native'); // mapped to mocks/react-native.js via jest config
7
+ const BG = require('../src/index.js').default;
8
+ const { Permission, AuthorizationStatus } = require('../src/index.js');
9
+
10
+ const native = NativeModules.RNBackgroundGeolocation;
11
+
12
+ describe('named runtime exports (WO-007 regression)', () => {
13
+ // index.d.ts has always promised `export *` of the shared types package, but the
14
+ // runtime module exported only the default class — named VALUE imports were
15
+ // silently undefined (field: "Cannot read property 'Location' of undefined").
16
+ test('Permission and AuthorizationStatus are real values, importable by name', () => {
17
+ expect(Permission).toBeDefined();
18
+ expect(Permission.Location).toBe('location');
19
+ expect(Permission.Motion).toBe('motion');
20
+ expect(AuthorizationStatus).toBeDefined();
21
+ expect(AuthorizationStatus.DeniedAlways).toBe(5);
22
+ // The class statics remain, unchanged.
23
+ expect(BG.Permission).toBe(Permission);
24
+ expect(BG.AuthorizationStatus).toBe(AuthorizationStatus);
25
+ });
26
+
27
+ // Drift-proof: EVERY runtime value the types package exports must be re-exported
28
+ // by name from this module — a hardcoded list rots (the first fix missed 7 of 22).
29
+ test('every types-package runtime export is re-exported by name', () => {
30
+ const typesPkg = require('@transistorsoft/background-geolocation-types');
31
+ const sdk = require('../src/index.js');
32
+ const runtimeKeys = Object.keys(typesPkg).filter((k) => k !== 'default' && k !== '__esModule');
33
+ expect(runtimeKeys.length).toBeGreaterThan(0);
34
+ for (const key of runtimeKeys) {
35
+ expect(sdk[key]).toBe(typesPkg[key]);
36
+ }
37
+ });
38
+ });
39
+
40
+ describe('requestPermission — JS -> native bridge marshaling (WO-007)', () => {
41
+ afterEach(() => {
42
+ jest.restoreAllMocks();
43
+ });
44
+
45
+ test('forwards the permission selector to the native module', async () => {
46
+ const request = jest.spyOn(native, 'requestPermission').mockResolvedValue(3);
47
+
48
+ await expect(BG.requestPermission('location')).resolves.toBe(3);
49
+ expect(request).toHaveBeenLastCalledWith('location');
50
+
51
+ await expect(BG.requestPermission('motion')).resolves.toBe(3);
52
+ expect(request).toHaveBeenLastCalledWith('motion');
53
+ });
54
+
55
+ test('the no-argument form passes an explicit null (both bridge architectures expect the slot)', async () => {
56
+ const request = jest.spyOn(native, 'requestPermission').mockResolvedValue(4);
57
+
58
+ await expect(BG.requestPermission()).resolves.toBe(4);
59
+ expect(request).toHaveBeenLastCalledWith(null);
60
+ });
61
+
62
+ test('normalizes a native rejection to the bare AuthorizationStatus carried in the error code', async () => {
63
+ // Both natives reject with code = String(status).
64
+ const error = new Error('5');
65
+ error.code = '5';
66
+ jest.spyOn(native, 'requestPermission').mockRejectedValue(error);
67
+
68
+ await expect(BG.requestPermission('motion')).rejects.toBe(5); // DeniedAlways
69
+ });
70
+
71
+ test('passes an unparseable native error through unchanged', async () => {
72
+ const error = new Error('bridge exploded');
73
+ error.code = 'E_UNEXPECTED';
74
+ jest.spyOn(native, 'requestPermission').mockRejectedValue(error);
75
+
76
+ await expect(BG.requestPermission()).rejects.toBe(error);
77
+ });
78
+ });
@@ -0,0 +1,33 @@
1
+ // Type-surface assertions — compile-only, no runtime. (WO-007)
2
+ //
3
+ // Verifies the per-permission requestPermission API against the published
4
+ // @transistorsoft/background-geolocation-types surface this package re-exports.
5
+ import BackgroundGeolocation, {
6
+ Permission,
7
+ AuthorizationStatus,
8
+ } from "react-native-background-geolocation";
9
+
10
+ async function requestPermissionSurface(): Promise<void> {
11
+ // A-form: no argument — everything the configuration requires.
12
+ const all: AuthorizationStatus = await BackgroundGeolocation.requestPermission();
13
+
14
+ // B-form: location only.
15
+ const location: AuthorizationStatus =
16
+ await BackgroundGeolocation.requestPermission(Permission.Location);
17
+
18
+ // C-form: motion only; DeniedAlways is the Android permanent-denial status.
19
+ try {
20
+ const motion: AuthorizationStatus =
21
+ await BackgroundGeolocation.requestPermission(Permission.Motion);
22
+ void motion;
23
+ } catch (status) {
24
+ if (status === AuthorizationStatus.DeniedAlways) {
25
+ // Only the app-settings screen can restore the motion permission now.
26
+ }
27
+ }
28
+
29
+ void all;
30
+ void location;
31
+ }
32
+
33
+ void requestPermissionSurface;