Skip to content

Commit 0738c0e

Browse files
Fix handler for multiple calls of -[FIRInstanceID instanceIDWithHandler:] (#2445) (#2559)
1 parent 69c8e52 commit 0738c0e

File tree

6 files changed

+324
-27
lines changed

6 files changed

+324
-27
lines changed

Example/InstanceID/Tests/FIRInstanceIDTest.m

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,182 @@ - (void)testDefaultToken_maxRetries {
811811
XCTAssertEqual(newTokenFetchCount, [FIRInstanceID maxRetryCountForDefaultToken]);
812812
}
813813

814+
- (void)testInstanceIDWithHandler_WhileRequesting_Success {
815+
[self stubKeyPairStoreToReturnValidKeypair];
816+
[self mockAuthServiceToAlwaysReturnValidCheckin];
817+
818+
// Expect `fetchNewTokenWithAuthorizedEntity` to be called once
819+
XCTestExpectation *fetchNewTokenExpectation =
820+
[self expectationWithDescription:@"fetchNewTokenExpectation"];
821+
__block FIRInstanceIDTokenHandler tokenHandler;
822+
823+
[[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
824+
[invocation getArgument:&tokenHandler atIndex:6];
825+
[fetchNewTokenExpectation fulfill];
826+
}] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
827+
scope:kFIRInstanceIDDefaultTokenScope
828+
keyPair:[OCMArg any]
829+
options:[OCMArg any]
830+
handler:[OCMArg any]];
831+
832+
// Make 1st call
833+
XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
834+
FIRInstanceIDResultHandler handler1 =
835+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
836+
[handlerExpectation1 fulfill];
837+
XCTAssertNotNil(result);
838+
XCTAssertEqual(result.token, kToken);
839+
XCTAssertNil(error);
840+
};
841+
842+
[self.mockInstanceID instanceIDWithHandler:handler1];
843+
844+
// Make 2nd call
845+
XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
846+
FIRInstanceIDResultHandler handler2 =
847+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
848+
[handlerExpectation2 fulfill];
849+
XCTAssertNotNil(result);
850+
XCTAssertEqual(result.token, kToken);
851+
XCTAssertNil(error);
852+
};
853+
854+
[self.mockInstanceID instanceIDWithHandler:handler2];
855+
856+
// Wait for `fetchNewTokenWithAuthorizedEntity` to be performed
857+
[self waitForExpectations:@[ fetchNewTokenExpectation ] timeout:1 enforceOrder:false];
858+
// Finish token fetch request
859+
tokenHandler(kToken, nil);
860+
861+
// Wait for completion handlers for both calls to be performed
862+
[self waitForExpectationsWithTimeout:1 handler:NULL];
863+
}
864+
865+
- (void)testInstanceIDWithHandler_WhileRequesting_RetrySuccess {
866+
[self stubKeyPairStoreToReturnValidKeypair];
867+
[self mockAuthServiceToAlwaysReturnValidCheckin];
868+
869+
// Expect `fetchNewTokenWithAuthorizedEntity` to be called twice
870+
XCTestExpectation *fetchNewTokenExpectation1 =
871+
[self expectationWithDescription:@"fetchNewTokenExpectation1"];
872+
XCTestExpectation *fetchNewTokenExpectation2 =
873+
[self expectationWithDescription:@"fetchNewTokenExpectation2"];
874+
NSArray *fetchNewTokenExpectations = @[ fetchNewTokenExpectation1, fetchNewTokenExpectation2 ];
875+
876+
__block NSInteger fetchNewTokenCallCount = 0;
877+
__block FIRInstanceIDTokenHandler tokenHandler;
878+
879+
[[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
880+
[invocation getArgument:&tokenHandler atIndex:6];
881+
[fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
882+
fetchNewTokenCallCount += 1;
883+
}] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
884+
scope:kFIRInstanceIDDefaultTokenScope
885+
keyPair:[OCMArg any]
886+
options:[OCMArg any]
887+
handler:[OCMArg any]];
888+
889+
// Mock Instance ID's retry interval to 0, to vastly speed up this test.
890+
[[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
891+
892+
// Make 1st call
893+
XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
894+
FIRInstanceIDResultHandler handler1 =
895+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
896+
[handlerExpectation1 fulfill];
897+
XCTAssertNotNil(result);
898+
XCTAssertEqual(result.token, kToken);
899+
XCTAssertNil(error);
900+
};
901+
902+
[self.mockInstanceID instanceIDWithHandler:handler1];
903+
904+
// Make 2nd call
905+
XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
906+
FIRInstanceIDResultHandler handler2 =
907+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
908+
[handlerExpectation2 fulfill];
909+
XCTAssertNotNil(result);
910+
XCTAssertEqual(result.token, kToken);
911+
XCTAssertNil(error);
912+
};
913+
914+
[self.mockInstanceID instanceIDWithHandler:handler2];
915+
916+
// Wait for the 1st `fetchNewTokenWithAuthorizedEntity` to be performed
917+
[self waitForExpectations:@[ fetchNewTokenExpectation1 ] timeout:1 enforceOrder:false];
918+
// Fail for the 1st time
919+
tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
920+
921+
// Wait for the 2nd token feth
922+
[self waitForExpectations:@[ fetchNewTokenExpectation2 ] timeout:1 enforceOrder:false];
923+
// Finish with success
924+
tokenHandler(kToken, nil);
925+
926+
// Wait for completion handlers for both calls to be performed
927+
[self waitForExpectationsWithTimeout:1 handler:NULL];
928+
}
929+
930+
- (void)testInstanceIDWithHandler_WhileRequesting_RetryFailure {
931+
[self stubKeyPairStoreToReturnValidKeypair];
932+
[self mockAuthServiceToAlwaysReturnValidCheckin];
933+
934+
// Expect `fetchNewTokenWithAuthorizedEntity` to be called once
935+
NSMutableArray<XCTestExpectation *> *fetchNewTokenExpectations = [NSMutableArray array];
936+
for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
937+
NSString *name = [NSString stringWithFormat:@"fetchNewTokenExpectation-%ld", (long)i];
938+
[fetchNewTokenExpectations addObject:[self expectationWithDescription:name]];
939+
}
940+
941+
__block NSInteger fetchNewTokenCallCount = 0;
942+
__block FIRInstanceIDTokenHandler tokenHandler;
943+
944+
[[[self.mockTokenManager stub] andDo:^(NSInvocation *invocation) {
945+
[invocation getArgument:&tokenHandler atIndex:6];
946+
[fetchNewTokenExpectations[fetchNewTokenCallCount] fulfill];
947+
fetchNewTokenCallCount += 1;
948+
}] fetchNewTokenWithAuthorizedEntity:kAuthorizedEntity
949+
scope:kFIRInstanceIDDefaultTokenScope
950+
keyPair:[OCMArg any]
951+
options:[OCMArg any]
952+
handler:[OCMArg any]];
953+
954+
// Mock Instance ID's retry interval to 0, to vastly speed up this test.
955+
[[[self.mockInstanceID stub] andReturnValue:@(0)] retryIntervalToFetchDefaultToken];
956+
957+
// Make 1st call
958+
XCTestExpectation *handlerExpectation1 = [self expectationWithDescription:@"handlerExpectation1"];
959+
FIRInstanceIDResultHandler handler1 =
960+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
961+
[handlerExpectation1 fulfill];
962+
XCTAssertNil(result);
963+
XCTAssertNotNil(error);
964+
};
965+
966+
[self.mockInstanceID instanceIDWithHandler:handler1];
967+
968+
// Make 2nd call
969+
XCTestExpectation *handlerExpectation2 = [self expectationWithDescription:@"handlerExpectation1"];
970+
FIRInstanceIDResultHandler handler2 =
971+
^(FIRInstanceIDResult *_Nullable result, NSError *_Nullable error) {
972+
[handlerExpectation2 fulfill];
973+
XCTAssertNil(result);
974+
XCTAssertNotNil(error);
975+
};
976+
977+
[self.mockInstanceID instanceIDWithHandler:handler2];
978+
979+
for (NSInteger i = 0; i < [[self.instanceID class] maxRetryCountForDefaultToken]; ++i) {
980+
// Wait for the i `fetchNewTokenWithAuthorizedEntity` to be performed
981+
[self waitForExpectations:@[ fetchNewTokenExpectations[i] ] timeout:1 enforceOrder:false];
982+
// Fail for the i time
983+
tokenHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown]);
984+
}
985+
986+
// Wait for completion handlers for both calls to be performed
987+
[self waitForExpectationsWithTimeout:1 handler:NULL];
988+
}
989+
814990
/**
815991
* Tests a Keychain read failure while we try to fetch a new InstanceID token. If the Keychain
816992
* read fails we won't be able to fetch the public key which is required while fetching a new

Example/default.profraw

2.01 KB
Binary file not shown.

Firebase/InstanceID/FIRInstanceID.m

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
2525
#import "FIRInstanceID+Private.h"
2626
#import "FIRInstanceIDAuthService.h"
27+
#import "FIRInstanceIDCombinedHandler.h"
2728
#import "FIRInstanceIDConstants.h"
2829
#import "FIRInstanceIDDefines.h"
2930
#import "FIRInstanceIDKeyPairStore.h"
@@ -114,9 +115,9 @@ @interface FIRInstanceID ()
114115
@property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore;
115116

116117
// backoff and retry for default token
117-
@property(atomic, readwrite, assign) BOOL isFetchingDefaultToken;
118-
@property(atomic, readwrite, assign) BOOL isDefaultTokenFetchScheduled;
119118
@property(nonatomic, readwrite, assign) NSInteger retryCountForDefaultToken;
119+
@property(atomic, strong, nullable)
120+
FIRInstanceIDCombinedHandler<NSString *> *defaultTokenFetchHandler;
120121

121122
@end
122123

@@ -831,10 +832,30 @@ - (NSInteger)retryIntervalToFetchDefaultToken {
831832
kMaxRetryIntervalForDefaultTokenInSeconds);
832833
}
833834

834-
- (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
835-
if (self.isFetchingDefaultToken || self.isDefaultTokenFetchScheduled) {
835+
- (void)defaultTokenWithHandler:(nullable FIRInstanceIDTokenHandler)aHandler {
836+
[self defaultTokenWithRetry:NO handler:aHandler];
837+
}
838+
839+
/**
840+
* @param retry Indicates if the method is called to perform a retry after a failed attempt.
841+
* If `YES`, then actual token request will be performed even if `self.defaultTokenFetchHandler !=
842+
* nil`
843+
*/
844+
- (void)defaultTokenWithRetry:(BOOL)retry handler:(nullable FIRInstanceIDTokenHandler)aHandler {
845+
BOOL shouldPerformRequest = retry || self.defaultTokenFetchHandler == nil;
846+
847+
if (!self.defaultTokenFetchHandler) {
848+
self.defaultTokenFetchHandler = [[FIRInstanceIDCombinedHandler<NSString *> alloc] init];
849+
}
850+
851+
if (aHandler) {
852+
[self.defaultTokenFetchHandler addHandler:aHandler];
853+
}
854+
855+
if (!shouldPerformRequest) {
836856
return;
837857
}
858+
838859
NSDictionary *instanceIDOptions = @{};
839860
BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil;
840861
if (hasFirebaseMessaging && self.apnsTokenData) {
@@ -851,7 +872,6 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
851872
FIRInstanceID_WEAKIFY(self);
852873
FIRInstanceIDTokenHandler newHandler = ^void(NSString *token, NSError *error) {
853874
FIRInstanceID_STRONGIFY(self);
854-
self.isFetchingDefaultToken = NO;
855875

856876
if (error) {
857877
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID009,
@@ -871,21 +891,12 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
871891
// Do not retry beyond the maximum limit.
872892
if (self.retryCountForDefaultToken < [[self class] maxRetryCountForDefaultToken]) {
873893
NSInteger retryInterval = [self retryIntervalToFetchDefaultToken];
874-
FIRInstanceID_WEAKIFY(self);
875-
self.isDefaultTokenFetchScheduled = YES;
876-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)),
877-
dispatch_get_main_queue(), ^{
878-
FIRInstanceID_STRONGIFY(self);
879-
self.isDefaultTokenFetchScheduled = NO;
880-
[self defaultTokenWithHandler:handler];
881-
});
894+
[self retryGetDefaultTokenAfter:retryInterval];
882895
} else {
883896
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007,
884897
@"Failed to retrieve the default FCM token after %ld retries",
885898
(long)self.retryCountForDefaultToken);
886-
if (handler) {
887-
handler(nil, error);
888-
}
899+
[self performDefaultTokenHandlerWithToken:nil error:error];
889900
}
890901
} else {
891902
// If somebody updated IID with APNS token while our initial request did not have it
@@ -904,13 +915,7 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
904915
if (!APNSRemainedSameDuringFetch && hasFirebaseMessaging) {
905916
// APNs value did change mid-fetch, so the token should be re-fetched with the current APNs
906917
// value.
907-
self.isDefaultTokenFetchScheduled = YES;
908-
FIRInstanceID_WEAKIFY(self);
909-
dispatch_async(dispatch_get_main_queue(), ^{
910-
FIRInstanceID_STRONGIFY(self);
911-
self.isDefaultTokenFetchScheduled = NO;
912-
[self defaultTokenWithHandler:handler];
913-
});
918+
[self retryGetDefaultTokenAfter:0];
914919
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeRefetchingTokenForAPNS,
915920
@"Received APNS token while fetching default token. "
916921
@"Refetching default token.");
@@ -934,20 +939,41 @@ - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
934939
object:[self.defaultFCMToken copy]];
935940
[[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification
936941
postingStyle:NSPostASAP];
937-
}
938-
if (handler) {
939-
handler(token, nil);
942+
943+
[self performDefaultTokenHandlerWithToken:token error:nil];
940944
}
941945
}
942946
};
943947

944-
self.isFetchingDefaultToken = YES;
945948
[self tokenWithAuthorizedEntity:self.fcmSenderID
946949
scope:kFIRInstanceIDDefaultTokenScope
947950
options:instanceIDOptions
948951
handler:newHandler];
949952
}
950953

954+
/**
955+
*
956+
*/
957+
- (void)performDefaultTokenHandlerWithToken:(NSString *)token error:(NSError *)error {
958+
if (!self.defaultTokenFetchHandler) {
959+
return;
960+
}
961+
962+
[self.defaultTokenFetchHandler combinedHandler](token, error);
963+
self.defaultTokenFetchHandler = nil;
964+
}
965+
966+
- (void)retryGetDefaultTokenAfter:(NSTimeInterval)retryInterval {
967+
FIRInstanceID_WEAKIFY(self);
968+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)),
969+
dispatch_get_main_queue(), ^{
970+
FIRInstanceID_STRONGIFY(self);
971+
// Pass nil: no new handlers to be added, currently existing handlers
972+
// will be called
973+
[self defaultTokenWithRetry:YES handler:nil];
974+
});
975+
}
976+
951977
#pragma mark - APNS Token
952978
// This should only be triggered from FCM.
953979
- (void)notifyAPNSTokenIsSet:(NSNotification *)notification {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
/**
22+
* A generic class to combine several handler blocks into a single block in a thread-safe manner
23+
*/
24+
@interface FIRInstanceIDCombinedHandler<ResultType> : NSObject
25+
26+
- (void)addHandler:(void (^)(ResultType _Nullable result, NSError* _Nullable error))handler;
27+
- (void (^)(ResultType _Nullable result, NSError* _Nullable error))combinedHandler;
28+
29+
@end
30+
31+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)