-
Notifications
You must be signed in to change notification settings - Fork 1.6k
FirebaseInstallations: CreateInstallation API request error handling #4024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e8bfa8
94d5300
59b6b1d
c4cd42b
6ad0568
99636c6
f54b2ba
e025eaf
a8e0641
58b101d
3785125
7cd74cc
3709315
bbafdf4
2428c14
f2c149e
1db242b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,9 +31,13 @@ | |
#import "FIRInstallationsLogger.h" | ||
#import "FIRInstallationsSingleOperationPromiseCache.h" | ||
#import "FIRInstallationsStore.h" | ||
#import "FIRInstallationsStoredAuthToken.h" | ||
#import "FIRSecureStorage.h" | ||
|
||
#import "FIRInstallationsHTTPError.h" | ||
#import "FIRInstallationsStoredAuthToken.h" | ||
#import "FIRInstallationsStoredRegistrationError.h" | ||
#import "FIRInstallationsStoredRegistrationParameters.h" | ||
|
||
const NSNotificationName FIRInstallationIDDidChangeNotification = | ||
@"FIRInstallationIDDidChangeNotification"; | ||
NSString *const kFIRInstallationIDDidChangeNotificationAppNameKey = | ||
|
@@ -135,9 +139,21 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
__PRETTY_FUNCTION__, self.appName); | ||
|
||
FBLPromise<FIRInstallationsItem *> *installationItemPromise = | ||
[self getStoredInstallation].recover(^id(NSError *error) { | ||
return [self createAndSaveFID]; | ||
}); | ||
[self getStoredInstallation] | ||
.recover(^id(NSError *error) { | ||
return [self createAndSaveFID]; | ||
}) | ||
.then(^id(FIRInstallationsItem *installation) { | ||
// Validate if a previous registration attempt failed with an error requiring Firebase | ||
// configuration changes. | ||
if (installation.registrationStatus == FIRInstallationStatusRegistrationFailed && | ||
[self areInstallationRegistrationParametersEqualToCurrent: | ||
installation.registrationError.registrationParameters]) { | ||
return installation.registrationError.APIError; | ||
} | ||
|
||
return installation; | ||
}); | ||
|
||
// Initiate registration process on success if needed, but return the installation without waiting | ||
// for it. | ||
|
@@ -156,6 +172,7 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
switch (installation.registrationStatus) { | ||
case FIRInstallationStatusUnregistered: | ||
case FIRInstallationStatusRegistered: | ||
case FIRInstallationStatusRegistrationFailed: | ||
isValid = YES; | ||
break; | ||
|
||
|
@@ -201,6 +218,14 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
}); | ||
} | ||
|
||
- (BOOL)areInstallationRegistrationParametersEqualToCurrent: | ||
(FIRInstallationsStoredRegistrationParameters *)parameters { | ||
NSString *APIKey = self.APIService.APIKey; | ||
NSString *projectID = self.APIService.projectID; | ||
return (parameters.APIKey == APIKey || [parameters.APIKey isEqual:APIKey]) && | ||
(parameters.projectID == projectID || [parameters.projectID isEqual:projectID]); | ||
} | ||
|
||
#pragma mark - FID registration | ||
|
||
- (FBLPromise<FIRInstallationsItem *> *)registerInstallationIfNeeded: | ||
|
@@ -212,11 +237,15 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
|
||
case FIRInstallationStatusUnknown: | ||
case FIRInstallationStatusUnregistered: | ||
case FIRInstallationStatusRegistrationFailed: | ||
// Registration required. Proceed. | ||
break; | ||
} | ||
|
||
return [self.APIService registerInstallation:installation] | ||
.recover(^id(NSError *error) { | ||
return [self handleRegistrationRequestError:error installation:installation]; | ||
}) | ||
.then(^id(FIRInstallationsItem *registeredInstallation) { | ||
// Expected successful result: @[FIRInstallationsItem *registeredInstallation, NSNull] | ||
return [FBLPromise all:@[ | ||
|
@@ -234,6 +263,56 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
}); | ||
} | ||
|
||
- (FBLPromise<FIRInstallationsItem *> *)handleRegistrationRequestError:(NSError *)error | ||
installation: | ||
(FIRInstallationsItem *)installation { | ||
if ([self doesRegistrationErrorRequireConfigChange:error]) { | ||
FIRLogError(kFIRLoggerInstallations, kFIRInstallationsMessageCodeInvalidFirebaseConfiguration, | ||
@"Firebase Installation registration failed for app with name: %@, error: " | ||
@"%@\nPlease make sure you use valid GoogleService-Info.plist", | ||
self.appName, error); | ||
|
||
FIRInstallationsItem *failedInstallation = [installation copy]; | ||
[failedInstallation updateWithRegistrationError:error | ||
registrationParameters:[self currentRegistrationParameters]]; | ||
|
||
// Save the error and then fail with the API error. | ||
return | ||
[self.installationsStore saveInstallation:failedInstallation].then(^NSError *(id result) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If registration returns with error, why do we still save such installation item? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a design doc link explaining the details (sorry I should've added it before). TL;DR: we would like to store the registration parameters and the error received, so we can return the error without sending the API request. We will send the API request again only if the parameters changed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if the error is because network is not available? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it thanks! |
||
return error; | ||
}); | ||
} | ||
|
||
FBLPromise *errorPromise = [FBLPromise pendingPromise]; | ||
[errorPromise reject:error]; | ||
return errorPromise; | ||
} | ||
|
||
- (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 FIRInstallationsRegistrationHTTPCodeInvalidAPIKey: | ||
case FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch: | ||
case FIRInstallationsRegistrationHTTPCodeProjectNotFound: | ||
return YES; | ||
|
||
default: | ||
return NO; | ||
} | ||
} | ||
|
||
- (FIRInstallationsStoredRegistrationParameters *)currentRegistrationParameters { | ||
return [[FIRInstallationsStoredRegistrationParameters alloc] | ||
initWithAPIKey:self.APIService.APIKey | ||
projectID:self.APIService.projectID]; | ||
} | ||
|
||
#pragma mark - Auth Token | ||
|
||
- (FBLPromise<FIRInstallationsItem *> *)getAuthTokenForcingRefresh:(BOOL)forceRefresh { | ||
|
@@ -321,6 +400,7 @@ - (instancetype)initWithGoogleAppID:(NSString *)appID | |
switch (installation.registrationStatus) { | ||
case FIRInstallationStatusUnknown: | ||
case FIRInstallationStatusUnregistered: | ||
case FIRInstallationStatusRegistrationFailed: | ||
// The installation is not registered, so it is safe to be deleted as is, so return early. | ||
return [FBLPromise resolvedWith:installation]; | ||
break; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parameters.APIKey == APIKey
Is this for comparing the address to be the same as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use
parameters.APIKey == APIKey
to cover the case when they are bothnil
.[parameters.APIKey isEqual:APIKey]
returnsNO
in this case. It is the easiest way to cover all equality cases I came up with, but I am open for suggestions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice! I didn't know that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I didn't pay attention to it until the integration tests failures. Basically if e.g.
parameters.APIKey == nil
then any message to it will returnnil
which is casted toNO
.