Files
MapLibreParticleLab/platform/ios/src/MGLMapboxEvents.m
Randall Lee 5a837c1702 [ios] Add custom events library start-up delay (#12088)
* [ios] Add custom delay 

Adds a custom delay to the events library initialization. This is only active when using certain profile configurations.

* [ios] events library version update to v0.4.1

* Update Events Library to include missing MGL_EXPORT

* Add MMEEventsService files to Events Lib Submodule

* Change delayTime type to int64_t
2018-06-08 14:39:43 -07:00

199 lines
10 KiB
Objective-C

#import "MGLMapboxEvents.h"
#import "NSBundle+MGLAdditions.h"
#import "NSProcessInfo+MGLAdditions.h"
static NSString * const MGLAPIClientUserAgentBase = @"mapbox-maps-ios";
static NSString * const MGLMapboxAccountType = @"MGLMapboxAccountType";
static NSString * const MGLMapboxMetricsEnabled = @"MGLMapboxMetricsEnabled";
static NSString * const MGLMapboxMetricsDebugLoggingEnabled = @"MGLMapboxMetricsDebugLoggingEnabled";
static NSString * const MGLTelemetryAccessToken = @"MGLTelemetryAccessToken";
static NSString * const MGLTelemetryBaseURL = @"MGLTelemetryBaseURL";
static NSString * const MGLEventsProfile = @"MMEEventsProfile";
static NSString * const MGLVariableGeofence = @"VariableGeofence";
@interface MGLMapboxEvents ()
@property (nonatomic) MMEEventsManager *eventsManager;
@property (nonatomic) NSURL *baseURL;
@property (nonatomic, copy) NSString *accessToken;
@end
@implementation MGLMapboxEvents
+ (void)initialize {
if (self == [MGLMapboxEvents class]) {
NSBundle *bundle = [NSBundle mainBundle];
NSNumber *accountTypeNumber = [bundle objectForInfoDictionaryKey:MGLMapboxAccountType];
[[NSUserDefaults standardUserDefaults] registerDefaults:@{MGLMapboxAccountType: accountTypeNumber ?: @0,
MGLMapboxMetricsEnabled: @YES,
MGLMapboxMetricsDebugLoggingEnabled: @NO}];
}
}
+ (nullable instancetype)sharedInstance {
if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
return nil;
}
static dispatch_once_t onceToken;
static MGLMapboxEvents *_sharedInstance;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_eventsManager = [[MMEEventsManager alloc] init];
_eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsDebugLoggingEnabled];
_eventsManager.accountType = [[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountType];
_eventsManager.metricsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:MGLMapboxMetricsEnabled];
// It is possible for the shared instance of this class to be created because of a call to
// +[MGLAccountManager load] early on in the app lifecycle of the host application.
// If user default values for access token and base URL are available, they are stored here
// on local properties so that they can be applied later once MMEEventsManager is fully initialized
// (once -[MMEEventsManager initializeWithAccessToken:userAgentBase:hostSDKVersion:] is called.
// Normally, the telem access token and base URL are not set this way. However, overriding these values
// with user defaults can be useful for testing with an alternative (test) backend system.
if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessToken]) {
self.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessToken];
}
if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURL]) {
self.baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURL]];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)userDefaultsDidChange:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNonDisablingConfigurationValues];
[self updateDisablingConfigurationValuesWithNotification:notification];
});
}
- (void)updateNonDisablingConfigurationValues {
self.eventsManager.debugLoggingEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsDebugLoggingEnabled"];
// It is possible for `MGLTelemetryAccessToken` to have been set yet `userDefaultsDidChange:`
// is called before `setupWithAccessToken:` is called.
// In that case, setting the access token here will have no effect. In practice, that's fine
// because the access token value will be resolved when `setupWithAccessToken:` is called eventually
if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryAccessToken]) {
self.eventsManager.accessToken = [[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryAccessToken];
}
// It is possible for `MGLTelemetryBaseURL` to have been set yet `userDefaultsDidChange:`
// is called before setupWithAccessToken: is called.
// In that case, setting the base URL here will have no effect. In practice, that's fine
// because the base URL value will be resolved when `setupWithAccessToken:` is called eventually
if ([[[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys] containsObject:MGLTelemetryBaseURL]) {
NSURL *baseURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:MGLTelemetryBaseURL]];
self.eventsManager.baseURL = baseURL;
}
}
- (void)updateDisablingConfigurationValuesWithNotification:(NSNotification *)notification {
// Guard against over calling pause / resume if the values this implementation actually
// cares about have not changed. We guard because the pause and resume method checks CoreLocation's
// authorization status and that can drag on the main thread if done too many times (e.g. if the host
// app heavily uses the user defaults API and this method is called very frequently)
if ([[notification object] respondsToSelector:@selector(objectForKey:)]) {
NSUserDefaults *userDefaults = [notification object];
NSInteger accountType = [userDefaults integerForKey:MGLMapboxAccountType];
BOOL metricsEnabled = [userDefaults boolForKey:MGLMapboxMetricsEnabled];
if (accountType != self.eventsManager.accountType || metricsEnabled != self.eventsManager.metricsEnabled) {
self.eventsManager.accountType = accountType;
self.eventsManager.metricsEnabled = metricsEnabled;
[self.eventsManager pauseOrResumeMetricsCollectionIfRequired];
}
}
}
+ (void)setupWithAccessToken:(NSString *)accessToken {
int64_t delayTime = 0;
if ([[[NSBundle mainBundle] objectForInfoDictionaryKey:MGLEventsProfile] isEqualToString:MGLVariableGeofence]) {
delayTime = 10;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *semanticVersion = [NSBundle mgl_frameworkInfoDictionary][@"MGLSemanticVersionString"];
NSString *shortVersion = [NSBundle mgl_frameworkInfoDictionary][@"CFBundleShortVersionString"];
NSString *sdkVersion = semanticVersion ?: shortVersion;
// It is possible that an alternative access token was already set on this instance when the class was loaded
// Use it if it exists
NSString *resolvedAccessToken = [MGLMapboxEvents sharedInstance].accessToken ?: accessToken;
[[[self sharedInstance] eventsManager] initializeWithAccessToken:resolvedAccessToken userAgentBase:MGLAPIClientUserAgentBase hostSDKVersion:sdkVersion];
// It is possible that an alternative base URL was set on this instance when the class was loaded
// Use it if it exists
if ([MGLMapboxEvents sharedInstance].baseURL) {
[[MGLMapboxEvents sharedInstance] eventsManager].baseURL = [MGLMapboxEvents sharedInstance].baseURL;
}
[self flush];
});
}
+ (void)pushTurnstileEvent {
[[[self sharedInstance] eventsManager] sendTurnstileEvent];
}
+ (void)pushEvent:(NSString *)event withAttributes:(MMEMapboxEventAttributes *)attributeDictionary {
[[[self sharedInstance] eventsManager] enqueueEventWithName:event attributes:attributeDictionary];
}
+ (void)flush {
[[[self sharedInstance] eventsManager] flush];
}
+ (void)ensureMetricsOptoutExists {
NSNumber *shownInAppNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMapboxMetricsEnabledSettingShownInApp"];
BOOL metricsEnabledSettingShownInAppFlag = [shownInAppNumber boolValue];
if (!metricsEnabledSettingShownInAppFlag &&
[[NSUserDefaults standardUserDefaults] integerForKey:MGLMapboxAccountType] == 0) {
// Opt-out is not configured in UI, so check for Settings.bundle
id defaultEnabledValue;
NSString *appSettingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
if (appSettingsBundle) {
// Dynamic Settings.bundle loading based on http://stackoverflow.com/a/510329/2094275
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[appSettingsBundle stringByAppendingPathComponent:@"Root.plist"]];
NSArray *preferences = settings[@"PreferenceSpecifiers"];
for (NSDictionary *prefSpecification in preferences) {
if ([prefSpecification[@"Key"] isEqualToString:MGLMapboxMetricsEnabled]) {
defaultEnabledValue = prefSpecification[@"DefaultValue"];
}
}
}
if (!defaultEnabledValue) {
[NSException raise:@"Telemetry opt-out missing" format:
@"End users must be able to opt out of Mapbox Telemetry in your app, either inside Settings (via Settings.bundle) or inside this app. "
@"By default, this opt-out control is included as a menu item in the attribution action sheet. "
@"If you reimplement the opt-out control inside this app, disable this assertion by setting MGLMapboxMetricsEnabledSettingShownInApp to YES in Info.plist."
@"\n\nSee https://www.mapbox.com/ios-sdk/#telemetry_opt_out for more information."
@"\n\nAdditionally, by hiding this attribution control you agree to display the required attribution elsewhere in this app."];
}
}
}
@end