diff options
Diffstat (limited to 'Firebase/Core')
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); |