Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion FirebaseInstallations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# v1.5.1 -- Unreleased
# v1.7.0 -- Unreleased
- [changed] Use ephemeral `NSURLSession` to prevent caching of request/response. (#6226)
- [changed] Backoff added for some error to prevent unnecessary API requests. (#6232)

# v1.5.0 -- M75
- [changed] Functionally neutral source reorganization. (#5832)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import "FirebaseInstallations/Source/Library/Public/FirebaseInstallations/FIRInstallationsErrors.h"

@class FIRInstallationsHTTPError;
@class FBLPromise<ResultType>;

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -45,12 +46,16 @@ void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer);
data:(nullable NSData *)data;
+ (BOOL)isAPIError:(NSError *)error withHTTPCode:(NSInteger)HTTPCode;

+ (NSError *)backoffIntervalWaitError;

/**
* Returns the passed error if it is already in the public domain or a new error with the passed
* error at `NSUnderlyingErrorKey`.
*/
+ (NSError *)publicDomainErrorWithError:(NSError *)error;

+ (FBLPromise *)rejectedPromiseWithError:(NSError *)error;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h"

#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif

NSString *const kFirebaseInstallationsErrorDomain = @"com.firebase.installations";

void FIRInstallationsItemSetErrorToPointer(NSError *error, NSError **pointer) {
Expand Down Expand Up @@ -101,6 +107,12 @@ + (NSError *)networkErrorWithError:(NSError *)error {
underlyingError:error];
}

+ (NSError *)backoffIntervalWaitError {
return [self installationsErrorWithCode:FIRInstallationsErrorCodeServerUnreachable
failureReason:@"Too many server requests."
underlyingError:nil];
}

+ (NSError *)publicDomainErrorWithError:(NSError *)error {
if ([error.domain isEqualToString:kFirebaseInstallationsErrorDomain]) {
return error;
Expand All @@ -121,4 +133,10 @@ + (NSError *)installationsErrorWithCode:(FIRInstallationsErrorCode)code
return [NSError errorWithDomain:kFirebaseInstallationsErrorDomain code:code userInfo:userInfo];
}

+ (FBLPromise *)rejectedPromiseWithError:(NSError *)error {
FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
[rejectedPromise reject:error];
return rejectedPromise;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ typedef NS_ENUM(NSInteger, FIRInstallationsHTTPCodes) {
typedef NS_ENUM(NSInteger, FIRInstallationsRegistrationHTTPCode) {
FIRInstallationsRegistrationHTTPCodeSuccess = 201,
FIRInstallationsRegistrationHTTPCodeInvalidArgument = 400,
FIRInstallationsRegistrationHTTPCodeInvalidAPIKey = 401,
FIRInstallationsRegistrationHTTPCodeAPIKeyToProjectIDMismatch = 403,
FIRInstallationsRegistrationHTTPCodeProjectNotFound = 404,
FIRInstallationsRegistrationHTTPCodeTooManyRequests = 429,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsErrorUtil.h"
#import "FirebaseInstallations/Source/Library/Errors/FIRInstallationsHTTPError.h"
#import "FirebaseInstallations/Source/Library/FIRInstallationsLogger.h"
#import "FirebaseInstallations/Source/Library/InstallationsAPI/FIRInstallationsItem+RegisterInstallationAPI.h"

Expand Down Expand Up @@ -333,7 +334,8 @@ - (instancetype)initWithURLSession:(NSURLSession *)URLSession
return [FBLPromise attempts:1
delay:1
condition:^BOOL(NSInteger remainingAttempts, NSError *_Nonnull error) {
return [FIRInstallationsErrorUtil isAPIError:error withHTTPCode:500];
return [FIRInstallationsErrorUtil isAPIError:error
withHTTPCode:FIRInstallationsHTTPCodesServerInternalError];
}
retry:^id _Nullable {
return [self URLRequestPromise:request];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2020 Google LLC
*
* 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.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/** A block returning current date. */
typedef NSDate *_Nonnull (^FIRCurrentDateProvider)(void);

/** The function returns a `FIRCurrentDateProvider` block that returns a real current date. */
FIRCurrentDateProvider FIRRealCurrentDateProvider(void);

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* 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.
*/

#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h"

FIRCurrentDateProvider FIRRealCurrentDateProvider(void) {
return ^NSDate *(void) {
return [NSDate date];
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Google LLC
*
* 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.
*/

#import <Foundation/Foundation.h>

#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRCurrentDateProvider.h"

NS_ASSUME_NONNULL_BEGIN

typedef NS_ENUM(NSInteger, FIRInstallationsBackoffEvent) {
FIRInstallationsBackoffEventSuccess,
FIRInstallationsBackoffEventRecoverableFailure,
FIRInstallationsBackoffEventUnrecoverableFailure
};

/** The protocol defines API for a class that encapsulates backoff logic that prevents the SDK from
* sending unnecessary server requests. See API docs for the methods for more details. */

@protocol FIRInstallationsBackoffControllerProtocol <NSObject>

/** The client must call the method each time a protected server request succeeds of fails. It will
* affect the `isNextRequestAllowed` method result for the current time, e.g. when 3 recoverable
* errors were logged in a row, then `isNextRequestAllowed` will return `YES` only in `pow(2, 3)`
* seconds. */
- (void)registerEvent:(FIRInstallationsBackoffEvent)event;

/** Returns if sending a next protected is recommended based on the time and the sequence of logged
* events and the current time. See also `registerEvent:`. */
- (BOOL)isNextRequestAllowed;

@end

/** An implementation of `FIRInstallationsBackoffControllerProtocol` with exponential backoff for
* recoverable errors and constant backoff for recoverable errors. */
@interface FIRInstallationsBackoffController : NSObject <FIRInstallationsBackoffControllerProtocol>

- (instancetype)initWithCurrentDateProvider:(FIRCurrentDateProvider)currentDateProvider;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright 2020 Google LLC
*
* 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.
*/

#import "FirebaseInstallations/Source/Library/InstallationsIDController/FIRInstallationsBackoffController.h"

static const NSTimeInterval k24Hours = 24 * 60 * 60;
static const NSTimeInterval k30Minutes = 30 * 60;

/** The class represents `FIRInstallationsBackoffController` sate required to calculate next allowed
request time. The properties of the class are intentionally immutable because changing them
separately leads to an inconsistent state. */
@interface FIRInstallationsBackoffEventData : NSObject

@property(nonatomic, readonly) FIRInstallationsBackoffEvent eventType;
@property(nonatomic, readonly) NSDate *lastEventDate;
@property(nonatomic, readonly) NSInteger eventCount;

@property(nonatomic, readonly) NSTimeInterval backoffTimeInterval;

@end

@implementation FIRInstallationsBackoffEventData

- (instancetype)initWithEvent:(FIRInstallationsBackoffEvent)eventType
lastEventDate:(NSDate *)lastEventDate
eventCount:(NSInteger)eventCount {
self = [super init];
if (self) {
_eventType = eventType;
_lastEventDate = lastEventDate;
_eventCount = eventCount;

_backoffTimeInterval = [[self class] backoffTimeIntervalWithEvent:eventType
eventCount:eventCount];
}
return self;
}

+ (NSTimeInterval)backoffTimeIntervalWithEvent:(FIRInstallationsBackoffEvent)eventType
eventCount:(NSInteger)eventCount {
switch (eventType) {
case FIRInstallationsBackoffEventSuccess:
return 0;
break;

case FIRInstallationsBackoffEventRecoverableFailure:
return [self recoverableErrorBackoffTimeForAttemptNumber:eventCount];
break;

case FIRInstallationsBackoffEventUnrecoverableFailure:
return k24Hours;
break;
}
}

+ (NSTimeInterval)recoverableErrorBackoffTimeForAttemptNumber:(NSInteger)attemptNumber {
NSTimeInterval exponentialInterval = pow(2, attemptNumber) + [self randomMilliseconds];
return MIN(exponentialInterval, k30Minutes);
}

+ (NSTimeInterval)randomMilliseconds {
int32_t random_millis = ABS(arc4random() % 1000);
return (double)random_millis * 0.001;
}

@end

@interface FIRInstallationsBackoffController ()

@property(nonatomic, readonly) FIRCurrentDateProvider currentDateProvider;

@property(nonatomic, nullable) FIRInstallationsBackoffEventData *lastEventData;

@end

@implementation FIRInstallationsBackoffController

- (instancetype)init {
return [self initWithCurrentDateProvider:FIRRealCurrentDateProvider()];
}

- (instancetype)initWithCurrentDateProvider:(FIRCurrentDateProvider)currentDateProvider {
self = [super init];
if (self) {
_currentDateProvider = [currentDateProvider copy];
}
return self;
}

- (BOOL)isNextRequestAllowed {
@synchronized(self) {
if (self.lastEventData == nil) {
return YES;
}

NSTimeInterval timeSinceLastEvent =
[self.currentDateProvider() timeIntervalSinceDate:self.lastEventData.lastEventDate];
return timeSinceLastEvent >= self.lastEventData.backoffTimeInterval;
}
}

- (void)registerEvent:(FIRInstallationsBackoffEvent)event {
@synchronized(self) {
// Event of the same type as was registered before.
if (self.lastEventData && self.lastEventData.eventType == event) {
self.lastEventData = [[FIRInstallationsBackoffEventData alloc]
initWithEvent:event
lastEventDate:self.currentDateProvider()
eventCount:self.lastEventData.eventCount + 1];
} else { // A different event.
self.lastEventData =
[[FIRInstallationsBackoffEventData alloc] initWithEvent:event
lastEventDate:self.currentDateProvider()
eventCount:1];
}
}
}

@end
Loading