Skip to content

Commit 76eab5e

Browse files
authored
Check Registration (#397)
* Check Registration • In rare cases, if APNS does not respond with a push token or an error, our SDK was not correctly registering the user regardless. • This commit adds an additional check to make sure that the user get registered and gets a OneSignal user ID. * Remove Delay Selector • Removed the 'checkRegistrationStatus' method from the previous commit as it is unnecessary • Instead, this PR now uses an NSDate instance that gets instantiated when the SDK is initialized, and uses it to ensure that if APNS does not respond within 25 seconds, it will register with OS anyways. • This reduces network traffic to onesignal servers. * Add Test Case • Adds an integration test to make sure that the SDK will register with OneSignal if APNS doesn't respond within a set amount of time • Fixes a typo (pendingRegiseterBlock in UIApplicationOverrider)
1 parent 5fe84b3 commit 76eab5e

File tree

7 files changed

+120
-32
lines changed

7 files changed

+120
-32
lines changed

iOS_SDK/OneSignalSDK/Source/OneSignal.m

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,6 @@
6969
#import "OneSignalCommonDefines.h"
7070
#import "DelayedInitializationParameters.h"
7171

72-
#define NOTIFICATION_TYPE_NONE 0
73-
#define NOTIFICATION_TYPE_BADGE 1
74-
#define NOTIFICATION_TYPE_SOUND 2
75-
#define NOTIFICATION_TYPE_ALERT 4
76-
#define NOTIFICATION_TYPE_ALL 7
77-
78-
#define ERROR_PUSH_CAPABLILITY_DISABLED -13
79-
#define ERROR_PUSH_DELEGATE_NEVER_FIRED -14
80-
#define ERROR_PUSH_SIMULATOR_NOT_SUPPORTED -15
81-
#define ERROR_PUSH_UNKNOWN_APNS_ERROR -16
82-
#define ERROR_PUSH_OTHER_3000_ERROR -17
83-
#define ERROR_PUSH_NEVER_PROMPTED -18
84-
#define ERROR_PUSH_PROMPT_NEVER_ANSWERED -19
85-
8672
#pragma clang diagnostic push
8773
#pragma clang diagnostic ignored "-Wundeclared-selector"
8874

@@ -169,6 +155,11 @@ @implementation OneSignal
169155
static BOOL delayedInitializationForPrivacyConsent = false;
170156
DelayedInitializationParameters *delayedInitParameters;
171157

158+
//used to ensure registration occurs even if APNS does not respond
159+
static NSDate *initializationTime;
160+
static NSTimeInterval maxApnsWait = APNS_TIMEOUT;
161+
static NSTimeInterval reattemptRegistrationInterval = REGISTRATION_DELAY_SECONDS;
162+
172163
//the iOS Native SDK will use the plist flag to enable privacy consent
173164
//however wrapper SDK's will use a method call before initialization
174165
//this boolean flag is switched on to enable this behavior
@@ -351,6 +342,14 @@ + (void) setWaitingForApnsResponse:(BOOL)value {
351342
waitingForApnsResponse = value;
352343
}
353344

345+
// Used for testing purposes to decrease the amount of time the
346+
// SDK will spend waiting for a response from APNS before it
347+
// gives up and registers with OneSignal anyways
348+
+ (void)setDelayIntervals:(NSTimeInterval)apnsMaxWait withRegistrationDelay:(NSTimeInterval)registrationDelay {
349+
reattemptRegistrationInterval = registrationDelay;
350+
maxApnsWait = apnsMaxWait;
351+
}
352+
354353
+ (void)clearStatics {
355354
app_id = nil;
356355
_osNotificationSettings = nil;
@@ -368,6 +367,9 @@ + (void)clearStatics {
368367
_permissionStateChangesObserver = nil;
369368

370369
didCallDownloadParameters = false;
370+
371+
maxApnsWait = APNS_TIMEOUT;
372+
reattemptRegistrationInterval = REGISTRATION_DELAY_SECONDS;
371373
}
372374

373375
// Set to false as soon as it's read.
@@ -398,6 +400,8 @@ + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId
398400
+ (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId handleNotificationReceived:(OSHandleNotificationReceivedBlock)receivedCallback handleNotificationAction:(OSHandleNotificationActionBlock)actionCallback settings:(NSDictionary*)settings {
399401
NSLog(@"Called init with app ID: %@", appId);
400402

403+
initializationTime = [NSDate date];
404+
401405
//Some wrapper SDK's call init multiple times and pass nil/NSNull as the appId on the first call
402406
//the app ID is required to download parameters, so do not download params until the appID is provided
403407
if (!didCallDownloadParameters && appId != nil && appId != (id)[NSNull null])
@@ -414,7 +418,7 @@ + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId
414418

415419
let success = [self initAppId:appId
416420
withUserDefaults:userDefaults
417-
withSettings:settings];
421+
withSettings:settings];
418422

419423
if (!success)
420424
return self;
@@ -476,14 +480,17 @@ + (id)initWithLaunchOptions:(NSDictionary*)launchOptions appId:(NSString*)appId
476480
self.inFocusDisplayType = (OSNotificationDisplayType)IFDSetting.integerValue;
477481
}
478482

483+
479484
if (self.currentSubscriptionState.userId)
480485
[self registerUser];
481486
else {
482487
[self.osNotificationSettings getNotificationPermissionState:^(OSPermissionState *state) {
483-
if (state.answeredPrompt)
488+
489+
if (state.answeredPrompt) {
484490
[self registerUser];
485-
else
486-
[self performSelector:@selector(registerUser) withObject:nil afterDelay:30.0f];
491+
} else {
492+
[self registerUserAfterDelay];
493+
}
487494
}];
488495
}
489496
}
@@ -1225,9 +1232,10 @@ + (void)updateDeviceToken:(NSString*)deviceToken onSuccess:(OSResultSuccessBlock
12251232
// The goal is to only have 1 server call.
12261233
[self.osNotificationSettings getNotificationPermissionState:^(OSPermissionState *status) {
12271234
if (status.answeredPrompt)
1228-
[OneSignal registerUser];
1229-
else
1235+
[self registerUser];
1236+
else {
12301237
[self registerUserAfterDelay];
1238+
}
12311239
}];
12321240
return;
12331241
}
@@ -1297,7 +1305,7 @@ +(BOOL)shouldRegisterNow {
12971305

12981306
+ (void)registerUserAfterDelay {
12991307
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(registerUser) object:nil];
1300-
[OneSignalHelper performSelector:@selector(registerUser) onMainThreadOnObject:self withObject:nil afterDelay:30.0f];
1308+
[OneSignalHelper performSelector:@selector(registerUser) onMainThreadOnObject:self withObject:nil afterDelay:reattemptRegistrationInterval];
13011309
}
13021310

13031311
static dispatch_queue_t serialQueue;
@@ -1307,12 +1315,14 @@ + (dispatch_queue_t) getRegisterQueue {
13071315
}
13081316

13091317
+ (void)registerUser {
1310-
13111318
// return if the user has not granted privacy permissions
13121319
if ([self shouldLogMissingPrivacyConsentErrorWithMethodName:nil])
13131320
return;
13141321

1315-
if (waitingForApnsResponse) {
1322+
// We should delay registration if we are waiting on APNS
1323+
// But if APNS hasn't responded within 30 seconds,
1324+
// we should continue and register the user.
1325+
if (waitingForApnsResponse && initializationTime && [[NSDate date] timeIntervalSinceDate:initializationTime] < maxApnsWait) {
13161326
[self registerUserAfterDelay];
13171327
return;
13181328
}

iOS_SDK/OneSignalSDK/Source/OneSignalCommonDefines.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,26 @@
6464

6565
#define ONESIGNAL_SUPPORTED_ATTACHMENT_TYPES @[@"aiff", @"wav", @"mp3", @"mp4", @"jpg", @"jpeg", @"png", @"gif", @"mpeg", @"mpg", @"avi", @"m4a", @"m4v"]
6666

67+
// Notification types
68+
#define NOTIFICATION_TYPE_NONE 0
69+
#define NOTIFICATION_TYPE_BADGE 1
70+
#define NOTIFICATION_TYPE_SOUND 2
71+
#define NOTIFICATION_TYPE_ALERT 4
72+
#define NOTIFICATION_TYPE_ALL 7
73+
74+
#define ERROR_PUSH_CAPABLILITY_DISABLED -13
75+
#define ERROR_PUSH_DELEGATE_NEVER_FIRED -14
76+
#define ERROR_PUSH_SIMULATOR_NOT_SUPPORTED -15
77+
#define ERROR_PUSH_UNKNOWN_APNS_ERROR -16
78+
#define ERROR_PUSH_OTHER_3000_ERROR -17
79+
#define ERROR_PUSH_NEVER_PROMPTED -18
80+
#define ERROR_PUSH_PROMPT_NEVER_ANSWERED -19
81+
82+
// Registration delay
83+
#define REGISTRATION_DELAY_SECONDS 30.0
84+
85+
// How long the SDK will wait for APNS to respond
86+
// before registering the user anyways
87+
#define APNS_TIMEOUT 25.0
88+
6789
#endif /* OneSignalCommonDefines_h */

iOS_SDK/OneSignalSDK/UnitTests/Shadows/UIApplicationOverrider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
@interface UIApplicationOverrider : NSObject
3232
+(void)reset;
3333

34+
+(void)setBlockApnsResponse:(BOOL)block;
35+
3436
+(void)setCurrentUIApplicationState:(UIApplicationState)value;
3537

3638
+(UILocalNotification*)lastUILocalNotification;

iOS_SDK/OneSignalSDK/UnitTests/Shadows/UIApplicationOverrider.m

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ @implementation UIApplicationOverrider
4545

4646
static UIUserNotificationSettings* lastUIUserNotificationSettings;
4747

48-
static BOOL pendingRegiseterBlock;
48+
static BOOL pendingRegisterBlock;
49+
50+
//mimics no response from APNS
51+
static BOOL blockApnsResponse;
4952

5053
+ (void)load {
5154
injectToProperClass(@selector(overrideRegisterForRemoteNotifications), @selector(registerForRemoteNotifications), @[], [UIApplicationOverrider class], [UIApplication class]);
@@ -58,8 +61,9 @@ + (void)load {
5861
}
5962

6063
+(void)reset {
64+
blockApnsResponse = false;
6165
lastUILocalNotification = nil;
62-
pendingRegiseterBlock = false;
66+
pendingRegisterBlock = false;
6367
shouldFireDeviceToken = true;
6468
calledRegisterForRemoteNotifications = false;
6569
calledCurrentUserNotificationSettings = false;
@@ -87,6 +91,10 @@ +(void) setDidFailRegistarationErrorCode:(NSInteger)value {
8791
didFailRegistarationErrorCode = value;
8892
}
8993

94+
+(void)setBlockApnsResponse:(BOOL)block {
95+
blockApnsResponse = true;
96+
}
97+
9098
// Keeps UIApplicationMain(...) from looping to continue to the next line.
9199
- (void) override_run {
92100
NSLog(@"override_run!!!!!!");
@@ -106,15 +114,15 @@ + (void)helperCallDidRegisterForRemoteNotificationsWithDeviceToken {
106114
if (!shouldFireDeviceToken)
107115
return;
108116

109-
pendingRegiseterBlock = true;
117+
pendingRegisterBlock = true;
110118
});
111119
}
112120

113121
// callPendingApplicationDidRegisterForRemoteNotificaitonsWithDeviceToken
114122
+ (void)runBackgroundThreads {
115-
if (!pendingRegiseterBlock || currentUIApplicationState != UIApplicationStateActive)
123+
if (!pendingRegisterBlock || currentUIApplicationState != UIApplicationStateActive || blockApnsResponse)
116124
return;
117-
pendingRegiseterBlock = false;
125+
pendingRegisterBlock = false;
118126

119127
id app = [UIApplication sharedApplication];
120128
id appDelegate = [[UIApplication sharedApplication] delegate];

iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ NSString * serverUrlWithPath(NSString *path);
4343

4444
@end
4545

46+
// Expose OneSignal test methods
47+
@interface OneSignal (UN_extra)
48+
+ (dispatch_queue_t) getRegisterQueue;
49+
+ (void)setDelayIntervals:(NSTimeInterval)apnsMaxWait withRegistrationDelay:(NSTimeInterval)registrationDelay;
50+
@end
51+
4652
// START - Start Observers
4753

4854
@interface OSPermissionStateTestObserver : NSObject<OSPermissionObserver> {

iOS_SDK/OneSignalSDK/UnitTests/UnitTestCommonMethods.m

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@
4848
return [NSString stringWithFormat:@"%@%@%@", SERVER_URL, API_VERSION, path];
4949
}
5050

51-
@interface OneSignal (UN_extra)
52-
+ (dispatch_queue_t) getRegisterQueue;
53-
@end
54-
5551
@implementation UnitTestCommonMethods
5652

5753
// Runs any blocks passed to dispatch_async()

iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2007,4 +2007,48 @@ - (void)testFileExtensionPrioritizesURLFileExtension {
20072007
XCTAssertTrue([downloadedGifFilename.supportedFileExtension isEqualToString:@"gif"]);
20082008
}
20092009

2010+
//integration test that makes sure that if APNS doesn't respond within a certain
2011+
//window of time, the SDK will register the user with onesignal anyways.
2012+
- (void)testRegistersAfterNoApnsResponse {
2013+
2014+
// simulates no response from APNS
2015+
[UIApplicationOverrider setBlockApnsResponse:true];
2016+
2017+
// Normally the SDK would wait at least 25 seconds to get a response
2018+
// and 30 seconds between registration attempts.
2019+
// This would be too long for a test, so we artificially set the
2020+
// delay times to be very very short.
2021+
[OneSignal setDelayIntervals:0.001f withRegistrationDelay:0.002f];
2022+
2023+
// add the subscription observer
2024+
OSSubscriptionStateTestObserver* observer = [OSSubscriptionStateTestObserver new];
2025+
[OneSignal addSubscriptionObserver:observer];
2026+
2027+
// create an expectation
2028+
XCTestExpectation *expectation = [self expectationWithDescription:@"onesignal_registration_wait"];
2029+
expectation.expectedFulfillmentCount = 1;
2030+
2031+
// do not answer the prompt (apns will not respond)
2032+
[UnitTestCommonMethods setCurrentNotificationPermissionAsUnanswered];
2033+
2034+
[OneSignal initWithLaunchOptions:nil
2035+
appId:@"b2f7f966-d8cc-11e4-bed1-df8f05be55ba"
2036+
handleNotificationAction:nil
2037+
settings:@{kOSSettingsKeyAutoPrompt: @false}];
2038+
2039+
[UnitTestCommonMethods runBackgroundThreads];
2040+
2041+
// wait for the registration to be re-attempted.
2042+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2043+
[expectation fulfill];
2044+
});
2045+
2046+
[self waitForExpectations:@[expectation] timeout:0.1];
2047+
2048+
// If APNS didn't respond within X seconds, the SDK
2049+
// should have registered the user with OneSignal
2050+
// and should have a user ID
2051+
XCTAssertTrue(observer->last.to.userId != nil);
2052+
}
2053+
20102054
@end

0 commit comments

Comments
 (0)