aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Core
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Core
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Firebase/Core')
-rw-r--r--Firebase/Core/FIRAnalyticsConfiguration.h54
-rw-r--r--Firebase/Core/FIRAnalyticsConfiguration.m61
-rw-r--r--Firebase/Core/FIRApp.h126
-rw-r--r--Firebase/Core/FIRApp.m596
-rw-r--r--Firebase/Core/FIRAppAssociationRegistration.m47
-rw-r--r--Firebase/Core/FIRAppEnvironmentUtil.m207
-rw-r--r--Firebase/Core/FIRBundleUtil.m65
-rw-r--r--Firebase/Core/FIRConfiguration.h80
-rw-r--r--Firebase/Core/FIRConfiguration.m51
-rw-r--r--Firebase/Core/FIRCoreSwiftNameSupport.h29
-rw-r--r--Firebase/Core/FIRErrors.m33
-rw-r--r--Firebase/Core/FIRLogger.m228
-rw-r--r--Firebase/Core/FIRLoggerLevel.h30
-rw-r--r--Firebase/Core/FIRMutableDictionary.m97
-rw-r--r--Firebase/Core/FIRNetwork.m390
-rw-r--r--Firebase/Core/FIRNetworkConstants.m39
-rw-r--r--Firebase/Core/FIRNetworkURLSession.m669
-rw-r--r--Firebase/Core/FIROptions.h131
-rw-r--r--Firebase/Core/FIROptions.m427
-rw-r--r--Firebase/Core/FIRReachabilityChecker.m245
-rw-r--r--Firebase/Core/FIRURLSchemeUtil.m43
-rw-r--r--Firebase/Core/FirebaseCore.h21
-rw-r--r--Firebase/Core/FirebaseCore.podspec35
-rw-r--r--Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h39
-rw-r--r--Firebase/Core/Private/FIRAppAssociationRegistration.h48
-rw-r--r--Firebase/Core/Private/FIRAppEnvironmentUtil.h48
-rw-r--r--Firebase/Core/Private/FIRAppInternal.h140
-rw-r--r--Firebase/Core/Private/FIRBundleUtil.h57
-rw-r--r--Firebase/Core/Private/FIRErrorCode.h55
-rw-r--r--Firebase/Core/Private/FIRErrors.h43
-rw-r--r--Firebase/Core/Private/FIRLogger.h115
-rw-r--r--Firebase/Core/Private/FIRMutableDictionary.h46
-rw-r--r--Firebase/Core/Private/FIRNetwork.h87
-rw-r--r--Firebase/Core/Private/FIRNetworkConstants.h75
-rw-r--r--Firebase/Core/Private/FIRNetworkLoggerProtocol.h50
-rw-r--r--Firebase/Core/Private/FIRNetworkMessageCode.h52
-rw-r--r--Firebase/Core/Private/FIRNetworkURLSession.h57
-rw-r--r--Firebase/Core/Private/FIROptionsInternal.h108
-rw-r--r--Firebase/Core/Private/FIRReachabilityChecker+Internal.h47
-rw-r--r--Firebase/Core/Private/FIRReachabilityChecker.h83
-rw-r--r--Firebase/Core/Private/FIRURLSchemeUtil.h25
41 files changed, 4879 insertions, 0 deletions
diff --git a/Firebase/Core/FIRAnalyticsConfiguration.h b/Firebase/Core/FIRAnalyticsConfiguration.h
new file mode 100644
index 0000000..f42eaf5
--- /dev/null
+++ b/Firebase/Core/FIRAnalyticsConfiguration.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FIRCoreSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides configuration fields for Firebase Analytics.
+ */
+FIR_SWIFT_NAME(AnalyticsConfiguration)
+@interface FIRAnalyticsConfiguration : NSObject
+
+/**
+ * Returns the shared instance of FIRAnalyticsConfiguration.
+ */
++ (FIRAnalyticsConfiguration *)sharedInstance FIR_SWIFT_NAME(shared());
+
+/**
+ * Sets the minimum engagement time in seconds required to start a new session. The default value
+ * is 10 seconds.
+ */
+- (void)setMinimumSessionInterval:(NSTimeInterval)minimumSessionInterval;
+
+/**
+ * Sets the interval of inactivity in seconds that terminates the current session. The default
+ * value is 1800 seconds (30 minutes).
+ */
+- (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval;
+
+/**
+ * Sets whether analytics collection is enabled for this app on this device. This setting is
+ * persisted across app sessions. By default it is enabled.
+ */
+- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/FIRAnalyticsConfiguration.m b/Firebase/Core/FIRAnalyticsConfiguration.m
new file mode 100644
index 0000000..cec3771
--- /dev/null
+++ b/Firebase/Core/FIRAnalyticsConfiguration.m
@@ -0,0 +1,61 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "FIRAnalyticsConfiguration.h"
+
+#import "Private/FIRAnalyticsConfiguration+Internal.h"
+
+@implementation FIRAnalyticsConfiguration
+
++ (FIRAnalyticsConfiguration *)sharedInstance {
+ static FIRAnalyticsConfiguration *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[FIRAnalyticsConfiguration alloc] init];
+ });
+ return sharedInstance;
+}
+
+- (void)postNotificationName:(NSString *)name value:(id)value {
+ if (!name.length || !value) {
+ return;
+ }
+ [[NSNotificationCenter defaultCenter] postNotificationName:name
+ object:self
+ userInfo:@{ name : value }];
+}
+
+- (void)setMinimumSessionInterval:(NSTimeInterval)minimumSessionInterval {
+ [self postNotificationName:kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification
+ value:@(minimumSessionInterval)];
+}
+
+- (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval {
+ [self postNotificationName:kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification
+ value:@(sessionTimeoutInterval)];
+}
+
+- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled {
+ // Persist the measurementEnabledState. Use FIRAnalyticsEnabledState values instead of YES/NO.
+ FIRAnalyticsEnabledState analyticsEnabledState =
+ analyticsCollectionEnabled ? kFIRAnalyticsEnabledStateSetYes : kFIRAnalyticsEnabledStateSetNo;
+ [[NSUserDefaults standardUserDefaults] setObject:@(analyticsEnabledState)
+ forKey:kFIRAPersistedConfigMeasurementEnabledStateKey];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+
+ [self postNotificationName:kFIRAnalyticsConfigurationSetEnabledNotification
+ value:@(analyticsCollectionEnabled)];
+}
+
+@end
diff --git a/Firebase/Core/FIRApp.h b/Firebase/Core/FIRApp.h
new file mode 100644
index 0000000..7f1d0c7
--- /dev/null
+++ b/Firebase/Core/FIRApp.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#import "FIRCoreSwiftNameSupport.h"
+
+@class FIROptions;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** A block that takes a BOOL and has no return value. */
+typedef void (^FIRAppVoidBoolCallback)(BOOL success) FIR_SWIFT_NAME(FirebaseAppVoidBoolCallback);
+
+/**
+ * The entry point of Firebase SDKs.
+ *
+ * Initialize and configure FIRApp using +[FIRApp configure]
+ * or other customized ways as shown below.
+ *
+ * The logging system has two modes: default mode and debug mode. In default mode, only logs with
+ * log level Notice, Warning and Error will be sent to device. In debug mode, all logs will be sent
+ * to device. The log levels that Firebase uses are consistent with the ASL log levels.
+ *
+ * Enable debug mode by passing the -FIRDebugEnabled argument to the application. You can add this
+ * argument in the application's Xcode scheme. When debug mode is enabled via -FIRDebugEnabled,
+ * further executions of the application will also be in debug mode. In order to return to default
+ * mode, you must explicitly disable the debug mode with the application argument -FIRDebugDisabled.
+ *
+ * It is also possible to change the default logging level in code by calling setLoggerLevel: on
+ * the FIRConfiguration interface.
+ */
+FIR_SWIFT_NAME(FirebaseApp)
+@interface FIRApp : NSObject
+
+/**
+ * Configures a default Firebase app. Raises an exception if any configuration step fails. The
+ * default app is named "__FIRAPP_DEFAULT". This method should be called after the app is launched
+ * and before using Firebase services. This method is thread safe.
+ */
++ (void)configure;
+
+/**
+ * Configures the default Firebase app with the provided options. The default app is named
+ * "__FIRAPP_DEFAULT". Raises an exception if any configuration step fails. This method is thread
+ * safe.
+ *
+ * @param options The Firebase application options used to configure the service.
+ */
++ (void)configureWithOptions:(FIROptions *)options FIR_SWIFT_NAME(configure(options:));
+
+/**
+ * Configures a Firebase app with the given name and options. Raises an exception if any
+ * configuration step fails. This method is thread safe.
+ *
+ * @param name The application's name given by the developer. The name should should only contain
+ Letters, Numbers and Underscore.
+ * @param options The Firebase application options used to configure the services.
+ */
++ (void)configureWithName:(NSString *)name options:(FIROptions *)options
+ FIR_SWIFT_NAME(configure(name:options:));
+
+/**
+ * Returns the default app, or nil if the default app does not exist.
+ */
++ (nullable FIRApp *)defaultApp FIR_SWIFT_NAME(app());
+
+/**
+ * Returns a previously created FIRApp instance with the given name, or nil if no such app exists.
+ * This method is thread safe.
+ */
++ (nullable FIRApp *)appNamed:(NSString *)name FIR_SWIFT_NAME(app(name:));
+
+#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
+/**
+ * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This
+ * method is thread safe.
+ */
+@property(class, readonly, nullable) NSDictionary <NSString *, FIRApp *> *allApps;
+#else
+/**
+ * Returns the set of all extant FIRApp instances, or nil if there are no FIRApp instances. This
+ * method is thread safe.
+ */
++ (nullable NSDictionary <NSString *, FIRApp *> *)allApps FIR_SWIFT_NAME(allApps());
+#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
+
+/**
+ * Cleans up the current FIRApp, freeing associated data and returning its name to the pool for
+ * future use. This method is thread safe.
+ */
+- (void)deleteApp:(FIRAppVoidBoolCallback)completion;
+
+/**
+ * FIRApp instances should not be initialized directly. Call +[FIRApp configure],
+ * +[FIRApp configureWithOptions:], or +[FIRApp configureWithNames:options:] directly.
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Gets the name of this app.
+ */
+@property(nonatomic, copy, readonly) NSString *name;
+
+/**
+ * Gets a copy of the options for this app. These are non-modifiable.
+ */
+@property(nonatomic, copy, readonly) FIROptions *options;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m
new file mode 100644
index 0000000..86784a3
--- /dev/null
+++ b/Firebase/Core/FIRApp.m
@@ -0,0 +1,596 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sys/utsname.h>
+
+#import "FIRApp.h"
+#import "Private/FIRAppInternal.h"
+#import "Private/FIRBundleUtil.h"
+#import "FIRConfiguration.h"
+#import "Private/FIRLogger.h"
+#import "Private/FIROptionsInternal.h"
+
+NSString *const kFIRServiceAdMob = @"AdMob";
+NSString *const kFIRServiceAuth = @"Auth";
+NSString *const kFIRServiceCrash = @"Crash";
+NSString *const kFIRServiceDatabase = @"Database";
+NSString *const kFIRServiceDynamicLinks = @"DynamicLinks";
+NSString *const kFIRServiceInstanceID = @"InstanceID";
+NSString *const kFIRServiceInvites = @"Invites";
+NSString *const kFIRServiceMessaging = @"Messaging";
+NSString *const kFIRServiceMeasurement = @"Measurement";
+NSString *const kFIRServiceRemoteConfig = @"RemoteConfig";
+NSString *const kFIRServiceStorage = @"Storage";
+NSString *const kGGLServiceAnalytics = @"Analytics";
+NSString *const kGGLServiceSignIn = @"SignIn";
+
+NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT";
+NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification";
+NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification";
+NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
+NSString *const kFIRAppNameKey = @"FIRAppNameKey";
+NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
+
+NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
+
+NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
+NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
+NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
+NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
+NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
+
+/**
+ * The URL to download plist files.
+ */
+static NSString *const kPlistURL = @"https://console.firebase.google.com/";
+
+@interface FIRApp ()
+
+@property(nonatomic) BOOL alreadySentConfigureNotification;
+
+@property(nonatomic) BOOL alreadySentDeleteNotification;
+
+@end
+
+@implementation FIRApp
+
+// This is necessary since our custom getter prevents `_options` from being created.
+@synthesize options = _options;
+
+static NSMutableDictionary *sAllApps;
+static FIRApp *sDefaultApp;
+
++ (void)configure {
+ FIROptions *options = [FIROptions defaultOptions];
+ if (!options) {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kFIRAppDiagnosticsNotification
+ object:nil
+ userInfo:@{
+ kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
+ kFIRAppDiagnosticsErrorKey : [FIRApp errorForMissingOptions]
+ }];
+ [NSException raise:kFirebaseCoreErrorDomain
+ format:@"[FIRApp configure] could not find a valid GoogleService-Info.plist in "
+ @"your project. Please download one from %@.",
+ kPlistURL];
+ }
+ [FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
+}
+
++ (void)configureWithOptions:(FIROptions *)options {
+ if (!options) {
+ [NSException raise:kFirebaseCoreErrorDomain
+ format:@"Options is nil. Please pass a valid options."];
+ }
+ [FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
+}
+
++ (void)configureWithoutSendingNotification {
+ FIROptions *options = [FIROptions defaultOptions];
+ if (!options) {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kFIRAppDiagnosticsNotification
+ object:nil
+ userInfo:@{
+ kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
+ kFIRAppDiagnosticsErrorKey : [FIRApp errorForMissingOptions]
+ }];
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Please check there is a valid "
+ @"GoogleService-Info.plist in the project."];
+ }
+ [FIRApp configureDefaultAppWithOptions:options sendingNotifications:NO];
+}
+
++ (void)configureDefaultAppWithOptions:(FIROptions *)options
+ sendingNotifications:(BOOL)sendNotifications {
+ if (sDefaultApp) {
+ // FIRApp sets up FirebaseAnalytics and does plist validation, but does not cause it
+ // to fire notifications. So, if the default app already exists, but has not sent out
+ // configuration notifications, then continue re-initializing it.
+ if (!sendNotifications || sDefaultApp.alreadySentConfigureNotification) {
+ [NSException raise:kFirebaseCoreErrorDomain
+ format:@"Default app has already been configured."];
+ }
+ }
+ @synchronized(self) {
+ FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
+ sDefaultApp = [[FIRApp alloc] initInstanceWithName:kFIRDefaultAppName options:options];
+ [FIRApp addAppToAppDictionary:sDefaultApp];
+ if (!sDefaultApp.alreadySentConfigureNotification && sendNotifications) {
+ [FIRApp sendNotificationsToSDKs:sDefaultApp];
+ sDefaultApp.alreadySentConfigureNotification = YES;
+ }
+ }
+}
+
++ (void)configureWithName:(NSString *)name options:(FIROptions *)options {
+ if (!name || !options) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."];
+ }
+ if (name.length == 0) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."];
+ }
+ if ([name isEqualToString:kFIRDefaultAppName]) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be __FIRAPP_DEFAULT."];
+ }
+ NSString *lowerCaseName = [name lowercaseString];
+ for (NSInteger charIndex = 0; charIndex < lowerCaseName.length; charIndex++) {
+ char character = [lowerCaseName characterAtIndex:charIndex];
+ if (!((character >= 'a' && character <= 'z')
+ || (character >= '0' && character <= '9')
+ || character == '_'
+ || character == '-')) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"App name should only contain Letters, "
+ @"Numbers, Underscores, and Dashes."];
+ }
+ }
+
+ if (sAllApps && sAllApps[name]) {
+ [NSException raise:kFirebaseCoreErrorDomain
+ format:@"App named %@ has already been configured.", name];
+ }
+
+ @synchronized(self) {
+ FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name);
+ FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options];
+ [FIRApp addAppToAppDictionary:app];
+ if (!app.alreadySentConfigureNotification) {
+ [FIRApp sendNotificationsToSDKs:app];
+ app.alreadySentConfigureNotification = YES;
+ }
+ }
+}
+
++ (FIRApp *)defaultApp {
+ if (sDefaultApp) {
+ return sDefaultApp;
+ }
+ FIRLogError(kFIRLoggerCore, @"I-COR000003", @"The default Firebase app has not yet been "
+ @"configured. Add [FIRApp configure] to your application initialization. Read more: "
+ @"https://goo.gl/ctyzm8.");
+ return nil;
+}
+
++ (FIRApp *)appNamed:(NSString *)name {
+ @synchronized(self) {
+ if (sAllApps) {
+ FIRApp *app = sAllApps[name];
+ if (app) {
+ return app;
+ }
+ }
+ FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name);
+ return nil;
+ }
+}
+
++ (NSDictionary *)allApps {
+ @synchronized(self) {
+ if (!sAllApps) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet.");
+ }
+ NSDictionary *dict = [NSDictionary dictionaryWithDictionary:sAllApps];
+ return dict;
+ }
+}
+
+// Public only for tests
++ (void)resetApps {
+ sDefaultApp = nil;
+ [sAllApps removeAllObjects];
+ sAllApps = nil;
+}
+
+- (void)deleteApp:(FIRAppVoidBoolCallback)completion {
+ @synchronized([self class]) {
+ if (sAllApps && sAllApps[self.name]) {
+ FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
+ [sAllApps removeObjectForKey:self.name];
+ if ([self.name isEqualToString:kFIRDefaultAppName]) {
+ sDefaultApp = nil;
+ }
+ if (!self.alreadySentDeleteNotification) {
+ NSDictionary *appInfoDict = @ {
+ kFIRAppNameKey : self.name
+ };
+ [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification
+ object:[self class]
+ userInfo:appInfoDict];
+ self.alreadySentDeleteNotification = YES;
+ }
+ completion(YES);
+ } else {
+ FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist.");
+ completion(NO);
+ }
+ }
+}
+
++ (void)addAppToAppDictionary:(FIRApp *)app {
+ if (!sAllApps) {
+ sAllApps = [NSMutableDictionary dictionary];
+ }
+ if ([app configureCore]) {
+ sAllApps[app.name] = app;
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kFIRAppDiagnosticsNotification
+ object:nil
+ userInfo:@{
+ kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
+ kFIRAppDiagnosticsFIRAppKey : app
+ }];
+ } else {
+ [NSException raise:kFirebaseCoreErrorDomain
+ format:@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in "
+ @"GoogleService-Info.plist or set in the customized options."];
+ }
+}
+
+- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options {
+ self = [super init];
+ if (self) {
+ _name = [name copy];
+ _options = [options copy];
+ _options.editingLocked = YES;
+
+ FIRApp *app = sAllApps[name];
+ _alreadySentConfigureNotification = app.alreadySentConfigureNotification;
+ _alreadySentDeleteNotification = app.alreadySentDeleteNotification;
+ }
+ return self;
+}
+
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback {
+ if (!_getTokenImplementation) {
+ callback(nil, nil);
+ return;
+ }
+
+ _getTokenImplementation(forceRefresh, callback);
+}
+
+- (BOOL)configureCore {
+ [self checkExpectedBundleID];
+ if (![self isAppIDValid]) {
+ if (_options.usingOptionsFromDefaultPlist) {
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kFIRAppDiagnosticsNotification
+ object:nil
+ userInfo:@{
+ kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
+ kFIRAppDiagnosticsErrorKey : [FIRApp errorForInvalidAppID],
+ }];
+ }
+ return NO;
+ }
+
+ if (NSClassFromString(@"FIRAppIndexing") != nil) {
+ FIRLogDebug(kFIRLoggerCore, @"I-COR000024", @"Firebase App Indexing on iOS is deprecated. "
+ @"You don't need to take any action at this time. Learn more about Firebase App "
+ @"Indexing at https://firebase.google.com/docs/app-indexing/.");
+ }
+
+ // Initialize the Analytics once there is a valid options under default app. Analytics should
+ // always initialize first by itself before the other SDKs.
+ if ([self.name isEqualToString:kFIRDefaultAppName]) {
+ Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics");
+ if (!firAnalyticsClass) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000022", @"Firebase Analytics is not available.");
+ } else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+ SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:);
+#pragma clang diagnostic pop
+ if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+ [firAnalyticsClass performSelector:startWithConfigurationSelector
+ withObject:[FIRConfiguration sharedInstance].analyticsConfiguration
+ withObject:_options];
+#pragma clang diagnostic pop
+ }
+ }
+ }
+ return YES;
+}
+
+- (FIROptions *)options {
+ return [_options copy];
+}
+
+#pragma mark - private
+
++ (void)sendNotificationsToSDKs:(FIRApp *)app {
+ NSNumber *isDefaultApp = [NSNumber numberWithBool:(app == sDefaultApp)];
+ NSDictionary *appInfoDict = @ {
+ kFIRAppNameKey : app.name,
+ kFIRAppIsDefaultAppKey : isDefaultApp,
+ kFIRGoogleAppIDKey : app.options.googleAppID
+ };
+ [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
+ object:self
+ userInfo:appInfoDict];
+}
+
++ (NSError *)errorForMissingOptions {
+ NSDictionary *errorDict = @{
+ NSLocalizedDescriptionKey :
+ @"Unable to parse GoogleService-Info.plist in order to configure services.",
+ NSLocalizedRecoverySuggestionErrorKey :
+ @"Check formatting and location of GoogleService-Info.plist."
+ };
+ return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidPlistFile, errorDict);
+}
+
++ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
+ errorCode:(FIRErrorCode)code
+ service:(NSString *)service
+ reason:(NSString *)reason {
+ NSString *description =
+ [NSString stringWithFormat:@"Configuration failed for service %@.", service];
+ NSDictionary *errorDict = @{
+ NSLocalizedDescriptionKey : description,
+ NSLocalizedFailureReasonErrorKey : reason
+ };
+ return FIRCreateError(domain, code, errorDict);
+}
+
++ (NSError *)errorForInvalidAppID {
+ NSDictionary *errorDict = @{
+ NSLocalizedDescriptionKey :
+ @"Unable to validate Google App ID",
+ NSLocalizedRecoverySuggestionErrorKey :
+ @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
+ @"customized options."
+ };
+ return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidAppID, errorDict);
+}
+
+- (void)checkExpectedBundleID {
+ NSArray *bundles = [FIRBundleUtil relevantBundles];
+ NSString *expectedBundleID = [self expectedBundleID];
+ // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for
+ // backward compatibility.
+ if (expectedBundleID != nil &&
+ ![FIRBundleUtil hasBundleIdentifier:expectedBundleID inBundles:bundles]) {
+ FIRLogInfo(kFIRLoggerCore, @"I-COR000008", @"The project's Bundle ID is inconsistent with "
+ @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are "
+ @"using a customized options. To ensure that everything can be configured "
+ @"correctly, you may need to make the Bundle IDs consistent. To continue with this "
+ @"plist file, you may change your app's bundle identifier to '%@'. Or you can "
+ @"download a new configuration file that matches your bundle identifier from %@ "
+ @"and replace the current one.", kServiceInfoFileName, kServiceInfoFileType,
+ expectedBundleID, kPlistURL);
+ }
+}
+
+- (nullable NSString *)getUID {
+ if (!_getUIDImplementation) {
+ FIRLogWarning(kFIRLoggerCore, @"I-COR000025", @"FIRAuth getUID implementation wasn't set.");
+ return nil;
+ }
+ return _getUIDImplementation();
+}
+
+#pragma mark - private - App ID Validation
+
+/**
+ * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file.
+ * This is the main method for validating app ID.
+ *
+ * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise.
+ */
+- (BOOL)isAppIDValid {
+ NSString *appID = _options.googleAppID;
+ BOOL isValid = [FIRApp validateAppID:appID];
+ if (!isValid){
+ NSString *expectedBundleID = [self expectedBundleID];
+ FIRLogError(kFIRLoggerCore, @"I-COR000009", @"The GOOGLE_APP_ID either in the plist file "
+ @"'%@.%@' or the one set in the customized options is invalid. If you are using "
+ @"the plist file, use the iOS version of bundle identifier to download the file, "
+ @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle "
+ @"identifier to '%@'. Or you can download a new configuration file that matches "
+ @"your bundle identifier from %@ and replace the current one.",
+ kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
+
+ };
+ return isValid;
+}
+
++ (BOOL)validateAppID:(NSString *)appID {
+ // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not
+ // have a valid fingerprint, otherwise we just warn about the potential issue.
+ if (!appID.length) {
+ return NO;
+ }
+
+ // All app IDs must start with at least "<version number>:".
+ NSString *const versionPattern = @"^\\d+:";
+ NSRegularExpression *versionRegex =
+ [NSRegularExpression regularExpressionWithPattern:versionPattern options:0 error:NULL];
+ if (!versionRegex) {
+ return NO;
+ }
+
+ NSRange appIDRange = NSMakeRange(0, appID.length);
+ NSArray *versionMatches = [versionRegex matchesInString:appID options:0 range:appIDRange];
+ if (versionMatches.count != 1) {
+ return NO;
+ }
+
+ NSRange versionRange = [(NSTextCheckingResult *)versionMatches.firstObject range];
+ NSString *appIDVersion = [appID substringWithRange:versionRange];
+ NSArray *knownVersions = @[ @"1:" ];
+ if (![knownVersions containsObject:appIDVersion]) {
+ // Permit unknown yet properly formatted app ID versions.
+ return YES;
+ }
+
+ if (![FIRApp validateAppIDFormat:appID withVersion:appIDVersion]) {
+ return NO;
+ }
+
+ if (![FIRApp validateAppIDFingerprint:appID withVersion:appIDVersion]) {
+ return NO;
+ }
+
+ return YES;
+}
+
++ (NSString *)actualBundleID {
+ return [[NSBundle mainBundle] bundleIdentifier];
+}
+
+/**
+ * Validates that the format of the app ID string is what is expected based on the supplied version.
+ * The version must end in ":".
+ *
+ * For v1 app ids the format is expected to be
+ * '<version #>:<project number>:ios:<fingerprint of bundle id>'.
+ *
+ * This method does not verify that the contents of the app id are correct, just that they fulfill
+ * the expected format.
+ *
+ * @param appID Contents of GOOGLE_APP_ID from the plist file.
+ * @param version Indicates what version of the app id format this string should be.
+ * @return YES if provided string fufills the expected format, NO otherwise.
+ */
++ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version {
+ if (!appID.length || !version.length) {
+ return NO;
+ }
+
+ if (![version hasSuffix:@":"]) {
+ return NO;
+ }
+
+ if (![appID hasPrefix:version]) {
+ return NO;
+ }
+
+ NSString *const pattern = @"^\\d+:ios:[a-f0-9]+$";
+ NSRegularExpression *regex =
+ [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
+ if (!regex) {
+ return NO;
+ }
+
+ NSRange localRange = NSMakeRange(version.length, appID.length - version.length);
+ NSUInteger numberOfMatches = [regex numberOfMatchesInString:appID options:0 range:localRange];
+ if (numberOfMatches != 1) {
+ return NO;
+ }
+ return YES;
+}
+
+/**
+ * Validates that the fingerprint of the app ID string is what is expected based on the supplied
+ * version. The version must end in ":".
+ *
+ * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated.
+ *
+ * @param appID Contents of GOOGLE_APP_ID from the plist file.
+ * @param version Indicates what version of the app id format this string should be.
+ * @return YES if provided string fufills the expected fingerprint and the version is known, NO
+ * otherwise.
+ */
++ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version {
+ if (!appID.length || !version.length) {
+ return NO;
+ }
+
+ if (![version hasSuffix:@":"]) {
+ return NO;
+ }
+
+ if (![appID hasPrefix:version]) {
+ return NO;
+ }
+
+ // Extract the supplied fingerprint from the supplied app ID.
+ // This assumes the app ID format is the same for all known versions below. If the app ID format
+ // changes in future versions, the tokenizing of the app ID format will need to take into account
+ // the version of the app ID.
+ NSArray *components = [appID componentsSeparatedByString:@":"];
+ if (components.count != 4) {
+ return NO;
+ }
+
+ NSString *suppliedFingerprintString = components[3];
+ if (!suppliedFingerprintString.length) {
+ return NO;
+ }
+
+ uint64_t suppliedFingerprint;
+ NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString];
+ if (![scanner scanHexLongLong:&suppliedFingerprint]) {
+ return NO;
+ }
+
+ if ([version isEqual:@"1:"]) {
+ // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated.
+ return YES;
+ }
+
+ // Unknown version.
+ return NO;
+}
+
+- (NSString *)expectedBundleID {
+ return _options.bundleID;
+}
+
+// end App ID validation
+#pragma mark
+
+- (void)sendLogsWithServiceName:(NSString *)serviceName
+ version:(NSString *)version
+ error:(NSError *)error{
+ NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
+ kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeSDK),
+ kFIRAppDiagnosticsSDKNameKey : serviceName,
+ kFIRAppDiagnosticsSDKVersionKey : version,
+ kFIRAppDiagnosticsFIRAppKey : self
+ }];
+ if (error) {
+ userInfo[kFIRAppDiagnosticsErrorKey] = error;
+ }
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:kFIRAppDiagnosticsNotification
+ object:nil
+ userInfo:userInfo];
+}
+
+@end
diff --git a/Firebase/Core/FIRAppAssociationRegistration.m b/Firebase/Core/FIRAppAssociationRegistration.m
new file mode 100644
index 0000000..a36396d
--- /dev/null
+++ b/Firebase/Core/FIRAppAssociationRegistration.m
@@ -0,0 +1,47 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRAppAssociationRegistration.h"
+
+#import <objc/runtime.h>
+
+@implementation FIRAppAssociationRegistration
+
++ (nullable id)registeredObjectWithHost:(id)host
+ key:(NSString *)key
+ creationBlock:(id _Nullable (^)())creationBlock {
+ @synchronized(self) {
+ SEL dictKey = @selector(registeredObjectWithHost:key:creationBlock:);
+ NSMutableDictionary<NSString *, id> *objectsByKey = objc_getAssociatedObject(host, dictKey);
+ if (!objectsByKey) {
+ objectsByKey = [[NSMutableDictionary alloc] init];
+ objc_setAssociatedObject(host, dictKey, objectsByKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+ }
+ id obj = objectsByKey[key];
+ NSValue *creationBlockBeingCalled = [NSValue valueWithPointer:dictKey];
+ if (obj) {
+ if ([creationBlockBeingCalled isEqual:obj]) {
+ [NSException raise:@"Reentering registeredObjectWithHost:key:creationBlock: not allowed"
+ format:@"host: %@ key: %@", host, key];
+ }
+ return obj;
+ }
+ objectsByKey[key] = creationBlockBeingCalled;
+ obj = creationBlock();
+ objectsByKey[key] = obj;
+ return obj;
+ }
+}
+
+@end
diff --git a/Firebase/Core/FIRAppEnvironmentUtil.m b/Firebase/Core/FIRAppEnvironmentUtil.m
new file mode 100644
index 0000000..b88b432
--- /dev/null
+++ b/Firebase/Core/FIRAppEnvironmentUtil.m
@@ -0,0 +1,207 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+
+#import "Private/FIRAppEnvironmentUtil.h"
+
+#import <dlfcn.h>
+#import <mach-o/dyld.h>
+#import <sys/utsname.h>
+
+/// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from
+/// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just
+/// provide the definitions here.
+#if TARGET_IPHONE_SIMULATOR && !defined(LC_ENCRYPTION_INFO)
+#define LC_ENCRYPTION_INFO 0x21
+struct encryption_info_command {
+ uint32_t cmd;
+ uint32_t cmdsize;
+ uint32_t cryptoff;
+ uint32_t cryptsize;
+ uint32_t cryptid;
+};
+#endif
+
+@implementation FIRAppEnvironmentUtil
+
+/// The file name of the sandbox receipt. This is available on iOS >= 8.0
+static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt";
+
+/// The following copyright from Landon J. Fuller applies to the isAppEncrypted function.
+///
+/// Copyright (c) 2017 Landon J. Fuller <landon@landonf.org>
+/// All rights reserved.
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+/// and associated documentation files (the "Software"), to deal in the Software without
+/// restriction, including without limitation the rights to use, copy, modify, merge, publish,
+/// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in all copies or
+/// substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+/// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+/// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+///
+/// Comment from <a href="http://iphonedevwiki.net/index.php/Crack_prevention">iPhone Dev Wiki
+/// Crack Prevention</a>:
+/// App Store binaries are signed by both their developer and Apple. This encrypts the binary so
+/// that decryption keys are needed in order to make the binary readable. When iOS executes the
+/// binary, the decryption keys are used to decrypt the binary into a readable state where it is
+/// then loaded into memory and executed. iOS can tell the encryption status of a binary via the
+/// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero
+/// value then the binary is encrypted.
+///
+/// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into
+/// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as
+/// codesignature validation has been removed. Resigning takes place because while the codesignature
+/// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have
+/// AppSync or similar to disable codesignature checks.
+///
+/// More information at <a href="http://landonf.org/2009/02/index.html">Landon Fuller's blog</a>
+static BOOL isAppEncrypted() {
+ const struct mach_header *executableHeader = NULL;
+ for (uint32_t i = 0; i < _dyld_image_count(); i++) {
+ const struct mach_header *header = _dyld_get_image_header(i);
+ if (header && header->filetype == MH_EXECUTE) {
+ executableHeader = header;
+ break;
+ }
+ }
+
+ if (!executableHeader) {
+ return NO;
+ }
+
+ BOOL is64bit = (executableHeader->magic == MH_MAGIC_64);
+ uintptr_t cursor = (uintptr_t)executableHeader +
+ (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header));
+ const struct segment_command *segmentCommand = NULL;
+ uint32_t i = 0;
+
+ while (i++ < executableHeader->ncmds) {
+ segmentCommand = (struct segment_command *)cursor;
+
+ if (!segmentCommand) {
+ continue;
+ }
+
+ if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) ||
+ (is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) {
+ if (is64bit) {
+ struct encryption_info_command_64 *cryptCmd =
+ (struct encryption_info_command_64 *)segmentCommand;
+ return cryptCmd && cryptCmd->cryptid != 0;
+ } else {
+ struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand;
+ return cryptCmd && cryptCmd->cryptid != 0;
+ }
+ }
+ cursor += segmentCommand->cmdsize;
+ }
+
+ return NO;
+}
+
++ (BOOL)isFromAppStore {
+ static dispatch_once_t isEncryptedOnce;
+ static BOOL isEncrypted = NO;
+
+ dispatch_once(&isEncryptedOnce, ^{
+ isEncrypted = isAppEncrypted();
+ });
+
+ if ([FIRAppEnvironmentUtil isSimulator]) {
+ return NO;
+ }
+ if ([FIRAppEnvironmentUtil hasSCInfoFolder]) {
+ // When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the
+ // main SC_Info directory.
+ return YES;
+ }
+
+ // For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read
+ // the iTunesMetadata.plist outside of the sandbox will be rejected by Apple.
+ // If the app does not contain the sandboxReceipt file which means it is a TestFlight beta, and
+ // it does not contain the embedded.mobileprovision which is stripped out by Apple when the
+ // app is submitted to store, then it is highly likely that it is from Apple Store.
+ return isEncrypted && ![FIRAppEnvironmentUtil isAppStoreReceiptSandbox] &&
+ ![FIRAppEnvironmentUtil hasEmbeddedMobileProvision];
+}
+
++ (BOOL)isAppStoreReceiptSandbox {
+ NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
+ NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
+ return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
+}
+
++ (BOOL)hasEmbeddedMobileProvision {
+ return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0;
+}
+
++ (BOOL)isSimulator {
+ NSString *platform = [FIRAppEnvironmentUtil deviceModel];
+ return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"];
+}
+
++ (NSString *)deviceModel {
+ static dispatch_once_t once;
+ static NSString *deviceModel;
+
+ dispatch_once(&once, ^{
+ struct utsname systemInfo;
+ if (uname(&systemInfo) == 0) {
+ deviceModel = [NSString stringWithUTF8String:systemInfo.machine];
+ }
+ });
+ return deviceModel;
+}
+
++ (NSString *)systemVersion {
+ return [UIDevice currentDevice].systemVersion;
+}
+
++ (BOOL)isAppExtension {
+ // Documented by <a href="https://goo.gl/RRB2Up">Apple</a>
+ BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
+ return appExtension;
+}
+
++ (UIApplication *)sharedApplication {
+ if ([FIRAppEnvironmentUtil isAppExtension]) {
+ return nil;
+ }
+ id sharedApplication = nil;
+ Class uiApplicationClass = NSClassFromString(@"UIApplication");
+ if (uiApplicationClass &&
+ [uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
+ sharedApplication = [uiApplicationClass sharedApplication];
+ }
+ return sharedApplication;
+}
+
+#pragma mark - Helper methods
+
++ (BOOL)hasSCInfoFolder {
+ NSString *bundlePath = [NSBundle mainBundle].bundlePath;
+ NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"];
+ return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath];
+}
+
+@end
diff --git a/Firebase/Core/FIRBundleUtil.m b/Firebase/Core/FIRBundleUtil.m
new file mode 100644
index 0000000..6c1d1e9
--- /dev/null
+++ b/Firebase/Core/FIRBundleUtil.m
@@ -0,0 +1,65 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRBundleUtil.h"
+
+@implementation FIRBundleUtil
+
++ (NSArray *)relevantBundles {
+ return @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
+}
+
++ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName
+ andFileType:(NSString *)fileType
+ inBundles:(NSArray *)bundles {
+ // Loop through all bundles to find the config dict.
+ for (NSBundle *bundle in bundles) {
+ NSString *path = [bundle pathForResource:resourceName ofType:fileType];
+ // Use the first one we find.
+ if (path) {
+ return path;
+ }
+ }
+ return nil;
+}
+
++ (NSArray *)relevantURLSchemes {
+ NSMutableArray *result = [[NSMutableArray alloc] init];
+ for (NSBundle *bundle in [[self class] relevantBundles]) {
+ NSArray *urlTypes = [bundle objectForInfoDictionaryKey:@"CFBundleURLTypes"];
+ for (NSDictionary *urlType in urlTypes) {
+ [result addObjectsFromArray:urlType[@"CFBundleURLSchemes"]];
+ }
+ }
+ return result;
+}
+
++ (NSSet *)relevantBundleIdentifiers {
+ NSMutableSet *result = [[NSMutableSet alloc] init];
+ for (NSBundle *bundle in [[self class] relevantBundles]) {
+ [result addObject:[bundle bundleIdentifier]];
+ }
+ return result;
+}
+
++ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles {
+ for (NSBundle *bundle in bundles) {
+ if ([bundle.bundleIdentifier isEqualToString:bundleIdentifier]) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+@end
diff --git a/Firebase/Core/FIRConfiguration.h b/Firebase/Core/FIRConfiguration.h
new file mode 100644
index 0000000..496b211
--- /dev/null
+++ b/Firebase/Core/FIRConfiguration.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FIRAnalyticsConfiguration.h"
+#import "FIRCoreSwiftNameSupport.h"
+#import "FIRLoggerLevel.h"
+
+/**
+ * The log levels used by FIRConfiguration.
+ */
+typedef NS_ENUM(NSInteger, FIRLogLevel) {
+ /** Error */
+ kFIRLogLevelError __deprecated = 0,
+ /** Warning */
+ kFIRLogLevelWarning __deprecated,
+ /** Info */
+ kFIRLogLevelInfo __deprecated,
+ /** Debug */
+ kFIRLogLevelDebug __deprecated,
+ /** Assert */
+ kFIRLogLevelAssert __deprecated,
+ /** Max */
+ kFIRLogLevelMax __deprecated = kFIRLogLevelAssert
+} DEPRECATED_MSG_ATTRIBUTE(
+ "Use -FIRDebugEnabled and -FIRDebugDisabled or setLoggerLevel. See FIRApp.h for more details.");
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This interface provides global level properties that the developer can tweak, and the singleton
+ * of the Firebase Analytics configuration class.
+ */
+FIR_SWIFT_NAME(FirebaseConfiguration)
+@interface FIRConfiguration : NSObject
+
+
+#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
+/** Returns the shared configuration object. */
+@property(class, nonatomic, readonly) FIRConfiguration *sharedInstance FIR_SWIFT_NAME(shared);
+#else
+/** Returns the shared configuration object. */
++ (FIRConfiguration *)sharedInstance FIR_SWIFT_NAME(shared());
+#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
+
+/** The configuration class for Firebase Analytics. */
+@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration;
+
+/** Global log level. Defaults to kFIRLogLevelError. */
+@property(nonatomic, readwrite, assign) FIRLogLevel logLevel DEPRECATED_MSG_ATTRIBUTE(
+ "Use -FIRDebugEnabled and -FIRDebugDisabled or setLoggerLevel. See FIRApp.h for more details.");
+
+/**
+ * Sets the logging level for internal Firebase logging. Firebase will only log messages
+ * that are logged at or below loggerLevel. The messages are logged both to the Xcode
+ * console and to the device's log. Note that if an app is running from AppStore, it will
+ * never log above FIRLoggerLevelNotice even if loggerLevel is set to a higher (more verbose)
+ * setting.
+ *
+ * @param loggerLevel The maximum logging level. The default level is set to FIRLoggerLevelNotice.
+ */
+- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/FIRConfiguration.m b/Firebase/Core/FIRConfiguration.m
new file mode 100644
index 0000000..921aa48
--- /dev/null
+++ b/Firebase/Core/FIRConfiguration.m
@@ -0,0 +1,51 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "FIRConfiguration.h"
+
+extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
+
+@implementation FIRConfiguration
+
++ (instancetype)sharedInstance {
+ static FIRConfiguration *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[FIRConfiguration alloc] init];
+ });
+ return sharedInstance;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _analyticsConfiguration = [FIRAnalyticsConfiguration sharedInstance];
+ }
+ return self;
+}
+
+// This is deprecated, use setLoggerLevel instead.
+- (void)setLogLevel:(FIRLogLevel)logLevel {
+ NSAssert(logLevel <= kFIRLogLevelMax, @"Invalid log level, %ld", (long)logLevel);
+ _logLevel = logLevel;
+}
+
+- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel {
+ NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin,
+ @"Invalid logger level, %ld", (long)loggerLevel);
+ FIRSetLoggerLevel(loggerLevel);
+}
+
+
+@end
diff --git a/Firebase/Core/FIRCoreSwiftNameSupport.h b/Firebase/Core/FIRCoreSwiftNameSupport.h
new file mode 100644
index 0000000..f58bdd7
--- /dev/null
+++ b/Firebase/Core/FIRCoreSwiftNameSupport.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIR_SWIFT_NAME
+
+#import <Foundation/Foundation.h>
+
+// NS_SWIFT_NAME can only translate factory methods before the iOS 9.3 SDK.
+// // Wrap it in our own macro if it's a non-compatible SDK.
+#ifdef __IPHONE_9_3
+#define FIR_SWIFT_NAME(X) NS_SWIFT_NAME(X)
+#else
+#define FIR_SWIFT_NAME(X) // Intentionally blank.
+#endif // #ifdef __IPHONE_9_3
+
+#endif // FIR_SWIFT_NAME
diff --git a/Firebase/Core/FIRErrors.m b/Firebase/Core/FIRErrors.m
new file mode 100644
index 0000000..3c7e39a
--- /dev/null
+++ b/Firebase/Core/FIRErrors.m
@@ -0,0 +1,33 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRErrors.h"
+
+NSString *const kFirebaseErrorDomain = @"com.firebase";
+NSString *const kFirebaseAdMobErrorDomain = @"com.firebase.admob";
+NSString *const kFirebaseAppInviteErrorDomain = @"com.firebase.appinvite";
+NSString *const kFirebaseAuthErrorDomain = @"com.firebase.auth";
+NSString *const kFirebaseCloudMessagingErrorDomain = @"com.firebase.cloudmessaging";
+NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config";
+NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core";
+NSString *const kFirebaseCrashReportingErrorDomain = @"com.firebase.crashreporting";
+NSString *const kFirebaseDatabaseErrorDomain = @"com.firebase.database";
+NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeeplink";
+NSString *const kFirebaseInstanceIDErrorDomain = @"com.firebase.instanceid";
+NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf";
+NSString *const kFirebaseStorageErrorDomain = @"com.firebase.storage";
+
+NSError *FIRCreateError(NSString *domain, enum FIRErrorCode code, NSDictionary *userInfo) {
+ return [NSError errorWithDomain:domain code:code userInfo:userInfo];
+}
diff --git a/Firebase/Core/FIRLogger.m b/Firebase/Core/FIRLogger.m
new file mode 100644
index 0000000..0e3e325
--- /dev/null
+++ b/Firebase/Core/FIRLogger.m
@@ -0,0 +1,228 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRLogger.h"
+
+#import "FIRLoggerLevel.h"
+#import "Private/FIRAppEnvironmentUtil.h"
+
+#include <asl.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+
+FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]";
+FIRLoggerService kFIRLoggerAdMob = @"[Firebase/AdMob]";
+FIRLoggerService kFIRLoggerAnalytics = @"[Firebase/Analytics]";
+FIRLoggerService kFIRLoggerAuth = @"[Firebase/Auth]";
+FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]";
+FIRLoggerService kFIRLoggerCrash = @"[Firebase/Crash]";
+FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]";
+FIRLoggerService kFIRLoggerDynamicLinks = @"[Firebase/DynamicLinks]";
+FIRLoggerService kFIRLoggerInstanceID = @"[Firebase/InstanceID]";
+FIRLoggerService kFIRLoggerInvites = @"[Firebase/Invites]";
+FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]";
+FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]";
+FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]";
+FIRLoggerService kFIRLoggerStorage = @"[Firebase/Storage]";
+
+/// Arguments passed on launch.
+NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled";
+NSString *const kFIREnableDebugModeApplicationArgument = @"-FIRDebugEnabled";
+
+/// Key for the debug mode bit in NSUserDefaults.
+NSString *const kFIRPersistedDebugModeKey = @"/google/firebase/debug_mode";
+
+/// ASL client facility name used by FIRLogger.
+const char *kFIRLoggerASLClientFacilityName = "com.firebase.app.logger";
+
+/// Message format used by ASL client that matches format of NSLog.
+const char *kFIRLoggerCustomASLMessageFormat =
+ "$((Time)(J.3)) $(Sender)[$(PID)] <$((Level)(str))> $Message";
+
+static dispatch_once_t sFIRLoggerOnceToken;
+
+static aslclient sFIRLoggerClient;
+
+static dispatch_queue_t sFIRClientQueue;
+
+static BOOL sFIRLoggerDebugMode;
+
+// The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled
+// flags used by Analytics. Users who use those flags expect Analytics to log verbosely,
+// while the rest of Firebase logs at the default level. This flag is introduced to support
+// that behavior.
+static BOOL sFIRAnalyticsDebugMode;
+
+static FIRLoggerLevel sFIRLoggerMaximumLevel;
+
+/// The regex pattern for the message code.
+static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$";
+static NSRegularExpression *sMessageCodeRegex;
+
+void FIRLoggerInitializeASL() {
+ dispatch_once(&sFIRLoggerOnceToken, ^{
+ // Initialize the ASL client handle.
+ sFIRLoggerClient = asl_open(NULL, kFIRLoggerASLClientFacilityName, ASL_OPT_STDERR);
+
+ // Set the filter used by system/device log. Initialize in default mode.
+ asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE));
+ sFIRLoggerDebugMode = NO;
+ sFIRAnalyticsDebugMode = NO;
+ sFIRLoggerMaximumLevel = FIRLoggerLevelNotice;
+
+ NSArray *arguments = [NSProcessInfo processInfo].arguments;
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+ BOOL debugMode = [userDefaults boolForKey:kFIRPersistedDebugModeKey];
+
+ if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode
+ [userDefaults removeObjectForKey:kFIRPersistedDebugModeKey];
+ } else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument]
+ || debugMode) { // Debug mode
+ [userDefaults setBool:YES forKey:kFIRPersistedDebugModeKey];
+ asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
+ sFIRLoggerDebugMode = YES;
+ }
+
+ // We should disable debug mode if we are running from App Store.
+ if (sFIRLoggerDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) {
+ sFIRLoggerDebugMode = NO;
+ }
+
+ // Need to call asl_add_output_file so that the logs can appear in Xcode's console view. Set
+ // the ASL filter mask for this output file up to debug level so that all messages are viewable
+ // in the console.
+ asl_add_output_file(sFIRLoggerClient, STDERR_FILENO, kFIRLoggerCustomASLMessageFormat,
+ ASL_TIME_FMT_LCL, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG), ASL_ENCODE_SAFE);
+
+ sFIRClientQueue = dispatch_queue_create("FIRLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
+ dispatch_set_target_queue(sFIRClientQueue,
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
+
+ sMessageCodeRegex =
+ [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL];
+ });
+}
+
+void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) {
+ FIRLoggerInitializeASL();
+ dispatch_async(sFIRClientQueue, ^{
+ // We should not enable debug mode if we are running from App Store.
+ if (analyticsDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) {
+ return;
+ }
+ sFIRAnalyticsDebugMode = analyticsDebugMode;
+ asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
+ });
+}
+
+void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) {
+ if (loggerLevel < FIRLoggerLevelMin || loggerLevel > FIRLoggerLevelMax) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000023", @"Invalid logger level, %ld", (long)loggerLevel);
+ return;
+ }
+ FIRLoggerInitializeASL();
+ dispatch_async(sFIRClientQueue, ^{
+ // We should not raise the logger level if we are running from App Store.
+ if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) {
+ return;
+ }
+
+ sFIRLoggerMaximumLevel = loggerLevel;
+ asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel));
+ });
+}
+
+BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent) {
+ FIRLoggerInitializeASL();
+ if (sFIRLoggerDebugMode) {
+ return YES;
+ } else if (sFIRAnalyticsDebugMode && analyticsComponent) {
+ return YES;
+ }
+ return (BOOL)(loggerLevel <= sFIRLoggerMaximumLevel);
+}
+
+#ifdef DEBUG
+void FIRResetLogger() {
+ sFIRLoggerOnceToken = 0;
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:kFIRPersistedDebugModeKey];
+}
+
+aslclient getFIRLoggerClient() {
+ return sFIRLoggerClient;
+}
+
+dispatch_queue_t getFIRClientQueue() {
+ return sFIRClientQueue;
+}
+
+BOOL getFIRLoggerDebugMode() {
+ return sFIRLoggerDebugMode;
+}
+#endif
+
+void FIRLogBasic(FIRLoggerLevel level, FIRLoggerService service, NSString *messageCode,
+ NSString *message, va_list args_ptr) {
+ FIRLoggerInitializeASL();
+ BOOL canLog = level <= sFIRLoggerMaximumLevel;
+
+ if (sFIRLoggerDebugMode) {
+ canLog = YES;
+ } else if (sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service]) {
+ canLog = YES;
+ }
+
+ if (!canLog) {
+ return;
+ }
+#ifdef DEBUG
+ NSCAssert(messageCode.length == 11, @"Incorrect message code length.");
+ NSRange messageCodeRange = NSMakeRange(0, messageCode.length);
+ NSUInteger numberOfMatches =
+ [sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange];
+ NSCAssert(numberOfMatches == 1, @"Incorrect message code format.");
+#endif
+ NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
+ logMsg = [NSString stringWithFormat:@"%@[%@] %@", service, messageCode, logMsg];
+ dispatch_async(sFIRClientQueue, ^{
+ asl_log(sFIRLoggerClient, NULL, level, "%s", logMsg.UTF8String);
+ });
+}
+
+/**
+ * Generates the logging functions using macros.
+ *
+ * Calling FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows:
+ * yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [Firebase/Core][I-COR000001] Configure blah failed.
+ * Calling FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configure succeed.") shows:
+ * yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [Firebase/Core][I-COR000001] Configure succeed.
+ */
+#define FIR_LOGGING_FUNCTION(level) \
+void FIRLog##level(FIRLoggerService service, NSString *messageCode, NSString *message, ...) { \
+ va_list args_ptr; \
+ va_start(args_ptr, message); \
+ FIRLogBasic(FIRLoggerLevel##level, service, messageCode, message, args_ptr); \
+ va_end(args_ptr); \
+}
+
+FIR_LOGGING_FUNCTION(Error)
+FIR_LOGGING_FUNCTION(Warning)
+FIR_LOGGING_FUNCTION(Notice)
+FIR_LOGGING_FUNCTION(Info)
+FIR_LOGGING_FUNCTION(Debug)
+
+#undef FIR_MAKE_LOGGER
diff --git a/Firebase/Core/FIRLoggerLevel.h b/Firebase/Core/FIRLoggerLevel.h
new file mode 100644
index 0000000..fe0d47d
--- /dev/null
+++ b/Firebase/Core/FIRLoggerLevel.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRCoreSwiftNameSupport.h"
+
+/**
+ * The log levels used by internal logging.
+ */
+typedef NS_ENUM(NSInteger, FIRLoggerLevel) {
+ FIRLoggerLevelError = 3 /*ASL_LEVEL_ERR*/,
+ FIRLoggerLevelWarning = 4 /*ASL_LEVEL_WARNING*/,
+ FIRLoggerLevelNotice = 5 /*ASL_LEVEL_NOTICE*/,
+ FIRLoggerLevelInfo = 6 /*ASL_LEVEL_INFO*/,
+ FIRLoggerLevelDebug = 7 /*ASL_LEVEL_DEBUG*/,
+ FIRLoggerLevelMin = FIRLoggerLevelError,
+ FIRLoggerLevelMax = FIRLoggerLevelDebug
+} FIR_SWIFT_NAME(FirebaseLoggerLevel);
diff --git a/Firebase/Core/FIRMutableDictionary.m b/Firebase/Core/FIRMutableDictionary.m
new file mode 100644
index 0000000..1d6ef3a
--- /dev/null
+++ b/Firebase/Core/FIRMutableDictionary.m
@@ -0,0 +1,97 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRMutableDictionary.h"
+
+@implementation FIRMutableDictionary {
+ /// The mutable dictionary.
+ NSMutableDictionary *_objects;
+
+ /// Serial synchronization queue. All reads should use dispatch_sync, while writes use
+ /// dispatch_async.
+ dispatch_queue_t _queue;
+}
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ _objects = [[NSMutableDictionary alloc] init];
+ _queue = dispatch_queue_create("FIRMutableDictionary", DISPATCH_QUEUE_SERIAL);
+ }
+
+ return self;
+}
+
+- (NSString *)description {
+ __block NSString *description;
+ dispatch_sync(_queue, ^{
+ description = _objects.description;
+ });
+ return description;
+}
+
+- (id)objectForKey:(id)key {
+ __block id object;
+ dispatch_sync(_queue, ^{
+ object = _objects[key];
+ });
+ return object;
+}
+
+- (void)setObject:(id)object forKey:(id<NSCopying>)key {
+ dispatch_async(_queue, ^{
+ _objects[key] = object;
+ });
+}
+
+- (void)removeObjectForKey:(id)key {
+ dispatch_async(_queue, ^{
+ [_objects removeObjectForKey:key];
+ });
+}
+
+- (void)removeAllObjects {
+ dispatch_async(_queue, ^{
+ [_objects removeAllObjects];
+ });
+}
+
+- (NSUInteger)count {
+ __block NSUInteger count;
+ dispatch_sync(_queue, ^{
+ count = _objects.count;
+ });
+ return count;
+}
+
+- (id)objectForKeyedSubscript:(id<NSCopying>)key {
+ // The method this calls is already synchronized.
+ return [self objectForKey:key];
+}
+
+- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
+ // The method this calls is already synchronized.
+ [self setObject:obj forKey:key];
+}
+
+- (NSDictionary *)dictionary {
+ __block NSDictionary *dictionary;
+ dispatch_sync(_queue, ^{
+ dictionary = [_objects copy];
+ });
+ return dictionary;
+}
+
+@end
diff --git a/Firebase/Core/FIRNetwork.m b/Firebase/Core/FIRNetwork.m
new file mode 100644
index 0000000..4926b2f
--- /dev/null
+++ b/Firebase/Core/FIRNetwork.m
@@ -0,0 +1,390 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRNetwork.h"
+#import "Private/FIRNetworkMessageCode.h"
+
+#import "Private/FIRMutableDictionary.h"
+#import "Private/FIRNetworkConstants.h"
+#import "Private/FIRReachabilityChecker.h"
+#import "Private/FIRLogger.h"
+
+#import <GoogleToolboxForMac/GTMNSData+zlib.h>
+
+/// Constant string for request header Content-Encoding.
+static NSString *const kFIRNetworkContentCompressionKey = @"Content-Encoding";
+
+/// Constant string for request header Content-Encoding value.
+static NSString *const kFIRNetworkContentCompressionValue = @"gzip";
+
+/// Constant string for request header Content-Length.
+static NSString *const kFIRNetworkContentLengthKey = @"Content-Length";
+
+/// Constant string for request header Content-Type.
+static NSString *const kFIRNetworkContentTypeKey = @"Content-Type";
+
+/// Constant string for request header Content-Type value.
+static NSString *const kFIRNetworkContentTypeValue = @"application/x-www-form-urlencoded";
+
+/// Constant string for GET request method.
+static NSString *const kFIRNetworkGETRequestMethod = @"GET";
+
+/// Constant string for POST request method.
+static NSString *const kFIRNetworkPOSTRequestMethod = @"POST";
+
+/// Default constant string as a prefix for network logger.
+static NSString *const kFIRNetworkLogTag = @"Firebase/Network";
+
+@interface FIRNetwork ()<FIRReachabilityDelegate, FIRNetworkLoggerDelegate>
+@end
+
+@implementation FIRNetwork {
+ /// Network reachability.
+ FIRReachabilityChecker *_reachability;
+
+ /// The dictionary of requests by session IDs { NSString : id }.
+ FIRMutableDictionary *_requests;
+}
+
+- (instancetype)init {
+ return [self initWithReachabilityHost:kFIRNetworkReachabilityHost];
+}
+
+- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {
+ self = [super init];
+ if (self) {
+ // Setup reachability.
+ _reachability =
+ [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self
+ loggerDelegate:self
+ withHost:reachabilityHost];
+ if (![_reachability start]) {
+ return nil;
+ }
+
+ _requests = [[FIRMutableDictionary alloc] init];
+ _timeoutInterval = kFIRNetworkTimeOutInterval;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ _reachability.reachabilityDelegate = nil;
+ [_reachability stop];
+}
+
+#pragma mark - External Methods
+
++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
+ completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler {
+ [FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
+ completionHandler:completionHandler];
+}
+
+- (NSString *)postURL:(NSURL *)url
+ payload:(NSData *)payload
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(FIRNetworkCompletionHandler)handler {
+ if (!url.absoluteString.length) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
+ return nil;
+ }
+
+ NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
+
+ NSMutableURLRequest *request =
+ [[NSMutableURLRequest alloc] initWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:timeOutInterval];
+
+ if (!request) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ NSError *compressError = nil;
+ NSData *compressedData = [NSData gtm_dataByGzippingData:payload error:&compressError];
+ if (!compressedData || compressError) {
+ if (compressError || payload.length > 0) {
+ // If the payload is not empty but it fails to compress the payload, something has been wrong.
+ [self handleErrorWithCode:FIRErrorCodeNetworkPayloadCompression
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+ compressedData = [[NSData alloc] init];
+ }
+
+ NSString *postLength = @(compressedData.length).stringValue;
+
+ // Set up the request with the compressed data.
+ [request setValue:postLength forHTTPHeaderField:kFIRNetworkContentLengthKey];
+ request.HTTPBody = compressedData;
+ request.HTTPMethod = kFIRNetworkPOSTRequestMethod;
+ [request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey];
+ [request setValue:kFIRNetworkContentCompressionValue
+ forHTTPHeaderField:kFIRNetworkContentCompressionKey];
+
+ FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
+ fetcher.backgroundNetworkEnabled = usingBackgroundSession;
+
+ __weak FIRNetwork *weakSelf = self;
+ NSString *requestID = [fetcher
+ sessionIDFromAsyncPOSTRequest:request
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data,
+ NSString *sessionID, NSError *error) {
+ FIRNetwork *strongSelf = weakSelf;
+ if (!strongSelf) {
+ return;
+ }
+ dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
+ dispatch_async(queueToDispatch, ^{
+ if (sessionID.length) {
+ [strongSelf->_requests removeObjectForKey:sessionID];
+ }
+ if (handler) {
+ handler(response, data, error);
+ }
+ });
+ }];
+ if (!requestID) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeNetwork000
+ message:@"Uploading data. Host"
+ context:url];
+ _requests[requestID] = fetcher;
+ return requestID;
+}
+
+- (NSString *)getURL:(NSURL *)url
+ headers:(NSDictionary *)headers
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(FIRNetworkCompletionHandler)handler {
+ if (!url.absoluteString.length) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
+ return nil;
+ }
+
+ NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
+ NSMutableURLRequest *request =
+ [[NSMutableURLRequest alloc] initWithURL:url
+ cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
+ timeoutInterval:timeOutInterval];
+
+ if (!request) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ request.HTTPMethod = kFIRNetworkGETRequestMethod;
+ request.allHTTPHeaderFields = headers;
+
+ FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
+ fetcher.backgroundNetworkEnabled = usingBackgroundSession;
+
+ __weak FIRNetwork *weakSelf = self;
+ NSString *requestID = [fetcher
+ sessionIDFromAsyncGETRequest:request
+ completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
+ NSError *error) {
+ FIRNetwork *strongSelf = weakSelf;
+ if (!strongSelf) {
+ return;
+ }
+ dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
+ dispatch_async(queueToDispatch, ^{
+ if (sessionID.length) {
+ [strongSelf->_requests removeObjectForKey:sessionID];
+ }
+ if (handler) {
+ handler(response, data, error);
+ }
+ });
+ }];
+
+ if (!requestID) {
+ [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
+ queue:queue
+ withHandler:handler];
+ return nil;
+ }
+
+ [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeNetwork001
+ message:@"Downloading data. Host"
+ context:url];
+ _requests[requestID] = fetcher;
+ return requestID;
+}
+
+- (BOOL)hasUploadInProgress {
+ return _requests.count > 0;
+}
+
+#pragma mark - Network Reachability
+
+/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network
+/// reachability has changed.
+- (void)reachability:(FIRReachabilityChecker *)reachability
+ statusChanged:(FIRReachabilityStatus)status {
+ _networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi);
+ [_reachabilityDelegate reachabilityDidChange];
+}
+
+#pragma mark - Network logger delegate
+
+- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
+ // Explicitly check whether the delegate responds to the methods because conformsToProtocol does
+ // not work correctly even though the delegate does respond to the methods.
+ if (!loggerDelegate ||
+ ![loggerDelegate
+ respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:contexts:)] ||
+ ![loggerDelegate
+ respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] ||
+ ![loggerDelegate
+ respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) {
+ FIRLogError(kFIRLoggerAnalytics,
+ [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002],
+ @"Cannot set the network logger delegate: delegate does not conform to the network "
+ "logger protocol.");
+ return;
+ }
+ _loggerDelegate = loggerDelegate;
+}
+
+#pragma mark - Private methods
+
+/// Handles network error and calls completion handler with the error.
+- (void)handleErrorWithCode:(NSInteger)code
+ queue:(dispatch_queue_t)queue
+ withHandler:(FIRNetworkCompletionHandler)handler {
+ NSDictionary *userInfo = @{ kFIRNetworkErrorContext : @"Failed to create network request" };
+ NSError *error =
+ [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo];
+ [self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
+ messageCode:kFIRNetworkMessageCodeNetwork002
+ message:@"Failed to create network request. Code, error"
+ contexts:@[ @(code), error ]];
+ if (handler) {
+ dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
+ dispatch_async(queueToDispatch, ^{
+ handler(nil, nil, error);
+ });
+ }
+}
+
+#pragma mark - Network logger
+
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message
+ contexts:(NSArray *)contexts {
+ // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log
+ // errors/warnings/info messages to the console log.
+ if (_loggerDelegate) {
+ [_loggerDelegate firNetwork_logWithLevel:logLevel
+ messageCode:messageCode
+ message:message
+ contexts:contexts];
+ return;
+ }
+ if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError ||
+ logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) {
+ NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts);
+ NSLog(@"%@", formattedMessage);
+ FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore,
+ [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
+ NULL);
+ }
+}
+
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message
+ context:(id)context {
+ if (_loggerDelegate) {
+ [_loggerDelegate firNetwork_logWithLevel:logLevel
+ messageCode:messageCode
+ message:message
+ context:context];
+ return;
+ }
+ NSArray *contexts = context ? @[ context ] : @[];
+ [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
+}
+
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message {
+ if (_loggerDelegate) {
+ [_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
+ return;
+ }
+ [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
+}
+
+/// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR").
+static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) {
+ static NSDictionary *levelNames = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ levelNames = @{
+ @(kFIRNetworkLogLevelError) : @"ERROR",
+ @(kFIRNetworkLogLevelWarning) : @"WARNING",
+ @(kFIRNetworkLogLevelInfo) : @"INFO",
+ @(kFIRNetworkLogLevelDebug) : @"DEBUG"
+ };
+ });
+ return levelNames[@(logLevel)];
+}
+
+/// Returns a formatted string to be used for console logging.
+static NSString *FIRStringWithLogMessage(NSString *message, FIRNetworkLogLevel logLevel,
+ NSArray *contexts) {
+ if (!message) {
+ message = @"(Message was nil)";
+ } else if (!message.length) {
+ message = @"(Message was empty)";
+ }
+ NSMutableString *result = [[NSMutableString alloc]
+ initWithFormat:@"<%@/%@> %@", kFIRNetworkLogTag, FIRLogLevelDescriptionFromLogLevel(logLevel),
+ message];
+
+ if (!contexts.count) {
+ return result;
+ }
+
+ NSMutableArray *formattedContexts = [[NSMutableArray alloc] init];
+ for (id item in contexts) {
+ [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")];
+ }
+
+ [result appendString:@": "];
+ [result appendString:[formattedContexts componentsJoinedByString:@", "]];
+ return result;
+}
+
+@end
diff --git a/Firebase/Core/FIRNetworkConstants.m b/Firebase/Core/FIRNetworkConstants.m
new file mode 100644
index 0000000..7ba0e15
--- /dev/null
+++ b/Firebase/Core/FIRNetworkConstants.m
@@ -0,0 +1,39 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRNetworkConstants.h"
+
+@import Foundation;
+
+NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix =
+ @"com.firebase.network.background-upload";
+NSString *const kFIRNetworkApplicationSupportSubdirectory = @"Firebase/Network";
+NSString *const kFIRNetworkTempDirectoryName = @"FIRNetworkTemporaryDirectory";
+const NSTimeInterval kFIRNetworkTempFolderExpireTime = 60 * 60; // 1 hour
+const NSTimeInterval kFIRNetworkTimeOutInterval = 60; // 1 minute.
+NSString *const kFIRNetworkReachabilityHost = @"app-measurement.com";
+NSString *const kFIRNetworkErrorContext = @"Context";
+
+const int kFIRNetworkHTTPStatusOK = 200;
+const int kFIRNetworkHTTPStatusNoContent = 204;
+const int kFIRNetworkHTTPStatusCodeMultipleChoices = 300;
+const int kFIRNetworkHTTPStatusCodeMovedPermanently = 301;
+const int kFIRNetworkHTTPStatusCodeFound = 302;
+const int kFIRNetworkHTTPStatusCodeNotModified = 304;
+const int kFIRNetworkHTTPStatusCodeMovedTemporarily = 307;
+const int kFIRNetworkHTTPStatusCodeNotFound = 404;
+const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic = 429;
+const int kFIRNetworkHTTPStatusCodeUnavailable = 503;
+
+NSString *const kFIRNetworkErrorDomain = @"com.firebase.network.ErrorDomain";
diff --git a/Firebase/Core/FIRNetworkURLSession.m b/Firebase/Core/FIRNetworkURLSession.m
new file mode 100644
index 0000000..2b17eb3
--- /dev/null
+++ b/Firebase/Core/FIRNetworkURLSession.m
@@ -0,0 +1,669 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRNetworkURLSession.h"
+
+#import "Private/FIRMutableDictionary.h"
+#import "Private/FIRNetworkConstants.h"
+#import "Private/FIRNetworkMessageCode.h"
+#import "Private/FIRLogger.h"
+
+@implementation FIRNetworkURLSession {
+ /// The handler to be called when the request completes or error has occurs.
+ FIRNetworkURLSessionCompletionHandler _completionHandler;
+
+ /// Session ID generated randomly with a fixed prefix.
+ NSString *_sessionID;
+
+ /// The session configuration.
+ NSURLSessionConfiguration *_sessionConfig;
+
+ /// The path to the directory where all temporary files are stored before uploading.
+ NSURL *_networkDirectoryURL;
+
+ /// The downloaded data from fetching.
+ NSData *_downloadedData;
+
+ /// The path to the temporary file which stores the uploading data.
+ NSURL *_uploadingFileURL;
+
+ /// The current request.
+ NSURLRequest *_request;
+}
+
+#pragma mark - Init
+
+- (instancetype)initWithNetworkLoggerDelegate:(id<FIRNetworkLoggerDelegate>)networkLoggerDelegate {
+ self = [super init];
+ if (self) {
+ // Create URL to the directory where all temporary files to upload have to be stored.
+ NSArray *paths =
+ NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ NSString *applicationSupportDirectory = paths.firstObject;
+ NSArray *tempPathComponents = @[
+ applicationSupportDirectory,
+ kFIRNetworkApplicationSupportSubdirectory,
+ kFIRNetworkTempDirectoryName
+ ];
+ _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents];
+ _sessionID = [NSString stringWithFormat:@"%@-%@", kFIRNetworkBackgroundSessionConfigIDPrefix,
+ [[NSUUID UUID] UUIDString]];
+ _loggerDelegate = networkLoggerDelegate;
+ }
+ return self;
+}
+
+#pragma mark - External Methods
+
+#pragma mark - To be called from AppDelegate
+
++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
+ completionHandler:
+ (FIRNetworkSystemCompletionHandler)systemCompletionHandler {
+ // The session may not be FIRAnalytics background. Ignore those that do not have the prefix.
+ if (![sessionID hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
+ return;
+ }
+ FIRNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID];
+ if (fetcher != nil) {
+ [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID];
+ } else {
+ FIRLogError(kFIRLoggerCore,
+ [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork003],
+ @"Failed to retrieve background session with ID %@ after app is relaunched.",
+ sessionID);
+ }
+}
+
+#pragma mark - External Methods
+
+/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the
+/// connection.
+- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
+ completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
+ // NSURLSessionUploadTask does not work with NSData in the background.
+ // To avoid this issue, write the data to a temporary file to upload it.
+ // Make a temporary file with the data subset.
+ _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID];
+ NSError *writeError;
+ NSURLSessionUploadTask *postRequestTask;
+ NSURLSession *session;
+ BOOL didWriteFile = NO;
+
+ // Clean up the entire temp folder to avoid temp files that remain in case the previous session
+ // crashed and did not clean up.
+ [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
+ expiringTime:kFIRNetworkTempFolderExpireTime];
+
+ // If there is no background network enabled, no need to write to file. This will allow default
+ // network session which runs on the foreground.
+ if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) {
+ didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path
+ options:NSDataWritingAtomic
+ error:&writeError];
+
+ if (writeError) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession000
+ message:@"Failed to write request data to file"
+ context:writeError];
+ }
+ }
+
+ if (didWriteFile) {
+ // Exclude this file from backing up to iTunes. There are conflicting reports that excluding
+ // directory from backing up does not excluding files of that directory from backing up.
+ [self excludeFromBackupForURL:_uploadingFileURL];
+
+ _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
+ [self populateSessionConfig:_sessionConfig withRequest:request];
+ session = [NSURLSession sessionWithConfiguration:_sessionConfig
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL];
+ } else {
+ // If we cannot write to file, just send it in the foreground.
+ _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
+ [self populateSessionConfig:_sessionConfig withRequest:request];
+ _sessionConfig.URLCache = nil;
+ session = [NSURLSession sessionWithConfiguration:_sessionConfig
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody];
+ }
+
+ if (!session || !postRequestTask) {
+ NSError *error =
+ [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
+ code:FIRErrorCodeNetworkRequestCreation
+ userInfo:@{
+ kFIRNetworkErrorContext : @"Cannot create network session"
+ }];
+ [self callCompletionHandler:handler withResponse:nil data:nil error:error];
+ return nil;
+ }
+
+ // Save the session into memory.
+ NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
+ [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
+
+ _request = [request copy];
+
+ // Store completion handler because background session does not accept handler block but custom
+ // delegate.
+ _completionHandler = [handler copy];
+ [postRequestTask resume];
+
+ return _sessionID;
+}
+
+/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session.
+- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
+ completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
+ if (_backgroundNetworkEnabled) {
+ _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
+ } else {
+ _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
+ }
+
+ [self populateSessionConfig:_sessionConfig withRequest:request];
+
+ // Do not cache the GET request.
+ _sessionConfig.URLCache = nil;
+
+ NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig
+ delegate:self
+ delegateQueue:[NSOperationQueue mainQueue]];
+ NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
+
+ if (!session || !downloadTask) {
+ NSError *error =
+ [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
+ code:FIRErrorCodeNetworkRequestCreation
+ userInfo:@{
+ kFIRNetworkErrorContext : @"Cannot create network session"
+ }];
+ [self callCompletionHandler:handler withResponse:nil data:nil error:error];
+ return nil;
+ }
+
+ // Save the session into memory.
+ NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
+ [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
+
+ _request = [request copy];
+
+ _completionHandler = [handler copy];
+ [downloadTask resume];
+
+ return _sessionID;
+}
+
+#pragma mark - NSURLSessionTaskDelegate
+
+/// Called by the NSURLSession once the download task is completed. The file is saved in the
+/// provided URL so we need to read the data and store into _downloadedData. Once the session is
+/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will
+/// be called with the downloaded data.
+- (void)URLSession:(NSURLSession *)session
+ downloadTask:(NSURLSessionDownloadTask *)task
+ didFinishDownloadingToURL:(NSURL *)url {
+ if (!url.path) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession001
+ message:@"Unable to read downloaded data from empty temp path"];
+ _downloadedData = nil;
+ return;
+ }
+
+ NSError *error;
+ _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error];
+
+ if (error) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession002
+ message:@"Cannot read the content of downloaded data"
+ context:error];
+ _downloadedData = nil;
+ }
+}
+
+- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeURLSession003
+ message:@"Background session finished"
+ context:session.configuration.identifier];
+ [self callSystemCompletionHandler:session.configuration.identifier];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ didCompleteWithError:(NSError *)error {
+ // Avoid any chance of recursive behavior leading to it being used repeatedly.
+ FIRNetworkURLSessionCompletionHandler handler = _completionHandler;
+ _completionHandler = nil;
+
+ if (task.response) {
+ // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7.
+ NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP");
+
+ // The server responded so ignore the error created by the system.
+ error = nil;
+ } else if (!error) {
+ error =
+ [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
+ code:FIRErrorCodeNetworkInvalidResponse
+ userInfo:@{
+ kFIRNetworkErrorContext : @"Network Error: Empty network response"
+ }];
+ }
+
+ [self callCompletionHandler:handler
+ withResponse:(NSHTTPURLResponse *)task.response
+ data:_downloadedData
+ error:error];
+
+ // Remove the temp file to avoid trashing devices with lots of temp files.
+ [self removeTempItemAtURL:_uploadingFileURL];
+
+ // Try to clean up stale files again.
+ [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
+ expiringTime:kFIRNetworkTempFolderExpireTime];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
+ completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
+ NSURLCredential *credential))completionHandler {
+ // The handling is modeled after GTMSessionFetcher.
+ if ([challenge.protectionSpace.authenticationMethod
+ isEqualToString:NSURLAuthenticationMethodServerTrust]) {
+ SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
+ if (serverTrust == NULL) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeURLSession004
+ message:@"Received empty server trust for host. Host"
+ context:_request.URL];
+ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+ return;
+ }
+ NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
+ if (!credential) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
+ messageCode:kFIRNetworkMessageCodeURLSession005
+ message:@"Unable to verify server identity. Host"
+ context:_request.URL];
+ completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
+ return;
+ }
+
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeURLSession006
+ message:@"Received SSL challenge for host. Host"
+ context:_request.URL];
+
+ void (^callback)(BOOL) = ^(BOOL allow) {
+ if (allow) {
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+ } else {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeURLSession007
+ message:@"Cancelling authentication challenge for host. Host"
+ context:_request.URL];
+ completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
+ }
+ };
+
+ // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7.
+ CFRetain(serverTrust);
+
+ // Evaluate the certificate chain.
+ //
+ // The delegate queue may be the main thread. Trust evaluation could cause some
+ // blocking network activity, so we must evaluate async, as documented at
+ // https://developer.apple.com/library/ios/technotes/tn2232/
+ dispatch_queue_t evaluateBackgroundQueue =
+ dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+ dispatch_async(evaluateBackgroundQueue, ^{
+ SecTrustResultType trustEval = kSecTrustResultInvalid;
+ BOOL shouldAllow;
+ OSStatus trustError;
+
+ @synchronized([FIRNetworkURLSession class]) {
+ trustError = SecTrustEvaluate(serverTrust, &trustEval);
+ }
+
+ if (trustError != errSecSuccess) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession008
+ message:@"Cannot evaluate server trust. Error, host"
+ contexts:@[ @(trustError), _request.URL ]];
+ shouldAllow = NO;
+ } else {
+ // Having a trust level "unspecified" by the user is the usual result, described at
+ // https://developer.apple.com/library/mac/qa/qa1360
+ shouldAllow =
+ (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed);
+ }
+
+ // Call the call back with the permission.
+ callback(shouldAllow);
+
+ CFRelease(serverTrust);
+ });
+ return;
+ }
+
+ // Default handling for other Auth Challenges.
+ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
+}
+
+#pragma mark - Internal Methods
+
+/// Stores system completion handler with session ID as key.
+- (void)addSystemCompletionHandler:(FIRNetworkSystemCompletionHandler)handler
+ forSession:(NSString *)identifier {
+ if (!handler) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession009
+ message:@"Cannot store nil system completion handler in network"];
+ return;
+ }
+
+ if (!identifier.length) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession010
+ message:@"Cannot store system completion handler with empty network "
+ "session identifier"];
+ return;
+ }
+
+ FIRMutableDictionary *systemCompletionHandlers =
+ [[self class] sessionIDToSystemCompletionHandlerDictionary];
+ if (systemCompletionHandlers[identifier]) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
+ messageCode:kFIRNetworkMessageCodeURLSession011
+ message:@"Got multiple system handlers for a single session ID"
+ context:identifier];
+ }
+
+ systemCompletionHandlers[identifier] = handler;
+}
+
+/// Calls the system provided completion handler with the session ID stored in the dictionary.
+/// The handler will be removed from the dictionary after being called.
+- (void)callSystemCompletionHandler:(NSString *)identifier {
+ FIRMutableDictionary *systemCompletionHandlers =
+ [[self class] sessionIDToSystemCompletionHandlerDictionary];
+ FIRNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier];
+
+ if (handler) {
+ [systemCompletionHandlers removeObjectForKey:identifier];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ handler();
+ });
+ }
+}
+
+/// Sets or updates the session ID of this session.
+- (void)setSessionID:(NSString *)sessionID {
+ _sessionID = [sessionID copy];
+}
+
+/// Creates a background session configuration with the session ID using the supported method.
+- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID {
+#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && \
+ MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
+ (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && \
+ __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
+ // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
+ return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
+#elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && \
+ MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \
+ (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)
+ // Do a runtime check to avoid a deprecation warning about using
+ // +backgroundSessionConfiguration: on iOS 8.
+ if ([NSURLSessionConfiguration
+ respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
+ // Running on iOS 8+/OS X 10.10+.
+ return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
+ } else {
+ // Running on iOS 7/OS X 10.9.
+ return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
+ }
+#else
+ // Building with an SDK earlier than iOS 8/OS X 10.10.
+ return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
+#endif
+}
+
+- (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime {
+ if (!folderURL.absoluteString.length) {
+ return;
+ }
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error = nil;
+
+ NSArray *properties = @[ NSURLCreationDateKey ];
+ NSArray *directoryContent =
+ [fileManager contentsOfDirectoryAtURL:folderURL
+ includingPropertiesForKeys:properties
+ options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
+ error:&error];
+ if (error && error.code != NSFileReadNoSuchFileError) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeURLSession012
+ message:@"Cannot get files from the temporary network folder. Error"
+ context:error];
+ return;
+ }
+
+ if (!directoryContent.count) {
+ return;
+ }
+
+ NSTimeInterval now = [NSDate date].timeIntervalSince1970;
+ for (NSURL *tempFile in directoryContent) {
+ NSDate *creationDate;
+ BOOL getCreationDate =
+ [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL];
+ if (!getCreationDate) {
+ continue;
+ }
+ NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970;
+ if (fabs(now - creationTimeInterval) > staleTime) {
+ [self removeTempItemAtURL:tempFile];
+ }
+ }
+}
+
+/// Removes the temporary file written to disk for sending the request. It has to be cleaned up
+/// after the session is done.
+- (void)removeTempItemAtURL:(NSURL *)fileURL {
+ if (!fileURL.absoluteString.length) {
+ return;
+ }
+
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error = nil;
+
+ if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession013
+ message:@"Failed to remove temporary uploading data file. Error"
+ context:error.localizedDescription];
+ }
+}
+
+/// Gets the fetcher with the session ID.
++ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
+ NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap];
+ FIRNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
+ if (!session && [sessionIdentifier hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
+ session = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil];
+ [session setSessionID:sessionIdentifier];
+ [sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier];
+ }
+ return session;
+}
+
+/// Returns a map of the fetcher by session ID. Creates a map if it is not created.
++ (NSMapTable *)sessionIDToFetcherMap {
+ static NSMapTable *sessionIDToFetcherMap;
+
+ static dispatch_once_t sessionMapOnceToken;
+ dispatch_once(&sessionMapOnceToken, ^{
+ sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
+ });
+ return sessionIDToFetcherMap;
+}
+
+/// Returns a map of system provided completion handler by session ID. Creates a map if it is not
+/// created.
++ (FIRMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary {
+ static FIRMutableDictionary *systemCompletionHandlers;
+
+ static dispatch_once_t systemCompletionHandlerOnceToken;
+ dispatch_once(&systemCompletionHandlerOnceToken, ^{
+ systemCompletionHandlers = [[FIRMutableDictionary alloc] init];
+ });
+ return systemCompletionHandlers;
+}
+
+- (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID {
+ NSString *tempName = [NSString stringWithFormat:@"FIRUpload_temp_%@", sessionID];
+ return [_networkDirectoryURL URLByAppendingPathComponent:tempName];
+}
+
+/// Makes sure that the directory to store temp files exists. If not, tries to create it and returns
+/// YES. If there is anything wrong, returns NO.
+- (BOOL)ensureTemporaryDirectoryExists {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error = nil;
+
+ // Create a temporary directory if it does not exist or was deleted.
+ if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
+ return YES;
+ }
+
+ if (error && error.code != NSFileReadNoSuchFileError) {
+ [_loggerDelegate
+ firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
+ messageCode:kFIRNetworkMessageCodeURLSession014
+ message:@"Error while trying to access Network temp folder. Error"
+ context:error];
+ }
+
+ NSError *writeError = nil;
+
+ [fileManager createDirectoryAtURL:_networkDirectoryURL
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&writeError];
+ if (writeError) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession015
+ message:@"Cannot create temporary directory. Error"
+ context:writeError];
+ return NO;
+ }
+
+ // Set the iCloud exclusion attribute on the Documents URL.
+ [self excludeFromBackupForURL:_networkDirectoryURL];
+
+ return YES;
+}
+
+- (void)excludeFromBackupForURL:(NSURL *)url {
+ if (!url.path) {
+ return;
+ }
+
+ // Set the iCloud exclusion attribute on the Documents URL.
+ NSError *preventBackupError = nil;
+ [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError];
+ if (preventBackupError) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession016
+ message:@"Cannot exclude temporary folder from iTunes backup"];
+ }
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
+ newRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSURLRequest *))completionHandler {
+ NSArray *nonAllowedRedirectionCodes = @[
+ @(kFIRNetworkHTTPStatusCodeFound),
+ @(kFIRNetworkHTTPStatusCodeMovedPermanently),
+ @(kFIRNetworkHTTPStatusCodeMovedTemporarily),
+ @(kFIRNetworkHTTPStatusCodeMultipleChoices)
+ ];
+
+ // Allow those not in the non allowed list to be followed.
+ if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) {
+ completionHandler(request);
+ return;
+ }
+
+ // Do not allow redirection if the response code is in the non-allowed list.
+ NSURLRequest *newRequest = request;
+
+ if (response) {
+ newRequest = nil;
+ }
+
+ completionHandler(newRequest);
+}
+
+#pragma mark - Helper Methods
+
+- (void)callCompletionHandler:(FIRNetworkURLSessionCompletionHandler)handler
+ withResponse:(NSHTTPURLResponse *)response
+ data:(NSData *)data
+ error:(NSError *)error {
+ if (error) {
+ [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeURLSession017
+ message:@"Encounter network error. Code, error"
+ contexts:@[@(error.code), error]];
+ }
+
+ if (handler) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ handler(response, data, _sessionID, error);
+ });
+ }
+}
+
+- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
+ withRequest:(NSURLRequest *)request {
+ sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
+ sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
+ sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
+ sessionConfig.requestCachePolicy = request.cachePolicy;
+}
+
+@end
diff --git a/Firebase/Core/FIROptions.h b/Firebase/Core/FIROptions.h
new file mode 100644
index 0000000..5bae59c
--- /dev/null
+++ b/Firebase/Core/FIROptions.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FIRCoreSwiftNameSupport.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides constant fields of Google APIs.
+ */
+FIR_SWIFT_NAME(FirebaseOptions)
+@interface FIROptions : NSObject<NSCopying>
+
+/**
+ * Returns the default options.
+ */
++ (nullable FIROptions *)defaultOptions FIR_SWIFT_NAME(defaultOptions());
+
+/**
+ * An iOS API key used for authenticating requests from your app, e.g.
+ * @"AIzaSyDdVgKwhZl0sTTTLZ7iTmt1r3N2cJLnaDk", used to identify your app to Google servers.
+ */
+@property(nonatomic, copy, nullable) NSString *APIKey FIR_SWIFT_NAME(apiKey);
+
+/**
+ * The bundle ID for the application. Defaults to `[[NSBundle mainBundle] bundleID]` when not set
+ * manually or in a plist.
+ */
+@property(nonatomic, copy) NSString *bundleID;
+
+/**
+ * The OAuth2 client ID for iOS application used to authenticate Google users, for example
+ * @"12345.apps.googleusercontent.com", used for signing in with Google.
+ */
+@property(nonatomic, copy, nullable) NSString *clientID;
+
+/**
+ * The tracking ID for Google Analytics, e.g. @"UA-12345678-1", used to configure Google Analytics.
+ */
+@property(nonatomic, copy, nullable) NSString *trackingID;
+
+/**
+ * The Project Number from the Google Developer's console, for example @"012345678901", used to
+ * configure Google Cloud Messaging.
+ */
+@property(nonatomic, copy) NSString *GCMSenderID FIR_SWIFT_NAME(gcmSenderID);
+
+/**
+ * The Project ID from the Firebase console, for example @"abc-xyz-123".
+ */
+@property(nonatomic, copy, nullable) NSString *projectID;
+
+/**
+ * The Android client ID used in Google AppInvite when an iOS app has its Android version, for
+ * example @"12345.apps.googleusercontent.com".
+ */
+@property(nonatomic, copy, nullable) NSString *androidClientID;
+
+/**
+ * The Google App ID that is used to uniquely identify an instance of an app.
+ */
+@property(nonatomic, copy) NSString *googleAppID;
+
+/**
+ * The database root URL, e.g. @"http://abc-xyz-123.firebaseio.com".
+ */
+@property(nonatomic, copy, nullable) NSString *databaseURL;
+
+/**
+ * The URL scheme used to set up Durable Deep Link service.
+ */
+@property(nonatomic, copy, nullable) NSString *deepLinkURLScheme;
+
+/**
+ * The Google Cloud Storage bucket name, e.g. @"abc-xyz-123.storage.firebase.com".
+ */
+@property(nonatomic, copy, nullable) NSString *storageBucket;
+
+/**
+ * Initializes a customized instance of FIROptions with keys. googleAppID, bundleID and GCMSenderID
+ * are required. Other keys may required for configuring specific services.
+ */
+- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
+ bundleID:(NSString *)bundleID
+ GCMSenderID:(NSString *)GCMSenderID
+ APIKey:(NSString *)APIKey
+ clientID:(NSString *)clientID
+ trackingID:(NSString *)trackingID
+ androidClientID:(NSString *)androidClientID
+ databaseURL:(NSString *)databaseURL
+ storageBucket:(NSString *)storageBucket
+ deepLinkURLScheme:(NSString *)deepLinkURLScheme
+ DEPRECATED_MSG_ATTRIBUTE("Use `-[FIROptions initWithGoogleAppID:gcmSenderID:]` and "
+ "properties instead.");
+
+/**
+ * Initializes a customized instance of FIROptions from the file at the given plist file path.
+ * For example,
+ * NSString *filePath =
+ * [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
+ * FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:filePath];
+ * Returns nil if the plist file does not exist or is invalid.
+ */
+- (nullable instancetype)initWithContentsOfFile:(NSString *)plistPath;
+
+/**
+ * Initializes a customized instance of FIROptions with required fields. Use the mutable properties
+ * to modify fields for configuring specific services.
+ */
+- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
+ GCMSenderID:(NSString *)GCMSenderID
+ FIR_SWIFT_NAME(init(googleAppID:gcmSenderID:));
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/FIROptions.m b/Firebase/Core/FIROptions.m
new file mode 100644
index 0000000..6e19c82
--- /dev/null
+++ b/Firebase/Core/FIROptions.m
@@ -0,0 +1,427 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRBundleUtil.h"
+#import "Private/FIRErrors.h"
+#import "Private/FIRLogger.h"
+#import "Private/FIROptionsInternal.h"
+
+// Keys for the strings in the plist file.
+NSString *const kFIRAPIKey = @"API_KEY";
+NSString *const kFIRTrackingID = @"TRACKING_ID";
+NSString *const kFIRGoogleAppID = @"GOOGLE_APP_ID";
+NSString *const kFIRClientID = @"CLIENT_ID";
+NSString *const kFIRGCMSenderID = @"GCM_SENDER_ID";
+NSString *const kFIRAndroidClientID = @"ANDROID_CLIENT_ID";
+NSString *const kFIRDatabaseURL = @"DATABASE_URL";
+NSString *const kFIRStorageBucket = @"STORAGE_BUCKET";
+// The key to locate the expected bundle identifier in the plist file.
+NSString *const kFIRBundleID = @"BUNDLE_ID";
+// The key to locate the project identifier in the plist file.
+NSString *const kFIRProjectID = @"PROJECT_ID";
+
+NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
+NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
+NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED";
+
+NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED";
+NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED";
+
+// Library version ID.
+NSString *const kFIRLibraryVersionID =
+ @"4" // Major version (one or more digits)
+ @"00" // Minor version (exactly 2 digits)
+ @"00" // Build number (exactly 2 digits)
+ @"000"; // Fixed "000"
+// Plist file name.
+NSString *const kServiceInfoFileName = @"GoogleService-Info";
+// Plist file type.
+NSString *const kServiceInfoFileType = @"plist";
+
+// Exception raised from attempting to modify a FIROptions after it's been copied to a FIRApp.
+NSString *const kFIRExceptionBadModification =
+ @"Attempted to modify options after it's set on FIRApp. Please modify all properties before "
+ @"initializing FIRApp.";
+
+@interface FIROptions ()
+
+/**
+ * This property maintains the actual configuration key-value pairs.
+ */
+@property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary;
+
+/**
+ * Combination of analytics options from both the main plist and the GoogleService-info.plist.
+ * Values which are present in the main plist override values from the GoogleService-info.plist.
+ */
+@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
+
+@end
+
+@implementation FIROptions {
+ /// Backing variable for self.analyticsOptionsDictionary.
+ NSDictionary *_analyticsOptionsDictionary;
+ dispatch_once_t _createAnalyticsOptionsDictionaryOnce;
+}
+
+static FIROptions *sDefaultOptions = nil;
+static NSDictionary *sDefaultOptionsDictionary = nil;
+
+#pragma mark - Public only for internal class methods
+
++ (FIROptions *)defaultOptions {
+ if (sDefaultOptions != nil) {
+ return sDefaultOptions;
+ }
+
+ NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
+ if (defaultOptionsDictionary == nil) {
+ return nil;
+ }
+
+ sDefaultOptions =
+ [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
+ return sDefaultOptions;
+}
+
+#pragma mark - Private class methods
+
++ (NSDictionary *)defaultOptionsDictionary {
+ if (sDefaultOptionsDictionary != nil) {
+ return sDefaultOptionsDictionary;
+ }
+ NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
+ if (plistFilePath == nil) {
+ return nil;
+ }
+ sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
+ if (sDefaultOptionsDictionary == nil) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000011", @"The configuration file is not a dictionary: "
+ @"'%@.%@'.", kServiceInfoFileName, kServiceInfoFileType);
+ }
+ return sDefaultOptionsDictionary;
+}
+
+// Returns the path of the plist file with a given file name.
++ (NSString *)plistFilePathWithName:(NSString *)fileName {
+ NSArray *bundles = [FIRBundleUtil relevantBundles];
+ NSString *plistFilePath =
+ [FIRBundleUtil optionsDictionaryPathWithResourceName:fileName
+ andFileType:kServiceInfoFileType
+ inBundles:bundles];
+ if (plistFilePath == nil) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.",
+ fileName, kServiceInfoFileType);
+ }
+ return plistFilePath;
+}
+
++ (void)resetDefaultOptions {
+ sDefaultOptions = nil;
+ sDefaultOptionsDictionary = nil;
+}
+
+#pragma mark - Private instance methods
+
+- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary {
+ self = [super init];
+ if (self) {
+ _optionsDictionary = [optionsDictionary mutableCopy];
+ _usingOptionsFromDefaultPlist = YES;
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ FIROptions *newOptions = [[[self class] allocWithZone:zone] init];
+ if (newOptions) {
+ newOptions.optionsDictionary = self.optionsDictionary;
+ newOptions.deepLinkURLScheme = self.deepLinkURLScheme;
+ newOptions.editingLocked = self.isEditingLocked;
+ newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist;
+ }
+ return newOptions;
+}
+
+#pragma mark - Public instance methods
+
+- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
+ bundleID:(NSString *)bundleID
+ GCMSenderID:(NSString *)GCMSenderID
+ APIKey:(NSString *)APIKey
+ clientID:(NSString *)clientID
+ trackingID:(NSString *)trackingID
+ androidClientID:(NSString *)androidClientID
+ databaseURL:(NSString *)databaseURL
+ storageBucket:(NSString *)storageBucket
+ deepLinkURLScheme:(NSString *)deepLinkURLScheme {
+ self = [super init];
+ if (self) {
+ if (!googleAppID) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Please specify a valid Google App ID."];
+ } else if (!GCMSenderID) {
+ [NSException raise:kFirebaseCoreErrorDomain format:@"Please specify a valid GCM Sender ID."];
+ }
+
+ // `bundleID` is a required property, default to the main `bundleIdentifier` if it's `nil`.
+ if (!bundleID) {
+ bundleID = [[NSBundle mainBundle] bundleIdentifier];
+ }
+
+ NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary];
+ [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID];
+ [mutableOptionsDict setValue:bundleID forKey:kFIRBundleID];
+ [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID];
+ [mutableOptionsDict setValue:APIKey forKey:kFIRAPIKey];
+ [mutableOptionsDict setValue:clientID forKey:kFIRClientID];
+ [mutableOptionsDict setValue:trackingID forKey:kFIRTrackingID];
+ [mutableOptionsDict setValue:androidClientID forKey:kFIRAndroidClientID];
+ [mutableOptionsDict setValue:databaseURL forKey:kFIRDatabaseURL];
+ [mutableOptionsDict setValue:storageBucket forKey:kFIRStorageBucket];
+ self.optionsDictionary = mutableOptionsDict;
+ self.deepLinkURLScheme = deepLinkURLScheme;
+ }
+ return self;
+}
+
+- (instancetype)initWithContentsOfFile:(NSString *)plistPath {
+ self = [super init];
+ if (self) {
+ if (plistPath == nil) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil.");
+ return nil;
+ }
+ _optionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:plistPath] mutableCopy];
+ if (_optionsDictionary == nil) {
+ FIRLogError(kFIRLoggerCore, @"I-COR000014", @"The configuration file at %@ does not exist or "
+ @"is not a well-formed plist file.", plistPath);
+ return nil;
+ }
+ // TODO: Do we want to validate the dictionary here? It says we do that already in
+ // the public header.
+ }
+ return self;
+}
+
+- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
+ GCMSenderID:(NSString *)GCMSenderID {
+ self = [super init];
+ if (self) {
+ NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary];
+ [mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID];
+ [mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID];
+ [mutableOptionsDict setValue:[[NSBundle mainBundle] bundleIdentifier] forKey:kFIRBundleID];
+ self.optionsDictionary = mutableOptionsDict;
+ }
+ return self;
+}
+
+- (NSString *)APIKey {
+ return self.optionsDictionary[kFIRAPIKey];
+}
+
+- (void)setAPIKey:(NSString *)APIKey {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRAPIKey] = [APIKey copy];
+}
+
+- (NSString *)clientID {
+ return self.optionsDictionary[kFIRClientID];
+}
+
+- (void)setClientID:(NSString *)clientID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRClientID] = [clientID copy];
+}
+
+- (NSString *)trackingID {
+ return self.optionsDictionary[kFIRTrackingID];
+}
+
+- (void)setTrackingID:(NSString *)trackingID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRTrackingID] = [trackingID copy];
+}
+
+- (NSString *)GCMSenderID {
+ return self.optionsDictionary[kFIRGCMSenderID];
+}
+
+- (void)setGCMSenderID:(NSString *)GCMSenderID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy];
+}
+
+- (NSString *)projectID {
+ return self.optionsDictionary[kFIRProjectID];
+}
+
+- (void)setProjectID:(NSString *)projectID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRProjectID] = [projectID copy];
+}
+
+- (NSString *)androidClientID {
+ return self.optionsDictionary[kFIRAndroidClientID];
+}
+
+- (void)setAndroidClientID:(NSString *)androidClientID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRAndroidClientID] = [androidClientID copy];
+}
+
+- (NSString *)googleAppID {
+ return self.optionsDictionary[kFIRGoogleAppID];
+}
+
+- (void)setGoogleAppID:(NSString *)googleAppID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRGoogleAppID] = [googleAppID copy];
+}
+
+- (NSString *)libraryVersionID {
+ return kFIRLibraryVersionID;
+}
+
+- (void)setLibraryVersionID:(NSString *)libraryVersionID {
+ _optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy];
+}
+
+- (NSString *)databaseURL {
+ return self.optionsDictionary[kFIRDatabaseURL];
+}
+
+- (void)setDatabaseURL:(NSString *)databaseURL {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRDatabaseURL] = [databaseURL copy];
+}
+
+- (NSString *)storageBucket {
+ return self.optionsDictionary[kFIRStorageBucket];
+}
+
+- (void)setStorageBucket:(NSString *)storageBucket {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
+}
+
+- (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _deepLinkURLScheme = [deepLinkURLScheme copy];
+}
+
+- (NSString *)bundleID {
+ return self.optionsDictionary[kFIRBundleID];
+}
+
+- (void)setBundleID:(NSString *)bundleID {
+ if (self.isEditingLocked) {
+ [NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
+ }
+
+ _optionsDictionary[kFIRBundleID] = [bundleID copy];
+}
+
+#pragma mark - Internal instance methods
+
+- (NSDictionary *)analyticsOptionsDictionary {
+ dispatch_once(&_createAnalyticsOptionsDictionaryOnce, ^{
+ NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
+ NSDictionary *mainInfoDictionary = [NSBundle mainBundle].infoDictionary;
+ NSArray *measurementKeys = @[ kFIRIsMeasurementEnabled,
+ kFIRIsAnalyticsCollectionEnabled,
+ kFIRIsAnalyticsCollectionDeactivated ];
+ for (NSString *key in measurementKeys) {
+ id value = mainInfoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
+ if (!value) {
+ continue;
+ }
+ tempAnalyticsOptions[key] = value;
+ }
+ _analyticsOptionsDictionary = tempAnalyticsOptions;
+ });
+ return _analyticsOptionsDictionary;
+}
+
+/**
+ * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
+ * GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still
+ * be supported.
+ */
+- (BOOL)isMeasurementEnabled {
+ if (self.isAnalyticsCollectionDeactivated) {
+ return NO;
+ }
+ if (!self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled]) {
+ return YES; // Enable Measurement by default when the key is not in the dictionary.
+ }
+ return [self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled] boolValue];
+}
+
+- (BOOL)isAnalyticsCollectionEnabled {
+ if (self.isAnalyticsCollectionDeactivated) {
+ return NO;
+ }
+ if (!self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled]) {
+ return self.isMeasurementEnabled; // Fall back to older plist flag.
+ }
+ return [self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled] boolValue];
+}
+
+- (BOOL)isAnalyticsCollectionDeactivated {
+ if (!self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated]) {
+ return NO; // Analytics Collection is not deactivated when the key is not in the dictionary.
+ }
+ return [self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated] boolValue];
+}
+
+- (BOOL)isAnalyticsEnabled {
+ return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue];
+}
+
+- (BOOL)isSignInEnabled {
+ return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue];
+}
+
+@end
diff --git a/Firebase/Core/FIRReachabilityChecker.m b/Firebase/Core/FIRReachabilityChecker.m
new file mode 100644
index 0000000..66b6547
--- /dev/null
+++ b/Firebase/Core/FIRReachabilityChecker.m
@@ -0,0 +1,245 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRReachabilityChecker.h"
+#import "Private/FIRReachabilityChecker+Internal.h"
+
+#import "Private/FIRNetwork.h"
+#import "Private/FIRNetworkMessageCode.h"
+#import "Private/FIRLogger.h"
+
+static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
+ SCNetworkReachabilityFlags flags, void *info);
+
+static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = {
+ SCNetworkReachabilityCreateWithName,
+ SCNetworkReachabilitySetCallback,
+ SCNetworkReachabilityScheduleWithRunLoop,
+ SCNetworkReachabilityUnscheduleFromRunLoop,
+ CFRelease,
+};
+
+static NSString *const kFIRReachabilityUnknownStatus = @"Unknown";
+static NSString *const kFIRReachabilityConnectedStatus = @"Connected";
+static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
+
+@interface FIRReachabilityChecker ()
+
+@property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi;
+@property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus;
+@property(nonatomic, copy) NSString *host;
+@property(nonatomic, assign) SCNetworkReachabilityRef reachability;
+
+@end
+
+@implementation FIRReachabilityChecker
+
+@synthesize reachabilityApi = reachabilityApi_;
+@synthesize reachability = reachability_;
+
+- (const struct FIRReachabilityApi *)reachabilityApi {
+ return reachabilityApi_;
+}
+
+- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi {
+ if (reachability_) {
+ NSString *message = @"Cannot change reachability API while reachability is running. "
+ @"Call stop first.";
+ [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeReachabilityChecker000
+ message:message];
+ return;
+ }
+ reachabilityApi_ = reachabilityApi;
+}
+
+@synthesize reachabilityStatus = reachabilityStatus_;
+@synthesize host = host_;
+@synthesize reachabilityDelegate = reachabilityDelegate_;
+@synthesize loggerDelegate = loggerDelegate_;
+
+- (BOOL)isActive {
+ return reachability_ != nil;
+}
+
+- (void)setReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate {
+ if (reachabilityDelegate &&
+ (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) {
+ FIRLogError(
+ kFIRLoggerCore,
+ [NSString stringWithFormat:@"I-NET%06ld",
+ (long)kFIRNetworkMessageCodeReachabilityChecker005],
+ @"Reachability delegate doesn't conform to Reachability protocol.");
+ return;
+ }
+ reachabilityDelegate_ = reachabilityDelegate;
+}
+
+- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
+ if (loggerDelegate &&
+ (![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) {
+ FIRLogError(
+ kFIRLoggerCore,
+ [NSString stringWithFormat:@"I-NET%06ld",
+ (long)kFIRNetworkMessageCodeReachabilityChecker006],
+ @"Reachability delegate doesn't conform to Logger protocol.");
+ return;
+ }
+ loggerDelegate_ = loggerDelegate;
+}
+
+- (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
+ loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
+ withHost:(NSString *)host {
+ self = [super init];
+
+ [self setLoggerDelegate:loggerDelegate];
+
+ if (!host || !host.length) {
+ [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeReachabilityChecker001
+ message:@"Invalid host specified"];
+ return nil;
+ }
+ if (self) {
+ [self setReachabilityDelegate:reachabilityDelegate];
+ reachabilityApi_ = &kFIRDefaultReachabilityApi;
+ reachabilityStatus_ = kFIRReachabilityUnknown;
+ host_ = [host copy];
+ reachability_ = nil;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ reachabilityDelegate_ = nil;
+ loggerDelegate_ = nil;
+ [self stop];
+}
+
+- (BOOL)start {
+ if (!reachability_) {
+ reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
+ if (!reachability_) {
+ return NO;
+ }
+ SCNetworkReachabilityContext context = {
+ 0, /* version */
+ (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
+ NULL, /* retain */
+ NULL, /* release */
+ NULL /* copyDescription */
+ };
+ if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
+ !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
+ kCFRunLoopCommonModes)) {
+ reachabilityApi_->releaseFn(reachability_);
+ reachability_ = nil;
+ [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
+ messageCode:kFIRNetworkMessageCodeReachabilityChecker002
+ message:@"Failed to start reachability handle"];
+ return NO;
+ }
+ }
+ [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeReachabilityChecker003
+ message:@"Monitoring the network status"];
+ return YES;
+}
+
+- (void)stop {
+ if (reachability_) {
+ reachabilityStatus_ = kFIRReachabilityUnknown;
+ reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
+ kCFRunLoopCommonModes);
+ reachabilityApi_->releaseFn(reachability_);
+ reachability_ = nil;
+ }
+}
+
+- (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
+ FIRReachabilityStatus status = kFIRReachabilityNotReachable;
+ // If the Reachable flag is not set, we definitely don't have connectivity.
+ if (flags & kSCNetworkReachabilityFlagsReachable) {
+ // Reachable flag is set. Check further flags.
+ if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
+ // Connection required flag is not set, so we have connectivity.
+ status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
+ : kFIRReachabilityViaWifi;
+ } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
+ kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
+ !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
+ // If the connection on demand or connection on traffic flag is set, and user intervention
+ // is not required, we have connectivity.
+ status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
+ : kFIRReachabilityViaWifi;
+ }
+ }
+ return status;
+}
+
+- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
+ FIRReachabilityStatus status = [self statusForFlags:flags];
+ if (reachabilityStatus_ != status) {
+ NSString *reachabilityStatusString;
+ if (status == kFIRReachabilityUnknown) {
+ reachabilityStatusString = kFIRReachabilityUnknownStatus;
+ } else {
+ reachabilityStatusString = (status == kFIRReachabilityNotReachable)
+ ? kFIRReachabilityDisconnectedStatus
+ : kFIRReachabilityConnectedStatus;
+ }
+ [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
+ messageCode:kFIRNetworkMessageCodeReachabilityChecker004
+ message:@"Network status has changed. Code, status"
+ contexts:@[ @(status), reachabilityStatusString ]];
+ reachabilityStatus_ = status;
+ [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
+ }
+}
+
+@end
+
+static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
+ SCNetworkReachabilityFlags flags, void *info) {
+ FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info;
+ [checker reachabilityFlagsChanged:flags];
+}
+
+// This function used to be at the top of the file, but it was moved here
+// as a workaround for a suspected compiler bug. When compiled in Release mode
+// and run on an iOS device with WiFi disabled, the reachability code crashed
+// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
+// After unsuccessfully trying to diagnose the cause of the crash, it was
+// discovered that moving this function to the end of the file magically fixed
+// the crash. If you are going to edit this file, exercise caution and make sure
+// to test thoroughly with an iOS device under various network conditions.
+const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) {
+ switch (status) {
+ case kFIRReachabilityUnknown:
+ return @"Reachability Unknown";
+
+ case kFIRReachabilityNotReachable:
+ return @"Not reachable";
+
+ case kFIRReachabilityViaWifi:
+ return @"Reachable via Wifi";
+
+ case kFIRReachabilityViaCellular:
+ return @"Reachable via Cellular Data";
+
+ default:
+ return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
+ }
+}
diff --git a/Firebase/Core/FIRURLSchemeUtil.m b/Firebase/Core/FIRURLSchemeUtil.m
new file mode 100644
index 0000000..8dbecae
--- /dev/null
+++ b/Firebase/Core/FIRURLSchemeUtil.m
@@ -0,0 +1,43 @@
+// Copyright 2017 Google
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#import "Private/FIRURLSchemeUtil.h"
+#import "Private/FIRLogger.h"
+
+/**
+ * Regular expression to match the URL scheme for Google sign-in.
+ */
+static NSString *const kFIRGoogleSignInURLSchemePattern =
+@"^com\\.googleusercontent\\.apps\\.\\d+-\\w+$";
+
+BOOL fir_areURLSchemesValidForGoogleSignIn(NSArray *urlSchemes) {
+ BOOL hasReversedClientID = NO;
+ for (NSString *urlScheme in urlSchemes) {
+ if (!hasReversedClientID) {
+ NSRange range = [urlScheme rangeOfString:kFIRGoogleSignInURLSchemePattern
+ options:NSRegularExpressionSearch];
+ if (range.location != NSNotFound) {
+ hasReversedClientID = YES;
+ }
+ }
+ }
+ if (hasReversedClientID) {
+ return YES;
+ }
+ if (!hasReversedClientID) {
+ FIRLogInfo(kFIRLoggerCore, @"I-COR000021", @"A reversed client ID should be added as a URL "
+ @"scheme to enable Google sign-in.");
+ }
+ return NO;
+}
diff --git a/Firebase/Core/FirebaseCore.h b/Firebase/Core/FirebaseCore.h
new file mode 100644
index 0000000..fa26f69
--- /dev/null
+++ b/Firebase/Core/FirebaseCore.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRAnalyticsConfiguration.h"
+#import "FIRApp.h"
+#import "FIRConfiguration.h"
+#import "FIRLoggerLevel.h"
+#import "FIROptions.h"
diff --git a/Firebase/Core/FirebaseCore.podspec b/Firebase/Core/FirebaseCore.podspec
new file mode 100644
index 0000000..f513367
--- /dev/null
+++ b/Firebase/Core/FirebaseCore.podspec
@@ -0,0 +1,35 @@
+# This podspec is not intended to be deployed. It is solely for the static
+# library framework build process at
+# https://github.com/firebase/firebase-ios-sdk/tree/master/BuildFrameworks
+
+Pod::Spec.new do |s|
+ s.name = 'FirebaseCore'
+ s.version = '4.0.0'
+ s.summary = 'Firebase Open Source Libraries for iOS.'
+
+ s.description = <<-DESC
+Simplify your iOS development, grow your user base, and monetize more effectively with Firebase.
+ DESC
+
+ s.homepage = 'https://firebase.google.com'
+ s.license = { :type => 'Apache', :file => '../../LICENSE' }
+ s.authors = 'Google, Inc.'
+
+ # NOTE that the FirebaseDev pod is neither publicly deployed nor yet interchangeable with the
+ # Firebase pod
+ s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => s.version.to_s }
+ s.social_media_url = 'https://twitter.com/Firebase'
+ s.ios.deployment_target = '7.0'
+
+ s.source_files = '**/*.[mh]'
+ s.public_header_files =
+ 'FirebaseCore.h',
+ 'FIRAnalyticsConfiguration.h',
+ 'FIRApp.h',
+ 'FIRConfiguration.h',
+ 'FIRLoggerLevel.h',
+ 'FIROptions.h',
+ 'FIRCoreSwiftNameSupport.h'
+
+ s.dependency 'GoogleToolboxForMac/NSData+zlib', '~> 2.1'
+end
diff --git a/Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h b/Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h
new file mode 100644
index 0000000..6c57a0f
--- /dev/null
+++ b/Firebase/Core/Private/FIRAnalyticsConfiguration+Internal.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "../FIRAnalyticsConfiguration.h"
+
+/// Values stored in analyticsEnabledState. Never alter these constants since they must match with
+/// values persisted to disk.
+typedef NS_ENUM(int64_t, FIRAnalyticsEnabledState) {
+ // 0 is the default value for keys not found stored in persisted config, so it cannot represent
+ // kFIRAnalyticsEnabledStateSetNo. It must represent kFIRAnalyticsEnabledStateNotSet.
+ kFIRAnalyticsEnabledStateNotSet = 0,
+ kFIRAnalyticsEnabledStateSetYes = 1,
+ kFIRAnalyticsEnabledStateSetNo = 2,
+};
+
+/// The user defaults key for the persisted measurementEnabledState value. FIRAPersistedConfig reads
+/// measurementEnabledState using this same key.
+static NSString *const kFIRAPersistedConfigMeasurementEnabledStateKey =
+ @"/google/measurement/measurement_enabled_state";
+
+static NSString *const kFIRAnalyticsConfigurationSetEnabledNotification =
+ @"FIRAnalyticsConfigurationSetEnabledNotification";
+static NSString *const kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification =
+ @"FIRAnalyticsConfigurationSetMinimumSessionIntervalNotification";
+static NSString *const kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification =
+ @"FIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification";
diff --git a/Firebase/Core/Private/FIRAppAssociationRegistration.h b/Firebase/Core/Private/FIRAppAssociationRegistration.h
new file mode 100644
index 0000000..3d697a7
--- /dev/null
+++ b/Firebase/Core/Private/FIRAppAssociationRegistration.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @class FIRAppAssociationRegistration
+ @brief Manages object associations as a singleton-dependent: At most one object is
+ registered for any given host/key pair, and the object shall be created on-the-fly when
+ asked for.
+ */
+@interface FIRAppAssociationRegistration<ObjectType> : NSObject
+
+/** @fn registeredObjectWithHost:key:creationBlock:
+ @brief Retrieves the registered object with a particular host and key.
+ @param host The host object.
+ @param key The key to specify the registered object on the host.
+ @param creationBlock The block to return the object to be registered if not already.
+ The block is executed immediately before this method returns if it is executed at all.
+ It can also be executed multiple times across different method invocations if previous
+ execution of the block returns @c nil.
+ @return The registered object for the host/key pair, or @c nil if no object is registered
+ and @c creationBlock returns @c nil.
+ @remarks The method is thread-safe but non-reentrant in the sense that attempting to call this
+ method again within the @c creationBlock with the same host/key pair raises an exception.
+ The registered object is retained by the host.
+ */
++ (nullable ObjectType)registeredObjectWithHost:(id)host
+ key:(NSString *)key
+ creationBlock:(ObjectType _Nullable (^)())creationBlock;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRAppEnvironmentUtil.h b/Firebase/Core/Private/FIRAppEnvironmentUtil.h
new file mode 100644
index 0000000..ba4696c
--- /dev/null
+++ b/Firebase/Core/Private/FIRAppEnvironmentUtil.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import <UIKit/UIKit.h>
+
+@interface FIRAppEnvironmentUtil : NSObject
+
+/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator,
+/// development environment or sideloaded.
++ (BOOL)isFromAppStore;
+
+/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt.
+/// Returns NO otherwise.
++ (BOOL)isAppStoreReceiptSandbox;
+
+/// Indicates whether the app is on simulator or not at runtime depending on the device
+/// architecture.
++ (BOOL)isSimulator;
+
+/// The current device model. Returns an empty string if device model cannot be retrieved.
++ (NSString *)deviceModel;
+
+/// The current operating system version. Returns an empty string if the system version cannot be
+/// retrieved.
++ (NSString *)systemVersion;
+
+/// Indicates whether it is running inside an extension or an app.
++ (BOOL)isAppExtension;
+
+/// Returns the [UIApplication sharedApplication] if it is running on an app, not an extension.
++ (UIApplication *)sharedApplication;
+
+@end
diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h
new file mode 100644
index 0000000..11b3bf6
--- /dev/null
+++ b/Firebase/Core/Private/FIRAppInternal.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRApp.h"
+#import "FIRErrors.h"
+
+/**
+ * The internal interface to FIRApp. This is meant for first-party integrators, who need to receive
+ * FIRApp notifications, log info about the success or failure of their configuration, and access
+ * other internal functionality of FIRApp.
+ *
+ * TODO(b/28296561): Restructure this header.
+ */
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSInteger, FIRConfigType) {
+ FIRConfigTypeCore = 1,
+ FIRConfigTypeSDK = 2,
+};
+
+/**
+ * Names of services provided by Firebase.
+ */
+extern NSString *const kFIRServiceAdMob;
+extern NSString *const kFIRServiceAuth;
+extern NSString *const kFIRServiceCrash;
+extern NSString *const kFIRServiceDatabase;
+extern NSString *const kFIRServiceDynamicLinks;
+extern NSString *const kFIRServiceInstanceID;
+extern NSString *const kFIRServiceInvites;
+extern NSString *const kFIRServiceMessaging;
+extern NSString *const kFIRServiceMeasurement;
+extern NSString *const kFIRServiceRemoteConfig;
+extern NSString *const kFIRServiceStorage;
+
+/**
+ * Names of services provided by the Google pod, but logged by the Firebase pod.
+ */
+extern NSString *const kGGLServiceAnalytics;
+extern NSString *const kGGLServiceSignIn;
+
+extern NSString *const kFIRDefaultAppName;
+extern NSString *const kFIRAppReadyToConfigureSDKNotification;
+extern NSString *const kFIRAppDeleteNotification;
+extern NSString *const kFIRAppIsDefaultAppKey;
+extern NSString *const kFIRAppNameKey;
+extern NSString *const kFIRGoogleAppIDKey;
+
+/** @typedef FIRTokenCallback
+ @brief The type of block which gets called when a token is ready.
+ */
+typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error);
+
+/** @typedef FIRAppGetTokenImplementation
+ @brief The type of block which can provide an implementation for the @c getTokenWithCallback:
+ method.
+ @param forceRefresh Forces the token to be refreshed.
+ @param callback The block which should be invoked when the async call completes.
+ */
+typedef void (^FIRAppGetTokenImplementation)(BOOL forceRefresh, FIRTokenCallback callback);
+
+/** @typedef FIRAppGetUID
+ @brief The type of block which can provide an implementation for the @c getUID method.
+ */
+typedef NSString *_Nullable (^FIRAppGetUIDImplementation)();
+
+@interface FIRApp ()
+
+/** @property getTokenImplementation
+ @brief Gets or sets the block to use for the implementation of
+ @c getTokenForcingRefresh:withCallback:
+ */
+@property(nonatomic, copy) FIRAppGetTokenImplementation getTokenImplementation;
+
+/** @property getUIDImplementation
+ @brief Gets or sets the block to use for the implementation of @c getUID.
+ */
+@property(nonatomic, copy) FIRAppGetUIDImplementation getUIDImplementation;
+
+/**
+ * Creates an error for failing to configure a subspec service. This method is called by each
+ * FIRApp notification listener.
+ */
++ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
+ errorCode:(FIRErrorCode)code
+ service:(NSString *)service
+ reason:(NSString *)reason;
+
+/**
+ * Used by each SDK to send logs about SDK configuration status to Clearcut.
+ */
+- (void)sendLogsWithServiceName:(NSString *)serviceName
+ version:(NSString *)version
+ error:(NSError *)error;
+
+/**
+ * Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe.
+ */
++ (void)resetApps;
+
+/**
+ * Can be used by the unit tests in each SDK to set customized options.
+ */
+- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
+
+/** @fn getTokenForcingRefresh:withCallback:
+ @brief Retrieves the Firebase authentication token, possibly refreshing it.
+ @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
+ other than an expiration.
+ @param callback The block to invoke when the token is available.
+ */
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback;
+
+/**
+ * Exposed for use by the Google pod. Configures the default app without sending notifications to
+ * other SDKs. Otherwise, behaves exactly like +configure.
+ */
++ (void)configureWithoutSendingNotification;
+
+/**
+ * Expose the UID of the current user for Firestore.
+ */
+- (nullable NSString *)getUID;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRBundleUtil.h b/Firebase/Core/Private/FIRBundleUtil.h
new file mode 100644
index 0000000..4bfef8d
--- /dev/null
+++ b/Firebase/Core/Private/FIRBundleUtil.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * This class provides utilities for accessing resources in bundles.
+ */
+@interface FIRBundleUtil : NSObject
+
+/**
+ * Finds all relevant bundles, starting with [NSBundle mainBundle].
+ */
++ (NSArray *)relevantBundles;
+
+/**
+ * Reads the options dictionary from one of the provided bundles.
+ *
+ * @param resourceName The resource name, e.g. @"GoogleService-Info".
+ * @param fileType The file type (extension), e.g. @"plist".
+ * @param bundles The bundles to expect, in priority order. See also
+ * +[FIRBundleUtil relevantBundles].
+ */
++ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName
+ andFileType:(NSString *)fileType
+ inBundles:(NSArray *)bundles;
+
+/**
+ * Finds URL schemes defined in all relevant bundles, starting with those from
+ * [NSBundle mainBundle].
+ */
++ (NSArray *)relevantURLSchemes;
+
+/**
+ * Finds bundle identifiers in all relevant bundles, starting with those from [NSBundle mainBundle].
+ */
++ (NSSet *)relevantBundleIdentifiers;
+
+/**
+ * Checks if the bundle identifier exists in the given bundles.
+ */
++ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles;
+
+@end
diff --git a/Firebase/Core/Private/FIRErrorCode.h b/Firebase/Core/Private/FIRErrorCode.h
new file mode 100644
index 0000000..01d3c56
--- /dev/null
+++ b/Firebase/Core/Private/FIRErrorCode.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Error codes in Firebase error domain. */
+typedef NS_ENUM(NSInteger, FIRErrorCode) {
+ /**
+ * Unknown error.
+ */
+ FIRErrorCodeUnknown = 0,
+ /**
+ * Loading data from the GoogleService-Info.plist file failed. This is a fatal error and should
+ * not be ignored. Further calls to the API will fail and/or possibly cause crashes.
+ */
+ FIRErrorCodeInvalidPlistFile = -100,
+
+ /**
+ * Validating the Google App ID format failed.
+ */
+ FIRErrorCodeInvalidAppID = -101,
+
+ /**
+ * Error code for failing to configure a specific service.
+ */
+ FIRErrorCodeAdMobFailed = -110,
+ FIRErrorCodeAppInviteFailed = -112,
+ FIRErrorCodeCloudMessagingFailed = -113,
+ FIRErrorCodeConfigFailed = -114,
+ FIRErrorCodeDatabaseFailed = -115,
+ FIRErrorCodeCrashReportingFailed = -118,
+ FIRErrorCodeDurableDeepLinkFailed = -119,
+ FIRErrorCodeAuthFailed = -120,
+ FIRErrorCodeInstanceIDFailed = -121,
+ FIRErrorCodeStorageFailed = -123,
+
+ /**
+ * Error codes returned by Dynamic Links
+ */
+ FIRErrorCodeDynamicLinksStrongMatchNotAvailable = -124,
+ FIRErrorCodeDynamicLinksManualRetrievalNotEnabled = -125,
+ FIRErrorCodeDynamicLinksPendingLinkOnlyAvailableAtFirstLaunch = -126,
+ FIRErrorCodeDynamicLinksPendingLinkRetrievalAlreadyRunning = -127,
+};
diff --git a/Firebase/Core/Private/FIRErrors.h b/Firebase/Core/Private/FIRErrors.h
new file mode 100644
index 0000000..9a03575
--- /dev/null
+++ b/Firebase/Core/Private/FIRErrors.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#include "FIRErrorCode.h"
+
+extern NSString *const kFirebaseErrorDomain;
+extern NSString *const kFirebaseAdMobErrorDomain;
+extern NSString *const kFirebaseAppInviteErrorDomain;
+extern NSString *const kFirebaseAuthErrorDomain;
+extern NSString *const kFirebaseCloudMessagingErrorDomain;
+extern NSString *const kFirebaseConfigErrorDomain;
+extern NSString *const kFirebaseCoreErrorDomain;
+extern NSString *const kFirebaseCrashReportingErrorDomain;
+extern NSString *const kFirebaseDatabaseErrorDomain;
+extern NSString *const kFirebaseDurableDeepLinkErrorDomain;
+extern NSString *const kFirebaseInstanceIDErrorDomain;
+extern NSString *const kFirebasePerfErrorDomain;
+extern NSString *const kFirebaseStorageErrorDomain;
+
+/**
+ * Factory for a NSError in the Firebase error domain.
+ *
+ * @param domain Domain of Firebase error.
+ * @param code Error code that NSError should have.
+ * @param userInfo User info that NSError should have.
+ * @return An NSError in the Firebase domain.
+ */
+extern NSError *FIRCreateError(NSString *domain, FIRErrorCode code, NSDictionary *userInfo);
diff --git a/Firebase/Core/Private/FIRLogger.h b/Firebase/Core/Private/FIRLogger.h
new file mode 100644
index 0000000..2206c0a
--- /dev/null
+++ b/Firebase/Core/Private/FIRLogger.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "FIRLoggerLevel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * The Firebase services used in Firebase logger.
+ */
+typedef NSString *const FIRLoggerService;
+
+extern FIRLoggerService kFIRLoggerABTesting;
+extern FIRLoggerService kFIRLoggerAdMob;
+extern FIRLoggerService kFIRLoggerAnalytics;
+extern FIRLoggerService kFIRLoggerAuth;
+extern FIRLoggerService kFIRLoggerCore;
+extern FIRLoggerService kFIRLoggerCrash;
+extern FIRLoggerService kFIRLoggerDatabase;
+extern FIRLoggerService kFIRLoggerDynamicLinks;
+extern FIRLoggerService kFIRLoggerInstanceID;
+extern FIRLoggerService kFIRLoggerInvites;
+extern FIRLoggerService kFIRLoggerMessaging;
+extern FIRLoggerService kFIRLoggerPerf;
+extern FIRLoggerService kFIRLoggerRemoteConfig;
+extern FIRLoggerService kFIRLoggerStorage;
+
+/**
+ * Enables or disables Analytics debug mode.
+ * If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug.
+ * Enabling the debug mode has no effect if the app is running from App Store.
+ * (required) analytics debug mode flag.
+ */
+void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode);
+
+/**
+ * Changes the default logging level of FIRLoggerLevelNotice to a user-specified level.
+ * The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store.
+ * (required) log level (one of the FIRLoggerLevel enum values).
+ */
+void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
+
+/**
+ * Checks if the specified logger level is loggable given the current settings.
+ * (required) log level (one of the FIRLoggerLevel enum values).
+ * (required) whether or not this function is called from the Analytics component.
+ */
+BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent);
+
+/**
+ * Logs a message to the Xcode console and the device log. If running from AppStore, will
+ * not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming.
+ * (required) log level (one of the FIRLoggerLevel enum values).
+ * (required) service name of type FIRLoggerService.
+ * (required) message code starting with "I-" which means iOS, followed by a capitalized
+ * three-character service identifier and a six digit integer message ID that is unique
+ * within the service.
+ * An example of the message code is @"I-COR000001".
+ * (required) message string which can be a format string.
+ * (optional) variable arguments list obtained from calling va_start, used when message is a format
+ * string.
+ */
+extern void FIRLogBasic(FIRLoggerLevel level,
+ FIRLoggerService service,
+ NSString *messageCode,
+ NSString *message,
+// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
+// See: http://stackoverflow.com/q/29095469
+#if __LP64__ && TARGET_IPHONE_SIMULATOR
+ va_list args_ptr
+#else
+ va_list _Nullable args_ptr
+#endif
+ );
+
+/**
+ * The following functions accept the following parameters in order:
+ * (required) service name of type FIRLoggerService.
+ * (required) message code starting from "I-" which means iOS, followed by a capitalized
+ * three-character service identifier and a six digit integer message ID that is unique
+ * within the service.
+ * An example of the message code is @"I-COR000001".
+ * See go/firebase-log-proposal for details.
+ * (required) message string which can be a format string.
+ * (optional) the list of arguments to substitute into the format string.
+ * Example usage:
+ * FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
+ */
+extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
+ NS_FORMAT_FUNCTION(3, 4);
+extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
+ NS_FORMAT_FUNCTION(3, 4);
+extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
+ NS_FORMAT_FUNCTION(3, 4);
+extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
+ NS_FORMAT_FUNCTION(3, 4);
+extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
+ NS_FORMAT_FUNCTION(3, 4);
+
+NS_ASSUME_NONNULL_END
diff --git a/Firebase/Core/Private/FIRMutableDictionary.h b/Firebase/Core/Private/FIRMutableDictionary.h
new file mode 100644
index 0000000..ebe2d33
--- /dev/null
+++ b/Firebase/Core/Private/FIRMutableDictionary.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+
+/// A mutable dictionary that provides atomic accessor and mutators.
+@interface FIRMutableDictionary : NSObject
+
+/// Returns an object given a key in the dictionary or nil if not found.
+- (id)objectForKey:(id)key;
+
+/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary.
+- (void)setObject:(id)object forKey:(id<NSCopying>)key;
+
+/// Removes the object given its session ID from the dictionary.
+- (void)removeObjectForKey:(id)key;
+
+/// Removes all objects.
+- (void)removeAllObjects;
+
+/// Returns the number of current objects in the dictionary.
+- (NSUInteger)count;
+
+/// Returns an object given a key in the dictionary or nil if not found.
+- (id)objectForKeyedSubscript:(id<NSCopying>)key;
+
+/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary.
+- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key;
+
+/// Returns the immutable dictionary.
+- (NSDictionary *)dictionary;
+
+@end
diff --git a/Firebase/Core/Private/FIRNetwork.h b/Firebase/Core/Private/FIRNetwork.h
new file mode 100644
index 0000000..aac0bca
--- /dev/null
+++ b/Firebase/Core/Private/FIRNetwork.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+
+#import "FIRNetworkConstants.h"
+#import "FIRNetworkLoggerProtocol.h"
+#import "FIRNetworkURLSession.h"
+
+/// Delegate protocol for FIRNetwork events.
+@protocol FIRNetworkReachabilityDelegate
+
+/// Tells the delegate to handle events when the network reachability changes to connected or not
+/// connected.
+- (void)reachabilityDidChange;
+
+@end
+
+/// The Network component that provides network status and handles network requests and responses.
+/// This is not thread safe.
+///
+/// NOTE:
+/// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the
+/// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler:
+@interface FIRNetwork : NSObject
+
+/// Indicates if network connectivity is available.
+@property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected;
+
+/// Indicates if there are any uploads in progress.
+@property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress;
+
+/// An optional delegate that can be used in the event when network reachability changes.
+@property(nonatomic, weak) id<FIRNetworkReachabilityDelegate> reachabilityDelegate;
+
+/// An optional delegate that can be used to log messages, warnings or errors that occur in the
+/// network operations.
+@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
+
+/// Indicates whether the logger should display debug messages.
+@property(nonatomic, assign) BOOL isDebugModeEnabled;
+
+/// The time interval in seconds for the network request to timeout.
+@property(nonatomic, assign) NSTimeInterval timeoutInterval;
+
+/// Initializes with the default reachability host.
+- (instancetype)init;
+
+/// Initializes with a custom reachability host.
+- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost;
+
+/// Handles events when background session with the given ID has finished.
++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
+ completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler;
+
+/// Compresses and sends a POST request with the provided data to the URL. The session will be
+/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default
+/// session. Returns a session ID or nil if an error occurs.
+- (NSString *)postURL:(NSURL *)url
+ payload:(NSData *)payload
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(FIRNetworkCompletionHandler)handler;
+
+/// Sends a GET request with the provided data to the URL. The session will be background session
+/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a
+/// session ID or nil if an error occurs.
+- (NSString *)getURL:(NSURL *)url
+ headers:(NSDictionary *)headers
+ queue:(dispatch_queue_t)queue
+ usingBackgroundSession:(BOOL)usingBackgroundSession
+ completionHandler:(FIRNetworkCompletionHandler)handler;
+
+@end
diff --git a/Firebase/Core/Private/FIRNetworkConstants.h b/Firebase/Core/Private/FIRNetworkConstants.h
new file mode 100644
index 0000000..5878088
--- /dev/null
+++ b/Firebase/Core/Private/FIRNetworkConstants.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+
+/// Error codes in Firebase Network error domain.
+/// Note: these error codes should never change. It would make it harder to decode the errors if
+/// we inadvertently altered any of these codes in a future SDK version.
+typedef NS_ENUM(NSInteger, FIRNetworkErrorCode) {
+ /// Unknown error.
+ FIRNetworkErrorCodeUnknown = 0,
+ /// Error occurs when the request URL is invalid.
+ FIRErrorCodeNetworkInvalidURL = 1,
+ /// Error occurs when request cannot be constructed.
+ FIRErrorCodeNetworkRequestCreation = 2,
+ /// Error occurs when payload cannot be compressed.
+ FIRErrorCodeNetworkPayloadCompression = 3,
+ /// Error occurs when session task cannot be created.
+ FIRErrorCodeNetworkSessionTaskCreation = 4,
+ /// Error occurs when there is no response.
+ FIRErrorCodeNetworkInvalidResponse = 5
+};
+
+#pragma mark - Network constants
+
+/// The prefix of the ID of the background session.
+extern NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix;
+
+/// The sub directory to store the files of data that is being uploaded in the background.
+extern NSString *const kFIRNetworkApplicationSupportSubdirectory;
+
+/// Name of the temporary directory that stores files for background uploading.
+extern NSString *const kFIRNetworkTempDirectoryName;
+
+/// The period when the temporary uploading file can stay.
+extern const NSTimeInterval kFIRNetworkTempFolderExpireTime;
+
+/// The default network request timeout interval.
+extern const NSTimeInterval kFIRNetworkTimeOutInterval;
+
+/// The host to check the reachability of the network.
+extern NSString *const kFIRNetworkReachabilityHost;
+
+/// The key to get the error context of the UserInfo.
+extern NSString *const kFIRNetworkErrorContext;
+
+#pragma mark - Network Status Code
+
+extern const int kFIRNetworkHTTPStatusOK;
+extern const int kFIRNetworkHTTPStatusNoContent;
+extern const int kFIRNetworkHTTPStatusCodeMultipleChoices;
+extern const int kFIRNetworkHTTPStatusCodeMovedPermanently;
+extern const int kFIRNetworkHTTPStatusCodeFound;
+extern const int kFIRNetworkHTTPStatusCodeNotModified;
+extern const int kFIRNetworkHTTPStatusCodeMovedTemporarily;
+extern const int kFIRNetworkHTTPStatusCodeNotFound;
+extern const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic;
+extern const int kFIRNetworkHTTPStatusCodeUnavailable;
+
+#pragma mark - Error Domain
+
+extern NSString *const kFIRNetworkErrorDomain;
diff --git a/Firebase/Core/Private/FIRNetworkLoggerProtocol.h b/Firebase/Core/Private/FIRNetworkLoggerProtocol.h
new file mode 100644
index 0000000..7b7d094
--- /dev/null
+++ b/Firebase/Core/Private/FIRNetworkLoggerProtocol.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+
+#import "FIRNetworkMessageCode.h"
+#import "../FIRLoggerLevel.h"
+
+/// The log levels used by FIRNetworkLogger.
+typedef NS_ENUM(NSInteger, FIRNetworkLogLevel) {
+ kFIRNetworkLogLevelError = FIRLoggerLevelError,
+ kFIRNetworkLogLevelWarning = FIRLoggerLevelWarning,
+ kFIRNetworkLogLevelInfo = FIRLoggerLevelInfo,
+ kFIRNetworkLogLevelDebug = FIRLoggerLevelDebug,
+};
+
+@protocol FIRNetworkLoggerDelegate<NSObject>
+
+@required
+/// Tells the delegate to log a message with an array of contexts and the log level.
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message
+ contexts:(NSArray *)contexts;
+
+/// Tells the delegate to log a message with a context and the log level.
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message
+ context:(id)context;
+
+/// Tells the delegate to log a message with the log level.
+- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
+ messageCode:(FIRNetworkMessageCode)messageCode
+ message:(NSString *)message;
+
+@end
diff --git a/Firebase/Core/Private/FIRNetworkMessageCode.h b/Firebase/Core/Private/FIRNetworkMessageCode.h
new file mode 100644
index 0000000..30f562f
--- /dev/null
+++ b/Firebase/Core/Private/FIRNetworkMessageCode.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum.
+typedef NS_ENUM(NSInteger, FIRNetworkMessageCode) {
+ // FIRNetwork.m
+ kFIRNetworkMessageCodeNetwork000 = 900000, // I-NET900000
+ kFIRNetworkMessageCodeNetwork001 = 900001, // I-NET900001
+ kFIRNetworkMessageCodeNetwork002 = 900002, // I-NET900002
+ kFIRNetworkMessageCodeNetwork003 = 900003, // I-NET900003
+ // FIRNetworkURLSession.m
+ kFIRNetworkMessageCodeURLSession000 = 901000, // I-NET901000
+ kFIRNetworkMessageCodeURLSession001 = 901001, // I-NET901001
+ kFIRNetworkMessageCodeURLSession002 = 901002, // I-NET901002
+ kFIRNetworkMessageCodeURLSession003 = 901003, // I-NET901003
+ kFIRNetworkMessageCodeURLSession004 = 901004, // I-NET901004
+ kFIRNetworkMessageCodeURLSession005 = 901005, // I-NET901005
+ kFIRNetworkMessageCodeURLSession006 = 901006, // I-NET901006
+ kFIRNetworkMessageCodeURLSession007 = 901007, // I-NET901007
+ kFIRNetworkMessageCodeURLSession008 = 901008, // I-NET901008
+ kFIRNetworkMessageCodeURLSession009 = 901009, // I-NET901009
+ kFIRNetworkMessageCodeURLSession010 = 901010, // I-NET901010
+ kFIRNetworkMessageCodeURLSession011 = 901011, // I-NET901011
+ kFIRNetworkMessageCodeURLSession012 = 901012, // I-NET901012
+ kFIRNetworkMessageCodeURLSession013 = 901013, // I-NET901013
+ kFIRNetworkMessageCodeURLSession014 = 901014, // I-NET901014
+ kFIRNetworkMessageCodeURLSession015 = 901015, // I-NET901015
+ kFIRNetworkMessageCodeURLSession016 = 901016, // I-NET901016
+ kFIRNetworkMessageCodeURLSession017 = 901017, // I-NET901017
+ kFIRNetworkMessageCodeURLSession018 = 901018, // I-NET901018
+ // FIRReachabilityChecker.m
+ kFIRNetworkMessageCodeReachabilityChecker000 = 902000, // I-NET902000
+ kFIRNetworkMessageCodeReachabilityChecker001 = 902001, // I-NET902001
+ kFIRNetworkMessageCodeReachabilityChecker002 = 902002, // I-NET902002
+ kFIRNetworkMessageCodeReachabilityChecker003 = 902003, // I-NET902003
+ kFIRNetworkMessageCodeReachabilityChecker004 = 902004, // I-NET902004
+ kFIRNetworkMessageCodeReachabilityChecker005 = 902005, // I-NET902005
+ kFIRNetworkMessageCodeReachabilityChecker006 = 902006, // I-NET902006
+};
diff --git a/Firebase/Core/Private/FIRNetworkURLSession.h b/Firebase/Core/Private/FIRNetworkURLSession.h
new file mode 100644
index 0000000..d146de2
--- /dev/null
+++ b/Firebase/Core/Private/FIRNetworkURLSession.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+
+#import "FIRNetworkLoggerProtocol.h"
+
+typedef void (^FIRNetworkCompletionHandler)(NSHTTPURLResponse *response, NSData *data,
+ NSError *error);
+typedef void (^FIRNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *response, NSData *data,
+ NSString *sessionID, NSError *error);
+typedef void (^FIRNetworkSystemCompletionHandler)(void);
+
+/// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses.
+@interface FIRNetworkURLSession
+ : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>
+
+/// Indicates whether the background network is enabled. Default value is NO.
+@property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled;
+
+/// The logger delegate to log message, errors or warnings that occur during the network operations.
+@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
+
+/// Calls the system provided completion handler after the background session is finished.
++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
+ completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler;
+
+/// Initializes with logger delegate.
+- (instancetype)initWithNetworkLoggerDelegate:(id<FIRNetworkLoggerDelegate>)networkLoggerDelegate
+ NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/// Sends an asynchronous POST request and calls the provided completion handler when the request
+/// completes or when errors occur, and returns an ID of the session/connection.
+- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
+ completionHandler:(FIRNetworkURLSessionCompletionHandler)handler;
+
+/// Sends an asynchronous GET request and calls the provided completion handler when the request
+/// completes or when errors occur, and returns an ID of the session.
+- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
+ completionHandler:(FIRNetworkURLSessionCompletionHandler)handler;
+
+@end
diff --git a/Firebase/Core/Private/FIROptionsInternal.h b/Firebase/Core/Private/FIROptionsInternal.h
new file mode 100644
index 0000000..2b30248
--- /dev/null
+++ b/Firebase/Core/Private/FIROptionsInternal.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "../FIROptions.h"
+
+/**
+ * Keys for the strings in the plist file.
+ */
+extern NSString *const kFIRAPIKey;
+extern NSString *const kFIRTrackingID;
+extern NSString *const kFIRGoogleAppID;
+extern NSString *const kFIRClientID;
+extern NSString *const kFIRGCMSenderID;
+extern NSString *const kFIRAndroidClientID;
+extern NSString *const kFIRDatabaseURL;
+extern NSString *const kFIRStorageBucket;
+extern NSString *const kFIRBundleID;
+extern NSString *const kFIRProjectID;
+
+/**
+ * Keys for the plist file name
+ */
+extern NSString *const kServiceInfoFileName;
+
+extern NSString *const kServiceInfoFileType;
+
+/**
+ * This header file exposes the initialization of FIROptions to internal use.
+ */
+@interface FIROptions ()
+
+/**
+ * resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests.
+ */
++ (void)resetDefaultOptions;
+
+/**
+ * Initializes the options with dictionary. The above strings are the keys of the dictionary.
+ * This is the designated initializer.
+ */
+- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary;
+
+/**
+ * defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and
+ * other first party services.
+ */
++ (FIROptions *)defaultOptions;
+
++ (NSDictionary *)defaultOptionsDictionary;
+
+/**
+ * Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless
+ * explicitly disabled in GoogleService-Info.plist.
+ */
+@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled;
+
+/**
+ * Whether or not Analytics Collection was completely disabled. If YES, then
+ * isAnalyticsCollectionEnabled will be NO.
+ */
+@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated;
+
+/**
+ * The version ID of the client library, e.g. @"1100000".
+ */
+@property(nonatomic, readonly, copy) NSString *libraryVersionID;
+
+/**
+ * The flag indicating whether this object was constructed with the values in the default plist
+ * file.
+ */
+@property(nonatomic) BOOL usingOptionsFromDefaultPlist;
+
+/**
+ * Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
+ * GoogleService-Info.plist.
+ */
+@property(nonatomic, readonly) BOOL isMeasurementEnabled;
+
+/**
+ * Whether or not Analytics was enabled in the developer console.
+ */
+@property(nonatomic, readonly) BOOL isAnalyticsEnabled;
+
+/**
+ * Whether or not SignIn was enabled in the developer console.
+ */
+@property(nonatomic, readonly) BOOL isSignInEnabled;
+
+/**
+ * Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp.
+ */
+@property(nonatomic, getter=isEditingLocked) BOOL editingLocked;
+
+@end
diff --git a/Firebase/Core/Private/FIRReachabilityChecker+Internal.h b/Firebase/Core/Private/FIRReachabilityChecker+Internal.h
new file mode 100644
index 0000000..f82d103
--- /dev/null
+++ b/Firebase/Core/Private/FIRReachabilityChecker+Internal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRReachabilityChecker.h"
+
+typedef SCNetworkReachabilityRef (*FIRReachabilityCreateWithNameFn)(CFAllocatorRef allocator,
+ const char *host);
+
+typedef Boolean (*FIRReachabilitySetCallbackFn)(SCNetworkReachabilityRef target,
+ SCNetworkReachabilityCallBack callback,
+ SCNetworkReachabilityContext *context);
+typedef Boolean (*FIRReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target,
+ CFRunLoopRef runLoop,
+ CFStringRef runLoopMode);
+typedef Boolean (*FIRReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target,
+ CFRunLoopRef runLoop,
+ CFStringRef runLoopMode);
+
+typedef void (*FIRReachabilityReleaseFn)(CFTypeRef cf);
+
+struct FIRReachabilityApi {
+ FIRReachabilityCreateWithNameFn createWithNameFn;
+ FIRReachabilitySetCallbackFn setCallbackFn;
+ FIRReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn;
+ FIRReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn;
+ FIRReachabilityReleaseFn releaseFn;
+};
+
+@interface FIRReachabilityChecker (Internal)
+
+- (const struct FIRReachabilityApi *)reachabilityApi;
+- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi;
+
+@end
diff --git a/Firebase/Core/Private/FIRReachabilityChecker.h b/Firebase/Core/Private/FIRReachabilityChecker.h
new file mode 100644
index 0000000..105cd3d
--- /dev/null
+++ b/Firebase/Core/Private/FIRReachabilityChecker.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import Foundation;
+@import SystemConfiguration;
+
+/// Reachability Status
+typedef enum {
+ kFIRReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable.
+ kFIRReachabilityNotReachable, ///< Host is not reachable.
+ kFIRReachabilityViaWifi, ///< Host is reachable via Wifi.
+ kFIRReachabilityViaCellular, ///< Host is reachable via cellular.
+} FIRReachabilityStatus;
+
+const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status);
+
+@class FIRReachabilityChecker;
+@protocol FIRNetworkLoggerDelegate;
+
+/// Google Analytics iOS Reachability Checker.
+@protocol FIRReachabilityDelegate
+@required
+/// Called when network status has changed.
+- (void)reachability:(FIRReachabilityChecker *)reachability
+ statusChanged:(FIRReachabilityStatus)status;
+@end
+
+/// Google Analytics iOS Network Status Checker.
+@interface FIRReachabilityChecker : NSObject
+
+/// The last known reachability status, or FIRReachabilityStatusUnknown if the
+/// checker is not active.
+@property(nonatomic, readonly) FIRReachabilityStatus reachabilityStatus;
+/// The host to which reachability status is to be checked.
+@property(nonatomic, copy, readonly) NSString *host;
+/// The delegate to be notified of reachability status changes.
+@property(nonatomic, weak) id<FIRReachabilityDelegate> reachabilityDelegate;
+/// The delegate to be notified to log messages.
+@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
+/// `YES` if the reachability checker is active, `NO` otherwise.
+@property(nonatomic, readonly) BOOL isActive;
+
+/// Initialize the reachability checker. Note that you must call start to begin checking for and
+/// receiving notifications about network status changes.
+///
+/// @param reachabilityDelegate The delegate to be notified when reachability status to host
+/// changes.
+///
+/// @param loggerDelegate The delegate to send log messages to.
+///
+/// @param host The name of the host.
+///
+- (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
+ loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
+ withHost:(NSString *)host;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/// Start checking for reachability to the specified host. This has no effect if the status
+/// checker is already checking for connectivity.
+///
+/// @return `YES` if initiating status checking was successful or the status checking has already
+/// been initiated, `NO` otherwise.
+- (BOOL)start;
+
+/// Stop checking for reachability to the specified host. This has no effect if the status
+/// checker is not checking for connectivity.
+- (void)stop;
+
+@end
diff --git a/Firebase/Core/Private/FIRURLSchemeUtil.h b/Firebase/Core/Private/FIRURLSchemeUtil.h
new file mode 100644
index 0000000..d4fa961
--- /dev/null
+++ b/Firebase/Core/Private/FIRURLSchemeUtil.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+/**
+ * Checks whether the URL schemes declared for Google SignIn are valid.
+ *
+ * @param urlSchemes The URL schemes to validate.
+ * @return YES if the schemes are valid; NO otherwise.
+ */
+extern BOOL fir_areURLSchemesValidForGoogleSignIn(NSArray *urlSchemes);