summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gyp/platform-ios.gypi2
-rw-r--r--platform/ios/src/MGLAPIClient.h12
-rw-r--r--platform/ios/src/MGLAPIClient.m195
-rw-r--r--platform/ios/src/MGLMapboxEvents.m198
4 files changed, 242 insertions, 165 deletions
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index dc52db7acd..e2c840581b 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -46,6 +46,8 @@
'../platform/darwin/src/MGLMapCamera.mm',
'../platform/ios/src/MGLMapboxEvents.h',
'../platform/ios/src/MGLMapboxEvents.m',
+ '../platform/ios/src/MGLAPIClient.h',
+ '../platform/ios/src/MGLAPIClient.m',
'../platform/ios/src/MGLMapView.mm',
'../platform/ios/src/MGLAccountManager_Private.h',
'../platform/ios/src/MGLAccountManager.m',
diff --git a/platform/ios/src/MGLAPIClient.h b/platform/ios/src/MGLAPIClient.h
new file mode 100644
index 0000000000..ef64b021b7
--- /dev/null
+++ b/platform/ios/src/MGLAPIClient.h
@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLMapboxEvents.h"
+#import "MGLTypes.h"
+
+@interface MGLAPIClient : NSObject <NSURLSessionDelegate>
+
+- (void)postEvents:(nonnull NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler;
+- (void)postEvent:(nonnull MGLMapboxEventAttributes *)event completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler;
+- (void)cancelAll;
+
+@end
diff --git a/platform/ios/src/MGLAPIClient.m b/platform/ios/src/MGLAPIClient.m
new file mode 100644
index 0000000000..b4f85b5631
--- /dev/null
+++ b/platform/ios/src/MGLAPIClient.m
@@ -0,0 +1,195 @@
+#import "MGLAPIClient.h"
+#import "NSBundle+MGLAdditions.h"
+#import "MGLAccountManager.h"
+
+static NSString * const MGLAPIClientUserAgent = @"MapboxEventsiOS/1.1";
+static NSString * const MGLAPIClientBaseURL = @"https://api.tiles.mapbox.com";
+
+static NSString * const MGLAPIClientHeaderFieldUserAgentKey = @"User-Agent";
+static NSString * const MGLAPIClientHeaderFieldContentTypeKey = @"Content-Type";
+static NSString * const MGLAPIClientHeaderFieldContentTypeValue = @"application/json";
+static NSString * const MGLAPIClientHTTPMethodPost = @"POST";
+
+@interface MGLAPIClient ()
+
+@property (nonatomic, copy) NSURLSession *session;
+@property (nonatomic, copy) NSString *baseURL;
+@property (nonatomic, copy) NSData *digicertCert;
+@property (nonatomic, copy) NSData *geoTrustCert;
+@property (nonatomic, copy) NSData *testServerCert;
+@property (nonatomic, copy) NSString *userAgent;
+@property (nonatomic) NSMutableArray *dataTasks;
+@property (nonatomic) BOOL usesTestServer;
+
+@end
+
+@implementation MGLAPIClient
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
+ delegate:self delegateQueue:nil];
+ _dataTasks = [NSMutableArray array];
+ [self loadCertificates];
+ [self setupBaseURL];
+ [self setupUserAgent];
+ }
+ return self;
+}
+
+#pragma mark Public API
+
+- (void)postEvents:(nonnull NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler {
+ NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self requestForEvents:events]
+ completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+ [self.dataTasks removeObject:dataTask];
+ if (completionHandler) {
+ completionHandler(error);
+ }
+ }];
+ [dataTask resume];
+ [self.dataTasks addObject:dataTask];
+}
+
+- (void)postEvent:(nonnull MGLMapboxEventAttributes *)event completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler {
+ [self postEvents:@[event] completionHandler:completionHandler];
+}
+
+- (void)cancelAll {
+ [self.dataTasks makeObjectsPerformSelector:@selector(cancel)];
+ [self.dataTasks removeAllObjects];
+}
+
+#pragma mark Utilities
+
+- (NSURLRequest *)requestForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events {
+ NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", self.baseURL, [MGLAccountManager accessToken]];
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
+ [request setValue:self.userAgent forHTTPHeaderField:MGLAPIClientHeaderFieldUserAgentKey];
+ [request setValue:MGLAPIClientHeaderFieldContentTypeValue forHTTPHeaderField:MGLAPIClientHeaderFieldContentTypeKey];
+ [request setHTTPMethod:MGLAPIClientHTTPMethodPost];
+ NSData *jsonData = [self serializedDataForEvents:events];
+ [request setHTTPBody:jsonData];
+ return [request copy];
+}
+
+- (void)setupBaseURL {
+ NSString *testServerURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMetricsTestServerURL"];
+ if (testServerURL) {
+ _baseURL = testServerURL;
+ _usesTestServer = YES;
+ } else {
+ _baseURL = MGLAPIClientBaseURL;
+ }
+}
+
+- (void)loadCertificates {
+ NSData *certificate;
+ [self loadCertificate:&certificate withResource:@"api_mapbox_com-geotrust"];
+ self.geoTrustCert = certificate;
+ [self loadCertificate:&certificate withResource:@"api_mapbox_com-digicert"];
+ self.digicertCert = certificate;
+ [self loadCertificate:&certificate withResource:@"star_tilestream_net"];
+ self.testServerCert = certificate;
+}
+
+- (void)loadCertificate:(NSData **)certificate withResource:(NSString *)resource {
+ NSBundle *resourceBundle = [NSBundle mgl_frameworkBundle];
+ NSString *cerPath = [resourceBundle pathForResource:resource ofType:@"der"];
+ if (cerPath != nil) {
+ *certificate = [NSData dataWithContentsOfFile:cerPath];
+ }
+}
+
+- (void)setupUserAgent {
+ NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+ NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
+ _userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@", appName, appVersion, appBuildNumber, MGLAPIClientUserAgent];
+}
+
+#pragma mark - JSON Serialization
+
+- (NSData *)serializedDataForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events {
+ return [NSJSONSerialization dataWithJSONObject:events options:0 error:nil];
+}
+
+#pragma mark NSURLSessionDelegate
+
+- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
+
+ if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
+
+ SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
+ SecTrustResultType trustResult;
+
+ // Validate the certificate chain with the device's trust store anyway
+ // This *might* give use revocation checking
+ SecTrustEvaluate(serverTrust, &trustResult);
+ if (trustResult == kSecTrustResultUnspecified)
+ {
+ // Look for a pinned certificate in the server's certificate chain
+ long numKeys = SecTrustGetCertificateCount(serverTrust);
+
+ BOOL found = NO;
+ // Try GeoTrust Cert First
+ for (int lc = 0; lc < numKeys; lc++) {
+ SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
+ NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
+
+ // Compare Remote Key With Local Version
+ if ([remoteCertificateData isEqualToData:_geoTrustCert]) {
+ // Found the certificate; continue connecting
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ found = YES;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Fallback to Digicert Cert
+ for (int lc = 0; lc < numKeys; lc++) {
+ SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
+ NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
+
+ // Compare Remote Key With Local Version
+ if ([remoteCertificateData isEqualToData:_digicertCert]) {
+ // Found the certificate; continue connecting
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ found = YES;
+ break;
+ }
+ }
+
+ if (!found && _usesTestServer) {
+ // See if this is test server
+ for (int lc = 0; lc < numKeys; lc++) {
+ SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
+ NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
+
+ // Compare Remote Key With Local Version
+ if ([remoteCertificateData isEqualToData:_testServerCert]) {
+ // Found the certificate; continue connecting
+ completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ found = YES;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ // The certificate wasn't found in GeoTrust nor Digicert. Cancel the connection.
+ completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ }
+ }
+ }
+ else
+ {
+ // Certificate chain validation failed; cancel the connection
+ completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+ }
+ }
+}
+
+@end
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
index bec86fd399..7f4b632e8f 100644
--- a/platform/ios/src/MGLMapboxEvents.m
+++ b/platform/ios/src/MGLMapboxEvents.m
@@ -5,13 +5,12 @@
#import "NSProcessInfo+MGLAdditions.h"
#import "NSBundle+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
+#import "MGLAPIClient.h"
#include <mbgl/platform/darwin/reachability.h>
#include <sys/sysctl.h>
static const NSUInteger version = 1;
-static NSString *const MGLMapboxEventsUserAgent = @"MapboxEventsiOS/1.1";
-static NSString *MGLMapboxEventsAPIBase = @"https://api.tiles.mapbox.com";
NSString *const MGLEventTypeAppUserTurnstile = @"appUserTurnstile";
NSString *const MGLEventTypeMapLoad = @"map.load";
@@ -94,16 +93,13 @@ const NSTimeInterval MGLFlushInterval = 60;
@property (nonatomic) MGLMapboxEventsData *data;
@property (nonatomic, copy) NSString *appBundleId;
-@property (nonatomic, copy) NSString *appName;
-@property (nonatomic, copy) NSString *appVersion;
-@property (nonatomic, copy) NSString *appBuildNumber;
@property (nonatomic, copy) NSString *instanceID;
@property (nonatomic, copy) NSString *dateForDebugLogFile;
@property (nonatomic, copy) NSData *digicertCert;
@property (nonatomic, copy) NSData *geoTrustCert;
@property (nonatomic, copy) NSData *testServerCert;
@property (nonatomic) NSDateFormatter *rfc3339DateFormatter;
-@property (nonatomic) NSURLSession *session;
+@property (nonatomic) MGLAPIClient *apiClient;
@property (nonatomic) BOOL usesTestServer;
@property (nonatomic) BOOL canEnableDebugLogging;
@property (nonatomic, getter=isPaused) BOOL paused;
@@ -145,47 +141,16 @@ const NSTimeInterval MGLFlushInterval = 60;
self = [super init];
if (self) {
_appBundleId = [[NSBundle mainBundle] bundleIdentifier];
- _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
- _appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
- _appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
_instanceID = [[NSUUID UUID] UUIDString];
+ _apiClient = [[MGLAPIClient alloc] init];
NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
_serialQueue = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.events.serial", _appBundleId, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
- // Configure Events Infrastructure
- // ===============================
-
- // Check for TEST Metrics URL
- NSString *testURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMetricsTestServerURL"];
- if (testURL != nil) {
- MGLMapboxEventsAPIBase = testURL;
- _usesTestServer = YES;
- } else {
- // Explicitly Set For Clarity
- _usesTestServer = NO;
- }
-
_paused = YES;
[self resumeMetricsCollection];
NSBundle *resourceBundle = [NSBundle mgl_frameworkBundle];
- // Load Local Copy of Server's Public Key
- NSString *cerPath = nil;
- cerPath = [resourceBundle pathForResource:@"api_mapbox_com-geotrust" ofType:@"der"];
- if (cerPath != nil) {
- _geoTrustCert = [NSData dataWithContentsOfFile:cerPath];
- }
-
- cerPath = [resourceBundle pathForResource:@"api_mapbox_com-digicert" ofType:@"der"];
- if (cerPath != nil) {
- _digicertCert = [NSData dataWithContentsOfFile:cerPath];
- }
- cerPath = [resourceBundle pathForResource:@"star_tilestream_net" ofType:@"der"];
- if (cerPath != nil) {
- _testServerCert = [NSData dataWithContentsOfFile:cerPath];
- }
-
// Events Control
_eventQueue = [[NSMutableArray alloc] init];
@@ -313,8 +278,6 @@ const NSTimeInterval MGLFlushInterval = 60;
self.timer = nil;
[self.eventQueue removeAllObjects];
self.data = nil;
- [self.session invalidateAndCancel];
- self.session = nil;
[self validateUpdatingLocation];
}
@@ -341,7 +304,6 @@ const NSTimeInterval MGLFlushInterval = 60;
self.paused = NO;
self.data = [[MGLMapboxEventsData alloc] init];
- self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
[self validateUpdatingLocation];
}
@@ -429,16 +391,24 @@ const NSTimeInterval MGLFlushInterval = 60;
return;
}
- NSDictionary *vevt = @{@"event" : MGLEventTypeAppUserTurnstile,
- @"created" : [strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]],
- @"appBundleId" : strongSelf.appBundleId,
- @"vendorId": vendorID,
- @"version": @(version),
- @"instance": strongSelf.instanceID};
-
- [strongSelf.eventQueue addObject:vevt];
- [strongSelf flush];
- [strongSelf writeEventToLocalDebugLog:vevt];
+ NSDictionary *turnstileEventAttributes = @{@"event" : MGLEventTypeAppUserTurnstile,
+ @"created" : [strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]],
+ @"appBundleId" : strongSelf.appBundleId,
+ @"vendorId": vendorID,
+ @"version": @(version),
+ @"instance": strongSelf.instanceID};
+
+ if ([MGLAccountManager accessToken] == nil) {
+ return;
+ }
+ [strongSelf.apiClient postEvent:turnstileEventAttributes completionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ [MGLMapboxEvents pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error",
+ @"error": error}];
+ return;
+ }
+ [strongSelf writeEventToLocalDebugLog:turnstileEventAttributes];
+ }];
});
}
@@ -471,42 +441,22 @@ const NSTimeInterval MGLFlushInterval = 60;
// Called implicitly from public use of +flush.
//
- (void)postEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events {
- __weak MGLMapboxEvents *weakSelf = self;
-
+ if ([self isPaused]) {
+ return;
+ }
+
+ __weak __typeof__(self) weakSelf = self;
dispatch_async(self.serialQueue, ^{
- MGLMapboxEvents *strongSelf = weakSelf;
-
- if (!strongSelf) {
- return;
- }
-
- NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, [MGLAccountManager accessToken]];
- NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
- [request setValue:strongSelf.userAgent forHTTPHeaderField:@"User-Agent"];
- [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
- [request setHTTPMethod:@"POST"];
-
- if ([NSJSONSerialization isValidJSONObject:events]) {
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:events options:NSJSONWritingPrettyPrinted error:nil];
- [request setHTTPBody:jsonData];
-
- if (!strongSelf.paused) {
- [[strongSelf.session dataTaskWithRequest:request] resume];
- } else {
- for (MGLMapboxEventAttributes *event in events) {
- if ([event[@"event"] isEqualToString:MGLEventTypeAppUserTurnstile]) {
- NSURLSession *temporarySession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
- delegate:strongSelf
- delegateQueue:nil];
- [[temporarySession dataTaskWithRequest:request] resume];
- [temporarySession finishTasksAndInvalidate];
- }
- }
+ __strong __typeof__(weakSelf) strongSelf = weakSelf;
+ [self.apiClient postEvents:events completionHandler:^(NSError * _Nullable error) {
+ if (error) {
+ [MGLMapboxEvents pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"Network error",
+ @"error": error}];
+ return;
}
-
[MGLMapboxEvents pushDebugEvent:MGLEventTypeLocalDebug withAttributes:@{MGLEventKeyLocalDebugDescription: @"post",
@"debug.eventsCount": @(events.count)}];
- }
+ }];
});
}
@@ -519,10 +469,6 @@ const NSTimeInterval MGLFlushInterval = 60;
repeats:YES];
}
-- (NSString *)userAgent {
- return [NSString stringWithFormat:@"%@/%@/%@ %@", self.appName, self.appVersion, self.appBuildNumber, MGLMapboxEventsUserAgent];
-}
-
- (NSInteger)batteryLevel {
return [[NSNumber numberWithFloat:100 * [UIDevice currentDevice].batteryLevel] integerValue];
}
@@ -694,84 +640,6 @@ const NSTimeInterval MGLFlushInterval = 60;
[self validateUpdatingLocation];
}
-#pragma mark NSURLSessionDelegate
-
-- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
-
- if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
-
- SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
- SecTrustResultType trustResult;
-
- // Validate the certificate chain with the device's trust store anyway
- // This *might* give use revocation checking
- SecTrustEvaluate(serverTrust, &trustResult);
- if (trustResult == kSecTrustResultUnspecified)
- {
- // Look for a pinned certificate in the server's certificate chain
- long numKeys = SecTrustGetCertificateCount(serverTrust);
-
- BOOL found = NO;
- // Try GeoTrust Cert First
- for (int lc = 0; lc < numKeys; lc++) {
- SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
- NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
-
- // Compare Remote Key With Local Version
- if ([remoteCertificateData isEqualToData:_geoTrustCert]) {
- // Found the certificate; continue connecting
- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- found = YES;
- break;
- }
- }
-
- if (!found) {
- // Fallback to Digicert Cert
- for (int lc = 0; lc < numKeys; lc++) {
- SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
- NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
-
- // Compare Remote Key With Local Version
- if ([remoteCertificateData isEqualToData:_digicertCert]) {
- // Found the certificate; continue connecting
- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- found = YES;
- break;
- }
- }
-
- if (!found && _usesTestServer) {
- // See if this is test server
- for (int lc = 0; lc < numKeys; lc++) {
- SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc);
- NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
-
- // Compare Remote Key With Local Version
- if ([remoteCertificateData isEqualToData:_testServerCert]) {
- // Found the certificate; continue connecting
- completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- found = YES;
- break;
- }
- }
- }
-
- if (!found) {
- // The certificate wasn't found in GeoTrust nor Digicert. Cancel the connection.
- completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- }
- }
- }
- else
- {
- // Certificate chain validation failed; cancel the connection
- completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
- }
- }
-
-}
-
#pragma mark MGLMapboxEvents Debug
+ (void)pushDebugEvent:(NSString *)event withAttributes:(MGLMapboxEventAttributes *)attributeDictionary {