2019-07-17 20:07:35 -04:00
|
|
|
/*
|
|
|
|
|
* Copyright 2019 Google
|
|
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-06-17 07:26:40 -07:00
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsIDController.h"
|
2019-07-17 20:07:35 -04:00
|
|
|
|
|
|
|
|
#if __has_include(<FBLPromises/FBLPromises.h>)
|
|
|
|
|
#import <FBLPromises/FBLPromises.h>
|
|
|
|
|
#else
|
|
|
|
|
#import "FBLPromises.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-09-29 07:26:10 -07:00
|
|
|
#import <GoogleUtilities/GULKeychainStorage.h>
|
2022-03-22 13:36:33 -04:00
|
|
|
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
2019-07-24 16:24:20 -04:00
|
|
|
|
2020-06-17 07:26:40 -07:00
|
|
|
#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/FIRInstallationsItem.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDStore.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/IIDMigration/FIRInstallationsIIDTokenStore.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsAPIService.h"
|
2020-08-10 09:36:04 -04:00
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h"
|
2020-06-17 07:26:40 -07:00
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsSingleOperationPromiseCache.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStore.h"
|
2019-07-17 20:07:35 -04:00
|
|
|
|
2020-06-17 07:26:40 -07:00
|
|
|
#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h"
|
|
|
|
|
#import "FirebaseInstallations/Source/Library/InstallationsStore/FIRInstallationsStoredAuthToken.h"
|
2019-10-09 13:47:58 -04:00
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
const NSNotificationName FIRInstallationIDDidChangeNotification =
|
|
|
|
|
@"FIRInstallationIDDidChangeNotification";
|
2019-07-24 12:48:23 -04:00
|
|
|
NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey =
|
|
|
|
|
@"FIRInstallationIDDidChangeNotification";
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
NSTimeInterval const kFIRInstallationsTokenExpirationThreshold = 60 * 60; // 1 hour.
|
|
|
|
|
|
2020-05-15 08:55:43 -04:00
|
|
|
static NSString *const kKeychainService = @"com.firebase.FIRInstallations.installations";
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
@interface FIRInstallationsIDController ()
|
|
|
|
|
@property(nonatomic, readonly) NSString *appID;
|
|
|
|
|
@property(nonatomic, readonly) NSString *appName;
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readonly) FIRInstallationsStore *installationsStore;
|
|
|
|
|
@property(nonatomic, readonly) FIRInstallationsIIDStore *IIDStore;
|
2019-11-04 11:45:17 -05:00
|
|
|
@property(nonatomic, readonly) FIRInstallationsIIDTokenStore *IIDTokenStore;
|
2019-07-17 20:07:35 -04:00
|
|
|
|
|
|
|
|
@property(nonatomic, readonly) FIRInstallationsAPIService *APIService;
|
|
|
|
|
|
2020-08-10 09:36:04 -04:00
|
|
|
@property(nonatomic, readonly) id<FIRInstallationsBackoffControllerProtocol> backoffController;
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *>
|
|
|
|
|
*getInstallationPromiseCache;
|
|
|
|
|
@property(nonatomic, readonly)
|
|
|
|
|
FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *> *authTokenPromiseCache;
|
|
|
|
|
@property(nonatomic, readonly) FIRInstallationsSingleOperationPromiseCache<FIRInstallationsItem *>
|
|
|
|
|
*authTokenForcingRefreshPromiseCache;
|
|
|
|
|
@property(nonatomic, readonly)
|
|
|
|
|
FIRInstallationsSingleOperationPromiseCache<NSNull *> *deleteInstallationPromiseCache;
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FIRInstallationsIDController
|
|
|
|
|
|
2022-04-01 10:01:02 -04:00
|
|
|
- (instancetype)initWithApp:(FIRApp *)app {
|
|
|
|
|
NSString *serviceName =
|
|
|
|
|
[FIRInstallationsIDController keychainServiceWithAppID:app.options.googleAppID];
|
2020-05-15 08:55:43 -04:00
|
|
|
GULKeychainStorage *secureStorage = [[GULKeychainStorage alloc] initWithService:serviceName];
|
2019-07-17 20:07:35 -04:00
|
|
|
FIRInstallationsStore *installationsStore =
|
2022-04-01 10:01:02 -04:00
|
|
|
[[FIRInstallationsStore alloc] initWithSecureStorage:secureStorage
|
|
|
|
|
accessGroup:app.options.appGroupID];
|
2020-01-16 16:13:49 -05:00
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
FIRInstallationsAPIService *apiService =
|
2022-04-01 10:01:02 -04:00
|
|
|
[[FIRInstallationsAPIService alloc] initWithAPIKey:app.options.APIKey
|
|
|
|
|
projectID:app.options.projectID
|
|
|
|
|
heartbeatLogger:app.heartbeatLogger];
|
2020-01-16 16:13:49 -05:00
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
FIRInstallationsIIDStore *IIDStore = [[FIRInstallationsIIDStore alloc] init];
|
2019-11-04 11:45:17 -05:00
|
|
|
FIRInstallationsIIDTokenStore *IIDCheckingStore =
|
2022-04-01 10:01:02 -04:00
|
|
|
[[FIRInstallationsIIDTokenStore alloc] initWithGCMSenderID:app.options.GCMSenderID];
|
2019-07-17 20:07:35 -04:00
|
|
|
|
2020-08-10 09:36:04 -04:00
|
|
|
FIRInstallationsBackoffController *backoffController =
|
|
|
|
|
[[FIRInstallationsBackoffController alloc] init];
|
|
|
|
|
|
2022-04-01 10:01:02 -04:00
|
|
|
return [self initWithGoogleAppID:app.options.googleAppID
|
|
|
|
|
appName:app.name
|
2019-07-17 20:07:35 -04:00
|
|
|
installationsStore:installationsStore
|
|
|
|
|
APIService:apiService
|
2019-10-18 16:21:38 -04:00
|
|
|
IIDStore:IIDStore
|
2020-08-10 09:36:04 -04:00
|
|
|
IIDTokenStore:IIDCheckingStore
|
|
|
|
|
backoffController:backoffController];
|
2019-07-17 20:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The initializer is supposed to be used by tests to inject `installationsStore`.
|
|
|
|
|
- (instancetype)initWithGoogleAppID:(NSString *)appID
|
|
|
|
|
appName:(NSString *)appName
|
|
|
|
|
installationsStore:(FIRInstallationsStore *)installationsStore
|
|
|
|
|
APIService:(FIRInstallationsAPIService *)APIService
|
2019-10-18 16:21:38 -04:00
|
|
|
IIDStore:(FIRInstallationsIIDStore *)IIDStore
|
2020-08-10 09:36:04 -04:00
|
|
|
IIDTokenStore:(FIRInstallationsIIDTokenStore *)IIDTokenStore
|
|
|
|
|
backoffController:
|
|
|
|
|
(id<FIRInstallationsBackoffControllerProtocol>)backoffController {
|
2019-07-17 20:07:35 -04:00
|
|
|
self = [super init];
|
|
|
|
|
if (self) {
|
|
|
|
|
_appID = appID;
|
|
|
|
|
_appName = appName;
|
|
|
|
|
_installationsStore = installationsStore;
|
|
|
|
|
_APIService = APIService;
|
|
|
|
|
_IIDStore = IIDStore;
|
2019-11-04 11:45:17 -05:00
|
|
|
_IIDTokenStore = IIDTokenStore;
|
2020-08-10 09:36:04 -04:00
|
|
|
_backoffController = backoffController;
|
2019-07-17 20:07:35 -04:00
|
|
|
|
|
|
|
|
__weak FIRInstallationsIDController *weakSelf = self;
|
|
|
|
|
|
|
|
|
|
_getInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
|
|
|
|
|
initWithNewOperationHandler:^FBLPromise *_Nonnull {
|
|
|
|
|
FIRInstallationsIDController *strongSelf = weakSelf;
|
|
|
|
|
return [strongSelf createGetInstallationItemPromise];
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
_authTokenPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
|
|
|
|
|
initWithNewOperationHandler:^FBLPromise *_Nonnull {
|
|
|
|
|
FIRInstallationsIDController *strongSelf = weakSelf;
|
|
|
|
|
return [strongSelf installationWithValidAuthTokenForcingRefresh:NO];
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
_authTokenForcingRefreshPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
|
|
|
|
|
initWithNewOperationHandler:^FBLPromise *_Nonnull {
|
|
|
|
|
FIRInstallationsIDController *strongSelf = weakSelf;
|
|
|
|
|
return [strongSelf installationWithValidAuthTokenForcingRefresh:YES];
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
_deleteInstallationPromiseCache = [[FIRInstallationsSingleOperationPromiseCache alloc]
|
|
|
|
|
initWithNewOperationHandler:^FBLPromise *_Nonnull {
|
|
|
|
|
FIRInstallationsIDController *strongSelf = weakSelf;
|
|
|
|
|
return [strongSelf createDeleteInstallationPromise];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Get Installation.
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)getInstallationItem {
|
2020-01-06 19:02:56 -05:00
|
|
|
return [self.getInstallationPromiseCache getExistingPendingOrCreateNewPromise];
|
2019-07-17 20:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)createGetInstallationItemPromise {
|
2019-07-22 16:00:32 -04:00
|
|
|
FIRLogDebug(kFIRLoggerInstallations,
|
|
|
|
|
kFIRInstallationsMessageCodeNewGetInstallationOperationCreated, @"%s, appName: %@",
|
|
|
|
|
__PRETTY_FUNCTION__, self.appName);
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
FBLPromise<FIRInstallationsItem *> *installationItemPromise =
|
2019-10-21 09:37:57 -04:00
|
|
|
[self getStoredInstallation].recover(^id(NSError *error) {
|
|
|
|
|
return [self createAndSaveFID];
|
|
|
|
|
});
|
2019-07-17 20:07:35 -04:00
|
|
|
|
|
|
|
|
// Initiate registration process on success if needed, but return the installation without waiting
|
|
|
|
|
// for it.
|
|
|
|
|
installationItemPromise.then(^id(FIRInstallationsItem *installation) {
|
|
|
|
|
[self getAuthTokenForcingRefresh:NO];
|
|
|
|
|
return nil;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return installationItemPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)getStoredInstallation {
|
|
|
|
|
return [self.installationsStore installationForAppID:self.appID appName:self.appName].validate(
|
|
|
|
|
^BOOL(FIRInstallationsItem *installation) {
|
2020-09-30 14:02:03 -04:00
|
|
|
NSError *validationError;
|
|
|
|
|
BOOL isValid = [installation isValid:&validationError];
|
2020-10-01 15:07:21 -04:00
|
|
|
|
|
|
|
|
if (!isValid) {
|
|
|
|
|
FIRLogWarning(
|
|
|
|
|
kFIRLoggerInstallations, kFIRInstallationsMessageCodeCorruptedStoredInstallation,
|
|
|
|
|
@"Stored installation validation error: %@", validationError.localizedDescription);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
return isValid;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)createAndSaveFID {
|
2019-10-18 16:21:38 -04:00
|
|
|
return [self migrateOrGenerateInstallation]
|
|
|
|
|
.then(^FBLPromise<FIRInstallationsItem *> *(FIRInstallationsItem *installation) {
|
|
|
|
|
return [self saveInstallation:installation];
|
2019-07-17 20:07:35 -04:00
|
|
|
})
|
|
|
|
|
.then(^FIRInstallationsItem *(FIRInstallationsItem *installation) {
|
2019-07-24 12:48:23 -04:00
|
|
|
[self postFIDDidChangeNotification];
|
2019-07-17 20:07:35 -04:00
|
|
|
return installation;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-18 16:21:38 -04:00
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)saveInstallation:(FIRInstallationsItem *)installation {
|
|
|
|
|
return [self.installationsStore saveInstallation:installation].then(
|
|
|
|
|
^FIRInstallationsItem *(NSNull *result) {
|
|
|
|
|
return installation;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tries to migrate IID data stored by FirebaseInstanceID SDK or generates a new Installation ID if
|
|
|
|
|
* not found.
|
|
|
|
|
*/
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)migrateOrGenerateInstallation {
|
|
|
|
|
if (![self isDefaultApp]) {
|
|
|
|
|
// Existing IID should be used only for default FirebaseApp.
|
|
|
|
|
FIRInstallationsItem *installation =
|
2019-11-04 11:45:17 -05:00
|
|
|
[self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil];
|
2019-10-18 16:21:38 -04:00
|
|
|
return [FBLPromise resolvedWith:installation];
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-04 11:45:17 -05:00
|
|
|
return [[[FBLPromise
|
|
|
|
|
all:@[ [self.IIDStore existingIID], [self.IIDTokenStore existingIIDDefaultToken] ]]
|
|
|
|
|
then:^id _Nullable(NSArray *_Nullable results) {
|
|
|
|
|
NSString *existingIID = results[0];
|
|
|
|
|
NSString *IIDDefaultToken = results[1];
|
2019-10-18 16:21:38 -04:00
|
|
|
|
2019-11-04 11:45:17 -05:00
|
|
|
return [self createInstallationWithFID:existingIID IIDDefaultToken:IIDDefaultToken];
|
|
|
|
|
}] recover:^id _Nullable(NSError *_Nonnull error) {
|
|
|
|
|
return [self createInstallationWithFID:[FIRInstallationsItem generateFID] IIDDefaultToken:nil];
|
|
|
|
|
}];
|
2019-10-18 16:21:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FIRInstallationsItem *)createInstallationWithFID:(NSString *)FID
|
2019-11-04 11:45:17 -05:00
|
|
|
IIDDefaultToken:(nullable NSString *)IIDDefaultToken {
|
2019-07-17 20:07:35 -04:00
|
|
|
FIRInstallationsItem *installation = [[FIRInstallationsItem alloc] initWithAppID:self.appID
|
|
|
|
|
firebaseAppName:self.appName];
|
|
|
|
|
installation.firebaseInstallationID = FID;
|
2019-11-04 11:45:17 -05:00
|
|
|
installation.IIDDefaultToken = IIDDefaultToken;
|
2019-07-17 20:07:35 -04:00
|
|
|
installation.registrationStatus = FIRInstallationStatusUnregistered;
|
2019-10-18 16:21:38 -04:00
|
|
|
return installation;
|
2019-07-17 20:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - FID registration
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)registerInstallationIfNeeded:
|
|
|
|
|
(FIRInstallationsItem *)installation {
|
|
|
|
|
switch (installation.registrationStatus) {
|
|
|
|
|
case FIRInstallationStatusRegistered:
|
|
|
|
|
// Already registered. Do nothing.
|
|
|
|
|
return [FBLPromise resolvedWith:installation];
|
|
|
|
|
|
|
|
|
|
case FIRInstallationStatusUnknown:
|
|
|
|
|
case FIRInstallationStatusUnregistered:
|
|
|
|
|
// Registration required. Proceed.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 09:36:04 -04:00
|
|
|
// Check for backoff.
|
|
|
|
|
if (![self.backoffController isNextRequestAllowed]) {
|
|
|
|
|
return [FIRInstallationsErrorUtil
|
|
|
|
|
rejectedPromiseWithError:[FIRInstallationsErrorUtil backoffIntervalWaitError]];
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
return [self.APIService registerInstallation:installation]
|
2019-10-21 09:37:57 -04:00
|
|
|
.catch(^(NSError *_Nonnull error) {
|
2020-08-10 09:36:04 -04:00
|
|
|
[self updateBackoffWithSuccess:NO APIError:error];
|
|
|
|
|
|
2019-10-21 09:37:57 -04:00
|
|
|
if ([self doesRegistrationErrorRequireConfigChange:error]) {
|
|
|
|
|
FIRLogError(kFIRLoggerInstallations,
|
|
|
|
|
kFIRInstallationsMessageCodeInvalidFirebaseConfiguration,
|
2020-05-20 12:48:16 -04:00
|
|
|
@"Firebase Installation registration failed for app with name: %@, error:\n"
|
2019-10-21 09:37:57 -04:00
|
|
|
@"%@\nPlease make sure you use valid GoogleService-Info.plist",
|
2020-05-20 12:48:16 -04:00
|
|
|
self.appName, error.userInfo[NSLocalizedFailureReasonErrorKey]);
|
2019-10-21 09:37:57 -04:00
|
|
|
}
|
2019-10-09 13:47:58 -04:00
|
|
|
})
|
2019-07-22 16:00:32 -04:00
|
|
|
.then(^id(FIRInstallationsItem *registeredInstallation) {
|
2020-08-10 09:36:04 -04:00
|
|
|
[self updateBackoffWithSuccess:YES APIError:nil];
|
2019-10-23 14:22:28 -04:00
|
|
|
return [self saveInstallation:registeredInstallation];
|
2019-07-17 20:07:35 -04:00
|
|
|
})
|
2019-10-23 14:22:28 -04:00
|
|
|
.then(^FIRInstallationsItem *(FIRInstallationsItem *registeredInstallation) {
|
2019-07-24 12:48:23 -04:00
|
|
|
// Server may respond with a different FID if the sent one cannot be accepted.
|
|
|
|
|
if (![registeredInstallation.firebaseInstallationID
|
|
|
|
|
isEqualToString:installation.firebaseInstallationID]) {
|
|
|
|
|
[self postFIDDidChangeNotification];
|
|
|
|
|
}
|
|
|
|
|
return registeredInstallation;
|
2019-07-17 20:07:35 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 13:47:58 -04:00
|
|
|
- (BOOL)doesRegistrationErrorRequireConfigChange:(NSError *)error {
|
|
|
|
|
FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error;
|
|
|
|
|
if (![HTTPError isKindOfClass:[FIRInstallationsHTTPError class]]) {
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (HTTPError.HTTPResponse.statusCode) {
|
|
|
|
|
// These are the errors that require Firebase configuration change.
|
|
|
|
|
case FIRInstallationsRegistrationHTTPCodeInvalidArgument:
|
|
|
|
|
case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch:
|
|
|
|
|
case FIRInstallationsRegistrationHTTPCodeProjectNotFound:
|
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
#pragma mark - Auth Token
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)getAuthTokenForcingRefresh:(BOOL)forceRefresh {
|
2019-10-15 17:37:07 -04:00
|
|
|
if (forceRefresh || [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise] != nil) {
|
2019-07-17 20:07:35 -04:00
|
|
|
return [self.authTokenForcingRefreshPromiseCache getExistingPendingOrCreateNewPromise];
|
|
|
|
|
} else {
|
|
|
|
|
return [self.authTokenPromiseCache getExistingPendingOrCreateNewPromise];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)installationWithValidAuthTokenForcingRefresh:
|
|
|
|
|
(BOOL)forceRefresh {
|
2019-07-22 16:00:32 -04:00
|
|
|
FIRLogDebug(kFIRLoggerInstallations, kFIRInstallationsMessageCodeNewGetAuthTokenOperationCreated,
|
|
|
|
|
@"-[FIRInstallationsIDController installationWithValidAuthTokenForcingRefresh:%@], "
|
|
|
|
|
@"appName: %@",
|
|
|
|
|
@(forceRefresh), self.appName);
|
|
|
|
|
|
2020-01-06 19:02:56 -05:00
|
|
|
return [self getInstallationItem]
|
2019-07-22 16:00:32 -04:00
|
|
|
.then(^FBLPromise<FIRInstallationsItem *> *(FIRInstallationsItem *installation) {
|
|
|
|
|
return [self registerInstallationIfNeeded:installation];
|
2019-07-17 20:07:35 -04:00
|
|
|
})
|
|
|
|
|
.then(^id(FIRInstallationsItem *registeredInstallation) {
|
|
|
|
|
BOOL isTokenExpiredOrExpiresSoon =
|
|
|
|
|
[registeredInstallation.authToken.expirationDate timeIntervalSinceDate:[NSDate date]] <
|
|
|
|
|
kFIRInstallationsTokenExpirationThreshold;
|
|
|
|
|
if (forceRefresh || isTokenExpiredOrExpiresSoon) {
|
2019-10-23 14:22:28 -04:00
|
|
|
return [self refreshAuthTokenForInstallation:registeredInstallation];
|
2019-07-17 20:07:35 -04:00
|
|
|
} else {
|
|
|
|
|
return registeredInstallation;
|
|
|
|
|
}
|
|
|
|
|
})
|
2019-10-15 17:37:07 -04:00
|
|
|
.recover(^id(NSError *error) {
|
|
|
|
|
return [self regenerateFIDOnRefreshTokenErrorIfNeeded:error];
|
2019-07-17 20:07:35 -04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-23 14:22:28 -04:00
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)refreshAuthTokenForInstallation:
|
|
|
|
|
(FIRInstallationsItem *)installation {
|
2020-08-10 09:36:04 -04:00
|
|
|
// Check for backoff.
|
|
|
|
|
if (![self.backoffController isNextRequestAllowed]) {
|
|
|
|
|
return [FIRInstallationsErrorUtil
|
|
|
|
|
rejectedPromiseWithError:[FIRInstallationsErrorUtil backoffIntervalWaitError]];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [[[self.APIService refreshAuthTokenForInstallation:installation]
|
2019-10-23 14:22:28 -04:00
|
|
|
then:^id _Nullable(FIRInstallationsItem *_Nullable refreshedInstallation) {
|
2020-08-10 09:36:04 -04:00
|
|
|
[self updateBackoffWithSuccess:YES APIError:nil];
|
2019-10-23 14:22:28 -04:00
|
|
|
return [self saveInstallation:refreshedInstallation];
|
2020-08-10 09:36:04 -04:00
|
|
|
}] recover:^id _Nullable(NSError *_Nonnull error) {
|
|
|
|
|
// Pass the error to the backoff controller.
|
|
|
|
|
[self updateBackoffWithSuccess:NO APIError:error];
|
|
|
|
|
return error;
|
|
|
|
|
}];
|
2019-10-23 14:22:28 -04:00
|
|
|
}
|
|
|
|
|
|
2019-10-15 17:37:07 -04:00
|
|
|
- (id)regenerateFIDOnRefreshTokenErrorIfNeeded:(NSError *)error {
|
|
|
|
|
if (![error isKindOfClass:[FIRInstallationsHTTPError class]]) {
|
|
|
|
|
// No recovery possible. Return the same error.
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIRInstallationsHTTPError *HTTPError = (FIRInstallationsHTTPError *)error;
|
|
|
|
|
switch (HTTPError.HTTPResponse.statusCode) {
|
|
|
|
|
case FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication:
|
|
|
|
|
case FIRInstallationsAuthTokenHTTPCodeFIDNotFound:
|
|
|
|
|
// The stored installation was damaged or blocked by the server.
|
|
|
|
|
// Delete the stored installation then generate and register a new one.
|
2020-01-06 19:02:56 -05:00
|
|
|
return [self getInstallationItem]
|
2019-10-15 17:37:07 -04:00
|
|
|
.then(^FBLPromise<NSNull *> *(FIRInstallationsItem *installation) {
|
|
|
|
|
return [self deleteInstallationLocally:installation];
|
|
|
|
|
})
|
|
|
|
|
.then(^FBLPromise<FIRInstallationsItem *> *(id result) {
|
|
|
|
|
return [self installationWithValidAuthTokenForcingRefresh:NO];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// No recovery possible. Return the same error.
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
#pragma mark - Delete FID
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<NSNull *> *)deleteInstallation {
|
|
|
|
|
return [self.deleteInstallationPromiseCache getExistingPendingOrCreateNewPromise];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<NSNull *> *)createDeleteInstallationPromise {
|
2019-07-22 16:00:32 -04:00
|
|
|
FIRLogDebug(kFIRLoggerInstallations,
|
|
|
|
|
kFIRInstallationsMessageCodeNewDeleteInstallationOperationCreated, @"%s, appName: %@",
|
|
|
|
|
__PRETTY_FUNCTION__, self.appName);
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
// Check for ongoing requests first, if there is no a request, then check local storage for
|
|
|
|
|
// existing installation.
|
|
|
|
|
FBLPromise<FIRInstallationsItem *> *currentInstallationPromise =
|
|
|
|
|
[self mostRecentInstallationOperation] ?: [self getStoredInstallation];
|
|
|
|
|
|
|
|
|
|
return currentInstallationPromise
|
|
|
|
|
.then(^id(FIRInstallationsItem *installation) {
|
|
|
|
|
return [self sendDeleteInstallationRequestIfNeeded:installation];
|
|
|
|
|
})
|
|
|
|
|
.then(^id(FIRInstallationsItem *installation) {
|
|
|
|
|
// Remove the installation from the local storage.
|
2019-10-15 17:37:07 -04:00
|
|
|
return [self deleteInstallationLocally:installation];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<NSNull *> *)deleteInstallationLocally:(FIRInstallationsItem *)installation {
|
|
|
|
|
return [self.installationsStore removeInstallationForAppID:installation.appID
|
|
|
|
|
appName:installation.firebaseAppName]
|
2019-07-24 16:24:20 -04:00
|
|
|
.then(^FBLPromise<NSNull *> *(NSNull *result) {
|
|
|
|
|
return [self deleteExistingIIDIfNeeded];
|
|
|
|
|
})
|
2019-07-17 20:07:35 -04:00
|
|
|
.then(^NSNull *(NSNull *result) {
|
2019-07-24 12:48:23 -04:00
|
|
|
[self postFIDDidChangeNotification];
|
2019-07-17 20:07:35 -04:00
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (FBLPromise<FIRInstallationsItem *> *)sendDeleteInstallationRequestIfNeeded:
|
|
|
|
|
(FIRInstallationsItem *)installation {
|
|
|
|
|
switch (installation.registrationStatus) {
|
|
|
|
|
case FIRInstallationStatusUnknown:
|
|
|
|
|
case FIRInstallationStatusUnregistered:
|
|
|
|
|
// The installation is not registered, so it is safe to be deleted as is, so return early.
|
|
|
|
|
return [FBLPromise resolvedWith:installation];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FIRInstallationStatusRegistered:
|
|
|
|
|
// Proceed to de-register the installation on the server.
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [self.APIService deleteInstallation:installation].recover(^id(NSError *APIError) {
|
|
|
|
|
if ([FIRInstallationsErrorUtil isAPIError:APIError withHTTPCode:404]) {
|
|
|
|
|
// The installation was not found on the server.
|
|
|
|
|
// Return success.
|
|
|
|
|
return installation;
|
|
|
|
|
} else {
|
|
|
|
|
// Re-throw the error otherwise.
|
|
|
|
|
return APIError;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 16:24:20 -04:00
|
|
|
- (FBLPromise<NSNull *> *)deleteExistingIIDIfNeeded {
|
|
|
|
|
if ([self isDefaultApp]) {
|
|
|
|
|
return [self.IIDStore deleteExistingIID];
|
|
|
|
|
} else {
|
|
|
|
|
return [FBLPromise resolvedWith:[NSNull null]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
- (nullable FBLPromise<FIRInstallationsItem *> *)mostRecentInstallationOperation {
|
|
|
|
|
return [self.authTokenForcingRefreshPromiseCache getExistingPendingPromise]
|
|
|
|
|
?: [self.authTokenPromiseCache getExistingPendingPromise]
|
|
|
|
|
?: [self.getInstallationPromiseCache getExistingPendingPromise];
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 09:36:04 -04:00
|
|
|
#pragma mark - Backoff
|
|
|
|
|
|
|
|
|
|
- (void)updateBackoffWithSuccess:(BOOL)success APIError:(nullable NSError *)APIError {
|
|
|
|
|
if (success) {
|
|
|
|
|
[self.backoffController registerEvent:FIRInstallationsBackoffEventSuccess];
|
|
|
|
|
} else if ([APIError isKindOfClass:[FIRInstallationsHTTPError class]]) {
|
|
|
|
|
FIRInstallationsHTTPError *HTTPResponseError = (FIRInstallationsHTTPError *)APIError;
|
|
|
|
|
NSInteger statusCode = HTTPResponseError.HTTPResponse.statusCode;
|
|
|
|
|
|
|
|
|
|
if (statusCode == FIRInstallationsAuthTokenHTTPCodeInvalidAuthentication ||
|
|
|
|
|
statusCode == FIRInstallationsAuthTokenHTTPCodeFIDNotFound) {
|
|
|
|
|
// These errors are explicitly excluded because they are handled by FIS SDK itself so don't
|
|
|
|
|
// require backoff.
|
|
|
|
|
} else if (statusCode == 400 || statusCode == 403) { // Explicitly unrecoverable errors.
|
|
|
|
|
[self.backoffController registerEvent:FIRInstallationsBackoffEventUnrecoverableFailure];
|
|
|
|
|
} else if (statusCode == 429 ||
|
|
|
|
|
(statusCode >= 500 && statusCode < 600)) { // Explicitly recoverable errors.
|
|
|
|
|
[self.backoffController registerEvent:FIRInstallationsBackoffEventRecoverableFailure];
|
|
|
|
|
} else { // Treat all unknown errors as recoverable.
|
|
|
|
|
[self.backoffController registerEvent:FIRInstallationsBackoffEventRecoverableFailure];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the error class is not `FIRInstallationsHTTPError` it indicates a connection error. Such
|
|
|
|
|
// errors should not change backoff interval.
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 12:48:23 -04:00
|
|
|
#pragma mark - Notifications
|
|
|
|
|
|
|
|
|
|
- (void)postFIDDidChangeNotification {
|
|
|
|
|
[[NSNotificationCenter defaultCenter]
|
|
|
|
|
postNotificationName:FIRInstallationIDDidChangeNotification
|
|
|
|
|
object:nil
|
|
|
|
|
userInfo:@{kFIRInstallationIDDidChangeNotificationAppNameKey : self.appName}];
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 16:24:20 -04:00
|
|
|
#pragma mark - Default App
|
|
|
|
|
|
|
|
|
|
- (BOOL)isDefaultApp {
|
|
|
|
|
return [self.appName isEqualToString:kFIRDefaultAppName];
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-15 08:55:43 -04:00
|
|
|
#pragma mark - Keychain
|
|
|
|
|
|
|
|
|
|
+ (NSString *)keychainServiceWithAppID:(NSString *)appID {
|
|
|
|
|
#if TARGET_OS_MACCATALYST || TARGET_OS_OSX
|
|
|
|
|
// We need to keep service name unique per application on macOS.
|
|
|
|
|
// Applications on macOS may request access to Keychain items stored by other applications. It
|
|
|
|
|
// means that when the app looks up for a relevant Keychain item in the service scope it will
|
|
|
|
|
// request user password to grant access to the Keychain if there are other Keychain items from
|
|
|
|
|
// other applications stored under the same Keychain Service.
|
|
|
|
|
return [kKeychainService stringByAppendingFormat:@".%@", appID];
|
|
|
|
|
#else
|
|
|
|
|
// Use a constant Keychain service for non-macOS because:
|
|
|
|
|
// 1. Keychain items cannot be shared between apps until configured specifically so the service
|
|
|
|
|
// name collisions are not a concern
|
|
|
|
|
// 2. We don't want to change the service name to avoid doing a migration.
|
|
|
|
|
return kKeychainService;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-17 20:07:35 -04:00
|
|
|
@end
|