Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2017 Google
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
 
15
#include <sys/utsname.h>
16
 
17
#if __has_include(<UIKit/UIKit.h>)
18
#import <UIKit/UIKit.h>
19
#endif
20
 
21
#if __has_include(<AppKit/AppKit.h>)
22
#import <AppKit/AppKit.h>
23
#endif
24
 
25
#import "FirebaseCore/Sources/Public/FirebaseCore/FIRApp.h"
26
 
27
#import "FirebaseCore/Sources/FIRAnalyticsConfiguration.h"
28
#import "FirebaseCore/Sources/FIRBundleUtil.h"
29
#import "FirebaseCore/Sources/FIRComponentContainerInternal.h"
30
#import "FirebaseCore/Sources/FIRConfigurationInternal.h"
31
#import "FirebaseCore/Sources/FIRFirebaseUserAgent.h"
32
 
33
#import "FirebaseCore/Sources/Private/FIRAppInternal.h"
34
#import "FirebaseCore/Sources/Private/FIRCoreDiagnosticsConnector.h"
35
#import "FirebaseCore/Sources/Private/FIRLibrary.h"
36
#import "FirebaseCore/Sources/Private/FIRLogger.h"
37
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
38
#import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h"
39
 
40
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
41
 
42
#import <objc/runtime.h>
43
 
44
// The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics
45
// pod to a Core dependency. These symbols are not used and should be deleted after the transition.
46
NSString *const kFIRServiceAdMob;
47
NSString *const kFIRServiceAuth;
48
NSString *const kFIRServiceAuthUI;
49
NSString *const kFIRServiceCrash;
50
NSString *const kFIRServiceDatabase;
51
NSString *const kFIRServiceDynamicLinks;
52
NSString *const kFIRServiceFirestore;
53
NSString *const kFIRServiceFunctions;
54
NSString *const kFIRServiceInstanceID;
55
NSString *const kFIRServiceInvites;
56
NSString *const kFIRServiceMessaging;
57
NSString *const kFIRServiceMeasurement;
58
NSString *const kFIRServicePerformance;
59
NSString *const kFIRServiceRemoteConfig;
60
NSString *const kFIRServiceStorage;
61
NSString *const kGGLServiceAnalytics;
62
NSString *const kGGLServiceSignIn;
63
 
64
NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT";
65
NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification";
66
NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification";
67
NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
68
NSString *const kFIRAppNameKey = @"FIRAppNameKey";
69
NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
70
 
71
NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
72
    @"/google/firebase/global_data_collection_enabled:%@";
73
NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
74
    @"FirebaseDataCollectionDefaultEnabled";
75
 
76
NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
77
NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
78
NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
79
NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
80
NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
81
NSString *const kFIRAppDiagnosticsApplePlatformPrefix = @"apple-platform";
82
 
83
// Auth internal notification notification and key.
84
NSString *const FIRAuthStateDidChangeInternalNotification =
85
    @"FIRAuthStateDidChangeInternalNotification";
86
NSString *const FIRAuthStateDidChangeInternalNotificationAppKey =
87
    @"FIRAuthStateDidChangeInternalNotificationAppKey";
88
NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey =
89
    @"FIRAuthStateDidChangeInternalNotificationTokenKey";
90
NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
91
    @"FIRAuthStateDidChangeInternalNotificationUIDKey";
92
 
93
/**
94
 * Error domain for exceptions and NSError construction.
95
 */
96
NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core";
97
 
98
/** The NSUserDefaults suite name for FirebaseCore, for those storage locations that use it. */
99
NSString *const kFirebaseCoreDefaultsSuiteName = @"com.firebase.core";
100
 
101
/**
102
 * The URL to download plist files.
103
 */
104
static NSString *const kPlistURL = @"https://console.firebase.google.com/";
105
 
106
/**
107
 * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle
108
 * events from Core.
109
 */
110
static NSMutableArray<Class<FIRLibrary>> *sRegisteredAsConfigurable;
111
 
112
@interface FIRApp ()
113
 
114
#ifdef DEBUG
115
@property(nonatomic) BOOL alreadyOutputDataCollectionFlag;
116
#endif  // DEBUG
117
 
118
@end
119
 
120
@implementation FIRApp
121
 
122
// This is necessary since our custom getter prevents `_options` from being created.
123
@synthesize options = _options;
124
 
125
static NSMutableDictionary *sAllApps;
126
static FIRApp *sDefaultApp;
127
 
128
+ (void)configure {
129
  FIROptions *options = [FIROptions defaultOptions];
130
  if (!options) {
131
    [NSException raise:kFirebaseCoreErrorDomain
132
                format:@"`FirebaseApp.configure()` could not find "
133
                       @"a valid GoogleService-Info.plist in your project. Please download one "
134
                       @"from %@.",
135
                       kPlistURL];
136
  }
137
  [FIRApp configureWithOptions:options];
138
}
139
 
140
+ (void)configureWithOptions:(FIROptions *)options {
141
  if (!options) {
142
    [NSException raise:kFirebaseCoreErrorDomain
143
                format:@"Options is nil. Please pass a valid options."];
144
  }
145
  [FIRApp configureWithName:kFIRDefaultAppName options:options];
146
}
147
 
148
+ (NSCharacterSet *)applicationNameAllowedCharacters {
149
  static NSCharacterSet *applicationNameAllowedCharacters;
150
  static dispatch_once_t onceToken;
151
  dispatch_once(&onceToken, ^{
152
    NSMutableCharacterSet *allowedNameCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
153
    [allowedNameCharacters addCharactersInString:@"-_"];
154
    applicationNameAllowedCharacters = [allowedNameCharacters copy];
155
  });
156
  return applicationNameAllowedCharacters;
157
}
158
 
159
+ (void)configureWithName:(NSString *)name options:(FIROptions *)options {
160
  if (!name || !options) {
161
    [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."];
162
  }
163
  if (name.length == 0) {
164
    [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."];
165
  }
166
 
167
  if ([name isEqualToString:kFIRDefaultAppName]) {
168
    if (sDefaultApp) {
169
      // The default app already exists. Handle duplicate `configure` calls and return.
170
      [self appWasConfiguredTwice:sDefaultApp usingOptions:options];
171
      return;
172
    }
173
 
174
    FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
175
  } else {
176
    // Validate the app name and ensure it hasn't been configured already.
177
    NSCharacterSet *nameCharacters = [NSCharacterSet characterSetWithCharactersInString:name];
178
 
179
    if (![[self applicationNameAllowedCharacters] isSupersetOfSet:nameCharacters]) {
180
      [NSException raise:kFirebaseCoreErrorDomain
181
                  format:@"App name can only contain alphanumeric, "
182
                         @"hyphen (-), and underscore (_) characters"];
183
    }
184
 
185
    @synchronized(self) {
186
      if (sAllApps && sAllApps[name]) {
187
        // The app already exists. Handle a duplicate `configure` call and return.
188
        [self appWasConfiguredTwice:sAllApps[name] usingOptions:options];
189
        return;
190
      }
191
    }
192
 
193
    FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name);
194
  }
195
 
196
  @synchronized(self) {
197
    FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options];
198
    if (app.isDefaultApp) {
199
      sDefaultApp = app;
200
    }
201
 
202
    [FIRApp addAppToAppDictionary:app];
203
 
204
    // The FIRApp instance is ready to go, `sDefaultApp` is assigned, other SDKs are now ready to be
205
    // instantiated.
206
    [app.container instantiateEagerComponents];
207
    [FIRApp sendNotificationsToSDKs:app];
208
  }
209
}
210
 
211
/// Called when `configure` has been called multiple times for the same app. This can either throw
212
/// an exception (most cases) or ignore the duplicate configuration in situations where it's allowed
213
/// like an extension.
214
+ (void)appWasConfiguredTwice:(FIRApp *)app usingOptions:(FIROptions *)options {
215
  // Only extensions should potentially be able to call `configure` more than once.
216
  if (![GULAppEnvironmentUtil isAppExtension]) {
217
    // Throw an exception since this is now an invalid state.
218
    if (app.isDefaultApp) {
219
      [NSException raise:kFirebaseCoreErrorDomain
220
                  format:@"Default app has already been configured."];
221
    } else {
222
      [NSException raise:kFirebaseCoreErrorDomain
223
                  format:@"App named %@ has already been configured.", app.name];
224
    }
225
  }
226
 
227
  // In an extension, the entry point could be called multiple times. As long as the options are
228
  // identical we should allow multiple `configure` calls.
229
  if ([options isEqual:app.options]) {
230
    // Everything is identical but the extension's lifecycle triggered `configure` twice.
231
    // Ignore duplicate calls and return since everything should still be in a valid state.
232
    FIRLogDebug(kFIRLoggerCore, @"I-COR000035",
233
                @"Ignoring second `configure` call in an extension.");
234
    return;
235
  } else {
236
    [NSException raise:kFirebaseCoreErrorDomain
237
                format:@"App named %@ has already been configured.", app.name];
238
  }
239
}
240
 
241
+ (FIRApp *)defaultApp {
242
  if (sDefaultApp) {
243
    return sDefaultApp;
244
  }
245
  FIRLogError(kFIRLoggerCore, @"I-COR000003",
246
              @"The default Firebase app has not yet been "
247
              @"configured. Add `FirebaseApp.configure()` to your "
248
              @"application initialization. This can be done in "
249
              @"in the App Delegate's application(_:didFinishLaunchingWithOptions:)` "
250
              @"(or the `@main` struct's initializer in SwiftUI). "
251
              @"Read more: https://goo.gl/ctyzm8.");
252
  return nil;
253
}
254
 
255
+ (FIRApp *)appNamed:(NSString *)name {
256
  @synchronized(self) {
257
    if (sAllApps) {
258
      FIRApp *app = sAllApps[name];
259
      if (app) {
260
        return app;
261
      }
262
    }
263
    FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name);
264
    return nil;
265
  }
266
}
267
 
268
+ (NSDictionary *)allApps {
269
  @synchronized(self) {
270
    if (!sAllApps) {
271
      FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet.");
272
    }
273
    return [sAllApps copy];
274
  }
275
}
276
 
277
// Public only for tests
278
+ (void)resetApps {
279
  @synchronized(self) {
280
    sDefaultApp = nil;
281
    [sAllApps removeAllObjects];
282
    sAllApps = nil;
283
    [[self userAgent] reset];
284
  }
285
}
286
 
287
- (void)deleteApp:(FIRAppVoidBoolCallback)completion {
288
  @synchronized([self class]) {
289
    if (sAllApps && sAllApps[self.name]) {
290
      FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
291
 
292
      // Remove all registered libraries from the container to avoid creating new instances.
293
      [self.container removeAllComponents];
294
      // Remove all cached instances from the container before deleting the app.
295
      [self.container removeAllCachedInstances];
296
 
297
      [sAllApps removeObjectForKey:self.name];
298
      [self clearDataCollectionSwitchFromUserDefaults];
299
      if ([self.name isEqualToString:kFIRDefaultAppName]) {
300
        sDefaultApp = nil;
301
      }
302
      NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name};
303
      [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification
304
                                                          object:[self class]
305
                                                        userInfo:appInfoDict];
306
      completion(YES);
307
    } else {
308
      FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist.");
309
      completion(NO);
310
    }
311
  }
312
}
313
 
314
+ (void)addAppToAppDictionary:(FIRApp *)app {
315
  if (!sAllApps) {
316
    sAllApps = [NSMutableDictionary dictionary];
317
  }
318
  if ([app configureCore]) {
319
    sAllApps[app.name] = app;
320
  } else {
321
    [NSException raise:kFirebaseCoreErrorDomain
322
                format:@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in "
323
                       @"GoogleService-Info.plist or set in the customized options."];
324
  }
325
}
326
 
327
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options {
328
  self = [super init];
329
  if (self) {
330
    _name = [name copy];
331
    _options = [options copy];
332
    _options.editingLocked = YES;
333
    _isDefaultApp = [name isEqualToString:kFIRDefaultAppName];
334
    _container = [[FIRComponentContainer alloc] initWithApp:self];
335
  }
336
  return self;
337
}
338
 
339
- (void)dealloc {
340
  [[NSNotificationCenter defaultCenter] removeObserver:self];
341
}
342
 
343
- (BOOL)configureCore {
344
  [self checkExpectedBundleID];
345
  if (![self isAppIDValid]) {
346
    return NO;
347
  }
348
 
349
  // Initialize the Analytics once there is a valid options under default app. Analytics should
350
  // always initialize first by itself before the other SDKs.
351
  if ([self.name isEqualToString:kFIRDefaultAppName]) {
352
    Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics");
353
    if (firAnalyticsClass) {
354
#pragma clang diagnostic push
355
#pragma clang diagnostic ignored "-Wundeclared-selector"
356
      SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:);
357
#pragma clang diagnostic pop
358
      if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) {
359
#pragma clang diagnostic push
360
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
361
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
362
        [firAnalyticsClass performSelector:startWithConfigurationSelector
363
                                withObject:[FIRConfiguration sharedInstance].analyticsConfiguration
364
                                withObject:_options];
365
#pragma clang diagnostic pop
366
      }
367
    }
368
  }
369
 
370
  [self subscribeForAppDidBecomeActiveNotifications];
371
 
372
  return YES;
373
}
374
 
375
- (FIROptions *)options {
376
  return [_options copy];
377
}
378
 
379
- (void)setDataCollectionDefaultEnabled:(BOOL)dataCollectionDefaultEnabled {
380
#ifdef DEBUG
381
  FIRLogDebug(kFIRLoggerCore, @"I-COR000034", @"Explicitly %@ data collection flag.",
382
              dataCollectionDefaultEnabled ? @"enabled" : @"disabled");
383
  self.alreadyOutputDataCollectionFlag = YES;
384
#endif  // DEBUG
385
 
386
  NSString *key =
387
      [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
388
  [[NSUserDefaults standardUserDefaults] setBool:dataCollectionDefaultEnabled forKey:key];
389
 
390
  // Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set
391
  // within FIROptions and change the Analytics value if necessary. Analytics only works with the
392
  // default app, so return if this isn't the default app.
393
  if (!self.isDefaultApp) {
394
    return;
395
  }
396
 
397
  // Check if the Analytics flag is explicitly set. If so, no further actions are necessary.
398
  if ([self.options isAnalyticsCollectionExplicitlySet]) {
399
    return;
400
  }
401
 
402
  // The Analytics flag has not been explicitly set, so update with the value being set.
403
#pragma clang diagnostic push
404
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
405
  [[FIRAnalyticsConfiguration sharedInstance]
406
      setAnalyticsCollectionEnabled:dataCollectionDefaultEnabled
407
                     persistSetting:NO];
408
#pragma clang diagnostic pop
409
}
410
 
411
- (BOOL)isDataCollectionDefaultEnabled {
412
  // Check if it's been manually set before in code, and use that as the higher priority value.
413
  NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
414
  if (defaultsObject != nil) {
415
#ifdef DEBUG
416
    if (!self.alreadyOutputDataCollectionFlag) {
417
      FIRLogDebug(kFIRLoggerCore, @"I-COR000031", @"Data Collection flag is %@ in user defaults.",
418
                  [defaultsObject boolValue] ? @"enabled" : @"disabled");
419
      self.alreadyOutputDataCollectionFlag = YES;
420
    }
421
#endif  // DEBUG
422
    return [defaultsObject boolValue];
423
  }
424
 
425
  // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
426
  // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
427
  // no performance impact calling multiple times.
428
  NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
429
  if (collectionEnabledPlistValue != nil) {
430
#ifdef DEBUG
431
    if (!self.alreadyOutputDataCollectionFlag) {
432
      FIRLogDebug(kFIRLoggerCore, @"I-COR000032", @"Data Collection flag is %@ in plist.",
433
                  [collectionEnabledPlistValue boolValue] ? @"enabled" : @"disabled");
434
      self.alreadyOutputDataCollectionFlag = YES;
435
    }
436
#endif  // DEBUG
437
    return [collectionEnabledPlistValue boolValue];
438
  }
439
 
440
#ifdef DEBUG
441
  if (!self.alreadyOutputDataCollectionFlag) {
442
    FIRLogDebug(kFIRLoggerCore, @"I-COR000033", @"Data Collection flag is not set.");
443
    self.alreadyOutputDataCollectionFlag = YES;
444
  }
445
#endif  // DEBUG
446
  return YES;
447
}
448
 
449
#pragma mark - private
450
 
451
+ (void)sendNotificationsToSDKs:(FIRApp *)app {
452
  // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`.
453
  NSNumber *isDefaultApp = [NSNumber numberWithBool:app.isDefaultApp];
454
  NSDictionary *appInfoDict = @{
455
    kFIRAppNameKey : app.name,
456
    kFIRAppIsDefaultAppKey : isDefaultApp,
457
    kFIRGoogleAppIDKey : app.options.googleAppID
458
  };
459
  [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
460
                                                      object:self
461
                                                    userInfo:appInfoDict];
462
 
463
  // This is the new way of sending information to SDKs.
464
  // TODO: Do we want this on a background thread, maybe?
465
  @synchronized(self) {
466
    for (Class<FIRLibrary> library in sRegisteredAsConfigurable) {
467
      [library configureWithApp:app];
468
    }
469
  }
470
}
471
 
472
+ (NSError *)errorForMissingOptions {
473
  NSDictionary *errorDict = @{
474
    NSLocalizedDescriptionKey :
475
        @"Unable to parse GoogleService-Info.plist in order to configure services.",
476
    NSLocalizedRecoverySuggestionErrorKey :
477
        @"Check formatting and location of GoogleService-Info.plist."
478
  };
479
  return [NSError errorWithDomain:kFirebaseCoreErrorDomain code:-100 userInfo:errorDict];
480
}
481
 
482
+ (NSError *)errorForInvalidAppID {
483
  NSDictionary *errorDict = @{
484
    NSLocalizedDescriptionKey : @"Unable to validate Google App ID",
485
    NSLocalizedRecoverySuggestionErrorKey :
486
        @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
487
        @"customized options."
488
  };
489
  return [NSError errorWithDomain:kFirebaseCoreErrorDomain code:-101 userInfo:errorDict];
490
}
491
 
492
+ (BOOL)isDefaultAppConfigured {
493
  return (sDefaultApp != nil);
494
}
495
 
496
+ (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version {
497
  // Create the set of characters which aren't allowed, only if this feature is used.
498
  NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet];
499
  [allowedSet addCharactersInString:@"-_."];
500
  NSCharacterSet *disallowedSet = [allowedSet invertedSet];
501
  // Make sure the library name and version strings do not contain unexpected characters, and
502
  // add the name/version pair to the dictionary.
503
  if ([name rangeOfCharacterFromSet:disallowedSet].location == NSNotFound &&
504
      [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) {
505
    [[self userAgent] setValue:version forComponent:name];
506
  } else {
507
    FIRLogError(kFIRLoggerCore, @"I-COR000027",
508
                @"The library name (%@) or version number (%@) contain invalid characters. "
509
                @"Only alphanumeric, dash, underscore and period characters are allowed.",
510
                name, version);
511
  }
512
}
513
 
514
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
515
                       withName:(nonnull NSString *)name {
516
  [self registerInternalLibrary:library withName:name withVersion:FIRFirebaseVersion()];
517
}
518
 
519
+ (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
520
                       withName:(nonnull NSString *)name
521
                    withVersion:(nonnull NSString *)version {
522
  // This is called at +load time, keep the work to a minimum.
523
 
524
  // Ensure the class given conforms to the proper protocol.
525
  if (![(Class)library conformsToProtocol:@protocol(FIRLibrary)] ||
526
      ![(Class)library respondsToSelector:@selector(componentsToRegister)]) {
527
    [NSException raise:NSInvalidArgumentException
528
                format:@"Class %@ attempted to register components, but it does not conform to "
529
                       @"`FIRLibrary or provide a `componentsToRegister:` method.",
530
                       library];
531
  }
532
 
533
  [FIRComponentContainer registerAsComponentRegistrant:library];
534
  if ([(Class)library respondsToSelector:@selector(configureWithApp:)]) {
535
    static dispatch_once_t onceToken;
536
    dispatch_once(&onceToken, ^{
537
      sRegisteredAsConfigurable = [[NSMutableArray alloc] init];
538
    });
539
    @synchronized(self) {
540
      [sRegisteredAsConfigurable addObject:library];
541
    }
542
  }
543
  [self registerLibrary:name withVersion:version];
544
}
545
 
546
+ (FIRFirebaseUserAgent *)userAgent {
547
  static dispatch_once_t onceToken;
548
  static FIRFirebaseUserAgent *_userAgent;
549
  dispatch_once(&onceToken, ^{
550
    _userAgent = [[FIRFirebaseUserAgent alloc] init];
551
    [_userAgent setValue:FIRFirebaseVersion() forComponent:@"fire-ios"];
552
  });
553
  return _userAgent;
554
}
555
 
556
+ (NSString *)firebaseUserAgent {
557
  return [[self userAgent] firebaseUserAgent];
558
}
559
 
560
- (void)checkExpectedBundleID {
561
  NSArray *bundles = [FIRBundleUtil relevantBundles];
562
  NSString *expectedBundleID = [self expectedBundleID];
563
  // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for
564
  // backward compatibility.
565
  if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifierPrefix:expectedBundleID
566
                                                                 inBundles:bundles]) {
567
    FIRLogError(kFIRLoggerCore, @"I-COR000008",
568
                @"The project's Bundle ID is inconsistent with "
569
                @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are "
570
                @"using a customized options. To ensure that everything can be configured "
571
                @"correctly, you may need to make the Bundle IDs consistent. To continue with this "
572
                @"plist file, you may change your app's bundle identifier to '%@'. Or you can "
573
                @"download a new configuration file that matches your bundle identifier from %@ "
574
                @"and replace the current one.",
575
                kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
576
  }
577
}
578
 
579
#pragma mark - private - App ID Validation
580
 
581
/**
582
 * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file.
583
 * This is the main method for validating app ID.
584
 *
585
 * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise.
586
 */
587
- (BOOL)isAppIDValid {
588
  NSString *appID = _options.googleAppID;
589
  BOOL isValid = [FIRApp validateAppID:appID];
590
  if (!isValid) {
591
    NSString *expectedBundleID = [self expectedBundleID];
592
    FIRLogError(kFIRLoggerCore, @"I-COR000009",
593
                @"The GOOGLE_APP_ID either in the plist file "
594
                @"'%@.%@' or the one set in the customized options is invalid. If you are using "
595
                @"the plist file, use the iOS version of bundle identifier to download the file, "
596
                @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle "
597
                @"identifier to '%@'. Or you can download a new configuration file that matches "
598
                @"your bundle identifier from %@ and replace the current one.",
599
                kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
600
  };
601
  return isValid;
602
}
603
 
604
+ (BOOL)validateAppID:(NSString *)appID {
605
  // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not
606
  // have a valid fingerprint, otherwise we just warn about the potential issue.
607
  if (!appID.length) {
608
    return NO;
609
  }
610
 
611
  NSScanner *stringScanner = [NSScanner scannerWithString:appID];
612
  stringScanner.charactersToBeSkipped = nil;
613
 
614
  NSString *appIDVersion;
615
  if (![stringScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet]
616
                                 intoString:&appIDVersion]) {
617
    return NO;
618
  }
619
 
620
  if (![stringScanner scanString:@":" intoString:NULL]) {
621
    // appIDVersion must be separated by ":"
622
    return NO;
623
  }
624
 
625
  NSArray *knownVersions = @[ @"1" ];
626
  if (![knownVersions containsObject:appIDVersion]) {
627
    // Permit unknown yet properly formatted app ID versions.
628
    FIRLogInfo(kFIRLoggerCore, @"I-COR000010", @"Unknown GOOGLE_APP_ID version: %@", appIDVersion);
629
    return YES;
630
  }
631
 
632
  if (![self validateAppIDFormat:appID withVersion:appIDVersion]) {
633
    return NO;
634
  }
635
 
636
  if (![self validateAppIDFingerprint:appID withVersion:appIDVersion]) {
637
    return NO;
638
  }
639
 
640
  return YES;
641
}
642
 
643
+ (NSString *)actualBundleID {
644
  return [[NSBundle mainBundle] bundleIdentifier];
645
}
646
 
647
/**
648
 * Validates that the format of the app ID string is what is expected based on the supplied version.
649
 * The version must end in ":".
650
 *
651
 * For v1 app ids the format is expected to be
652
 * '<version #>:<project number>:ios:<fingerprint of bundle id>'.
653
 *
654
 * This method does not verify that the contents of the app id are correct, just that they fulfill
655
 * the expected format.
656
 *
657
 * @param appID Contents of GOOGLE_APP_ID from the plist file.
658
 * @param version Indicates what version of the app id format this string should be.
659
 * @return YES if provided string fufills the expected format, NO otherwise.
660
 */
661
+ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version {
662
  if (!appID.length || !version.length) {
663
    return NO;
664
  }
665
 
666
  NSScanner *stringScanner = [NSScanner scannerWithString:appID];
667
  stringScanner.charactersToBeSkipped = nil;
668
 
669
  // Skip version part
670
  // '*<version #>*:<project number>:ios:<fingerprint of bundle id>'
671
  if (![stringScanner scanString:version intoString:NULL]) {
672
    // The version part is missing or mismatched
673
    return NO;
674
  }
675
 
676
  // Validate version part (see part between '*' symbols below)
677
  // '<version #>*:*<project number>:ios:<fingerprint of bundle id>'
678
  if (![stringScanner scanString:@":" intoString:NULL]) {
679
    // appIDVersion must be separated by ":"
680
    return NO;
681
  }
682
 
683
  // Validate version part (see part between '*' symbols below)
684
  // '<version #>:*<project number>*:ios:<fingerprint of bundle id>'.
685
  NSInteger projectNumber = NSNotFound;
686
  if (![stringScanner scanInteger:&projectNumber]) {
687
    // NO project number found.
688
    return NO;
689
  }
690
 
691
  // Validate version part (see part between '*' symbols below)
692
  // '<version #>:<project number>*:*ios:<fingerprint of bundle id>'.
693
  if (![stringScanner scanString:@":" intoString:NULL]) {
694
    // The project number must be separated by ":"
695
    return NO;
696
  }
697
 
698
  // Validate version part (see part between '*' symbols below)
699
  // '<version #>:<project number>:*ios*:<fingerprint of bundle id>'.
700
  NSString *platform;
701
  if (![stringScanner scanUpToString:@":" intoString:&platform]) {
702
    return NO;
703
  }
704
 
705
  if (![platform isEqualToString:@"ios"]) {
706
    // The platform must be @"ios"
707
    return NO;
708
  }
709
 
710
  // Validate version part (see part between '*' symbols below)
711
  // '<version #>:<project number>:ios*:*<fingerprint of bundle id>'.
712
  if (![stringScanner scanString:@":" intoString:NULL]) {
713
    // The platform must be separated by ":"
714
    return NO;
715
  }
716
 
717
  // Validate version part (see part between '*' symbols below)
718
  // '<version #>:<project number>:ios:*<fingerprint of bundle id>*'.
719
  unsigned long long fingerprint = NSNotFound;
720
  if (![stringScanner scanHexLongLong:&fingerprint]) {
721
    // Fingerprint part is missing
722
    return NO;
723
  }
724
 
725
  if (!stringScanner.isAtEnd) {
726
    // There are not allowed characters in the fingerprint part
727
    return NO;
728
  }
729
 
730
  return YES;
731
}
732
 
733
/**
734
 * Validates that the fingerprint of the app ID string is what is expected based on the supplied
735
 * version.
736
 *
737
 * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated.
738
 *
739
 * @param appID Contents of GOOGLE_APP_ID from the plist file.
740
 * @param version Indicates what version of the app id format this string should be.
741
 * @return YES if provided string fufills the expected fingerprint and the version is known, NO
742
 *         otherwise.
743
 */
744
+ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version {
745
  // Extract the supplied fingerprint from the supplied app ID.
746
  // This assumes the app ID format is the same for all known versions below. If the app ID format
747
  // changes in future versions, the tokenizing of the app ID format will need to take into account
748
  // the version of the app ID.
749
  NSArray *components = [appID componentsSeparatedByString:@":"];
750
  if (components.count != 4) {
751
    return NO;
752
  }
753
 
754
  NSString *suppliedFingerprintString = components[3];
755
  if (!suppliedFingerprintString.length) {
756
    return NO;
757
  }
758
 
759
  uint64_t suppliedFingerprint;
760
  NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString];
761
  if (![scanner scanHexLongLong:&suppliedFingerprint]) {
762
    return NO;
763
  }
764
 
765
  if ([version isEqual:@"1"]) {
766
    // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated.
767
    return YES;
768
  }
769
 
770
  // Unknown version.
771
  return NO;
772
}
773
 
774
- (NSString *)expectedBundleID {
775
  return _options.bundleID;
776
}
777
 
778
// end App ID validation
779
 
780
#pragma mark - Reading From Plist & User Defaults
781
 
782
/**
783
 * Clears the data collection switch from the standard NSUserDefaults for easier testing and
784
 * readability.
785
 */
786
- (void)clearDataCollectionSwitchFromUserDefaults {
787
  NSString *key =
788
      [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
789
  [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
790
}
791
 
792
/**
793
 * Reads the data collection switch from the standard NSUserDefaults for easier testing and
794
 * readability.
795
 */
796
+ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
797
  // Read the object in user defaults, and only return if it's an NSNumber.
798
  NSString *key =
799
      [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
800
  id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
801
  if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
802
    return collectionEnabledDefaultsObject;
803
  }
804
 
805
  return nil;
806
}
807
 
808
/**
809
 * Reads the data collection switch from the Info.plist for easier testing and readability. Will
810
 * only read once from the plist and return the cached value.
811
 */
812
+ (nullable NSNumber *)readDataCollectionSwitchFromPlist {
813
  static NSNumber *collectionEnabledPlistObject;
814
  static dispatch_once_t onceToken;
815
  dispatch_once(&onceToken, ^{
816
    // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
817
    id plistValue = [[NSBundle mainBundle]
818
        objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
819
    if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
820
      collectionEnabledPlistObject = (NSNumber *)plistValue;
821
    }
822
  });
823
 
824
  return collectionEnabledPlistObject;
825
}
826
 
827
#pragma mark - App Life Cycle
828
 
829
- (void)subscribeForAppDidBecomeActiveNotifications {
830
#if TARGET_OS_IOS || TARGET_OS_TV
831
  NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification;
832
#elif TARGET_OS_OSX
833
  NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification;
834
#endif
835
 
836
#if !TARGET_OS_WATCH
837
  [[NSNotificationCenter defaultCenter] addObserver:self
838
                                           selector:@selector(appDidBecomeActive:)
839
                                               name:notificationName
840
                                             object:nil];
841
#endif
842
}
843
 
844
- (void)appDidBecomeActive:(NSNotification *)notification {
845
  [self logCoreTelemetryIfEnabled];
846
}
847
 
848
- (void)logCoreTelemetryIfEnabled {
849
  if ([self isDataCollectionDefaultEnabled]) {
850
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
851
      [FIRCoreDiagnosticsConnector logCoreTelemetryWithOptions:[self options]];
852
    });
853
  }
854
}
855
 
856
@end