added iOS source code
[wl-app.git] / iOS / Pods / FirebaseCore / Firebase / Core / FIROptions.m
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 #import "Private/FIRAppInternal.h"
16 #import "Private/FIRBundleUtil.h"
17 #import "Private/FIRErrors.h"
18 #import "Private/FIRLogger.h"
19 #import "Private/FIROptionsInternal.h"
20
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";
34
35 NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
36 NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
37 NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED";
38
39 NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED";
40 NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED";
41
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"
48 // Plist file name.
49 NSString *const kServiceInfoFileName = @"GoogleService-Info";
50 // Plist file type.
51 NSString *const kServiceInfoFileType = @"plist";
52
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.";
57
58 @interface FIROptions ()
59
60 /**
61  * This property maintains the actual configuration key-value pairs.
62  */
63 @property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary;
64
65 /**
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.
69  */
70 @property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
71
72 /**
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.
75  */
76 - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary;
77
78 /**
79  * Throw exception if editing is locked when attempting to modify an option.
80  */
81 - (void)checkEditingLocked;
82
83 @end
84
85 @implementation FIROptions {
86   /// Backing variable for self.analyticsOptionsDictionary.
87   NSDictionary *_analyticsOptionsDictionary;
88 }
89
90 static FIROptions *sDefaultOptions = nil;
91 static NSDictionary *sDefaultOptionsDictionary = nil;
92
93 #pragma mark - Public only for internal class methods
94
95 + (FIROptions *)defaultOptions {
96   if (sDefaultOptions != nil) {
97     return sDefaultOptions;
98   }
99
100   NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
101   if (defaultOptionsDictionary == nil) {
102     return nil;
103   }
104
105   sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
106   return sDefaultOptions;
107 }
108
109 #pragma mark - Private class methods
110
111 + (void)initialize {
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);
116   [FIRApp
117       registerLibrary:@"fire-ios"
118           withVersion:[NSString stringWithFormat:@"%@.%d.%d",
119                                                  [kFIRLibraryVersionID substringWithRange:major],
120                                                  [[kFIRLibraryVersionID substringWithRange:minor]
121                                                      intValue],
122                                                  [[kFIRLibraryVersionID substringWithRange:patch]
123                                                      intValue]]];
124   NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
125   NSString *xcodeVersion = info[@"DTXcodeBuild"];
126   NSString *sdkVersion = info[@"DTSDKBuild"];
127   if (xcodeVersion) {
128     [FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
129   }
130   if (sdkVersion) {
131     [FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
132   }
133 }
134
135 + (NSDictionary *)defaultOptionsDictionary {
136   if (sDefaultOptionsDictionary != nil) {
137     return sDefaultOptionsDictionary;
138   }
139   NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
140   if (plistFilePath == nil) {
141     return nil;
142   }
143   sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
144   if (sDefaultOptionsDictionary == nil) {
145     FIRLogError(kFIRLoggerCore, @"I-COR000011",
146                 @"The configuration file is not a dictionary: "
147                 @"'%@.%@'.",
148                 kServiceInfoFileName, kServiceInfoFileType);
149   }
150   return sDefaultOptionsDictionary;
151 }
152
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
159                                                  inBundles:bundles];
160   if (plistFilePath == nil) {
161     FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.",
162                 fileName, kServiceInfoFileType);
163   }
164   return plistFilePath;
165 }
166
167 + (void)resetDefaultOptions {
168   sDefaultOptions = nil;
169   sDefaultOptionsDictionary = nil;
170 }
171
172 #pragma mark - Private instance methods
173
174 - (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary {
175   self = [super init];
176   if (self) {
177     _optionsDictionary = [optionsDictionary mutableCopy];
178     _usingOptionsFromDefaultPlist = YES;
179   }
180   return self;
181 }
182
183 - (id)copyWithZone:(NSZone *)zone {
184   FIROptions *newOptions = [[[self class] allocWithZone:zone] init];
185   if (newOptions) {
186     newOptions.optionsDictionary = self.optionsDictionary;
187     newOptions.deepLinkURLScheme = self.deepLinkURLScheme;
188     newOptions.editingLocked = self.isEditingLocked;
189     newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist;
190   }
191   return newOptions;
192 }
193
194 #pragma mark - Public instance methods
195
196 - (instancetype)initWithContentsOfFile:(NSString *)plistPath {
197   self = [super init];
198   if (self) {
199     if (plistPath == nil) {
200       FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil.");
201       return nil;
202     }
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.",
208                   plistPath);
209       return nil;
210     }
211     // TODO: Do we want to validate the dictionary here? It says we do that already in
212     // the public header.
213   }
214   return self;
215 }
216
217 - (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID {
218   self = [super init];
219   if (self) {
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;
225   }
226   return self;
227 }
228
229 - (NSString *)APIKey {
230   return self.optionsDictionary[kFIRAPIKey];
231 }
232
233 - (void)checkEditingLocked {
234   if (self.isEditingLocked) {
235     [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
236   }
237 }
238
239 - (void)setAPIKey:(NSString *)APIKey {
240   [self checkEditingLocked];
241   _optionsDictionary[kFIRAPIKey] = [APIKey copy];
242 }
243
244 - (NSString *)clientID {
245   return self.optionsDictionary[kFIRClientID];
246 }
247
248 - (void)setClientID:(NSString *)clientID {
249   [self checkEditingLocked];
250   _optionsDictionary[kFIRClientID] = [clientID copy];
251 }
252
253 - (NSString *)trackingID {
254   return self.optionsDictionary[kFIRTrackingID];
255 }
256
257 - (void)setTrackingID:(NSString *)trackingID {
258   [self checkEditingLocked];
259   _optionsDictionary[kFIRTrackingID] = [trackingID copy];
260 }
261
262 - (NSString *)GCMSenderID {
263   return self.optionsDictionary[kFIRGCMSenderID];
264 }
265
266 - (void)setGCMSenderID:(NSString *)GCMSenderID {
267   [self checkEditingLocked];
268   _optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy];
269 }
270
271 - (NSString *)projectID {
272   return self.optionsDictionary[kFIRProjectID];
273 }
274
275 - (void)setProjectID:(NSString *)projectID {
276   [self checkEditingLocked];
277   _optionsDictionary[kFIRProjectID] = [projectID copy];
278 }
279
280 - (NSString *)androidClientID {
281   return self.optionsDictionary[kFIRAndroidClientID];
282 }
283
284 - (void)setAndroidClientID:(NSString *)androidClientID {
285   [self checkEditingLocked];
286   _optionsDictionary[kFIRAndroidClientID] = [androidClientID copy];
287 }
288
289 - (NSString *)googleAppID {
290   return self.optionsDictionary[kFIRGoogleAppID];
291 }
292
293 - (void)setGoogleAppID:(NSString *)googleAppID {
294   [self checkEditingLocked];
295   _optionsDictionary[kFIRGoogleAppID] = [googleAppID copy];
296 }
297
298 - (NSString *)libraryVersionID {
299   return kFIRLibraryVersionID;
300 }
301
302 - (void)setLibraryVersionID:(NSString *)libraryVersionID {
303   _optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy];
304 }
305
306 - (NSString *)databaseURL {
307   return self.optionsDictionary[kFIRDatabaseURL];
308 }
309
310 - (void)setDatabaseURL:(NSString *)databaseURL {
311   [self checkEditingLocked];
312
313   _optionsDictionary[kFIRDatabaseURL] = [databaseURL copy];
314 }
315
316 - (NSString *)storageBucket {
317   return self.optionsDictionary[kFIRStorageBucket];
318 }
319
320 - (void)setStorageBucket:(NSString *)storageBucket {
321   [self checkEditingLocked];
322   _optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
323 }
324
325 - (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme {
326   [self checkEditingLocked];
327   _deepLinkURLScheme = [deepLinkURLScheme copy];
328 }
329
330 - (NSString *)bundleID {
331   return self.optionsDictionary[kFIRBundleID];
332 }
333
334 - (void)setBundleID:(NSString *)bundleID {
335   [self checkEditingLocked];
336   _optionsDictionary[kFIRBundleID] = [bundleID copy];
337 }
338
339 #pragma mark - Internal instance methods
340
341 - (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
342   if (_analyticsOptionsDictionary == nil) {
343     NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
344     NSArray *measurementKeys = @[
345       kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled,
346       kFIRIsAnalyticsCollectionDeactivated
347     ];
348     for (NSString *key in measurementKeys) {
349       id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
350       if (!value) {
351         continue;
352       }
353       tempAnalyticsOptions[key] = value;
354     }
355     _analyticsOptionsDictionary = tempAnalyticsOptions;
356   }
357   return _analyticsOptionsDictionary;
358 }
359
360 - (NSDictionary *)analyticsOptionsDictionary {
361   return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary];
362 }
363
364 /**
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
367  * be supported.
368  */
369 - (BOOL)isMeasurementEnabled {
370   if (self.isAnalyticsCollectionDeactivated) {
371     return NO;
372   }
373   NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
374   if (value == nil) {
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.
379
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]) {
383       return NO;
384     }
385
386     // Fall back to the default app's collection switch when the key is not in the dictionary.
387     return [FIRApp defaultApp].isDataCollectionDefaultEnabled;
388   }
389   return [value boolValue];
390 }
391
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) {
396     return YES;
397   }
398
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.
403     return YES;
404   }
405
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.
410     return YES;
411   }
412
413   // No flags are set to explicitly enable or disable FirebaseAnalytics.
414   return NO;
415 }
416
417 - (BOOL)isAnalyticsCollectionEnabled {
418   if (self.isAnalyticsCollectionDeactivated) {
419     return NO;
420   }
421   NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
422   if (value == nil) {
423     return self.isMeasurementEnabled;  // Fall back to older plist flag.
424   }
425   return [value boolValue];
426 }
427
428 - (BOOL)isAnalyticsCollectionDeactivated {
429   NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated];
430   if (value == nil) {
431     return NO;  // Analytics Collection is not deactivated when the key is not in the dictionary.
432   }
433   return [value boolValue];
434 }
435
436 - (BOOL)isAnalyticsEnabled {
437   return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue];
438 }
439
440 - (BOOL)isSignInEnabled {
441   return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue];
442 }
443
444 @end