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/FIRMessagingTokenManager.h"
18
 
19
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
20
#import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
21
#import "FirebaseMessaging/Sources/FIRMessagingDefines.h"
22
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
23
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
24
#import "FirebaseMessaging/Sources/Token/FIRMessagingAuthKeychain.h"
25
#import "FirebaseMessaging/Sources/Token/FIRMessagingAuthService.h"
26
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h"
27
#import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.h"
28
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenDeleteOperation.h"
29
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenFetchOperation.h"
30
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenInfo.h"
31
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenOperation.h"
32
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenStore.h"
33
 
34
@interface FIRMessagingTokenManager () {
35
  FIRMessagingTokenStore *_tokenStore;
36
  NSString *_defaultFCMToken;
37
}
38
 
39
@property(nonatomic, readwrite, strong) FIRMessagingCheckinStore *checkinStore;
40
@property(nonatomic, readwrite, strong) FIRMessagingAuthService *authService;
41
@property(nonatomic, readonly, strong) NSOperationQueue *tokenOperations;
42
 
43
@property(nonatomic, readwrite, strong) FIRMessagingAPNSInfo *currentAPNSInfo;
44
@property(nonatomic, readwrite) FIRInstallations *installations;
45
 
46
@end
47
 
48
@implementation FIRMessagingTokenManager
49
 
50
- (instancetype)init {
51
  self = [super init];
52
  if (self) {
53
    _tokenStore = [[FIRMessagingTokenStore alloc] init];
54
    _authService = [[FIRMessagingAuthService alloc] init];
55
    [self resetCredentialsIfNeeded];
56
    [self configureTokenOperations];
57
    _installations = [FIRInstallations installations];
58
  }
59
  return self;
60
}
61
 
62
- (void)dealloc {
63
  [self stopAllTokenOperations];
64
}
65
 
66
- (NSString *)tokenAndRequestIfNotExist {
67
  if (!self.fcmSenderID.length) {
68
    return nil;
69
  }
70
 
71
  if (_defaultFCMToken.length) {
72
    return _defaultFCMToken;
73
  }
74
 
75
  FIRMessagingTokenInfo *cachedTokenInfo =
76
      [self cachedTokenInfoWithAuthorizedEntity:self.fcmSenderID
77
                                          scope:kFIRMessagingDefaultTokenScope];
78
  NSString *cachedToken = cachedTokenInfo.token;
79
 
80
  if (cachedToken) {
81
    return cachedToken;
82
  } else {
83
    [self tokenWithAuthorizedEntity:self.fcmSenderID
84
                              scope:kFIRMessagingDefaultTokenScope
85
                            options:[self tokenOptions]
86
                            handler:^(NSString *_Nullable FCMToken, NSError *_Nullable error){
87
 
88
                            }];
89
    return nil;
90
  }
91
}
92
 
93
- (NSString *)defaultFCMToken {
94
  return _defaultFCMToken;
95
}
96
 
97
- (void)postTokenRefreshNotificationWithDefaultFCMToken:(NSString *)defaultFCMToken {
98
  // Should always trigger the token refresh notification when the delegate method is called
99
  // No need to check if the token has changed, it's handled in the notification receiver.
100
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
101
  [center postNotificationName:kFIRMessagingRegistrationTokenRefreshNotification
102
                        object:defaultFCMToken];
103
}
104
 
105
- (void)saveDefaultTokenInfoInKeychain:(NSString *)defaultFcmToken {
106
  if ([self hasTokenChangedFromOldToken:_defaultFCMToken toNewToken:defaultFcmToken]) {
107
    _defaultFCMToken = [defaultFcmToken copy];
108
    FIRMessagingTokenInfo *tokenInfo =
109
        [[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:_fcmSenderID
110
                                                          scope:kFIRMessagingDefaultTokenScope
111
                                                          token:defaultFcmToken
112
                                                     appVersion:FIRMessagingCurrentAppVersion()
113
                                                  firebaseAppID:_firebaseAppID];
114
    tokenInfo.APNSInfo =
115
        [[FIRMessagingAPNSInfo alloc] initWithTokenOptionsDictionary:[self tokenOptions]];
116
 
117
    [self->_tokenStore saveTokenInfoInCache:tokenInfo];
118
  }
119
}
120
 
121
- (BOOL)hasTokenChangedFromOldToken:(NSString *)oldToken toNewToken:(NSString *)newToken {
122
  return oldToken.length != newToken.length ||
123
         (oldToken.length && newToken.length && ![oldToken isEqualToString:newToken]);
124
}
125
 
126
- (NSDictionary *)tokenOptions {
127
  NSDictionary *instanceIDOptions = @{};
128
  NSData *apnsTokenData = self.currentAPNSInfo.deviceToken;
129
  if (apnsTokenData) {
130
    instanceIDOptions = @{
131
      kFIRMessagingTokenOptionsAPNSKey : apnsTokenData,
132
      kFIRMessagingTokenOptionsAPNSIsSandboxKey : @(self.currentAPNSInfo.isSandbox),
133
    };
134
  }
135
 
136
  return instanceIDOptions;
137
}
138
 
139
- (NSString *)deviceAuthID {
140
  return [_authService checkinPreferences].deviceID;
141
}
142
 
143
- (NSString *)secretToken {
144
  return [_authService checkinPreferences].secretToken;
145
}
146
 
147
- (NSString *)versionInfo {
148
  return [_authService checkinPreferences].versionInfo;
149
}
150
 
151
- (void)configureTokenOperations {
152
  _tokenOperations = [[NSOperationQueue alloc] init];
153
  _tokenOperations.name = @"com.google.iid-token-operations";
154
  // For now, restrict the operations to be serial, because in some cases (like if the
155
  // authorized entity and scope are the same), order matters.
156
  // If we have to deal with several different token requests simultaneously, it would be a good
157
  // idea to add some better intelligence around this (performing unrelated token operations
158
  // simultaneously, etc.).
159
  _tokenOperations.maxConcurrentOperationCount = 1;
160
  if ([_tokenOperations respondsToSelector:@selector(qualityOfService)]) {
161
    _tokenOperations.qualityOfService = NSOperationQualityOfServiceUtility;
162
  }
163
}
164
 
165
- (void)tokenWithAuthorizedEntity:(NSString *)authorizedEntity
166
                            scope:(NSString *)scope
167
                          options:(NSDictionary *)options
168
                          handler:(FIRMessagingFCMTokenFetchCompletion)handler {
169
  if (!handler) {
170
    FIRMessagingLoggerError(kFIRMessagingMessageCodeInstanceID000, @"Invalid nil handler");
171
    return;
172
  }
173
 
174
  // Add internal options
175
  NSMutableDictionary *tokenOptions = [NSMutableDictionary dictionary];
176
  if (options.count) {
177
    [tokenOptions addEntriesFromDictionary:options];
178
  }
179
 
180
  if (tokenOptions[kFIRMessagingTokenOptionsAPNSKey] != nil &&
181
      tokenOptions[kFIRMessagingTokenOptionsAPNSIsSandboxKey] == nil) {
182
    // APNS key was given, but server type is missing. Supply the server type with automatic
183
    // checking. This can happen when the token is requested from FCM, which does not include a
184
    // server type during its request.
185
    tokenOptions[kFIRMessagingTokenOptionsAPNSIsSandboxKey] = @(FIRMessagingIsSandboxApp());
186
  }
187
  if (self.firebaseAppID) {
188
    tokenOptions[kFIRMessagingTokenOptionsFirebaseAppIDKey] = self.firebaseAppID;
189
  }
190
 
191
  // comparing enums to ints directly throws a warning
192
  FIRMessagingErrorCode noError = INT_MAX;
193
  FIRMessagingErrorCode errorCode = noError;
194
  if (![authorizedEntity length]) {
195
    errorCode = kFIRMessagingErrorCodeMissingAuthorizedEntity;
196
  } else if (![scope length]) {
197
    errorCode = kFIRMessagingErrorCodeMissingScope;
198
  } else if (!self.installations) {
199
    errorCode = kFIRMessagingErrorCodeMissingFid;
200
  }
201
 
202
  FIRMessagingFCMTokenFetchCompletion newHandler = ^(NSString *token, NSError *error) {
203
    dispatch_async(dispatch_get_main_queue(), ^{
204
      handler(token, error);
205
    });
206
  };
207
 
208
  if (errorCode != noError) {
209
    newHandler(
210
        nil,
211
        [NSError messagingErrorWithCode:errorCode
212
                          failureReason:@"Failed to send token request, missing critical info."]);
213
    return;
214
  }
215
 
216
  FIRMessaging_WEAKIFY(self);
217
  [_authService
218
      fetchCheckinInfoWithHandler:^(FIRMessagingCheckinPreferences *preferences, NSError *error) {
219
        FIRMessaging_STRONGIFY(self);
220
        if (error) {
221
          newHandler(nil, error);
222
          return;
223
        }
224
 
225
        FIRMessaging_WEAKIFY(self);
226
        [self->_installations installationIDWithCompletion:^(NSString *_Nullable identifier,
227
                                                             NSError *_Nullable error) {
228
          FIRMessaging_STRONGIFY(self);
229
 
230
          if (error) {
231
            newHandler(nil, error);
232
          } else {
233
            FIRMessagingTokenInfo *cachedTokenInfo =
234
                [self cachedTokenInfoWithAuthorizedEntity:authorizedEntity scope:scope];
235
            FIRMessagingAPNSInfo *optionsAPNSInfo =
236
                [[FIRMessagingAPNSInfo alloc] initWithTokenOptionsDictionary:tokenOptions];
237
            // Check if APNS Info is changed
238
            if ((!cachedTokenInfo.APNSInfo && !optionsAPNSInfo) ||
239
                [cachedTokenInfo.APNSInfo isEqualToAPNSInfo:optionsAPNSInfo]) {
240
              // check if token is fresh
241
              if ([cachedTokenInfo isFreshWithIID:identifier]) {
242
                newHandler(cachedTokenInfo.token, nil);
243
                return;
244
              }
245
            }
246
            [self fetchNewTokenWithAuthorizedEntity:[authorizedEntity copy]
247
                                              scope:[scope copy]
248
                                         instanceID:identifier
249
                                            options:tokenOptions
250
                                            handler:newHandler];
251
          }
252
        }];
253
      }];
254
}
255
 
256
- (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity
257
                                    scope:(NSString *)scope
258
                               instanceID:(NSString *)instanceID
259
                                  options:(NSDictionary *)options
260
                                  handler:(FIRMessagingFCMTokenFetchCompletion)handler {
261
  FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTokenManager000,
262
                          @"Fetch new token for authorizedEntity: %@, scope: %@", authorizedEntity,
263
                          scope);
264
  FIRMessagingTokenFetchOperation *operation =
265
      [self createFetchOperationWithAuthorizedEntity:authorizedEntity
266
                                               scope:scope
267
                                             options:options
268
                                          instanceID:instanceID];
269
  FIRMessaging_WEAKIFY(self);
270
  FIRMessagingTokenOperationCompletion completion =
271
      ^(FIRMessagingTokenOperationResult result, NSString *_Nullable token,
272
        NSError *_Nullable error) {
273
        FIRMessaging_STRONGIFY(self);
274
        if (error) {
275
          handler(nil, error);
276
          return;
277
        }
278
        if ([self isDefaultTokenWithAuthorizedEntity:authorizedEntity scope:scope]) {
279
          [self postTokenRefreshNotificationWithDefaultFCMToken:token];
280
        }
281
        NSString *firebaseAppID = options[kFIRMessagingTokenOptionsFirebaseAppIDKey];
282
        FIRMessagingTokenInfo *tokenInfo =
283
            [[FIRMessagingTokenInfo alloc] initWithAuthorizedEntity:authorizedEntity
284
                                                              scope:scope
285
                                                              token:token
286
                                                         appVersion:FIRMessagingCurrentAppVersion()
287
                                                      firebaseAppID:firebaseAppID];
288
        tokenInfo.APNSInfo = [[FIRMessagingAPNSInfo alloc] initWithTokenOptionsDictionary:options];
289
 
290
        [self->_tokenStore
291
            saveTokenInfo:tokenInfo
292
                  handler:^(NSError *error) {
293
                    if (!error) {
294
                      // Do not send the token back in case the save was unsuccessful. Since with
295
                      // the new asychronous fetch mechanism this can lead to infinite loops, for
296
                      // example, we will return a valid token even though we weren't able to store
297
                      // it in our cache. The first token will lead to a onTokenRefresh callback
298
                      // wherein the user again calls `getToken` but since we weren't able to save
299
                      // it we won't hit the cache but hit the server again leading to an infinite
300
                      // loop.
301
                      FIRMessagingLoggerDebug(
302
                          kFIRMessagingMessageCodeTokenManager001,
303
                          @"Token fetch successful, token: %@, authorizedEntity: %@, scope:%@",
304
                          token, authorizedEntity, scope);
305
 
306
                      if (handler) {
307
                        handler(token, nil);
308
                      }
309
                    } else {
310
                      if (handler) {
311
                        handler(nil, error);
312
                      }
313
                    }
314
                  }];
315
      };
316
  // Add completion handler, and ensure it's called on the main queue
317
  [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
318
                                    NSString *_Nullable token, NSError *_Nullable error) {
319
    dispatch_async(dispatch_get_main_queue(), ^{
320
      completion(result, token, error);
321
    });
322
  }];
323
  [self.tokenOperations addOperation:operation];
324
}
325
 
326
- (FIRMessagingTokenInfo *)cachedTokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
327
                                                         scope:(NSString *)scope {
328
  FIRMessagingTokenInfo *tokenInfo = [_tokenStore tokenInfoWithAuthorizedEntity:authorizedEntity
329
                                                                          scope:scope];
330
  return tokenInfo;
331
}
332
 
333
- (BOOL)isDefaultTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope {
334
  if (_fcmSenderID.length != authorizedEntity.length) {
335
    return NO;
336
  }
337
  if (![_fcmSenderID isEqualToString:authorizedEntity]) {
338
    return NO;
339
  }
340
  return [scope isEqualToString:kFIRMessagingDefaultTokenScope];
341
}
342
 
343
- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity
344
                                  scope:(NSString *)scope
345
                             instanceID:(NSString *)instanceID
346
                                handler:(FIRMessagingDeleteFCMTokenCompletion)handler {
347
  if ([_tokenStore tokenInfoWithAuthorizedEntity:authorizedEntity scope:scope]) {
348
    [_tokenStore removeTokenWithAuthorizedEntity:authorizedEntity scope:scope];
349
  }
350
  // Does not matter if we cannot find it in the cache. Still make an effort to unregister
351
  // from the server.
352
  FIRMessagingCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
353
  FIRMessagingTokenDeleteOperation *operation =
354
      [self createDeleteOperationWithAuthorizedEntity:authorizedEntity
355
                                                scope:scope
356
                                   checkinPreferences:checkinPreferences
357
                                           instanceID:instanceID
358
                                               action:FIRMessagingTokenActionDeleteToken];
359
 
360
  if (handler) {
361
    [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
362
                                      NSString *_Nullable token, NSError *_Nullable error) {
363
      if ([self isDefaultTokenWithAuthorizedEntity:authorizedEntity scope:scope]) {
364
        [self postTokenRefreshNotificationWithDefaultFCMToken:nil];
365
      }
366
      dispatch_async(dispatch_get_main_queue(), ^{
367
        handler(error);
368
      });
369
    }];
370
  }
371
  [self.tokenOperations addOperation:operation];
372
}
373
 
374
- (void)deleteAllTokensWithHandler:(void (^)(NSError *))handler {
375
  FIRMessaging_WEAKIFY(self);
376
 
377
  [self.installations
378
      installationIDWithCompletion:^(NSString *_Nullable identifier, NSError *_Nullable error) {
379
        FIRMessaging_STRONGIFY(self);
380
        if (error) {
381
          if (handler) {
382
            dispatch_async(dispatch_get_main_queue(), ^{
383
              handler(error);
384
            });
385
          }
386
          return;
387
        }
388
        // delete all tokens
389
        FIRMessagingCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
390
        if (!checkinPreferences) {
391
          // The checkin is already deleted. No need to trigger the token delete operation as client
392
          // no longer has the checkin information for server to delete.
393
          dispatch_async(dispatch_get_main_queue(), ^{
394
            handler(nil);
395
          });
396
          return;
397
        }
398
        FIRMessagingTokenDeleteOperation *operation = [self
399
            createDeleteOperationWithAuthorizedEntity:kFIRMessagingKeychainWildcardIdentifier
400
                                                scope:kFIRMessagingKeychainWildcardIdentifier
401
                                   checkinPreferences:checkinPreferences
402
                                           instanceID:identifier
403
                                               action:FIRMessagingTokenActionDeleteTokenAndIID];
404
        if (handler) {
405
          [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
406
                                            NSString *_Nullable token, NSError *_Nullable error) {
407
            self->_defaultFCMToken = nil;
408
            dispatch_async(dispatch_get_main_queue(), ^{
409
              handler(error);
410
            });
411
          }];
412
        }
413
        [self.tokenOperations addOperation:operation];
414
      }];
415
}
416
 
417
- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler {
418
  [_tokenStore removeAllTokensWithHandler:handler];
419
}
420
 
421
- (void)stopAllTokenOperations {
422
  [self.authService stopCheckinRequest];
423
  [self.tokenOperations cancelAllOperations];
424
}
425
 
426
- (void)deleteWithHandler:(void (^)(NSError *))handler {
427
  FIRMessaging_WEAKIFY(self);
428
  [self deleteAllTokensWithHandler:^(NSError *_Nullable error) {
429
    FIRMessaging_STRONGIFY(self);
430
    if (error) {
431
      handler(error);
432
      return;
433
    }
434
    [self deleteAllTokensLocallyWithHandler:^(NSError *localError) {
435
      [self postTokenRefreshNotificationWithDefaultFCMToken:nil];
436
      self->_defaultFCMToken = nil;
437
      if (localError) {
438
        handler(localError);
439
        return;
440
      }
441
      [self.authService resetCheckinWithHandler:^(NSError *_Nonnull authError) {
442
        handler(authError);
443
      }];
444
    }];
445
  }];
446
}
447
 
448
#pragma mark - CheckinStore
449
 
450
/**
451
 *  Reset the keychain preferences if the app had been deleted earlier and then reinstalled.
452
 *  Keychain preferences are not cleared in the above scenario so explicitly clear them.
453
 *
454
 *  In case of an iCloud backup and restore the Keychain preferences should already be empty
455
 *  since the Keychain items are marked with `*BackupThisDeviceOnly`.
456
 */
457
- (void)resetCredentialsIfNeeded {
458
  BOOL checkinPlistExists = [_authService hasCheckinPlist];
459
  // Checkin info existed in backup excluded plist. Should not be a fresh install.
460
  if (checkinPlistExists) {
461
    return;
462
  }
463
  // Keychain can still exist even if app is uninstalled.
464
  FIRMessagingCheckinPreferences *oldCheckinPreferences = _authService.checkinPreferences;
465
 
466
  if (!oldCheckinPreferences) {
467
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore009,
468
                            @"App reset detected but no valid checkin auth preferences found."
469
                            @" Will not delete server token registrations.");
470
    return;
471
  }
472
  [_authService resetCheckinWithHandler:^(NSError *_Nonnull error) {
473
    if (!error) {
474
      FIRMessagingLoggerDebug(
475
          kFIRMessagingMessageCodeStore002,
476
          @"Removed cached checkin preferences from Keychain because this is a fresh install.");
477
    } else {
478
      FIRMessagingLoggerError(
479
          kFIRMessagingMessageCodeStore003,
480
          @"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error);
481
    }
482
 
483
    if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) {
484
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeStore006,
485
                              @"Resetting old checkin and deleting server token registrations.");
486
      // We don't really need to delete old FCM tokens created via IID auth tokens since
487
      // those tokens are already hashed by APNS token as the has so creating a new
488
      // token should automatically delete the old-token.
489
      [self didDeleteFCMScopedTokensForCheckin:oldCheckinPreferences];
490
    }
491
  }];
492
}
493
 
494
- (void)didDeleteFCMScopedTokensForCheckin:(FIRMessagingCheckinPreferences *)checkin {
495
  // Make a best effort try to delete the old client related state on the FCM server. This is
496
  // required to delete old pubusb registrations which weren't cleared when the app was deleted.
497
  //
498
  // This is only a one time effort. If this call fails the client would still receive duplicate
499
  // pubsub notifications if he is again subscribed to the same topic.
500
  //
501
  // The client state should be cleared on the server for the provided checkin preferences.
502
  FIRMessagingTokenDeleteOperation *operation =
503
      [self createDeleteOperationWithAuthorizedEntity:nil
504
                                                scope:nil
505
                                   checkinPreferences:checkin
506
                                           instanceID:nil
507
                                               action:FIRMessagingTokenActionDeleteToken];
508
  [operation addCompletionHandler:^(FIRMessagingTokenOperationResult result,
509
                                    NSString *_Nullable token, NSError *_Nullable error) {
510
    if (error) {
511
      FIRMessagingMessageCode code =
512
          kFIRMessagingMessageCodeTokenManagerErrorDeletingFCMTokensOnAppReset;
513
      FIRMessagingLoggerDebug(code, @"Failed to delete GCM server registrations on app reset.");
514
    } else {
515
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTokenManagerDeletedFCMTokensOnAppReset,
516
                              @"Successfully deleted GCM server registrations on app reset");
517
    }
518
  }];
519
 
520
  [self.tokenOperations addOperation:operation];
521
}
522
 
523
#pragma mark - Unit Testing Stub Helpers
524
// We really have this method so that we can more easily stub it out for unit testing
525
- (FIRMessagingTokenFetchOperation *)
526
    createFetchOperationWithAuthorizedEntity:(NSString *)authorizedEntity
527
                                       scope:(NSString *)scope
528
                                     options:(NSDictionary<NSString *, NSString *> *)options
529
                                  instanceID:(NSString *)instanceID {
530
  FIRMessagingCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
531
  FIRMessagingTokenFetchOperation *operation =
532
      [[FIRMessagingTokenFetchOperation alloc] initWithAuthorizedEntity:authorizedEntity
533
                                                                  scope:scope
534
                                                                options:options
535
                                                     checkinPreferences:checkinPreferences
536
                                                             instanceID:instanceID];
537
  return operation;
538
}
539
 
540
// We really have this method so that we can more easily stub it out for unit testing
541
- (FIRMessagingTokenDeleteOperation *)
542
    createDeleteOperationWithAuthorizedEntity:(NSString *)authorizedEntity
543
                                        scope:(NSString *)scope
544
                           checkinPreferences:(FIRMessagingCheckinPreferences *)checkinPreferences
545
                                   instanceID:(NSString *)instanceID
546
                                       action:(FIRMessagingTokenAction)action {
547
  FIRMessagingTokenDeleteOperation *operation =
548
      [[FIRMessagingTokenDeleteOperation alloc] initWithAuthorizedEntity:authorizedEntity
549
                                                                   scope:scope
550
                                                      checkinPreferences:checkinPreferences
551
                                                              instanceID:instanceID
552
                                                                  action:action];
553
  return operation;
554
}
555
 
556
#pragma mark - Invalidating Cached Tokens
557
- (BOOL)checkTokenRefreshPolicyWithIID:(NSString *)IID {
558
  // We know at least one cached token exists.
559
  BOOL shouldFetchDefaultToken = NO;
560
  NSArray<FIRMessagingTokenInfo *> *tokenInfos = [_tokenStore cachedTokenInfos];
561
 
562
  NSMutableArray<FIRMessagingTokenInfo *> *tokenInfosToDelete =
563
      [NSMutableArray arrayWithCapacity:tokenInfos.count];
564
  for (FIRMessagingTokenInfo *tokenInfo in tokenInfos) {
565
    if ([tokenInfo isFreshWithIID:IID]) {
566
      // Token is fresh and in right format, do nothing
567
      continue;
568
    }
569
    if ([tokenInfo isDefaultToken]) {
570
      // Default token is expired, do not mark for deletion. Fetch directly from server to
571
      // replace the current one.
572
      shouldFetchDefaultToken = YES;
573
    } else {
574
      // Non-default token is expired, mark for deletion.
575
      [tokenInfosToDelete addObject:tokenInfo];
576
    }
577
    FIRMessagingLoggerDebug(
578
        kFIRMessagingMessageCodeTokenManagerInvalidateStaleToken,
579
        @"Invalidating cached token for %@ (%@) due to token is no longer fresh.",
580
        tokenInfo.authorizedEntity, tokenInfo.scope);
581
  }
582
  for (FIRMessagingTokenInfo *tokenInfoToDelete in tokenInfosToDelete) {
583
    [_tokenStore removeTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity
584
                                           scope:tokenInfoToDelete.scope];
585
  }
586
  return shouldFetchDefaultToken;
587
}
588
 
589
- (NSArray<FIRMessagingTokenInfo *> *)updateTokensToAPNSDeviceToken:(NSData *)deviceToken
590
                                                          isSandbox:(BOOL)isSandbox {
591
  // Each cached IID token that is missing an APNSInfo, or has an APNSInfo associated should be
592
  // checked and invalidated if needed.
593
  FIRMessagingAPNSInfo *APNSInfo = [[FIRMessagingAPNSInfo alloc] initWithDeviceToken:deviceToken
594
                                                                           isSandbox:isSandbox];
595
  if ([self.currentAPNSInfo isEqualToAPNSInfo:APNSInfo]) {
596
    return @[];
597
  }
598
  self.currentAPNSInfo = APNSInfo;
599
 
600
  NSArray<FIRMessagingTokenInfo *> *tokenInfos = [_tokenStore cachedTokenInfos];
601
  NSMutableArray<FIRMessagingTokenInfo *> *tokenInfosToDelete =
602
      [NSMutableArray arrayWithCapacity:tokenInfos.count];
603
  for (FIRMessagingTokenInfo *cachedTokenInfo in tokenInfos) {
604
    // Check if the cached APNSInfo is nil, or if it is an old APNSInfo.
605
    if (!cachedTokenInfo.APNSInfo ||
606
        ![cachedTokenInfo.APNSInfo isEqualToAPNSInfo:self.currentAPNSInfo]) {
607
      // Mark for invalidation.
608
      [tokenInfosToDelete addObject:cachedTokenInfo];
609
    }
610
  }
611
  for (FIRMessagingTokenInfo *tokenInfoToDelete in tokenInfosToDelete) {
612
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTokenManagerAPNSChangedTokenInvalidated,
613
                            @"Invalidating cached token for %@ (%@) due to APNs token change.",
614
                            tokenInfoToDelete.authorizedEntity, tokenInfoToDelete.scope);
615
    [_tokenStore removeTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity
616
                                           scope:tokenInfoToDelete.scope];
617
  }
618
  return tokenInfosToDelete;
619
}
620
 
621
#pragma mark - APNS Token
622
- (void)setAPNSToken:(NSData *)APNSToken withUserInfo:(NSDictionary *)userInfo {
623
  if (!APNSToken || ![APNSToken isKindOfClass:[NSData class]]) {
624
    if ([APNSToken class]) {
625
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeInternal002, @"Invalid APNS token type %@",
626
                              NSStringFromClass([APNSToken class]));
627
    } else {
628
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeInternal002, @"Empty APNS token type");
629
    }
630
    return;
631
  }
632
  NSInteger type = [userInfo[kFIRMessagingAPNSTokenType] integerValue];
633
 
634
  // The APNS token is being added, or has changed (rare)
635
  if ([self.currentAPNSInfo.deviceToken isEqualToData:APNSToken]) {
636
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeInstanceID011,
637
                            @"Trying to reset APNS token to the same value. Will return");
638
    return;
639
  }
640
  // Use this token type for when we have to automatically fetch tokens in the future
641
  BOOL isSandboxApp = (type == FIRMessagingAPNSTokenTypeSandbox);
642
  if (type == FIRMessagingAPNSTokenTypeUnknown) {
643
    isSandboxApp = FIRMessagingIsSandboxApp();
644
  }
645
 
646
  // Pro-actively invalidate the default token, if the APNs change makes it
647
  // invalid. Previously, we invalidated just before fetching the token.
648
  NSArray<FIRMessagingTokenInfo *> *invalidatedTokens =
649
      [self updateTokensToAPNSDeviceToken:APNSToken isSandbox:isSandboxApp];
650
 
651
  self.currentAPNSInfo = [[FIRMessagingAPNSInfo alloc] initWithDeviceToken:[APNSToken copy]
652
                                                                 isSandbox:isSandboxApp];
653
 
654
  // Re-fetch any invalidated tokens automatically, this time with the current APNs token, so that
655
  // they are up-to-date. Or this is a fresh install and no apns token stored yet.
656
  if (invalidatedTokens.count > 0 || [_tokenStore cachedTokenInfos].count == 0) {
657
    FIRMessaging_WEAKIFY(self);
658
 
659
    [self.installations installationIDWithCompletion:^(NSString *_Nullable identifier,
660
                                                       NSError *_Nullable error) {
661
      FIRMessaging_STRONGIFY(self);
662
      if (self == nil) {
663
        FIRMessagingLoggerError(kFIRMessagingMessageCodeInstanceID017,
664
                                @"Instance ID shut down during token reset. Aborting");
665
        return;
666
      }
667
      if (self.currentAPNSInfo == nil) {
668
        FIRMessagingLoggerError(kFIRMessagingMessageCodeInstanceID018,
669
                                @"apnsTokenData was set to nil during token reset. Aborting");
670
        return;
671
      }
672
 
673
      NSMutableDictionary *tokenOptions = [@{
674
        kFIRMessagingTokenOptionsAPNSKey : self.currentAPNSInfo.deviceToken,
675
        kFIRMessagingTokenOptionsAPNSIsSandboxKey : @(isSandboxApp)
676
      } mutableCopy];
677
      if (self.firebaseAppID) {
678
        tokenOptions[kFIRMessagingTokenOptionsFirebaseAppIDKey] = self.firebaseAppID;
679
      }
680
 
681
      for (FIRMessagingTokenInfo *tokenInfo in invalidatedTokens) {
682
        [self fetchNewTokenWithAuthorizedEntity:tokenInfo.authorizedEntity
683
                                          scope:tokenInfo.scope
684
                                     instanceID:identifier
685
                                        options:tokenOptions
686
                                        handler:^(NSString *_Nullable token,
687
                                                  NSError *_Nullable error){
688
                                            // Do nothing as callback is not needed and the
689
                                            // sub-funciton already handle errors.
690
                                        }];
691
      }
692
      if ([self->_tokenStore cachedTokenInfos].count == 0) {
693
        [self tokenWithAuthorizedEntity:self.fcmSenderID
694
                                  scope:kFIRMessagingDefaultTokenScope
695
                                options:tokenOptions
696
                                handler:^(NSString *_Nullable FCMToken, NSError *_Nullable error){
697
                                    // Do nothing as callback is not needed and the sub-funciton
698
                                    // already handle errors.
699
                                }];
700
      }
701
    }];
702
  }
703
}
704
 
705
#pragma mark - checkin
706
- (BOOL)hasValidCheckinInfo {
707
  return self.authService.checkinPreferences.hasValidCheckinInfo;
708
}
709
 
710
@end