Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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 "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h"
18
 
19
#import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
20
#import "FirebaseMessaging/Sources/FIRMessagingDefines.h"
21
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
22
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
23
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h"
24
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.h"
25
 
26
// Max time interval between checkin retry in seconds.
27
static const int64_t kMaxCheckinRetryIntervalInSeconds = 1 << 5;
28
 
29
@interface FIRMessagingAuthService ()
30
 
31
// Used to retrieve and cache the checkin info to disk and Keychain.
32
@property(nonatomic, readwrite, strong) FIRMessagingCheckinStore *checkinStore;
33
// Used to perform single checkin fetches.
34
@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService;
35
// The current checkin info. It will be compared to what is retrieved to determine whether it is
36
// different than what is in the cache.
37
@property(nonatomic, readwrite, strong) FIRMessagingCheckinPreferences *checkinPreferences;
38
 
39
// This array will track multiple handlers waiting for checkin to be performed. When a checkin
40
// request completes, all the handlers will be notified.
41
// Changes to the checkinHandlers array should happen in a thread-safe manner.
42
@property(nonatomic, readonly, strong)
43
    NSMutableArray<FIRMessagingDeviceCheckinCompletion> *checkinHandlers;
44
 
45
// This is set to true if there is a checkin request in-flight.
46
@property(atomic, readwrite, assign) BOOL isCheckinInProgress;
47
// This timer is used a perform checkin retries. It is cancellable.
48
@property(atomic, readwrite, strong) NSTimer *scheduledCheckinTimer;
49
// The number of times checkin has been retried during a scheduled checkin.
50
@property(atomic, readwrite, assign) int checkinRetryCount;
51
 
52
@end
53
 
54
@implementation FIRMessagingAuthService
55
 
56
- (instancetype)init {
57
  self = [super init];
58
  if (self) {
59
    _checkinStore = [[FIRMessagingCheckinStore alloc] init];
60
    _checkinPreferences = [_checkinStore cachedCheckinPreferences];
61
    _checkinService = [[FIRMessagingCheckinService alloc] init];
62
    _checkinHandlers = [[NSMutableArray alloc] init];
63
  }
64
  return self;
65
}
66
 
67
- (void)dealloc {
68
  [_scheduledCheckinTimer invalidate];
69
}
70
 
71
#pragma mark - Schedule Checkin
72
 
73
- (BOOL)hasCheckinPlist {
74
  return [_checkinStore hasCheckinPlist];
75
}
76
 
77
- (void)scheduleCheckin:(BOOL)immediately {
78
  // Checkin is still valid, so a remote checkin is not required.
79
  if ([self.checkinPreferences hasValidCheckinInfo]) {
80
    return;
81
  }
82
 
83
  // Checkin is already scheduled, so this (non-immediate) request can be ignored.
84
  if (!immediately && [self.scheduledCheckinTimer isValid]) {
85
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthService000,
86
                            @"Checkin sync already scheduled. Will not schedule.");
87
    return;
88
  }
89
 
90
  if (immediately) {
91
    [self performScheduledCheckin];
92
  } else {
93
    int64_t checkinRetryDuration = [self calculateNextCheckinRetryIntervalInSeconds];
94
    [self startCheckinTimerWithDuration:(NSTimeInterval)checkinRetryDuration];
95
  }
96
}
97
 
98
- (void)startCheckinTimerWithDuration:(NSTimeInterval)timerDuration {
99
  self.scheduledCheckinTimer =
100
      [NSTimer scheduledTimerWithTimeInterval:timerDuration
101
                                       target:self
102
                                     selector:@selector(onScheduledCheckinTimerFired:)
103
                                     userInfo:nil
104
                                      repeats:NO];
105
  // Add some tolerance to the timer, to allow iOS to be more flexible with this timer
106
  self.scheduledCheckinTimer.tolerance = 0.5;
107
}
108
 
109
- (void)clearScheduledCheckinTimer {
110
  [self.scheduledCheckinTimer invalidate];
111
  self.scheduledCheckinTimer = nil;
112
}
113
 
114
- (void)onScheduledCheckinTimerFired:(NSTimer *)timer {
115
  [self performScheduledCheckin];
116
}
117
 
118
- (void)performScheduledCheckin {
119
  // No checkin scheduled as of now.
120
  [self clearScheduledCheckinTimer];
121
 
122
  // Checkin is still valid, so a remote checkin is not required.
123
  if ([self.checkinPreferences hasValidCheckinInfo]) {
124
    return;
125
  }
126
 
127
  FIRMessaging_WEAKIFY(self);
128
  [self fetchCheckinInfoWithHandler:^(FIRMessagingCheckinPreferences *_Nullable checkinPreferences,
129
                                      NSError *_Nullable error) {
130
    FIRMessaging_STRONGIFY(self);
131
    self.checkinRetryCount++;
132
 
133
    if (error) {
134
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthService001, @"Checkin error %@.", error);
135
 
136
      dispatch_async(dispatch_get_main_queue(), ^{
137
        // Schedule another checkin
138
        [self scheduleCheckin:NO];
139
      });
140
 
141
    } else {
142
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthService002, @"Checkin success.");
143
    }
144
  }];
145
}
146
 
147
- (int64_t)calculateNextCheckinRetryIntervalInSeconds {
148
  // persistent failures can lead to overflow prevent that.
149
  if (self.checkinRetryCount >= 10) {
150
    return kMaxCheckinRetryIntervalInSeconds;
151
  }
152
  return MIN(1 << self.checkinRetryCount, kMaxCheckinRetryIntervalInSeconds);
153
}
154
 
155
#pragma mark - Checkin Service
156
 
157
- (BOOL)hasValidCheckinInfo {
158
  return [self.checkinPreferences hasValidCheckinInfo];
159
}
160
 
161
- (void)fetchCheckinInfoWithHandler:(nullable FIRMessagingDeviceCheckinCompletion)handler {
162
  // Perform any changes to self.checkinHandlers and _isCheckinInProgress in a thread-safe way.
163
  @synchronized(self) {
164
    [self.checkinHandlers addObject:[handler copy]];
165
 
166
    if (_isCheckinInProgress) {
167
      // Nothing more to do until our checkin request is done
168
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthServiceCheckinInProgress,
169
                              @"Checkin is in progress\n");
170
      return;
171
    }
172
  }
173
 
174
  // Checkin is still valid, so a remote checkin is not required.
175
  if ([self.checkinPreferences hasValidCheckinInfo]) {
176
    [self notifyCheckinHandlersWithCheckin:self.checkinPreferences error:nil];
177
    return;
178
  }
179
 
180
  @synchronized(self) {
181
    _isCheckinInProgress = YES;
182
  }
183
  [self.checkinService
184
      checkinWithExistingCheckin:self.checkinPreferences
185
                      completion:^(FIRMessagingCheckinPreferences *checkinPreferences,
186
                                   NSError *error) {
187
                        @synchronized(self) {
188
                          self->_isCheckinInProgress = NO;
189
                        }
190
                        if (error) {
191
                          FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthService003,
192
                                                  @"Failed to checkin device %@", error);
193
                          [self notifyCheckinHandlersWithCheckin:nil error:error];
194
                          return;
195
                        }
196
 
197
                        FIRMessagingLoggerDebug(kFIRMessagingMessageCodeAuthService004,
198
                                                @"Successfully got checkin credentials");
199
                        BOOL hasSameCachedPreferences =
200
                            [self cachedCheckinMatchesCheckin:checkinPreferences];
201
                        checkinPreferences.hasPreCachedAuthCredentials = hasSameCachedPreferences;
202
 
203
                        // Update to the most recent checkin preferences
204
                        self.checkinPreferences = checkinPreferences;
205
 
206
                        // Save the checkin info to disk
207
                        // Keychain might not be accessible, so confirm that checkin preferences can
208
                        // be saved
209
                        [self->_checkinStore
210
                            saveCheckinPreferences:checkinPreferences
211
                                           handler:^(NSError *checkinSaveError) {
212
                                             if (checkinSaveError && !hasSameCachedPreferences) {
213
                                               // The checkin info was new, but it couldn't be
214
                                               // written to the Keychain. Delete any stuff that was
215
                                               // cached in memory. This doesn't delete any
216
                                               // previously persisted preferences.
217
                                               FIRMessagingLoggerError(
218
                                                   kFIRMessagingMessageCodeService004,
219
                                                   @"Unable to save checkin info, resetting "
220
                                                   @"checkin preferences "
221
                                                    "in memory.");
222
                                               [checkinPreferences reset];
223
                                               [self
224
                                                   notifyCheckinHandlersWithCheckin:nil
225
                                                                              error:
226
                                                                                  checkinSaveError];
227
                                             } else {
228
                                               // The checkin is either new, or it was the same (and
229
                                               // it couldn't be saved). Either way, report that the
230
                                               // checkin preferences were received successfully.
231
                                               [self notifyCheckinHandlersWithCheckin:
232
                                                         checkinPreferences
233
                                                                                error:nil];
234
                                               if (!hasSameCachedPreferences) {
235
                                                 // Checkin is new.
236
                                                 // Notify any listeners that might be waiting for
237
                                                 // checkin to be fetched, such as Firebase
238
                                                 // Messaging (for its MCS connection).
239
                                                 dispatch_async(dispatch_get_main_queue(), ^{
240
                                                   [[NSNotificationCenter defaultCenter]
241
                                                       postNotificationName:
242
                                                           kFIRMessagingCheckinFetchedNotification
243
                                                                     object:nil];
244
                                                 });
245
                                               }
246
                                             }
247
                                           }];
248
                      }];
249
}
250
 
251
- (FIRMessagingCheckinPreferences *)checkinPreferences {
252
  return _checkinPreferences;
253
}
254
 
255
- (void)stopCheckinRequest {
256
  [self.checkinService stopFetching];
257
}
258
 
259
- (void)resetCheckinWithHandler:(void (^)(NSError *error))handler {
260
  [_checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) {
261
    if (!error) {
262
      self.checkinPreferences = nil;
263
    }
264
    if (handler) {
265
      handler(error);
266
    }
267
  }];
268
}
269
 
270
#pragma mark - Private
271
 
272
/**
273
 *  Goes through the current list of checkin handlers and fires them with the same checkin and/or
274
 *  error info. The checkin handlers will get cleared after.
275
 */
276
- (void)notifyCheckinHandlersWithCheckin:(nullable FIRMessagingCheckinPreferences *)checkin
277
                                   error:(nullable NSError *)error {
278
  @synchronized(self) {
279
    for (FIRMessagingDeviceCheckinCompletion handler in self.checkinHandlers) {
280
      handler(checkin, error);
281
    }
282
    [self.checkinHandlers removeAllObjects];
283
  }
284
}
285
 
286
- (void)setCheckinHandlers:(NSMutableArray<FIRMessagingDeviceCheckinCompletion> *)checkinHandlers {
287
  NSLog(@"%lu", (unsigned long)self.checkinHandlers.count);
288
}
289
 
290
/**
291
 *  Given a |checkin|, it will compare it to the current checkinPreferences to see if the
292
 *  deviceID and secretToken are the same.
293
 */
294
- (BOOL)cachedCheckinMatchesCheckin:(FIRMessagingCheckinPreferences *)checkin {
295
  if (self.checkinPreferences && checkin) {
296
    return ([self.checkinPreferences.deviceID isEqualToString:checkin.deviceID] &&
297
            [self.checkinPreferences.secretToken isEqualToString:checkin.secretToken]);
298
  }
299
  return NO;
300
}
301
@end