FIRAppCheckTokenRefresher.m 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /*
  2. * Copyright 2021 Google LLC
  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. #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTokenRefresher.h"
  17. #import "FirebaseAppCheck/Sources/Core/FIRAppCheckSettings.h"
  18. #import "FirebaseAppCheck/Sources/Core/TokenRefresh/FIRAppCheckTimer.h"
  19. NS_ASSUME_NONNULL_BEGIN
  20. static const NSTimeInterval kInitialBackoffTimeInterval = 30;
  21. static const NSTimeInterval kMaximumBackoffTimeInterval = 16 * 60;
  22. @interface FIRAppCheckTokenRefresher ()
  23. @property(nonatomic, readonly) dispatch_queue_t refreshQueue;
  24. @property(nonatomic, readonly) id<FIRAppCheckSettingsProtocol> settings;
  25. @property(nonatomic, readonly) FIRTimerProvider timerProvider;
  26. @property(atomic, nullable) id<FIRAppCheckTimerProtocol> timer;
  27. @property(atomic) NSUInteger retryCount;
  28. @property(nonatomic, nullable) NSDate *initialTokenExpirationDate;
  29. @property(nonatomic, readonly) NSTimeInterval tokenExpirationThreshold;
  30. @end
  31. @implementation FIRAppCheckTokenRefresher
  32. @synthesize tokenRefreshHandler = _tokenRefreshHandler;
  33. - (instancetype)initWithTokenExpirationDate:(NSDate *)tokenExpirationDate
  34. tokenExpirationThreshold:(NSTimeInterval)tokenExpirationThreshold
  35. timerProvider:(FIRTimerProvider)timerProvider
  36. settings:(id<FIRAppCheckSettingsProtocol>)settings {
  37. self = [super init];
  38. if (self) {
  39. _refreshQueue =
  40. dispatch_queue_create("com.firebase.FIRAppCheckTokenRefresher", DISPATCH_QUEUE_SERIAL);
  41. _tokenExpirationThreshold = tokenExpirationThreshold;
  42. _initialTokenExpirationDate = tokenExpirationDate;
  43. _timerProvider = timerProvider;
  44. _settings = settings;
  45. }
  46. return self;
  47. }
  48. - (instancetype)initWithTokenExpirationDate:(NSDate *)tokenExpirationDate
  49. tokenExpirationThreshold:(NSTimeInterval)tokenExpirationThreshold
  50. settings:(id<FIRAppCheckSettingsProtocol>)settings {
  51. return [self initWithTokenExpirationDate:tokenExpirationDate
  52. tokenExpirationThreshold:tokenExpirationThreshold
  53. timerProvider:[FIRAppCheckTimer timerProvider]
  54. settings:settings];
  55. }
  56. - (void)dealloc {
  57. [self cancelTimer];
  58. }
  59. - (void)setTokenRefreshHandler:(FIRAppCheckTokenRefreshBlock)tokenRefreshHandler {
  60. @synchronized(self) {
  61. _tokenRefreshHandler = tokenRefreshHandler;
  62. // Check if handler is being set for the first time and if yes then schedule first refresh.
  63. if (tokenRefreshHandler && self.initialTokenExpirationDate &&
  64. self.settings.isTokenAutoRefreshEnabled) {
  65. NSDate *initialTokenExpirationDate = self.initialTokenExpirationDate;
  66. self.initialTokenExpirationDate = nil;
  67. [self scheduleWithTokenExpirationDate:initialTokenExpirationDate];
  68. }
  69. }
  70. }
  71. - (FIRAppCheckTokenRefreshBlock)tokenRefreshHandler {
  72. @synchronized(self) {
  73. return _tokenRefreshHandler;
  74. }
  75. }
  76. - (void)updateTokenExpirationDate:(NSDate *)tokenExpirationDate {
  77. if (self.settings.isTokenAutoRefreshEnabled) {
  78. [self scheduleWithTokenExpirationDate:tokenExpirationDate];
  79. }
  80. }
  81. - (void)refresh {
  82. if (self.tokenRefreshHandler == nil) {
  83. return;
  84. }
  85. if (!self.settings.isTokenAutoRefreshEnabled) {
  86. return;
  87. }
  88. __auto_type __weak weakSelf = self;
  89. self.tokenRefreshHandler(^(BOOL success, NSDate *_Nullable tokenExpirationDate) {
  90. __auto_type strongSelf = weakSelf;
  91. [strongSelf tokenRefreshedWithSuccess:success tokenExpirationDate:tokenExpirationDate];
  92. });
  93. }
  94. - (void)tokenRefreshedWithSuccess:(BOOL)success tokenExpirationDate:(NSDate *)tokenExpirationDate {
  95. if (success) {
  96. self.retryCount = 0;
  97. } else {
  98. self.retryCount += 1;
  99. }
  100. [self scheduleWithTokenExpirationDate:tokenExpirationDate ?: [NSDate date]];
  101. }
  102. - (void)scheduleWithTokenExpirationDate:(NSDate *)tokenExpirationDate {
  103. NSDate *refreshDate = [self nextRefreshDateWithTokenExpirationDate:tokenExpirationDate];
  104. [self scheduleRefreshAtDate:refreshDate];
  105. }
  106. - (void)scheduleRefreshAtDate:(NSDate *)refreshDate {
  107. [self cancelTimer];
  108. NSTimeInterval scheduleInSec = [refreshDate timeIntervalSinceNow];
  109. __auto_type __weak weakSelf = self;
  110. dispatch_block_t refreshHandler = ^{
  111. __auto_type strongSelf = weakSelf;
  112. [strongSelf refresh];
  113. };
  114. // Refresh straight away if the refresh time is too close.
  115. if (scheduleInSec <= 0) {
  116. dispatch_async(self.refreshQueue, refreshHandler);
  117. return;
  118. }
  119. self.timer = self.timerProvider(refreshDate, self.refreshQueue, refreshHandler);
  120. }
  121. - (void)cancelTimer {
  122. [self.timer invalidate];
  123. }
  124. #pragma mark - Backoff
  125. - (NSDate *)nextRefreshDateWithTokenExpirationDate:(NSDate *)tokenExpirationDate {
  126. NSDate *targetRefreshDate =
  127. [tokenExpirationDate dateByAddingTimeInterval:-self.tokenExpirationThreshold];
  128. NSTimeInterval scheduleIn = [targetRefreshDate timeIntervalSinceNow];
  129. NSTimeInterval backoffTime = [[self class] backoffTimeForRetryCount:self.retryCount];
  130. if (scheduleIn >= backoffTime) {
  131. return targetRefreshDate;
  132. } else {
  133. return [NSDate dateWithTimeIntervalSinceNow:backoffTime];
  134. }
  135. }
  136. + (NSTimeInterval)backoffTimeForRetryCount:(NSInteger)retryCount {
  137. if (retryCount == 0) {
  138. // No backoff for the first attempt.
  139. return 0;
  140. }
  141. NSTimeInterval exponentialInterval =
  142. kInitialBackoffTimeInterval * pow(2, retryCount - 1) + [self randomMilliseconds];
  143. return MIN(exponentialInterval, kMaximumBackoffTimeInterval);
  144. }
  145. + (NSTimeInterval)randomMilliseconds {
  146. int32_t random_millis = ABS(arc4random() % 1000);
  147. return (double)random_millis * 0.001;
  148. }
  149. @end
  150. NS_ASSUME_NONNULL_END