@josuelmm/cordova-background-geolocation 4.2.2 → 4.5.1

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 (86) hide show
  1. package/.npmignore +11 -0
  2. package/CHANGELOG.md +213 -0
  3. package/HISTORY.md +73 -0
  4. package/README.md +45 -74
  5. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +24 -0
  6. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +61 -1
  7. package/android/common/src/main/AndroidManifest.xml +1 -1
  8. package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +6 -3
  9. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +65 -1
  10. package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +1 -1
  11. package/android/common/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +94 -0
  12. package/android/common/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +205 -0
  13. package/android/common/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +6 -0
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +5 -1
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +32 -1
  16. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +12 -2
  17. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +33 -2
  18. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +15 -1
  19. package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +23 -8
  20. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +246 -21
  21. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +5 -2
  22. package/android/common/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +46 -13
  23. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +23 -1
  24. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +111 -5
  25. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +20 -0
  26. package/ios/common/BackgroundGeolocation/MAURConfig.h +2 -0
  27. package/ios/common/BackgroundGeolocation/MAURConfig.m +16 -2
  28. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +3 -0
  29. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +3 -1
  30. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +15 -1
  31. package/ios/common/BackgroundGeolocation/MAURLocation.h +12 -0
  32. package/ios/common/BackgroundGeolocation/MAURLocation.m +33 -4
  33. package/ios/common/BackgroundGeolocation/MAURLocationContract.h +4 -0
  34. package/ios/common/BackgroundGeolocation/MAURLocationContract.m +5 -1
  35. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +9 -0
  36. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +59 -1
  37. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +54 -4
  38. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +12 -0
  39. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +125 -5
  40. package/package.json +36 -1
  41. package/plugin.xml +3 -2
  42. package/www/BackgroundGeolocation.d.ts +114 -3
  43. package/www/BackgroundGeolocation.js +11 -4
  44. package/CLAUDE.md +0 -56
  45. package/android/CDVBackgroundGeolocation/src/test/java/com/marianhello/ConfigMapperTest.java +0 -220
  46. package/android/common/src/androidTest/java/com/marianhello/bgloc/BackgroundGeolocationFacadeTest.java +0 -45
  47. package/android/common/src/androidTest/java/com/marianhello/bgloc/BatchManagerTest.java +0 -570
  48. package/android/common/src/androidTest/java/com/marianhello/bgloc/ConfigTest.java +0 -76
  49. package/android/common/src/androidTest/java/com/marianhello/bgloc/ContentProviderLocationDAOTest.java +0 -437
  50. package/android/common/src/androidTest/java/com/marianhello/bgloc/DBLogReaderTest.java +0 -95
  51. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationContentProviderTest.java +0 -159
  52. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceProxyTest.java +0 -161
  53. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceTest.java +0 -247
  54. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteConfigurationDAOTest.java +0 -200
  55. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOTest.java +0 -457
  56. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOThreadTest.java +0 -96
  57. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteOpenHelperTest.java +0 -225
  58. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestPluginDelegate.java +0 -46
  59. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestResourceResolver.java +0 -14
  60. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/MockLocationProvider.java +0 -50
  61. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/TestLocationProviderFactory.java +0 -17
  62. package/android/common/src/androidTest/java/com/marianhello/bgloc/sqlite/SQLiteOpenHelper10.java +0 -92
  63. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/LocationProviderTestCase.java +0 -107
  64. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/TestConstants.java +0 -5
  65. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ArrayListLocationTemplateTest.java +0 -82
  66. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/BackgroundLocationTest.java +0 -128
  67. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ConfigTest.java +0 -191
  68. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/DBLogReaderTest.java +0 -37
  69. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HashMapLocationTemplateTest.java +0 -216
  70. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HttpPostServiceTest.java +0 -223
  71. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/LocationTemplateFactoryTest.java +0 -50
  72. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/PostLocationTaskTest.java +0 -180
  73. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/TestHelper.java +0 -16
  74. package/ios/common/BackgroundGeolocationTests/Info.plist +0 -24
  75. package/ios/common/BackgroundGeolocationTests/MAURBackgroundLocationTest.m +0 -185
  76. package/ios/common/BackgroundGeolocationTests/MAURConfigTest.m +0 -161
  77. package/ios/common/BackgroundGeolocationTests/MAURGeolocationOpenHelperTest.m +0 -102
  78. package/ios/common/BackgroundGeolocationTests/MAURLocationTest.m +0 -216
  79. package/ios/common/BackgroundGeolocationTests/MAURLocationUploaderTest.m +0 -55
  80. package/ios/common/BackgroundGeolocationTests/MAURLogReaderTest.m +0 -43
  81. package/ios/common/BackgroundGeolocationTests/MAURSQLiteConfigurationDAOTest.m +0 -102
  82. package/ios/common/BackgroundGeolocationTests/MAURSQLiteHelperTest.m +0 -41
  83. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOTests.m +0 -240
  84. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOThreadTest.m +0 -84
  85. package/ios/common/BackgroundGeolocationTests/MAURSQLiteOpenHelperTest.m +0 -144
  86. package/ios/common/scripts/xcode-refactor.js +0 -184
@@ -26,7 +26,11 @@
26
26
  @{ @"name": @LC_COLUMN_NAME_PROVIDER, @"type": [SQLColumnType sqlColumnWithType: kText]},
27
27
  @{ @"name": @LC_COLUMN_NAME_LOCATION_PROVIDER, @"type": [SQLColumnType sqlColumnWithType: kText]},
28
28
  @{ @"name": @LC_COLUMN_NAME_STATUS, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
29
- @{ @"name": @LC_COLUMN_NAME_RECORDED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]}
29
+ @{ @"name": @LC_COLUMN_NAME_RECORDED_AT, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
30
+ // v4.5 — survive sync queue
31
+ @{ @"name": @LC_COLUMN_NAME_EVENTS_JSON, @"type": [SQLColumnType sqlColumnWithType: kText]},
32
+ @{ @"name": @LC_COLUMN_NAME_BATTERY_LEVEL, @"type": [SQLColumnType sqlColumnWithType: kInteger]},
33
+ @{ @"name": @LC_COLUMN_NAME_IS_CHARGING, @"type": [SQLColumnType sqlColumnWithType: kInteger]}
30
34
  ];
31
35
 
32
36
  return [MAURSQLiteHelper createTableSqlStatement:@LC_TABLE_NAME columns:columns];
@@ -30,6 +30,15 @@
30
30
 
31
31
  @property (nonatomic, weak) MAURConfig * _Nullable config;
32
32
  @property (nonatomic, weak) id<MAURPostLocationTaskDelegate> _Nullable delegate;
33
+ /** v4.5.1 — pending driving events buffer owned by the facade; the task drains it onto the
34
+ * post-transform location so events fired without a simultaneous fix (provider change,
35
+ * sensor crash, phone usage) survive even if `locationTransform` returns a new instance.
36
+ * Weak ref: if the facade is gone, no flush — by design. */
37
+ @property (nonatomic, weak) NSMutableArray * _Nullable pendingDrivingEventsBuffer;
38
+ /** v4.5.1 — same idea for the battery snapshot block. The facade installs a block that the
39
+ * task invokes AFTER a successful transform, so even when `locationTransform` returns a
40
+ * fresh instance, battery/charging fields land on what actually gets POSTed. */
41
+ @property (nonatomic, copy) void (^ _Nullable attachBatterySnapshot)(MAURLocation * _Nonnull);
33
42
 
34
43
  - (void) add:(MAURLocation * _Nonnull)location;
35
44
  - (void) start;
@@ -83,11 +83,63 @@ static MAURLocationTransform s_locationTransform = nil;
83
83
  MAURLocation *location = inLocation;
84
84
 
85
85
  if (locationTransform != nil) {
86
+ // v4.5.1 — snapshot v4.3+ fields BEFORE transform so they survive a transform
87
+ // that returns a brand new MAURLocation instance (otherwise events/battery would
88
+ // be lost en route to SQLite / backend).
89
+ NSMutableArray *rawEvents = inLocation.drivingEvents;
90
+ NSNumber *rawBattery = inLocation.batteryLevel;
91
+ NSNumber *rawCharging = inLocation.isCharging;
92
+
86
93
  location = locationTransform(location);
87
94
 
88
95
  if (location == nil) {
89
96
  return;
90
97
  }
98
+
99
+ // v4.5.1 — re-attach fields the transform may have dropped. When the transform
100
+ // produced a NEW instance (`location != inLocation`), MERGE rawEvents into the new
101
+ // instance's array instead of overwriting — same semantics as Android
102
+ // `LocationServiceImpl.onLocation` re-attach. If the transform returned the same
103
+ // instance (mutated in place) the rawEvents are already there.
104
+ if (location != inLocation) {
105
+ if (rawEvents != nil && [rawEvents count] > 0) {
106
+ if (location.drivingEvents == nil) {
107
+ location.drivingEvents = [rawEvents mutableCopy];
108
+ } else {
109
+ [location.drivingEvents addObjectsFromArray:rawEvents];
110
+ }
111
+ }
112
+ if (location.batteryLevel == nil) location.batteryLevel = rawBattery;
113
+ if (location.isCharging == nil) location.isCharging = rawCharging;
114
+ }
115
+ }
116
+
117
+ // v4.5.1 — drain pending driving events ONTO the post-transform location. Previously
118
+ // the facade drained them BEFORE [postLocationTask add:], so a transform that returned
119
+ // nil silently lost every buffered event. Now: if transform succeeded we're guaranteed
120
+ // `location != nil` here and the buffer is drained safely.
121
+ NSMutableArray *pendingBuffer = self.pendingDrivingEventsBuffer;
122
+ if (pendingBuffer != nil) {
123
+ @synchronized (pendingBuffer) {
124
+ if ([pendingBuffer count] > 0) {
125
+ NSTimeInterval nowMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
126
+ if (location.drivingEvents == nil) location.drivingEvents = [NSMutableArray array];
127
+ for (NSDictionary *ev in pendingBuffer) {
128
+ NSNumber *t = ev[@"time"];
129
+ NSTimeInterval evMs = t != nil ? [t doubleValue] : nowMs;
130
+ if (nowMs - evMs <= 60000.0) {
131
+ [location.drivingEvents addObject:ev];
132
+ }
133
+ }
134
+ [pendingBuffer removeAllObjects];
135
+ }
136
+ }
137
+ }
138
+ // v4.5.1 — stamp battery snapshot AFTER transform so it lands on the POSTed instance
139
+ // even if the transform created a new one.
140
+ void (^attachBattery)(MAURLocation *) = self.attachBatterySnapshot;
141
+ if (attachBattery != nil) {
142
+ attachBattery(location);
91
143
  }
92
144
 
93
145
  // v3.5 Phase 4: mock location policy. Detection already exists in MAURLocation.simulated.
@@ -255,7 +307,8 @@ static MAURLocationTransform s_locationTransform = nil;
255
307
  return YES;
256
308
  }
257
309
 
258
- if (*outError == nil) {
310
+ // v4.4.1: guard against outError == NULL (defensive — current callers pass &error).
311
+ if (outError == NULL || *outError == nil) {
259
312
  DDLogDebug(@"%@ Server error while posting locations responseCode: %ld", TAG, (long)statusCode);
260
313
  } else {
261
314
  DDLogError(@"%@ Error while posting locations %@", TAG, [*outError localizedDescription]);
@@ -269,6 +322,11 @@ static MAURLocationTransform s_locationTransform = nil;
269
322
  if (![self.config syncEnabled] || ![self.config hasValidSyncUrl]) {
270
323
  return;
271
324
  }
325
+ // v4.5.1 — rescue rows stuck in SyncPending from a previous upload that never completed
326
+ // (app/process killed mid-flight). Anything older than 15 min is safe to revert to
327
+ // PostPending; rows younger than that may still be uploading on a background NSURLSession.
328
+ NSTimeInterval staleCutoff = [[NSDate date] timeIntervalSince1970] - (15 * 60);
329
+ [[MAURSQLiteLocationDAO sharedInstance] restoreStaleSyncLocationsOlderThan:staleCutoff error:nil];
272
330
  // For sync (batch) only static queryParams placeholders apply; per-location templating
273
331
  // belongs in real-time post (httpMode="single" + httpMethod=GET) instead.
274
332
  NSString *resolvedSyncUrl = [MAURUrlTemplateResolver resolve:self.config.syncUrl location:nil queryParams:self.config.queryParams];
@@ -88,7 +88,8 @@
88
88
  @COMMA_SEP @CC_COLUMN_NAME_PAUSE_LOCATION_UPDATES
89
89
  @COMMA_SEP @CC_COLUMN_NAME_TEMPLATE
90
90
  @COMMA_SEP @CC_COLUMN_NAME_LAST_UPDATED_AT
91
- @") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,DateTime('now'))";
91
+ @COMMA_SEP @CC_COLUMN_NAME_CONFIG_JSON
92
+ @") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,DateTime('now'),?)";
92
93
 
93
94
  [queue inDatabase:^(FMDatabase *database) {
94
95
  success = [database executeUpdate:sql,
@@ -119,7 +120,9 @@
119
120
  [config hasSaveBatteryOnBackground] ? config._saveBatteryOnBackground : @CC_COLUMN_NAME_NULLABLE,
120
121
  [config hasMaxLocations] ? config.maxLocations : @CC_COLUMN_NAME_NULLABLE,
121
122
  [config hasPauseLocationUpdates] ? config._pauseLocationUpdates : @CC_COLUMN_NAME_NULLABLE,
122
- (templateString != nil) ? templateString : @CC_COLUMN_NAME_NULLABLE
123
+ (templateString != nil) ? templateString : @CC_COLUMN_NAME_NULLABLE,
124
+ // v4.5: full Config as JSON for paridad con Android
125
+ [self serializeConfigToJson:config]
123
126
  ];
124
127
 
125
128
  if (success) {
@@ -165,6 +168,7 @@
165
168
  @COMMA_SEP @CC_COLUMN_NAME_MAX_LOCATIONS
166
169
  @COMMA_SEP @CC_COLUMN_NAME_PAUSE_LOCATION_UPDATES
167
170
  @COMMA_SEP @CC_COLUMN_NAME_TEMPLATE
171
+ @COMMA_SEP @CC_COLUMN_NAME_CONFIG_JSON
168
172
  @" FROM " @CC_TABLE_NAME @" WHERE " @CC_COLUMN_NAME_ID @" = 1";
169
173
 
170
174
  [queue inDatabase:^(FMDatabase *database) {
@@ -231,14 +235,60 @@
231
235
  config._template = [NSJSONSerialization JSONObjectWithData:jsonTemplate options:0 error:nil];
232
236
  }
233
237
  }
238
+ // v4.5: rehydrate post-3.2 keys from config_json blob (paridad Android).
239
+ // Index 28 is the new column. Strict NULL check (no NULLHACK sentinel for JSON column).
240
+ if (![rs columnIndexIsNull:28]) {
241
+ NSString *jsonString = [rs stringForColumnIndex:28];
242
+ if (jsonString != nil && jsonString.length > 0) {
243
+ [self applyConfigJson:jsonString to:config];
244
+ }
245
+ }
234
246
  }
235
-
247
+
236
248
  [rs close];
237
249
  }];
238
-
250
+
239
251
  return config;
240
252
  }
241
253
 
254
+ // v4.5: serialize all post-3.2 keys to JSON for storage. Mirrors Android ConfigJsonMapper.
255
+ - (NSString*) serializeConfigToJson:(MAURConfig*)config
256
+ {
257
+ NSMutableDictionary *j = [NSMutableDictionary dictionary];
258
+ if (config.httpMethod != nil) j[@"httpMethod"] = config.httpMethod;
259
+ if (config.syncHttpMethod != nil) j[@"syncHttpMethod"] = config.syncHttpMethod;
260
+ if (config.httpMode != nil) j[@"httpMode"] = config.httpMode;
261
+ if (config.syncMode != nil) j[@"syncMode"] = config.syncMode;
262
+ if (config.queryParams != nil) j[@"queryParams"] = config.queryParams;
263
+ if (config.heartbeatInterval != nil) j[@"heartbeatInterval"] = config.heartbeatInterval;
264
+ if (config.mockLocationPolicy != nil) j[@"mockLocationPolicy"] = config.mockLocationPolicy;
265
+ if (config.drivingEvents != nil) j[@"drivingEvents"] = config.drivingEvents;
266
+ if (config.includeBattery != nil) j[@"includeBattery"] = config.includeBattery;
267
+ if (config._showsBackgroundLocationIndicator != nil) j[@"showsBackgroundLocationIndicator"] = config._showsBackgroundLocationIndicator;
268
+ NSError *err = nil;
269
+ NSData *data = [NSJSONSerialization dataWithJSONObject:j options:0 error:&err];
270
+ if (err != nil || data == nil) return @"";
271
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
272
+ }
273
+
274
+ - (void) applyConfigJson:(NSString*)jsonString to:(MAURConfig*)config
275
+ {
276
+ NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
277
+ NSError *err = nil;
278
+ NSDictionary *j = [NSJSONSerialization JSONObjectWithData:data options:0 error:&err];
279
+ if (err != nil || ![j isKindOfClass:[NSDictionary class]]) return;
280
+ if (j[@"httpMethod"]) config.httpMethod = j[@"httpMethod"];
281
+ if (j[@"syncHttpMethod"]) config.syncHttpMethod = j[@"syncHttpMethod"];
282
+ if (j[@"httpMode"]) config.httpMode = j[@"httpMode"];
283
+ if (j[@"syncMode"]) config.syncMode = j[@"syncMode"];
284
+ if ([j[@"queryParams"] isKindOfClass:[NSDictionary class]]) config.queryParams = [j[@"queryParams"] mutableCopy];
285
+ if (j[@"heartbeatInterval"]) config.heartbeatInterval = j[@"heartbeatInterval"];
286
+ if (j[@"mockLocationPolicy"]) config.mockLocationPolicy = j[@"mockLocationPolicy"];
287
+ if ([j[@"drivingEvents"] isKindOfClass:[NSDictionary class]]) config.drivingEvents = j[@"drivingEvents"];
288
+ if (j[@"includeBattery"] != nil) config.includeBattery = j[@"includeBattery"];
289
+ if (j[@"showsBackgroundLocationIndicator"] != nil) config._showsBackgroundLocationIndicator = j[@"showsBackgroundLocationIndicator"];
290
+ }
291
+
242
292
  - (BOOL) clearDatabase
243
293
  {
244
294
  __block BOOL success;
@@ -28,6 +28,18 @@
28
28
  - (BOOL) deleteAllLocations:(NSError * __autoreleasing *)outError;
29
29
  /** Mark all locations pending sync (PostPending) as deleted. Clears the sync queue without sending. */
30
30
  - (BOOL) deletePendingSyncLocations:(NSError * __autoreleasing *)outError;
31
+ /** v4.5.1 — soft-delete only sync-pending rows whose `recorded_at` is <= cutoff (UNIX seconds).
32
+ * Used after a successful background-sync POST so locations persisted DURING the upload (race
33
+ * window) are NOT incorrectly marked deleted. */
34
+ - (BOOL) deleteSyncedLocationsBefore:(NSTimeInterval)cutoff error:(NSError * __autoreleasing *)outError;
35
+ /** v4.5.1 — undo the in-flight SyncPending state when the upload failed. SyncPending → PostPending
36
+ * so the next sync window re-tries them. Without this, a network failure during background-sync
37
+ * would silently drop every pending location. */
38
+ - (BOOL) restoreFailedSyncLocations:(NSError * __autoreleasing *)outError;
39
+ /** v4.5.1 — recover SyncPending rows that got stuck (app/process killed between getLocationsForSync
40
+ * and the upload's success/failure callback). Rows whose `recorded_at` is older than `cutoff`
41
+ * (UNIX seconds) are restored to PostPending so they get retried. Call before each sync window. */
42
+ - (BOOL) restoreStaleSyncLocationsOlderThan:(NSTimeInterval)cutoff error:(NSError * __autoreleasing *)outError;
31
43
  - (BOOL) clearDatabase;
32
44
  - (NSString*) getDatabaseName;
33
45
  - (NSString*) getDatabasePath;
@@ -94,9 +94,17 @@
94
94
  }
95
95
  [rs close];
96
96
 
97
- sql = @"UPDATE " @LC_TABLE_NAME @" SET " @LC_COLUMN_NAME_STATUS @" = ?";
98
- if (![database executeUpdate:sql, [NSString stringWithFormat:@"%ld", MAURLocationDeleted]]) {
99
- NSLog(@"Deleting all location failed code: %d: message: %@", [database lastErrorCode], [database lastErrorMessage]);
97
+ // v4.5.1 FIX (CRITICAL): mark the rows we just selected as SyncPending — NOT Deleted.
98
+ // The previous code UPDATEd the WHOLE table to Deleted before the upload had even started,
99
+ // losing every fix on HTTP failure / network drop. Now:
100
+ // PostPending → SyncPending (in-flight, do not re-include)
101
+ // on success in the network task: SyncPending → Deleted (deleteSyncedLocationsBefore:)
102
+ // on failure in the network task: SyncPending → PostPending (restoreFailedSyncLocations)
103
+ NSString *upd = @"UPDATE " @LC_TABLE_NAME @" SET " @LC_COLUMN_NAME_STATUS @" = ? WHERE " @LC_COLUMN_NAME_STATUS @" = ?";
104
+ if (![database executeUpdate:upd,
105
+ [NSString stringWithFormat:@"%ld", MAURLocationSyncPending],
106
+ [NSString stringWithFormat:@"%ld", MAURLocationPostPending]]) {
107
+ NSLog(@"Marking PostPending → SyncPending failed code: %d: message: %@", [database lastErrorCode], [database lastErrorMessage]);
100
108
  }
101
109
  }];
102
110
 
@@ -139,7 +147,18 @@
139
147
  @COMMA_SEP @LC_COLUMN_NAME_LOCATION_PROVIDER
140
148
  @COMMA_SEP @LC_COLUMN_NAME_STATUS
141
149
  @COMMA_SEP @LC_COLUMN_NAME_RECORDED_AT
142
- @") VALUES (?,?,?,?,?,?,?,?,?,?,?)";
150
+ @COMMA_SEP @LC_COLUMN_NAME_EVENTS_JSON
151
+ @COMMA_SEP @LC_COLUMN_NAME_BATTERY_LEVEL
152
+ @COMMA_SEP @LC_COLUMN_NAME_IS_CHARGING
153
+ @") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
154
+
155
+ // v4.5: serialize driving events array to JSON for SQLite storage.
156
+ NSString *eventsJson = nil;
157
+ if (location.drivingEvents != nil && [location.drivingEvents count] > 0) {
158
+ NSError *jerr = nil;
159
+ NSData *jd = [NSJSONSerialization dataWithJSONObject:location.drivingEvents options:0 error:&jerr];
160
+ if (jd != nil) eventsJson = [[NSString alloc] initWithData:jd encoding:NSUTF8StringEncoding];
161
+ }
143
162
 
144
163
  BOOL success = [database executeUpdate:sql,
145
164
  [NSNumber numberWithDouble:[location.time timeIntervalSince1970]],
@@ -152,7 +171,10 @@
152
171
  location.provider ?: [NSNull null],
153
172
  location.locationProvider ?: [NSNull null],
154
173
  location.isValid == YES ? @(1) : @(0),
155
- recordedAt
174
+ recordedAt,
175
+ eventsJson ?: [NSNull null],
176
+ location.batteryLevel ?: [NSNull null],
177
+ location.isCharging ?: [NSNull null]
156
178
  ];
157
179
 
158
180
  if (success) {
@@ -223,8 +245,19 @@
223
245
  @COMMA_SEP @LC_COLUMN_NAME_LOCATION_PROVIDER @EQ_BIND
224
246
  @COMMA_SEP @LC_COLUMN_NAME_STATUS @EQ_BIND
225
247
  @COMMA_SEP @LC_COLUMN_NAME_RECORDED_AT @EQ_BIND
248
+ @COMMA_SEP @LC_COLUMN_NAME_EVENTS_JSON @EQ_BIND
249
+ @COMMA_SEP @LC_COLUMN_NAME_BATTERY_LEVEL @EQ_BIND
250
+ @COMMA_SEP @LC_COLUMN_NAME_IS_CHARGING @EQ_BIND
226
251
  @" WHERE " @LC_COLUMN_NAME_ID @EQ_BIND;
227
252
 
253
+ // v4.5.1: serialize events for UPDATE path so old values don't bleed onto new locations
254
+ NSString *eventsJsonForUpdate = nil;
255
+ if (location.drivingEvents != nil && [location.drivingEvents count] > 0) {
256
+ NSError *jerr = nil;
257
+ NSData *jd = [NSJSONSerialization dataWithJSONObject:location.drivingEvents options:0 error:&jerr];
258
+ if (jd != nil) eventsJsonForUpdate = [[NSString alloc] initWithData:jd encoding:NSUTF8StringEncoding];
259
+ }
260
+
228
261
  BOOL success = [database executeUpdate:sql,
229
262
  [NSNumber numberWithDouble:[location.time timeIntervalSince1970]],
230
263
  location.accuracy,
@@ -237,6 +270,9 @@
237
270
  location.locationProvider ?: [NSNull null],
238
271
  location.isValid == YES ? @(1) : @(0),
239
272
  recordedAt,
273
+ eventsJsonForUpdate ?: [NSNull null],
274
+ location.batteryLevel ?: [NSNull null],
275
+ location.isCharging ?: [NSNull null],
240
276
  locationId
241
277
  ];
242
278
 
@@ -304,6 +340,77 @@
304
340
  return success;
305
341
  }
306
342
 
343
+ - (BOOL) deleteSyncedLocationsBefore:(NSTimeInterval)cutoff error:(NSError * __autoreleasing *)outError
344
+ {
345
+ __block BOOL success = YES;
346
+ // v4.5.1 — operate on SyncPending (the rows the network task is/was uploading), not on
347
+ // PostPending (those are still queued for real-time POST and must NOT be touched).
348
+ NSString *sql = @"UPDATE " @LC_TABLE_NAME
349
+ @" SET " @LC_COLUMN_NAME_STATUS @" = ? "
350
+ @" WHERE " @LC_COLUMN_NAME_STATUS @" = ? AND " @LC_COLUMN_NAME_RECORDED_AT @" <= ?";
351
+ [queue inDatabase:^(FMDatabase *database) {
352
+ if (![database executeUpdate:sql,
353
+ @(MAURLocationDeleted),
354
+ @(MAURLocationSyncPending),
355
+ @(cutoff)]) {
356
+ int errorCode = [database lastErrorCode];
357
+ NSString *errorMessage = [database lastErrorMessage];
358
+ NSLog(@"deleteSyncedLocationsBefore failed code: %d: message: %@", errorCode, errorMessage);
359
+ if (outError != NULL) {
360
+ *outError = [NSError errorWithDomain:Domain code:errorCode userInfo:@{ NSLocalizedDescriptionKey: errorMessage ?: @"" }];
361
+ }
362
+ success = NO;
363
+ }
364
+ }];
365
+ return success;
366
+ }
367
+
368
+ - (BOOL) restoreStaleSyncLocationsOlderThan:(NSTimeInterval)cutoff error:(NSError * __autoreleasing *)outError
369
+ {
370
+ // v4.5.1 — rows left as SyncPending because the previous sync's task was killed
371
+ // (app suspended mid-upload, OS process death, manual kill) never reach their
372
+ // success/failure callback. Call at the start of each sync window to rescue them.
373
+ __block BOOL success = YES;
374
+ NSString *sql = @"UPDATE " @LC_TABLE_NAME
375
+ @" SET " @LC_COLUMN_NAME_STATUS @" = ? "
376
+ @" WHERE " @LC_COLUMN_NAME_STATUS @" = ? AND " @LC_COLUMN_NAME_RECORDED_AT @" < ?";
377
+ [queue inDatabase:^(FMDatabase *database) {
378
+ if (![database executeUpdate:sql,
379
+ @(MAURLocationPostPending),
380
+ @(MAURLocationSyncPending),
381
+ @(cutoff)]) {
382
+ int errorCode = [database lastErrorCode];
383
+ NSString *errorMessage = [database lastErrorMessage];
384
+ NSLog(@"restoreStaleSyncLocations failed code: %d: message: %@", errorCode, errorMessage);
385
+ if (outError != NULL) {
386
+ *outError = [NSError errorWithDomain:Domain code:errorCode userInfo:@{ NSLocalizedDescriptionKey: errorMessage ?: @"" }];
387
+ }
388
+ success = NO;
389
+ }
390
+ }];
391
+ return success;
392
+ }
393
+
394
+ - (BOOL) restoreFailedSyncLocations:(NSError * __autoreleasing *)outError
395
+ {
396
+ // v4.5.1 — undo the in-flight transition: SyncPending → PostPending so the next sync window
397
+ // (or real-time post) re-tries them.
398
+ __block BOOL success = YES;
399
+ NSString *sql = @"UPDATE " @LC_TABLE_NAME @" SET " @LC_COLUMN_NAME_STATUS @" = ? WHERE " @LC_COLUMN_NAME_STATUS @" = ?";
400
+ [queue inDatabase:^(FMDatabase *database) {
401
+ if (![database executeUpdate:sql, @(MAURLocationPostPending), @(MAURLocationSyncPending)]) {
402
+ int errorCode = [database lastErrorCode];
403
+ NSString *errorMessage = [database lastErrorMessage];
404
+ NSLog(@"restoreFailedSyncLocations failed code: %d: message: %@", errorCode, errorMessage);
405
+ if (outError != NULL) {
406
+ *outError = [NSError errorWithDomain:Domain code:errorCode userInfo:@{ NSLocalizedDescriptionKey: errorMessage ?: @"" }];
407
+ }
408
+ success = NO;
409
+ }
410
+ }];
411
+ return success;
412
+ }
413
+
307
414
  - (BOOL) deletePendingSyncLocations:(NSError * __autoreleasing *)outError
308
415
  {
309
416
  __block BOOL success = YES;
@@ -368,6 +475,9 @@
368
475
  @COMMA_SEP @LC_COLUMN_NAME_LOCATION_PROVIDER
369
476
  @COMMA_SEP @LC_COLUMN_NAME_STATUS
370
477
  @COMMA_SEP @LC_COLUMN_NAME_RECORDED_AT
478
+ @COMMA_SEP @LC_COLUMN_NAME_EVENTS_JSON
479
+ @COMMA_SEP @LC_COLUMN_NAME_BATTERY_LEVEL
480
+ @COMMA_SEP @LC_COLUMN_NAME_IS_CHARGING
371
481
  @" FROM " @LC_TABLE_NAME;
372
482
  }
373
483
 
@@ -387,6 +497,16 @@
387
497
  location.isValid = [rs intForColumnIndex:10] == 1 ? YES : NO;
388
498
  NSTimeInterval recordedAt = [rs longForColumnIndex:11];
389
499
  location.recordedAt = [NSDate dateWithTimeIntervalSince1970:recordedAt];
500
+ // v4.5: events / battery / charging
501
+ NSString *eventsJson = [rs stringForColumnIndex:12];
502
+ if (eventsJson != nil && eventsJson.length > 0) {
503
+ NSError *jerr = nil;
504
+ id parsed = [NSJSONSerialization JSONObjectWithData:[eventsJson dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&jerr];
505
+ if ([parsed isKindOfClass:[NSMutableArray class]]) location.drivingEvents = parsed;
506
+ else if ([parsed isKindOfClass:[NSArray class]]) location.drivingEvents = [parsed mutableCopy];
507
+ }
508
+ if (![rs columnIndexIsNull:13]) location.batteryLevel = @([rs intForColumnIndex:13]);
509
+ if (![rs columnIndexIsNull:14]) location.isCharging = @([rs intForColumnIndex:14] == 1);
390
510
  return location;
391
511
  }
392
512
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josuelmm/cordova-background-geolocation",
3
- "version": "4.2.2",
3
+ "version": "4.5.1",
4
4
  "description": "Cordova Background Geolocation (fork actualizado)",
5
5
  "main": "./www/BackgroundGeolocation.js",
6
6
  "types": "./www/BackgroundGeolocation.d.ts",
@@ -104,6 +104,41 @@
104
104
  "cordova": ">=10.0.0",
105
105
  "cordova-android": ">=12.0.0",
106
106
  "cordova-ios": ">=6.2.0"
107
+ },
108
+ "4.2.3": {
109
+ "cordova": ">=10.0.0",
110
+ "cordova-android": ">=12.0.0",
111
+ "cordova-ios": ">=6.2.0"
112
+ },
113
+ "4.2.4": {
114
+ "cordova": ">=10.0.0",
115
+ "cordova-android": ">=12.0.0",
116
+ "cordova-ios": ">=6.2.0"
117
+ },
118
+ "4.3.0": {
119
+ "cordova": ">=10.0.0",
120
+ "cordova-android": ">=12.0.0",
121
+ "cordova-ios": ">=6.2.0"
122
+ },
123
+ "4.4.0": {
124
+ "cordova": ">=10.0.0",
125
+ "cordova-android": ">=12.0.0",
126
+ "cordova-ios": ">=6.2.0"
127
+ },
128
+ "4.4.1": {
129
+ "cordova": ">=10.0.0",
130
+ "cordova-android": ">=12.0.0",
131
+ "cordova-ios": ">=6.2.0"
132
+ },
133
+ "4.5.0": {
134
+ "cordova": ">=10.0.0",
135
+ "cordova-android": ">=12.0.0",
136
+ "cordova-ios": ">=6.2.0"
137
+ },
138
+ "4.5.1": {
139
+ "cordova": ">=10.0.0",
140
+ "cordova-android": ">=12.0.0",
141
+ "cordova-ios": ">=6.2.0"
107
142
  }
108
143
  }
109
144
  },
package/plugin.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
3
3
  xmlns:android="http://schemas.android.com/apk/res/android"
4
4
  id="cordova-background-geolocation"
5
- version="4.2.2">
5
+ version="4.5.1">
6
6
  <name>cordova-background-geolocation</name>
7
7
  <description>Cordova Background Geolocation Plugin</description>
8
8
  <license>Apache-2.0</license>
@@ -59,6 +59,7 @@
59
59
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/BackgroundActivity.java" target-dir="src/com/marianhello/bgloc/data" />
60
60
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java" target-dir="src/com/marianhello/bgloc/data" />
61
61
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/ConfigurationDAO.java" target-dir="src/com/marianhello/bgloc/data" />
62
+ <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java" target-dir="src/com/marianhello/bgloc/data" />
62
63
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/DAOFactory.java" target-dir="src/com/marianhello/bgloc/data" />
63
64
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/HashMapLocationTemplate.java" target-dir="src/com/marianhello/bgloc/data" />
64
65
  <source-file src="android/common/src/main/java/com/marianhello/bgloc/data/LocationDAO.java" target-dir="src/com/marianhello/bgloc/data" />
@@ -196,7 +197,7 @@
196
197
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
197
198
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
198
199
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
199
- <uses-permission android:name="android.hardware.location" />
200
+ <uses-feature android:name="android.hardware.location" android:required="false" />
200
201
  </config-file>
201
202
  <config-file target="res/xml/config.xml" parent="/*">
202
203
  <feature name="BackgroundGeolocation">
@@ -153,7 +153,7 @@ export interface ConfigureOptions {
153
153
  * Platform: Android
154
154
  * Provider: all
155
155
  *
156
- * @default 60000
156
+ * @default 600000
157
157
  * @see {@link https://bit.ly/1x00RUu|Android docs}
158
158
  */
159
159
  interval?: number;
@@ -510,6 +510,52 @@ export interface ConfigureOptions {
510
510
  */
511
511
  mockLocationPolicy?: 'allow' | 'flag' | 'drop';
512
512
 
513
+ /**
514
+ * v4.4 — Stamp device battery percentage (0-100) and charging state on every location
515
+ * sent to the backend. Default `true`. Set `false` to opt out.
516
+ *
517
+ * - With `bodyTemplate`/`postTemplate`, use placeholders `'@battery'` and `'@isCharging'`.
518
+ * - Default JSON includes `battery` and `isCharging` keys automatically.
519
+ *
520
+ * Android: read via `BatteryManager` sticky broadcast (no permission required).
521
+ * iOS: read via `UIDevice.batteryLevel` (free).
522
+ *
523
+ * Platform: Android, iOS
524
+ * @since 4.4.0
525
+ */
526
+ includeBattery?: boolean;
527
+
528
+ /**
529
+ * v4.5.1 — WakeLock policy (Android only). Controls whether the service holds a
530
+ * `PARTIAL_WAKE_LOCK` while tracking. Default `'posting'`.
531
+ *
532
+ * - `'none'` — never acquire a wake lock. Best battery, but the device may
533
+ * sleep mid-POST. Recommended only with `httpMode: 'batch'`.
534
+ * - `'posting'` — acquire a 30 s wake lock on every fix while writing to SQLite
535
+ * and posting. Default. Good battery / reliability trade-off.
536
+ * - `'always'` — keep CPU awake the whole time the service is running. Most
537
+ * reliable, worst battery. Use only for fleet/emergency apps.
538
+ *
539
+ * @platform Android
540
+ * @since 4.5.1
541
+ */
542
+ wakeLockMode?: 'none' | 'posting' | 'always';
543
+
544
+ /**
545
+ * v4.5.1 — Stationary detection knobs (Android `DISTANCE_FILTER_PROVIDER`).
546
+ * Override the previously hard-coded constants. All values in milliseconds.
547
+ *
548
+ * - `stationaryTimeout` (default 300_000) — time of no movement before declaring stationary.
549
+ * - `stationaryPollInterval` (default 180_000) — lazy poll while stationary.
550
+ * - `stationaryPollFast` (default 60_000) — aggressive poll near boundary.
551
+ *
552
+ * @platform Android
553
+ * @since 4.5.1
554
+ */
555
+ stationaryTimeout?: number;
556
+ stationaryPollInterval?: number;
557
+ stationaryPollFast?: number;
558
+
513
559
  /**
514
560
  * v4.0 Phase 6 — Driver insights configuration. Enables a GPS-based state machine
515
561
  * that emits `moving`, `stopped`, `tripStart`, `tripEnd`, `speeding` and
@@ -643,6 +689,31 @@ export interface Location {
643
689
  * True if location was simulated by software (e.g. Simulator). (iOS 15+)
644
690
  */
645
691
  simulated?: boolean;
692
+
693
+ /**
694
+ * v4.3 — Driving events anexados a este fix por el detector interno.
695
+ *
696
+ * Solo presente cuando un evento se disparó al mismo tiempo que esta location y
697
+ * `drivingEvents.enabled` está activo. Cada elemento es `{ type, time, ...payload }`,
698
+ * donde `payload` depende del tipo:
699
+ * - hardBrake / rapidAcceleration / sharpTurn → `value: number`
700
+ * - speeding → `speedKmh: number, limitKmh: number`
701
+ * - tripEnd → `distance: number, durationMs: number`
702
+ * - possibleCrash → `value: number, source: 'gps' | 'sensor'`
703
+ * - providerChange → `provider: string`
704
+ * - moving / stopped / tripStart / phoneUsageWhileDriving → solo type+time.
705
+ *
706
+ * Desde v4.5.0, `events` se persiste en la cola de sync y sobrevive a POST fallidos.
707
+ *
708
+ * @since 4.3.0
709
+ */
710
+ events?: Array<{ type: string; time: number; [key: string]: any }>;
711
+
712
+ /** v4.4 — Device battery percentage (0-100) at the time of the fix. Disabled with
713
+ * `includeBattery: false` in ConfigureOptions. @since 4.4.0 */
714
+ battery?: number;
715
+ /** v4.4 — Whether the device is charging at the time of the fix. @since 4.4.0 */
716
+ isCharging?: boolean;
646
717
  }
647
718
 
648
719
  export interface StationaryLocation extends Location {
@@ -972,6 +1043,39 @@ export interface BackgroundGeolocationPlugin {
972
1043
  fail?: (error: BackgroundGeolocationError) => void
973
1044
  ): Promise<void>;
974
1045
 
1046
+ /**
1047
+ * v4.5 — Request `ACCESS_BACKGROUND_LOCATION` runtime permission (Android 10+).
1048
+ * On Android < 10 resolves immediately as `{ granted: true, notRequired: true }`.
1049
+ * On iOS resolves as `{ granted: true, notRequired: true }` (Apple does not surface
1050
+ * a separate background permission — the standard "Always" authorization covers it).
1051
+ * @since 4.5.0
1052
+ */
1053
+ requestBackgroundLocationPermission(
1054
+ success?: (result: { granted: boolean; denied?: string[]; notRequired?: boolean }) => void,
1055
+ fail?: (error: BackgroundGeolocationError) => void
1056
+ ): Promise<{ granted: boolean; denied?: string[]; notRequired?: boolean }>;
1057
+
1058
+ /**
1059
+ * v4.5 — Request `ACTIVITY_RECOGNITION` runtime permission (Android 10+).
1060
+ * Required by `ActivityLocationProvider`. iOS / Android < 10 resolve `granted: true, notRequired`.
1061
+ * @since 4.5.0
1062
+ */
1063
+ requestActivityRecognitionPermission(
1064
+ success?: (result: { granted: boolean; denied?: string[]; notRequired?: boolean }) => void,
1065
+ fail?: (error: BackgroundGeolocationError) => void
1066
+ ): Promise<{ granted: boolean; denied?: string[]; notRequired?: boolean }>;
1067
+
1068
+ /**
1069
+ * v4.5 — Request `POST_NOTIFICATIONS` runtime permission (Android 13+).
1070
+ * Without it the foreground-service notification is invisible. iOS / Android < 13
1071
+ * resolve `granted: true, notRequired`.
1072
+ * @since 4.5.0
1073
+ */
1074
+ requestNotificationPermission(
1075
+ success?: (result: { granted: boolean; denied?: string[]; notRequired?: boolean }) => void,
1076
+ fail?: (error: BackgroundGeolocationError) => void
1077
+ ): Promise<{ granted: boolean; denied?: string[]; notRequired?: boolean }>;
1078
+
975
1079
  /**
976
1080
  * Show app settings to allow change of app location permissions.
977
1081
  *
@@ -1245,13 +1349,20 @@ export interface BackgroundGeolocationPlugin {
1245
1349
  ): Promise<void>;
1246
1350
 
1247
1351
  /**
1248
- * A special task that gets executed when the app is terminated, but
1249
- * the plugin was configured to continue running in the background
1352
+ * **Android only.** A special task that gets executed when the app is terminated,
1353
+ * but the plugin was configured to continue running in the background
1250
1354
  * (option <code>stopOnTerminate: false</code>).
1251
1355
  *
1252
1356
  * In this scenario the Activity was killed by the system and all registered
1253
1357
  * event listeners will not be triggered until the app is relaunched.
1254
1358
  *
1359
+ * **iOS:** Apple does not support running JS in a killed-app scenario the same
1360
+ * way; on iOS this method is a no-op. Use `significantLocationChanges` and the
1361
+ * normal `BackgroundGeolocation.on(...)` listeners with the standard background
1362
+ * location mode instead.
1363
+ *
1364
+ * Platform: Android
1365
+ *
1255
1366
  * @example
1256
1367
  * BackgroundGeolocation.headlessTask(function(event) {
1257
1368
  *