1 // Copyright 2017 Google
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
15 #import "Private/FIRAppInternal.h"
16 #import "Private/FIRBundleUtil.h"
17 #import "Private/FIRErrors.h"
18 #import "Private/FIRLogger.h"
19 #import "Private/FIROptionsInternal.h"
21 // Keys for the strings in the plist file.
22 NSString *const kFIRAPIKey = @"API_KEY";
23 NSString *const kFIRTrackingID = @"TRACKING_ID";
24 NSString *const kFIRGoogleAppID = @"GOOGLE_APP_ID";
25 NSString *const kFIRClientID = @"CLIENT_ID";
26 NSString *const kFIRGCMSenderID = @"GCM_SENDER_ID";
27 NSString *const kFIRAndroidClientID = @"ANDROID_CLIENT_ID";
28 NSString *const kFIRDatabaseURL = @"DATABASE_URL";
29 NSString *const kFIRStorageBucket = @"STORAGE_BUCKET";
30 // The key to locate the expected bundle identifier in the plist file.
31 NSString *const kFIRBundleID = @"BUNDLE_ID";
32 // The key to locate the project identifier in the plist file.
33 NSString *const kFIRProjectID = @"PROJECT_ID";
35 NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
36 NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
37 NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED";
39 NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED";
40 NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED";
42 // Library version ID.
43 NSString *const kFIRLibraryVersionID =
44 @"5" // Major version (one or more digits)
45 @"01" // Minor version (exactly 2 digits)
46 @"04" // Build number (exactly 2 digits)
47 @"000"; // Fixed "000"
49 NSString *const kServiceInfoFileName = @"GoogleService-Info";
51 NSString *const kServiceInfoFileType = @"plist";
53 // Exception raised from attempting to modify a FIROptions after it's been copied to a FIRApp.
54 NSString *const kFIRExceptionBadModification =
55 @"Attempted to modify options after it's set on FIRApp. Please modify all properties before "
56 @"initializing FIRApp.";
58 @interface FIROptions ()
61 * This property maintains the actual configuration key-value pairs.
63 @property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary;
66 * Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary.
67 * It combines analytics options from both the infoDictionary and the GoogleService-Info.plist.
68 * Values which are present in the main plist override values from the GoogleService-Info.plist.
70 @property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
73 * Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist.
74 * Values which are present in the infoDictionary override values from the GoogleService-Info.plist.
76 - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary;
79 * Throw exception if editing is locked when attempting to modify an option.
81 - (void)checkEditingLocked;
85 @implementation FIROptions {
86 /// Backing variable for self.analyticsOptionsDictionary.
87 NSDictionary *_analyticsOptionsDictionary;
90 static FIROptions *sDefaultOptions = nil;
91 static NSDictionary *sDefaultOptionsDictionary = nil;
93 #pragma mark - Public only for internal class methods
95 + (FIROptions *)defaultOptions {
96 if (sDefaultOptions != nil) {
97 return sDefaultOptions;
100 NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
101 if (defaultOptionsDictionary == nil) {
105 sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
106 return sDefaultOptions;
109 #pragma mark - Private class methods
112 // Report FirebaseCore version for useragent string
113 NSRange major = NSMakeRange(0, 1);
114 NSRange minor = NSMakeRange(1, 2);
115 NSRange patch = NSMakeRange(3, 2);
117 registerLibrary:@"fire-ios"
118 withVersion:[NSString stringWithFormat:@"%@.%d.%d",
119 [kFIRLibraryVersionID substringWithRange:major],
120 [[kFIRLibraryVersionID substringWithRange:minor]
122 [[kFIRLibraryVersionID substringWithRange:patch]
124 NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
125 NSString *xcodeVersion = info[@"DTXcodeBuild"];
126 NSString *sdkVersion = info[@"DTSDKBuild"];
128 [FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
131 [FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
135 + (NSDictionary *)defaultOptionsDictionary {
136 if (sDefaultOptionsDictionary != nil) {
137 return sDefaultOptionsDictionary;
139 NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
140 if (plistFilePath == nil) {
143 sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
144 if (sDefaultOptionsDictionary == nil) {
145 FIRLogError(kFIRLoggerCore, @"I-COR000011",
146 @"The configuration file is not a dictionary: "
148 kServiceInfoFileName, kServiceInfoFileType);
150 return sDefaultOptionsDictionary;
153 // Returns the path of the plist file with a given file name.
154 + (NSString *)plistFilePathWithName:(NSString *)fileName {
155 NSArray *bundles = [FIRBundleUtil relevantBundles];
156 NSString *plistFilePath =
157 [FIRBundleUtil optionsDictionaryPathWithResourceName:fileName
158 andFileType:kServiceInfoFileType
160 if (plistFilePath == nil) {
161 FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.",
162 fileName, kServiceInfoFileType);
164 return plistFilePath;
167 + (void)resetDefaultOptions {
168 sDefaultOptions = nil;
169 sDefaultOptionsDictionary = nil;
172 #pragma mark - Private instance methods
174 - (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary {
177 _optionsDictionary = [optionsDictionary mutableCopy];
178 _usingOptionsFromDefaultPlist = YES;
183 - (id)copyWithZone:(NSZone *)zone {
184 FIROptions *newOptions = [[[self class] allocWithZone:zone] init];
186 newOptions.optionsDictionary = self.optionsDictionary;
187 newOptions.deepLinkURLScheme = self.deepLinkURLScheme;
188 newOptions.editingLocked = self.isEditingLocked;
189 newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist;
194 #pragma mark - Public instance methods
196 - (instancetype)initWithContentsOfFile:(NSString *)plistPath {
199 if (plistPath == nil) {
200 FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil.");
203 _optionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:plistPath] mutableCopy];
204 if (_optionsDictionary == nil) {
205 FIRLogError(kFIRLoggerCore, @"I-COR000014",
206 @"The configuration file at %@ does not exist or "
207 @"is not a well-formed plist file.",
211 // TODO: Do we want to validate the dictionary here? It says we do that already in
212 // the public header.
217 - (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID {
220 NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary];
221 [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID];
222 [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID];
223 [mutableOptionsDict setValue:[[NSBundle mainBundle] bundleIdentifier] forKey:kFIRBundleID];
224 self.optionsDictionary = mutableOptionsDict;
229 - (NSString *)APIKey {
230 return self.optionsDictionary[kFIRAPIKey];
233 - (void)checkEditingLocked {
234 if (self.isEditingLocked) {
235 [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
239 - (void)setAPIKey:(NSString *)APIKey {
240 [self checkEditingLocked];
241 _optionsDictionary[kFIRAPIKey] = [APIKey copy];
244 - (NSString *)clientID {
245 return self.optionsDictionary[kFIRClientID];
248 - (void)setClientID:(NSString *)clientID {
249 [self checkEditingLocked];
250 _optionsDictionary[kFIRClientID] = [clientID copy];
253 - (NSString *)trackingID {
254 return self.optionsDictionary[kFIRTrackingID];
257 - (void)setTrackingID:(NSString *)trackingID {
258 [self checkEditingLocked];
259 _optionsDictionary[kFIRTrackingID] = [trackingID copy];
262 - (NSString *)GCMSenderID {
263 return self.optionsDictionary[kFIRGCMSenderID];
266 - (void)setGCMSenderID:(NSString *)GCMSenderID {
267 [self checkEditingLocked];
268 _optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy];
271 - (NSString *)projectID {
272 return self.optionsDictionary[kFIRProjectID];
275 - (void)setProjectID:(NSString *)projectID {
276 [self checkEditingLocked];
277 _optionsDictionary[kFIRProjectID] = [projectID copy];
280 - (NSString *)androidClientID {
281 return self.optionsDictionary[kFIRAndroidClientID];
284 - (void)setAndroidClientID:(NSString *)androidClientID {
285 [self checkEditingLocked];
286 _optionsDictionary[kFIRAndroidClientID] = [androidClientID copy];
289 - (NSString *)googleAppID {
290 return self.optionsDictionary[kFIRGoogleAppID];
293 - (void)setGoogleAppID:(NSString *)googleAppID {
294 [self checkEditingLocked];
295 _optionsDictionary[kFIRGoogleAppID] = [googleAppID copy];
298 - (NSString *)libraryVersionID {
299 return kFIRLibraryVersionID;
302 - (void)setLibraryVersionID:(NSString *)libraryVersionID {
303 _optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy];
306 - (NSString *)databaseURL {
307 return self.optionsDictionary[kFIRDatabaseURL];
310 - (void)setDatabaseURL:(NSString *)databaseURL {
311 [self checkEditingLocked];
313 _optionsDictionary[kFIRDatabaseURL] = [databaseURL copy];
316 - (NSString *)storageBucket {
317 return self.optionsDictionary[kFIRStorageBucket];
320 - (void)setStorageBucket:(NSString *)storageBucket {
321 [self checkEditingLocked];
322 _optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
325 - (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme {
326 [self checkEditingLocked];
327 _deepLinkURLScheme = [deepLinkURLScheme copy];
330 - (NSString *)bundleID {
331 return self.optionsDictionary[kFIRBundleID];
334 - (void)setBundleID:(NSString *)bundleID {
335 [self checkEditingLocked];
336 _optionsDictionary[kFIRBundleID] = [bundleID copy];
339 #pragma mark - Internal instance methods
341 - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
342 if (_analyticsOptionsDictionary == nil) {
343 NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
344 NSArray *measurementKeys = @[
345 kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled,
346 kFIRIsAnalyticsCollectionDeactivated
348 for (NSString *key in measurementKeys) {
349 id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
353 tempAnalyticsOptions[key] = value;
355 _analyticsOptionsDictionary = tempAnalyticsOptions;
357 return _analyticsOptionsDictionary;
360 - (NSDictionary *)analyticsOptionsDictionary {
361 return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary];
365 * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
366 * GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still
369 - (BOOL)isMeasurementEnabled {
370 if (self.isAnalyticsCollectionDeactivated) {
373 NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
375 // TODO: This could probably be cleaned up since FIROptions shouldn't know about FIRApp or have
376 // to check if it's the default app. The FIROptions instance can't be modified after
377 // `+configure` is called, so it's not a good place to copy it either in case the flag is
378 // changed at runtime.
380 // If no values are set for Analytics, fall back to the global collection switch in FIRApp.
381 // Analytics only supports the default FIRApp, so check that first.
382 if (![FIRApp isDefaultAppConfigured]) {
386 // Fall back to the default app's collection switch when the key is not in the dictionary.
387 return [FIRApp defaultApp].isDataCollectionDefaultEnabled;
389 return [value boolValue];
392 - (BOOL)isAnalyticsCollectionExpicitlySet {
393 // If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication
394 // that the developer wants FirebaseAnalytics enabled so continue checking.
395 if (self.isAnalyticsCollectionDeactivated) {
399 // Check if the current Analytics flag is set.
400 id collectionEnabledObject = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
401 if (collectionEnabledObject && [collectionEnabledObject isKindOfClass:[NSNumber class]]) {
402 // It doesn't matter what the value is, it's explicitly set.
406 // Check if the old measurement flag is set.
407 id measurementEnabledObject = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
408 if (measurementEnabledObject && [measurementEnabledObject isKindOfClass:[NSNumber class]]) {
409 // It doesn't matter what the value is, it's explicitly set.
413 // No flags are set to explicitly enable or disable FirebaseAnalytics.
417 - (BOOL)isAnalyticsCollectionEnabled {
418 if (self.isAnalyticsCollectionDeactivated) {
421 NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
423 return self.isMeasurementEnabled; // Fall back to older plist flag.
425 return [value boolValue];
428 - (BOOL)isAnalyticsCollectionDeactivated {
429 NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated];
431 return NO; // Analytics Collection is not deactivated when the key is not in the dictionary.
433 return [value boolValue];
436 - (BOOL)isAnalyticsEnabled {
437 return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue];
440 - (BOOL)isSignInEnabled {
441 return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue];