Skip to content

Commit

Permalink
feat: add default events (#455)
Browse files Browse the repository at this point in the history
* feat: add app lifecycle events and screen view events

* feat: add deep link helper

* fix: swift auto-rename

* feat: add tests, fix minor issues

* fix: xcode file issue

* fix: build issue

* fix: flaky test

* refactor: add save build/version check and move events to constant file
  • Loading branch information
liuyang1520 authored Jul 5, 2023
1 parent 94b4dcf commit 9bf9664
Show file tree
Hide file tree
Showing 16 changed files with 862 additions and 23 deletions.
56 changes: 56 additions & 0 deletions Amplitude.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Framework/AmplitudeFramework.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import <Amplitude/AMPIngestionMetadata.h>
#import <Amplitude/AMPMiddleware.h>
#import <Amplitude/AMPServerZone.h>
#import <Amplitude/AMPDefaultTrackingOptions.h>

#if TARGET_OS_WATCH
#import <Amplitude/AMPBackgroundNotifier.h>
Expand Down
20 changes: 20 additions & 0 deletions Sources/Amplitude/AMPConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,23 @@ extern NSString *const AMP_PLAN_VERSION_ID;
// Ingestion Metadata
extern NSString *const AMP_INGESTION_METADATA_SOURCE_NAME;
extern NSString *const AMP_INGESTION_METADATA_SOURCE_VERSION;

// Events
extern NSString *const kAMPSessionStartEvent;
extern NSString *const kAMPSessionEndEvent;
extern NSString *const kAMPApplicationInstalled;
extern NSString *const kAMPApplicationUpdated;
extern NSString *const kAMPApplicationOpened;
extern NSString *const kAMPApplicationBackgrounded;
extern NSString *const kAMPDeepLinkOpened;
extern NSString *const kAMPScreenViewed;
extern NSString *const kAMPRevenueEvent;

extern NSString *const kAMPEventPropVersion;
extern NSString *const kAMPEventPropBuild;
extern NSString *const kAMPEventPropPreviousVersion;
extern NSString *const kAMPEventPropPreviousBuild;
extern NSString *const kAMPEventPropFromBackground;
extern NSString *const kAMPEventPropLinkUrl;
extern NSString *const kAMPEventPropLinkReferrer;
extern NSString *const kAMPEventPropScreenName;
20 changes: 20 additions & 0 deletions Sources/Amplitude/AMPConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,23 @@

NSString *const AMP_INGESTION_METADATA_SOURCE_NAME = @"source_name";
NSString *const AMP_INGESTION_METADATA_SOURCE_VERSION = @"source_version";

// Amplitude Events
NSString *const kAMPSessionStartEvent = @"session_start";
NSString *const kAMPSessionEndEvent = @"session_end";
NSString *const kAMPApplicationInstalled = @"[Amplitude] Application Installed";
NSString *const kAMPApplicationUpdated = @"[Amplitude] Application Updated";
NSString *const kAMPApplicationOpened = @"[Amplitude] Application Opened";
NSString *const kAMPApplicationBackgrounded = @"[Amplitude] Application Backgrounded";
NSString *const kAMPDeepLinkOpened = @"[Amplitude] Deep Link Opened";
NSString *const kAMPScreenViewed = @"[Amplitude] Screen Viewed";
NSString *const kAMPRevenueEvent = @"revenue_amount";

NSString *const kAMPEventPropVersion = @"[Amplitude] Version";
NSString *const kAMPEventPropBuild = @"[Amplitude] Build";
NSString *const kAMPEventPropPreviousVersion = @"[Amplitude] Previous Version";
NSString *const kAMPEventPropPreviousBuild = @"[Amplitude] Previous Build";
NSString *const kAMPEventPropFromBackground = @"[Amplitude] From Background";
NSString *const kAMPEventPropLinkUrl = @"[Amplitude] Link URL";
NSString *const kAMPEventPropLinkReferrer = @"[Amplitude] Link Referrer";
NSString *const kAMPEventPropScreenName = @"[Amplitude] Screen Name";
67 changes: 67 additions & 0 deletions Sources/Amplitude/AMPDefaultTrackingOptions.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// AMPDefaultTrackingOptions.m
// Copyright (c) 2023 Amplitude Inc. (https://amplitude.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#import "AMPDefaultTrackingOptions.h"

@implementation AMPDefaultTrackingOptions

/*
* Create an AMPDefaultTrackingOptions object
*/
- (instancetype)init {
if (self = [super init]) {
self.sessions = NO;
self.appLifecycles = NO;
self.deepLinks = NO;
self.screenViews = NO;
}
return self;
}

+ (instancetype)initWithSessions:(BOOL)sessions
appLifecycles:(BOOL)appLifecycles
deepLinks:(BOOL)deepLinks
screenViews:(BOOL)screenViews {
AMPDefaultTrackingOptions *instance = [[self alloc] init];
instance.sessions = sessions;
instance.appLifecycles = appLifecycles;
instance.deepLinks = deepLinks;
instance.screenViews = screenViews;
return instance;
}

+ (instancetype)initWithAllEnabled {
return [self initWithSessions:YES
appLifecycles:YES
deepLinks:YES
screenViews:YES];
}

+ (instancetype)initWithNoneEnabled {
return [self initWithSessions:NO
appLifecycles:NO
deepLinks:NO
screenViews:NO];
}

@end
138 changes: 119 additions & 19 deletions Sources/Amplitude/Amplitude.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
#import "AMPMiddlewareRunner.h"
#import "AMPIdentifyInterceptor.h"
#import "AMPEventUtils.h"

#if !TARGET_OS_OSX && !TARGET_OS_WATCH
#import "UIViewController+AMPScreen.h"
#endif

#import <math.h>
#import <CommonCrypto/CommonDigest.h>

Expand Down Expand Up @@ -101,10 +106,6 @@ @interface Amplitude ()
@property (nonatomic, copy, readwrite) NSString *contentTypeHeader;
@end

NSString *const kAMPSessionStartEvent = @"session_start";
NSString *const kAMPSessionEndEvent = @"session_end";
NSString *const kAMPRevenueEvent = @"revenue_amount";

static NSString *const BACKGROUND_QUEUE_NAME = @"BACKGROUND";
static NSString *const DATABASE_VERSION = @"database_version";
static NSString *const DEVICE_ID = @"device_id";
Expand All @@ -117,6 +118,9 @@ @interface Amplitude ()
static NSString *const OPT_OUT = @"opt_out";
static NSString *const USER_ID = @"user_id";
static NSString *const SEQUENCE_NUMBER = @"sequence_number";
// for app lifecycle events
static NSString *const APP_VERSION = @"app_version";
static NSString *const APP_BUILD = @"app_build";


@implementation Amplitude {
Expand Down Expand Up @@ -240,6 +244,8 @@ - (instancetype)initWithInstanceName:(NSString *)instanceName {
[[[AnalyticsConnector getInstance:self.instanceName] eventBridge] setEventReceiver:^(AnalyticsEvent * _Nonnull event) {
[self logEvent:[event eventType] withEventProperties:[event eventProperties] withApiProperties:nil withUserProperties:[event userProperties] withGroups:nil withGroupProperties:nil withTimestamp:nil outOfSession:false];
}];

self.defaultTracking = [[AMPDefaultTrackingOptions alloc] init];

_initializerQueue = [[NSOperationQueue alloc] init];
_backgroundQueue = [[NSOperationQueue alloc] init];
Expand Down Expand Up @@ -403,20 +409,75 @@ - (void)addObservers {
name:NSApplicationDidResignActiveNotification
object:nil];
#endif

#if !TARGET_OS_OSX && !TARGET_OS_WATCH
// mount the default events handler
UIApplication *app = [AMPUtils getSharedApplication];
if (app) {
for (NSString *name in @[UIApplicationDidEnterBackgroundNotification,
UIApplicationDidFinishLaunchingNotification,
UIApplicationWillEnterForegroundNotification]) {
[center addObserver:self selector:@selector(handleAppStateUpdates:) name:name object:app];
}
}
#endif
}

#if !TARGET_OS_OSX && !TARGET_OS_WATCH
- (void)handleAppStateUpdates:(NSNotification *)notification {
// lazy checking the settings here to avoid early init with false value
if (!self.defaultTracking.appLifecycles) {
return;
}
if ([notification.name isEqualToString:UIApplicationDidFinishLaunchingNotification]) {
NSString *previousBuild = [_dbHelper getValue:APP_BUILD];
NSString *previousVersion = [_dbHelper getValue:APP_VERSION];
NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
if (!previousBuild) {
[self logEvent:kAMPApplicationInstalled withEventProperties:@{
kAMPEventPropBuild: currentBuild ?: @"",
kAMPEventPropVersion: currentVersion ?: @"",
}];
} else if (![currentBuild isEqualToString:previousBuild]) {
[self logEvent:kAMPApplicationUpdated withEventProperties:@{
kAMPEventPropBuild: currentBuild ?: @"",
kAMPEventPropVersion: currentVersion ?: @"",
kAMPEventPropPreviousBuild: previousBuild ?: @"",
kAMPEventPropPreviousVersion: previousVersion ?: @"",
}];
}
[self logEvent:kAMPApplicationOpened withEventProperties:@{
kAMPEventPropBuild: currentBuild ?: @"",
kAMPEventPropVersion: currentVersion ?: @"",
kAMPEventPropFromBackground: @NO,
}];

// persist the build/version when changed
if (currentBuild ? ![currentBuild isEqualToString:previousBuild] : (previousBuild != nil)) {
[_dbHelper insertOrReplaceKeyValue:APP_BUILD value:currentBuild];
}
if (currentVersion ? ![currentVersion isEqualToString:previousVersion] : (previousVersion != nil)) {
[_dbHelper insertOrReplaceKeyValue:APP_VERSION value:currentVersion];
}
} else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) {
NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
[self logEvent:kAMPApplicationOpened withEventProperties:@{
kAMPEventPropBuild: currentBuild ?: @"",
kAMPEventPropVersion: currentVersion ?: @"",
kAMPEventPropFromBackground: @YES,
}];
} else if ([notification.name isEqualToString:UIApplicationDidEnterBackgroundNotification]) {
[self logEvent:kAMPApplicationBackgrounded];
}
}
#endif

- (void)removeObservers {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
#if TARGET_OS_WATCH
[center removeObserver:self name:AMPAppWillEnterForegroundNotification object:nil];
[center removeObserver:self name:AMPAppDidEnterBackgroundNotification object:nil];
#elif !TARGET_OS_OSX
[center removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
[center removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
#else
[center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil];
[center removeObserver:self name:NSApplicationDidResignActiveNotification object:nil];
#endif
// unregister all observers added by addObservers method
[center removeObserver:self];
}

- (void)dealloc {
Expand Down Expand Up @@ -477,6 +538,13 @@ - (void)initializeApiKey:(NSString *)apiKey
if (self.initCompletionBlock != nil) {
self.initCompletionBlock();
}

#if !TARGET_OS_OSX && !TARGET_OS_WATCH
// Unlike other default events options that can be evaluated later, screenViews has to be evaluated during the actual initialization
if (self.defaultTracking.screenViews) {
[UIViewController amp_swizzleViewDidAppear];
}
#endif
}];

if (!self.deferCheckInForeground) {
Expand Down Expand Up @@ -622,7 +690,7 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event
}

// skip session check if logging start_session or end_session events
BOOL loggingSessionEvent = self->_trackingSessionEvents && ([eventType isEqualToString:kAMPSessionStartEvent] || [eventType isEqualToString:kAMPSessionEndEvent]);
BOOL loggingSessionEvent = (self->_trackingSessionEvents || self.defaultTracking.sessions) && ([eventType isEqualToString:kAMPSessionStartEvent] || [eventType isEqualToString:kAMPSessionEndEvent]);
if (!loggingSessionEvent && !outOfSession) {
[self startOrContinueSessionNSNumber:timestamp inForeground:inForeground];
}
Expand Down Expand Up @@ -833,6 +901,36 @@ - (void)logRevenueV2:(AMPRevenue *)revenue {
[self logEvent:kAMPRevenueEvent withEventProperties:[revenue toNSDictionary]];
}

#pragma mark - Deep link methods
- (void)continueUserActivity:(NSUserActivity *)activity {
if (!self.defaultTracking.deepLinks) {
return;
}

if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
NSString *urlString = activity.webpageURL.absoluteString;
NSString *referrerString = nil;
if (@available(iOS 11, tvOS 11.0, macOS 10.13, watchOS 4.0, *)) {
referrerString = activity.referrerURL.absoluteString;
}
[self logEvent:kAMPDeepLinkOpened withEventProperties:@{
kAMPEventPropLinkUrl: urlString ?: @"",
kAMPEventPropLinkReferrer: referrerString ?: @"",
}];
}
}

- (void)openURL:(NSURL *)url {
if (!self.defaultTracking.deepLinks) {
return;
}

NSString *urlString = url.absoluteString;
[self logEvent:kAMPDeepLinkOpened withEventProperties:@{
kAMPEventPropLinkUrl: urlString ?: @"",
}];
}

#pragma mark - Upload events

- (void)uploadEventsWithDelay:(int)delay {
Expand Down Expand Up @@ -1231,12 +1329,13 @@ - (BOOL)startOrContinueSession:(long long)timestamp {
}

- (void)startNewSession:(NSNumber *)timestamp {
if (_trackingSessionEvents) {
BOOL loggingSessionEvent = _trackingSessionEvents || self.defaultTracking.sessions;
if (loggingSessionEvent) {
[self sendSessionEvent:kAMPSessionEndEvent];
}
[self setSessionId:[timestamp longLongValue]];
[self refreshSessionTime:timestamp];
if (_trackingSessionEvents) {
if (loggingSessionEvent) {
[self sendSessionEvent:kAMPSessionStartEvent];
}
}
Expand Down Expand Up @@ -1426,7 +1525,8 @@ - (void)setUserId:(NSString *)userId startNewSession:(BOOL)startNewSession {
}

[self runOnBackgroundQueue:^{
if (startNewSession && self->_trackingSessionEvents) {
BOOL loggingSessionEvent = self->_trackingSessionEvents || self.defaultTracking.sessions;
if (startNewSession && loggingSessionEvent) {
[self sendSessionEvent:kAMPSessionEndEvent];
}

Expand All @@ -1442,7 +1542,7 @@ - (void)setUserId:(NSString *)userId startNewSession:(BOOL)startNewSession {
NSNumber *timestamp = [NSNumber numberWithLongLong:[[self currentTime] timeIntervalSince1970] * 1000];
[self setSessionId:[timestamp longLongValue]];
[self refreshSessionTime:timestamp];
if (self->_trackingSessionEvents) {
if (loggingSessionEvent) {
[self sendSessionEvent:kAMPSessionStartEvent];
}
}
Expand Down
Loading

0 comments on commit 9bf9664

Please sign in to comment.