Skip to content

Commit 1e9d652

Browse files
authored
Swizzles APNs token error app delegate method for faster turnaround. (#226)
Also removes the server request in case the token is missing.
1 parent 36c20d9 commit 1e9d652

12 files changed

+201
-23
lines changed

Example/Auth/Tests/FIRAuthAPNSTokenManagerTests.m

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ @implementation FIRAuthAPNSTokenManagerTests {
8080
@brief Another piece of data used for testing.
8181
*/
8282
NSData *_otherData;
83+
84+
/** @var _error
85+
@brief The fake error used for testing.
86+
*/
87+
NSError *_error;
8388
}
8489

8590
- (void)setUp {
@@ -118,18 +123,20 @@ - (void)testCallback {
118123
// Add first callback, which is yet to be called.
119124
OCMExpect([_mockApplication registerForRemoteNotifications]);
120125
__block BOOL firstCallbackCalled = NO;
121-
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
126+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
122127
XCTAssertEqualObjects(token.data, _data);
123128
XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
129+
XCTAssertNil(error);
124130
firstCallbackCalled = YES;
125131
}];
126132
XCTAssertFalse(firstCallbackCalled);
127133

128134
// Add second callback, which is yet to be called either.
129135
__block BOOL secondCallbackCalled = NO;
130-
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
136+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
131137
XCTAssertEqualObjects(token.data, _data);
132138
XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
139+
XCTAssertNil(error);
133140
secondCallbackCalled = YES;
134141
}];
135142
XCTAssertFalse(secondCallbackCalled);
@@ -149,9 +156,10 @@ - (void)testCallback {
149156

150157
// Add third callback, which should be called back immediately.
151158
__block BOOL thirdCallbackCalled = NO;
152-
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
159+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
153160
XCTAssertEqualObjects(token.data, _data);
154161
XCTAssertEqual(token.type, FIRAuthAPNSTokenTypeSandbox);
162+
XCTAssertNil(error);
155163
thirdCallbackCalled = YES;
156164
}];
157165
XCTAssertTrue(thirdCallbackCalled);
@@ -176,8 +184,48 @@ - (void)testTimeout {
176184
// Add callback to time out.
177185
OCMExpect([_mockApplication registerForRemoteNotifications]);
178186
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
179-
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
187+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
188+
XCTAssertNil(token);
189+
XCTAssertNil(error);
190+
[expectation fulfill];
191+
}];
192+
193+
// Time out.
194+
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
195+
OCMVerifyAll(_mockApplication);
196+
197+
// Calling cancel afterwards should have no effect.
198+
[_manager cancelWithError:_error];
199+
}
200+
201+
/** @fn testCancel
202+
@brief Tests cancelling the pending callbacks.
203+
*/
204+
- (void)testCancel {
205+
// Set up timeout.
206+
XCTAssertGreaterThan(_manager.timeout, 0);
207+
_manager.timeout = kRegistrationTimeout;
208+
209+
// Add callback to cancel.
210+
OCMExpect([_mockApplication registerForRemoteNotifications]);
211+
__block BOOL callbackCalled = NO;
212+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
213+
XCTAssertNil(token);
214+
XCTAssertEqualObjects(error, _error);
215+
XCTAssertFalse(callbackCalled); // verify callback is not called twice
216+
callbackCalled = YES;
217+
}];
218+
XCTAssertFalse(callbackCalled);
219+
220+
// Call cancel.
221+
[_manager cancelWithError:_error];
222+
XCTAssertTrue(callbackCalled);
223+
224+
// Add callback to timeout.
225+
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
226+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
180227
XCTAssertNil(token);
228+
XCTAssertNil(error);
181229
[expectation fulfill];
182230
}];
183231

@@ -200,9 +248,10 @@ - (void)testLegacyRegistration {
200248
[[[_mockApplication expect] ignoringNonObjectArgs] registerForRemoteNotificationTypes:0];
201249
#pragma clang diagnostic pop
202250
__block BOOL callbackCalled = NO;
203-
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
251+
[_manager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token, NSError *_Nullable error) {
204252
XCTAssertEqualObjects(token.data, _data);
205253
XCTAssertNotEqual(token.type, FIRAuthAPNSTokenTypeUnknown);
254+
XCTAssertNil(error);
206255
callbackCalled = YES;
207256
}];
208257
XCTAssertFalse(callbackCalled);

Example/Auth/Tests/FIRAuthAppDelegateProxyTests.m

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ @interface FIRAuthModernAppDelegate : NSObject <UIApplicationDelegate>
8181
*/
8282
@property(nonatomic, copy, nullable) NSData *deviceTokenReceived;
8383

84+
/** @var tokenErrorReceived
85+
@brief The last token error received, if any.
86+
*/
87+
@property(nonatomic, copy, nullable) NSError *tokenErrorReceived;
88+
8489
/** @var notificationReceived
8590
@brief The last notification received, if any.
8691
*/
@@ -100,6 +105,11 @@ - (void)application:(UIApplication *)application
100105
self.deviceTokenReceived = deviceToken;
101106
}
102107

108+
- (void)application:(UIApplication *)application
109+
didFailToRegisterForRemoteNotificationsWithError:(nonnull NSError *)error {
110+
self.tokenErrorReceived = error;
111+
}
112+
103113
- (void)application:(UIApplication *)application
104114
didReceiveRemoteNotification:(NSDictionary *)userInfo
105115
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
@@ -153,6 +163,11 @@ @implementation FIRAuthAppDelegateProxyTests {
153163
*/
154164
NSData *_deviceToken;
155165

166+
/** @var _error
167+
@brief The fake error for testing.
168+
*/
169+
NSError *_error;
170+
156171
/** @var _notification
157172
@brief The fake notification for testing.
158173
*/
@@ -173,6 +188,7 @@ - (void)setUp {
173188
[super setUp];
174189
_mockApplication = OCMClassMock([UIApplication class]);
175190
_deviceToken = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
191+
_error = [NSError errorWithDomain:@"FakeError" code:12345 userInfo:nil];
176192
_notification = @{ @"zxcv" : @1234 };
177193
_url = [NSURL URLWithString:@"https://abc.def/ghi"];
178194
_isIOS9orLater = [[[UIDevice currentDevice] systemVersion] doubleValue] >= 9.0;
@@ -261,6 +277,8 @@ - (void)testEmptyDelegateOneHandler {
261277
// Verify certain methods are swizzled while others are not.
262278
XCTAssertTrue([delegate respondsToSelector:
263279
@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
280+
XCTAssertTrue([delegate respondsToSelector:
281+
@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
264282
XCTAssertTrue([delegate respondsToSelector:
265283
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]);
266284
XCTAssertFalse([delegate respondsToSelector:
@@ -288,6 +306,12 @@ - (void)testEmptyDelegateOneHandler {
288306
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
289307
OCMVerifyAll(mockHandler);
290308

309+
// Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled.
310+
OCMExpect([mockHandler handleAPNSTokenError:_error]);
311+
[delegate application:_mockApplication
312+
didFailToRegisterForRemoteNotificationsWithError:_error];
313+
OCMVerifyAll(mockHandler);
314+
291315
// Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled.
292316
OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES);
293317
__block BOOL fetchCompletionHandlerCalled = NO;
@@ -323,6 +347,8 @@ - (void)testEmptyDelegateOneHandler {
323347
// Verify nothing bad happens after the handler is released.
324348
[delegate application:_mockApplication
325349
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
350+
[delegate application:_mockApplication
351+
didFailToRegisterForRemoteNotificationsWithError:_error];
326352
[delegate application:_mockApplication
327353
didReceiveRemoteNotification:_notification
328354
fetchCompletionHandler:^(UIBackgroundFetchResult result) {
@@ -374,6 +400,8 @@ - (void)testLegacyDelegateTwoHandlers {
374400
// Verify certain methods are swizzled while others are not.
375401
XCTAssertTrue([delegate respondsToSelector:
376402
@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
403+
XCTAssertTrue([delegate respondsToSelector:
404+
@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
377405
XCTAssertFalse([delegate respondsToSelector:
378406
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]);
379407
XCTAssertTrue([delegate respondsToSelector:
@@ -401,6 +429,14 @@ - (void)testLegacyDelegateTwoHandlers {
401429
OCMVerifyAll(mockHandler1);
402430
OCMVerifyAll(mockHandler2);
403431

432+
// Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled.
433+
OCMExpect([mockHandler1 handleAPNSTokenError:_error]);
434+
OCMExpect([mockHandler2 handleAPNSTokenError:_error]);
435+
[delegate application:_mockApplication
436+
didFailToRegisterForRemoteNotificationsWithError:_error];
437+
OCMVerifyAll(mockHandler1);
438+
OCMVerifyAll(mockHandler2);
439+
404440
// Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled.
405441
OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(YES);
406442
// handler2 shouldn't been invoked because it is already handled by handler1.
@@ -431,6 +467,12 @@ - (void)testLegacyDelegateTwoHandlers {
431467
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
432468
OCMVerifyAll(mockHandler1);
433469

470+
// Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled.
471+
OCMExpect([mockHandler1 handleAPNSTokenError:_error]);
472+
[delegate application:_mockApplication
473+
didFailToRegisterForRemoteNotificationsWithError:_error];
474+
OCMVerifyAll(mockHandler1);
475+
434476
// Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is NOT handled.
435477
OCMExpect([mockHandler1 canHandleNotification:_notification]).andReturn(NO);
436478
[delegate application:_mockApplication didReceiveRemoteNotification:_notification];
@@ -457,6 +499,8 @@ - (void)testLegacyDelegateTwoHandlers {
457499
// Verify the delegate still works after all handlers are released.
458500
[delegate application:_mockApplication
459501
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
502+
[delegate application:_mockApplication
503+
didFailToRegisterForRemoteNotificationsWithError:_error];
460504
[delegate application:_mockApplication didReceiveRemoteNotification:_notification];
461505
XCTAssertEqualObjects(delegate.notificationReceived, _notification);
462506
delegate.notificationReceived = nil;
@@ -504,6 +548,8 @@ - (void)testModernDelegateWithUnaffectedInstance {
504548
// Verify certain methods are swizzled while others are not.
505549
XCTAssertTrue([delegate respondsToSelector:
506550
@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
551+
XCTAssertTrue([delegate respondsToSelector:
552+
@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
507553
XCTAssertTrue([delegate respondsToSelector:
508554
@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]);
509555
XCTAssertFalse([delegate respondsToSelector:
@@ -532,6 +578,14 @@ - (void)testModernDelegateWithUnaffectedInstance {
532578
XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
533579
delegate.deviceTokenReceived = nil;
534580

581+
// Verify `application:didFailToRegisterForRemoteNotificationsWithError:` is handled.
582+
OCMExpect([mockHandler handleAPNSTokenError:_error]);
583+
[delegate application:_mockApplication
584+
didFailToRegisterForRemoteNotificationsWithError:_error];
585+
OCMVerifyAll(mockHandler);
586+
XCTAssertEqualObjects(delegate.tokenErrorReceived, _error);
587+
delegate.tokenErrorReceived = nil;
588+
535589
// Verify `application:didReceiveRemoteNotification:fetchCompletionHandler:` is handled.
536590
OCMExpect([mockHandler canHandleNotification:_notification]).andReturn(YES);
537591
__block BOOL fetchCompletionHandlerCalled = NO;
@@ -565,6 +619,10 @@ - (void)testModernDelegateWithUnaffectedInstance {
565619
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
566620
XCTAssertEqualObjects(unaffectedDelegate.deviceTokenReceived, _deviceToken);
567621
unaffectedDelegate.deviceTokenReceived = nil;
622+
[unaffectedDelegate application:_mockApplication
623+
didFailToRegisterForRemoteNotificationsWithError:_error];
624+
XCTAssertEqualObjects(unaffectedDelegate.tokenErrorReceived, _error);
625+
unaffectedDelegate.tokenErrorReceived = nil;
568626
fetchCompletionHandlerCalled = NO;
569627
[unaffectedDelegate application:_mockApplication
570628
didReceiveRemoteNotification:_notification
@@ -590,6 +648,10 @@ - (void)testModernDelegateWithUnaffectedInstance {
590648
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
591649
XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
592650
delegate.deviceTokenReceived = nil;
651+
[delegate application:_mockApplication
652+
didFailToRegisterForRemoteNotificationsWithError:_error];
653+
XCTAssertEqualObjects(delegate.tokenErrorReceived, _error);
654+
delegate.tokenErrorReceived = nil;
593655
__block BOOL fetchCompletionHandlerCalled = NO;
594656
[delegate application:_mockApplication
595657
didReceiveRemoteNotification:_notification
@@ -615,7 +677,11 @@ - (void)testModernDelegateWithUnaffectedInstance {
615677
didRegisterForRemoteNotificationsWithDeviceToken:_deviceToken];
616678
XCTAssertEqualObjects(delegate.deviceTokenReceived, _deviceToken);
617679
delegate.deviceTokenReceived = nil;
618-
__block BOOL fetchCompletionHandlerCalled = NO;
680+
[delegate application:_mockApplication
681+
didFailToRegisterForRemoteNotificationsWithError:_error];
682+
XCTAssertEqualObjects(delegate.tokenErrorReceived, _error);
683+
delegate.tokenErrorReceived = nil;
684+
__block BOOL fetchCompletionHandlerCalled = NO;
619685
[delegate application:_mockApplication
620686
didReceiveRemoteNotification:_notification
621687
fetchCompletionHandler:^(UIBackgroundFetchResult result) {

Example/Auth/Tests/FIRPhoneAuthProviderTests.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ - (void)testMissingAPNSToken {
274274
.andCallBlock1(^(FIRAuthNotificationForwardingCallback callback) { callback(YES); });
275275
OCMExpect([_mockAppCredentialManager credential]).andReturn(nil);
276276
OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
277-
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(nil); });
277+
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(nil, nil); });
278278
// Expect verify client request to the backend wth empty token.
279279
OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
280280
.andCallBlock2(^(FIRVerifyClientRequest *request,
@@ -314,7 +314,7 @@ - (void)testVerifyClient {
314314
FIRAuthAPNSToken *token = [[FIRAuthAPNSToken alloc] initWithData:data
315315
type:FIRAuthAPNSTokenTypeProd];
316316
OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
317-
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
317+
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
318318
// Expect verify client request to the backend.
319319
OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
320320
.andCallBlock2(^(FIRVerifyClientRequest *request,
@@ -417,7 +417,7 @@ - (void)testSendVerificationCodeFailedRetry {
417417
});
418418

419419
OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
420-
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
420+
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
421421
// Expect verify client request to the backend.
422422
OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
423423
.andCallBlock2(^(FIRVerifyClientRequest *request,
@@ -509,7 +509,7 @@ - (void)testSendVerificationCodeSuccessFulRetry {
509509
});
510510

511511
OCMExpect([_mockAPNSTokenManager getTokenWithCallback:OCMOCK_ANY])
512-
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token); });
512+
.andCallBlock1(^(FIRAuthAPNSTokenCallback callback) { callback(token, nil); });
513513
// Expect verify client request to the backend.
514514
OCMExpect([_mockBackend verifyClient:[OCMArg any] callback:[OCMArg any]])
515515
.andCallBlock2(^(FIRVerifyClientRequest *request,

Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ - (void)verifyClientWithCompletion:(FIRVerifyClientCallback)completion {
169169
completion(_auth.appCredentialManager.credential, nil);
170170
return;
171171
}
172-
[_auth.tokenManager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token) {
172+
[_auth.tokenManager getTokenWithCallback:^(FIRAuthAPNSToken *_Nullable token,
173+
NSError *_Nullable error) {
174+
if (!token) {
175+
completion(nil, [FIRAuthErrorUtils missingAppTokenErrorWithUnderlyingError:error]);
176+
return;
177+
}
173178
FIRVerifyClientRequest *request =
174179
[[FIRVerifyClientRequest alloc] initWithAppToken:token.string
175180
isSandbox:token.type == FIRAuthAPNSTokenTypeSandbox

Firebase/Auth/Source/FIRAuth.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,12 @@ - (void)setAPNSToken:(NSData *)token type:(FIRAuthAPNSTokenType)type {
971971
});
972972
}
973973

974+
- (void)handleAPNSTokenError:(NSError *)error {
975+
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
976+
[_tokenManager cancelWithError:error];
977+
});
978+
}
979+
974980
- (BOOL)canHandleNotification:(NSDictionary *)userInfo {
975981
__block BOOL result = NO;
976982
dispatch_sync(FIRAuthGlobalWorkQueue(), ^{

Firebase/Auth/Source/FIRAuthAPNSTokenManager.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ NS_ASSUME_NONNULL_BEGIN
2424
/** @typedef FIRAuthAPNSTokenCallback
2525
@brief The type of block to receive an APNs token.
2626
@param token The APNs token if one is available.
27+
@param error The error happened if any.
28+
@remarks Both `token` and `error` being `nil` means the request timed-out.
2729
*/
28-
typedef void (^FIRAuthAPNSTokenCallback)(FIRAuthAPNSToken *_Nullable token);
30+
typedef void (^FIRAuthAPNSTokenCallback)(FIRAuthAPNSToken *_Nullable token,
31+
NSError *_Nullable error);
2932

3033
/** @class FIRAuthAPNSTokenManager
3134
@brief A class to manage APNs token in memory.
@@ -64,6 +67,12 @@ typedef void (^FIRAuthAPNSTokenCallback)(FIRAuthAPNSToken *_Nullable token);
6467
*/
6568
- (void)getTokenWithCallback:(FIRAuthAPNSTokenCallback)callback;
6669

70+
/** @fn cancelWithError:
71+
@brief Cancels any pending `getTokenWithCallback:` request.
72+
@param error The error to return.
73+
*/
74+
- (void)cancelWithError:(NSError *)error;
75+
6776
@end
6877

6978
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)