Ei kuvausta

FIRInstanceID.m 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  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. #import "FIRInstanceID.h"
  17. #import <FirebaseCore/FIRAppInternal.h>
  18. #import <FirebaseCore/FIRComponent.h>
  19. #import <FirebaseCore/FIRComponentContainer.h>
  20. #import <FirebaseCore/FIRLibrary.h>
  21. #import <FirebaseCore/FIROptions.h>
  22. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  23. #import "FIRInstanceID+Private.h"
  24. #import "FIRInstanceIDAuthService.h"
  25. #import "FIRInstanceIDConstants.h"
  26. #import "FIRInstanceIDDefines.h"
  27. #import "FIRInstanceIDKeyPairStore.h"
  28. #import "FIRInstanceIDLogger.h"
  29. #import "FIRInstanceIDStore.h"
  30. #import "FIRInstanceIDTokenInfo.h"
  31. #import "FIRInstanceIDTokenManager.h"
  32. #import "FIRInstanceIDUtilities.h"
  33. #import "FIRInstanceIDVersionUtilities.h"
  34. #import "NSError+FIRInstanceID.h"
  35. // Public constants
  36. NSString *const kFIRInstanceIDScopeFirebaseMessaging = @"fcm";
  37. #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  38. const NSNotificationName kFIRInstanceIDTokenRefreshNotification =
  39. @"com.firebase.iid.notif.refresh-token";
  40. #else
  41. NSString *const kFIRInstanceIDTokenRefreshNotification = @"com.firebase.iid.notif.refresh-token";
  42. #endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  43. NSString *const kFIRInstanceIDInvalidNilHandlerError = @"Invalid nil handler.";
  44. // Private constants
  45. int64_t const kMaxRetryIntervalForDefaultTokenInSeconds = 20 * 60; // 20 minutes
  46. int64_t const kMinRetryIntervalForDefaultTokenInSeconds = 10; // 10 seconds
  47. // we retry only a max 5 times.
  48. // TODO(chliangGoogle): If we still fail we should listen for the network change notification
  49. // since GCM would have started Reachability. We only start retrying after we see a configuration
  50. // change.
  51. NSInteger const kMaxRetryCountForDefaultToken = 5;
  52. static NSString *const kEntitlementsAPSEnvironmentKey = @"Entitlements.aps-environment";
  53. static NSString *const kAPSEnvironmentDevelopmentValue = @"development";
  54. /// FIRMessaging selector that returns the current FIRMessaging auto init
  55. /// enabled flag.
  56. static NSString *const kFIRInstanceIDFCMSelectorAutoInitEnabled = @"isAutoInitEnabled";
  57. static NSString *const kFIRInstanceIDFCMSelectorInstance = @"messaging";
  58. static NSString *const kFIRInstanceIDAPNSTokenType = @"APNSTokenType";
  59. static NSString *const kFIRIIDAppReadyToConfigureSDKNotification =
  60. @"FIRAppReadyToConfigureSDKNotification";
  61. static NSString *const kFIRIIDAppNameKey = @"FIRAppNameKey";
  62. static NSString *const kFIRIIDErrorDomain = @"com.firebase.instanceid";
  63. static NSString *const kFIRIIDServiceInstanceID = @"InstanceID";
  64. // This should be the same value as FIRErrorCodeInstanceIDFailed, which we can't import directly
  65. static NSInteger const kFIRIIDErrorCodeInstanceIDFailed = -121;
  66. typedef void (^FIRInstanceIDKeyPairHandler)(FIRInstanceIDKeyPair *keyPair, NSError *error);
  67. /**
  68. * The APNS token type for the app. If the token type is set to `UNKNOWN`
  69. * InstanceID will implicitly try to figure out what the actual token type
  70. * is from the provisioning profile.
  71. * This must match FIRMessagingAPNSTokenType in FIRMessaging.h
  72. */
  73. typedef NS_ENUM(NSInteger, FIRInstanceIDAPNSTokenType) {
  74. /// Unknown token type.
  75. FIRInstanceIDAPNSTokenTypeUnknown,
  76. /// Sandbox token type.
  77. FIRInstanceIDAPNSTokenTypeSandbox,
  78. /// Production token type.
  79. FIRInstanceIDAPNSTokenTypeProd,
  80. } NS_SWIFT_NAME(InstanceIDAPNSTokenType);
  81. @interface FIRInstanceIDResult ()
  82. @property(nonatomic, readwrite, copy) NSString *instanceID;
  83. @property(nonatomic, readwrite, copy) NSString *token;
  84. @end
  85. @interface FIRInstanceID ()
  86. // FIRApp configuration objects.
  87. @property(nonatomic, readwrite, copy) NSString *fcmSenderID;
  88. @property(nonatomic, readwrite, copy) NSString *firebaseAppID;
  89. // Raw APNS token data
  90. @property(nonatomic, readwrite, strong) NSData *apnsTokenData;
  91. @property(nonatomic, readwrite) FIRInstanceIDAPNSTokenType apnsTokenType;
  92. // String-based, internal representation of APNS token
  93. @property(nonatomic, readwrite, copy) NSString *APNSTupleString;
  94. // Token fetched from the server automatically for the default app.
  95. @property(nonatomic, readwrite, copy) NSString *defaultFCMToken;
  96. @property(nonatomic, readwrite, strong) FIRInstanceIDTokenManager *tokenManager;
  97. @property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore;
  98. // backoff and retry for default token
  99. @property(atomic, readwrite, assign) BOOL isFetchingDefaultToken;
  100. @property(atomic, readwrite, assign) BOOL isDefaultTokenFetchScheduled;
  101. @property(nonatomic, readwrite, assign) NSInteger retryCountForDefaultToken;
  102. @end
  103. // InstanceID doesn't provide any functionality to other components,
  104. // so it provides a private, empty protocol that it conforms to and use it for registration.
  105. @protocol FIRInstanceIDInstanceProvider
  106. @end
  107. @interface FIRInstanceID () <FIRInstanceIDInstanceProvider, FIRLibrary>
  108. @end
  109. @implementation FIRInstanceIDResult
  110. - (id)copyWithZone:(NSZone *)zone {
  111. FIRInstanceIDResult *result = [[[self class] allocWithZone:zone] init];
  112. result.instanceID = self.instanceID;
  113. result.token = self.token;
  114. return result;
  115. }
  116. @end
  117. @implementation FIRInstanceID
  118. // File static to support InstanceID tests that call [FIRInstanceID instanceID] after
  119. // [FIRInstanceID instanceIDForTests].
  120. static FIRInstanceID *gInstanceID;
  121. + (instancetype)instanceID {
  122. // If the static instance was created, return it. This should only be set in tests and we should
  123. // eventually use proper dependency injection for a better test structure.
  124. if (gInstanceID != nil) {
  125. return gInstanceID;
  126. }
  127. FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here.
  128. FIRInstanceID *instanceID =
  129. (FIRInstanceID *)FIR_COMPONENT(FIRInstanceIDInstanceProvider, defaultApp.container);
  130. return instanceID;
  131. }
  132. - (instancetype)initPrivately {
  133. self = [super init];
  134. if (self != nil) {
  135. // Use automatic detection of sandbox, unless otherwise set by developer
  136. _apnsTokenType = FIRInstanceIDAPNSTokenTypeUnknown;
  137. }
  138. return self;
  139. }
  140. + (FIRInstanceID *)instanceIDForTests {
  141. gInstanceID = [[FIRInstanceID alloc] initPrivately];
  142. [gInstanceID start];
  143. return gInstanceID;
  144. }
  145. - (void)dealloc {
  146. [[NSNotificationCenter defaultCenter] removeObserver:self];
  147. }
  148. #pragma mark - Tokens
  149. - (NSString *)token {
  150. if (!self.fcmSenderID.length) {
  151. return nil;
  152. }
  153. NSString *cachedToken = [self cachedTokenIfAvailable];
  154. if (cachedToken) {
  155. return cachedToken;
  156. } else {
  157. // If we've never had a cached default token, we should fetch one because unrelatedly,
  158. // this request will help us determine whether the locally-generated Instance ID keypair is not
  159. // unique, and therefore generate a new one.
  160. [self fetchDefaultToken];
  161. return nil;
  162. }
  163. }
  164. - (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler {
  165. FIRInstanceID_WEAKIFY(self);
  166. [self getIDWithHandler:^(NSString *identity, NSError *error) {
  167. FIRInstanceID_STRONGIFY(self);
  168. // This is in main queue already
  169. if (error) {
  170. if (handler) {
  171. handler(nil, error);
  172. }
  173. return;
  174. }
  175. FIRInstanceIDResult *result = [[FIRInstanceIDResult alloc] init];
  176. result.instanceID = identity;
  177. NSString *cachedToken = [self cachedTokenIfAvailable];
  178. if (cachedToken) {
  179. if (handler) {
  180. result.token = cachedToken;
  181. handler(result, nil);
  182. }
  183. // If no handler, simply return since client has generated iid and token.
  184. return;
  185. }
  186. // Now get token
  187. FIRInstanceIDTokenHandler tokenHandler = ^void(NSString *token, NSError *error) {
  188. if (error) {
  189. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007,
  190. @"Failed to retrieve the default FCM token after %ld retries",
  191. (long)self.retryCountForDefaultToken);
  192. if (handler) {
  193. // If token fetching fails, result should be nil with error returned.
  194. handler(nil, error);
  195. }
  196. return;
  197. }
  198. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@",
  199. token);
  200. NSString *previousFCMToken = self.defaultFCMToken;
  201. self.defaultFCMToken = token;
  202. // Only notify of token refresh if we have a new valid token that's different than before
  203. if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) {
  204. NSNotification *tokenRefreshNotification =
  205. [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification
  206. object:[self.defaultFCMToken copy]];
  207. [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification
  208. postingStyle:NSPostASAP];
  209. }
  210. if (handler) {
  211. result.token = token;
  212. handler(result, nil);
  213. }
  214. };
  215. [self defaultTokenWithHandler:tokenHandler];
  216. }];
  217. }
  218. - (NSString *)cachedTokenIfAvailable {
  219. FIRInstanceIDTokenInfo *cachedTokenInfo =
  220. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:self.fcmSenderID
  221. scope:kFIRInstanceIDDefaultTokenScope];
  222. return cachedTokenInfo.token;
  223. }
  224. - (void)setDefaultFCMToken:(NSString *)defaultFCMToken {
  225. if (_defaultFCMToken && defaultFCMToken && [defaultFCMToken isEqualToString:_defaultFCMToken]) {
  226. return;
  227. }
  228. _defaultFCMToken = defaultFCMToken;
  229. // Sending this notification out will ensure that FIRMessaging has the updated
  230. // default FCM token.
  231. NSNotification *internalDefaultTokenNotification =
  232. [NSNotification notificationWithName:kFIRInstanceIDDefaultGCMTokenNotification
  233. object:_defaultFCMToken];
  234. [[NSNotificationQueue defaultQueue] enqueueNotification:internalDefaultTokenNotification
  235. postingStyle:NSPostASAP];
  236. }
  237. - (void)tokenWithAuthorizedEntity:(NSString *)authorizedEntity
  238. scope:(NSString *)scope
  239. options:(NSDictionary *)options
  240. handler:(FIRInstanceIDTokenHandler)handler {
  241. _FIRInstanceIDDevAssert(handler != nil && [authorizedEntity length] && [scope length],
  242. @"Invalid authorizedEntity or scope to new token");
  243. if (!handler) {
  244. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID000,
  245. kFIRInstanceIDInvalidNilHandlerError);
  246. return;
  247. }
  248. NSMutableDictionary *tokenOptions = [NSMutableDictionary dictionary];
  249. if (options.count) {
  250. [tokenOptions addEntriesFromDictionary:options];
  251. }
  252. NSString *APNSKey = kFIRInstanceIDTokenOptionsAPNSKey;
  253. NSString *serverTypeKey = kFIRInstanceIDTokenOptionsAPNSIsSandboxKey;
  254. if (tokenOptions[APNSKey] != nil && tokenOptions[serverTypeKey] == nil) {
  255. // APNS key was given, but server type is missing. Supply the server type with automatic
  256. // checking. This can happen when the token is requested from FCM, which does not include a
  257. // server type during its request.
  258. tokenOptions[serverTypeKey] = @([self isSandboxApp]);
  259. }
  260. // comparing enums to ints directly throws a warning
  261. FIRInstanceIDErrorCode noError = INT_MAX;
  262. FIRInstanceIDErrorCode errorCode = noError;
  263. if (FIRInstanceIDIsValidGCMScope(scope) && !tokenOptions[APNSKey]) {
  264. errorCode = kFIRInstanceIDErrorCodeMissingAPNSToken;
  265. } else if (FIRInstanceIDIsValidGCMScope(scope) &&
  266. ![tokenOptions[APNSKey] isKindOfClass:[NSData class]]) {
  267. errorCode = kFIRInstanceIDErrorCodeInvalidRequest;
  268. } else if (![authorizedEntity length]) {
  269. errorCode = kFIRInstanceIDErrorCodeInvalidAuthorizedEntity;
  270. } else if (![scope length]) {
  271. errorCode = kFIRInstanceIDErrorCodeInvalidScope;
  272. } else if (!self.keyPairStore) {
  273. errorCode = kFIRInstanceIDErrorCodeInvalidStart;
  274. }
  275. FIRInstanceIDTokenHandler newHandler = ^(NSString *token, NSError *error) {
  276. dispatch_async(dispatch_get_main_queue(), ^{
  277. handler(token, error);
  278. });
  279. };
  280. if (errorCode != noError) {
  281. newHandler(nil, [NSError errorWithFIRInstanceIDErrorCode:errorCode]);
  282. return;
  283. }
  284. // TODO(chliangGoogle): Add some validation logic that the APNs token data and sandbox value are
  285. // supplied in the valid format (NSData and BOOL, respectively).
  286. // Add internal options
  287. if (self.firebaseAppID) {
  288. tokenOptions[kFIRInstanceIDTokenOptionsFirebaseAppIDKey] = self.firebaseAppID;
  289. }
  290. FIRInstanceID_WEAKIFY(self);
  291. FIRInstanceIDAuthService *authService = self.tokenManager.authService;
  292. [authService
  293. fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) {
  294. FIRInstanceID_STRONGIFY(self);
  295. if (error) {
  296. newHandler(nil, error);
  297. return;
  298. }
  299. // Only use the token in the cache if the APNSInfo matches what the request's options has.
  300. // It's possible for the request to be with a newer APNs device token, which should be
  301. // honored.
  302. FIRInstanceIDTokenInfo *cachedTokenInfo =
  303. [self.tokenManager cachedTokenInfoWithAuthorizedEntity:authorizedEntity scope:scope];
  304. if (cachedTokenInfo) {
  305. // Ensure that the cached token matches APNs data before returning it.
  306. FIRInstanceIDAPNSInfo *optionsAPNSInfo =
  307. [[FIRInstanceIDAPNSInfo alloc] initWithTokenOptionsDictionary:tokenOptions];
  308. // If either the APNs info is missing in both, or if they are an exact match, then we can
  309. // use this cached token.
  310. if ((!cachedTokenInfo.APNSInfo && !optionsAPNSInfo) ||
  311. [cachedTokenInfo.APNSInfo isEqualToAPNSInfo:optionsAPNSInfo]) {
  312. newHandler(cachedTokenInfo.token, nil);
  313. return;
  314. }
  315. }
  316. FIRInstanceID_WEAKIFY(self);
  317. [self asyncLoadKeyPairWithHandler:^(FIRInstanceIDKeyPair *keyPair, NSError *error) {
  318. FIRInstanceID_STRONGIFY(self);
  319. if (error) {
  320. NSError *newError =
  321. [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  322. newHandler(nil, newError);
  323. } else {
  324. [self.tokenManager fetchNewTokenWithAuthorizedEntity:[authorizedEntity copy]
  325. scope:[scope copy]
  326. keyPair:keyPair
  327. options:tokenOptions
  328. handler:newHandler];
  329. }
  330. }];
  331. }];
  332. }
  333. - (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity
  334. scope:(NSString *)scope
  335. handler:(FIRInstanceIDDeleteTokenHandler)handler {
  336. _FIRInstanceIDDevAssert(handler != nil && [authorizedEntity length] && [scope length],
  337. @"Invalid authorizedEntity or scope to delete token");
  338. if (!handler) {
  339. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID001,
  340. kFIRInstanceIDInvalidNilHandlerError);
  341. }
  342. // comparing enums to ints directly throws a warning
  343. FIRInstanceIDErrorCode noError = INT_MAX;
  344. FIRInstanceIDErrorCode errorCode = noError;
  345. if (![authorizedEntity length]) {
  346. errorCode = kFIRInstanceIDErrorCodeInvalidAuthorizedEntity;
  347. } else if (![scope length]) {
  348. errorCode = kFIRInstanceIDErrorCodeInvalidScope;
  349. } else if (!self.keyPairStore) {
  350. errorCode = kFIRInstanceIDErrorCodeInvalidStart;
  351. }
  352. FIRInstanceIDDeleteTokenHandler newHandler = ^(NSError *error) {
  353. // If a default token is deleted successfully, reset the defaultFCMToken too.
  354. if (!error && [authorizedEntity isEqualToString:self.fcmSenderID] &&
  355. [scope isEqualToString:kFIRInstanceIDDefaultTokenScope]) {
  356. self.defaultFCMToken = nil;
  357. }
  358. dispatch_async(dispatch_get_main_queue(), ^{
  359. handler(error);
  360. });
  361. };
  362. if (errorCode != noError) {
  363. newHandler([NSError errorWithFIRInstanceIDErrorCode:errorCode]);
  364. return;
  365. }
  366. FIRInstanceID_WEAKIFY(self);
  367. FIRInstanceIDAuthService *authService = self.tokenManager.authService;
  368. [authService
  369. fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) {
  370. FIRInstanceID_STRONGIFY(self);
  371. if (error) {
  372. newHandler(error);
  373. return;
  374. }
  375. FIRInstanceID_WEAKIFY(self);
  376. [self asyncLoadKeyPairWithHandler:^(FIRInstanceIDKeyPair *keyPair, NSError *error) {
  377. FIRInstanceID_STRONGIFY(self);
  378. if (error) {
  379. NSError *newError =
  380. [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  381. newHandler(newError);
  382. } else {
  383. [self.tokenManager deleteTokenWithAuthorizedEntity:authorizedEntity
  384. scope:scope
  385. keyPair:keyPair
  386. handler:newHandler];
  387. }
  388. }];
  389. }];
  390. }
  391. - (void)asyncLoadKeyPairWithHandler:(FIRInstanceIDKeyPairHandler)handler {
  392. FIRInstanceID_WEAKIFY(self);
  393. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  394. FIRInstanceID_STRONGIFY(self);
  395. NSError *error = nil;
  396. FIRInstanceIDKeyPair *keyPair = [self.keyPairStore loadKeyPairWithError:&error];
  397. dispatch_async(dispatch_get_main_queue(), ^{
  398. if (error) {
  399. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID002,
  400. @"Failed to retreieve keyPair %@", error);
  401. if (handler) {
  402. handler(nil, error);
  403. }
  404. } else if (!keyPair && !error) {
  405. if (handler) {
  406. handler(nil,
  407. [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair]);
  408. }
  409. } else {
  410. if (handler) {
  411. handler(keyPair, nil);
  412. }
  413. }
  414. });
  415. });
  416. }
  417. #pragma mark - Identity
  418. - (void)getIDWithHandler:(FIRInstanceIDHandler)handler {
  419. _FIRInstanceIDDevAssert(handler, @"Invalid nil handler to getIdentity");
  420. if (!handler) {
  421. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID003,
  422. kFIRInstanceIDInvalidNilHandlerError);
  423. return;
  424. }
  425. void (^callHandlerOnMainThread)(NSString *, NSError *) = ^(NSString *identity, NSError *error) {
  426. dispatch_async(dispatch_get_main_queue(), ^{
  427. handler(identity, error);
  428. });
  429. };
  430. if (!self.keyPairStore) {
  431. NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidStart];
  432. callHandlerOnMainThread(nil, error);
  433. return;
  434. }
  435. FIRInstanceID_WEAKIFY(self);
  436. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  437. FIRInstanceID_STRONGIFY(self);
  438. NSError *error;
  439. NSString *appIdentity = [self.keyPairStore appIdentityWithError:&error];
  440. // When getID is explicitly called, trigger getToken to make sure token always exists.
  441. // This is to avoid ID conflict (ID is not checked for conflict until we generate a token)
  442. if (appIdentity) {
  443. #pragma clang diagnostic push
  444. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  445. [self token];
  446. #pragma clang diagnostic pop
  447. }
  448. callHandlerOnMainThread(appIdentity, error);
  449. });
  450. }
  451. - (void)deleteIDWithHandler:(FIRInstanceIDDeleteHandler)handler {
  452. _FIRInstanceIDDevAssert(handler, @"Invalid nil handler to delete Identity");
  453. if (!handler) {
  454. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID004,
  455. kFIRInstanceIDInvalidNilHandlerError);
  456. return;
  457. }
  458. void (^callHandlerOnMainThread)(NSError *) = ^(NSError *error) {
  459. if ([NSThread isMainThread]) {
  460. handler(error);
  461. return;
  462. }
  463. dispatch_async(dispatch_get_main_queue(), ^{
  464. handler(error);
  465. });
  466. };
  467. if (!self.keyPairStore) {
  468. FIRInstanceIDErrorCode error = kFIRInstanceIDErrorCodeInvalidStart;
  469. callHandlerOnMainThread([NSError errorWithFIRInstanceIDErrorCode:error]);
  470. return;
  471. }
  472. FIRInstanceID_WEAKIFY(self);
  473. void (^deleteTokensHandler)(NSError *) = ^void(NSError *error) {
  474. FIRInstanceID_STRONGIFY(self);
  475. if (error) {
  476. callHandlerOnMainThread(error);
  477. return;
  478. }
  479. [self deleteIdentityWithHandler:^(NSError *error) {
  480. callHandlerOnMainThread(error);
  481. }];
  482. };
  483. [self asyncLoadKeyPairWithHandler:^(FIRInstanceIDKeyPair *keyPair, NSError *error) {
  484. FIRInstanceID_STRONGIFY(self);
  485. if (error) {
  486. NSError *newError =
  487. [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPair];
  488. callHandlerOnMainThread(newError);
  489. } else {
  490. [self.tokenManager deleteAllTokensWithKeyPair:keyPair handler:deleteTokensHandler];
  491. }
  492. }];
  493. }
  494. - (void)notifyIdentityReset {
  495. [self deleteIdentityWithHandler:nil];
  496. }
  497. // Delete all the local cache checkin, IID and token.
  498. - (void)deleteIdentityWithHandler:(FIRInstanceIDDeleteHandler)handler {
  499. // Delete tokens.
  500. [self.tokenManager deleteAllTokensLocallyWithHandler:^(NSError *deleteTokenError) {
  501. // Reset FCM token.
  502. self.defaultFCMToken = nil;
  503. if (deleteTokenError) {
  504. if (handler) {
  505. handler(deleteTokenError);
  506. }
  507. return;
  508. }
  509. // Delete Instance ID.
  510. [self.keyPairStore
  511. deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType
  512. handler:^(NSError *error) {
  513. NSError *deletePlistError;
  514. [self.keyPairStore
  515. removeKeyPairCreationTimePlistWithError:&deletePlistError];
  516. if (error || deletePlistError) {
  517. if (handler) {
  518. // Prefer to use the delete Instance ID error.
  519. error = [NSError
  520. errorWithFIRInstanceIDErrorCode:
  521. kFIRInstanceIDErrorCodeUnknown
  522. userInfo:@{
  523. NSUnderlyingErrorKey : error
  524. ? error
  525. : deletePlistError
  526. }];
  527. handler(error);
  528. }
  529. return;
  530. }
  531. // Delete checkin.
  532. [self.tokenManager.authService
  533. resetCheckinWithHandler:^(NSError *error) {
  534. if (error) {
  535. if (handler) {
  536. handler(error);
  537. }
  538. return;
  539. }
  540. // Only request new token if FCM auto initialization is
  541. // enabled.
  542. if ([self isFCMAutoInitEnabled]) {
  543. // Deletion succeeds! Requesting new checkin, IID and token.
  544. // TODO(chliangGoogle) see if dispatch_after is necessary
  545. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  546. (int64_t)(0.5 * NSEC_PER_SEC)),
  547. dispatch_get_main_queue(), ^{
  548. [self fetchDefaultToken];
  549. });
  550. }
  551. if (handler) {
  552. handler(nil);
  553. }
  554. }];
  555. }];
  556. }];
  557. }
  558. #pragma mark - Config
  559. + (void)load {
  560. [FIRApp registerInternalLibrary:(Class<FIRLibrary>)self
  561. withName:@"fire-iid"
  562. withVersion:FIRInstanceIDCurrentLibraryVersion()];
  563. }
  564. + (nonnull NSArray<FIRComponent *> *)componentsToRegister {
  565. FIRComponentCreationBlock creationBlock =
  566. ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
  567. // Ensure it's cached so it returns the same instance every time instanceID is called.
  568. *isCacheable = YES;
  569. FIRInstanceID *instanceID = [[FIRInstanceID alloc] initPrivately];
  570. [instanceID start];
  571. return instanceID;
  572. };
  573. FIRComponent *instanceIDProvider =
  574. [FIRComponent componentWithProtocol:@protocol(FIRInstanceIDInstanceProvider)
  575. instantiationTiming:FIRInstantiationTimingLazy
  576. dependencies:@[]
  577. creationBlock:creationBlock];
  578. return @[ instanceIDProvider ];
  579. }
  580. + (void)configureWithApp:(FIRApp *)app {
  581. if (!app.isDefaultApp) {
  582. // Only configure for the default FIRApp.
  583. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeFIRApp002,
  584. @"Firebase Instance ID only works with the default app.");
  585. return;
  586. }
  587. [[FIRInstanceID instanceID] configureInstanceIDWithOptions:app.options app:app];
  588. }
  589. - (void)configureInstanceIDWithOptions:(FIROptions *)options app:(FIRApp *)firApp {
  590. NSString *GCMSenderID = options.GCMSenderID;
  591. if (!GCMSenderID.length) {
  592. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeFIRApp000,
  593. @"Firebase not set up correctly, nil or empty senderID.");
  594. [FIRInstanceID exitWithReason:@"GCM_SENDER_ID must not be nil or empty." forFirebaseApp:firApp];
  595. return;
  596. }
  597. self.fcmSenderID = GCMSenderID;
  598. self.firebaseAppID = firApp.options.googleAppID;
  599. // FCM generates a FCM token during app start for sending push notification to device.
  600. // This is not needed for app extension.
  601. if (![GULAppEnvironmentUtil isAppExtension]) {
  602. [self didCompleteConfigure];
  603. }
  604. }
  605. + (NSError *)configureErrorWithReason:(nonnull NSString *)reason {
  606. NSString *description =
  607. [NSString stringWithFormat:@"Configuration failed for service %@.", kFIRIIDServiceInstanceID];
  608. if (!reason.length) {
  609. reason = @"Unknown reason";
  610. }
  611. NSDictionary *userInfo =
  612. @{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason};
  613. return [NSError errorWithDomain:kFIRIIDErrorDomain
  614. code:kFIRIIDErrorCodeInstanceIDFailed
  615. userInfo:userInfo];
  616. }
  617. // If the firebaseApp is available we should send logs for the error through it before
  618. // raising an exception.
  619. + (void)exitWithReason:(nonnull NSString *)reason forFirebaseApp:(FIRApp *)firebaseApp {
  620. [firebaseApp sendLogsWithServiceName:kFIRIIDServiceInstanceID
  621. version:FIRInstanceIDCurrentLibraryVersion()
  622. error:[self configureErrorWithReason:reason]];
  623. [NSException raise:kFIRIIDErrorDomain
  624. format:@"Could not configure Firebase InstanceID. %@", reason];
  625. }
  626. // This is used to start any operations when we receive FirebaseSDK setup notification
  627. // from FIRCore.
  628. - (void)didCompleteConfigure {
  629. NSString *cachedToken = [self cachedTokenIfAvailable];
  630. // When there is a cached token, do the token refresh.
  631. if (cachedToken) {
  632. // Clean up expired tokens by checking the token refresh policy.
  633. if ([self.tokenManager checkForTokenRefreshPolicy]) {
  634. // Default token is expired, fetch default token from server.
  635. [self fetchDefaultToken];
  636. }
  637. // Notify FCM with the default token.
  638. #pragma clang diagnostic push
  639. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  640. self.defaultFCMToken = [self token];
  641. #pragma clang diagnostic pop
  642. } else if ([self isFCMAutoInitEnabled]) {
  643. // When there is no cached token, must check auto init is enabled.
  644. // If it's disabled, don't initiate token generation/refresh.
  645. // If no cache token and auto init is enabled, fetch a token from server.
  646. [self fetchDefaultToken];
  647. // Notify FCM with the default token.
  648. #pragma clang diagnostic push
  649. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  650. self.defaultFCMToken = [self token];
  651. #pragma clang diagnostic pop
  652. }
  653. // ONLY checkin when auto data collection is turned on.
  654. if ([self isFCMAutoInitEnabled]) {
  655. [self.tokenManager.authService scheduleCheckin:YES];
  656. }
  657. }
  658. - (BOOL)isFCMAutoInitEnabled {
  659. Class messagingClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString);
  660. // Firebase Messaging is not installed, auto init should be disabled since it's for FCM.
  661. if (!messagingClass) {
  662. return NO;
  663. }
  664. // Messaging doesn't have the singleton method, auto init should be enabled since FCM exists.
  665. SEL instanceSelector = NSSelectorFromString(kFIRInstanceIDFCMSelectorInstance);
  666. if (![messagingClass respondsToSelector:instanceSelector]) {
  667. return YES;
  668. }
  669. // Get FIRMessaging shared instance.
  670. IMP messagingInstanceIMP = [messagingClass methodForSelector:instanceSelector];
  671. id (*getMessagingInstance)(id, SEL) = (void *)messagingInstanceIMP;
  672. id messagingInstance = getMessagingInstance(messagingClass, instanceSelector);
  673. // Messaging doesn't have the property, auto init should be enabled since FCM exists.
  674. SEL autoInitSelector = NSSelectorFromString(kFIRInstanceIDFCMSelectorAutoInitEnabled);
  675. if (![messagingInstance respondsToSelector:autoInitSelector]) {
  676. return YES;
  677. }
  678. // Get autoInitEnabled method.
  679. IMP isAutoInitEnabledIMP = [messagingInstance methodForSelector:autoInitSelector];
  680. BOOL (*isAutoInitEnabled)(id, SEL) = (BOOL(*)(id, SEL))isAutoInitEnabledIMP;
  681. // Check FCM's isAutoInitEnabled property.
  682. return isAutoInitEnabled(messagingInstance, autoInitSelector);
  683. }
  684. // Actually makes InstanceID instantiate both the IID and Token-related subsystems.
  685. - (void)start {
  686. if (![FIRInstanceIDStore hasSubDirectory:kFIRInstanceIDSubDirectoryName]) {
  687. [FIRInstanceIDStore createSubDirectory:kFIRInstanceIDSubDirectoryName];
  688. }
  689. [self setupTokenManager];
  690. [self setupKeyPairManager];
  691. [self setupNotificationListeners];
  692. }
  693. // Creates the token manager, which is used for fetching, caching, and retrieving tokens.
  694. - (void)setupTokenManager {
  695. self.tokenManager = [[FIRInstanceIDTokenManager alloc] init];
  696. }
  697. // Creates a key pair manager, which stores the public/private keys needed to generate an
  698. // application instance ID.
  699. - (void)setupKeyPairManager {
  700. self.keyPairStore = [[FIRInstanceIDKeyPairStore alloc] init];
  701. if ([self.keyPairStore invalidateKeyPairsIfNeeded]) {
  702. // Reset tokens right away when keypair is deleted, otherwise async call can make first query
  703. // of token happens before reset old tokens during app start.
  704. // TODO(chliangGoogle): Delete all tokens on server too, using
  705. // deleteAllTokensWithKeyPair:handler:. This requires actually retrieving the invalid keypair
  706. // from Keychain, which is something that the key pair store does not currently do.
  707. [self.tokenManager deleteAllTokensLocallyWithHandler:nil];
  708. }
  709. }
  710. - (void)setupNotificationListeners {
  711. // To prevent double notifications remove observer from all events during setup.
  712. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  713. [center removeObserver:self];
  714. [center addObserver:self
  715. selector:@selector(notifyIdentityReset)
  716. name:kFIRInstanceIDIdentityInvalidatedNotification
  717. object:nil];
  718. [center addObserver:self
  719. selector:@selector(notifyAPNSTokenIsSet:)
  720. name:kFIRInstanceIDAPNSTokenNotification
  721. object:nil];
  722. }
  723. #pragma mark - Private Helpers
  724. /// Maximum retry count to fetch the default token.
  725. + (int64_t)maxRetryCountForDefaultToken {
  726. return kMaxRetryCountForDefaultToken;
  727. }
  728. /// Minimum interval in seconds between retries to fetch the default token.
  729. + (int64_t)minIntervalForDefaultTokenRetry {
  730. return kMinRetryIntervalForDefaultTokenInSeconds;
  731. }
  732. /// Maximum retry interval between retries to fetch default token.
  733. + (int64_t)maxRetryIntervalForDefaultTokenInSeconds {
  734. return kMaxRetryIntervalForDefaultTokenInSeconds;
  735. }
  736. - (NSInteger)retryIntervalToFetchDefaultToken {
  737. if (self.retryCountForDefaultToken >= [[self class] maxRetryCountForDefaultToken]) {
  738. return (NSInteger)[[self class] maxRetryIntervalForDefaultTokenInSeconds];
  739. }
  740. // exponential backoff with a fixed initial retry time
  741. // 11s, 22s, 44s, 88s ...
  742. int64_t minInterval = [[self class] minIntervalForDefaultTokenRetry];
  743. return (NSInteger)MIN(
  744. (1 << self.retryCountForDefaultToken) + minInterval * self.retryCountForDefaultToken,
  745. kMaxRetryIntervalForDefaultTokenInSeconds);
  746. }
  747. - (void)fetchDefaultToken {
  748. if (self.isFetchingDefaultToken) {
  749. return;
  750. }
  751. FIRInstanceID_WEAKIFY(self);
  752. FIRInstanceIDTokenHandler handler = ^void(NSString *token, NSError *error) {
  753. FIRInstanceID_STRONGIFY(self);
  754. if (error) {
  755. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID007,
  756. @"Failed to retrieve the default FCM token after %ld retries",
  757. (long)self.retryCountForDefaultToken);
  758. } else {
  759. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID008, @"Got default token %@",
  760. token);
  761. NSString *previousFCMToken = self.defaultFCMToken;
  762. self.defaultFCMToken = token;
  763. // Only notify of token refresh if we have a new valid token that's different than before
  764. if (self.defaultFCMToken.length && ![self.defaultFCMToken isEqualToString:previousFCMToken]) {
  765. NSNotification *tokenRefreshNotification =
  766. [NSNotification notificationWithName:kFIRInstanceIDTokenRefreshNotification
  767. object:[self.defaultFCMToken copy]];
  768. [[NSNotificationQueue defaultQueue] enqueueNotification:tokenRefreshNotification
  769. postingStyle:NSPostASAP];
  770. }
  771. }
  772. };
  773. // Get a "*" token using this APNS token.
  774. [self defaultTokenWithHandler:handler];
  775. }
  776. - (void)defaultTokenWithHandler:(FIRInstanceIDTokenHandler)handler {
  777. if (self.isFetchingDefaultToken || self.isDefaultTokenFetchScheduled) {
  778. return;
  779. }
  780. NSDictionary *instanceIDOptions = @{};
  781. BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil;
  782. if (hasFirebaseMessaging && self.apnsTokenData) {
  783. BOOL isSandboxApp = (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeSandbox);
  784. if (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeUnknown) {
  785. isSandboxApp = [self isSandboxApp];
  786. }
  787. instanceIDOptions = @{
  788. kFIRInstanceIDTokenOptionsAPNSKey : self.apnsTokenData,
  789. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandboxApp),
  790. };
  791. }
  792. FIRInstanceID_WEAKIFY(self);
  793. FIRInstanceIDTokenHandler newHandler = ^void(NSString *token, NSError *error) {
  794. FIRInstanceID_STRONGIFY(self);
  795. self.isFetchingDefaultToken = NO;
  796. if (error) {
  797. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID009,
  798. @"Failed to fetch default token %@", error);
  799. // This notification can be sent multiple times since we can't guarantee success at any point
  800. // of time.
  801. NSNotification *tokenFetchFailNotification =
  802. [NSNotification notificationWithName:kFIRInstanceIDDefaultGCMTokenFailNotification
  803. object:[error copy]];
  804. [[NSNotificationQueue defaultQueue] enqueueNotification:tokenFetchFailNotification
  805. postingStyle:NSPostASAP];
  806. self.retryCountForDefaultToken = (NSInteger)MIN(self.retryCountForDefaultToken + 1,
  807. [[self class] maxRetryCountForDefaultToken]);
  808. // Do not retry beyond the maximum limit.
  809. if (self.retryCountForDefaultToken < [[self class] maxRetryCountForDefaultToken]) {
  810. NSInteger retryInterval = [self retryIntervalToFetchDefaultToken];
  811. FIRInstanceID_WEAKIFY(self);
  812. self.isDefaultTokenFetchScheduled = YES;
  813. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)),
  814. dispatch_get_main_queue(), ^{
  815. FIRInstanceID_STRONGIFY(self);
  816. self.isDefaultTokenFetchScheduled = NO;
  817. [self defaultTokenWithHandler:handler];
  818. });
  819. } else {
  820. if (handler) {
  821. handler(nil, error);
  822. }
  823. }
  824. } else {
  825. // If somebody updated IID with APNS token while our initial request did not have it
  826. // set we need to update it on the server.
  827. BOOL shouldNotifyHandler = YES;
  828. NSData *deviceTokenInRequest = instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSKey];
  829. BOOL isSandboxInRequest =
  830. [instanceIDOptions[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey] boolValue];
  831. // Note that APNSTupleStringInRequest will be nil if deviceTokenInRequest is nil
  832. NSString *APNSTupleStringInRequest = FIRInstanceIDAPNSTupleStringForTokenAndServerType(
  833. deviceTokenInRequest, isSandboxInRequest);
  834. // If the APNs value either remained nil, or was the same non-nil value, the APNs value
  835. // did not change.
  836. BOOL APNSRemainedSameDuringFetch =
  837. (self.APNSTupleString == nil && APNSTupleStringInRequest == nil) ||
  838. ([self.APNSTupleString isEqualToString:APNSTupleStringInRequest]);
  839. if (!APNSRemainedSameDuringFetch && hasFirebaseMessaging) {
  840. // APNs value did change mid-fetch, so the token should be re-fetched with the current APNs
  841. // value.
  842. self.isDefaultTokenFetchScheduled = YES;
  843. // Wait to notify until we can modify this token with APNS (or receive a new token)
  844. shouldNotifyHandler = NO;
  845. FIRInstanceID_WEAKIFY(self);
  846. dispatch_async(dispatch_get_main_queue(), ^{
  847. FIRInstanceID_STRONGIFY(self);
  848. self.isDefaultTokenFetchScheduled = NO;
  849. [self defaultTokenWithHandler:handler];
  850. });
  851. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeRefetchingTokenForAPNS,
  852. @"Received APNS token while fetching default token. "
  853. @"Refetching default token.");
  854. } else {
  855. FIRInstanceIDLoggerInfo(kFIRInstanceIDMessageCodeInstanceID010,
  856. @"Successfully fetched default token.");
  857. }
  858. // Post the required notifications if somebody is waiting.
  859. if (shouldNotifyHandler && handler) {
  860. handler(token, nil);
  861. }
  862. }
  863. };
  864. self.isFetchingDefaultToken = YES;
  865. [self tokenWithAuthorizedEntity:self.fcmSenderID
  866. scope:kFIRInstanceIDDefaultTokenScope
  867. options:instanceIDOptions
  868. handler:newHandler];
  869. }
  870. #pragma mark - APNS Token
  871. // This should only be triggered from FCM.
  872. - (void)notifyAPNSTokenIsSet:(NSNotification *)notification {
  873. NSData *token = notification.object;
  874. if (!token || ![token isKindOfClass:[NSData class]]) {
  875. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInternal002, @"Invalid APNS token type %@",
  876. NSStringFromClass([notification.object class]));
  877. return;
  878. }
  879. NSInteger type = [notification.userInfo[kFIRInstanceIDAPNSTokenType] integerValue];
  880. // The APNS token is being added, or has changed (rare)
  881. if ([self.apnsTokenData isEqualToData:token]) {
  882. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID011,
  883. @"Trying to reset APNS token to the same value. Will return");
  884. return;
  885. }
  886. // Use this token type for when we have to automatically fetch tokens in the future
  887. self.apnsTokenType = type;
  888. BOOL isSandboxApp = (type == FIRInstanceIDAPNSTokenTypeSandbox);
  889. if (self.apnsTokenType == FIRInstanceIDAPNSTokenTypeUnknown) {
  890. isSandboxApp = [self isSandboxApp];
  891. }
  892. self.apnsTokenData = [token copy];
  893. self.APNSTupleString = FIRInstanceIDAPNSTupleStringForTokenAndServerType(token, isSandboxApp);
  894. // Pro-actively invalidate the default token, if the APNs change makes it
  895. // invalid. Previously, we invalidated just before fetching the token.
  896. NSArray<FIRInstanceIDTokenInfo *> *invalidatedTokens =
  897. [self.tokenManager updateTokensToAPNSDeviceToken:self.apnsTokenData isSandbox:isSandboxApp];
  898. // Re-fetch any invalidated tokens automatically, this time with the current APNs token, so that
  899. // they are up-to-date.
  900. if (invalidatedTokens.count > 0) {
  901. FIRInstanceID_WEAKIFY(self);
  902. [self asyncLoadKeyPairWithHandler:^(FIRInstanceIDKeyPair *keyPair, NSError *error) {
  903. FIRInstanceID_STRONGIFY(self);
  904. NSMutableDictionary *tokenOptions = [@{
  905. kFIRInstanceIDTokenOptionsAPNSKey : self.apnsTokenData,
  906. kFIRInstanceIDTokenOptionsAPNSIsSandboxKey : @(isSandboxApp)
  907. } mutableCopy];
  908. if (self.firebaseAppID) {
  909. tokenOptions[kFIRInstanceIDTokenOptionsFirebaseAppIDKey] = self.firebaseAppID;
  910. }
  911. for (FIRInstanceIDTokenInfo *tokenInfo in invalidatedTokens) {
  912. if ([tokenInfo.token isEqualToString:self.defaultFCMToken]) {
  913. // We will perform a special fetch for the default FCM token, so that the delegate methods
  914. // are called. For all others, we will do an internal re-fetch.
  915. [self fetchDefaultToken];
  916. } else {
  917. [self.tokenManager fetchNewTokenWithAuthorizedEntity:tokenInfo.authorizedEntity
  918. scope:tokenInfo.scope
  919. keyPair:keyPair
  920. options:tokenOptions
  921. handler:^(NSString *_Nullable token,
  922. NSError *_Nullable error){
  923. }];
  924. }
  925. }
  926. }];
  927. }
  928. }
  929. - (BOOL)isSandboxApp {
  930. static BOOL isSandboxApp = YES;
  931. static dispatch_once_t onceToken;
  932. dispatch_once(&onceToken, ^{
  933. isSandboxApp = ![self isProductionApp];
  934. });
  935. return isSandboxApp;
  936. }
  937. - (BOOL)isProductionApp {
  938. const BOOL defaultAppTypeProd = YES;
  939. NSError *error = nil;
  940. Class envClass = NSClassFromString(@"FIRAppEnvironmentUtil");
  941. SEL isSimulatorSelector = NSSelectorFromString(@"isSimulator");
  942. if ([envClass respondsToSelector:isSimulatorSelector]) {
  943. #pragma clang diagnostic push
  944. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  945. if ([envClass performSelector:isSimulatorSelector]) {
  946. #pragma clang diagnostic pop
  947. [self logAPNSConfigurationError:@"Running InstanceID on a simulator doesn't have APNS. "
  948. @"Use prod profile by default."];
  949. return defaultAppTypeProd;
  950. }
  951. }
  952. NSString *path = [[[NSBundle mainBundle] bundlePath]
  953. stringByAppendingPathComponent:@"embedded.mobileprovision"];
  954. // Apps distributed via AppStore or TestFlight use the Production APNS certificates.
  955. SEL isFromAppStoreSelector = NSSelectorFromString(@"isFromAppStore");
  956. if ([envClass respondsToSelector:isFromAppStoreSelector]) {
  957. #pragma clang diagnostic push
  958. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  959. if ([envClass performSelector:isFromAppStoreSelector]) {
  960. #pragma clang diagnostic pop
  961. return defaultAppTypeProd;
  962. }
  963. }
  964. SEL isAppStoreReceiptSandboxSelector = NSSelectorFromString(@"isAppStoreReceiptSandbox");
  965. if ([envClass respondsToSelector:isAppStoreReceiptSandboxSelector]) {
  966. #pragma clang diagnostic push
  967. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  968. if ([envClass performSelector:isAppStoreReceiptSandboxSelector] && !path.length) {
  969. #pragma clang diagnostic pop
  970. // Distributed via TestFlight
  971. return defaultAppTypeProd;
  972. }
  973. }
  974. NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:path options:0 error:&error];
  975. if (!profileData.length || error) {
  976. NSString *errorString =
  977. [NSString stringWithFormat:@"Error while reading embedded mobileprovision %@", error];
  978. [self logAPNSConfigurationError:errorString];
  979. return defaultAppTypeProd;
  980. }
  981. // The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the
  982. // end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit
  983. // ASCII. Replace any 0s or invalid characters in the input.
  984. uint8_t *profileBytes = (uint8_t *)profileData.bytes;
  985. for (int i = 0; i < profileData.length; i++) {
  986. uint8_t currentByte = profileBytes[i];
  987. if (!currentByte || currentByte > 127) {
  988. profileBytes[i] = '.';
  989. }
  990. }
  991. NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes
  992. length:profileData.length
  993. encoding:NSASCIIStringEncoding
  994. freeWhenDone:NO];
  995. if (error || !embeddedProfile.length) {
  996. NSString *errorString =
  997. [NSString stringWithFormat:@"Error while reading embedded mobileprovision %@", error];
  998. [self logAPNSConfigurationError:errorString];
  999. return defaultAppTypeProd;
  1000. }
  1001. NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile];
  1002. NSString *plistContents;
  1003. if ([scanner scanUpToString:@"<plist" intoString:nil]) {
  1004. if ([scanner scanUpToString:@"</plist>" intoString:&plistContents]) {
  1005. plistContents = [plistContents stringByAppendingString:@"</plist>"];
  1006. }
  1007. }
  1008. if (!plistContents.length) {
  1009. return defaultAppTypeProd;
  1010. }
  1011. NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding];
  1012. if (!data.length) {
  1013. [self logAPNSConfigurationError:@"Couldn't read plist fetched from embedded mobileprovision"];
  1014. return defaultAppTypeProd;
  1015. }
  1016. NSError *plistMapError;
  1017. id plistData = [NSPropertyListSerialization propertyListWithData:data
  1018. options:NSPropertyListImmutable
  1019. format:nil
  1020. error:&plistMapError];
  1021. if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) {
  1022. NSString *errorString =
  1023. [NSString stringWithFormat:@"Error while converting assumed plist to dict %@",
  1024. plistMapError.localizedDescription];
  1025. [self logAPNSConfigurationError:errorString];
  1026. return defaultAppTypeProd;
  1027. }
  1028. NSDictionary *plistMap = (NSDictionary *)plistData;
  1029. if ([plistMap valueForKeyPath:@"ProvisionedDevices"]) {
  1030. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID012,
  1031. @"Provisioning profile has specifically provisioned devices, "
  1032. @"most likely a Dev profile.");
  1033. }
  1034. NSString *apsEnvironment = [plistMap valueForKeyPath:kEntitlementsAPSEnvironmentKey];
  1035. NSString *debugString __unused =
  1036. [NSString stringWithFormat:@"APNS Environment in profile: %@", apsEnvironment];
  1037. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID013, @"%@", debugString);
  1038. // No aps-environment in the profile.
  1039. if (!apsEnvironment.length) {
  1040. [self logAPNSConfigurationError:@"No aps-environment set. If testing on a device APNS is not "
  1041. @"correctly configured. Please recheck your provisioning "
  1042. @"profiles. If testing on a simulator this is fine since APNS "
  1043. @"doesn't work on the simulator."];
  1044. return defaultAppTypeProd;
  1045. }
  1046. if ([apsEnvironment isEqualToString:kAPSEnvironmentDevelopmentValue]) {
  1047. return NO;
  1048. }
  1049. return defaultAppTypeProd;
  1050. }
  1051. /// Log error messages only when Messaging exists in the pod.
  1052. - (void)logAPNSConfigurationError:(NSString *)errorString {
  1053. BOOL hasFirebaseMessaging = NSClassFromString(kFIRInstanceIDFCMSDKClassString) != nil;
  1054. if (hasFirebaseMessaging) {
  1055. FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeInstanceID014, @"%@", errorString);
  1056. } else {
  1057. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInstanceID015, @"%@", errorString);
  1058. }
  1059. }
  1060. @end