From d303b715b191d5e5b71cef9e83a816f35e80c81f Mon Sep 17 00:00:00 2001 From: emawby Date: Mon, 15 Nov 2021 11:09:33 -0800 Subject: [PATCH 1/6] Adding additionalHeaders property to OneSignalRequest This property contains key value pairs for setting headers on a the urlRequest. --- iOS_SDK/OneSignalSDK/Source/OneSignalRequest.h | 1 + iOS_SDK/OneSignalSDK/Source/OneSignalRequest.m | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.h b/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.h index 1fb0aa25f..ec949f5a9 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.h +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.h @@ -38,6 +38,7 @@ @property (nonatomic) HTTPMethod method; @property (strong, nonatomic, nonnull) NSString *path; @property (strong, nonatomic, nullable) NSDictionary *parameters; +@property (strong, nonatomic, nullable) NSDictionary *additionalHeaders; @property (nonatomic) int reattemptCount; @property (nonatomic) BOOL dataRequest; //false for JSON based requests -(BOOL)missingAppId; //for requests that don't require an appId parameter, the subclass should override this method and return false diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.m b/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.m index c8c385e06..a627de130 100644 --- a/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignalRequest.m @@ -60,6 +60,10 @@ -(NSMutableURLRequest *)urlRequest { let request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]]; + for (NSString *key in self.additionalHeaders) { + [request setValue:self.additionalHeaders[key] forHTTPHeaderField:key]; + } + if (!self.dataRequest) [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; From e9c67df3684e5d41682dc36fce161929b418a91e Mon Sep 17 00:00:00 2001 From: emawby Date: Mon, 15 Nov 2021 11:10:07 -0800 Subject: [PATCH 2/6] Creating trackUsageData request This request requires that we set the usageData and appId as a header instead of body parameters --- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 6 ++++++ iOS_SDK/OneSignalSDK/Source/Requests.h | 4 ++++ iOS_SDK/OneSignalSDK/Source/Requests.m | 17 +++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 929bc10ae..48912cc22 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -1709,6 +1709,12 @@ + (BOOL)shouldRegisterNow { // Make sure last time we closed app was more than 30 secs ago const int minTimeThreshold = 30; NSTimeInterval delta = now - lastTimeClosed; + if (delta < minTimeThreshold && appId) { + // https://api.onesignal.com/api/v1/track + // do track here + // OS-Usage-Data + NSLog(@"ECM tracking restart appId: %@", appId); + } return delta >= minTimeThreshold; } diff --git a/iOS_SDK/OneSignalSDK/Source/Requests.h b/iOS_SDK/OneSignalSDK/Source/Requests.h index dcfc9d62d..b14d288b6 100644 --- a/iOS_SDK/OneSignalSDK/Source/Requests.h +++ b/iOS_SDK/OneSignalSDK/Source/Requests.h @@ -203,5 +203,9 @@ NS_ASSUME_NONNULL_END + (instancetype _Nonnull)withPlayerId:(NSString * _Nullable)playerId notificationId:(NSString * _Nonnull)notificationId appId:(NSString * _Nonnull)appId; @end +@interface OSRequestTrackV1 : OneSignalRequest ++ (instancetype _Nonnull)trackUsageData:(NSString * _Nonnull)osUsageData + appId:(NSString * _Nonnull)appId; +@end #endif /* Requests_h */ diff --git a/iOS_SDK/OneSignalSDK/Source/Requests.m b/iOS_SDK/OneSignalSDK/Source/Requests.m index 9e6f1df68..1aa4717df 100644 --- a/iOS_SDK/OneSignalSDK/Source/Requests.m +++ b/iOS_SDK/OneSignalSDK/Source/Requests.m @@ -749,3 +749,20 @@ + (instancetype)measureOutcomeEvent:(OSOutcomeEventParams *)outcome appId:(NSStr return request; } @end + +@implementation OSRequestTrackV1 +NSString * const OS_USAGE_DATA = @"OS-Usage-Data"; ++ (instancetype)trackUsageData:(NSString *)osUsageData appId:(NSString *)appId { + let request = [OSRequestTrackV1 new]; + let params = [NSMutableDictionary new]; + let headers = [NSMutableDictionary new]; + params[APP_ID] = appId; + headers[APP_ID] = appId; + headers[OS_USAGE_DATA] = osUsageData; + request.method = POST; + request.path = @"v1/track"; + request.parameters = params; + request.additionalHeaders = headers; + return request; +} +@end From 3138efe1d228c7a234b3d447693a6f99013c0752 Mon Sep 17 00:00:00 2001 From: emawby Date: Mon, 15 Nov 2021 11:31:20 -0800 Subject: [PATCH 3/6] Tracking cold restarts A cold restart is when an app is closed (swipe away) and relaunched within 30 seconds. In this scenario we currently won't call onSession. We want to track how often this occurs to know the impact of calling onSession in this scenario. We don't want to consider backgrounding in this case so we are using the registerUserFinished boolean to remove cases where we have already registered a session. We also only want to track this once per app launch so the trackedColdRestart boolean prevents it from being called multiple times. --- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 48912cc22..30c24c9ae 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -1678,6 +1678,7 @@ + (BOOL)isRegisterUserSuccessful { return _registerUserSuccessful || isOnSessionSuccessfulForCurrentState; } +static BOOL _trackedColdRestart = false; + (BOOL)shouldRegisterNow { // return if the user has not granted privacy permissions if ([self shouldLogMissingPrivacyConsentErrorWithMethodName:nil]) @@ -1709,11 +1710,14 @@ + (BOOL)shouldRegisterNow { // Make sure last time we closed app was more than 30 secs ago const int minTimeThreshold = 30; NSTimeInterval delta = now - lastTimeClosed; - if (delta < minTimeThreshold && appId) { - // https://api.onesignal.com/api/v1/track - // do track here - // OS-Usage-Data - NSLog(@"ECM tracking restart appId: %@", appId); + if (delta < minTimeThreshold && appId && !_registerUserFinished && !_trackedColdRestart) { + NSString *osUsageData = [NSString stringWithFormat:@"lib-name=OneSignal-iOS-SDK,lib-version=%@,lib-event=cold_restart", ONESIGNAL_VERSION]; + _trackedColdRestart = true; + [[OneSignalClient sharedClient] executeRequest:[OSRequestTrackV1 trackUsageData:osUsageData appId:appId] onSuccess:^(NSDictionary *result) { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:trackColdRestart: successfully tracked cold restart"]; + } onFailure:^(NSError *error) { + [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"shouldRegisterNow:trackColdRestart: Failed to track cold restart: %@", error.localizedDescription]]; + }]; } return delta >= minTimeThreshold; } From 20902ee2bc12f593f2690d78dd6dc74b26a563f5 Mon Sep 17 00:00:00 2001 From: emawby Date: Mon, 15 Nov 2021 11:50:50 -0800 Subject: [PATCH 4/6] unit tests for addtionalHeaders field and track request --- iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m b/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m index 0cae99073..c57208794 100644 --- a/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m +++ b/iOS_SDK/OneSignalSDK/UnitTests/RequestTests.m @@ -167,6 +167,10 @@ BOOL checkHttpBody(NSData *bodyData, NSDictionary *correct) { return dictionariesAreEquivalent(serialized, correct); } +BOOL checkHttpHeaders(NSDictionary *additionalHeaders, NSDictionary *correct) { + return dictionariesAreEquivalent(additionalHeaders, correct); +} + - (void)testBuildGetTags { let request = [OSRequestGetTags withUserId:testUserId appId:testAppId]; @@ -743,4 +747,38 @@ - (void)testSendExternalWithAuthUserId { XCTAssertTrue(checkHttpBody(request.urlRequest.HTTPBody, @{@"app_id" : testAppId, @"external_user_id" : testExternalUserId, @"external_user_id_auth_hash" : testExternalUserIdHashToken})); } +- (void)testSendTrackUsageRequest { + NSString *testUsageData = @"test usage data"; + let request = [OSRequestTrackV1 trackUsageData:testUsageData appId:testAppId]; + let correctUrl = correctUrlWithPath(@"v1/track"); + + XCTAssertTrue([correctUrl isEqualToString:request.urlRequest.URL.absoluteString]); + XCTAssertTrue(checkHttpBody(request.urlRequest.HTTPBody, @{@"app_id" : testAppId})); + XCTAssertTrue(checkHttpHeaders(request.additionalHeaders, @{@"app_id" : testAppId, + @"OS-Usage-Data" : testUsageData, + })); +} + +- (void)testAdditionalHeaders { + // Create a fake request + let request = [OneSignalRequest new]; + let params = [NSMutableDictionary new]; + let headers = [NSMutableDictionary new]; + params[@"app_id"] = testAppId; + headers[@"app_id"] = testAppId; + headers[@"test-header"] = @"test_header_value"; + request.method = POST; + request.path = @"test/path"; + request.parameters = params; + request.additionalHeaders = headers; + + // Properties must be set in the request before accessing urlRequest + let urlRequest = request.urlRequest; + let requestHeaders = urlRequest.allHTTPHeaderFields; + // Verify that all headers we added via additionalHeaders are in the request's header fields + for (NSString *key in headers) { + XCTAssertTrue(requestHeaders[key] != nil); + } +} + @end From f5c58fbd346396e3aaabe0215b5b49d0f34788f0 Mon Sep 17 00:00:00 2001 From: emawby Date: Mon, 15 Nov 2021 12:11:13 -0800 Subject: [PATCH 5/6] only track 1 out of 100 cold restarts --- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 30c24c9ae..a39a63f38 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -1710,14 +1710,24 @@ + (BOOL)shouldRegisterNow { // Make sure last time we closed app was more than 30 secs ago const int minTimeThreshold = 30; NSTimeInterval delta = now - lastTimeClosed; + + // Tracking cold starts within 30 seconds of last close. + // Depending on the results of our tracking we will change this case + // from a tracking request to return true if (delta < minTimeThreshold && appId && !_registerUserFinished && !_trackedColdRestart) { - NSString *osUsageData = [NSString stringWithFormat:@"lib-name=OneSignal-iOS-SDK,lib-version=%@,lib-event=cold_restart", ONESIGNAL_VERSION]; + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:coldRestart"]; + // Set to true even if it doesn't pass the sample check _trackedColdRestart = true; - [[OneSignalClient sharedClient] executeRequest:[OSRequestTrackV1 trackUsageData:osUsageData appId:appId] onSuccess:^(NSDictionary *result) { - [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:trackColdRestart: successfully tracked cold restart"]; - } onFailure:^(NSError *error) { - [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"shouldRegisterNow:trackColdRestart: Failed to track cold restart: %@", error.localizedDescription]]; - }]; + // Sample /track calls to avoid hitting our endpoint too hard + int randomSample = arc4random_uniform(100); + if (randomSample == 99) { + NSString *osUsageData = [NSString stringWithFormat:@"lib-name=OneSignal-iOS-SDK,lib-version=%@,lib-event=cold_restart", ONESIGNAL_VERSION]; + [[OneSignalClient sharedClient] executeRequest:[OSRequestTrackV1 trackUsageData:osUsageData appId:appId] onSuccess:^(NSDictionary *result) { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:trackColdRestart: successfully tracked cold restart"]; + } onFailure:^(NSError *error) { + [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"shouldRegisterNow:trackColdRestart: Failed to track cold restart: %@", error.localizedDescription]]; + }]; + } } return delta >= minTimeThreshold; } From 6f5f9455828278223d4b616d6cea725a6cd0147b Mon Sep 17 00:00:00 2001 From: emawby Date: Thu, 18 Nov 2021 12:29:36 -0800 Subject: [PATCH 6/6] move trackColdRestart to its own method and update usage string --- iOS_SDK/OneSignalSDK/Source/OneSignal.m | 30 ++++++++++++++----------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index a39a63f38..b8b1d1233 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -1715,23 +1715,27 @@ + (BOOL)shouldRegisterNow { // Depending on the results of our tracking we will change this case // from a tracking request to return true if (delta < minTimeThreshold && appId && !_registerUserFinished && !_trackedColdRestart) { - [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:coldRestart"]; - // Set to true even if it doesn't pass the sample check - _trackedColdRestart = true; - // Sample /track calls to avoid hitting our endpoint too hard - int randomSample = arc4random_uniform(100); - if (randomSample == 99) { - NSString *osUsageData = [NSString stringWithFormat:@"lib-name=OneSignal-iOS-SDK,lib-version=%@,lib-event=cold_restart", ONESIGNAL_VERSION]; - [[OneSignalClient sharedClient] executeRequest:[OSRequestTrackV1 trackUsageData:osUsageData appId:appId] onSuccess:^(NSDictionary *result) { - [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"shouldRegisterNow:trackColdRestart: successfully tracked cold restart"]; - } onFailure:^(NSError *error) { - [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"shouldRegisterNow:trackColdRestart: Failed to track cold restart: %@", error.localizedDescription]]; - }]; - } + [OneSignal trackColdRestart]; } return delta >= minTimeThreshold; } ++ (void)trackColdRestart { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"trackColdRestart"]; + // Set to true even if it doesn't pass the sample check + _trackedColdRestart = true; + // Sample /track calls to avoid hitting our endpoint too hard + int randomSample = arc4random_uniform(100); + if (randomSample == 99) { + NSString *osUsageData = [NSString stringWithFormat:@"kind=sdk, version=%@, source=iOS_SDK, name=cold_restart, lockScreenApp=false", ONESIGNAL_VERSION]; + [[OneSignalClient sharedClient] executeRequest:[OSRequestTrackV1 trackUsageData:osUsageData appId:appId] onSuccess:^(NSDictionary *result) { + [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"trackColdRestart: successfully tracked cold restart"]; + } onFailure:^(NSError *error) { + [OneSignal onesignal_Log:ONE_S_LL_ERROR message:[NSString stringWithFormat:@"trackColdRestart: Failed to track cold restart: %@", error.localizedDescription]]; + }]; + } +} + + (void)registerUserAfterDelay { [OneSignal onesignal_Log:ONE_S_LL_VERBOSE message:@"registerUserAfterDelay"]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(registerUser) object:nil];