diff --git a/iphone/Classes/APSAnalytics/APSAnalytics.h b/iphone/Classes/APSAnalytics/APSAnalytics.h index 6fe2d9e0d2e..d456fbd4a77 100644 --- a/iphone/Classes/APSAnalytics/APSAnalytics.h +++ b/iphone/Classes/APSAnalytics/APSAnalytics.h @@ -72,12 +72,6 @@ withPayload:(NSDictionary*)data; //Helper properties -/** - * Retrieves the version of the SDK - * @return SDK version - */ -+(NSString*)getVersion; - /** * Returns if analytics has been enabled * @return Returns YES if analytics is enabled diff --git a/iphone/Classes/APSAnalytics/libAPSAnalytics.a b/iphone/Classes/APSAnalytics/libAPSAnalytics.a index 7953e46f342..dfb3ebad138 100644 Binary files a/iphone/Classes/APSAnalytics/libAPSAnalytics.a and b/iphone/Classes/APSAnalytics/libAPSAnalytics.a differ diff --git a/iphone/Classes/AnalyticsModule.h b/iphone/Classes/AnalyticsModule.h new file mode 100644 index 00000000000..a03bfd9c617 --- /dev/null +++ b/iphone/Classes/AnalyticsModule.h @@ -0,0 +1,12 @@ +/** + * Appcelerator Titanium Mobile + * Copyright (c) 2009-2014 by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ + +#import "TiModule.h" + +@interface AnalyticsModule : TiModule + +@end diff --git a/iphone/Classes/AnalyticsModule.m b/iphone/Classes/AnalyticsModule.m index ec976f66462..703adbc48f1 100644 --- a/iphone/Classes/AnalyticsModule.m +++ b/iphone/Classes/AnalyticsModule.m @@ -1,710 +1,51 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2010 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-2014 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Apache Public License * Please see the LICENSE included with this distribution for details. */ -#import "AnalyticsModule.h" -#import "TiApp.h" -#import "TiBase.h" -#import "TiHost.h" -#import "APSHTTPClient.h" - -#import -#import "NSData+Additions.h" -#import "Reachability.h" - -//TODO: -// -// 1. internal feature events -// 2. KS instrumentation -// 3. device reg for push -// - -extern BOOL const TI_APPLICATION_ANALYTICS; -extern NSString * const TI_APPLICATION_NAME; -extern NSString * const TI_APPLICATION_DEPLOYTYPE; -extern NSString * const TI_APPLICATION_ID; -extern NSString * const TI_APPLICATION_VERSION; -extern NSString * const TI_APPLICATION_GUID; - -#define TI_DB_WARN_ON_ATTEMPT_COUNT 5 -#define TI_DB_RETRY_INTERVAL_IN_SEC 15 -#define TI_DB_FLUSH_INTERVAL_IN_SEC 5 - -// version of our analytics DB -NSString * const TI_DB_VERSION = @"1"; - -@interface AnalyticsModule () - --(void)enqueueBlock:(void (^)(void))block; - -@end +#import "AnalyticsModule.h" +#import "APSAnalytics/APSAnalytics.h" @implementation AnalyticsModule -@synthesize lastEvent; --(id)init -{ - if ((self = [super init])) - { - lock = [[NSRecursiveLock alloc] init]; - eventQueue = [[NSOperationQueue alloc] init]; - } - return self; -} - --(void)dealloc -{ - if (database!=nil) - { - @try - { - [database close]; - } - @catch (NSException * e) - { - NSLog(@"[ERROR] Analytics: database error on shutdown: %@",e); - } - } - [eventQueue release]; - RELEASE_TO_NIL(database); - RELEASE_TO_NIL(retryTimer); - RELEASE_TO_NIL(flushTimer); - RELEASE_TO_NIL(url); - RELEASE_TO_NIL(lock); - [lastEvent release]; - [super dealloc]; -} -(NSString*)apiName { return @"Ti.Analytics"; } --(void)enqueueBlock:(void (^)(void))block -{ - [eventQueue addOperationWithBlock:block]; -} - - --(id)platform -{ - return [[[self pageContext] host] moduleNamed:@"Platform" context:[self pageContext]]; -} - --(id)network -{ - return [[[self pageContext] host] moduleNamed:@"Network" context:[self pageContext]]; -} - -#pragma mark Work - --(void)backgroundFlushEventQueue -{ - [self enqueueBlock:^{[self flushEventQueueWrapper];}]; -} - --(void)requeueEventsOnTimer -{ - [lock lock]; - NSError *error = nil; - - [database beginTransaction]; - - // get the number of attempts - PLSqliteResultSet *rs = (PLSqliteResultSet*)[database executeQuery:@"select attempts from last_attempt"]; - BOOL found = [rs next]; - int count = found ? [rs intForColumn:@"attempts"] : 0; - [rs close]; - - if (count == TI_DB_WARN_ON_ATTEMPT_COUNT) - { - DebugLog(@"[WARN] Analytics: %d transmission attempts failed.",count); - } - - NSString *sql = count == 0 ? @"insert into last_attempt VALUES (?,?)" : @"update last_attempt set date = ?, attempts = ?"; - PLSqlitePreparedStatement * statement = (PLSqlitePreparedStatement *) [database prepareStatement:sql error:&error]; - [statement bindParameters:[NSArray arrayWithObjects:[NSDate date],NUMINT(count+1),nil]]; - [statement executeUpdate]; - [database commitTransaction]; - - [statement close]; - - if (retryTimer==nil) - { - // start our re-attempt timer - DeveloperLog(@"[DEBUG] Attempted to send analytics event. No network; will try again in %d seconds.",TI_DB_RETRY_INTERVAL_IN_SEC); - retryTimer = [[NSTimer timerWithTimeInterval:TI_DB_RETRY_INTERVAL_IN_SEC target:self selector:@selector(backgroundFlushEventQueue) userInfo:nil repeats:YES] retain]; - [[NSRunLoop mainRunLoop] addTimer:retryTimer forMode:NSDefaultRunLoopMode]; - } - - if (flushTimer!=nil) - { - [flushTimer invalidate]; - RELEASE_TO_NIL(flushTimer); - } - [lock unlock]; -} - --(void)flushEventQueue +-(NSString*)lastEvent { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - if (![lock tryLock]) { - // We're currently in the middle of flushing the event queue, but didn't block on actually ADDING an event - - // this means that we don't need to run a second flush. - [pool release]; - return; - } - - // Can't use network module since pageContext/host may have shut down when sending 'final' events, - // giving us bad reachability info. - NetworkStatus status = [[Reachability reachabilityForInternetConnection] currentReachabilityStatus]; - - // when we can't reach the network, we need to log our attempt, - // set a retry timer and just bail... - if (status != ReachableViaWiFi && status != ReachableViaWWAN) - { - [self requeueEventsOnTimer]; - [lock unlock]; - [pool release]; - pool = nil; - return; - } - - // we can cancel our timers - if (retryTimer!=nil) - { - [retryTimer invalidate]; - RELEASE_TO_NIL(retryTimer); - } - if (flushTimer!=nil) - { - [flushTimer invalidate]; - RELEASE_TO_NIL(flushTimer); - } - - // We have a TOTAL of 5s to complete on shutdown, so take up as little of it as possible with analytics - - // analytics should be on the main thread only during shutdown (and if it is on the main thread otherwise we - // want it to not block) - NSTimeInterval timeout = [NSThread isMainThread] ? 2 : 5; - - //... And of course, if we're on a background timer, take up even less. We want this to complete - // before the expiration date, one way or another. - // TODO: Documentation is vague about this - can we ever be shutting down AND in the background state, - // at the same time? - if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) { - timeout = [[UIApplication sharedApplication] backgroundTimeRemaining] * 0.75; // Give us a good chunk of time to complete - } - [database beginTransaction]; - - NSMutableArray *data = [NSMutableArray array]; - - PLSqliteResultSet *rs = (PLSqliteResultSet*)[database executeQuery:@"SELECT data FROM pending_events"]; - while ([rs next]) - { - NSString *event = [rs stringForColumn:@"data"]; - NSError *jsonError = nil; - id frag = [TiUtils jsonParse:event error:&jsonError]; - if(jsonError != nil) { - NSLog(@"[ERROR] Problem sending analytics: %@", [jsonError localizedDescription]); - NSLog(@"[ERROR] Dropped event was: %@", event); - continue; - } - [data addObject:frag]; - } - [rs close]; - - if (url == nil) - { - NSString * kTiAnalyticsUrl = stringWithHexString(@"68747470733a2f2f6170692e61707063656c657261746f722e6e65742f702f76332f6d6f62696c652d747261636b2f"); - url = [[NSURL URLWithString:[kTiAnalyticsUrl stringByAppendingString:TI_APPLICATION_GUID]] retain]; - } - - APSHTTPPostForm *form = [[[APSHTTPPostForm alloc] init] autorelease]; - [form addHeaderKey:@"Content-Type" andHeaderValue:@"text/json"]; - [form addHeaderKey:@"User-Agent" andHeaderValue:[[TiApp app] userAgent]]; - [form setJSONData:data]; - - APSHTTPRequest *request = [[[APSHTTPRequest alloc] init] autorelease]; - [request setUrl:url]; - [request setPostForm:form]; - [request setMethod:@"POST"]; - [request setTimeout: timeout]; - [request setSynchronous:YES]; - @try - { - // run synchronous ... we are either in a sync call or - // we're on a background timer thread - [request send]; - - NSError* error = [[request response] error]; - if (error != nil) { - NSLog(@"[ERROR] Analytics error sending request: %@", [error localizedDescription]); - [database rollbackTransaction]; - [self requeueEventsOnTimer]; - [lock unlock]; - [pool release]; - pool = nil; - return; - } - - NSString *result = [[request response] responseString]; - if (result!=nil) - { - DeveloperLog(@"[DEBUG] analytics response %@",result); - DeveloperLog(@"[DEBUG] We tried to send to %@ the data: %@ ", url, [TiUtils jsonStringify:data]); - } - - // if we get here, it succeeded and we can clean up records in DB - [database executeUpdate:@"delete from pending_events"]; - [database executeUpdate:@"delete from last_attempt"]; - - // only commit if we don't get an error - [database commitTransaction]; - } - @catch (NSException * e) - { - NSLog(@"[ERROR] Error sending analytics: %@",e); - [database rollbackTransaction]; - } - [lock unlock]; - [pool release]; - pool = nil; -} - --(void)flushEventQueueWrapper -{ - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - __block UIBackgroundTaskIdentifier backgroundId = UIBackgroundTaskInvalid; - - // If we're calling the method outside iOS 4 we have bigger problems, so don't even perform that check here. - if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) - { - backgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - [[UIApplication sharedApplication] endBackgroundTask:backgroundId]; - backgroundId = UIBackgroundTaskInvalid; - }]; - } - - [self flushEventQueue]; - - if (backgroundId != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:backgroundId]; - } - [pool release]; -} - --(void)startFlushTimer -{ - // TODO: Race condition central - if (flushTimer==nil) - { - flushTimer = [[NSTimer timerWithTimeInterval:TI_DB_FLUSH_INTERVAL_IN_SEC target:self selector:@selector(backgroundFlushEventQueue) userInfo:nil repeats:NO] retain]; - [[NSRunLoop mainRunLoop] addTimer:flushTimer forMode:NSDefaultRunLoopMode]; - } -} - --(void)queueEvent:(NSString*)type name:(NSString*)name data:(NSDictionary*)data immediate:(BOOL)immediate -{ - static int sequence = 0; - - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - - [dict setObject:@"2" forKey:@"ver"]; - [dict setObject:[TiUtils UTCDate] forKey:@"ts"]; - [dict setObject:[TiUtils createUUID] forKey:@"id"]; - [dict setObject:NUMINT(sequence++) forKey:@"seq"]; - [dict setObject:[TiUtils appIdentifier] forKey:@"mid"]; - - [dict setObject:TI_APPLICATION_GUID forKey:@"aguid"]; - [dict setObject:TI_APPLICATION_DEPLOYTYPE forKey:@"deploytype"]; - [dict setObject:name forKey:@"event"]; - [dict setObject:type forKey:@"type"]; - [dict setObject:[[TiApp app] sessionId] forKey:@"sid"]; - if (data==nil) - { - [dict setObject:[NSNull null] forKey:@"data"]; - } - else - { - [dict setObject:data forKey:@"data"]; - } - NSString *remoteDeviceUUID = [[TiApp app] remoteDeviceUUID]; - if (remoteDeviceUUID==nil) - { - [dict setObject:[NSNull null] forKey:@"rdu"]; - } - else - { - [dict setObject:remoteDeviceUUID forKey:@"rdu"]; - } - NSError *err = nil; - id value = [TiUtils jsonStringify:dict error:&err]; - self.lastEvent = value; - - NSString *sql = [NSString stringWithFormat:@"INSERT INTO pending_events VALUES (?)"]; - NSError *error = nil; - // Don't lock until we need to - [lock lock]; - if (database==nil) - { - // doh, no database??? - [lock unlock]; - return; - } - PLSqlitePreparedStatement * statement = (PLSqlitePreparedStatement *) [database prepareStatement:sql error:&error]; - [statement bindParameters:[NSArray arrayWithObjects:value,nil]]; - - - [database beginTransaction]; - [statement executeUpdate]; - [database commitTransaction]; - [lock unlock]; - - [statement close]; - - - if (immediate) - { - // if immediate we send right now - [self flushEventQueueWrapper]; - } - else - { - // otherwise, we start our flush timer to send later - [self startFlushTimer]; - } -} - --(NSString*)checkForEnrollment:(BOOL*)enrolled -{ - NSString * supportFolderPath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - NSString * folderPath = [supportFolderPath stringByAppendingPathComponent:@"analytics"]; - NSFileManager * theFM = [NSFileManager defaultManager]; - BOOL isDirectory; - BOOL exists = [theFM fileExistsAtPath:folderPath isDirectory:&isDirectory]; - if (!exists) [theFM createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil]; - *enrolled = exists; - return folderPath; -} - --(void)loadDB:(NSString*)path create:(BOOL)create -{ - [lock lock]; - if ([database goodConnection]) { - [lock unlock]; - return; - } - // make sure SQLite can run from multiple threads - sqlite3_enable_shared_cache(TRUE); - - NSString *filepath = [NSString stringWithFormat:@"%@/analytics.db",path]; - - RELEASE_TO_NIL(database); - database = [[PLSqliteDatabase alloc] initWithPath:filepath]; - if (![database open]) - { - NSLog(@"[ERROR] Couldn't open analytics database"); - RELEASE_TO_NIL(database); - [lock unlock]; - return; - } - - [database beginTransaction]; - [database executeUpdate:@"CREATE TABLE IF NOT EXISTS version (version INTEGER)"]; - - NSString *currentVersion = nil; - PLSqliteResultSet *rs = (PLSqliteResultSet*)[database executeQuery:@"SELECT version from version"]; - [rs next]; - currentVersion = [TiUtils stringValue:[rs objectForColumn:@"version"]]; - [rs close]; - - BOOL migrate = NO; - - if (currentVersion==nil||[currentVersion isKindOfClass:[NSNull class]]) - { - migrate = YES; - [database executeUpdate:[NSString stringWithFormat:@"INSERT INTO version VALUES('%@')",TI_DB_VERSION]]; - } - else if (![currentVersion isEqualToString:TI_DB_VERSION]) - { - migrate = YES; - } - - if (migrate) - { - [database executeUpdate:@"DROP TABLE IF EXISTS last_attempt"]; - [database executeUpdate:@"DROP TABLE IF EXISTS pending_events"]; - [database executeUpdate:@"CREATE TABLE IF NOT EXISTS last_attempt (date DATE, attempts INTEGER)"]; - [database executeUpdate:@"CREATE TABLE IF NOT EXISTS pending_events (data TEXT)"]; - } - - [database commitTransaction]; - [lock unlock]; -} - --(void)enroll -{ - @try - { - // if not online (since we need some stuff), re-queue for later - id online = [[self network] valueForKey:@"online"]; - if ([TiUtils boolValue:online]==NO) - { - [self performSelector:@selector(enroll) withObject:nil afterDelay:10]; - return; - } - - id platform = [self platform]; - - NSMutableDictionary *enrollment = [NSMutableDictionary dictionary]; - - [enrollment setObject:[platform valueForKey:@"processorCount"] forKey:@"oscpu"]; - [enrollment setObject:[platform valueForKey:@"ostype"] forKey:@"ostype"]; - [enrollment setObject:[platform valueForKey:@"architecture"] forKey:@"osarch"]; - [enrollment setObject:[platform valueForKey:@"model"] forKey:@"model"]; - [enrollment setObject:TI_APPLICATION_NAME forKey:@"app_name"]; - [enrollment setObject:TI_APPLICATION_DEPLOYTYPE forKey:@"deploytype"]; - [enrollment setObject:TI_APPLICATION_ID forKey:@"app_id"]; - [enrollment setObject:TI_APPLICATION_VERSION forKey:@"app_version"]; - [enrollment setObject:@"iphone" forKey:@"platform"]; - - [self queueEvent:@"ti.enroll" name:@"ti.enroll" data:enrollment immediate:NO]; - } - @catch (NSException * e) - { - NSLog(@"[ERROR] Error sending analytics event: %@",e); - } -} - --(NSDictionary *)startupDataPayload -{ - BOOL enrolled = NO; - NSString *path = [self checkForEnrollment:&enrolled]; - - [self loadDB:path create:enrolled==NO]; - - if (enrolled==NO) - { - [self enroll]; - } - - int tz = [[NSTimeZone systemTimeZone] secondsFromGMT] / 60; // get the timezone offset to UTC in minutes - struct utsname u; - uname(&u); - - id platform = [self platform]; - id network = [self network]; - - NSString * version = [NSString stringWithCString:TI_VERSION_STR encoding:NSUTF8StringEncoding]; - NSString * os = [platform valueForKey:@"version"]; - NSString * username = [platform valueForKey:@"username"]; - NSString * mmodel = [platform valueForKey:@"model"]; - NSString * nettype = [network valueForKey:@"networkTypeName"]; - - NSDictionary * data = [NSDictionary dictionaryWithObjectsAndKeys: - NUMINT(tz),@"tz", - TI_APPLICATION_DEPLOYTYPE,@"deploytype", - @"iphone",@"os", - version,@"version", - TI_APPLICATION_VERSION,@"app_version", - os,@"osver", - VAL_OR_NSNULL(nettype),@"nettype", - VAL_OR_NSNULL(mmodel),@"model", - @"iphone", @"platform", - nil - ]; - return data; -} - -#pragma mark Lifecycle - --(void)startup -{ - static bool AnalyticsStarted = NO; - - DebugLog(@"[DEBUG] Analytics is enabled = %@", (TI_APPLICATION_ANALYTICS==NO ? @"NO":@"YES")); - - if (AnalyticsStarted || TI_APPLICATION_ANALYTICS==NO) - { - return; - } - - AnalyticsStarted = YES; - - WARN_IF_BACKGROUND_THREAD_OBJ; //NSNotificationCenter is not threadsafe! - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(analyticsEvent:) name:kTiAnalyticsNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteDeviceUUIDChanged:) name:kTiRemoteDeviceUUIDNotification object:nil]; - [self enqueueBlock:^{[self queueEvent:@"ti.start" name:@"ti.start" data:[self startupDataPayload] immediate:NO];}]; - [super startup]; -} - --(void)shutdown:(id)sender -{ - if (TI_APPLICATION_ANALYTICS) - { - [self enqueueBlock:^{[self queueEvent:@"ti.end" name:@"ti.end" data:nil immediate:YES];}]; - WARN_IF_BACKGROUND_THREAD_OBJ; //NSNotificationCenter is not threadsafe! - [[NSNotificationCenter defaultCenter] removeObserver:self name:kTiAnalyticsNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:kTiRemoteDeviceUUIDNotification object:nil]; - } -} - -#pragma mark Event Notification - --(void)analyticsEvent:(NSNotification*)note -{ - id userInfo = [note userInfo]; - if (![userInfo isKindOfClass:[NSDictionary class]]) - { - DebugLog(@"[ERROR] Invalid analytics event received. Expected dictionary, got: %@",[userInfo class]); - return; - } - [self enqueueBlock:^{ - NSDictionary *event = (NSDictionary*)userInfo; - NSString *name = [event objectForKey:@"name"]; - NSString *type = [event objectForKey:@"type"]; - NSDictionary *data = [event objectForKey:@"data"]; - - if (IS_NULL_OR_NIL(data) && [type isEqualToString:@"ti.foreground"]) { - //Do we want to open this up to other events? On one hand, more data - //is good. On the other, sending unneeded data is expensive. - data = [self startupDataPayload]; - } - [self queueEvent:type name:name data:data immediate:NO]; - }]; -} - --(void)remoteDeviceUUIDChanged:(NSNotification*)note -{ - id userInfo = [note userInfo]; - NSString *deviceid = [userInfo objectForKey:@"deviceid"]; - NSDictionary *event = [NSDictionary dictionaryWithObject:deviceid forKey:@"deviceid"]; - [self enqueueBlock:^{[self queueEvent:@"app.settings" name:@"RemoteDeviceUUID" data:event immediate:NO];}]; -} - -#pragma mark Helper methods - --(NSDictionary*)dataToDictionary:(id)data -{ - if (data!=nil && [data isKindOfClass:[NSDictionary class]]==NO) - { - id value = [TiUtils jsonParse:data]; - data = [NSDictionary dictionaryWithObject:value forKey:@"data"]; - } - return data; -} - -// internal event handler --(void)queueKeyValueEvent:(id)args type:(NSString*)type -{ - if ([args count] < 1) - { - [self throwException:@"invalid number of arguments, expected at least 1" subreason:nil location:CODELOCATION]; - return; - } - NSString *event = [args objectAtIndex:0]; - id data = [args count] > 1 ? [args objectAtIndex:1] : [NSDictionary dictionary]; - [self enqueueBlock:^{ - NSDictionary *payload = [NSDictionary dictionaryWithObjectsAndKeys:[self dataToDictionary:data],@"data",nil]; - [self queueEvent:type name:event data:payload immediate:NO]; - }]; -} - -#pragma mark Public APIs - --(void)addEvent:(id)args -{ - if ([args count] < 2) - { - [self throwException:@"invalid number of arguments, expected at least 2" subreason:nil location:CODELOCATION]; - return; - } - NSString *type = [args objectAtIndex:0]; - NSString *name = [args objectAtIndex:1]; - id data = [args count] > 2 ? [args objectAtIndex:2] : [NSDictionary dictionary]; - [self enqueueBlock:^{ - DeveloperLog(@"[INFO] Analytics->addEvent with type: %@, name: %@, data: %@",type,name,data); - - [self queueEvent:type name:name data:[self dataToDictionary:data] immediate:NO]; - }]; + return [APSAnalytics getLastEvent]; } -(void)navEvent:(id)args { - // from, to, event, data - if ([args count] < 2) + if ([args count] < 2) { [self throwException:@"invalid number of arguments, expected at least 2" subreason:nil location:CODELOCATION]; return; } - NSString *from = [args objectAtIndex:0]; - NSString *to = [args objectAtIndex:1]; - NSString *event = [args count] > 2 ? [args objectAtIndex:2] : @""; - id data = [args count] > 3 ? [args objectAtIndex:3] : [NSDictionary dictionary]; - [self enqueueBlock:^{ - NSDictionary *payload = [NSDictionary dictionaryWithObjectsAndKeys:from,@"from", - to,@"to",[self dataToDictionary:data],@"data",nil]; - - DeveloperLog(@"[INFO] Analytics->navEvent with from: %@, to: %@, event: %@, data: %@",from,to,event,data); - - [self queueEvent:@"app.nav" name:event data:payload immediate:NO]; - }]; + NSString *from = [[args objectAtIndex:0] autorelease]; + NSString *to = [[args objectAtIndex:1] autorelease]; + NSString *event = [args count] > 2 ? [[args objectAtIndex:2] autorelease] : @""; + id data = [args count] > 3 ? [[args objectAtIndex:3] autorelease] : [NSDictionary dictionary]; + [APSAnalytics sendAppNavEventFrom:from to:to withName:event withPayload:data]; } --(void)timedEvent:(id)args + +-(void)featureEvent:(id)args { - // event, start, stop, duration, data - if ([args count] < 4) + if ([args count] < 1) { - [self throwException:@"invalid number of arguments, expected at least 4" subreason:nil location:CODELOCATION]; + [self throwException:@"invalid number of arguments, expected at least 1" subreason:nil location:CODELOCATION]; return; } - NSString *event = [args objectAtIndex:0]; - NSDate *start = [args objectAtIndex:1]; - NSDate *stop = [args objectAtIndex:2]; - ENSURE_TYPE(start,NSDate); - ENSURE_TYPE(stop,NSDate); - - id duration = [args objectAtIndex:3]; - id data = [args count] > 4 ? [args objectAtIndex:4] : [NSDictionary dictionary]; - - [self enqueueBlock:^{ - NSDictionary *payload = [NSDictionary dictionaryWithObjectsAndKeys: - [TiUtils UTCDateForDate:start],@"start", - [TiUtils UTCDateForDate:stop],@"stop", - duration,@"duration", - [self dataToDictionary:data],@"data",nil]; - - DeveloperLog(@"[INFO] Analytics->timedEvent with event: %@, start: %@, stop: %@, duration: %@, data: %@",event,start,stop,duration,data); - - [self queueEvent:@"app.timed_event" name:event data:payload immediate:NO]; - }]; -} - -#define PRINT_EVENT_DETAILS(name,args) \ - id event = [args objectAtIndex:0];\ - id data = [args count] > 1 ? [args objectAtIndex:1] : nil;\ - DeveloperLog(@"[INFO] Analytics->%s with event: %@, data: %@",#name,event,data);\ - - --(void)featureEvent:(id)args -{ - PRINT_EVENT_DETAILS(featureEvent,args); - [self queueKeyValueEvent:args type:@"app.feature"]; -} - --(void)settingsEvent:(id)args -{ - PRINT_EVENT_DETAILS(settingsEvent,args); - [self queueKeyValueEvent:args type:@"app.settings"]; -} - --(void)userEvent:(id)args -{ - PRINT_EVENT_DETAILS(userEvent,args); - [self queueKeyValueEvent:args type:@"app.user"]; + NSString *event = [[args objectAtIndex:0] autorelease]; + id data = [args count] > 1 ? [[args objectAtIndex:1] autorelease] : [NSDictionary dictionary]; + + [APSAnalytics sendFeatureEvent:event withPayload:data]; } @end diff --git a/iphone/Classes/KrollBridge.m b/iphone/Classes/KrollBridge.m index 8aee2ea57ea..7c19c3b5c3c 100644 --- a/iphone/Classes/KrollBridge.m +++ b/iphone/Classes/KrollBridge.m @@ -71,6 +71,7 @@ -(id)initWithContext:(KrollContext*)context_ host:(TiHost*)host_ context:(id 0)) { [[APSAnalytics class] performSelector:@selector(setBuildType:) withObject:TI_APPLICATION_BUILD_TYPE]; } + [[APSAnalytics class] performSelector:@selector(setSDKVersion:) withObject:[NSString stringWithFormat:@"ti.%@",[module performSelector:@selector(version)]]]; [APSAnalytics enableWithAppKey:TI_APPLICATION_GUID withDeployType:TI_APPLICATION_DEPLOYTYPE]; } } diff --git a/iphone/iphone/Titanium.xcodeproj/project.pbxproj b/iphone/iphone/Titanium.xcodeproj/project.pbxproj index 5adc52003a0..a86b585b6a2 100755 --- a/iphone/iphone/Titanium.xcodeproj/project.pbxproj +++ b/iphone/iphone/Titanium.xcodeproj/project.pbxproj @@ -466,6 +466,9 @@ 312E36C1190B06D9008EAB8D /* libAPSAnalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E36C0190B06D9008EAB8D /* libAPSAnalytics.a */; }; 312E36C2190B06D9008EAB8D /* libAPSAnalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E36C0190B06D9008EAB8D /* libAPSAnalytics.a */; }; 312E36C3190B06D9008EAB8D /* libAPSAnalytics.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 312E36C0190B06D9008EAB8D /* libAPSAnalytics.a */; }; + 3186097F192BDB4E00093482 /* AnalyticsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3186097E192BDB4E00093482 /* AnalyticsModule.m */; }; + 31860980192BDB4E00093482 /* AnalyticsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3186097E192BDB4E00093482 /* AnalyticsModule.m */; }; + 31860981192BDB4E00093482 /* AnalyticsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 3186097E192BDB4E00093482 /* AnalyticsModule.m */; }; 4688700715E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4688700615E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m */; }; 4688700815E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4688700615E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m */; }; 4688700915E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 4688700615E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m */; }; @@ -1362,6 +1365,8 @@ 2BDEA4F21448FFB7004EC750 /* TiUIiOSTabbedBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSTabbedBarProxy.m; sourceTree = ""; }; 312E36BF190B06D9008EAB8D /* APSAnalytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APSAnalytics.h; sourceTree = ""; }; 312E36C0190B06D9008EAB8D /* libAPSAnalytics.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libAPSAnalytics.a; sourceTree = ""; }; + 3186097D192BDB4E00093482 /* AnalyticsModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AnalyticsModule.h; sourceTree = ""; }; + 3186097E192BDB4E00093482 /* AnalyticsModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnalyticsModule.m; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* Titanium_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Titanium_Prefix.pch; sourceTree = ""; }; 4688700515E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIiPhoneAlertDialogStyleProxy.h; sourceTree = ""; }; 4688700615E39A9D008FA326 /* TiUIiPhoneAlertDialogStyleProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiPhoneAlertDialogStyleProxy.m; sourceTree = ""; }; @@ -2203,6 +2208,8 @@ 24CA8C57111165070084E2DE /* Analytics */ = { isa = PBXGroup; children = ( + 3186097D192BDB4E00093482 /* AnalyticsModule.h */, + 3186097E192BDB4E00093482 /* AnalyticsModule.m */, ); name = Analytics; sourceTree = ""; @@ -3430,6 +3437,7 @@ 1DCC542F13FF0B0800DF3EE5 /* TIDOMCharacterDataProxy.m in Sources */, 1D19459613FF3BC400E2B4D0 /* TIDOMDOMImplementationProxy.m in Sources */, 1D19459B13FF3BE500E2B4D0 /* TIDOMDocumentTypeProxy.m in Sources */, + 3186097F192BDB4E00093482 /* AnalyticsModule.m in Sources */, EF0ADCFA143F9ABF00977386 /* NSData+Additions.m in Sources */, EF0ADD1A143FA0CD00977386 /* GeolocationModule.m in Sources */, 2B1F65771431031E006C5D37 /* ApplicationDefaults.m in Sources */, @@ -3708,12 +3716,11 @@ 2B94603813F0A2AE000C5BEA /* TiUIiOSCoverFlowView.m in Sources */, 2B94603913F0A2AE000C5BEA /* TiUIiOSCoverFlowViewProxy.m in Sources */, 1DCC543013FF0B0800DF3EE5 /* TIDOMCharacterDataProxy.m in Sources */, + 31860980192BDB4E00093482 /* AnalyticsModule.m in Sources */, 1D19459713FF3BC400E2B4D0 /* TIDOMDOMImplementationProxy.m in Sources */, 1D19459C13FF3BE500E2B4D0 /* TIDOMDocumentTypeProxy.m in Sources */, EF0ADCFB143F9ABF00977386 /* NSData+Additions.m in Sources */, EF0ADD1B143FA0CD00977386 /* GeolocationModule.m in Sources */, - B610BB021899CE2000B178C3 /* TiHTTPResponse.m in Sources */, - EF0ADD20143FA0E200977386 /* AnalyticsModule.m in Sources */, 2B1F65781431031E006C5D37 /* ApplicationDefaults.m in Sources */, 848C2595145F37A300E1B0F1 /* TiDOMCDATANodeProxy.m in Sources */, 848C259B145F3FE200E1B0F1 /* TiDOMCommentProxy.m in Sources */, @@ -3988,12 +3995,11 @@ 2B94603A13F0A2AE000C5BEA /* TiUIiOSCoverFlowView.m in Sources */, 2B94603B13F0A2AE000C5BEA /* TiUIiOSCoverFlowViewProxy.m in Sources */, 1DCC543113FF0B0800DF3EE5 /* TIDOMCharacterDataProxy.m in Sources */, + 31860981192BDB4E00093482 /* AnalyticsModule.m in Sources */, 1D19459813FF3BC400E2B4D0 /* TIDOMDOMImplementationProxy.m in Sources */, 1D19459D13FF3BE500E2B4D0 /* TIDOMDocumentTypeProxy.m in Sources */, EF0ADCFC143F9ABF00977386 /* NSData+Additions.m in Sources */, EF0ADD1C143FA0CD00977386 /* GeolocationModule.m in Sources */, - B610BB031899CE2000B178C3 /* TiHTTPResponse.m in Sources */, - EF0ADD21143FA0E200977386 /* AnalyticsModule.m in Sources */, 2B1F65791431031E006C5D37 /* ApplicationDefaults.m in Sources */, 848C2596145F37A300E1B0F1 /* TiDOMCDATANodeProxy.m in Sources */, 848C259C145F3FE200E1B0F1 /* TiDOMCommentProxy.m in Sources */,