@josuelmm/cordova-background-geolocation 3.1.1 → 4.2.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.
- package/.npmignore +4 -0
- package/CHANGELOG.md +313 -0
- package/CLAUDE.md +56 -0
- package/HISTORY.md +124 -0
- package/README.md +198 -6
- package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +90 -0
- package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +362 -1
- package/android/common/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +153 -0
- package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +27 -11
- package/android/common/src/main/java/com/marianhello/bgloc/Config.java +268 -0
- package/android/common/src/main/java/com/marianhello/bgloc/HttpPostService.java +86 -26
- package/android/common/src/main/java/com/marianhello/bgloc/PluginDelegate.java +26 -0
- package/android/common/src/main/java/com/marianhello/bgloc/PostLocationTask.java +48 -5
- package/android/common/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +8 -1
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
- package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
- package/android/common/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
- package/android/common/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
- package/android/common/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
- package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +13 -9
- package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +29 -40
- package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +14 -34
- package/android/common/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +310 -7
- package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +14 -2
- package/android/common/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +50 -3
- package/android/dependencies.gradle +0 -3
- package/angular/background-geolocation-events.ts +21 -0
- package/angular/background-geolocation.service.ts +91 -0
- package/angular/dist/background-geolocation-events.d.ts +18 -1
- package/angular/dist/background-geolocation.service.d.ts +40 -0
- package/angular/dist/esm2022/background-geolocation-events.mjs +22 -1
- package/angular/dist/esm2022/background-geolocation.service.mjs +47 -1
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs +67 -0
- package/angular/dist/fesm2022/josuelmm-cordova-background-geolocation.mjs.map +1 -1
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.h +4 -0
- package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +352 -1
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +26 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +421 -15
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +12 -0
- package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +83 -5
- package/ios/common/BackgroundGeolocation/MAURConfig.h +15 -0
- package/ios/common/BackgroundGeolocation/MAURConfig.m +100 -3
- package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +29 -2
- package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +12 -3
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +4 -0
- package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +102 -44
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
- package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
- package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
- package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
- package/package.json +36 -1
- package/plugin.xml +26 -8
- package/www/BackgroundGeolocation.d.ts +559 -3
- package/www/BackgroundGeolocation.js +78 -1
- package/RELEASE.MD +0 -16
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
#import "MAURLogging.h"
|
|
10
10
|
#import "MAURBackgroundSync.h"
|
|
11
11
|
#import "MAURSQLiteLocationDAO.h"
|
|
12
|
+
#import <objc/runtime.h>
|
|
13
|
+
|
|
14
|
+
NSString * const MAURBackgroundSyncDidStartNotification = @"MAURBackgroundSyncDidStart";
|
|
15
|
+
NSString * const MAURBackgroundSyncDidSucceedNotification = @"MAURBackgroundSyncDidSucceed";
|
|
16
|
+
NSString * const MAURBackgroundSyncDidFailNotification = @"MAURBackgroundSyncDidFail";
|
|
17
|
+
NSString * const MAURBackgroundSyncDidProgressNotification = @"MAURBackgroundSyncDidProgress";
|
|
12
18
|
|
|
13
19
|
@interface MAURBackgroundSync () <NSURLSessionDelegate, NSURLSessionTaskDelegate>
|
|
14
20
|
{
|
|
@@ -22,11 +28,15 @@
|
|
|
22
28
|
- (instancetype) init
|
|
23
29
|
{
|
|
24
30
|
if(!(self = [super init])) return nil;
|
|
25
|
-
|
|
31
|
+
|
|
32
|
+
// v3.5 Phase 4: previously `tasks` was never allocated; addObject/removeObject/cancel/status
|
|
33
|
+
// silently no-op'd on nil. Allocate now so cancel and status actually work.
|
|
34
|
+
tasks = [[NSMutableArray alloc] init];
|
|
35
|
+
|
|
26
36
|
NSURLSessionConfiguration *conf = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.marianhello.session"];
|
|
27
37
|
conf.allowsCellularAccess = YES;
|
|
28
38
|
urlSession = [NSURLSession sessionWithConfiguration:conf delegate:self delegateQueue:[NSOperationQueue mainQueue]];
|
|
29
|
-
|
|
39
|
+
|
|
30
40
|
return self;
|
|
31
41
|
}
|
|
32
42
|
|
|
@@ -55,6 +65,11 @@
|
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
- (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders
|
|
68
|
+
{
|
|
69
|
+
[self sync:url withTemplate:locationTemplate withHttpHeaders:httpHeaders withMethod:@"POST"];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void) sync:(NSString * _Nonnull)url withTemplate:(id)locationTemplate withHttpHeaders:(NSMutableDictionary * _Nullable)httpHeaders withMethod:(NSString * _Nullable)method
|
|
58
73
|
{
|
|
59
74
|
MAURSQLiteLocationDAO* locationDAO = [MAURSQLiteLocationDAO sharedInstance];
|
|
60
75
|
NSArray *locations = [locationDAO getLocationsForSync];
|
|
@@ -77,7 +92,8 @@
|
|
|
77
92
|
uint64_t bytesTotalForThisFile = [[[NSFileManager defaultManager] attributesOfItemAtPath:jsonUrl.path error:nil] fileSize];
|
|
78
93
|
|
|
79
94
|
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
|
|
80
|
-
[
|
|
95
|
+
NSString *resolvedMethod = (method != nil && method.length > 0) ? [method uppercaseString] : @"POST";
|
|
96
|
+
[request setHTTPMethod:resolvedMethod];
|
|
81
97
|
[request setTimeoutInterval:120]; // Prevents sync from hanging indefinitely if server does not respond
|
|
82
98
|
[request setValue:[NSString stringWithFormat:@"%llu", bytesTotalForThisFile] forHTTPHeaderField:@"Content-Length"];
|
|
83
99
|
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
@@ -92,8 +108,18 @@
|
|
|
92
108
|
task.taskDescription = fileName;
|
|
93
109
|
[tasks addObject:task];
|
|
94
110
|
DDLogInfo(@"Started upload for %@ as task %zu/%@/%@", jsonUrl.lastPathComponent, (unsigned long)task.taskIdentifier, task.taskDescription, task);
|
|
111
|
+
|
|
112
|
+
// v3.5 Phase 4: emit syncStart now that we are about to push to the server.
|
|
113
|
+
if (self.delegate && [self.delegate respondsToSelector:@selector(backgroundSyncStarted:)]) {
|
|
114
|
+
[self.delegate backgroundSyncStarted:self];
|
|
115
|
+
}
|
|
116
|
+
[[NSNotificationCenter defaultCenter] postNotificationName:MAURBackgroundSyncDidStartNotification object:self];
|
|
117
|
+
|
|
118
|
+
// Stash count so didCompleteWithError can report it as syncSuccess payload.
|
|
119
|
+
objc_setAssociatedObject(task, "locationsSent", @([jsonArray count]), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
120
|
+
|
|
95
121
|
[task resume];
|
|
96
|
-
|
|
122
|
+
|
|
97
123
|
}
|
|
98
124
|
|
|
99
125
|
// http://stackoverflow.com/a/572623/48125
|
|
@@ -133,6 +159,23 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
133
159
|
|
|
134
160
|
|
|
135
161
|
#pragma mark -
|
|
162
|
+
// v3.5 Phase 4: forward upload progress as syncProgress (0..100).
|
|
163
|
+
- (void)URLSession:(NSURLSession *)session
|
|
164
|
+
task:(NSURLSessionTask *)task
|
|
165
|
+
didSendBodyData:(int64_t)bytesSent
|
|
166
|
+
totalBytesSent:(int64_t)totalBytesSent
|
|
167
|
+
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
|
168
|
+
{
|
|
169
|
+
if (totalBytesExpectedToSend <= 0) return;
|
|
170
|
+
NSInteger progress = (NSInteger)((totalBytesSent * 100) / totalBytesExpectedToSend);
|
|
171
|
+
if (progress < 0) progress = 0;
|
|
172
|
+
if (progress > 100) progress = 100;
|
|
173
|
+
[[NSNotificationCenter defaultCenter]
|
|
174
|
+
postNotificationName:MAURBackgroundSyncDidProgressNotification
|
|
175
|
+
object:self
|
|
176
|
+
userInfo:@{@"progress": @(progress)}];
|
|
177
|
+
}
|
|
178
|
+
|
|
136
179
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
|
|
137
180
|
{
|
|
138
181
|
NSInteger statusCode = [(NSHTTPURLResponse *)task.response statusCode];
|
|
@@ -157,7 +200,7 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
157
200
|
}
|
|
158
201
|
|
|
159
202
|
if (statusCode == 401)
|
|
160
|
-
{
|
|
203
|
+
{
|
|
161
204
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
162
205
|
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncHttpAuthorizationUpdates:)])
|
|
163
206
|
{
|
|
@@ -165,6 +208,41 @@ NSString *stringFromFileSize(unsigned long long theSize)
|
|
|
165
208
|
}
|
|
166
209
|
});
|
|
167
210
|
}
|
|
211
|
+
|
|
212
|
+
// v3.5 Phase 4: emit syncSuccess / syncError.
|
|
213
|
+
NSNumber *sentNum = objc_getAssociatedObject(task, "locationsSent");
|
|
214
|
+
NSInteger locationsSent = sentNum != nil ? [sentNum integerValue] : 0;
|
|
215
|
+
BOOL isStatusOkay = (statusCode >= 200 && statusCode < 300);
|
|
216
|
+
|
|
217
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
218
|
+
if (error != nil) {
|
|
219
|
+
NSString *msg = error.localizedDescription ?: @"";
|
|
220
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
|
|
221
|
+
[_delegate backgroundSyncFailed:self httpStatus:0 message:msg];
|
|
222
|
+
}
|
|
223
|
+
[[NSNotificationCenter defaultCenter]
|
|
224
|
+
postNotificationName:MAURBackgroundSyncDidFailNotification
|
|
225
|
+
object:self
|
|
226
|
+
userInfo:@{@"httpStatus": @0, @"message": msg}];
|
|
227
|
+
} else if (!isStatusOkay) {
|
|
228
|
+
NSString *msg = [NSString stringWithFormat:@"HTTP %ld", (long)statusCode];
|
|
229
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncFailed:httpStatus:message:)]) {
|
|
230
|
+
[_delegate backgroundSyncFailed:self httpStatus:statusCode message:msg];
|
|
231
|
+
}
|
|
232
|
+
[[NSNotificationCenter defaultCenter]
|
|
233
|
+
postNotificationName:MAURBackgroundSyncDidFailNotification
|
|
234
|
+
object:self
|
|
235
|
+
userInfo:@{@"httpStatus": @(statusCode), @"message": msg}];
|
|
236
|
+
} else {
|
|
237
|
+
if (_delegate && [_delegate respondsToSelector:@selector(backgroundSyncSucceeded:locationsSent:)]) {
|
|
238
|
+
[_delegate backgroundSyncSucceeded:self locationsSent:locationsSent];
|
|
239
|
+
}
|
|
240
|
+
[[NSNotificationCenter defaultCenter]
|
|
241
|
+
postNotificationName:MAURBackgroundSyncDidSucceedNotification
|
|
242
|
+
object:self
|
|
243
|
+
userInfo:@{@"sent": @(locationsSent)}];
|
|
244
|
+
}
|
|
245
|
+
});
|
|
168
246
|
}
|
|
169
247
|
|
|
170
248
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
|
|
@@ -31,6 +31,19 @@ enum {
|
|
|
31
31
|
@property NSNumber *syncThreshold;
|
|
32
32
|
@property NSNumber *syncEnabled;
|
|
33
33
|
@property NSMutableDictionary* httpHeaders;
|
|
34
|
+
// v3.3 Phase 2: backend-agnostic HTTP transport
|
|
35
|
+
@property NSString *httpMethod; // POST | GET | PUT | PATCH (default POST)
|
|
36
|
+
@property NSString *syncHttpMethod; // POST | GET | PUT | PATCH (default POST)
|
|
37
|
+
@property NSString *httpMode; // batch | single (default batch)
|
|
38
|
+
@property NSString *syncMode; // batch | single (default batch)
|
|
39
|
+
@property NSMutableDictionary* queryParams; // static placeholder values for URL templating
|
|
40
|
+
// v3.4 Phase 3: location API modernization
|
|
41
|
+
@property NSNumber *_showsBackgroundLocationIndicator; // iOS 11+: show blue bar when app uses location in background
|
|
42
|
+
// v3.5 Phase 4: diagnostics
|
|
43
|
+
@property NSNumber *heartbeatInterval; // ms; 0 disables (default)
|
|
44
|
+
@property NSString *mockLocationPolicy; // allow | flag | drop (default allow)
|
|
45
|
+
// v4.0 Phase 6: driver insights — passed through as a dictionary; the facade reads keys at runtime.
|
|
46
|
+
@property NSDictionary *drivingEvents;
|
|
34
47
|
@property NSNumber *_saveBatteryOnBackground;
|
|
35
48
|
@property NSNumber *maxLocations;
|
|
36
49
|
@property NSNumber *_pauseLocationUpdates;
|
|
@@ -57,6 +70,8 @@ enum {
|
|
|
57
70
|
- (BOOL) syncEnabled;
|
|
58
71
|
- (BOOL) hasHttpHeaders;
|
|
59
72
|
- (BOOL) hasSaveBatteryOnBackground;
|
|
73
|
+
- (BOOL) hasShowsBackgroundLocationIndicator;
|
|
74
|
+
- (BOOL) showsBackgroundLocationIndicator;
|
|
60
75
|
- (BOOL) hasMaxLocations;
|
|
61
76
|
- (BOOL) hasPauseLocationUpdates;
|
|
62
77
|
- (BOOL) hasLocationProvider;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
@implementation MAURConfig
|
|
14
14
|
|
|
15
|
-
@synthesize stationaryRadius, distanceFilter, desiredAccuracy, _debug, activityType, activitiesInterval, _stopOnTerminate, url, syncUrl, syncThreshold, syncEnabled, httpHeaders, _saveBatteryOnBackground, maxLocations, _pauseLocationUpdates, locationProvider, _template;
|
|
15
|
+
@synthesize stationaryRadius, distanceFilter, desiredAccuracy, _debug, activityType, activitiesInterval, _stopOnTerminate, url, syncUrl, syncThreshold, syncEnabled, httpHeaders, httpMethod, syncHttpMethod, httpMode, syncMode, queryParams, _showsBackgroundLocationIndicator, heartbeatInterval, mockLocationPolicy, drivingEvents, _saveBatteryOnBackground, maxLocations, _pauseLocationUpdates, locationProvider, _template;
|
|
16
16
|
|
|
17
17
|
-(instancetype) initWithDefaults {
|
|
18
18
|
self = [super init];
|
|
@@ -34,8 +34,14 @@
|
|
|
34
34
|
syncEnabled = [NSNumber numberWithBool:YES];
|
|
35
35
|
_pauseLocationUpdates = [NSNumber numberWithBool:NO];
|
|
36
36
|
locationProvider = [NSNumber numberWithInt:DISTANCE_FILTER_PROVIDER];
|
|
37
|
+
httpMethod = @"POST";
|
|
38
|
+
syncHttpMethod = @"POST";
|
|
39
|
+
httpMode = @"batch";
|
|
40
|
+
syncMode = @"batch";
|
|
41
|
+
heartbeatInterval = [NSNumber numberWithInt:0];
|
|
42
|
+
mockLocationPolicy = @"allow";
|
|
37
43
|
// template =
|
|
38
|
-
|
|
44
|
+
|
|
39
45
|
return self;
|
|
40
46
|
}
|
|
41
47
|
|
|
@@ -58,7 +64,7 @@
|
|
|
58
64
|
if (isNotNull(config[@"activityType"])) {
|
|
59
65
|
instance.activityType = config[@"activityType"];
|
|
60
66
|
}
|
|
61
|
-
if (
|
|
67
|
+
if (isNotNull(config[@"activitiesInterval"])) {
|
|
62
68
|
instance.activitiesInterval = config[@"activitiesInterval"];
|
|
63
69
|
}
|
|
64
70
|
if (isNotNull(config[@"stopOnTerminate"])) {
|
|
@@ -79,6 +85,38 @@
|
|
|
79
85
|
if (config[@"httpHeaders"] != nil) {
|
|
80
86
|
instance.httpHeaders = config[@"httpHeaders"];
|
|
81
87
|
}
|
|
88
|
+
// headers (alias of httpHeaders)
|
|
89
|
+
if (config[@"headers"] != nil) {
|
|
90
|
+
instance.httpHeaders = config[@"headers"];
|
|
91
|
+
}
|
|
92
|
+
// v3.3 Phase 2: HTTP transport
|
|
93
|
+
if (isNotNull(config[@"httpMethod"])) {
|
|
94
|
+
instance.httpMethod = [(NSString*)config[@"httpMethod"] uppercaseString];
|
|
95
|
+
}
|
|
96
|
+
if (isNotNull(config[@"syncHttpMethod"])) {
|
|
97
|
+
instance.syncHttpMethod = [(NSString*)config[@"syncHttpMethod"] uppercaseString];
|
|
98
|
+
}
|
|
99
|
+
if (isNotNull(config[@"httpMode"])) {
|
|
100
|
+
instance.httpMode = [(NSString*)config[@"httpMode"] lowercaseString];
|
|
101
|
+
}
|
|
102
|
+
if (isNotNull(config[@"syncMode"])) {
|
|
103
|
+
instance.syncMode = [(NSString*)config[@"syncMode"] lowercaseString];
|
|
104
|
+
}
|
|
105
|
+
if (config[@"queryParams"] != nil) {
|
|
106
|
+
instance.queryParams = config[@"queryParams"];
|
|
107
|
+
}
|
|
108
|
+
if (isNotNull(config[@"showsBackgroundLocationIndicator"])) {
|
|
109
|
+
instance._showsBackgroundLocationIndicator = config[@"showsBackgroundLocationIndicator"];
|
|
110
|
+
}
|
|
111
|
+
if (isNotNull(config[@"heartbeatInterval"])) {
|
|
112
|
+
instance.heartbeatInterval = config[@"heartbeatInterval"];
|
|
113
|
+
}
|
|
114
|
+
if (isNotNull(config[@"mockLocationPolicy"])) {
|
|
115
|
+
instance.mockLocationPolicy = [(NSString*)config[@"mockLocationPolicy"] lowercaseString];
|
|
116
|
+
}
|
|
117
|
+
if ([config[@"drivingEvents"] isKindOfClass:[NSDictionary class]]) {
|
|
118
|
+
instance.drivingEvents = config[@"drivingEvents"];
|
|
119
|
+
}
|
|
82
120
|
if (isNotNull(config[@"saveBatteryOnBackground"])) {
|
|
83
121
|
instance._saveBatteryOnBackground = config[@"saveBatteryOnBackground"];
|
|
84
122
|
}
|
|
@@ -94,6 +132,10 @@
|
|
|
94
132
|
if (config[@"postTemplate"] != nil) {
|
|
95
133
|
instance._template = config[@"postTemplate"];
|
|
96
134
|
}
|
|
135
|
+
// bodyTemplate (alias of postTemplate)
|
|
136
|
+
if (config[@"bodyTemplate"] != nil) {
|
|
137
|
+
instance._template = config[@"bodyTemplate"];
|
|
138
|
+
}
|
|
97
139
|
|
|
98
140
|
return instance;
|
|
99
141
|
}
|
|
@@ -146,6 +188,33 @@
|
|
|
146
188
|
if ([newConfig hasHttpHeaders]) {
|
|
147
189
|
merger.httpHeaders = newConfig.httpHeaders;
|
|
148
190
|
}
|
|
191
|
+
if (newConfig.httpMethod != nil) {
|
|
192
|
+
merger.httpMethod = newConfig.httpMethod;
|
|
193
|
+
}
|
|
194
|
+
if (newConfig.syncHttpMethod != nil) {
|
|
195
|
+
merger.syncHttpMethod = newConfig.syncHttpMethod;
|
|
196
|
+
}
|
|
197
|
+
if (newConfig.httpMode != nil) {
|
|
198
|
+
merger.httpMode = newConfig.httpMode;
|
|
199
|
+
}
|
|
200
|
+
if (newConfig.syncMode != nil) {
|
|
201
|
+
merger.syncMode = newConfig.syncMode;
|
|
202
|
+
}
|
|
203
|
+
if (newConfig.queryParams != nil) {
|
|
204
|
+
merger.queryParams = newConfig.queryParams;
|
|
205
|
+
}
|
|
206
|
+
if ([newConfig hasShowsBackgroundLocationIndicator]) {
|
|
207
|
+
merger._showsBackgroundLocationIndicator = newConfig._showsBackgroundLocationIndicator;
|
|
208
|
+
}
|
|
209
|
+
if (newConfig.heartbeatInterval != nil) {
|
|
210
|
+
merger.heartbeatInterval = newConfig.heartbeatInterval;
|
|
211
|
+
}
|
|
212
|
+
if (newConfig.mockLocationPolicy != nil) {
|
|
213
|
+
merger.mockLocationPolicy = newConfig.mockLocationPolicy;
|
|
214
|
+
}
|
|
215
|
+
if (newConfig.drivingEvents != nil) {
|
|
216
|
+
merger.drivingEvents = newConfig.drivingEvents;
|
|
217
|
+
}
|
|
149
218
|
if ([newConfig hasSaveBatteryOnBackground]) {
|
|
150
219
|
merger._saveBatteryOnBackground = newConfig._saveBatteryOnBackground;
|
|
151
220
|
}
|
|
@@ -181,6 +250,15 @@
|
|
|
181
250
|
copy.syncThreshold = syncThreshold;
|
|
182
251
|
copy.syncEnabled = syncEnabled;
|
|
183
252
|
copy.httpHeaders = httpHeaders;
|
|
253
|
+
copy.httpMethod = httpMethod;
|
|
254
|
+
copy.syncHttpMethod = syncHttpMethod;
|
|
255
|
+
copy.httpMode = httpMode;
|
|
256
|
+
copy.syncMode = syncMode;
|
|
257
|
+
copy.queryParams = queryParams;
|
|
258
|
+
copy._showsBackgroundLocationIndicator = _showsBackgroundLocationIndicator;
|
|
259
|
+
copy.heartbeatInterval = heartbeatInterval;
|
|
260
|
+
copy.mockLocationPolicy = mockLocationPolicy;
|
|
261
|
+
copy.drivingEvents = drivingEvents;
|
|
184
262
|
copy._saveBatteryOnBackground = _saveBatteryOnBackground;
|
|
185
263
|
copy.maxLocations = maxLocations;
|
|
186
264
|
copy._pauseLocationUpdates = _pauseLocationUpdates;
|
|
@@ -322,6 +400,16 @@
|
|
|
322
400
|
return _saveBatteryOnBackground != nil;
|
|
323
401
|
}
|
|
324
402
|
|
|
403
|
+
- (BOOL) hasShowsBackgroundLocationIndicator
|
|
404
|
+
{
|
|
405
|
+
return _showsBackgroundLocationIndicator != nil;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
- (BOOL) showsBackgroundLocationIndicator
|
|
409
|
+
{
|
|
410
|
+
return _showsBackgroundLocationIndicator != nil ? [_showsBackgroundLocationIndicator boolValue] : NO;
|
|
411
|
+
}
|
|
412
|
+
|
|
325
413
|
- (BOOL) hasMaxLocations
|
|
326
414
|
{
|
|
327
415
|
return maxLocations != nil;
|
|
@@ -479,6 +567,15 @@
|
|
|
479
567
|
if ([self hasUrl]) [dict setObject:self.url forKey:@"url"];
|
|
480
568
|
if ([self hasSyncUrl]) [dict setObject:self.syncUrl forKey:@"syncUrl"];
|
|
481
569
|
if ([self hasHttpHeaders]) [dict setObject:self.httpHeaders forKey:@"httpHeaders"];
|
|
570
|
+
if (self.httpMethod != nil) [dict setObject:self.httpMethod forKey:@"httpMethod"];
|
|
571
|
+
if (self.syncHttpMethod != nil) [dict setObject:self.syncHttpMethod forKey:@"syncHttpMethod"];
|
|
572
|
+
if (self.httpMode != nil) [dict setObject:self.httpMode forKey:@"httpMode"];
|
|
573
|
+
if (self.syncMode != nil) [dict setObject:self.syncMode forKey:@"syncMode"];
|
|
574
|
+
if (self.queryParams != nil) [dict setObject:self.queryParams forKey:@"queryParams"];
|
|
575
|
+
if ([self hasShowsBackgroundLocationIndicator]) [dict setObject:self._showsBackgroundLocationIndicator forKey:@"showsBackgroundLocationIndicator"];
|
|
576
|
+
if (self.heartbeatInterval != nil) [dict setObject:self.heartbeatInterval forKey:@"heartbeatInterval"];
|
|
577
|
+
if (self.mockLocationPolicy != nil) [dict setObject:self.mockLocationPolicy forKey:@"mockLocationPolicy"];
|
|
578
|
+
if (self.drivingEvents != nil) [dict setObject:self.drivingEvents forKey:@"drivingEvents"];
|
|
482
579
|
if ([self hasStationaryRadius]) [dict setObject:self.stationaryRadius forKey:@"stationaryRadius"];
|
|
483
580
|
if ([self hasDistanceFilter]) [dict setObject:self.distanceFilter forKey:@"distanceFilter"];
|
|
484
581
|
if ([self hasDesiredAccuracy]) [dict setObject:self.desiredAccuracy forKey:@"desiredAccuracy"];
|
|
@@ -87,6 +87,12 @@ enum {
|
|
|
87
87
|
_config = config;
|
|
88
88
|
|
|
89
89
|
locationManager.pausesLocationUpdatesAutomatically = [_config pauseLocationUpdates];
|
|
90
|
+
// v3.4 Phase 3: showsBackgroundLocationIndicator (iOS 11+).
|
|
91
|
+
if (@available(iOS 11.0, *)) {
|
|
92
|
+
if ([_config hasShowsBackgroundLocationIndicator]) {
|
|
93
|
+
locationManager.showsBackgroundLocationIndicator = [_config showsBackgroundLocationIndicator];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
90
96
|
locationManager.activityType = [_config decodeActivityType];
|
|
91
97
|
locationManager.distanceFilter = _config.distanceFilter.integerValue; // meters
|
|
92
98
|
locationManager.desiredAccuracy = [_config decodeDesiredAccuracy];
|
|
@@ -376,13 +382,34 @@ enum {
|
|
|
376
382
|
}
|
|
377
383
|
}
|
|
378
384
|
|
|
385
|
+
// v3.4 Phase 3: iOS 14+ delegate callback. Replaces the legacy `didChangeAuthorizationStatus:`
|
|
386
|
+
// (which is deprecated in iOS 14 but still delivered). On iOS 14+ this is the canonical entry
|
|
387
|
+
// point and exposes accuracyAuthorization (Precise vs Reduced).
|
|
388
|
+
- (void) locationManagerDidChangeAuthorization:(CLLocationManager *)manager API_AVAILABLE(ios(14.0))
|
|
389
|
+
{
|
|
390
|
+
CLAuthorizationStatus status = manager.authorizationStatus;
|
|
391
|
+
DDLogInfo(@"LocationManager didChangeAuthorization (iOS 14+) status=%d accuracy=%ld",
|
|
392
|
+
(int)status, (long)manager.accuracyAuthorization);
|
|
393
|
+
[self handleAuthorizationStatusChange:status];
|
|
394
|
+
}
|
|
395
|
+
|
|
379
396
|
- (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
|
|
380
397
|
{
|
|
381
|
-
|
|
398
|
+
// On iOS 14+ the system also delivers `locationManagerDidChangeAuthorization:`; ignore this
|
|
399
|
+
// legacy callback there to avoid double-notifying delegates.
|
|
400
|
+
if (@available(iOS 14.0, *)) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
DDLogInfo(@"LocationManager didChangeAuthorizationStatus (legacy) %u", status);
|
|
404
|
+
[self handleAuthorizationStatusChange:status];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
- (void) handleAuthorizationStatusChange:(CLAuthorizationStatus)status
|
|
408
|
+
{
|
|
382
409
|
if ([_config isDebugging]) {
|
|
383
410
|
[self notify:[NSString stringWithFormat:@"Authorization status changed %u", status]];
|
|
384
411
|
}
|
|
385
|
-
|
|
412
|
+
|
|
386
413
|
switch(status) {
|
|
387
414
|
case kCLAuthorizationStatusRestricted:
|
|
388
415
|
case kCLAuthorizationStatusDenied:
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
#import "MAURGeolocationOpenHelper.h"
|
|
11
11
|
#import "MAURLocationContract.h"
|
|
12
12
|
#import "MAURConfigurationContract.h"
|
|
13
|
+
#import "MAURSessionLocationContract.h"
|
|
13
14
|
|
|
14
15
|
@implementation MAURGeolocationOpenHelper
|
|
15
16
|
|
|
16
17
|
static NSString *const kDatabaseName = @"cordova_bg_geolocation.db";
|
|
17
|
-
static NSInteger const kDatabaseVersion =
|
|
18
|
+
static NSInteger const kDatabaseVersion = 5;
|
|
18
19
|
|
|
19
20
|
- (instancetype)init
|
|
20
21
|
{
|
|
@@ -39,7 +40,9 @@ static NSInteger const kDatabaseVersion = 4;
|
|
|
39
40
|
NSString *sql = [@[
|
|
40
41
|
[MAURLocationContract createTableSQL],
|
|
41
42
|
[MAURConfigurationContract createTableSQL],
|
|
42
|
-
@"CREATE INDEX recorded_at_idx ON " @LC_TABLE_NAME @" (" @LC_COLUMN_NAME_RECORDED_AT @")"
|
|
43
|
+
@"CREATE INDEX recorded_at_idx ON " @LC_TABLE_NAME @" (" @LC_COLUMN_NAME_RECORDED_AT @")",
|
|
44
|
+
[MAURSessionLocationContract createTableSQL],
|
|
45
|
+
@"CREATE INDEX session_recorded_at_idx ON " @LSC_TABLE_NAME @" (" @LSC_COLUMN_NAME_RECORDED_AT @")"
|
|
43
46
|
] componentsJoinedByString:@";"];
|
|
44
47
|
if (![database executeStatements:sql]) {
|
|
45
48
|
NSLog(@"%@ failed code: %d: message: %@", sql, [database lastErrorCode], [database lastErrorMessage]);
|
|
@@ -52,7 +55,8 @@ static NSInteger const kDatabaseVersion = 4;
|
|
|
52
55
|
NSLog(@"Downgrading geolocation db oldVersion: %ld, newVersion: %ld", oldVersion, newVersion);
|
|
53
56
|
|
|
54
57
|
NSString *sql = [@[
|
|
55
|
-
@"DROP TABLE IF EXISTS " @LC_TABLE_NAME
|
|
58
|
+
@"DROP TABLE IF EXISTS " @LC_TABLE_NAME,
|
|
59
|
+
@"DROP TABLE IF EXISTS " @LSC_TABLE_NAME
|
|
56
60
|
] componentsJoinedByString:@";"];
|
|
57
61
|
|
|
58
62
|
[queue inDatabase:^(FMDatabase *database) {
|
|
@@ -85,6 +89,11 @@ static NSInteger const kDatabaseVersion = 4;
|
|
|
85
89
|
[sql addObjectsFromArray: @[
|
|
86
90
|
[NSString stringWithFormat:@"ALTER TABLE %s ADD COLUMN %s INTEGER", CC_TABLE_NAME, CC_COLUMN_NAME_SYNC_ENABLED]
|
|
87
91
|
]];
|
|
92
|
+
case 4:
|
|
93
|
+
[sql addObjectsFromArray: @[
|
|
94
|
+
[MAURSessionLocationContract createTableSQL],
|
|
95
|
+
[NSString stringWithFormat:@"CREATE INDEX session_recorded_at_idx ON %@ (%@)", @LSC_TABLE_NAME, @LSC_COLUMN_NAME_RECORDED_AT]
|
|
96
|
+
]];
|
|
88
97
|
break; // break only for previous db version (cascade statements)
|
|
89
98
|
default:
|
|
90
99
|
return;
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
@optional
|
|
20
20
|
- (void)postLocationTaskRequestedAbortUpdates:(MAURPostLocationTask * _Nonnull)task;
|
|
21
21
|
- (void)postLocationTaskHttpAuthorizationUpdates:(MAURPostLocationTask * _Nonnull)task;
|
|
22
|
+
// v3.5 Phase 4
|
|
23
|
+
- (void)postLocationTaskSyncStarted:(MAURPostLocationTask * _Nonnull)task;
|
|
24
|
+
- (void)postLocationTaskSyncSucceeded:(MAURPostLocationTask * _Nonnull)task locationsSent:(NSInteger)locationsSent;
|
|
25
|
+
- (void)postLocationTaskSyncFailed:(MAURPostLocationTask * _Nonnull)task httpStatus:(NSInteger)httpStatus message:(NSString * _Nullable)message;
|
|
22
26
|
|
|
23
27
|
@end
|
|
24
28
|
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
#import "MAURLogging.h"
|
|
15
15
|
#import "MAURPostLocationTask.h"
|
|
16
16
|
#import "MAURSQLiteLocationDAO.h"
|
|
17
|
+
#import "MAURSessionLocationDAO.h"
|
|
18
|
+
#import "MAURUrlTemplateResolver.h"
|
|
17
19
|
|
|
18
20
|
static NSString * const TAG = @"MAURPostLocationTask";
|
|
19
21
|
|
|
@@ -79,19 +81,34 @@ static MAURLocationTransform s_locationTransform = nil;
|
|
|
79
81
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
|
80
82
|
|
|
81
83
|
MAURLocation *location = inLocation;
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
if (locationTransform != nil) {
|
|
84
86
|
location = locationTransform(location);
|
|
85
|
-
|
|
87
|
+
|
|
86
88
|
if (location == nil) {
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
|
-
|
|
92
|
+
|
|
93
|
+
// v3.5 Phase 4: mock location policy. Detection already exists in MAURLocation.simulated.
|
|
94
|
+
if (location.simulated != nil && [location.simulated boolValue]) {
|
|
95
|
+
NSString *policy = self.config.mockLocationPolicy ?: @"allow";
|
|
96
|
+
if ([@"drop" isEqualToString:policy]) {
|
|
97
|
+
DDLogInfo(@"%@ Simulated/mock location dropped (mockLocationPolicy=drop)", TAG);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// "flag": leave it. The simulated NSNumber is already on the model and propagates via toResultFromTemplate.
|
|
101
|
+
// "allow": no-op.
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
MAURSQLiteLocationDAO *locationDAO = [MAURSQLiteLocationDAO sharedInstance];
|
|
92
105
|
// TODO: investigate location id always 0
|
|
93
106
|
NSNumber *locationId = [locationDAO persistLocation:location limitRows:self.config.maxLocations.integerValue];
|
|
94
107
|
|
|
108
|
+
if ([[MAURSessionLocationDAO sharedInstance] isSessionActive]) {
|
|
109
|
+
[[MAURSessionLocationDAO sharedInstance] persistSessionLocation:location];
|
|
110
|
+
}
|
|
111
|
+
|
|
95
112
|
if (hasConnectivity && [self.config hasValidUrl]) {
|
|
96
113
|
NSError *error = nil;
|
|
97
114
|
if ([self post:location toUrl:self.config.url withTemplate:self.config._template withHttpHeaders:self.config.httpHeaders error:&error]) {
|
|
@@ -112,28 +129,47 @@ static MAURLocationTransform s_locationTransform = nil;
|
|
|
112
129
|
});
|
|
113
130
|
}
|
|
114
131
|
|
|
115
|
-
- (BOOL) post:(MAURLocation*)location
|
|
116
|
-
toUrl:(NSString*)url
|
|
117
|
-
withTemplate:(id)locationTemplate
|
|
118
|
-
withHttpHeaders:(NSMutableDictionary*)httpHeaders
|
|
132
|
+
- (BOOL) post:(MAURLocation*)location
|
|
133
|
+
toUrl:(NSString*)url
|
|
134
|
+
withTemplate:(id)locationTemplate
|
|
135
|
+
withHttpHeaders:(NSMutableDictionary*)httpHeaders
|
|
119
136
|
error:(NSError * __autoreleasing *)outError
|
|
120
137
|
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
// v3.3 Phase 2: backend-agnostic transport.
|
|
139
|
+
// Resolve URL template using current location + queryParams (for both single and batch modes).
|
|
140
|
+
NSString *resolvedUrl = [MAURUrlTemplateResolver resolve:url location:location queryParams:self.config.queryParams];
|
|
141
|
+
|
|
142
|
+
NSString *method = self.config.httpMethod ?: @"POST";
|
|
143
|
+
NSString *mode = self.config.httpMode ?: @"batch";
|
|
144
|
+
BOOL isBodyless = [@"GET" isEqualToString:method];
|
|
145
|
+
BOOL singleMode = isBodyless || [@"single" isEqualToString:mode];
|
|
146
|
+
|
|
147
|
+
NSData *data = nil;
|
|
148
|
+
if (!isBodyless) {
|
|
149
|
+
// For single mode (or body methods that prefer one location per request) send a JSONObject;
|
|
150
|
+
// for batch send the array (current behaviour).
|
|
151
|
+
if (singleMode) {
|
|
152
|
+
data = [NSJSONSerialization dataWithJSONObject:[location toResultFromTemplate:locationTemplate] options:0 error:outError];
|
|
153
|
+
} else {
|
|
154
|
+
NSArray *locations = [[NSArray alloc] initWithObjects:[location toResultFromTemplate:locationTemplate], nil];
|
|
155
|
+
data = [NSJSONSerialization dataWithJSONObject:locations options:0 error:outError];
|
|
156
|
+
}
|
|
157
|
+
if (!data) {
|
|
158
|
+
return NO;
|
|
159
|
+
}
|
|
125
160
|
}
|
|
126
|
-
|
|
127
|
-
NSString *jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
128
|
-
|
|
161
|
+
|
|
162
|
+
NSString *jsonStr = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
|
|
129
163
|
NSString *contentType = [httpHeaders objectForKey:@"Content-Type"];
|
|
130
164
|
if (!contentType) {
|
|
131
165
|
contentType = @"application/json";
|
|
132
166
|
}
|
|
133
|
-
|
|
134
|
-
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:
|
|
135
|
-
[request
|
|
136
|
-
|
|
167
|
+
|
|
168
|
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:resolvedUrl]];
|
|
169
|
+
[request setHTTPMethod:method];
|
|
170
|
+
if (!isBodyless) {
|
|
171
|
+
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
|
172
|
+
}
|
|
137
173
|
if (httpHeaders != nil) {
|
|
138
174
|
for (id key in httpHeaders) {
|
|
139
175
|
if (![key isEqualToString:@"Content-Type"]) {
|
|
@@ -142,36 +178,54 @@ static MAURLocationTransform s_locationTransform = nil;
|
|
|
142
178
|
}
|
|
143
179
|
}
|
|
144
180
|
}
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
NSString *
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
181
|
+
|
|
182
|
+
if (!isBodyless) {
|
|
183
|
+
if ([contentType isEqualToString:@"application/x-www-form-urlencoded"]) {
|
|
184
|
+
id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:outError];
|
|
185
|
+
NSDictionary *dict = nil;
|
|
186
|
+
if ([jsonObject isKindOfClass:[NSArray class]] && [jsonObject count] == 1) {
|
|
187
|
+
dict = [jsonObject firstObject];
|
|
188
|
+
} else if ([jsonObject isKindOfClass:[NSDictionary class]]) {
|
|
189
|
+
dict = jsonObject;
|
|
190
|
+
}
|
|
191
|
+
if (dict) {
|
|
192
|
+
NSMutableArray *parts = [NSMutableArray array];
|
|
193
|
+
for (NSString *key in dict) {
|
|
194
|
+
NSString *value = [NSString stringWithFormat:@"%@", dict[key]];
|
|
195
|
+
NSString *encodedKey = [key stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
|
196
|
+
NSString *encodedValue = [value stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
|
|
197
|
+
NSString *part = [NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue];
|
|
198
|
+
[parts addObject:part];
|
|
199
|
+
}
|
|
200
|
+
NSString *encodedString = [parts componentsJoinedByString:@"&"];
|
|
201
|
+
[request setHTTPBody:[encodedString dataUsingEncoding:NSUTF8StringEncoding]];
|
|
202
|
+
} else {
|
|
203
|
+
[request setHTTPBody:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
|
|
162
204
|
}
|
|
163
|
-
NSString *encodedString = [parts componentsJoinedByString:@"&"];
|
|
164
|
-
[request setHTTPBody:[encodedString dataUsingEncoding:NSUTF8StringEncoding]];
|
|
165
205
|
} else {
|
|
166
206
|
[request setHTTPBody:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
|
|
167
207
|
}
|
|
168
|
-
} else {
|
|
169
|
-
[request setHTTPBody:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
|
|
170
208
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
209
|
+
|
|
210
|
+
// v3.4: NSURLSession (iOS 7+) replaces deprecated [NSURLConnection sendSynchronousRequest:].
|
|
211
|
+
// We run on a background queue (see -add: dispatch_async) so a semaphore-based wait is safe.
|
|
212
|
+
__block NSHTTPURLResponse *urlResponse = nil;
|
|
213
|
+
__block NSError *taskError = nil;
|
|
214
|
+
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
|
215
|
+
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession]
|
|
216
|
+
dataTaskWithRequest:request
|
|
217
|
+
completionHandler:^(NSData * _Nullable d, NSURLResponse * _Nullable response, NSError * _Nullable err) {
|
|
218
|
+
urlResponse = (NSHTTPURLResponse *)response;
|
|
219
|
+
taskError = err;
|
|
220
|
+
dispatch_semaphore_signal(sema);
|
|
221
|
+
}];
|
|
222
|
+
[dataTask resume];
|
|
223
|
+
// 120s ceiling to mirror the previous synchronous timeout; URLSession also enforces its own.
|
|
224
|
+
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(120 * NSEC_PER_SEC)));
|
|
225
|
+
if (taskError != nil && outError != NULL) {
|
|
226
|
+
*outError = taskError;
|
|
227
|
+
}
|
|
228
|
+
|
|
175
229
|
NSInteger statusCode = urlResponse.statusCode;
|
|
176
230
|
|
|
177
231
|
if (statusCode == 285)
|
|
@@ -215,7 +269,11 @@ static MAURLocationTransform s_locationTransform = nil;
|
|
|
215
269
|
if (![self.config syncEnabled] || ![self.config hasValidSyncUrl]) {
|
|
216
270
|
return;
|
|
217
271
|
}
|
|
218
|
-
|
|
272
|
+
// For sync (batch) only static queryParams placeholders apply; per-location templating
|
|
273
|
+
// belongs in real-time post (httpMode="single" + httpMethod=GET) instead.
|
|
274
|
+
NSString *resolvedSyncUrl = [MAURUrlTemplateResolver resolve:self.config.syncUrl location:nil queryParams:self.config.queryParams];
|
|
275
|
+
NSString *syncMethod = self.config.syncHttpMethod ?: @"POST";
|
|
276
|
+
[uploader sync:resolvedSyncUrl withTemplate:self.config._template withHttpHeaders:self.config.httpHeaders withMethod:syncMethod];
|
|
219
277
|
}
|
|
220
278
|
|
|
221
279
|
#pragma mark - Location transform
|