Adds firebase live data

Also no longer crashes when touching something while loading
This commit is contained in:
Patrick McDonagh
2018-05-31 19:55:47 -05:00
parent c94cdc1369
commit 95467f9161
463 changed files with 62157 additions and 2177 deletions

View File

@@ -14,6 +14,8 @@ target 'pocloud' do
pod 'PromiseKit/Alamofire'
pod 'Kingfisher'
pod 'SwiftChart'
pod 'Firebase/Core'
pod 'Firebase/Database'
end

View File

@@ -3,7 +3,36 @@ PODS:
- ChameleonFramework (2.1.0):
- ChameleonFramework/Default (= 2.1.0)
- ChameleonFramework/Default (2.1.0)
- Firebase/Core (5.1.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 5.0.1)
- Firebase/CoreOnly (5.1.0):
- FirebaseCore (= 5.0.2)
- Firebase/Database (5.1.0):
- Firebase/CoreOnly
- FirebaseDatabase (= 5.0.1)
- FirebaseAnalytics (5.0.1):
- FirebaseCore (~> 5.0)
- FirebaseInstanceID (~> 3.0)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- nanopb (~> 0.3)
- FirebaseCore (5.0.2):
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- FirebaseDatabase (5.0.1):
- FirebaseCore (~> 5.0)
- leveldb-library (~> 1.18)
- FirebaseInstanceID (3.0.0):
- FirebaseCore (~> 5.0)
- GoogleToolboxForMac/Defines (2.1.4)
- "GoogleToolboxForMac/NSData+zlib (2.1.4)":
- GoogleToolboxForMac/Defines (= 2.1.4)
- Kingfisher (4.8.0)
- leveldb-library (1.20)
- nanopb (0.3.8):
- nanopb/decode (= 0.3.8)
- nanopb/encode (= 0.3.8)
- nanopb/decode (0.3.8)
- nanopb/encode (0.3.8)
- PromiseKit/Alamofire (6.2.8):
- Alamofire (~> 4.0)
- PromiseKit/CorePromise
@@ -20,6 +49,8 @@ PODS:
DEPENDENCIES:
- Alamofire
- ChameleonFramework
- Firebase/Core
- Firebase/Database
- Kingfisher
- PromiseKit/Alamofire
- RealmSwift
@@ -31,7 +62,15 @@ SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Alamofire
- ChameleonFramework
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseDatabase
- FirebaseInstanceID
- GoogleToolboxForMac
- Kingfisher
- leveldb-library
- nanopb
- PromiseKit
- Realm
- RealmSwift
@@ -42,7 +81,15 @@ SPEC REPOS:
SPEC CHECKSUMS:
Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223
ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64
Firebase: e08fb0795f35707aeb1d8a715c731c45bdf6fd56
FirebaseAnalytics: b3628aea54c50464c32c393fb2ea032566e7ecc2
FirebaseCore: b81044df1044c0857a0737c6324678b72d4f7f00
FirebaseDatabase: 482bad9c2abd422bb2321194fb8c937e67426a89
FirebaseInstanceID: 83e0040351565df711a5db3d8ebe5ea21aca998a
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
Kingfisher: 976d828df2b24834c6a3f2fc4d82cdbd26552be1
leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
PromiseKit: 6788ce1a0ed5448b83d4aaf56b9fc49fb7647d32
Realm: 08b464b462d4f31bbd4ba5f5a1c8722ef0a700b7
RealmSwift: 973e1499c44ab571f894c2c908e2db450be280c3
@@ -50,6 +97,6 @@ SPEC CHECKSUMS:
SwiftChart: ba767a678d568a5ee22d419e146a0582865e1aff
SwiftyJSON: c29297daf073d2aa016295d5809cdd68045c39b3
PODFILE CHECKSUM: 5a9239d1a30e88fe3ab43f13976dd5e699d65855
PODFILE CHECKSUM: 6fae490c5739e7f6932d3d20eccacafe6a9c1ab3
COCOAPODS: 1.5.3

101
Pods/Firebase/CoreOnly/Sources/Firebase.h generated Executable file
View File

@@ -0,0 +1,101 @@
#import <FirebaseCore/FirebaseCore.h>
#if !defined(__has_include)
#error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \
import the headers individually."
#else
#if __has_include(<FirebaseAnalytics/FirebaseAnalytics.h>)
#import <FirebaseAnalytics/FirebaseAnalytics.h>
#else
#ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#warning "FirebaseAnalytics.framework is not included in your target. Please add \
`Firebase/Core` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \
Firebase services work as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
#import <FirebaseAuth/FirebaseAuth.h>
#endif
#if __has_include(<FirebaseCrash/FirebaseCrash.h>)
#import <FirebaseCrash/FirebaseCrash.h>
#endif
#if __has_include(<FirebaseDatabase/FirebaseDatabase.h>)
#import <FirebaseDatabase/FirebaseDatabase.h>
#endif
#if __has_include(<FirebaseDynamicLinks/FirebaseDynamicLinks.h>)
#import <FirebaseDynamicLinks/FirebaseDynamicLinks.h>
#endif
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#endif
#if __has_include(<FirebaseFunctions/FirebaseFunctions.h>)
#import <FirebaseFunctions/FirebaseFunctions.h>
#endif
#if __has_include(<FirebaseInstanceID/FirebaseInstanceID.h>)
#import <FirebaseInstanceID/FirebaseInstanceID.h>
#endif
#if __has_include(<FirebaseInvites/FirebaseInvites.h>)
#import <FirebaseInvites/FirebaseInvites.h>
#endif
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
#import <FirebaseMessaging/FirebaseMessaging.h>
#endif
#if __has_include(<FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>)
#import <FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>
#endif
#if __has_include(<FirebaseMLVision/FirebaseMLVision.h>)
#import <FirebaseMLVision/FirebaseMLVision.h>
#endif
#if __has_include(<FirebaseMLVisionBarcodeModel/FirebaseMLVisionBarcodeModel.h>)
#import <FirebaseMLVisionBarcodeModel/FirebaseMLVisionBarcodeModel.h>
#endif
#if __has_include(<FirebaseMLVisionFaceModel/FirebaseMLVisionFaceModel.h>)
#import <FirebaseMLVisionFaceModel/FirebaseMLVisionFaceModel.h>
#endif
#if __has_include(<FirebaseMLVisionLabelModel/FirebaseMLVisionLabelModel.h>)
#import <FirebaseMLVisionLabelModel/FirebaseMLVisionLabelModel.h>
#endif
#if __has_include(<FirebaseMLVisionTextModel/FirebaseMLVisionTextModel.h>)
#import <FirebaseMLVisionTextModel/FirebaseMLVisionTextModel.h>
#endif
#if __has_include(<FirebasePerformance/FirebasePerformance.h>)
#import <FirebasePerformance/FirebasePerformance.h>
#endif
#if __has_include(<FirebaseRemoteConfig/FirebaseRemoteConfig.h>)
#import <FirebaseRemoteConfig/FirebaseRemoteConfig.h>
#endif
#if __has_include(<FirebaseStorage/FirebaseStorage.h>)
#import <FirebaseStorage/FirebaseStorage.h>
#endif
#if __has_include(<GoogleMobileAds/GoogleMobileAds.h>)
#import <GoogleMobileAds/GoogleMobileAds.h>
#endif
#if __has_include(<Fabric/Fabric.h>)
#import <Fabric/Fabric.h>
#endif
#if __has_include(<Crashlytics/Crashlytics.h>)
#import <Crashlytics/Crashlytics.h>
#endif
#endif // defined(__has_include)

View File

@@ -0,0 +1,4 @@
module Firebase {
export *
header "Firebase.h"
}

87
Pods/Firebase/README.md generated Executable file
View File

@@ -0,0 +1,87 @@
# Firebase APIs for iOS
Simplify your iOS development, grow your user base, and monetize more
effectively with Firebase services.
Much more information can be found at [https://firebase.google.com](https://firebase.google.com).
## Install a Firebase SDK using CocoaPods
Firebase distributes several iOS specific APIs and SDKs via CocoaPods.
You can install the CocoaPods tool on OS X by running the following command from
the terminal. Detailed information is available in the [Getting Started
guide](https://guides.cocoapods.org/using/getting-started.html#getting-started).
```
$ sudo gem install cocoapods
```
## Try out an SDK
You can try any of the SDKs with `pod try`. Run the following command and select
the SDK you are interested in when prompted:
```
$ pod try Firebase
```
Note that some SDKs may require credentials. More information is available in
the SDK-specific documentation at [https://firebase.google.com/docs/](https://firebase.google.com/docs/).
## Add a Firebase SDK to your iOS app
CocoaPods is used to install and manage dependencies in existing Xcode projects.
1. Create an Xcode project, and save it to your local machine.
2. Create a file named `Podfile` in your project directory. This file defines
your project's dependencies, and is commonly referred to as a Podspec.
3. Open `Podfile`, and add your dependencies. A simple Podspec is shown here:
```
platform :ios, '8.0'
pod 'Firebase'
```
4. Save the file.
5. Open a terminal and `cd` to the directory containing the Podfile.
```
$ cd <path-to-project>/project/
```
6. Run the `pod install` command. This will install the SDKs specified in the
Podspec, along with any dependencies they may have.
```
$ pod install
```
7. Open your app's `.xcworkspace` file to launch Xcode. Use this file for all
development on your app.
8. You can also install other Firebase SDKs by adding the subspecs in the
Podfile.
```
pod 'Firebase/AdMob'
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Firestore'
pod 'Firebase/Functions'
pod 'Firebase/Invites'
pod 'Firebase/Messaging'
pod 'Firebase/MLCommon'
pod 'Firebase/MLModelInterpreter'
pod 'Firebase/MLVision'
pod 'Firebase/MLVisionBarcodeModel'
pod 'Firebase/MLVisionFaceModel'
pod 'Firebase/MLVisionLabelModel'
pod 'Firebase/MLVisionTextModel'
pod 'Firebase/Performance'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
```

View File

@@ -0,0 +1,62 @@
#import <Foundation/Foundation.h>
#import "FIRAnalytics.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Provides App Delegate handlers to be used in your App Delegate.
*
* To save time integrating Firebase Analytics in an application, Firebase Analytics does not
* require delegation implementation from the AppDelegate. Instead this is automatically done by
* Firebase Analytics. Should you choose instead to delegate manually, you can turn off the App
* Delegate Proxy by adding FirebaseAppDelegateProxyEnabled into your app's Info.plist and setting
* it to NO, and adding the methods in this category to corresponding delegation handlers.
*
* To handle Universal Links, you must return YES in
* [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
*/
@interface FIRAnalytics (AppDelegate)
/**
* Handles events related to a URL session that are waiting to be processed.
*
* For optimal use of Firebase Analytics, call this method from the
* [UIApplicationDelegate application:handleEventsForBackgroundURLSession:completionHandler]
* method of the app delegate in your app.
*
* @param identifier The identifier of the URL session requiring attention.
* @param completionHandler The completion handler to call when you finish processing the events.
* Calling this completion handler lets the system know that your app's user interface is
* updated and a new snapshot can be taken.
*/
+ (void)handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(nullable void (^)(void))completionHandler;
/**
* Handles the event when the app is launched by a URL.
*
* Call this method from [UIApplicationDelegate application:openURL:options:] &#40;on iOS 9.0 and
* above&#41;, or [UIApplicationDelegate application:openURL:sourceApplication:annotation:] &#40;on
* iOS 8.x and below&#41; in your app.
*
* @param url The URL resource to open. This resource can be a network resource or a file.
*/
+ (void)handleOpenURL:(NSURL *)url;
/**
* Handles the event when the app receives data associated with user activity that includes a
* Universal Link (on iOS 9.0 and above).
*
* Call this method from [UIApplication continueUserActivity:restorationHandler:] in your app
* delegate (on iOS 9.0 and above).
*
* @param userActivity The activity object containing the data associated with the task the user
* was performing.
*/
+ (void)handleUserActivity:(id)userActivity;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,119 @@
#import <Foundation/Foundation.h>
#import "FIREventNames.h"
#import "FIRParameterNames.h"
#import "FIRUserPropertyNames.h"
NS_ASSUME_NONNULL_BEGIN
/// The top level Firebase Analytics singleton that provides methods for logging events and setting
/// user properties. See <a href="http://goo.gl/gz8SLz">the developer guides</a> for general
/// information on using Firebase Analytics in your apps.
NS_SWIFT_NAME(Analytics)
@interface FIRAnalytics : NSObject
/// Logs an app event. The event can have up to 25 parameters. Events with the same name must have
/// the same parameters. Up to 500 event names are supported. Using predefined events and/or
/// parameters is recommended for optimal reporting.
///
/// The following event names are reserved and cannot be used:
/// <ul>
/// <li>ad_activeview</li>
/// <li>ad_click</li>
/// <li>ad_exposure</li>
/// <li>ad_impression</li>
/// <li>ad_query</li>
/// <li>adunit_exposure</li>
/// <li>app_clear_data</li>
/// <li>app_remove</li>
/// <li>app_update</li>
/// <li>error</li>
/// <li>first_open</li>
/// <li>in_app_purchase</li>
/// <li>notification_dismiss</li>
/// <li>notification_foreground</li>
/// <li>notification_open</li>
/// <li>notification_receive</li>
/// <li>os_update</li>
/// <li>screen_view</li>
/// <li>session_start</li>
/// <li>user_engagement</li>
/// </ul>
///
/// @param name The name of the event. Should contain 1 to 40 alphanumeric characters or
/// underscores. The name must start with an alphabetic character. Some event names are
/// reserved. See FIREventNames.h for the list of reserved event names. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used. Note that event names are
/// case-sensitive and that logging two events whose names differ only in case will result in
/// two distinct events.
/// @param parameters The dictionary of event parameters. Passing nil indicates that the event has
/// no parameters. Parameter names can be up to 40 characters long and must start with an
/// alphabetic character and contain only alphanumeric characters and underscores. Only NSString
/// and NSNumber (signed 64-bit integer and 64-bit floating-point number) parameter types are
/// supported. NSString parameter values can be up to 100 characters long. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used for parameter names.
+ (void)logEventWithName:(NSString *)name
parameters:(nullable NSDictionary<NSString *, id> *)parameters
NS_SWIFT_NAME(logEvent(_:parameters:));
/// Sets a user property to a given value. Up to 25 user property names are supported. Once set,
/// user property values persist throughout the app lifecycle and across sessions.
///
/// The following user property names are reserved and cannot be used:
/// <ul>
/// <li>first_open_time</li>
/// <li>last_deep_link_referrer</li>
/// <li>user_id</li>
/// </ul>
///
/// @param value The value of the user property. Values can be up to 36 characters long. Setting the
/// value to nil removes the user property.
/// @param name The name of the user property to set. Should contain 1 to 24 alphanumeric characters
/// or underscores and must start with an alphabetic character. The "firebase_", "google_", and
/// "ga_" prefixes are reserved and should not be used for user property names.
+ (void)setUserPropertyString:(nullable NSString *)value forName:(NSString *)name
NS_SWIFT_NAME(setUserProperty(_:forName:));
/// Sets the user ID property. This feature must be used in accordance with
/// <a href="https://www.google.com/policies/privacy">Google's Privacy Policy</a>
///
/// @param userID The user ID to ascribe to the user of this app on this device, which must be
/// non-empty and no more than 256 characters long. Setting userID to nil removes the user ID.
+ (void)setUserID:(nullable NSString *)userID;
/// Sets the current screen name, which specifies the current visual context in your app. This helps
/// identify the areas in your app where users spend their time and how they interact with your app.
/// Must be called on the main thread.
///
/// Note that screen reporting is enabled automatically and records the class name of the current
/// UIViewController for you without requiring you to call this method. If you implement
/// viewDidAppear in your UIViewController but do not call [super viewDidAppear:], that screen class
/// will not be automatically tracked. The class name can optionally be overridden by calling this
/// method in the viewDidAppear callback of your UIViewController and specifying the
/// screenClassOverride parameter. setScreenName:screenClass: must be called after
/// [super viewDidAppear:].
///
/// If your app does not use a distinct UIViewController for each screen, you should call this
/// method and specify a distinct screenName each time a new screen is presented to the user.
///
/// The screen name and screen class remain in effect until the current UIViewController changes or
/// a new call to setScreenName:screenClass: is made.
///
/// @param screenName The name of the current screen. Should contain 1 to 100 characters. Set to nil
/// to clear the current screen name.
/// @param screenClassOverride The name of the screen class. Should contain 1 to 100 characters. By
/// default this is the class name of the current UIViewController. Set to nil to revert to the
/// default class name.
+ (void)setScreenName:(nullable NSString *)screenName
screenClass:(nullable NSString *)screenClassOverride;
/// The unique ID for this instance of the application.
+ (NSString *)appInstanceID;
/// Clears all analytics data for this instance from the device and resets the app instance ID.
/// FIRAnalyticsConfiguration values will be reset to the default values.
+ (void)resetAnalyticsData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,13 @@
#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

View File

@@ -0,0 +1,407 @@
/// @file FIREventNames.h
///
/// Predefined event names.
///
/// An Event is an important occurrence in your app that you want to measure. You can report up to
/// 500 different types of Events per app and you can associate up to 25 unique parameters with each
/// Event type. Some common events are suggested below, but you may also choose to specify custom
/// Event types that are associated with your specific app. Each event type is identified by a
/// unique name. Event names can be up to 40 characters long, may only contain alphanumeric
/// characters and underscores ("_"), and must start with an alphabetic character. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used.
#import <Foundation/Foundation.h>
/// Add Payment Info event. This event signifies that a user has submitted their payment information
/// to your app.
static NSString *const kFIREventAddPaymentInfo NS_SWIFT_NAME(AnalyticsEventAddPaymentInfo) =
@"add_payment_info";
/// E-Commerce Add To Cart event. This event signifies that an item was added to a cart for
/// purchase. Add this event to a funnel with kFIREventEcommercePurchase to gauge the effectiveness
/// of your checkout process. Note: If you supply the @c kFIRParameterValue parameter, you must
/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventAddToCart NS_SWIFT_NAME(AnalyticsEventAddToCart) = @"add_to_cart";
/// E-Commerce Add To Wishlist event. This event signifies that an item was added to a wishlist.
/// Use this event to identify popular gift items in your app. Note: If you supply the
/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency
/// parameter so that revenue metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventAddToWishlist NS_SWIFT_NAME(AnalyticsEventAddToWishlist) =
@"add_to_wishlist";
/// App Open event. By logging this event when an App becomes active, developers can understand how
/// often users leave and return during the course of a Session. Although Sessions are automatically
/// reported, this event can provide further clarification around the continuous engagement of
/// app-users.
static NSString *const kFIREventAppOpen NS_SWIFT_NAME(AnalyticsEventAppOpen) = @"app_open";
/// E-Commerce Begin Checkout event. This event signifies that a user has begun the process of
/// checking out. Add this event to a funnel with your kFIREventEcommercePurchase event to gauge the
/// effectiveness of your checkout process. Note: If you supply the @c kFIRParameterValue
/// parameter, you must also supply the @c kFIRParameterCurrency parameter so that revenue
/// metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventBeginCheckout NS_SWIFT_NAME(AnalyticsEventBeginCheckout) =
@"begin_checkout";
/// Campaign Detail event. Log this event to supply the referral details of a re-engagement
/// campaign. Note: you must supply at least one of the required parameters kFIRParameterSource,
/// kFIRParameterMedium or kFIRParameterCampaign. Params:
///
/// <ul>
/// <li>@c kFIRParameterSource (NSString)</li>
/// <li>@c kFIRParameterMedium (NSString)</li>
/// <li>@c kFIRParameterCampaign (NSString)</li>
/// <li>@c kFIRParameterTerm (NSString) (optional)</li>
/// <li>@c kFIRParameterContent (NSString) (optional)</li>
/// <li>@c kFIRParameterAdNetworkClickID (NSString) (optional)</li>
/// <li>@c kFIRParameterCP1 (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventCampaignDetails NS_SWIFT_NAME(AnalyticsEventCampaignDetails) =
@"campaign_details";
/// Checkout progress. Params:
///
/// <ul>
/// <li>@c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCheckoutOption (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventCheckoutProgress NS_SWIFT_NAME(AnalyticsEventCheckoutProgress) =
@"checkout_progress";
/// Earn Virtual Currency event. This event tracks the awarding of virtual currency in your app. Log
/// this along with @c kFIREventSpendVirtualCurrency to better understand your virtual economy.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterVirtualCurrencyName (NSString)</li>
/// <li>@c kFIRParameterValue (signed 64-bit integer or double as NSNumber)</li>
/// </ul>
static NSString *const kFIREventEarnVirtualCurrency
NS_SWIFT_NAME(AnalyticsEventEarnVirtualCurrency) = @"earn_virtual_currency";
/// E-Commerce Purchase event. This event signifies that an item was purchased by a user. Note:
/// This is different from the in-app purchase event, which is reported automatically for App
/// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also
/// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// <li>@c kFIRParameterTax (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterShipping (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCoupon (NSString) (optional)</li>
/// <li>@c kFIRParameterLocation (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventEcommercePurchase NS_SWIFT_NAME(AnalyticsEventEcommercePurchase) =
@"ecommerce_purchase";
/// Generate Lead event. Log this event when a lead has been generated in the app to understand the
/// efficacy of your install and re-engagement campaigns. Note: If you supply the
/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency
/// parameter so that revenue metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventGenerateLead NS_SWIFT_NAME(AnalyticsEventGenerateLead) =
@"generate_lead";
/// Join Group event. Log this event when a user joins a group such as a guild, team or family. Use
/// this event to analyze how popular certain groups or social features are in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterGroupID (NSString)</li>
/// </ul>
static NSString *const kFIREventJoinGroup NS_SWIFT_NAME(AnalyticsEventJoinGroup) = @"join_group";
/// Level Up event. This event signifies that a player has leveled up in your gaming app. It can
/// help you gauge the level distribution of your userbase and help you identify certain levels that
/// are difficult to pass. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevel (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCharacter (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventLevelUp NS_SWIFT_NAME(AnalyticsEventLevelUp) = @"level_up";
/// Login event. Apps with a login feature can report this event to signify that a user has logged
/// in.
static NSString *const kFIREventLogin NS_SWIFT_NAME(AnalyticsEventLogin) = @"login";
/// Post Score event. Log this event when the user posts a score in your gaming app. This event can
/// help you understand how users are actually performing in your game and it can help you correlate
/// high scores with certain audiences or behaviors. Params:
///
/// <ul>
/// <li>@c kFIRParameterScore (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterLevel (signed 64-bit integer as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCharacter (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventPostScore NS_SWIFT_NAME(AnalyticsEventPostScore) = @"post_score";
/// Present Offer event. This event signifies that the app has presented a purchase offer to a user.
/// Add this event to a funnel with the kFIREventAddToCart and kFIREventEcommercePurchase to gauge
/// your conversion process. Note: If you supply the @c kFIRParameterValue parameter, you must
/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventPresentOffer NS_SWIFT_NAME(AnalyticsEventPresentOffer) =
@"present_offer";
/// E-Commerce Purchase Refund event. This event signifies that an item purchase was refunded.
/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the
/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventPurchaseRefund NS_SWIFT_NAME(AnalyticsEventPurchaseRefund) =
@"purchase_refund";
/// Remove from cart event. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventRemoveFromCart NS_SWIFT_NAME(AnalyticsEventRemoveFromCart) =
@"remove_from_cart";
/// Search event. Apps that support search features can use this event to contextualize search
/// operations by supplying the appropriate, corresponding parameters. This event can help you
/// identify the most popular content in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterSearchTerm (NSString)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventSearch NS_SWIFT_NAME(AnalyticsEventSearch) = @"search";
/// Select Content event. This general purpose event signifies that a user has selected some content
/// of a certain type in an app. The content can be any object in your app. This event can help you
/// identify popular content and categories of content in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterContentType (NSString)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// </ul>
static NSString *const kFIREventSelectContent NS_SWIFT_NAME(AnalyticsEventSelectContent) =
@"select_content";
/// Set checkout option. Params:
///
/// <ul>
/// <li>@c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCheckoutOption (NSString)</li>
/// </ul>
static NSString *const kFIREventSetCheckoutOption NS_SWIFT_NAME(AnalyticsEventSetCheckoutOption) =
@"set_checkout_option";
/// Share event. Apps with social features can log the Share event to identify the most viral
/// content. Params:
///
/// <ul>
/// <li>@c kFIRParameterContentType (NSString)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// </ul>
static NSString *const kFIREventShare NS_SWIFT_NAME(AnalyticsEventShare) = @"share";
/// Sign Up event. This event indicates that a user has signed up for an account in your app. The
/// parameter signifies the method by which the user signed up. Use this event to understand the
/// different behaviors between logged in and logged out users. Params:
///
/// <ul>
/// <li>@c kFIRParameterSignUpMethod (NSString)</li>
/// </ul>
static NSString *const kFIREventSignUp NS_SWIFT_NAME(AnalyticsEventSignUp) = @"sign_up";
/// Spend Virtual Currency event. This event tracks the sale of virtual goods in your app and can
/// help you identify which virtual goods are the most popular objects of purchase. Params:
///
/// <ul>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterVirtualCurrencyName (NSString)</li>
/// <li>@c kFIRParameterValue (signed 64-bit integer or double as NSNumber)</li>
/// </ul>
static NSString *const kFIREventSpendVirtualCurrency
NS_SWIFT_NAME(AnalyticsEventSpendVirtualCurrency) = @"spend_virtual_currency";
/// Tutorial Begin event. This event signifies the start of the on-boarding process in your app. Use
/// this in a funnel with kFIREventTutorialComplete to understand how many users complete this
/// process and move on to the full app experience.
static NSString *const kFIREventTutorialBegin NS_SWIFT_NAME(AnalyticsEventTutorialBegin) =
@"tutorial_begin";
/// Tutorial End event. Use this event to signify the user's completion of your app's on-boarding
/// process. Add this to a funnel with kFIREventTutorialBegin to gauge the completion rate of your
/// on-boarding process.
static NSString *const kFIREventTutorialComplete NS_SWIFT_NAME(AnalyticsEventTutorialComplete) =
@"tutorial_complete";
/// Unlock Achievement event. Log this event when the user has unlocked an achievement in your
/// game. Since achievements generally represent the breadth of a gaming experience, this event can
/// help you understand how many users are experiencing all that your game has to offer. Params:
///
/// <ul>
/// <li>@c kFIRParameterAchievementID (NSString)</li>
/// </ul>
static NSString *const kFIREventUnlockAchievement NS_SWIFT_NAME(AnalyticsEventUnlockAchievement) =
@"unlock_achievement";
/// View Item event. This event signifies that some content was shown to the user. This content may
/// be a product, a webpage or just a simple image or text. Use the appropriate parameters to
/// contextualize the event. Use this event to discover the most popular items viewed in your app.
/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the
/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterFlightNumber (NSString) (optional) for travel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// travel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterSearchTerm (NSString) (optional) for travel bookings</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventViewItem NS_SWIFT_NAME(AnalyticsEventViewItem) = @"view_item";
/// View Item List event. Log this event when the user has been presented with a list of items of a
/// certain category. Params:
///
/// <ul>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// </ul>
static NSString *const kFIREventViewItemList NS_SWIFT_NAME(AnalyticsEventViewItemList) =
@"view_item_list";
/// View Search Results event. Log this event when the user has been presented with the results of a
/// search. Params:
///
/// <ul>
/// <li>@c kFIRParameterSearchTerm (NSString)</li>
/// </ul>
static NSString *const kFIREventViewSearchResults NS_SWIFT_NAME(AnalyticsEventViewSearchResults) =
@"view_search_results";
/// Level Start event. Log this event when the user starts a new level. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevelName (NSString)</li>
/// </ul>
static NSString *const kFIREventLevelStart NS_SWIFT_NAME(AnalyticsEventLevelStart) =
@"level_start";
/// Level End event. Log this event when the user finishes a level. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevelName (NSString)</li>
/// <li>@c kFIRParameterSuccess (NSString)</li>
/// </ul>
static NSString *const kFIREventLevelEnd NS_SWIFT_NAME(AnalyticsEventLevelEnd) = @"level_end";

View File

@@ -0,0 +1,507 @@
/// @file FIRParameterNames.h
///
/// Predefined event parameter names.
///
/// Params supply information that contextualize Events. You can associate up to 25 unique Params
/// with each Event type. Some Params are suggested below for certain common Events, but you are
/// not limited to these. You may supply extra Params for suggested Events or custom Params for
/// Custom events. Param names can be up to 40 characters long, may only contain alphanumeric
/// characters and underscores ("_"), and must start with an alphabetic character. Param values can
/// be up to 100 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and
/// should not be used.
#import <Foundation/Foundation.h>
/// Game achievement ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAchievementID : @"10_matches_won",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAchievementID NS_SWIFT_NAME(AnalyticsParameterAchievementID) =
@"achievement_id";
/// Ad Network Click ID (NSString). Used for network-specific click IDs which vary in format.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAdNetworkClickID : @"1234567",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAdNetworkClickID
NS_SWIFT_NAME(AnalyticsParameterAdNetworkClickID) = @"aclid";
/// The store or affiliation from which this transaction occurred (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAffiliation : @"Google Store",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAffiliation NS_SWIFT_NAME(AnalyticsParameterAffiliation) =
@"affiliation";
/// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to
/// capture campaign information, otherwise can be populated by developer. Highly Recommended
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCampaign : @"winter_promotion",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) =
@"campaign";
/// Character used in game (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCharacter : @"beat_boss",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCharacter NS_SWIFT_NAME(AnalyticsParameterCharacter) =
@"character";
/// The checkout step (1..N) (unsigned 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCheckoutStep : @"1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCheckoutStep NS_SWIFT_NAME(AnalyticsParameterCheckoutStep) =
@"checkout_step";
/// Some option on a step in an ecommerce flow (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCheckoutOption : @"Visa",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCheckoutOption
NS_SWIFT_NAME(AnalyticsParameterCheckoutOption) = @"checkout_option";
/// Campaign content (NSString).
static NSString *const kFIRParameterContent NS_SWIFT_NAME(AnalyticsParameterContent) = @"content";
/// Type of content selected (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterContentType : @"news article",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterContentType NS_SWIFT_NAME(AnalyticsParameterContentType) =
@"content_type";
/// Coupon code for a purchasable item (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCoupon : @"zz123",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCoupon NS_SWIFT_NAME(AnalyticsParameterCoupon) = @"coupon";
/// Campaign custom parameter (NSString). Used as a method of capturing custom data in a campaign.
/// Use varies by network.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCP1 : @"custom_data",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCP1 NS_SWIFT_NAME(AnalyticsParameterCP1) = @"cp1";
/// The name of a creative used in a promotional spot (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCreativeName : @"Summer Sale",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCreativeName NS_SWIFT_NAME(AnalyticsParameterCreativeName) =
@"creative_name";
/// The name of a creative slot (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCreativeSlot : @"summer_banner2",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCreativeSlot NS_SWIFT_NAME(AnalyticsParameterCreativeSlot) =
@"creative_slot";
/// Purchase currency in 3-letter <a href="http://en.wikipedia.org/wiki/ISO_4217#Active_codes">
/// ISO_4217</a> format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCurrency : @"USD",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCurrency NS_SWIFT_NAME(AnalyticsParameterCurrency) =
@"currency";
/// Flight or Travel destination (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterDestination : @"Mountain View, CA",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterDestination NS_SWIFT_NAME(AnalyticsParameterDestination) =
@"destination";
/// The arrival date, check-out date or rental end date for the item. This should be in
/// YYYY-MM-DD format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterEndDate : @"2015-09-14",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterEndDate NS_SWIFT_NAME(AnalyticsParameterEndDate) = @"end_date";
/// Flight number for travel events (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterFlightNumber : @"ZZ800",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterFlightNumber NS_SWIFT_NAME(AnalyticsParameterFlightNumber) =
@"flight_number";
/// Group/clan/guild ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterGroupID : @"g1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterGroupID NS_SWIFT_NAME(AnalyticsParameterGroupID) = @"group_id";
/// Index of an item in a list (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterIndex : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterIndex NS_SWIFT_NAME(AnalyticsParameterIndex) = @"index";
/// Item brand (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemBrand : @"Google",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemBrand NS_SWIFT_NAME(AnalyticsParameterItemBrand) =
@"item_brand";
/// Item category (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemCategory : @"t-shirts",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemCategory NS_SWIFT_NAME(AnalyticsParameterItemCategory) =
@"item_category";
/// Item ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemID : @"p7654",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemID NS_SWIFT_NAME(AnalyticsParameterItemID) = @"item_id";
/// The Google <a href="https://developers.google.com/places/place-id">Place ID</a> (NSString) that
/// corresponds to the associated item. Alternatively, you can supply your own custom Location ID.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemLocationID
NS_SWIFT_NAME(AnalyticsParameterItemLocationID) = @"item_location_id";
/// Item name (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemName : @"abc",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemName NS_SWIFT_NAME(AnalyticsParameterItemName) =
@"item_name";
/// The list in which the item was presented to the user (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemList : @"Search Results",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemList NS_SWIFT_NAME(AnalyticsParameterItemList) =
@"item_list";
/// Item variant (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemVariant : @"Red",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemVariant NS_SWIFT_NAME(AnalyticsParameterItemVariant) =
@"item_variant";
/// Level in game (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLevel : @(42),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLevel NS_SWIFT_NAME(AnalyticsParameterLevel) = @"level";
/// Location (NSString). The Google <a href="https://developers.google.com/places/place-id">Place ID
/// </a> that corresponds to the associated event. Alternatively, you can supply your own custom
/// Location ID.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLocation : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLocation NS_SWIFT_NAME(AnalyticsParameterLocation) =
@"location";
/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterMedium : @"email",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium";
/// Number of nights staying at hotel (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfNights : @(3),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfNights
NS_SWIFT_NAME(AnalyticsParameterNumberOfNights) = @"number_of_nights";
/// Number of passengers traveling (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfPassengers : @(11),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfPassengers
NS_SWIFT_NAME(AnalyticsParameterNumberOfPassengers) = @"number_of_passengers";
/// Number of rooms for travel events (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfRooms : @(2),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfRooms NS_SWIFT_NAME(AnalyticsParameterNumberOfRooms) =
@"number_of_rooms";
/// Flight or Travel origin (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterOrigin : @"Mountain View, CA",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterOrigin NS_SWIFT_NAME(AnalyticsParameterOrigin) = @"origin";
/// Purchase price (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterPrice : @(1.0),
/// kFIRParameterCurrency : @"USD", // e.g. $1.00 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterPrice NS_SWIFT_NAME(AnalyticsParameterPrice) = @"price";
/// Purchase quantity (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterQuantity : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterQuantity NS_SWIFT_NAME(AnalyticsParameterQuantity) =
@"quantity";
/// Score in game (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterScore : @(4200),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterScore NS_SWIFT_NAME(AnalyticsParameterScore) = @"score";
/// The search string/keywords used (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSearchTerm : @"periodic table",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSearchTerm NS_SWIFT_NAME(AnalyticsParameterSearchTerm) =
@"search_term";
/// Shipping cost (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterShipping : @(9.50),
/// kFIRParameterCurrency : @"USD", // e.g. $9.50 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterShipping NS_SWIFT_NAME(AnalyticsParameterShipping) =
@"shipping";
/// Sign up method (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSignUpMethod : @"google",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSignUpMethod NS_SWIFT_NAME(AnalyticsParameterSignUpMethod) =
@"sign_up_method";
/// The origin of your traffic, such as an Ad network (for example, google) or partner (urban
/// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your
/// property. Highly recommended (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSource : @"InMobi",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source";
/// The departure date, check-in date or rental start date for the item. This should be in
/// YYYY-MM-DD format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterStartDate : @"2015-09-14",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterStartDate NS_SWIFT_NAME(AnalyticsParameterStartDate) =
@"start_date";
/// Tax amount (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTax : @(1.0),
/// kFIRParameterCurrency : @"USD", // e.g. $1.00 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTax NS_SWIFT_NAME(AnalyticsParameterTax) = @"tax";
/// If you're manually tagging keyword campaigns, you should use utm_term to specify the keyword
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTerm : @"game",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTerm NS_SWIFT_NAME(AnalyticsParameterTerm) = @"term";
/// A single ID for a ecommerce group transaction (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTransactionID : @"ab7236dd9823",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTransactionID NS_SWIFT_NAME(AnalyticsParameterTransactionID) =
@"transaction_id";
/// Travel class (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTravelClass : @"business",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTravelClass NS_SWIFT_NAME(AnalyticsParameterTravelClass) =
@"travel_class";
/// A context-specific numeric value which is accumulated automatically for each event type. This is
/// a general purpose parameter that is useful for accumulating a key metric that pertains to an
/// event. Examples include revenue, distance, time and points. Value should be specified as signed
/// 64-bit integer or double as NSNumber. Notes: Values for pre-defined currency-related events
/// (such as @c kFIREventAddToCart) should be supplied using double as NSNumber and must be
/// accompanied by a @c kFIRParameterCurrency parameter. The valid range of accumulated values is
/// [-9,223,372,036,854.77, 9,223,372,036,854.77]. Supplying a non-numeric value, omitting the
/// corresponding @c kFIRParameterCurrency parameter, or supplying an invalid
/// <a href="https://goo.gl/qqX3J2">currency code</a> for conversion events will cause that
/// conversion to be omitted from reporting.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterValue : @(3.99),
/// kFIRParameterCurrency : @"USD", // e.g. $3.99 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterValue NS_SWIFT_NAME(AnalyticsParameterValue) = @"value";
/// Name of virtual currency type (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterVirtualCurrencyName : @"virtual_currency_name",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterVirtualCurrencyName
NS_SWIFT_NAME(AnalyticsParameterVirtualCurrencyName) = @"virtual_currency_name";
/// The name of a level in a game (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLevelName : @"room_1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLevelName NS_SWIFT_NAME(AnalyticsParameterLevelName) =
@"level_name";
/// The result of an operation. Specify 1 to indicate success and 0 to indicate failure (unsigned
/// integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSuccess : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSuccess NS_SWIFT_NAME(AnalyticsParameterSuccess) = @"success";

View File

@@ -0,0 +1,17 @@
/// @file FIRUserPropertyNames.h
///
/// Predefined user property names.
///
/// A UserProperty is an attribute that describes the app-user. By supplying UserProperties, you can
/// later analyze different behaviors of various segments of your userbase. You may supply up to 25
/// unique UserProperties per app, and you can use the name and value of your choosing for each one.
/// UserProperty names can be up to 24 characters long, may only contain alphanumeric characters and
/// underscores ("_"), and must start with an alphabetic character. UserProperty values can be up to
/// 36 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and should not
/// be used.
#import <Foundation/Foundation.h>
/// The method used to sign in. For example, "google", "facebook" or "twitter".
static NSString *const kFIRUserPropertySignUpMethod
NS_SWIFT_NAME(AnalyticsUserPropertySignUpMethod) = @"sign_up_method";

View File

@@ -0,0 +1,6 @@
#import "FIRAnalytics+AppDelegate.h"
#import "FIRAnalytics.h"
#import "FIRAnalyticsSwiftNameSupport.h"
#import "FIREventNames.h"
#import "FIRParameterNames.h"
#import "FIRUserPropertyNames.h"

View File

@@ -0,0 +1,10 @@
framework module FirebaseAnalytics {
umbrella header "FirebaseAnalytics.h"
export *
module * { export *}
link "sqlite3"
link "z"
link framework "Security"
link framework "StoreKit"
link framework "SystemConfiguration"
link framework "UIKit"}

View File

@@ -0,0 +1,6 @@
framework module FirebaseCoreDiagnostics {
export *
module * { export *}
link "z"
link framework "Security"
link framework "SystemConfiguration"}

Binary file not shown.

View File

@@ -0,0 +1,69 @@
// 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 {
[self setAnalyticsCollectionEnabled:analyticsCollectionEnabled persistSetting:YES];
}
- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled
persistSetting:(BOOL)shouldPersist {
// Persist the measurementEnabledState. Use FIRAnalyticsEnabledState values instead of YES/NO.
FIRAnalyticsEnabledState analyticsEnabledState =
analyticsCollectionEnabled ? kFIRAnalyticsEnabledStateSetYes : kFIRAnalyticsEnabledStateSetNo;
if (shouldPersist) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@(analyticsEnabledState)
forKey:kFIRAPersistedConfigMeasurementEnabledStateKey];
[userDefaults synchronize];
}
[self postNotificationName:kFIRAnalyticsConfigurationSetEnabledNotification
value:@(analyticsCollectionEnabled)];
}
@end

737
Pods/FirebaseCore/Firebase/Core/FIRApp.m generated Normal file
View File

@@ -0,0 +1,737 @@
// 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 "FIRConfiguration.h"
#import "Private/FIRAnalyticsConfiguration+Internal.h"
#import "Private/FIRAppInternal.h"
#import "Private/FIRBundleUtil.h"
#import "Private/FIRLogger.h"
#import "Private/FIROptionsInternal.h"
#import "third_party/FIRAppEnvironmentUtil.h"
NSString *const kFIRServiceAdMob = @"AdMob";
NSString *const kFIRServiceAuth = @"Auth";
NSString *const kFIRServiceAuthUI = @"AuthUI";
NSString *const kFIRServiceCrash = @"Crash";
NSString *const kFIRServiceDatabase = @"Database";
NSString *const kFIRServiceDynamicLinks = @"DynamicLinks";
NSString *const kFIRServiceFirestore = @"Firestore";
NSString *const kFIRServiceFunctions = @"Functions";
NSString *const kFIRServiceInstanceID = @"InstanceID";
NSString *const kFIRServiceInvites = @"Invites";
NSString *const kFIRServiceMessaging = @"Messaging";
NSString *const kFIRServiceMeasurement = @"Measurement";
NSString *const kFIRServicePerformance = @"Performance";
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 kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
@"/google/firebase/global_data_collection_enabled:%@";
NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
@"FirebaseAutomaticDataCollectionEnabled";
NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
// Auth internal notification notification and key.
NSString *const FIRAuthStateDidChangeInternalNotification =
@"FIRAuthStateDidChangeInternalNotification";
NSString *const FIRAuthStateDidChangeInternalNotificationAppKey =
@"FIRAuthStateDidChangeInternalNotificationAppKey";
NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey =
@"FIRAuthStateDidChangeInternalNotificationTokenKey";
NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
@"FIRAuthStateDidChangeInternalNotificationUIDKey";
/**
* 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;
static NSMutableDictionary *sLibraryVersions;
+ (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];` (`FirebaseApp.configure()` in Swift) could not find "
@"a valid GoogleService-Info.plist in your project. Please download one "
@"from %@.",
kPlistURL];
}
[FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
#if TARGET_OS_OSX || TARGET_OS_TV
FIRLogNotice(kFIRLoggerCore, @"I-COR000028",
@"tvOS and macOS SDK support is not part of the official Firebase product. "
@"Instead they are community supported. Details at "
@"https://github.com/firebase/firebase-ios-sdk/blob/master/README.md.");
#endif
}
+ (void)configureWithOptions:(FIROptions *)options {
if (!options) {
[NSException raise:kFirebaseCoreErrorDomain
format:@"Options is nil. Please pass a valid options."];
}
[FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
}
+ (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."];
}
for (NSInteger charIndex = 0; charIndex < name.length; charIndex++) {
char character = [name characterAtIndex:charIndex];
if (!((character >= 'a' && character <= 'z') || (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];` (`FirebaseApp.configure()` in Swift) 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;
[sLibraryVersions removeAllObjects];
sLibraryVersions = 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];
[self clearDataCollectionSwitchFromUserDefaults];
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;
}
// 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];
}
- (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
[[NSUserDefaults standardUserDefaults] setBool:automaticDataCollectionEnabled forKey:key];
// Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set
// within FIROptions and change the Analytics value if necessary. Analytics only works with the
// default app, so return if this isn't the default app.
if (self != sDefaultApp) {
return;
}
// Check if the Analytics flag is explicitly set. If so, no further actions are necessary.
if ([self.options isAnalyticsCollectionExpicitlySet]) {
return;
}
// The Analytics flag has not been explicitly set, so update with the value being set.
[[FIRAnalyticsConfiguration sharedInstance]
setAnalyticsCollectionEnabled:automaticDataCollectionEnabled
persistSetting:NO];
}
- (BOOL)isAutomaticDataCollectionEnabled {
// Check if it's been manually set before in code, and use that as the higher priority value.
NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
if (defaultsObject) {
return [defaultsObject boolValue];
}
// Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
// As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
// no performance impact calling multiple times.
NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
if (collectionEnabledPlistValue) {
return [collectionEnabledPlistValue boolValue];
}
return YES;
}
#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 [NSError errorWithDomain:kFirebaseCoreErrorDomain
code:FIRErrorCodeInvalidPlistFile
userInfo: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 [NSError errorWithDomain:domain code:code userInfo: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 [NSError errorWithDomain:kFirebaseCoreErrorDomain
code:FIRErrorCodeInvalidAppID
userInfo:errorDict];
}
+ (BOOL)isDefaultAppConfigured {
return (sDefaultApp != nil);
}
+ (void)registerLibrary:(nonnull NSString *)library withVersion:(nonnull NSString *)version {
// Create the set of characters which aren't allowed, only if this feature is used.
NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedSet addCharactersInString:@"-_."];
NSCharacterSet *disallowedSet = [allowedSet invertedSet];
// Make sure the library name and version strings do not contain unexpected characters, and
// add the name/version pair to the dictionary.
if ([library rangeOfCharacterFromSet:disallowedSet].location == NSNotFound &&
[version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) {
if (!sLibraryVersions) {
sLibraryVersions = [[NSMutableDictionary alloc] init];
}
sLibraryVersions[library] = version;
} else {
FIRLogError(kFIRLoggerCore, @"I-COR000027",
@"The library name (%@) or version number (%@) contain illegal characters. "
@"Only alphanumeric, dash, underscore and period characters are allowed.",
library, version);
}
}
+ (NSString *)firebaseUserAgent {
NSMutableArray<NSString *> *libraries =
[[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count];
for (NSString *libraryName in sLibraryVersions) {
[libraries
addObject:[NSString stringWithFormat:@"%@/%@", libraryName, sLibraryVersions[libraryName]]];
}
[libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
return [libraries componentsJoinedByString:@" "];
}
- (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]) {
FIRLogError(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 - Reading From Plist & User Defaults
/**
* Clears the data collection switch from the standard NSUserDefaults for easier testing and
* readability.
*/
- (void)clearDataCollectionSwitchFromUserDefaults {
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
/**
* Reads the data collection switch from the standard NSUserDefaults for easier testing and
* readability.
*/
+ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
// Read the object in user defaults, and only return if it's an NSNumber.
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
return collectionEnabledDefaultsObject;
}
return nil;
}
/**
* Reads the data collection switch from the Info.plist for easier testing and readability. Will
* only read once from the plist and return the cached value.
*/
+ (nullable NSNumber *)readDataCollectionSwitchFromPlist {
static NSNumber *collectionEnabledPlistObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
id plistValue = [[NSBundle mainBundle]
objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
collectionEnabledPlistObject = (NSNumber *)plistValue;
}
});
return collectionEnabledPlistObject;
}
#pragma mark - Sending Logs
- (void)sendLogsWithServiceName:(NSString *)serviceName
version:(NSString *)version
error:(NSError *)error {
// If the user has manually turned off data collection, return and don't send logs.
if (![self isAutomaticDataCollectionEnabled]) {
return;
}
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

View File

@@ -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 (^)(void))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

View File

@@ -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 "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;
}
+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles {
for (NSBundle *bundle in bundles) {
if ([bundle.bundleIdentifier isEqualToString:bundleIdentifier]) {
return YES;
}
}
return NO;
}
@end

View File

@@ -0,0 +1,44 @@
// 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;
}
- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel {
NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin,
@"Invalid logger level, %ld", (long)loggerLevel);
FIRSetLoggerLevel(loggerLevel);
}
@end

View File

@@ -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.
#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";

View File

@@ -0,0 +1,275 @@
// 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/FIRVersion.h"
#import "third_party/FIRAppEnvironmentUtil.h"
#include <asl.h>
#include <assert.h>
#include <stdbool.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.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 kFIRLoggerFirestore = @"[Firebase/Firestore]";
FIRLoggerService kFIRLoggerInstanceID = @"[Firebase/InstanceID]";
FIRLoggerService kFIRLoggerInvites = @"[Firebase/Invites]";
FIRLoggerService kFIRLoggerMLKit = @"[Firebase/MLKit]";
FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]";
FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]";
FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]";
FIRLoggerService kFIRLoggerStorage = @"[Firebase/Storage]";
FIRLoggerService kFIRLoggerSwizzler = @"[FirebaseSwizzlingUtilities]";
/// Arguments passed on launch.
NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled";
NSString *const kFIREnableDebugModeApplicationArgument = @"-FIRDebugEnabled";
NSString *const kFIRLoggerForceSDTERRApplicationArgument = @"-FIRLoggerForceSTDERR";
/// 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";
/// Keys for the number of errors and warnings logged.
NSString *const kFIRLoggerErrorCountKey = @"/google/firebase/count_of_errors_logged";
NSString *const kFIRLoggerWarningCountKey = @"/google/firebase/count_of_warnings_logged";
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;
#ifdef DEBUG
/// The regex pattern for the message code.
static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$";
static NSRegularExpression *sMessageCodeRegex;
#endif
void FIRLoggerInitializeASL() {
dispatch_once(&sFIRLoggerOnceToken, ^{
NSInteger majorOSVersion = [[FIRAppEnvironmentUtil systemVersion] integerValue];
uint32_t aslOptions = ASL_OPT_STDERR;
#if TARGET_OS_SIMULATOR
// The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 11) {
aslOptions = 0;
}
#else
// Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 10) {
aslOptions = 0;
}
#endif // TARGET_OS_SIMULATOR
// Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in.
NSArray *arguments = [NSProcessInfo processInfo].arguments;
if ([arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]) {
aslOptions = ASL_OPT_STDERR;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated
// Initialize the ASL client handle.
sFIRLoggerClient = asl_open(NULL, kFIRLoggerASLClientFacilityName, aslOptions);
// 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;
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;
}
sFIRClientQueue = dispatch_queue_create("FIRLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(sFIRClientQueue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
#ifdef DEBUG
sMessageCodeRegex =
[NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL];
#endif
});
}
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();
// We should not raise the logger level if we are running from App Store.
if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) {
return;
}
sFIRLoggerMaximumLevel = loggerLevel;
dispatch_async(sFIRClientQueue, ^{
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:@"%s - %@[%@] %@", FIRVersionString, service, messageCode, logMsg];
dispatch_async(sFIRClientQueue, ^{
asl_log(sFIRLoggerClient, NULL, level, "%s", logMsg.UTF8String);
});
}
#pragma clang diagnostic pop
/**
* 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
#pragma mark - FIRLoggerWrapper
@implementation FIRLoggerWrapper
+ (void)logWithLevel:(FIRLoggerLevel)level
withService:(FIRLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args {
FIRLogBasic(level, service, messageCode, message, args);
}
@end

View File

@@ -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 = self->_objects.description;
});
return description;
}
- (id)objectForKey:(id)key {
__block id object;
dispatch_sync(_queue, ^{
object = self->_objects[key];
});
return object;
}
- (void)setObject:(id)object forKey:(id<NSCopying>)key {
dispatch_async(_queue, ^{
self->_objects[key] = object;
});
}
- (void)removeObjectForKey:(id)key {
dispatch_async(_queue, ^{
[self->_objects removeObjectForKey:key];
});
}
- (void)removeAllObjects {
dispatch_async(_queue, ^{
[self->_objects removeAllObjects];
});
}
- (NSUInteger)count {
__block NSUInteger count;
dispatch_sync(_queue, ^{
count = self->_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 = [self->_objects copy];
});
return dictionary;
}
@end

View File

@@ -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/FIRLogger.h"
#import "Private/FIRMutableDictionary.h"
#import "Private/FIRNetworkConstants.h"
#import "Private/FIRReachabilityChecker.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

View File

@@ -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/Foundation.h>
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";

View File

@@ -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 <Foundation/Foundation.h>
#import "Private/FIRNetworkURLSession.h"
#import "Private/FIRLogger.h"
#import "Private/FIRMutableDictionary.h"
#import "Private/FIRNetworkConstants.h"
#import "Private/FIRNetworkMessageCode.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;
}
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession003
message:@"Background session finished"
context:session.configuration.identifier];
[self callSystemCompletionHandler:session.configuration.identifier];
}
#endif
- (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 {
[self->_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession007
message:@"Cancelling authentication challenge for host. Host"
context:self->_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) {
[self->_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession008
message:@"Cannot evaluate server trust. Error, host"
contexts:@[ @(trustError), self->_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_OSX && defined(MAC_OS_X_VERSION_10_10) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
TARGET_OS_TV || \
(TARGET_OS_IOS && 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_OSX && defined(MAC_OS_X_VERSION_10_10) && \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \
(TARGET_OS_IOS && 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, self->_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

View File

@@ -0,0 +1,445 @@
// 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/FIRAppInternal.h"
#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 =
@"5" // Major version (one or more digits)
@"00" // Minor version (exactly 2 digits)
@"02" // 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;
/**
* Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary.
* It combines analytics options from both the infoDictionary 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;
/**
* Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist.
* Values which are present in the infoDictionary override values from the GoogleService-Info.plist.
*/
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary;
/**
* Throw exception if editing is locked when attempting to modify an option.
*/
- (void)checkEditingLocked;
@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
+ (void)initialize {
// Report FirebaseCore version for useragent string
NSRange major = NSMakeRange(0, 1);
NSRange minor = NSMakeRange(1, 2);
NSRange patch = NSMakeRange(3, 2);
[FIRApp
registerLibrary:@"fire-ios"
withVersion:[NSString stringWithFormat:@"%@.%d.%d",
[kFIRLibraryVersionID substringWithRange:major],
[[kFIRLibraryVersionID substringWithRange:minor]
intValue],
[[kFIRLibraryVersionID substringWithRange:patch]
intValue]]];
NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
NSString *xcodeVersion = info[@"DTXcodeBuild"];
NSString *sdkVersion = info[@"DTSDKBuild"];
if (xcodeVersion) {
[FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
}
if (sdkVersion) {
[FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
}
}
+ (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)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)checkEditingLocked {
if (self.isEditingLocked) {
[NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
}
}
- (void)setAPIKey:(NSString *)APIKey {
[self checkEditingLocked];
_optionsDictionary[kFIRAPIKey] = [APIKey copy];
}
- (NSString *)clientID {
return self.optionsDictionary[kFIRClientID];
}
- (void)setClientID:(NSString *)clientID {
[self checkEditingLocked];
_optionsDictionary[kFIRClientID] = [clientID copy];
}
- (NSString *)trackingID {
return self.optionsDictionary[kFIRTrackingID];
}
- (void)setTrackingID:(NSString *)trackingID {
[self checkEditingLocked];
_optionsDictionary[kFIRTrackingID] = [trackingID copy];
}
- (NSString *)GCMSenderID {
return self.optionsDictionary[kFIRGCMSenderID];
}
- (void)setGCMSenderID:(NSString *)GCMSenderID {
[self checkEditingLocked];
_optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy];
}
- (NSString *)projectID {
return self.optionsDictionary[kFIRProjectID];
}
- (void)setProjectID:(NSString *)projectID {
[self checkEditingLocked];
_optionsDictionary[kFIRProjectID] = [projectID copy];
}
- (NSString *)androidClientID {
return self.optionsDictionary[kFIRAndroidClientID];
}
- (void)setAndroidClientID:(NSString *)androidClientID {
[self checkEditingLocked];
_optionsDictionary[kFIRAndroidClientID] = [androidClientID copy];
}
- (NSString *)googleAppID {
return self.optionsDictionary[kFIRGoogleAppID];
}
- (void)setGoogleAppID:(NSString *)googleAppID {
[self checkEditingLocked];
_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 {
[self checkEditingLocked];
_optionsDictionary[kFIRDatabaseURL] = [databaseURL copy];
}
- (NSString *)storageBucket {
return self.optionsDictionary[kFIRStorageBucket];
}
- (void)setStorageBucket:(NSString *)storageBucket {
[self checkEditingLocked];
_optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
}
- (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme {
[self checkEditingLocked];
_deepLinkURLScheme = [deepLinkURLScheme copy];
}
- (NSString *)bundleID {
return self.optionsDictionary[kFIRBundleID];
}
- (void)setBundleID:(NSString *)bundleID {
[self checkEditingLocked];
_optionsDictionary[kFIRBundleID] = [bundleID copy];
}
#pragma mark - Internal instance methods
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
dispatch_once(&_createAnalyticsOptionsDictionaryOnce, ^{
NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
NSArray *measurementKeys = @[
kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled,
kFIRIsAnalyticsCollectionDeactivated
];
for (NSString *key in measurementKeys) {
id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
if (!value) {
continue;
}
tempAnalyticsOptions[key] = value;
}
self->_analyticsOptionsDictionary = tempAnalyticsOptions;
});
return _analyticsOptionsDictionary;
}
- (NSDictionary *)analyticsOptionsDictionary {
return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary];
}
/**
* 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;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
if (value == nil) {
// TODO: This could probably be cleaned up since FIROptions shouldn't know about FIRApp or have
// to check if it's the default app. The FIROptions instance can't be modified after
// `+configure` is called, so it's not a good place to copy it either in case the flag is
// changed at runtime.
// If no values are set for Analytics, fall back to the global collection switch in FIRApp.
// Analytics only supports the default FIRApp, so check that first.
if (![FIRApp isDefaultAppConfigured]) {
return NO;
}
// Fall back to the default app's collection switch when the key is not in the dictionary.
return [FIRApp defaultApp].automaticDataCollectionEnabled;
}
return [value boolValue];
}
- (BOOL)isAnalyticsCollectionExpicitlySet {
// If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication
// that the developer wants FirebaseAnalytics enabled so continue checking.
if (self.isAnalyticsCollectionDeactivated) {
return YES;
}
// Check if the current Analytics flag is set.
id collectionEnabledObject = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
if (collectionEnabledObject && [collectionEnabledObject isKindOfClass:[NSNumber class]]) {
// It doesn't matter what the value is, it's explicitly set.
return YES;
}
// Check if the old measurement flag is set.
id measurementEnabledObject = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
if (measurementEnabledObject && [measurementEnabledObject isKindOfClass:[NSNumber class]]) {
// It doesn't matter what the value is, it's explicitly set.
return YES;
}
// No flags are set to explicitly enable or disable FirebaseAnalytics.
return NO;
}
- (BOOL)isAnalyticsCollectionEnabled {
if (self.isAnalyticsCollectionDeactivated) {
return NO;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
if (value == nil) {
return self.isMeasurementEnabled; // Fall back to older plist flag.
}
return [value boolValue];
}
- (BOOL)isAnalyticsCollectionDeactivated {
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated];
if (value == nil) {
return NO; // Analytics Collection is not deactivated when the key is not in the dictionary.
}
return [value boolValue];
}
- (BOOL)isAnalyticsEnabled {
return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue];
}
- (BOOL)isSignInEnabled {
return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue];
}
@end

View File

@@ -0,0 +1,256 @@
// 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 "Private/FIRReachabilityChecker+Internal.h"
#import "Private/FIRReachabilityChecker.h"
#import "Private/FIRLogger.h"
#import "Private/FIRNetwork.h"
#import "Private/FIRNetworkMessageCode.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.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kFIRReachabilityViaWifi;
#endif
} 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.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kFIRReachabilityViaWifi;
#endif
}
}
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];
}
}

View File

@@ -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.
*/
#ifndef Firebase_VERSION
#error "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation"
#endif
#ifndef FIRCore_VERSION
#error "FIRCore_VERSION is not defined: add -DFIRCore_VERSION=... to the build invocation"
#endif
// The following two macros supply the incantation so that the C
// preprocessor does not try to parse the version as a floating
// point number. See
// https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
const unsigned char *const FIRVersionString = (const unsigned char *const)STR(Firebase_VERSION);
const unsigned char *const FIRCoreVersionString = (const unsigned char *const)STR(FIRCore_VERSION);

View File

@@ -0,0 +1,49 @@
/*
* 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";
@interface FIRAnalyticsConfiguration (Internal)
/// Sets whether analytics collection is enabled for this app on this device, and a flag to persist
/// the value or not. The setting should not be persisted if being set by the global data collection
/// flag.
- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled
persistSetting:(BOOL)shouldPersist;
@end

View File

@@ -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 (^)(void))creationBlock;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,214 @@
/*
* 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 kFIRServiceAuthUI;
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;
/**
* The format string for the User Defaults key used for storing the data collection enabled flag.
* This includes formatting to append the Firebase App's name.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
/**
* The plist key used for storing the data collection enabled flag.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
/**
* A notification fired containing diagnostic information when SDK errors occur.
*/
extern NSString *const kFIRAppDiagnosticsNotification;
/** @var FIRAuthStateDidChangeInternalNotification
@brief The name of the @c NSNotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
the notification is a dictionary possibly containing the key:
@c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not
contain this key it indicates a sign-out event took place.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotification;
/** @var FIRAuthStateDidChangeInternalNotificationTokenKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new access token.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
/** @var FIRAuthStateDidChangeInternalNotificationAppKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the FIRApp associated with the auth instance.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey;
/** @var FIRAuthStateDidChangeInternalNotificationUIDKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new user's UID (or nil if there is no longer a user signed in).
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey;
/** @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)(void);
@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;
/**
* Checks if the default app is configured without trying to configure it.
*/
+ (BOOL)isDefaultAppConfigured;
/**
* Registers a given third-party library with the given version number to be reported for
* analyitcs.
*
* @param library Name of the library
* @param version Version of the library
*/
// clang-format off
+ (void)registerLibrary:(NSString *)library
withVersion:(NSString *)version NS_SWIFT_NAME(registerLibrary(_:version:));
// clang-format on
/**
* A concatenated string representing all the third-party libraries and version numbers.
*/
+ (NSString *)firebaseUserAgent;
/**
* 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;
/**
* Expose the UID of the current user for Firestore.
*/
- (nullable NSString *)getUID;
/**
* WARNING: THIS SETTING DOES NOT WORK YET. IT WILL BE MOVED TO THE PUBLIC HEADER ONCE ALL SDKS
* CONFORM TO THIS PREFERENCE. DO NOT RELY ON IT.
*
* Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES`
* unless `FirebaseAutomaticDataCollectionEnabled` is set to `NO` in your app's Info.plist. This
* value is persisted across runs of the app so that it can be set once when users have consented to
* collection.
*/
@property(nonatomic, readwrite, getter=isAutomaticDataCollectionEnabled)
BOOL automaticDataCollectionEnabled;
@end
NS_ASSUME_NONNULL_END

View File

@@ -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.
*/
#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;
/**
* Checks if the bundle identifier exists in the given bundles.
*/
+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles;
@end

View File

@@ -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,
};

View File

@@ -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 <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;

View File

@@ -0,0 +1,159 @@
/*
* 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 kFIRLoggerFirestore;
extern FIRLoggerService kFIRLoggerInstanceID;
extern FIRLoggerService kFIRLoggerInvites;
extern FIRLoggerService kFIRLoggerMLKit;
extern FIRLoggerService kFIRLoggerMessaging;
extern FIRLoggerService kFIRLoggerPerf;
extern FIRLoggerService kFIRLoggerRemoteConfig;
extern FIRLoggerService kFIRLoggerStorage;
extern FIRLoggerService kFIRLoggerSwizzler;
/**
* The key used to store the logger's error count.
*/
extern NSString *const kFIRLoggerErrorCountKey;
/**
* The key used to store the logger's warning count.
*/
extern NSString *const kFIRLoggerWarningCountKey;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* 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_OS_SIMULATOR || TARGET_OS_OSX
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);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@interface FIRLoggerWrapper : NSObject
/**
* Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger
* (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.
*/
+ (void)logWithLevel:(FIRLoggerLevel)level
withService:(FIRLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args;
@end
NS_ASSUME_NONNULL_END

View File

@@ -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/Foundation.h>
/// 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

View File

@@ -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/Foundation.h>
#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

View File

@@ -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/Foundation.h>
/// 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;

View File

@@ -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/Foundation.h>
#import "FIRLoggerLevel.h"
#import "FIRNetworkMessageCode.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

View File

@@ -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
};

View File

@@ -0,0 +1,60 @@
/*
* 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 "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

View File

@@ -0,0 +1,114 @@
/*
* 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;
/**
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
* runtime.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExpicitlySet;
/**
* 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

View File

@@ -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

View File

@@ -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/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/// 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

View File

@@ -0,0 +1,23 @@
/*
* 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>
/** The version of the Firebase SDK. */
FOUNDATION_EXPORT const unsigned char *const FIRVersionString;
/** The version of the FirebaseCore Component. */
FOUNDATION_EXPORT const unsigned char *const FIRCoreVersionString;

View File

@@ -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.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* This class provides configuration fields for Firebase Analytics.
*/
NS_SWIFT_NAME(AnalyticsConfiguration)
@interface FIRAnalyticsConfiguration : NSObject
/**
* Returns the shared instance of FIRAnalyticsConfiguration.
*/
+ (FIRAnalyticsConfiguration *)sharedInstance NS_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

View File

@@ -0,0 +1,118 @@
/*
* 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>
@class FIROptions;
NS_ASSUME_NONNULL_BEGIN
/** A block that takes a BOOL and has no return value. */
typedef void (^FIRAppVoidBoolCallback)(BOOL success) NS_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.
*/
NS_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 and contains synchronous file I/O
* (reading GoogleService-Info.plist from disk).
*/
+ (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 NS_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.
*/
// clang-format off
+ (void)configureWithName:(NSString *)name
options:(FIROptions *)options NS_SWIFT_NAME(configure(name:options:));
// clang-format on
/**
* Returns the default app, or nil if the default app does not exist.
*/
+ (nullable FIRApp *)defaultApp NS_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 NS_SWIFT_NAME(app(name:));
/**
* 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;
/**
* 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

View File

@@ -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/Foundation.h>
#import "FIRAnalyticsConfiguration.h"
#import "FIRLoggerLevel.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This interface provides global level properties that the developer can tweak, and the singleton
* of the Firebase Analytics configuration class.
*/
NS_SWIFT_NAME(FirebaseConfiguration)
@interface FIRConfiguration : NSObject
/** Returns the shared configuration object. */
@property(class, nonatomic, readonly) FIRConfiguration *sharedInstance NS_SWIFT_NAME(shared);
/** The configuration class for Firebase Analytics. */
@property(nonatomic, readwrite) FIRAnalyticsConfiguration *analyticsConfiguration;
/**
* 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

View File

@@ -0,0 +1,35 @@
/*
* 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.
*/
/**
* The log levels used by internal logging.
*/
typedef NS_ENUM(NSInteger, FIRLoggerLevel) {
/** Error level, matches ASL_LEVEL_ERR. */
FIRLoggerLevelError = 3,
/** Warning level, matches ASL_LEVEL_WARNING. */
FIRLoggerLevelWarning = 4,
/** Notice level, matches ASL_LEVEL_NOTICE. */
FIRLoggerLevelNotice = 5,
/** Info level, matches ASL_LEVEL_NOTICE. */
FIRLoggerLevelInfo = 6,
/** Debug level, matches ASL_LEVEL_DEBUG. */
FIRLoggerLevelDebug = 7,
/** Minimum log level. */
FIRLoggerLevelMin = FIRLoggerLevelError,
/** Maximum log level. */
FIRLoggerLevelMax = FIRLoggerLevelDebug
} NS_SWIFT_NAME(FirebaseLoggerLevel);

View File

@@ -0,0 +1,116 @@
/*
* 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
/**
* This class provides constant fields of Google APIs.
*/
NS_SWIFT_NAME(FirebaseOptions)
@interface FIROptions : NSObject <NSCopying>
/**
* Returns the default options. The first time this is called it synchronously reads
* GoogleService-Info.plist from disk.
*/
+ (nullable FIROptions *)defaultOptions NS_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 NS_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 NS_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 from the file at the given plist file path. This
* will read the file synchronously from disk.
* 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.
*/
// clang-format off
- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
GCMSenderID:(NSString *)GCMSenderID
NS_SWIFT_NAME(init(googleAppID:gcmSenderID:));
// clang-format on
@end
NS_ASSUME_NONNULL_END

View File

@@ -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"

View File

@@ -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>
@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;
@end

View File

@@ -0,0 +1,239 @@
// 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 "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_OS_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
/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox.
/// This will affect your data integrity when using Firebase Analytics, as it will disable some
/// necessary checks.
static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey =
@"FirebaseAppStoreReceiptURLCheckEnabled";
/// 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 an app contain the sandboxReceipt file, it means its coming from TestFlight
// This must be checked before the SCInfo Folder check below since TestFlight apps may
// also have an SCInfo folder.
if ([FIRAppEnvironmentUtil isAppStoreReceiptSandbox]) {
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 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 hasEmbeddedMobileProvision];
}
+ (BOOL)isAppStoreReceiptSandbox {
// Since checking the App Store's receipt URL can be memory intensive, check the option in the
// Info.plist if developers opted out of this check.
id enableSandboxCheck =
[[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey];
if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] &&
![enableSandboxCheck boolValue]) {
return NO;
}
NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
}
+ (BOOL)hasEmbeddedMobileProvision {
#if TARGET_OS_IOS || TARGET_OS_TV
return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0;
#elif TARGET_OS_OSX
return NO;
#endif
}
+ (BOOL)isSimulator {
#if TARGET_OS_IOS || TARGET_OS_TV
NSString *platform = [FIRAppEnvironmentUtil deviceModel];
return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"];
#elif TARGET_OS_OSX
return NO;
#endif
}
+ (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 {
// Assemble the systemVersion, excluding the patch version if it's 0.
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
NSMutableString *versionString = [[NSMutableString alloc]
initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion];
if (osVersion.patchVersion != 0) {
[versionString appendFormat:@".%ld", (long)osVersion.patchVersion];
}
return versionString;
}
+ (BOOL)isAppExtension {
#if TARGET_OS_IOS || TARGET_OS_TV
// Documented by <a href="https://goo.gl/RRB2Up">Apple</a>
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
#elif TARGET_OS_OSX
return NO;
#endif
}
#pragma mark - Helper methods
+ (BOOL)hasSCInfoFolder {
#if TARGET_OS_IOS || TARGET_OS_TV
NSString *bundlePath = [NSBundle mainBundle].bundlePath;
NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"];
return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath];
#elif TARGET_OS_OSX
return NO;
#endif
}
@end

202
Pods/FirebaseCore/LICENSE generated Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

198
Pods/FirebaseCore/README.md generated Normal file
View File

@@ -0,0 +1,198 @@
# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore,
FirebaseFunctions, FirebaseMessaging and FirebaseStorage.
Firebase is an app development platform with tools to help you build, grow and
monetize your app. More information about Firebase can be found at
[https://firebase.google.com](https://firebase.google.com).
## Installation
See the three subsections for details about three different installation methods.
1. [Standard pod install](README.md#standard-pod-install)
1. [Installing from the GitHub repo](README.md#installing-from-github)
1. [Experimental Carthage](README.md#carthage-ios-only)
### Standard pod install
Go to
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
### Installing from GitHub
For releases starting with 5.0.0, the source for each release is also deployed
to CocoaPods master and available via standard
[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod).
These instructions can be used to access the Firebase repo at other branches,
tags, or commits.
#### Background
See
[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
for instructions and options about overriding pod source locations.
#### Step-by-step Source Pod Installation Instructions
For iOS, copy a subset of the following lines to your Podfile:
```
pod 'Firebase' # To enable Firebase module, with `@import Firebase` support
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseAuth', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseDatabase', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseFunctions', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseMessaging', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseStorage', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
```
For macOS and tvOS, copy a subset of the following:
```
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseAuth', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseDatabase', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
pod 'FirebaseStorage', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => '5.0.0'
```
1. Make sure you have at least CocoaPods version 1.4.0 - `pod --version`.
1. Delete pods for any components you don't need, except `FirebaseCore` must always be included.
1. Update the tags to the latest Firebase release. See the
[release notes](https://firebase.google.com/support/release-notes/ios).
1. Run `pod update`.
#### Examples
To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
```
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
```
To access via a branch:
```
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
```
### Carthage (iOS only)
An experimental Carthage distribution is now available. See
[Carthage](Carthage.md).
## Development
Follow the subsequent instructions to develop, debug, unit test, run integration
tests, and try out reference samples:
```
$ git clone git@github.com:firebase/firebase-ios-sdk.git
$ cd firebase-ios-sdk/Example
$ pod update
$ open Firebase.xcworkspace
```
Firestore and Functions have self contained Xcode projects. See
[Firestore/README.md](Firestore/README.md) and
[Functions/README.md](Functions/README.md).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
### Running Sample Apps
In order to run the sample apps and integration tests, you'll need valid
`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist
files without real values, but can be replaced with real plist files. To get your own
`GoogleService-Info.plist` files:
1. Go to the [Firebase Console](https://console.firebase.google.com/)
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
### Firebase Auth
If you're doing specific Firebase Auth development, see
[AuthSamples/README.md](AuthSamples/README.md) for instructions about
building and running the FirebaseAuth pod along with various samples and tests.
### Firebase Database
To run the Database Integration tests, make your database authentication rules
[public](https://firebase.google.com/docs/database/security/quickstart).
### Firebase Storage
To run the Storage Integration tests, follow the instructions in
[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m).
#### Push Notifications
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
In order to actually test receiving push notifications, you will need to:
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
account, and enable that App ID for push notifications.
2. You'll also need to
[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
#### iOS Simulator
The iOS Simulator cannot register for remote notifications, and will not receive push notifications.
In order to receive push notifications, you'll have to follow the steps above and run the app on a
physical device.
## Community Supported Efforts
We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community.
### macOS and tvOS
FirebaseAuth, FirebaseCore, FirebaseDatabase and FirebaseStorage now compile, run unit tests, and
work on macOS and tvOS, thanks to contributions from the community. There are a few tweaks needed,
like ensuring iOS-only, macOS-only, or tvOS-only code is correctly guarded with checks for
`TARGET_OS_IOS`, `TARGET_OS_OSX` and `TARGET_OS_TV`.
For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
For installation instructions, see [above](README.md#step-by-step-source-pod-installation-instructions).
## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source
plans and directions.
## Contributing
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
iOS SDK.
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Your use of Firebase is governed by the
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).

View File

@@ -0,0 +1,101 @@
/*
* 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 "FIRDataSnapshot.h"
#import "FIRDataSnapshot_Private.h"
#import "FChildrenNode.h"
#import "FValidation.h"
#import "FTransformedEnumerator.h"
#import "FIRDatabaseReference.h"
@interface FIRDataSnapshot ()
@property (nonatomic, strong) FIRDatabaseReference *ref;
@end
@implementation FIRDataSnapshot
- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node
{
self = [super init];
if (self != nil) {
self->_ref = ref;
self->_node = node;
}
return self;
}
- (id) value {
return [self.node.node val];
}
- (id) valueInExportFormat {
return [self.node.node valForExport:YES];
}
- (FIRDataSnapshot *)childSnapshotForPath:(NSString *)childPathString {
[FValidation validateFrom:@"child:" validPathString:childPathString];
FPath* childPath = [[FPath alloc] initWith:childPathString];
FIRDatabaseReference * childRef = [self.ref child:childPathString];
id<FNode> childNode = [self.node.node getChild:childPath];
return [[FIRDataSnapshot alloc] initWithRef:childRef indexedNode:[FIndexedNode indexedNodeWithNode:childNode]];
}
- (BOOL) hasChild:(NSString *)childPathString {
[FValidation validateFrom:@"hasChild:" validPathString:childPathString];
FPath* childPath = [[FPath alloc] initWith:childPathString];
return ! [[self.node.node getChild:childPath] isEmpty];
}
- (id) priority {
id<FNode> priority = [self.node.node getPriority];
return priority.val;
}
- (BOOL) hasChildren {
if([self.node.node isLeafNode]) {
return false;
}
else {
return ![self.node.node isEmpty];
}
}
- (BOOL) exists {
return ![self.node.node isEmpty];
}
- (NSString *) key {
return [self.ref key];
}
- (NSUInteger) childrenCount {
return [self.node.node numChildren];
}
- (NSEnumerator<FIRDataSnapshot *> *) children {
return [[FTransformedEnumerator alloc] initWithEnumerator:self.node.childEnumerator andTransform:^id(FNamedNode *node) {
FIRDatabaseReference *childRef = [self.ref child:node.name];
return [[FIRDataSnapshot alloc] initWithRef:childRef indexedNode:[FIndexedNode indexedNodeWithNode:node.node]];
}];
}
- (NSString *) description {
return [NSString stringWithFormat:@"Snap (%@) %@", self.key, self.node.node];
}
@end

View File

@@ -0,0 +1,305 @@
/*
* 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 <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIRLogger.h>
#import "FIRDatabase.h"
#import "FIRDatabase_Private.h"
#import "FIRDatabaseQuery_Private.h"
#import "FRepoManager.h"
#import "FValidation.h"
#import "FIRDatabaseConfig_Private.h"
#import "FRepoInfo.h"
#import "FIRDatabaseConfig.h"
#import "FIRDatabaseReference_Private.h"
#import <FirebaseCore/FIROptions.h>
@interface FIRDatabase ()
@property (nonatomic, strong) FRepoInfo *repoInfo;
@property (nonatomic, strong) FIRDatabaseConfig *config;
@property (nonatomic, strong) FRepo *repo;
@end
@implementation FIRDatabase
/** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase instance. */
typedef NSMutableDictionary<NSString *, NSMutableDictionary<FRepoInfo *, FIRDatabase *> *> FIRDatabaseDictionary;
// The STR and STR_EXPAND macro allow a numeric version passed to he compiler driver
// with a -D to be treated as a string instead of an invalid floating point value.
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
static const char *FIREBASE_SEMVER = (const char *)STR(FIRDatabase_VERSION);
+ (void)load {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserverForName:kFIRAppDeleteNotification
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
NSString *appName = note.userInfo[kFIRAppNameKey];
if (appName == nil) { return; }
FIRDatabaseDictionary* instances = [self instances];
@synchronized (instances) {
NSMutableDictionary<FRepoInfo *, FIRDatabase *> *databaseInstances = instances[appName];
if (databaseInstances) {
// Clean up the deleted instance in an effort to remove any resources still in use.
// Note: Any leftover instances of this exact database will be invalid.
for (FIRDatabase * database in [databaseInstances allValues]) {
[FRepoManager disposeRepos:database.config];
}
[instances removeObjectForKey:appName];
}
}
}];
}
/**
* A static NSMutableDictionary of FirebaseApp name and FRepoInfo to
* FirebaseDatabase instance. To ensure thread-safety, it should only be
* accessed in databaseForApp:URL:, which is synchronized.
*
* TODO: This serves a duplicate purpose as RepoManager. We should clean up.
* TODO: We should maybe be conscious of leaks and make this a weak map or
* similar but we have a lot of work to do to allow FirebaseDatabase/Repo etc.
* to be GC'd.
*/
+ (FIRDatabaseDictionary *)instances {
static dispatch_once_t pred = 0;
static FIRDatabaseDictionary *instances;
dispatch_once(&pred, ^{
instances = [NSMutableDictionary dictionary];
});
return instances;
}
+ (FIRDatabase *)database {
if (![FIRApp isDefaultAppConfigured]) {
[NSException raise:@"FIRAppNotConfigured"
format:@"Failed to get default Firebase Database instance. Must call `[FIRApp "
@"configure]` (`FirebaseApp.configure()` in Swift) before using "
@"Firebase Database."];
}
FIRApp *app = [FIRApp defaultApp];
return [FIRDatabase databaseForApp:app];
}
+ (FIRDatabase *)databaseWithURL:(NSString *)url {
FIRApp *app = [FIRApp defaultApp];
if (app == nil) {
[NSException raise:@"FIRAppNotConfigured"
format:@"Failed to get default Firebase Database instance. "
@"Must call `[FIRApp configure]` (`FirebaseApp.configure()` in Swift) "
@"before using Firebase Database."];
}
return [FIRDatabase databaseForApp:app URL:url];
}
+ (FIRDatabase *)databaseForApp:(FIRApp *)app {
if (app == nil) {
[NSException raise:@"InvalidFIRApp" format:@"nil FIRApp instance passed to databaseForApp."];
}
return [FIRDatabase databaseForApp:app URL:app.options.databaseURL];
}
+ (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
if (app == nil) {
[NSException raise:@"InvalidFIRApp"
format:@"nil FIRApp instance passed to databaseForApp."];
}
if (url == nil) {
[NSException raise:@"MissingDatabaseURL"
format:@"Failed to get FirebaseDatabase instance: "
"Specify DatabaseURL within FIRApp or from your databaseForApp:URL: call."];
}
NSURL *databaseUrl = [NSURL URLWithString:url];
if (databaseUrl == nil) {
[NSException raise:@"InvalidDatabaseURL" format:@"The Database URL '%@' cannot be parsed. "
"Specify a valid DatabaseURL within FIRApp or from your databaseForApp:URL: call.", databaseUrl];
} else if (![databaseUrl.path isEqualToString:@""] && ![databaseUrl.path isEqualToString:@"/"]) {
[NSException raise:@"InvalidDatabaseURL" format:@"Configured Database URL '%@' is invalid. It should point "
"to the root of a Firebase Database but it includes a path: %@",databaseUrl, databaseUrl.path];
}
FIRDatabaseDictionary *instances = [self instances];
@synchronized (instances) {
NSMutableDictionary<FRepoInfo *, FIRDatabase *> *urlInstanceMap =
instances[app.name];
if (!urlInstanceMap) {
urlInstanceMap = [NSMutableDictionary dictionary];
instances[app.name] = urlInstanceMap;
}
FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl.absoluteString];
FIRDatabase *database = urlInstanceMap[parsedUrl.repoInfo];
if (!database) {
id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:app];
// If this is the default app, don't set the session persistence key so that we use our
// default ("default") instead of the FIRApp default ("[DEFAULT]") so that we
// preserve the default location used by the legacy Firebase SDK.
NSString *sessionIdentifier = @"default";
if (![FIRApp isDefaultAppConfigured] || app != [FIRApp defaultApp]) {
sessionIdentifier = app.name;
}
FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier
authTokenProvider:authTokenProvider];
database = [[FIRDatabase alloc] initWithApp:app
repoInfo:parsedUrl.repoInfo
config:config];
urlInstanceMap[parsedUrl.repoInfo] = database;
}
return database;
}
}
+ (NSString *) buildVersion {
// TODO: Restore git hash when build moves back to git
return [NSString stringWithFormat:@"%s_%s", FIREBASE_SEMVER, __DATE__];
}
+ (FIRDatabase *)createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config {
FIRDatabase *db = [[FIRDatabase alloc] initWithApp:nil repoInfo:repoInfo config:config];
[db ensureRepo];
return db;
}
+ (NSString *) sdkVersion {
return [NSString stringWithUTF8String:FIREBASE_SEMVER];
}
+ (void) setLoggingEnabled:(BOOL)enabled {
[FUtilities setLoggingEnabled:enabled];
FFLog(@"I-RDB024001", @"BUILD Version: %@", [FIRDatabase buildVersion]);
}
- (id)initWithApp:(FIRApp *)app repoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config {
self = [super init];
if (self != nil) {
self->_repoInfo = info;
self->_config = config;
self->_app = app;
}
return self;
}
- (FIRDatabaseReference *)reference {
[self ensureRepo];
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:[FPath empty]];
}
- (FIRDatabaseReference *)referenceWithPath:(NSString *)path {
[self ensureRepo];
[FValidation validateFrom:@"referenceWithPath" validRootPathString:path];
FPath *childPath = [[FPath alloc] initWith:path];
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:childPath];
}
- (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
[self ensureRepo];
if (databaseUrl == nil) {
[NSException raise:@"InvalidDatabaseURL" format:@"Invalid nil url passed to referenceFromURL:"];
}
FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
[FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) {
[NSException raise:@"InvalidDatabaseURL" format:@"Invalid URL (%@) passed to getReference(). URL was expected "
"to match configured Database URL: %@", databaseUrl, [self reference].URL];
}
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:parsedUrl.path];
}
- (void)purgeOutstandingWrites {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo purgeOutstandingWrites];
});
}
- (void)goOnline {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo resume];
});
}
- (void)goOffline {
[self ensureRepo];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo interrupt];
});
}
- (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
[self assertUnfrozen:@"setPersistenceEnabled"];
self->_config.persistenceEnabled = persistenceEnabled;
}
- (BOOL)persistenceEnabled {
return self->_config.persistenceEnabled;
}
- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
[self assertUnfrozen:@"setPersistenceCacheSizeBytes"];
self->_config.persistenceCacheSizeBytes = persistenceCacheSizeBytes;
}
- (NSUInteger)persistenceCacheSizeBytes {
return self->_config.persistenceCacheSizeBytes;
}
- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
[self assertUnfrozen:@"setCallbackQueue"];
self->_config.callbackQueue = callbackQueue;
}
- (dispatch_queue_t)callbackQueue {
return self->_config.callbackQueue;
}
- (void) assertUnfrozen:(NSString*)methodName {
if (self.repo != nil) {
[NSException raise:@"FIRDatabaseAlreadyInUse" format:@"Calls to %@ must be made before any other usage of "
"FIRDatabase instance.", methodName];
}
}
- (void) ensureRepo {
if (self.repo == nil) {
self.repo = [FRepoManager createRepo:self.repoInfo config:self.config database:self];
}
}
@end

View File

@@ -0,0 +1,63 @@
/*
* 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>
@protocol FAuthTokenProvider;
NS_ASSUME_NONNULL_BEGIN
/**
* TODO: Merge FIRDatabaseConfig into FIRDatabase.
*/
@interface FIRDatabaseConfig : NSObject
- (id)initWithSessionIdentifier:(NSString *)identifier authTokenProvider:(id<FAuthTokenProvider>)authTokenProvider;
/**
* By default the Firebase Database client will keep data in memory while your application is running, but not
* when it is restarted. By setting this value to YES, the data will be persisted to on-device (disk)
* storage and will thus be available again when the app is restarted (even when there is no network
* connectivity at that time). Note that this property must be set before creating your first FIRDatabaseReference
* and only needs to be called once per application.
*
* If your app uses Firebase Authentication, the client will automatically persist the user's authentication
* token across restarts, even without persistence enabled. But if the auth token expired while offline and
* you've enabled persistence, the client will pause write operations until you successfully re-authenticate
* (or explicitly unauthenticate) to prevent your writes from being sent unauthenticated and failing due to
* security rules.
*/
@property (nonatomic) BOOL persistenceEnabled;
/**
* By default the Firebase Database client will use up to 10MB of disk space to cache data. If the cache grows beyond this size,
* the client will start removing data that hasn't been recently used. If you find that your application caches too
* little or too much data, call this method to change the cache size. This property must be set before creating
* your first FIRDatabaseReference and only needs to be called once per application.
*
* Note that the specified cache size is only an approximation and the size on disk may temporarily exceed it
* at times.
*/
@property (nonatomic) NSUInteger persistenceCacheSizeBytes;
/**
* Sets the dispatch queue on which all events are raised. The default queue is the main queue.
*/
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,117 @@
/*
* 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 <FirebaseCore/FIRApp.h>
#import "FIRDatabaseConfig.h"
#import "FIRDatabaseConfig_Private.h"
#import "FIRNoopAuthTokenProvider.h"
#import "FAuthTokenProvider.h"
@interface FIRDatabaseConfig (Private)
@property (nonatomic, strong, readwrite) NSString *sessionIdentifier;
@end
@implementation FIRDatabaseConfig
- (id)init {
[NSException raise:NSInvalidArgumentException format:@"Can't create config objects!"];
return nil;
}
- (id)initWithSessionIdentifier:(NSString *)identifier authTokenProvider:(id<FAuthTokenProvider>)authTokenProvider {
self = [super init];
if (self != nil) {
self->_sessionIdentifier = identifier;
self->_callbackQueue = dispatch_get_main_queue();
self->_persistenceCacheSizeBytes = 10*1024*1024; // Default cache size is 10MB
self->_authTokenProvider = authTokenProvider;
}
return self;
}
- (void)assertUnfrozen {
if (self.isFrozen) {
[NSException raise:NSGenericException format:@"Can't modify config objects after they are in use for FIRDatabaseReferences."];
}
}
- (void)setAuthTokenProvider:(id<FAuthTokenProvider>)authTokenProvider {
[self assertUnfrozen];
self->_authTokenProvider = authTokenProvider;
}
- (void)setPersistenceEnabled:(BOOL)persistenceEnabled {
[self assertUnfrozen];
self->_persistenceEnabled = persistenceEnabled;
}
- (void)setPersistenceCacheSizeBytes:(NSUInteger)persistenceCacheSizeBytes {
[self assertUnfrozen];
// Can't be less than 1MB
if (persistenceCacheSizeBytes < 1024*1024) {
[NSException raise:NSInvalidArgumentException format:@"The minimum cache size must be at least 1MB"];
}
if (persistenceCacheSizeBytes > 100*1024*1024) {
[NSException raise:NSInvalidArgumentException format:@"Firebase Database currently doesn't support a cache size larger than 100MB"];
}
self->_persistenceCacheSizeBytes = persistenceCacheSizeBytes;
}
- (void)setCallbackQueue:(dispatch_queue_t)callbackQueue {
[self assertUnfrozen];
self->_callbackQueue = callbackQueue;
}
- (void)freeze {
self->_isFrozen = YES;
}
// TODO: Only used for tests. Migrate to FIRDatabase and remove.
+ (FIRDatabaseConfig *)defaultConfig {
static dispatch_once_t onceToken;
static FIRDatabaseConfig *defaultConfig;
dispatch_once(&onceToken, ^{
defaultConfig = [FIRDatabaseConfig configForName:@"default"];
});
return defaultConfig;
}
// TODO: This is only used for tests. We should fix them to go through FIRDatabase and remove
// this method and the sessionsConfigs dictionary (FIRDatabase automatically creates one config per app).
+ (FIRDatabaseConfig *)configForName:(NSString *)name {
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z0-9-_]+$" options:0 error:nil];
if ([expression numberOfMatchesInString:name options:0 range:NSMakeRange(0, name.length)] == 0) {
[NSException raise:NSInvalidArgumentException format:@"Name can only contain [a-zA-Z0-9-_]"];
}
static dispatch_once_t onceToken;
static NSMutableDictionary *sessionConfigs;
dispatch_once(&onceToken, ^{
sessionConfigs = [NSMutableDictionary dictionary];
});
@synchronized(sessionConfigs) {
if (!sessionConfigs[name]) {
id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:[FIRApp defaultApp]];
sessionConfigs[name] = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:name
authTokenProvider:authTokenProvider];
}
return sessionConfigs[name];
}
}
@end

View File

@@ -0,0 +1,525 @@
/*
* 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 "FIRDatabaseQuery.h"
#import "FIRDatabaseQuery_Private.h"
#import "FValidation.h"
#import "FQueryParams.h"
#import "FQuerySpec.h"
#import "FValueEventRegistration.h"
#import "FChildEventRegistration.h"
#import "FPath.h"
#import "FKeyIndex.h"
#import "FPathIndex.h"
#import "FPriorityIndex.h"
#import "FValueIndex.h"
#import "FLeafNode.h"
#import "FSnapshotUtilities.h"
#import "FConstants.h"
@implementation FIRDatabaseQuery
@synthesize repo;
@synthesize path;
@synthesize queryParams;
#define INVALID_QUERY_PARAM_ERROR @"InvalidQueryParameter"
+ (dispatch_queue_t)sharedQueue
{
// We use this shared queue across all of the FQueries so things happen FIFO (as opposed to dispatch_get_global_queue(0, 0) which is concurrent)
static dispatch_once_t pred;
static dispatch_queue_t sharedDispatchQueue;
dispatch_once(&pred, ^{
sharedDispatchQueue = dispatch_queue_create("FirebaseWorker", NULL);
});
return sharedDispatchQueue;
}
- (id) initWithRepo:(FRepo *)theRepo path:(FPath *)thePath {
return [self initWithRepo:theRepo path:thePath params:nil orderByCalled:NO priorityMethodCalled:NO];
}
- (id) initWithRepo:(FRepo *)theRepo
path:(FPath *)thePath
params:(FQueryParams *)theParams
orderByCalled:(BOOL)orderByCalled
priorityMethodCalled:(BOOL)priorityMethodCalled {
self = [super init];
if (self) {
self.repo = theRepo;
self.path = thePath;
if (!theParams) {
theParams = [FQueryParams defaultInstance];
}
if (![theParams isValid]) {
@throw [[NSException alloc] initWithName:@"InvalidArgumentError" reason:@"Queries are limited to two constraints" userInfo:nil];
}
self.queryParams = theParams;
self.orderByCalled = orderByCalled;
self.priorityMethodCalled = priorityMethodCalled;
}
return self;
}
- (FQuerySpec *)querySpec {
return [[FQuerySpec alloc] initWithPath:self.path params:self.queryParams];
}
- (void)validateQueryEndpointsForParams:(FQueryParams *)params {
if ([params.index isEqual:[FKeyIndex keyIndex]]) {
if ([params hasStart]) {
if (params.indexStartKey != [FUtilities minName]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryStartingAtValue:childKey: or queryEqualTo:andChildKey: in combination with queryOrderedByKey"];
}
if (![params.indexStartValue.val isKindOfClass:[NSString class]]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryStartingAtValue: with other types than string in combination with queryOrderedByKey"];
}
}
if ([params hasEnd]) {
if (params.indexEndKey != [FUtilities maxName]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryEndingAtValue:childKey: or queryEqualToValue:childKey: in combination with queryOrderedByKey"];
}
if (![params.indexEndValue.val isKindOfClass:[NSString class]]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't use queryEndingAtValue: with other types than string in combination with queryOrderedByKey"];
}
}
} else if ([params.index isEqual:[FPriorityIndex priorityIndex]]) {
if (([params hasStart] && ![FValidation validatePriorityValue:params.indexStartValue.val]) ||
([params hasEnd] && ![FValidation validatePriorityValue:params.indexEndValue.val])) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"When using queryOrderedByPriority, values provided to queryStartingAtValue:, queryEndingAtValue:, or queryEqualToValue: must be valid priorities."];
}
}
}
- (void)validateEqualToCall {
if ([self.queryParams hasStart]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Cannot combine queryEqualToValue: and queryStartingAtValue:"];
}
if ([self.queryParams hasEnd]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Cannot combine queryEqualToValue: and queryEndingAtValue:"];
}
}
- (void)validateNoPreviousOrderByCalled {
if (self.orderByCalled) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Cannot use multiple queryOrderedBy calls!"];
}
}
- (void)validateIndexValueType:(id)type fromMethod:(NSString *)method {
if (type != nil &&
![type isKindOfClass:[NSNumber class]] &&
![type isKindOfClass:[NSString class]] &&
![type isKindOfClass:[NSNull class]]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"You can only pass nil, NSString or NSNumber to %@", method];
}
}
- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue {
return [self queryStartingAtInternal:startValue childKey:nil from:@"queryStartingAtValue:" priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryStartingAtValue:(id)startValue childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryStartingAtValue: instead of queryStartingAtValue:childKey: when using queryOrderedByKey:"
userInfo:nil];
}
return [self queryStartingAtInternal:startValue
childKey:childKey
from:@"queryStartingAtValue:childKey:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryStartingAtInternal:(id<FNode>)startValue
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:startValue fromMethod:methodName];
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
if ([self.queryParams hasStart]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryStartingAtValue or queryEqualToValue was previously called", methodName];
}
id<FNode> startNode = [FSnapshotUtilities nodeFrom:startValue];
FQueryParams* params = [self.queryParams startAt:startNode childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue {
return [self queryEndingAtInternal:endValue
childKey:nil
from:@"queryEndingAtValue:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEndingAtValue:(id)endValue childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryEndingAtValue: instead of queryEndingAtValue:childKey: when using queryOrderedByKey:"
userInfo:nil];
}
return [self queryEndingAtInternal:endValue
childKey:childKey
from:@"queryEndingAtValue:childKey:"
priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEndingAtInternal:(id)endValue
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:endValue fromMethod:methodName];
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
if ([self.queryParams hasEnd]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryEndingAtValue or queryEqualToValue was previously called", methodName];
}
id<FNode> endNode = [FSnapshotUtilities nodeFrom:endValue];
FQueryParams* params = [self.queryParams endAt:endNode childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryEqualToValue:(id)value {
return [self queryEqualToInternal:value childKey:nil from:@"queryEqualToValue:" priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEqualToValue:(id)value childKey:(NSString *)childKey {
if ([self.queryParams.index isEqual:[FKeyIndex keyIndex]]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:@"You must use queryEqualToValue: instead of queryEqualTo:childKey: when using queryOrderedByKey:"
userInfo:nil];
}
return [self queryEqualToInternal:value childKey:childKey from:@"queryEqualToValue:childKey:" priorityMethod:NO];
}
- (FIRDatabaseQuery *)queryEqualToInternal:(id)value
childKey:(NSString *)childKey
from:(NSString *)methodName
priorityMethod:(BOOL)priorityMethod {
[self validateIndexValueType:value fromMethod:methodName];
if (childKey != nil) {
[FValidation validateFrom:methodName validKey:childKey];
}
if ([self.queryParams hasEnd] || [self.queryParams hasStart]) {
[NSException raise:INVALID_QUERY_PARAM_ERROR
format:@"Can't call %@ after queryStartingAtValue, queryEndingAtValue or queryEqualToValue was previously called", methodName];
}
id<FNode> node = [FSnapshotUtilities nodeFrom:value];
FQueryParams* params = [[self.queryParams startAt:node childKey:childKey] endAt:node childKey:childKey];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:priorityMethod || self.priorityMethodCalled];
}
- (void)validateLimitRange:(NSUInteger)limit
{
// No need to check for negative ranges, since limit is unsigned
if (limit == 0) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Limit can't be zero"];
}
if (limit >= 1l<<31) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Limit must be less than 2,147,483,648"];
}
}
- (FIRDatabaseQuery *)queryLimitedToFirst:(NSUInteger)limit {
if (self.queryParams.limitSet) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't call queryLimitedToFirst: if a limit was previously set"];
}
[self validateLimitRange:limit];
FQueryParams* params = [self.queryParams limitToFirst:limit];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryLimitedToLast:(NSUInteger)limit {
if (self.queryParams.limitSet) {
[NSException raise:INVALID_QUERY_PARAM_ERROR format:@"Can't call queryLimitedToLast: if a limit was previously set"];
}
[self validateLimitRange:limit];
FQueryParams* params = [self.queryParams limitToLast:limit];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:self.orderByCalled
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *)queryOrderedByChild:(NSString *)indexPathString {
if ([indexPathString isEqualToString:@"$key"] || [indexPathString isEqualToString:@".key"]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:@"(queryOrderedByChild:) %@ is invalid. Use queryOrderedByKey: instead.", indexPathString]
userInfo:nil];
} else if ([indexPathString isEqualToString:@"$priority"] || [indexPathString isEqualToString:@".priority"]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:@"(queryOrderedByChild:) %@ is invalid. Use queryOrderedByPriority: instead.", indexPathString]
userInfo:nil];
} else if ([indexPathString isEqualToString:@"$value"] || [indexPathString isEqualToString:@".value"]) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:@"(queryOrderedByChild:) %@ is invalid. Use queryOrderedByValue: instead.", indexPathString]
userInfo:nil];
}
[self validateNoPreviousOrderByCalled];
[FValidation validateFrom:@"queryOrderedByChild:" validPathString:indexPathString];
FPath *indexPath = [FPath pathWithString:indexPathString];
if (indexPath.isEmpty) {
@throw [[NSException alloc] initWithName:INVALID_QUERY_PARAM_ERROR
reason:[NSString stringWithFormat:@"(queryOrderedByChild:) with an empty path is invalid. Use queryOrderedByValue: instead."]
userInfo:nil];
}
id<FIndex> index = [[FPathIndex alloc] initWithPath:indexPath];
FQueryParams *params = [self.queryParams orderBy:index];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *) queryOrderedByKey {
[self validateNoPreviousOrderByCalled];
FQueryParams *params = [self.queryParams orderBy:[FKeyIndex keyIndex]];
[self validateQueryEndpointsForParams:params];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *) queryOrderedByValue {
[self validateNoPreviousOrderByCalled];
FQueryParams *params = [self.queryParams orderBy:[FValueIndex valueIndex]];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseQuery *) queryOrderedByPriority {
[self validateNoPreviousOrderByCalled];
FQueryParams *params = [self.queryParams orderBy:[FPriorityIndex priorityIndex]];
return [[FIRDatabaseQuery alloc] initWithRepo:self.repo
path:self.path
params:params
orderByCalled:YES
priorityMethodCalled:self.priorityMethodCalled];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(void (^)(FIRDataSnapshot *))block {
[FValidation validateFrom:@"observeEventType:withBlock:" knownEventType:eventType];
return [self observeEventType:eventType withBlock:block withCancelBlock:nil];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
[FValidation validateFrom:@"observeEventType:andPreviousSiblingKeyWithBlock:" knownEventType:eventType];
return [self observeEventType:eventType andPreviousSiblingKeyWithBlock:block withCancelBlock:nil];
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType withBlock:(fbt_void_datasnapshot)block withCancelBlock:(fbt_void_nserror)cancelBlock {
[FValidation validateFrom:@"observeEventType:withBlock:withCancelBlock:" knownEventType:eventType];
if (eventType == FIRDataEventTypeValue) {
// Handle FIRDataEventTypeValue specially because they shouldn't have prevName callbacks
NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
[self observeValueEventWithHandle:handle withBlock:block cancelCallback:cancelBlock];
return handle;
} else {
// Wrap up the userCallback so we can treat everything as a callback that has a prevName
fbt_void_datasnapshot userCallback = [block copy];
return [self observeEventType:eventType andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
if (userCallback != nil) {
userCallback(snapshot);
}
} withCancelBlock:cancelBlock];
}
}
- (FIRDatabaseHandle)observeEventType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block withCancelBlock:(fbt_void_nserror)cancelBlock {
[FValidation validateFrom:@"observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:" knownEventType:eventType];
if (eventType == FIRDataEventTypeValue) {
// TODO: This gets hit by observeSingleEventOfType. Need to fix.
/*
@throw [[NSException alloc] initWithName:@"InvalidEventTypeForObserver"
reason:@"(observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock:) Cannot use observeEventType:andPreviousSiblingKeyWithBlock:withCancelBlock: with FIRDataEventTypeValue. Use observeEventType:withBlock:withCancelBlock: instead."
userInfo:nil];
*/
}
NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
NSDictionary *callbacks = @{[NSNumber numberWithInteger:eventType]: [block copy]};
[self observeChildEventWithHandle:handle withCallbacks:callbacks cancelCallback:cancelBlock];
return handle;
}
// If we want to distinguish between value event listeners and child event listeners, like in the Java client, we can
// consider exporting this. If we do, add argument validation. Otherwise, arguments are validated in the public-facing
// portions of the API. Also, move the FIRDatabaseHandle logic.
- (void)observeValueEventWithHandle:(FIRDatabaseHandle)handle withBlock:(fbt_void_datasnapshot)block cancelCallback:(fbt_void_nserror)cancelBlock {
// Note that we don't need to copy the callbacks here, FEventRegistration callback properties set to copy
FValueEventRegistration *registration = [[FValueEventRegistration alloc] initWithRepo:self.repo
handle:handle
callback:block
cancelCallback:cancelBlock];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo addEventRegistration:registration forQuery:self.querySpec];
});
}
// Note: as with the above method, we may wish to expose this at some point.
- (void)observeChildEventWithHandle:(FIRDatabaseHandle)handle withCallbacks:(NSDictionary *)callbacks cancelCallback:(fbt_void_nserror)cancelBlock {
// Note that we don't need to copy the callbacks here, FEventRegistration callback properties set to copy
FChildEventRegistration *registration = [[FChildEventRegistration alloc] initWithRepo:self.repo
handle:handle
callbacks:callbacks
cancelCallback:cancelBlock];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo addEventRegistration:registration forQuery:self.querySpec];
});
}
- (void) removeObserverWithHandle:(FIRDatabaseHandle)handle {
FValueEventRegistration *event = [[FValueEventRegistration alloc] initWithRepo:self.repo
handle:handle
callback:nil
cancelCallback:nil];
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo removeEventRegistration:event forQuery:self.querySpec];
});
}
- (void) removeAllObservers {
[self removeObserverWithHandle:NSNotFound];
}
- (void)keepSynced:(BOOL)keepSynced {
if ([self.path.getFront isEqualToString:kDotInfoPrefix]) {
[NSException raise:NSInvalidArgumentException format:@"Can't keep query on .info tree synced (this already is the case)."];
}
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
[self.repo keepQuery:self.querySpec synced:keepSynced];
});
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(fbt_void_datasnapshot)block {
[self observeSingleEventOfType:eventType withBlock:block withCancelBlock:nil];
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block {
[self observeSingleEventOfType:eventType andPreviousSiblingKeyWithBlock:block withCancelBlock:nil];
}
- (void)observeSingleEventOfType:(FIRDataEventType)eventType withBlock:(fbt_void_datasnapshot)block withCancelBlock:(fbt_void_nserror)cancelBlock {
// XXX: user reported memory leak in method
// "When you copy a block, any references to other blocks from within that block are copied if necessary—an entire tree may be copied (from the top). If you have block variables and you reference a block from within the block, that block will be copied."
// http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
// So... we don't need to do this since inside the on: we copy this block off the stack to the heap.
// __block fbt_void_datasnapshot userCallback = [callback copy];
[self observeSingleEventOfType:eventType andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
if (block != nil) {
block(snapshot);
}
} withCancelBlock:cancelBlock];
}
/**
* Attaches a listener, waits for the first event, and then removes the listener
*/
- (void)observeSingleEventOfType:(FIRDataEventType)eventType andPreviousSiblingKeyWithBlock:(fbt_void_datasnapshot_nsstring)block withCancelBlock:(fbt_void_nserror)cancelBlock {
// XXX: user reported memory leak in method
// "When you copy a block, any references to other blocks from within that block are copied if necessary—an entire tree may be copied (from the top). If you have block variables and you reference a block from within the block, that block will be copied."
// http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW1
// So... we don't need to do this since inside the on: we copy this block off the stack to the heap.
// __block fbt_void_datasnapshot userCallback = [callback copy];
__block FIRDatabaseHandle handle;
__block BOOL firstCall = YES;
fbt_void_datasnapshot_nsstring callback = [block copy];
fbt_void_datasnapshot_nsstring wrappedCallback = ^(FIRDataSnapshot *snap, NSString* prevName) {
if (firstCall) {
firstCall = NO;
[self removeObserverWithHandle:handle];
callback(snap, prevName);
}
};
fbt_void_nserror cancelCallback = [cancelBlock copy];
handle = [self observeEventType:eventType andPreviousSiblingKeyWithBlock:wrappedCallback withCancelBlock:^(NSError* error){
[self removeObserverWithHandle:handle];
if (cancelCallback) {
cancelCallback(error);
}
}];
}
- (NSString *) description {
return [NSString stringWithFormat:@"(%@ %@)", self.path, self.queryParams.description];
}
- (FIRDatabaseReference *) ref {
return [[FIRDatabaseReference alloc] initWithRepo:self.repo path:self.path];
}
@end

View File

@@ -0,0 +1,134 @@
/*
* 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 "FIRMutableData.h"
#import "FIRMutableData_Private.h"
#import "FSnapshotHolder.h"
#import "FSnapshotUtilities.h"
#import "FChildrenNode.h"
#import "FTransformedEnumerator.h"
#import "FNamedNode.h"
#import "FIndexedNode.h"
@interface FIRMutableData ()
- (id) initWithPrefixPath:(FPath *)path andSnapshotHolder:(FSnapshotHolder *)snapshotHolder;
@property (strong, nonatomic) FSnapshotHolder* data;
@property (strong, nonatomic) FPath* prefixPath;
@end
@implementation FIRMutableData
@synthesize data;
@synthesize prefixPath;
- (id) initWithNode:(id<FNode>)node {
FSnapshotHolder* holder = [[FSnapshotHolder alloc] init];
FPath* path = [FPath empty];
[holder updateSnapshot:path withNewSnapshot:node];
return [self initWithPrefixPath:path andSnapshotHolder:holder];
}
- (id) initWithPrefixPath:(FPath *)path andSnapshotHolder:(FSnapshotHolder *)snapshotHolder {
self = [super init];
if (self) {
self.prefixPath = path;
self.data = snapshotHolder;
}
return self;
}
- (FIRMutableData *)childDataByAppendingPath:(NSString *)path {
FPath* wholePath = [self.prefixPath childFromString:path];
return [[FIRMutableData alloc] initWithPrefixPath:wholePath andSnapshotHolder:self.data];
}
- (FIRMutableData *) parent {
if ([self.prefixPath isEmpty]) {
return nil;
} else {
FPath* path = [self.prefixPath parent];
return [[FIRMutableData alloc] initWithPrefixPath:path andSnapshotHolder:self.data];
}
}
- (void) setValue:(id)aValue {
id<FNode> node = [FSnapshotUtilities nodeFrom:aValue withValidationFrom:@"setValue:"];
[self.data updateSnapshot:self.prefixPath withNewSnapshot:node];
}
- (void) setPriority:(id)aPriority {
id<FNode> node = [self.data getNode:self.prefixPath];
id<FNode> pri = [FSnapshotUtilities nodeFrom:aPriority];
node = [node updatePriority:pri];
[self.data updateSnapshot:self.prefixPath withNewSnapshot:node];
}
- (id) value {
return [[self.data getNode:self.prefixPath] val];
}
- (id) priority {
return [[[self.data getNode:self.prefixPath] getPriority] val];
}
- (BOOL) hasChildren {
id<FNode> node = [self.data getNode:self.prefixPath];
return ![node isLeafNode] && ![(FChildrenNode*)node isEmpty];
}
- (BOOL) hasChildAtPath:(NSString *)path {
id<FNode> node = [self.data getNode:self.prefixPath];
FPath* childPath = [[FPath alloc] initWith:path];
return ![[node getChild:childPath] isEmpty];
}
- (NSUInteger) childrenCount {
return [[self.data getNode:self.prefixPath] numChildren];
}
- (NSString *) key {
return [self.prefixPath getBack];
}
- (id<FNode>) nodeValue {
return [self.data getNode:self.prefixPath];
}
- (NSEnumerator<FIRMutableData *> *) children {
FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:self.nodeValue];
return [[FTransformedEnumerator alloc] initWithEnumerator:[indexedNode childEnumerator] andTransform:^id(FNamedNode *node) {
FPath* childPath = [self.prefixPath childFromString:node.name];
FIRMutableData * childData = [[FIRMutableData alloc] initWithPrefixPath:childPath andSnapshotHolder:self.data];
return childData;
}];
}
- (BOOL) isEqualToData:(FIRMutableData *)other {
return self.data == other.data && [[self.prefixPath description] isEqualToString:[other.prefixPath description]];
}
- (NSString *) description {
if (self.key == nil) {
return [NSString stringWithFormat:@"FIRMutableData (top-most transaction) %@ %@", self.key, self.value];
} else {
return [NSString stringWithFormat:@"FIRMutableData (%@) %@", self.key, self.value];
}
}
@end

View File

@@ -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 "FIRDatabaseReference.h"
#import "FIRServerValue.h"
@implementation FIRServerValue
+ (NSDictionary *) timestamp {
static NSDictionary *timestamp = nil;
if (timestamp == nil) {
timestamp = @{ @".sv": @"timestamp" };
}
return timestamp;
}
@end

View File

@@ -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 "FIRTransactionResult.h"
#import "FIRTransactionResult_Private.h"
@implementation FIRTransactionResult
@synthesize update;
@synthesize isSuccess;
+ (FIRTransactionResult *)successWithValue:(FIRMutableData *)value {
FIRTransactionResult * result = [[FIRTransactionResult alloc] init];
result.isSuccess = YES;
result.update = value;
return result;
}
+ (FIRTransactionResult *) abort {
FIRTransactionResult * result = [[FIRTransactionResult alloc] init];
result.isSuccess = NO;
result.update = nil;
return result;
}
@end

View File

@@ -0,0 +1,28 @@
/*
* 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 "FIRDataSnapshot.h"
#import "FIndexedNode.h"
#import "FTypedefs_Private.h"
@interface FIRDataSnapshot ()
// in _Private for testing purposes
@property (nonatomic, strong) FIndexedNode *node;
- (id)initWithRef:(FIRDatabaseReference *)ref indexedNode:(FIndexedNode *)node;
@end

View File

@@ -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 "FRepo.h"
#import "FPath.h"
#import "FRepoManager.h"
#import "FTypedefs_Private.h"
#import "FQueryParams.h"
#import "FIRDatabaseQuery.h"
@interface FIRDatabaseQuery ()
+ (dispatch_queue_t)sharedQueue;
- (id) initWithRepo:(FRepo *)repo path:(FPath *)path;
- (id) initWithRepo:(FRepo *)repo
path:(FPath *)path
params:(FQueryParams *)params
orderByCalled:(BOOL)orderByCalled
priorityMethodCalled:(BOOL)priorityMethodCalled;
@property (nonatomic, strong) FRepo* repo;
@property (nonatomic, strong) FPath* path;
@property (nonatomic, strong) FQueryParams *queryParams;
@property (nonatomic) BOOL orderByCalled;
@property (nonatomic) BOOL priorityMethodCalled;
- (FQuerySpec *)querySpec;
@end

View File

@@ -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.
*/
#import "FIRDatabaseReference.h"
#import "FTypedefs_Private.h"
#import "FIRDatabaseConfig.h"
#import "FRepo.h"
@interface FIRDatabaseReference ()
- (id)initWithConfig:(FIRDatabaseConfig *)config;
- (id)initWithRepo:(FRepo *)repo path:(FPath *)path;
// TODO: Update tests to not use this.
+ (FIRDatabaseConfig *)defaultConfig;
@end

View File

@@ -0,0 +1,28 @@
/*
* 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 "FIRDatabase.h"
@class FRepo;
@class FRepoInfo;
@class FIRDatabaseConfig;
@interface FIRDatabase ()
+ (NSString *) buildVersion;
+ (FIRDatabase *) createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config;
@end

View File

@@ -0,0 +1,26 @@
/*
* 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 "FIRMutableData.h"
#import "FNode.h"
@interface FIRMutableData ()
- (id) initWithNode:(id<FNode>)node;
- (id<FNode>) nodeValue;
- (BOOL) isEqualToData:(FIRMutableData *)other;
@end

View File

@@ -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 "FIRTransactionResult.h"
#import "FIRMutableData.h"
@interface FIRTransactionResult ()
@property (nonatomic) BOOL isSuccess;
@property (nonatomic, strong) FIRMutableData * update;
@end

View File

@@ -0,0 +1,56 @@
/*
* 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 __FTYPEDEFS_PRIVATE__
#define __FTYPEDEFS_PRIVATE__
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, FTransactionStatus) {
FTransactionInitializing, // 0
FTransactionRun, // 1
FTransactionSent, // 2
FTransactionCompleted, // 3
FTransactionSentNeedsAbort, // 4
FTransactionNeedsAbort // 5
};
@protocol FNode;
@class FPath;
@class FIRTransactionResult;
@class FIRMutableData;
@class FIRDataSnapshot;
@class FCompoundHash;
typedef void (^fbt_void_nserror_bool_datasnapshot) (NSError* error, BOOL committed, FIRDataSnapshot * snapshot);
typedef FIRTransactionResult * (^fbt_transactionresult_mutabledata) (FIRMutableData * currentData);
typedef void (^fbt_void_path_node) (FPath*, id<FNode>);
typedef void (^fbt_void_nsstring) (NSString *);
typedef BOOL (^fbt_bool_nsstring_node) (NSString *, id<FNode>);
typedef void (^fbt_void_path_node_marray) (FPath *, id<FNode>, NSMutableArray *);
typedef BOOL (^fbt_bool_void) (void);
typedef void (^fbt_void_nsstring_nsstring)(NSString *str1, NSString* str2);
typedef void (^fbt_void_nsstring_nserror)(NSString *str, NSError* error);
typedef BOOL (^fbt_bool_path)(FPath *str);
typedef void (^fbt_void_id)(id data);
typedef NSString* (^fbt_nsstring_void) (void);
typedef FCompoundHash* (^fbt_compoundhash_void) (void);
typedef NSArray* (^fbt_nsarray_nsstring_id)(NSString *status, id Data);
typedef NSArray* (^fbt_nsarray_nsstring)(NSString *status);
// WWDC 2012 session 712 starting in page 83 for saving blocks in properties (use @property (strong) type name).
#endif

View File

@@ -0,0 +1,190 @@
/*
* 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 Firebase_FConstants_h
#define Firebase_FConstants_h
#import <Foundation/Foundation.h>
#pragma mark -
#pragma mark Wire Protocol Envelope Constants
FOUNDATION_EXPORT NSString *const kFWPRequestType;
FOUNDATION_EXPORT NSString *const kFWPRequestTypeData;
FOUNDATION_EXPORT NSString *const kFWPRequestDataPayload;
FOUNDATION_EXPORT NSString *const kFWPRequestNumber;
FOUNDATION_EXPORT NSString *const kFWPRequestPayloadBody;
FOUNDATION_EXPORT NSString *const kFWPRequestError;
FOUNDATION_EXPORT NSString *const kFWPRequestAction;
FOUNDATION_EXPORT NSString *const kFWPResponseForRNData;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatus;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusOk;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionStatusDataStale;
FOUNDATION_EXPORT NSString *const kFWPResponseForActionData;
FOUNDATION_EXPORT NSString *const kFWPResponseDataWarnings;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerAction;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerPayloadBody;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdate;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataRangeMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerAuthRevoked;
FOUNDATION_EXPORT NSString *const kFWPASyncServerListenCancelled;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerSecurityDebug;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyPath; // {“a”: “d”, “b”: {“p”: “/”, “d”: “<data>”}}
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateStartPath;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateEndPath;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateRangeMerge;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataUpdateBodyTag;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataQueries;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeType;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerEnvelopeData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessage;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageType;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageData;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerDataMessage;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHello;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloTimestamp;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloVersion;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloConnectedHost;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerHelloSession;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageShutdown;
FOUNDATION_EXPORT NSString *const kFWPAsyncServerControlMessageReset;
#pragma mark -
#pragma mark Wire Protocol Payload Constants
FOUNDATION_EXPORT NSString *const kFWPRequestActionPut;
FOUNDATION_EXPORT NSString *const kFWPRequestActionMerge;
FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedListen;
FOUNDATION_EXPORT NSString *const kFWPRequestActionTaggedUnlisten;
FOUNDATION_EXPORT NSString *const kFWPRequestActionListen; // {"t": "d", "d": {"r": 1, "a": "l", "b": { "p": "/" } } }
FOUNDATION_EXPORT NSString *const kFWPRequestActionUnlisten;
FOUNDATION_EXPORT NSString *const kFWPRequestActionStats;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectPut;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectMerge;
FOUNDATION_EXPORT NSString *const kFWPRequestActionDisconnectCancel;
FOUNDATION_EXPORT NSString *const kFWPRequestActionAuth;
FOUNDATION_EXPORT NSString *const kFWPRequestActionUnauth;
FOUNDATION_EXPORT NSString *const kFWPRequestCredential;
FOUNDATION_EXPORT NSString *const kFWPRequestPath;
FOUNDATION_EXPORT NSString *const kFWPRequestCounters;
FOUNDATION_EXPORT NSString *const kFWPRequestQueries;
FOUNDATION_EXPORT NSString *const kFWPRequestTag;
FOUNDATION_EXPORT NSString *const kFWPRequestData;
FOUNDATION_EXPORT NSString *const kFWPRequestHash;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHash;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashPaths;
FOUNDATION_EXPORT NSString *const kFWPRequestCompoundHashHashes;
FOUNDATION_EXPORT NSString *const kFWPRequestStatus;
#pragma mark -
#pragma mark Websock Transport Constants
FOUNDATION_EXPORT NSString *const kWireProtocolVersionParam;
FOUNDATION_EXPORT NSString *const kWebsocketProtocolVersion;
FOUNDATION_EXPORT NSString *const kWebsocketServerKillPacket;
FOUNDATION_EXPORT const int kWebsocketMaxFrameSize;
FOUNDATION_EXPORT NSUInteger const kWebsocketKeepaliveInterval;
FOUNDATION_EXPORT NSUInteger const kWebsocketConnectTimeout;
FOUNDATION_EXPORT float const kPersistentConnReconnectMinDelay;
FOUNDATION_EXPORT float const kPersistentConnReconnectMaxDelay;
FOUNDATION_EXPORT float const kPersistentConnReconnectMultiplier;
FOUNDATION_EXPORT float const kPersistentConnSuccessfulConnectionEstablishedDelay;
#pragma mark -
#pragma mark Query / QueryParams constants
FOUNDATION_EXPORT NSString *const kQueryDefault;
FOUNDATION_EXPORT NSString *const kQueryDefaultObject;
FOUNDATION_EXPORT NSString *const kViewManagerDictConstView;
FOUNDATION_EXPORT NSString *const kFQPIndexStartValue;
FOUNDATION_EXPORT NSString *const kFQPIndexStartName;
FOUNDATION_EXPORT NSString *const kFQPIndexEndValue;
FOUNDATION_EXPORT NSString *const kFQPIndexEndName;
FOUNDATION_EXPORT NSString *const kFQPLimit;
FOUNDATION_EXPORT NSString *const kFQPViewFrom;
FOUNDATION_EXPORT NSString *const kFQPViewFromLeft;
FOUNDATION_EXPORT NSString *const kFQPViewFromRight;
FOUNDATION_EXPORT NSString *const kFQPIndex;
#pragma mark -
#pragma mark Interrupt Reasons
FOUNDATION_EXPORT NSString *const kFInterruptReasonServerKill;
FOUNDATION_EXPORT NSString *const kFInterruptReasonWaitingForOpen;
FOUNDATION_EXPORT NSString *const kFInterruptReasonRepoInterrupt;
FOUNDATION_EXPORT NSString *const kFInterruptReasonAuthExpired;
#pragma mark -
#pragma mark Payload constants
FOUNDATION_EXPORT NSString *const kPayloadPriority;
FOUNDATION_EXPORT NSString *const kPayloadValue;
FOUNDATION_EXPORT NSString *const kPayloadMetadataPrefix;
#pragma mark -
#pragma mark ServerValue constants
FOUNDATION_EXPORT NSString *const kServerValueSubKey;
FOUNDATION_EXPORT NSString *const kServerValuePriority;
#pragma mark -
#pragma mark .info/ constants
FOUNDATION_EXPORT NSString *const kDotInfoPrefix;
FOUNDATION_EXPORT NSString *const kDotInfoConnected;
FOUNDATION_EXPORT NSString *const kDotInfoServerTimeOffset;
#pragma mark -
#pragma mark ObjectiveC to JavaScript type constants
FOUNDATION_EXPORT NSString *const kJavaScriptObject;
FOUNDATION_EXPORT NSString *const kJavaScriptString;
FOUNDATION_EXPORT NSString *const kJavaScriptBoolean;
FOUNDATION_EXPORT NSString *const kJavaScriptNumber;
FOUNDATION_EXPORT NSString *const kJavaScriptNull;
FOUNDATION_EXPORT NSString *const kJavaScriptTrue;
FOUNDATION_EXPORT NSString *const kJavaScriptFalse;
#pragma mark -
#pragma mark Error handling constants
FOUNDATION_EXPORT NSString *const kFErrorDomain;
FOUNDATION_EXPORT NSUInteger const kFAuthError;
FOUNDATION_EXPORT NSString *const kFErrorWriteCanceled;
#pragma mark -
#pragma mark Validation Constants
FOUNDATION_EXPORT NSUInteger const kFirebaseMaxObjectDepth;
FOUNDATION_EXPORT const unsigned int kFirebaseMaxLeafSize;
#pragma mark -
#pragma mark Transaction Constants
FOUNDATION_EXPORT NSUInteger const kFTransactionMaxRetries;
FOUNDATION_EXPORT NSString *const kFTransactionTooManyRetries;
FOUNDATION_EXPORT NSString *const kFTransactionNoData;
FOUNDATION_EXPORT NSString *const kFTransactionSet;
FOUNDATION_EXPORT NSString *const kFTransactionDisconnect;
#endif

View File

@@ -0,0 +1,183 @@
/*
* 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 "FConstants.h"
#pragma mark -
#pragma mark Wire Protocol Envelope Constants
NSString *const kFWPRequestType = @"t";
NSString *const kFWPRequestTypeData = @"d";
NSString *const kFWPRequestDataPayload = @"d";
NSString *const kFWPRequestNumber = @"r";
NSString *const kFWPRequestPayloadBody = @"b";
NSString *const kFWPRequestError = @"error";
NSString *const kFWPRequestAction = @"a";
NSString *const kFWPResponseForRNData = @"b";
NSString *const kFWPResponseForActionStatus = @"s";
NSString *const kFWPResponseForActionStatusOk = @"ok";
NSString *const kFWPResponseForActionStatusDataStale = @"datastale";
NSString *const kFWPResponseForActionData = @"d";
NSString *const kFWPResponseDataWarnings = @"w";
NSString *const kFWPAsyncServerAction = @"a";
NSString *const kFWPAsyncServerPayloadBody = @"b";
NSString *const kFWPAsyncServerDataUpdate = @"d";
NSString *const kFWPAsyncServerDataMerge = @"m";
NSString *const kFWPAsyncServerDataRangeMerge = @"rm";
NSString *const kFWPAsyncServerAuthRevoked = @"ac";
NSString *const kFWPASyncServerListenCancelled = @"c";
NSString *const kFWPAsyncServerSecurityDebug = @"sd";
NSString *const kFWPAsyncServerDataUpdateBodyPath = @"p"; // {a: d, b: {p: /, d: <data>}}
NSString *const kFWPAsyncServerDataUpdateBodyData = @"d";
NSString *const kFWPAsyncServerDataUpdateStartPath = @"s";
NSString *const kFWPAsyncServerDataUpdateEndPath = @"e";
NSString *const kFWPAsyncServerDataUpdateRangeMerge = @"m";
NSString *const kFWPAsyncServerDataUpdateBodyTag = @"t";
NSString *const kFWPAsyncServerDataQueries = @"q";
NSString *const kFWPAsyncServerEnvelopeType = @"t";
NSString *const kFWPAsyncServerEnvelopeData = @"d";
NSString *const kFWPAsyncServerControlMessage = @"c";
NSString *const kFWPAsyncServerControlMessageType = @"t";
NSString *const kFWPAsyncServerControlMessageData = @"d";
NSString *const kFWPAsyncServerDataMessage = @"d";
NSString *const kFWPAsyncServerHello = @"h";
NSString *const kFWPAsyncServerHelloTimestamp = @"ts";
NSString *const kFWPAsyncServerHelloVersion = @"v";
NSString *const kFWPAsyncServerHelloConnectedHost = @"h";
NSString *const kFWPAsyncServerHelloSession = @"s";
NSString *const kFWPAsyncServerControlMessageShutdown = @"s";
NSString *const kFWPAsyncServerControlMessageReset = @"r";
#pragma mark -
#pragma mark Wire Protocol Payload Constants
NSString *const kFWPRequestActionPut = @"p";
NSString *const kFWPRequestActionMerge = @"m";
NSString *const kFWPRequestActionListen = @"l"; // {"t": "d", "d": {"r": 1, "a": "l", "b": { "p": "/" } } }
NSString *const kFWPRequestActionUnlisten = @"u";
NSString *const kFWPRequestActionStats = @"s";
NSString *const kFWPRequestActionTaggedListen = @"q";
NSString *const kFWPRequestActionTaggedUnlisten = @"n";
NSString *const kFWPRequestActionDisconnectPut = @"o";
NSString *const kFWPRequestActionDisconnectMerge = @"om";
NSString *const kFWPRequestActionDisconnectCancel = @"oc";
NSString *const kFWPRequestActionAuth = @"auth";
NSString *const kFWPRequestActionUnauth = @"unauth";
NSString *const kFWPRequestCredential = @"cred";
NSString *const kFWPRequestPath = @"p";
NSString *const kFWPRequestCounters = @"c";
NSString *const kFWPRequestQueries = @"q";
NSString *const kFWPRequestTag = @"t";
NSString *const kFWPRequestData = @"d";
NSString *const kFWPRequestHash = @"h";
NSString *const kFWPRequestCompoundHash = @"ch";
NSString *const kFWPRequestCompoundHashPaths = @"ps";
NSString *const kFWPRequestCompoundHashHashes = @"hs";
NSString *const kFWPRequestStatus = @"s";
#pragma mark -
#pragma mark Websock Transport Constants
NSString *const kWireProtocolVersionParam = @"v";
NSString *const kWebsocketProtocolVersion = @"5";
NSString *const kWebsocketServerKillPacket = @"kill";
const int kWebsocketMaxFrameSize = 16384;
NSUInteger const kWebsocketKeepaliveInterval = 45;
NSUInteger const kWebsocketConnectTimeout = 30;
float const kPersistentConnReconnectMinDelay = 1.0;
float const kPersistentConnReconnectMaxDelay = 30.0;
float const kPersistentConnReconnectMultiplier = 1.3f;
float const kPersistentConnSuccessfulConnectionEstablishedDelay = 30.0;
#pragma mark -
#pragma mark Query constants
NSString *const kQueryDefault = @"default";
NSString *const kQueryDefaultObject = @"{}";
NSString *const kViewManagerDictConstView = @"view";
NSString *const kFQPIndexStartValue = @"sp";
NSString *const kFQPIndexStartName = @"sn";
NSString *const kFQPIndexEndValue = @"ep";
NSString *const kFQPIndexEndName = @"en";
NSString *const kFQPLimit = @"l";
NSString *const kFQPViewFrom = @"vf";
NSString *const kFQPViewFromLeft = @"l";
NSString *const kFQPViewFromRight = @"r";
NSString *const kFQPIndex = @"i";
#pragma mark -
#pragma mark Interrupt Reasons
NSString *const kFInterruptReasonServerKill = @"server_kill";
NSString *const kFInterruptReasonWaitingForOpen = @"waiting_for_open";
NSString *const kFInterruptReasonRepoInterrupt = @"repo_interrupt";
#pragma mark -
#pragma mark Payload constants
NSString *const kPayloadPriority = @".priority";
NSString *const kPayloadValue = @".value";
NSString *const kPayloadMetadataPrefix = @".";
#pragma mark -
#pragma mark ServerValue constants
NSString *const kServerValueSubKey = @".sv";
NSString *const kServerValuePriority = @"timestamp";
#pragma mark -
#pragma mark .info/ constants
NSString *const kDotInfoPrefix = @".info";
NSString *const kDotInfoConnected = @"connected";
NSString *const kDotInfoServerTimeOffset = @"serverTimeOffset";
#pragma mark -
#pragma mark ObjectiveC to JavaScript type constants
NSString *const kJavaScriptObject = @"object";
NSString *const kJavaScriptString = @"string";
NSString *const kJavaScriptBoolean = @"boolean";
NSString *const kJavaScriptNumber = @"number";
NSString *const kJavaScriptNull = @"null";
NSString *const kJavaScriptTrue = @"true";
NSString *const kJavaScriptFalse = @"false";
#pragma mark -
#pragma mark Error handling constants
NSString *const kFErrorDomain = @"com.firebase";
NSUInteger const kFAuthError = 1;
NSString *const kFErrorWriteCanceled = @"write_canceled";
#pragma mark -
#pragma mark Validation Constants
NSUInteger const kFirebaseMaxObjectDepth = 1000;
const unsigned int kFirebaseMaxLeafSize = 1024 * 1024 * 10; // 10 MB
#pragma mark -
#pragma mark Transaction Constants
NSUInteger const kFTransactionMaxRetries = 25;
NSString *const kFTransactionTooManyRetries = @"maxretry";
NSString *const kFTransactionNoData = @"nodata";
NSString *const kFTransactionSet = @"set";
NSString *const kFTransactionDisconnect = @"disconnect";

View File

@@ -0,0 +1,40 @@
/*
* 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 "FNode.h"
@interface FCompoundHashBuilder : NSObject
- (FPath *)currentPath;
@end
typedef BOOL (^FCompoundHashSplitStrategy) (FCompoundHashBuilder *builder);
@interface FCompoundHash : NSObject
@property (nonatomic, strong, readonly) NSArray *posts;
@property (nonatomic, strong, readonly) NSArray *hashes;
+ (FCompoundHash *)fromNode:(id<FNode>)node;
+ (FCompoundHash *)fromNode:(id<FNode>)node splitStrategy:(FCompoundHashSplitStrategy)strategy;
@end

View File

@@ -0,0 +1,236 @@
/*
* 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 "FCompoundHash.h"
#import "FLeafNode.h"
#import "FStringUtilities.h"
#import "FSnapshotUtilities.h"
#import "FChildrenNode.h"
@interface FCompoundHashBuilder ()
@property (nonatomic, strong) FCompoundHashSplitStrategy splitStrategy;
@property (nonatomic, strong) NSMutableArray *currentPaths;
@property (nonatomic, strong) NSMutableArray *currentHashes;
@end
@implementation FCompoundHashBuilder {
// NOTE: We use the existence of this to know if we've started building a range (i.e. encountered a leaf node).
NSMutableString *optHashValueBuilder;
// The current path as a stack. This is used in combination with currentPathDepth to simultaneously store the
// last leaf node path. The depth is changed when descending and ascending, at the same time the current key
// is set for the current depth. Because the keys are left unchanged for ascending the path will also contain
// the path of the last visited leaf node (using lastLeafDepth elements)
NSMutableArray *currentPath;
NSInteger lastLeafDepth;
NSInteger currentPathDepth;
BOOL needsComma;
}
- (instancetype)initWithSplitStrategy:(FCompoundHashSplitStrategy)strategy {
self = [super init];
if (self != nil) {
self->_splitStrategy = strategy;
self->optHashValueBuilder = nil;
self->currentPath = [NSMutableArray array];
self->lastLeafDepth = -1;
self->currentPathDepth = 0;
self->needsComma = YES;
self->_currentPaths = [NSMutableArray array];
self->_currentHashes = [NSMutableArray array];
}
return self;
}
- (BOOL)isBuildingRange {
return self->optHashValueBuilder != nil;
}
- (NSUInteger)currentHashLength {
return self->optHashValueBuilder.length;
}
- (FPath *)currentPath {
return [self currentPathWithDepth:self->currentPathDepth];
}
- (FPath *)currentPathWithDepth:(NSInteger)depth {
NSArray *pieces = [self->currentPath subarrayWithRange:NSMakeRange(0, depth)];
return [[FPath alloc] initWithPieces:pieces andPieceNum:0];
}
- (void)enumerateCurrentPathToDepth:(NSInteger)depth withBlock:(void (^) (NSString *key))block {
for (NSInteger i = 0; i < depth; i++) {
block(self->currentPath[i]);
}
}
- (void)appendKey:(NSString *)key toString:(NSMutableString *)string {
[FSnapshotUtilities appendHashV2RepresentationForString:key toString:string];
}
- (void)ensureRange {
if (![self isBuildingRange]) {
optHashValueBuilder = [NSMutableString string];
[optHashValueBuilder appendString:@"("];
[self enumerateCurrentPathToDepth:self->currentPathDepth withBlock:^(NSString *key) {
[self appendKey:key toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
}];
self->needsComma = NO;
}
}
- (void)processLeaf:(FLeafNode *)leafNode {
[self ensureRange];
self->lastLeafDepth = self->currentPathDepth;
[FSnapshotUtilities appendHashRepresentationForLeafNode:leafNode
toString:self->optHashValueBuilder
hashVersion:FDataHashVersionV2];
self->needsComma = YES;
if (self.splitStrategy(self)) {
[self endRange];
}
}
- (void)startChild:(NSString *)key {
[self ensureRange];
if (self->needsComma) {
[self->optHashValueBuilder appendString:@","];
}
[self appendKey:key toString:self->optHashValueBuilder];
[self->optHashValueBuilder appendString:@":("];
if (self->currentPathDepth == currentPath.count) {
[self->currentPath addObject:key];
} else {
self->currentPath[self->currentPathDepth] = key;
}
self->currentPathDepth++;
self->needsComma = NO;
}
- (void)endChild {
self->currentPathDepth--;
if ([self isBuildingRange]) {
[self->optHashValueBuilder appendString:@")"];
}
self->needsComma = YES;
}
- (void)finishHashing {
NSAssert(self->currentPathDepth == 0, @"Can't finish hashing in the middle of processing a child");
if ([self isBuildingRange] ) {
[self endRange];
}
// Always close with the empty hash for the remaining range to allow simple appending
[self.currentHashes addObject:@""];
}
- (void)endRange {
NSAssert([self isBuildingRange], @"Can't end range without starting a range!");
// Add closing parenthesis for current depth
for (NSUInteger i = 0; i < currentPathDepth; i++) {
[self->optHashValueBuilder appendString:@")"];
}
[self->optHashValueBuilder appendString:@")"];
FPath *lastLeafPath = [self currentPathWithDepth:self->lastLeafDepth];
NSString *hash = [FStringUtilities base64EncodedSha1:self->optHashValueBuilder];
[self.currentHashes addObject:hash];
[self.currentPaths addObject:lastLeafPath];
self->optHashValueBuilder = nil;
}
@end
@interface FCompoundHash ()
@property (nonatomic, strong, readwrite) NSArray *posts;
@property (nonatomic, strong, readwrite) NSArray *hashes;
@end
@implementation FCompoundHash
- (id)initWithPosts:(NSArray *)posts hashes:(NSArray *)hashes {
self = [super init];
if (self != nil) {
if (posts.count != hashes.count - 1) {
[NSException raise:NSInvalidArgumentException format:@"Number of posts need to be n-1 for n hashes in FCompoundHash"];
}
self.posts = posts;
self.hashes = hashes;
}
return self;
}
+ (FCompoundHashSplitStrategy)simpleSizeSplitStrategyForNode:(id<FNode>)node {
NSUInteger estimatedSize = [FSnapshotUtilities estimateSerializedNodeSize:node];
// Splits for
// 1k -> 512 (2 parts)
// 5k -> 715 (7 parts)
// 100k -> 3.2k (32 parts)
// 500k -> 7k (71 parts)
// 5M -> 23k (228 parts)
NSUInteger splitThreshold = MAX(512, (NSUInteger)sqrt(estimatedSize * 100));
return ^BOOL(FCompoundHashBuilder *builder) {
// Never split on priorities
return [builder currentHashLength] > splitThreshold && ![[[builder currentPath] getBack] isEqualToString:@".priority"];
};
}
+ (FCompoundHash *)fromNode:(id<FNode>)node {
return [FCompoundHash fromNode:node splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
}
+ (FCompoundHash *)fromNode:(id<FNode>)node splitStrategy:(FCompoundHashSplitStrategy)strategy {
if ([node isEmpty]) {
return [[FCompoundHash alloc] initWithPosts:@[] hashes:@[@""]];
} else {
FCompoundHashBuilder *builder = [[FCompoundHashBuilder alloc] initWithSplitStrategy:strategy];
[FCompoundHash processNode:node builder:builder];
[builder finishHashing];
return [[FCompoundHash alloc] initWithPosts:builder.currentPaths hashes:builder.currentHashes];
}
}
+ (void)processNode:(id<FNode>)node builder:(FCompoundHashBuilder *)builder {
if ([node isLeafNode]) {
[builder processLeaf:node];
} else {
NSAssert(![node isEmpty], @"Can't calculate hash on empty node!");
FChildrenNode *childrenNode = (FChildrenNode *)node;
[childrenNode enumerateChildrenAndPriorityUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
[builder startChild:key];
[self processNode:node builder:builder];
[builder endChild];
}];
}
}
@end

View File

@@ -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 "FTypedefs_Private.h"
@class FQuerySpec;
@protocol FSyncTreeHash;
typedef NSArray* (^fbt_startListeningBlock)(FQuerySpec *query,
NSNumber *tagId,
id<FSyncTreeHash> hash,
fbt_nsarray_nsstring onComplete);
typedef void (^fbt_stopListeningBlock)(FQuerySpec *query, NSNumber *tagId);
@interface FListenProvider : NSObject
@property (nonatomic, copy) fbt_startListeningBlock startListening;
@property (nonatomic, copy) fbt_stopListeningBlock stopListening;
@end

View File

@@ -0,0 +1,26 @@
/*
* 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 "FListenProvider.h"
#import "FIRDatabaseQuery.h"
@implementation FListenProvider
@synthesize startListening;
@synthesize stopListening;
@end

View File

@@ -0,0 +1,78 @@
/*
* 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 "FConnection.h"
#import "FRepoInfo.h"
#import "FTypedefs.h"
#import "FTypedefs_Private.h"
@protocol FPersistentConnectionDelegate;
@protocol FSyncTreeHash;
@class FQuerySpec;
@class FIRDatabaseConfig;
@interface FPersistentConnection : NSObject <FConnectionDelegate>
@property (nonatomic, weak) id <FPersistentConnectionDelegate> delegate;
@property (nonatomic) BOOL pauseWrites;
- (id)initWithRepoInfo:(FRepoInfo *)repoInfo
dispatchQueue:(dispatch_queue_t)queue
config:(FIRDatabaseConfig *)config;
- (void)open;
- (void) putData:(id)data forPath:(NSString *)pathString withHash:(NSString *)hash withCallback:(fbt_void_nsstring_nsstring)onComplete;
- (void) mergeData:(id)data forPath:(NSString *)pathString withCallback:(fbt_void_nsstring_nsstring)onComplete;
- (void) listen:(FQuerySpec *)query
tagId:(NSNumber *)tagId
hash:(id<FSyncTreeHash>)hash
onComplete:(fbt_void_nsstring)onComplete;
- (void) unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId;
- (void) refreshAuthToken:(NSString *)token;
- (void) onDisconnectPutData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback;
- (void) onDisconnectMergeData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback;
- (void) onDisconnectCancelPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback;
- (void) ackPuts;
- (void) purgeOutstandingWrites;
- (void) interruptForReason:(NSString *)reason;
- (void) resumeForReason:(NSString *)reason;
- (BOOL) isInterruptedForReason:(NSString *)reason;
// FConnection delegate methods
- (void)onReady:(FConnection *)fconnection atTime:(NSNumber *)timestamp sessionID:(NSString *)sessionID;
- (void)onDataMessage:(FConnection *)fconnection withMessage:(NSDictionary *)message;
- (void)onDisconnect:(FConnection *)fconnection withReason:(FDisconnectReason)reason;
- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason;
// Testing methods
- (NSDictionary *) dumpListens;
@end
@protocol FPersistentConnectionDelegate <NSObject>
- (void)onDataUpdate:(FPersistentConnection *)fpconnection forPath:(NSString *)pathString message:(id)message isMerge:(BOOL)isMerge tagId:(NSNumber *)tagId;
- (void)onRangeMerge:(NSArray *)ranges forPath:(NSString *)path tagId:(NSNumber *)tag;
- (void)onConnect:(FPersistentConnection *)fpconnection;
- (void)onDisconnect:(FPersistentConnection *)fpconnection;
- (void)onServerInfoUpdate:(FPersistentConnection *)fpconnection updates:(NSDictionary *)updates;
@end

View File

@@ -0,0 +1,952 @@
/*
* 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 <FirebaseCore/FIRLogger.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <netinet/in.h>
#import <dlfcn.h>
#import "FIRDatabaseReference.h"
#import "FPersistentConnection.h"
#import "FConstants.h"
#import "FAtomicNumber.h"
#import "FQueryParams.h"
#import "FTupleOnDisconnect.h"
#import "FTupleCallbackStatus.h"
#import "FQuerySpec.h"
#import "FIndex.h"
#import "FIRDatabaseConfig.h"
#import "FIRDatabaseConfig_Private.h"
#import "FSnapshotUtilities.h"
#import "FRangeMerge.h"
#import "FCompoundHash.h"
#import "FSyncTree.h"
#import "FIRRetryHelper.h"
#import "FAuthTokenProvider.h"
#import "FUtilities.h"
@interface FOutstandingQuery : NSObject
@property (nonatomic, strong) FQuerySpec* query;
@property (nonatomic, strong) NSNumber *tagId;
@property (nonatomic, strong) id<FSyncTreeHash> syncTreeHash;
@property (nonatomic, copy) fbt_void_nsstring onComplete;
@end
@implementation FOutstandingQuery
@end
@interface FOutstandingPut : NSObject
@property (nonatomic, strong) NSString *action;
@property (nonatomic, strong) NSDictionary *request;
@property (nonatomic, copy) fbt_void_nsstring_nsstring onCompleteBlock;
@property (nonatomic) BOOL sent;
@end
@implementation FOutstandingPut
@end
typedef enum {
ConnectionStateDisconnected,
ConnectionStateGettingToken,
ConnectionStateConnecting,
ConnectionStateAuthenticating,
ConnectionStateConnected
} ConnectionState;
@interface FPersistentConnection () {
ConnectionState connectionState;
BOOL firstConnection;
NSTimeInterval reconnectDelay;
NSTimeInterval lastConnectionAttemptTime;
NSTimeInterval lastConnectionEstablishedTime;
SCNetworkReachabilityRef reachability;
}
- (int) getNextRequestNumber;
- (void) onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body;
- (void) handleTimestamp:(NSNumber *)timestamp;
- (void) sendOnDisconnectAction:(NSString *)action forPath:(NSString *)pathString withData:(id)data andCallback:(fbt_void_nsstring_nsstring)callback;
@property (nonatomic, strong) FConnection* realtime;
@property (nonatomic, strong) NSMutableDictionary* listens;
@property (nonatomic, strong) NSMutableDictionary* outstandingPuts;
@property (nonatomic, strong) NSMutableArray* onDisconnectQueue;
@property (nonatomic, strong) FRepoInfo* repoInfo;
@property (nonatomic, strong) FAtomicNumber* putCounter;
@property (nonatomic, strong) FAtomicNumber* requestNumber;
@property (nonatomic, strong) NSMutableDictionary* requestCBHash;
@property (nonatomic, strong) FIRDatabaseConfig *config;
@property (nonatomic) NSUInteger unackedListensCount;
@property (nonatomic, strong) NSMutableArray *putsToAck;
@property (nonatomic, strong) dispatch_queue_t dispatchQueue;
@property (nonatomic, strong) NSString* lastSessionID;
@property (nonatomic, strong) NSMutableSet *interruptReasons;
@property (nonatomic, strong) FIRRetryHelper *retryHelper;
@property (nonatomic, strong) id<FAuthTokenProvider> authTokenProvider;
@property (nonatomic, strong) NSString *authToken;
@property (nonatomic) BOOL forceAuthTokenRefresh;
@property (nonatomic) NSUInteger currentFetchTokenAttempt;
@end
@implementation FPersistentConnection
- (id)initWithRepoInfo:(FRepoInfo *)repoInfo dispatchQueue:(dispatch_queue_t)dispatchQueue config:(FIRDatabaseConfig *)config {
self = [super init];
if (self) {
self->_config = config;
self->_repoInfo = repoInfo;
self->_dispatchQueue = dispatchQueue;
self->_authTokenProvider = config.authTokenProvider;
NSAssert(self->_authTokenProvider != nil, @"Expected auth token provider");
self.interruptReasons = [NSMutableSet set];
self.listens = [[NSMutableDictionary alloc] init];
self.outstandingPuts = [[NSMutableDictionary alloc] init];
self.onDisconnectQueue = [[NSMutableArray alloc] init];
self.putCounter = [[FAtomicNumber alloc] init];
self.requestNumber = [[FAtomicNumber alloc] init];
self.requestCBHash = [[NSMutableDictionary alloc] init];
self.unackedListensCount = 0;
self.putsToAck = [NSMutableArray array];
connectionState = ConnectionStateDisconnected;
firstConnection = YES;
reconnectDelay = kPersistentConnReconnectMinDelay;
self->_retryHelper = [[FIRRetryHelper alloc] initWithDispatchQueue:dispatchQueue
minRetryDelayAfterFailure:kPersistentConnReconnectMinDelay
maxRetryDelay:kPersistentConnReconnectMaxDelay
retryExponent:kPersistentConnReconnectMultiplier
jitterFactor:0.7];
[self setupNotifications];
// Make sure we don't actually connect until open is called
[self interruptForReason:kFInterruptReasonWaitingForOpen];
}
// nb: The reason establishConnection isn't called here like the JS version is because
// callers need to set the delegate first. The ctor can be modified to accept the delegate
// but that deviates from normal ios conventions. After the delegate has been set, the caller
// is responsible for calling establishConnection:
return self;
}
- (void) dealloc {
if (reachability) {
// Unschedule the notifications
SCNetworkReachabilitySetDispatchQueue(reachability, NULL);
CFRelease(reachability);
}
}
#pragma mark -
#pragma mark Public methods
- (void) open {
[self resumeForReason:kFInterruptReasonWaitingForOpen];
}
/**
* Note that the listens dictionary has a type of Map[String (pathString), Map[FQueryParams, FOutstandingQuery]]
*
* This means, for each path we care about, there are sets of queryParams that correspond to an FOutstandingQuery object.
* There can be multiple sets at a path since we overlap listens for a short time while adding or removing a query from a
* location in the tree.
*/
- (void) listen:(FQuerySpec *)query
tagId:(NSNumber *)tagId
hash:(id<FSyncTreeHash>)hash
onComplete:(fbt_void_nsstring)onComplete {
FFLog(@"I-RDB034001", @"Listen called for %@", query);
NSAssert(self.listens[query] == nil, @"listen() called twice for the same query");
NSAssert(query.isDefault || !query.loadsAllData, @"listen called for non-default but complete query");
FOutstandingQuery* outstanding = [[FOutstandingQuery alloc] init];
outstanding.query = query;
outstanding.tagId = tagId;
outstanding.syncTreeHash = hash;
outstanding.onComplete = onComplete;
[self.listens setObject:outstanding forKey:query];
if ([self connected]) {
[self sendListen:outstanding];
}
}
- (void) putData:(id)data forPath:(NSString *)pathString withHash:(NSString *)hash withCallback:(fbt_void_nsstring_nsstring)onComplete {
[self putInternal:data forAction:kFWPRequestActionPut forPath:pathString withHash:hash withCallback:onComplete];
}
- (void) mergeData:(id)data forPath:(NSString *)pathString withCallback:(fbt_void_nsstring_nsstring)onComplete {
[self putInternal:data forAction:kFWPRequestActionMerge forPath:pathString withHash:nil withCallback:onComplete];
}
- (void) onDisconnectPutData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
if ([self canSendWrites]) {
[self sendOnDisconnectAction:kFWPRequestActionDisconnectPut forPath:[path description] withData:data andCallback:callback];
} else {
FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
tuple.pathString = [path description];
tuple.action = kFWPRequestActionDisconnectPut;
tuple.data = data;
tuple.onComplete = callback;
[self.onDisconnectQueue addObject:tuple];
}
}
- (void) onDisconnectMergeData:(id)data forPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
if ([self canSendWrites]) {
[self sendOnDisconnectAction:kFWPRequestActionDisconnectMerge forPath:[path description] withData:data andCallback:callback];
} else {
FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
tuple.pathString = [path description];
tuple.action = kFWPRequestActionDisconnectMerge;
tuple.data = data;
tuple.onComplete = callback;
[self.onDisconnectQueue addObject:tuple];
}
}
- (void) onDisconnectCancelPath:(FPath *)path withCallback:(fbt_void_nsstring_nsstring)callback {
if ([self canSendWrites]) {
[self sendOnDisconnectAction:kFWPRequestActionDisconnectCancel forPath:[path description] withData:[NSNull null] andCallback:callback];
} else {
FTupleOnDisconnect* tuple = [[FTupleOnDisconnect alloc] init];
tuple.pathString = [path description];
tuple.action = kFWPRequestActionDisconnectCancel;
tuple.data = [NSNull null];
tuple.onComplete = callback;
[self.onDisconnectQueue addObject:tuple];
}
}
- (void) unlisten:(FQuerySpec *)query tagId:(NSNumber *)tagId {
FPath *path = query.path;
FFLog(@"I-RDB034002", @"Unlistening for %@", query);
NSArray *outstanding = [self removeListen:query];
if (outstanding.count > 0 && [self connected]) {
[self sendUnlisten:path queryParams:query.params tagId:tagId];
}
}
- (void) refreshAuthToken:(NSString *)token {
self.authToken = token;
if ([self connected]) {
if (token != nil) {
[self sendAuthAndRestoreStateAfterComplete:NO];
} else {
[self sendUnauth];
}
}
}
#pragma mark -
#pragma mark Connection status
- (BOOL)connected {
return self->connectionState == ConnectionStateAuthenticating || self->connectionState == ConnectionStateConnected;
}
- (BOOL)canSendWrites {
return self->connectionState == ConnectionStateConnected;
}
#pragma mark -
#pragma mark FConnection delegate methods
- (void)onReady:(FConnection *)fconnection atTime:(NSNumber *)timestamp sessionID:(NSString *)sessionID {
FFLog(@"I-RDB034003", @"On ready");
lastConnectionEstablishedTime = [[NSDate date] timeIntervalSince1970];
[self handleTimestamp:timestamp];
if (firstConnection) {
[self sendConnectStats];
}
[self restoreAuth];
firstConnection = NO;
self.lastSessionID = sessionID;
dispatch_async(self.dispatchQueue, ^{
[self.delegate onConnect:self];
});
}
- (void)onDataMessage:(FConnection *)fconnection withMessage:(NSDictionary *)message {
if (message[kFWPRequestNumber] != nil) {
// this is a response to a request we sent
NSNumber* rn = [NSNumber numberWithInt:[[message objectForKey:kFWPRequestNumber] intValue]];
if ([self.requestCBHash objectForKey:rn]) {
void (^callback)(NSDictionary*) = [self.requestCBHash objectForKey:rn];
[self.requestCBHash removeObjectForKey:rn];
if (callback) {
//dispatch_async(self.dispatchQueue, ^{
callback([message objectForKey:kFWPResponseForRNData]);
//});
}
}
} else if (message[kFWPRequestError] != nil) {
NSString* error = [message objectForKey:kFWPRequestError];
@throw [[NSException alloc] initWithName:@"FirebaseDatabaseServerError" reason:error userInfo:nil];
} else if (message[kFWPAsyncServerAction] != nil) {
// this is a server push of some sort
NSString* action = [message objectForKey:kFWPAsyncServerAction];
NSDictionary* body = [message objectForKey:kFWPAsyncServerPayloadBody];
[self onDataPushWithAction:action andBody:body];
}
}
- (void)onDisconnect:(FConnection *)fconnection withReason:(FDisconnectReason)reason {
FFLog(@"I-RDB034004", @"Got on disconnect due to %s", (reason == DISCONNECT_REASON_SERVER_RESET) ? "server_reset" : "other");
connectionState = ConnectionStateDisconnected;
// Drop the realtime connection
self.realtime = nil;
[self cancelSentTransactions];
[self.requestCBHash removeAllObjects];
self.unackedListensCount = 0;
if ([self shouldReconnect]) {
NSTimeInterval timeSinceLastConnectSucceeded = [[NSDate date] timeIntervalSince1970] - lastConnectionEstablishedTime;
BOOL lastConnectionWasSuccessful;
if (lastConnectionEstablishedTime > 0) {
lastConnectionWasSuccessful = timeSinceLastConnectSucceeded > kPersistentConnSuccessfulConnectionEstablishedDelay;
} else {
lastConnectionWasSuccessful = NO;
}
if (reason == DISCONNECT_REASON_SERVER_RESET || lastConnectionWasSuccessful) {
[self.retryHelper signalSuccess];
}
[self tryScheduleReconnect];
}
lastConnectionEstablishedTime = 0;
[self.delegate onDisconnect:self];
}
- (void)onKill:(FConnection *)fconnection withReason:(NSString *)reason {
FFWarn(@"I-RDB034005", @"Firebase Database connection was forcefully killed by the server. Will not attempt reconnect. Reason: %@", reason);
[self interruptForReason:kFInterruptReasonServerKill];
}
#pragma mark -
#pragma mark Connection handling methods
- (void) interruptForReason:(NSString *)reason {
FFLog(@"I-RDB034006", @"Connection interrupted for: %@", reason);
[self.interruptReasons addObject:reason];
if (self.realtime) {
// Will call onDisconnect and set the connection state to Disconnected
[self.realtime close];
self.realtime = nil;
} else {
[self.retryHelper cancel];
self->connectionState = ConnectionStateDisconnected;
}
// Reset timeouts
[self.retryHelper signalSuccess];
}
- (void) resumeForReason:(NSString *)reason {
FFLog(@"I-RDB034007", @"Connection no longer interrupted for: %@", reason);
[self.interruptReasons removeObject:reason];
if ([self shouldReconnect] && connectionState == ConnectionStateDisconnected) {
[self tryScheduleReconnect];
}
}
- (BOOL) shouldReconnect {
return self.interruptReasons.count == 0;
}
- (BOOL) isInterruptedForReason:(NSString *)reason {
return [self.interruptReasons containsObject:reason];
}
#pragma mark -
#pragma mark Private methods
- (void) tryScheduleReconnect {
if ([self shouldReconnect]) {
NSAssert(self->connectionState == ConnectionStateDisconnected,
@"Not in disconnected state: %d", self->connectionState);
BOOL forceRefresh = self.forceAuthTokenRefresh;
self.forceAuthTokenRefresh = NO;
FFLog(@"I-RDB034008", @"Scheduling connection attempt");
[self.retryHelper retry:^{
FFLog(@"I-RDB034009", @"Trying to fetch auth token");
NSAssert(self->connectionState == ConnectionStateDisconnected,
@"Not in disconnected state: %d", self->connectionState);
self->connectionState = ConnectionStateGettingToken;
self.currentFetchTokenAttempt++;
NSUInteger thisFetchTokenAttempt = self.currentFetchTokenAttempt;
[self.authTokenProvider fetchTokenForcingRefresh:forceRefresh withCallback:^(NSString *token, NSError *error) {
if (thisFetchTokenAttempt == self.currentFetchTokenAttempt) {
if (error != nil) {
self->connectionState = ConnectionStateDisconnected;
FFLog(@"I-RDB034010", @"Error fetching token: %@", error);
[self tryScheduleReconnect];
} else {
// Someone could have interrupted us while fetching the token,
// marking the connection as Disconnected
if (self->connectionState == ConnectionStateGettingToken) {
FFLog(@"I-RDB034011", @"Successfully fetched token, opening connection");
[self openNetworkConnectionWithToken:token];
} else {
NSAssert(self->connectionState == ConnectionStateDisconnected,
@"Expected connection state disconnected, but got %d", self->connectionState);
FFLog(@"I-RDB034012", @"Not opening connection after token refresh, because connection was set to disconnected.");
}
}
} else {
FFLog(@"I-RDB034013", @"Ignoring fetch token result, because this was not the latest attempt.");
}
}];
}];
}
}
- (void) openNetworkConnectionWithToken:(NSString *)token {
NSAssert(self->connectionState == ConnectionStateGettingToken,
@"Trying to open network connection while in wrong state: %d", self->connectionState);
self.authToken = token;
self->connectionState = ConnectionStateConnecting;
self.realtime = [[FConnection alloc] initWith:self.repoInfo
andDispatchQueue:self.dispatchQueue
lastSessionID:self.lastSessionID];
self.realtime.delegate = self;
[self.realtime open];
}
static void reachabilityCallback(SCNetworkReachabilityRef ref, SCNetworkReachabilityFlags flags, void* info) {
if (flags & kSCNetworkReachabilityFlagsReachable) {
FFLog(@"I-RDB034014", @"Network became reachable. Trigger a connection attempt");
FPersistentConnection* self = (__bridge FPersistentConnection *)info;
// Reset reconnect delay
[self.retryHelper signalSuccess];
if (self->connectionState == ConnectionStateDisconnected) {
[self tryScheduleReconnect];
}
} else {
FFLog(@"I-RDB034015", @"Network is not reachable");
}
}
- (void) enteringForeground {
dispatch_async(self.dispatchQueue, ^{
// Reset reconnect delay
[self.retryHelper signalSuccess];
if (self->connectionState == ConnectionStateDisconnected) {
[self tryScheduleReconnect];
}
});
}
- (void) setupNotifications {
NSString * const* foregroundConstant = (NSString * const *) dlsym(RTLD_DEFAULT, "UIApplicationWillEnterForegroundNotification");
if (foregroundConstant) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(enteringForeground)
name:*foregroundConstant
object:nil];
}
// An empty address is interpreted a generic internet access
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
SCNetworkReachabilityContext ctx = {0, (__bridge void *)(self), NULL, NULL, NULL};
if (SCNetworkReachabilitySetCallback(reachability, reachabilityCallback, &ctx)) {
SCNetworkReachabilitySetDispatchQueue(reachability, self.dispatchQueue);
} else {
FFLog(@"I-RDB034016", @"Failed to set up network reachability monitoring");
CFRelease(reachability);
reachability = NULL;
}
}
- (void) sendAuthAndRestoreStateAfterComplete:(BOOL)restoreStateAfterComplete {
NSAssert([self connected], @"Must be connected to send auth");
NSAssert(self.authToken != nil, @"Can't send auth if there is no credential");
NSDictionary* requestData = @{kFWPRequestCredential: self.authToken};
[self sendAction:kFWPRequestActionAuth body:requestData sensitive:YES callback:^(NSDictionary *data) {
self->connectionState = ConnectionStateConnected;
NSString* status = [data objectForKey:kFWPResponseForActionStatus];
id responseData = [data objectForKey:kFWPResponseForActionData];
if (responseData == nil) {
responseData = @"error";
}
BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk];
if (statusOk) {
if (restoreStateAfterComplete) {
[self restoreState];
}
} else {
self.authToken = nil;
self.forceAuthTokenRefresh = YES;
if ([status isEqualToString:@"expired_token"]) {
FFLog(@"I-RDB034017", @"Authentication failed: %@ (%@)", status, responseData);
} else {
FFWarn(@"I-RDB034018", @"Authentication failed: %@ (%@)", status, responseData);
}
[self.realtime close];
}
}];
}
- (void) sendUnauth {
[self sendAction:kFWPRequestActionUnauth body:@{} sensitive:NO callback:nil];
}
- (void) onAuthRevokedWithStatus:(NSString *)status andReason:(NSString *)reason {
// This might be for an earlier token than we just recently sent. But since we need to close the connection anyways,
// we can set it to null here and we will refresh the token later on reconnect
if ([status isEqualToString:@"expired_token"]) {
FFLog(@"I-RDB034019", @"Auth token revoked: %@ (%@)", status, reason);
} else {
FFWarn(@"I-RDB034020", @"Auth token revoked: %@ (%@)", status, reason);
}
self.authToken = nil;
self.forceAuthTokenRefresh = YES;
// Try reconnecting on auth revocation
[self.realtime close];
}
- (void) onListenRevoked:(FPath *)path {
NSArray *queries = [self removeAllListensAtPath:path];
for (FOutstandingQuery* query in queries) {
query.onComplete(@"permission_denied");
}
}
- (void) sendOnDisconnectAction:(NSString *)action forPath:(NSString *)pathString withData:(id)data andCallback:(fbt_void_nsstring_nsstring)callback {
NSDictionary* request = @{kFWPRequestPath: pathString, kFWPRequestData: data};
FFLog(@"I-RDB034021", @"onDisconnect %@: %@", action, request);
[self sendAction:action
body:request
sensitive:NO
callback:^(NSDictionary *data) {
NSString* status = [data objectForKey:kFWPResponseForActionStatus];
NSString* errorReason = [data objectForKey:kFWPResponseForActionData];
callback(status, errorReason);
}];
}
- (void) sendPut:(NSNumber *) index {
NSAssert([self canSendWrites], @"sendPut called when not able to send writes");
FOutstandingPut* put = self.outstandingPuts[index];
assert(put != nil);
fbt_void_nsstring_nsstring onComplete = put.onCompleteBlock;
// Do not async this block; copying the block insinde sendAction: doesn't happen in time (or something) so coredumps
put.sent = YES;
[self sendAction:put.action
body:put.request
sensitive:NO
callback:^(NSDictionary* data) {
FOutstandingPut *currentPut = self.outstandingPuts[index];
if (currentPut == put) {
[self.outstandingPuts removeObjectForKey:index];
if (onComplete != nil) {
NSString *status = [data objectForKey:kFWPResponseForActionStatus];
NSString *errorReason = [data objectForKey:kFWPResponseForActionData];
if (self.unackedListensCount == 0) {
onComplete(status, errorReason);
} else {
FTupleCallbackStatus *putToAck = [[FTupleCallbackStatus alloc] init];
putToAck.block = onComplete;
putToAck.status = status;
putToAck.errorReason = errorReason;
[self.putsToAck addObject:putToAck];
}
}
} else {
FFLog(@"I-RDB034022", @"Ignoring on complete for put %@ because it was already removed", index);
}
}];
}
- (void) sendUnlisten:(FPath *)path queryParams:(FQueryParams *)queryParams tagId:(NSNumber *)tagId {
FFLog(@"I-RDB034023", @"Unlisten on %@ for %@", path, queryParams);
NSMutableDictionary* request = [NSMutableDictionary dictionaryWithObjectsAndKeys:[path toString], kFWPRequestPath, nil];
if (tagId) {
[request setObject:queryParams.wireProtocolParams forKey:kFWPRequestQueries];
[request setObject:tagId forKey:kFWPRequestTag];
}
[self sendAction:kFWPRequestActionTaggedUnlisten
body:request
sensitive:NO
callback:nil];
}
- (void) putInternal:(id)data forAction:(NSString *)action forPath:(NSString *)pathString withHash:(NSString *)hash withCallback:(fbt_void_nsstring_nsstring)onComplete {
NSMutableDictionary *request = [NSMutableDictionary dictionaryWithObjectsAndKeys:
pathString, kFWPRequestPath,
data, kFWPRequestData, nil];
if(hash) {
[request setObject:hash forKey:kFWPRequestHash];
}
FOutstandingPut *put = [[FOutstandingPut alloc] init];
put.action = action;
put.request = request;
put.onCompleteBlock = onComplete;
put.sent = NO;
NSNumber* index = [self.putCounter getAndIncrement];
self.outstandingPuts[index] = put;
if ([self canSendWrites]) {
FFLog(@"I-RDB034024", @"Was connected, and added as index: %@", index);
[self sendPut:index];
}
else {
FFLog(@"I-RDB034025", @"Wasn't connected or writes paused, so added to outstanding puts only. Path: %@", pathString);
}
}
- (void) sendListen:(FOutstandingQuery *)listenSpec {
FQuerySpec *query = listenSpec.query;
FFLog(@"I-RDB034026", @"Listen for %@", query);
NSMutableDictionary *request = [NSMutableDictionary dictionaryWithObject:[query.path toString] forKey:kFWPRequestPath];
// Only bother to send query if it's non-default
if (listenSpec.tagId != nil) {
[request setObject:[query.params wireProtocolParams] forKey:kFWPRequestQueries];
[request setObject:listenSpec.tagId forKey:kFWPRequestTag];
}
[request setObject:[listenSpec.syncTreeHash simpleHash] forKey:kFWPRequestHash];
if ([listenSpec.syncTreeHash includeCompoundHash]) {
FCompoundHash *compoundHash = [listenSpec.syncTreeHash compoundHash];
NSMutableArray *posts = [NSMutableArray array];
for (FPath *path in compoundHash.posts) {
[posts addObject:path.wireFormat];
}
request[kFWPRequestCompoundHash] = @{ kFWPRequestCompoundHashHashes: compoundHash.hashes,
kFWPRequestCompoundHashPaths: posts };
}
fbt_void_nsdictionary onResponse = ^(NSDictionary *response) {
FFLog(@"I-RDB034027", @"Listen response %@", response);
// warn in any case, even if the listener was removed
[self warnOnListenWarningsForQuery:query payload:response[kFWPResponseForActionData]];
FOutstandingQuery *currentListenSpec = self.listens[query];
// only trigger actions if the listen hasn't been removed (and maybe readded)
if (currentListenSpec == listenSpec) {
NSString *status = [response objectForKey:kFWPRequestStatus];
if (![status isEqualToString:@"ok"]) {
[self removeListen:query];
}
if (listenSpec.onComplete) {
listenSpec.onComplete(status);
}
}
self.unackedListensCount--;
NSAssert(self.unackedListensCount >= 0, @"unackedListensCount decremented to be negative.");
if (self.unackedListensCount == 0) {
[self ackPuts];
}
};
[self sendAction:kFWPRequestActionTaggedListen
body:request
sensitive:NO
callback:onResponse];
self.unackedListensCount++;
}
- (void) warnOnListenWarningsForQuery:(FQuerySpec *)query payload:(id)payload {
if (payload != nil && [payload isKindOfClass:[NSDictionary class]]) {
NSDictionary *payloadDict = payload;
id warnings = payloadDict[kFWPResponseDataWarnings];
if (warnings != nil && [warnings isKindOfClass:[NSArray class]]) {
NSArray *warningsArr = warnings;
if ([warningsArr containsObject:@"no_index"]) {
NSString *indexSpec = [NSString stringWithFormat:@"\".indexOn\": \"%@\"", [query.params.index queryDefinition]];
NSString *indexPath = [query.path description];
FFWarn(@"I-RDB034028", @"Using an unspecified index. Your data will be downloaded and filtered on the client. "
"Consider adding %@ at %@ to your security rules for better performance", indexSpec, indexPath);
}
}
}
}
- (int) getNextRequestNumber {
return [[self.requestNumber getAndIncrement] intValue];
}
- (void)sendAction:(NSString *)action
body:(NSDictionary *)message
sensitive:(BOOL)sensitive
callback:(void (^)(NSDictionary* data))onMessage {
// Hold onto the onMessage callback for this request before firing it off
NSNumber* rn = [NSNumber numberWithInt:[self getNextRequestNumber]];
NSDictionary* msg = [NSDictionary dictionaryWithObjectsAndKeys:
rn, kFWPRequestNumber,
action, kFWPRequestAction,
message, kFWPRequestPayloadBody,
nil];
[self.realtime sendRequest:msg sensitive:sensitive];
if (onMessage) {
// Debug message without a callback; bump the rn, but don't hold onto the cb
[self.requestCBHash setObject:[onMessage copy] forKey:rn];
}
}
- (void) cancelSentTransactions {
NSMutableDictionary<NSNumber*, FOutstandingPut*>* cancelledOutstandingPuts = [[NSMutableDictionary alloc] init];
for (NSNumber* index in self.outstandingPuts) {
FOutstandingPut* put = self.outstandingPuts[index];
if (put.request[kFWPRequestHash] && put.sent) {
// This is a sent transaction put.
cancelledOutstandingPuts[index] = put;
}
}
[cancelledOutstandingPuts enumerateKeysAndObjectsUsingBlock:^(NSNumber *index, FOutstandingPut *outstandingPut, BOOL *stop) {
// `onCompleteBlock:` may invoke `rerunTransactionsForPath:` and enqueue new writes. We defer calling
// it until we have finished enumerating all existing writes.
outstandingPut.onCompleteBlock(kFTransactionDisconnect, @"Client was disconnected while running a transaction");
[self.outstandingPuts removeObjectForKey:index];
}];
}
- (void) onDataPushWithAction:(NSString *)action andBody:(NSDictionary *)body {
FFLog(@"I-RDB034029", @"handleServerMessage: %@, %@", action, body);
id<FPersistentConnectionDelegate> delegate = self.delegate;
if ([action isEqualToString:kFWPAsyncServerDataUpdate] || [action isEqualToString:kFWPAsyncServerDataMerge]) {
BOOL isMerge = [action isEqualToString:kFWPAsyncServerDataMerge];
if ([body objectForKey:kFWPAsyncServerDataUpdateBodyPath] && [body objectForKey:kFWPAsyncServerDataUpdateBodyData]) {
NSString* path = [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
id payloadData = [body objectForKey:kFWPAsyncServerDataUpdateBodyData];
if (isMerge && [payloadData isKindOfClass:[NSDictionary class]] && [payloadData count] == 0) {
// ignore empty merge
} else {
[delegate onDataUpdate:self forPath:path message:payloadData isMerge:isMerge tagId:[body objectForKey:kFWPAsyncServerDataUpdateBodyTag]];
}
}
else {
FFLog(@"I-RDB034030", @"Malformed data response from server missing path or data: %@", body);
}
} else if ([action isEqualToString:kFWPAsyncServerDataRangeMerge]) {
NSString *path = body[kFWPAsyncServerDataUpdateBodyPath];
NSArray *ranges = body[kFWPAsyncServerDataUpdateBodyData];
NSNumber *tag = body[kFWPAsyncServerDataUpdateBodyTag];
NSMutableArray *rangeMerges = [NSMutableArray array];
for (NSDictionary *range in ranges) {
NSString *startString = range[kFWPAsyncServerDataUpdateStartPath];
NSString *endString = range[kFWPAsyncServerDataUpdateEndPath];
id updateData = range[kFWPAsyncServerDataUpdateRangeMerge];
id<FNode> updates = [FSnapshotUtilities nodeFrom:updateData];
FPath *start = (startString != nil) ? [[FPath alloc] initWith:startString] : nil;
FPath *end = (endString != nil) ? [[FPath alloc] initWith:endString] : nil;
FRangeMerge *merge = [[FRangeMerge alloc] initWithStart:start end:end updates:updates];
[rangeMerges addObject:merge];
}
[delegate onRangeMerge:rangeMerges forPath:path tagId:tag];
} else if ([action isEqualToString:kFWPAsyncServerAuthRevoked]) {
NSString* status = [body objectForKey:kFWPResponseForActionStatus];
NSString* reason = [body objectForKey:kFWPResponseForActionData];
[self onAuthRevokedWithStatus:status andReason:reason];
} else if ([action isEqualToString:kFWPASyncServerListenCancelled]) {
NSString* pathString = [body objectForKey:kFWPAsyncServerDataUpdateBodyPath];
[self onListenRevoked:[[FPath alloc] initWith:pathString]];
} else if ([action isEqualToString:kFWPAsyncServerSecurityDebug]) {
NSString* msg = [body objectForKey:@"msg"];
if (msg != nil) {
NSArray *msgs = [msg componentsSeparatedByString:@"\n"];
for (NSString* m in msgs) {
FFWarn(@"I-RDB034031", @"%@", m);
}
}
} else {
// TODO: revoke listens, auth, security debug
FFLog(@"I-RDB034032", @"Unsupported action from server: %@", action);
}
}
- (void) restoreAuth {
FFLog(@"I-RDB034033", @"Calling restore state");
NSAssert(self->connectionState == ConnectionStateConnecting,
@"Wanted to restore auth, but was in wrong state: %d", self->connectionState);
if (self.authToken == nil) {
FFLog(@"I-RDB034034", @"Not restoring auth because token is nil");
self->connectionState = ConnectionStateConnected;
[self restoreState];
} else {
FFLog(@"I-RDB034035", @"Restoring auth");
self->connectionState = ConnectionStateAuthenticating;
[self sendAuthAndRestoreStateAfterComplete:YES];
}
}
- (void) restoreState {
NSAssert(self->connectionState == ConnectionStateConnected,
@"Should be connected if we're restoring state, but we are: %d", self->connectionState);
[self.listens enumerateKeysAndObjectsUsingBlock:^(FQuerySpec *query, FOutstandingQuery *outstandingListen, BOOL *stop) {
FFLog(@"I-RDB034036", @"Restoring listen for %@", query);
[self sendListen:outstandingListen];
}];
NSArray* keys = [[self.outstandingPuts allKeys] sortedArrayUsingSelector:@selector(compare:)];
for(int i = 0; i < [keys count]; i++) {
if([self.outstandingPuts objectForKey:[keys objectAtIndex:i]] != nil) {
FFLog(@"I-RDB034037", @"Restoring put: %d", i);
[self sendPut:[keys objectAtIndex:i]];
}
else {
FFLog(@"I-RDB034038", @"Restoring put: skipped nil: %d", i);
}
}
for (FTupleOnDisconnect* tuple in self.onDisconnectQueue) {
[self sendOnDisconnectAction:tuple.action forPath:tuple.pathString withData:tuple.data andCallback:tuple.onComplete];
}
[self.onDisconnectQueue removeAllObjects];
}
- (NSArray *) removeListen:(FQuerySpec *)query {
NSAssert(query.isDefault || !query.loadsAllData, @"removeListen called for non-default but complete query");
FOutstandingQuery* outstanding = self.listens[query];
if (!outstanding) {
FFLog(@"I-RDB034039", @"Trying to remove listener for query %@ but no listener exists", query);
return @[];
} else {
[self.listens removeObjectForKey:query];
return @[outstanding];
}
}
- (NSArray *) removeAllListensAtPath:(FPath *)path {
FFLog(@"I-RDB034040", @"Removing all listens at path %@", path);
NSMutableArray *removed = [NSMutableArray array];
NSMutableArray *toRemove = [NSMutableArray array];
[self.listens enumerateKeysAndObjectsUsingBlock:^(FQuerySpec *spec, FOutstandingQuery *outstanding, BOOL *stop) {
if ([spec.path isEqual:path]) {
[removed addObject:outstanding];
[toRemove addObject:spec];
}
}];
[self.listens removeObjectsForKeys:toRemove];
return removed;
}
- (void) purgeOutstandingWrites {
// We might have unacked puts in our queue that we need to ack now before we send out any cancels...
[self ackPuts];
// Cancel in order
NSArray* keys = [[self.outstandingPuts allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSNumber *key in keys) {
FOutstandingPut *put = self.outstandingPuts[key];
if (put.onCompleteBlock != nil) {
put.onCompleteBlock(kFErrorWriteCanceled, nil);
}
}
for (FTupleOnDisconnect *onDisconnect in self.onDisconnectQueue) {
if (onDisconnect.onComplete != nil) {
onDisconnect.onComplete(kFErrorWriteCanceled, nil);
}
}
[self.outstandingPuts removeAllObjects];
[self.onDisconnectQueue removeAllObjects];
}
- (void) ackPuts {
for (FTupleCallbackStatus *put in self.putsToAck) {
put.block(put.status, put.errorReason);
}
[self.putsToAck removeAllObjects];
}
- (void) handleTimestamp:(NSNumber *)timestamp {
FFLog(@"I-RDB034041", @"Handling timestamp: %@", timestamp);
double timestampDeltaMs = [timestamp doubleValue] - ([[NSDate date] timeIntervalSince1970] * 1000);
[self.delegate onServerInfoUpdate:self updates:@{kDotInfoServerTimeOffset: [NSNumber numberWithDouble:timestampDeltaMs]}];
}
- (void) sendStats:(NSDictionary *)stats {
if ([stats count] > 0) {
NSDictionary *request = @{ kFWPRequestCounters: stats };
[self sendAction:kFWPRequestActionStats body:request sensitive:NO callback:^(NSDictionary *data) {
NSString* status = [data objectForKey:kFWPResponseForActionStatus];
NSString* errorReason = [data objectForKey:kFWPResponseForActionData];
BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk];
if (!statusOk) {
FFLog(@"I-RDB034042", @"Failed to send stats: %@", errorReason);
}
}];
} else {
FFLog(@"I-RDB034043", @"Not sending stats because stats are empty");
}
}
- (void) sendConnectStats {
NSMutableDictionary *stats = [NSMutableDictionary dictionary];
#if TARGET_OS_IOS || TARGET_OS_TV
if (self.config.persistenceEnabled) {
stats[@"persistence.ios.enabled"] = @1;
}
#elif TARGET_OS_OSX
if (self.config.persistenceEnabled) {
stats[@"persistence.osx.enabled"] = @1;
}
#endif
NSString *sdkVersion = [[FIRDatabase sdkVersion] stringByReplacingOccurrencesOfString:@"." withString:@"-"];
NSString *sdkStatName = [NSString stringWithFormat:@"sdk.objc.%@", sdkVersion];
stats[sdkStatName] = @1;
FFLog(@"I-RDB034044", @"Sending first connection stats");
[self sendStats:stats];
}
- (NSDictionary *) dumpListens {
return self.listens;
}
@end

View File

@@ -0,0 +1,59 @@
/*
* 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>
@protocol FIndex, FNodeFilter, FNode;
@interface FQueryParams : NSObject <NSCopying>
@property (nonatomic, readonly) BOOL limitSet;
@property (nonatomic, readonly) NSInteger limit;
@property (nonatomic, strong, readonly) NSString *viewFrom;
@property (nonatomic, strong, readonly) id<FNode> indexStartValue;
@property (nonatomic, strong, readonly) NSString *indexStartKey;
@property (nonatomic, strong, readonly) id<FNode> indexEndValue;
@property (nonatomic, strong, readonly) NSString *indexEndKey;
@property (nonatomic, strong, readonly) id<FIndex> index;
- (BOOL)loadsAllData;
- (BOOL)isDefault;
- (BOOL)isValid;
- (BOOL)hasAnchoredLimit;
- (FQueryParams *) limitTo:(NSInteger) limit;
- (FQueryParams *) limitToFirst:(NSInteger) newLimit;
- (FQueryParams *) limitToLast:(NSInteger) newLimit;
- (FQueryParams *) startAt:(id<FNode>)indexValue childKey:(NSString *)key;
- (FQueryParams *) startAt:(id<FNode>)indexValue;
- (FQueryParams *) endAt:(id<FNode>)indexValue childKey:(NSString *)key;
- (FQueryParams *) endAt:(id<FNode>)indexValue;
- (FQueryParams *) orderBy:(id<FIndex>) index;
+ (FQueryParams *) defaultInstance;
+ (FQueryParams *) fromQueryObject:(NSDictionary *)dict;
- (BOOL)hasStart;
- (BOOL)hasEnd;
- (NSDictionary *) wireProtocolParams;
- (BOOL) isViewFromLeft;
- (id<FNodeFilter>) nodeFilter;
@end

View File

@@ -0,0 +1,372 @@
/*
* 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 "FQueryParams.h"
#import "FValidation.h"
#import "FConstants.h"
#import "FIndex.h"
#import "FPriorityIndex.h"
#import "FUtilities.h"
#import "FNodeFilter.h"
#import "FIndexedFilter.h"
#import "FLimitedFilter.h"
#import "FRangedFilter.h"
#import "FNode.h"
#import "FSnapshotUtilities.h"
@interface FQueryParams ()
@property (nonatomic, readwrite) BOOL limitSet;
@property (nonatomic, readwrite) NSInteger limit;
@property (nonatomic, strong, readwrite) NSString *viewFrom;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property (nonatomic, strong, readwrite) id<FNode> indexStartValue;
@property (nonatomic, strong, readwrite) NSString *indexStartKey;
/**
* indexStartValue is anything you can store as a priority / value.
*/
@property (nonatomic, strong, readwrite) id<FNode> indexEndValue;
@property (nonatomic, strong, readwrite) NSString *indexEndKey;
@property (nonatomic, strong, readwrite) id<FIndex> index;
@end
@implementation FQueryParams
+ (FQueryParams *) defaultInstance {
static FQueryParams *defaultParams = nil;
static dispatch_once_t defaultParamsToken;
dispatch_once(&defaultParamsToken, ^{
defaultParams = [[FQueryParams alloc] init];
});
return defaultParams;
}
- (id)init {
self = [super init];
if (self) {
self->_limitSet = NO;
self->_limit = 0;
self->_viewFrom = nil;
self->_indexStartValue = nil;
self->_indexStartKey = nil;
self->_indexEndValue = nil;
self->_indexEndKey = nil;
self->_index = [FPriorityIndex priorityIndex];
}
return self;
}
/**
* Only valid if hasStart is true
*/
- (id) indexStartValue {
NSAssert([self hasStart], @"Only valid if start has been set");
return _indexStartValue;
}
/**
* Only valid if hasStart is true.
* @return The starting key name for the range defined by these query parameters
*/
- (NSString *) indexStartKey {
NSAssert([self hasStart], @"Only valid if start has been set");
if (_indexStartKey == nil) {
return [FUtilities minName];
} else {
return _indexStartKey;
}
}
/**
* Only valid if hasEnd is true.
*/
- (id) indexEndValue {
NSAssert([self hasEnd], @"Only valid if end has been set");
return _indexEndValue;
}
/**
* Only valid if hasEnd is true.
* @return The end key name for the range defined by these query parameters
*/
- (NSString *) indexEndKey {
NSAssert([self hasEnd], @"Only valid if end has been set");
if (_indexEndKey == nil) {
return [FUtilities maxName];
} else {
return _indexEndKey;
}
}
/**
* @return true if a limit has been set and has been explicitly anchored
*/
- (BOOL) hasAnchoredLimit {
return self.limitSet && self.viewFrom != nil;
}
/**
* Only valid to call if limitSet returns true
*/
- (NSInteger) limit {
NSAssert(self.limitSet, @"Only valid if limit has been set");
return _limit;
}
- (BOOL)hasStart {
return self->_indexStartValue != nil;
}
- (BOOL)hasEnd {
return self->_indexEndValue != nil;
}
- (id) copyWithZone:(NSZone *)zone {
// Immutable
return self;
}
- (id) mutableCopy {
FQueryParams* other = [[[self class] alloc] init];
// Maybe need to do extra copying here
other->_limitSet = _limitSet;
other->_limit = _limit;
other->_indexStartValue = _indexStartValue;
other->_indexStartKey = _indexStartKey;
other->_indexEndValue = _indexEndValue;
other->_indexEndKey = _indexEndKey;
other->_viewFrom = _viewFrom;
other->_index = _index;
return other;
}
- (FQueryParams *) limitTo:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = nil;
return newParams;
}
- (FQueryParams *) limitToFirst:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = kFQPViewFromLeft;
return newParams;
}
- (FQueryParams *) limitToLast:(NSInteger)newLimit {
FQueryParams *newParams = [self mutableCopy];
newParams->_limitSet = YES;
newParams->_limit = newLimit;
newParams->_viewFrom = kFQPViewFromRight;
return newParams;
}
- (FQueryParams *) startAt:(id<FNode>)indexValue childKey:(NSString *)key {
NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
FQueryParams *newParams = [self mutableCopy];
newParams->_indexStartValue = indexValue;
newParams->_indexStartKey = key;
return newParams;
}
- (FQueryParams *) startAt:(id<FNode>)indexValue {
return [self startAt:indexValue childKey:nil];
}
- (FQueryParams *) endAt:(id<FNode>)indexValue childKey:(NSString *)key {
NSAssert([indexValue isLeafNode] || [indexValue isEmpty], nil);
FQueryParams *newParams = [self mutableCopy];
newParams->_indexEndValue = indexValue;
newParams->_indexEndKey = key;
return newParams;
}
- (FQueryParams *) endAt:(id<FNode>)indexValue {
return [self endAt:indexValue childKey:nil];
}
- (FQueryParams *) orderBy:(id)newIndex {
FQueryParams *newParams = [self mutableCopy];
newParams->_index = newIndex;
return newParams;
}
- (NSDictionary *) wireProtocolParams {
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
if ([self hasStart]) {
[dict setObject:[self.indexStartValue valForExport:YES] forKey:kFQPIndexStartValue];
// Don't use property as it will be [MIN-NAME]
if (self->_indexStartKey != nil) {
[dict setObject:self->_indexStartKey forKey:kFQPIndexStartName];
}
}
if ([self hasEnd]) {
[dict setObject:[self.indexEndValue valForExport:YES] forKey:kFQPIndexEndValue];
// Don't use property as it will be [MAX-NAME]
if (self->_indexEndKey != nil) {
[dict setObject:self->_indexEndKey forKey:kFQPIndexEndName];
}
}
if (self.limitSet) {
[dict setObject:[NSNumber numberWithInteger:self.limit] forKey:kFQPLimit];
NSString *vf = self.viewFrom;
if (vf == nil) {
// limit() rather than limitToFirst or limitToLast was called.
// This means that only one of startSet or endSet is true. Use them
// to calculate which side of the view to anchor to. If neither is set,
// Anchor to end
if ([self hasStart]) {
vf = kFQPViewFromLeft;
} else {
vf = kFQPViewFromRight;
}
}
[dict setObject:vf forKey:kFQPViewFrom];
}
// For now, priority index is the default, so we only specify if it's some other index.
if (![self.index isEqual:[FPriorityIndex priorityIndex]]) {
[dict setObject:[self.index queryDefinition] forKey:kFQPIndex];
}
return dict;
}
+ (FQueryParams *)fromQueryObject:(NSDictionary *)dict {
if (dict.count == 0) {
return [FQueryParams defaultInstance];
}
FQueryParams *params = [[FQueryParams alloc] init];
if (dict[kFQPLimit] != nil) {
params->_limitSet = YES;
params->_limit = [dict[kFQPLimit] integerValue];
}
if (dict[kFQPIndexStartValue] != nil) {
params->_indexStartValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexStartValue]];
if (dict[kFQPIndexStartName] != nil) {
params->_indexStartKey = dict[kFQPIndexStartName];
}
}
if (dict[kFQPIndexEndValue] != nil) {
params->_indexEndValue = [FSnapshotUtilities nodeFrom:dict[kFQPIndexEndValue]];
if (dict[kFQPIndexEndName] != nil) {
params->_indexEndKey = dict[kFQPIndexEndName];
}
}
if (dict[kFQPViewFrom] != nil) {
NSString *viewFrom = dict[kFQPViewFrom];
if (![viewFrom isEqualToString:kFQPViewFromLeft] && ![viewFrom isEqualToString:kFQPViewFromRight]) {
[NSException raise:NSInvalidArgumentException format:@"Unknown view from paramter: %@", viewFrom];
}
params->_viewFrom = viewFrom;
}
NSString *index = dict[kFQPIndex];
if (index != nil) {
params->_index = [FIndex indexFromQueryDefinition:index];
}
return params;
}
- (BOOL) isViewFromLeft {
if (self.viewFrom != nil) {
// Not null, we can just check
return [self.viewFrom isEqualToString:kFQPViewFromLeft];
} else {
// If start is set, it's view from left. Otherwise not.
return self.hasStart;
}
}
- (id<FNodeFilter>) nodeFilter {
if (self.loadsAllData) {
return [[FIndexedFilter alloc] initWithIndex:self.index];
} else if (self.limitSet) {
return [[FLimitedFilter alloc] initWithQueryParams:self];
} else {
return [[FRangedFilter alloc] initWithQueryParams:self];
}
}
- (BOOL) isValid {
return !(self.hasStart && self.hasEnd && self.limitSet && !self.hasAnchoredLimit);
}
- (BOOL) loadsAllData {
return !(self.hasStart || self.hasEnd || self.limitSet);
}
- (BOOL) isDefault {
return [self loadsAllData] && [self.index isEqual:[FPriorityIndex priorityIndex]];
}
- (NSString *) description {
return [[self wireProtocolParams] description];
}
- (BOOL) isEqual:(id)obj {
if (self == obj) {
return YES;
}
if (![obj isKindOfClass:[self class]]) {
return NO;
}
FQueryParams *other = (FQueryParams *)obj;
if (self->_limitSet != other->_limitSet) return NO;
if (self->_limit != other->_limit) return NO;
if ((self->_index != other->_index) && ![self->_index isEqual:other->_index]) return NO;
if ((self->_indexStartKey != other->_indexStartKey) && ![self->_indexStartKey isEqualToString:other->_indexStartKey]) return NO;
if ((self->_indexStartValue != other->_indexStartValue) && ![self->_indexStartValue isEqual:other->_indexStartValue]) return NO;
if ((self->_indexEndKey != other->_indexEndKey) && ![self->_indexEndKey isEqualToString:other->_indexEndKey]) return NO;
if ((self->_indexEndValue != other->_indexEndValue) && ![self->_indexEndValue isEqual:other->_indexEndValue]) return NO;
if ([self isViewFromLeft] != [other isViewFromLeft]) return NO;
return YES;
}
- (NSUInteger) hash {
NSUInteger result = _limitSet ? _limit : 0;
result = 31 * result + ([self isViewFromLeft] ? 1231 : 1237);
result = 31 * result + [_indexStartKey hash];
result = 31 * result + [_indexStartValue hash];
result = 31 * result + [_indexEndKey hash];
result = 31 * result + [_indexEndValue hash];
result = 31 * result + [_index hash];
return result;
}
@end

View File

@@ -0,0 +1,36 @@
/*
* 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 "FQueryParams.h"
#import "FPath.h"
#import "FIndex.h"
@interface FQuerySpec : NSObject<NSCopying>
@property (nonatomic, strong, readonly) FPath* path;
@property (nonatomic, strong, readonly) FQueryParams *params;
- (id)initWithPath:(FPath *)path params:(FQueryParams *)params;
+ (FQuerySpec *)defaultQueryAtPath:(FPath *)path;
- (id<FIndex>)index;
- (BOOL)isDefault;
- (BOOL)loadsAllData;
@end

View File

@@ -0,0 +1,85 @@
/*
* 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 "FQuerySpec.h"
@interface FQuerySpec ()
@property (nonatomic, strong, readwrite) FPath* path;
@property (nonatomic, strong, readwrite) FQueryParams *params;
@end
@implementation FQuerySpec
- (id)initWithPath:(FPath *)path params:(FQueryParams *)params {
self = [super init];
if (self != nil) {
self->_path = path;
self->_params = params;
}
return self;
}
+ (FQuerySpec *)defaultQueryAtPath:(FPath *)path {
return [[FQuerySpec alloc] initWithPath:path params:[FQueryParams defaultInstance]];
}
- (id)copyWithZone:(NSZone *)zone {
// Immutable
return self;
}
- (id<FIndex>)index {
return self.params.index;
}
- (BOOL)isDefault {
return self.params.isDefault;
}
- (BOOL)loadsAllData {
return self.params.loadsAllData;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[FQuerySpec class]]) {
return NO;
}
FQuerySpec *other = (FQuerySpec *)object;
if (![self.path isEqual:other.path]) {
return NO;
}
return [self.params isEqual:other.params];
}
- (NSUInteger)hash {
return self.path.hash * 31 + self.params.hash;
}
- (NSString *)description {
return [NSString stringWithFormat:@"FQuerySpec (path: %@, params: %@)", self.path, self.params];
}
@end

View File

@@ -0,0 +1,35 @@
/*
* 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 "FNode.h"
/**
* Applies a merge of a snap for a given interval of paths.
* Each leaf in the current node which the relative path lies *after* (the optional) start and lies *before or at*
* (the optional) end will be deleted. Each leaf in snap that lies in the interval will be added to the resulting node.
* Nodes outside of the range are ignored. nil for start and end are sentinel values that represent -infinity and
* +infinity respectively (aka includes any path).
* Priorities of children nodes are treated as leaf children of that node.
*/
@interface FRangeMerge : NSObject
- (instancetype)initWithStart:(FPath *)start end:(FPath *)end updates:(id<FNode>)updates;
- (id<FNode>)applyToNode:(id<FNode>)node;
@end

View File

@@ -0,0 +1,107 @@
/*
* 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 "FRangeMerge.h"
#import "FEmptyNode.h"
@interface FRangeMerge ()
@property (nonatomic, strong) FPath *optExclusiveStart;
@property (nonatomic, strong) FPath *optInclusiveEnd;
@property (nonatomic, strong) id<FNode> updates;
@end
@implementation FRangeMerge
- (instancetype)initWithStart:(FPath *)start end:(FPath *)end updates:(id<FNode>)updates {
self = [super init];
if (self != nil) {
self->_optExclusiveStart = start;
self->_optInclusiveEnd = end;
self->_updates = updates;
}
return self;
}
- (id<FNode>)applyToNode:(id<FNode>)node {
return [self updateRangeInNode:[FPath empty] node:node updates:self.updates];
}
- (id<FNode>)updateRangeInNode:(FPath *)currentPath node:(id<FNode>)node updates:(id<FNode>)updates {
NSComparisonResult startComparison = (self.optExclusiveStart == nil) ? NSOrderedDescending : [currentPath compare:self.optExclusiveStart];
NSComparisonResult endComparison = (self.optInclusiveEnd == nil) ? NSOrderedAscending : [currentPath compare:self.optInclusiveEnd];
BOOL startInNode = self.optExclusiveStart != nil && [currentPath contains:self.optExclusiveStart];
BOOL endInNode = self.optInclusiveEnd != nil && [currentPath contains:self.optInclusiveEnd];
if (startComparison == NSOrderedDescending && endComparison == NSOrderedAscending && !endInNode) {
// child is completly contained
return updates;
} else if (startComparison == NSOrderedDescending && endInNode && [updates isLeafNode]) {
return updates;
} else if (startComparison == NSOrderedDescending && endComparison == NSOrderedSame) {
NSAssert(endInNode, @"End not in node");
NSAssert(![updates isLeafNode], @"Found leaf node update, this case should have been handled above.");
if ([node isLeafNode]) {
// Update node was not a leaf node, so we can delete it
return [FEmptyNode emptyNode];
} else {
// Unaffected by range, ignore
return node;
}
} else if (startInNode || endInNode) {
// There is a partial update we need to do, so collect all relevant children
NSMutableSet *allChildren = [NSMutableSet set];
[node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
[allChildren addObject:key];
}];
[updates enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
[allChildren addObject:key];
}];
__block id<FNode> newNode = node;
void (^action)(id, BOOL *) = ^void(NSString *key, BOOL *stop) {
id<FNode> currentChild = [node getImmediateChild:key];
id<FNode> updatedChild = [self updateRangeInNode:[currentPath childFromString:key]
node:currentChild
updates:[updates getImmediateChild:key]];
// Only need to update if the node changed
if (updatedChild != currentChild) {
newNode = [newNode updateImmediateChild:key withNewChild:updatedChild];
}
};
[allChildren enumerateObjectsUsingBlock:action];
// Add priority last, so the node is not empty when applying
if (!updates.getPriority.isEmpty || !node.getPriority.isEmpty) {
BOOL stop = NO;
action(@".priority", &stop);
}
return newNode;
} else {
// Unaffected by this range
NSAssert(endComparison == NSOrderedDescending || startComparison <= NSOrderedSame, @"Invalid range for update");
return node;
}
}
- (NSString *)description {
return [NSString stringWithFormat:@"RangeMerge (optExclusiveStart = %@, optExclusiveEng = %@, updates = %@)",
self.optExclusiveStart, self.optInclusiveEnd, self.updates];
}
@end

View File

@@ -0,0 +1,76 @@
/*
* 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 "FRepoInfo.h"
#import "FPersistentConnection.h"
#import "FIRDataEventType.h"
#import "FTupleUserCallback.h"
@class FQuerySpec;
@class FPersistence;
@class FAuthenticationManager;
@class FIRDatabaseConfig;
@protocol FEventRegistration;
@class FCompoundWrite;
@protocol FClock;
@class FIRDatabase;
@interface FRepo : NSObject <FPersistentConnectionDelegate>
@property (nonatomic, strong) FIRDatabaseConfig *config;
- (id)initWithRepoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config database:(FIRDatabase *)database;
- (void) set:(FPath *)path withNode:(id)node withCallback:(fbt_void_nserror_ref)onComplete;
- (void) update:(FPath *)path withNodes:(FCompoundWrite *)compoundWrite withCallback:(fbt_void_nserror_ref)callback;
- (void) purgeOutstandingWrites;
- (void) addEventRegistration:(id<FEventRegistration>)eventRegistration forQuery:(FQuerySpec *)query;
- (void) removeEventRegistration:(id<FEventRegistration>)eventRegistration forQuery:(FQuerySpec *)query;
- (void) keepQuery:(FQuerySpec *)query synced:(BOOL)synced;
- (NSString*)name;
- (NSTimeInterval)serverTime;
- (void) onDataUpdate:(FPersistentConnection *)fpconnection forPath:(NSString *)pathString message:(id)message isMerge:(BOOL)isMerge tagId:(NSNumber *)tagId;
- (void) onConnect:(FPersistentConnection *)fpconnection;
- (void) onDisconnect:(FPersistentConnection *)fpconnection;
// Disconnect methods
- (void) onDisconnectCancel:(FPath *)path withCallback:(fbt_void_nserror_ref)callback;
- (void) onDisconnectSet:(FPath *)path withNode:(id<FNode>)node withCallback:(fbt_void_nserror_ref)callback;
- (void) onDisconnectUpdate:(FPath *)path withNodes:(FCompoundWrite *)compoundWrite withCallback:(fbt_void_nserror_ref)callback;
// Connection Management.
- (void) interrupt;
- (void) resume;
// Transactions
- (void) startTransactionOnPath:(FPath *)path
update:(fbt_transactionresult_mutabledata)update
onComplete:(fbt_void_nserror_bool_datasnapshot)onComplete
withLocalEvents:(BOOL)applyLocally;
// Testing methods
- (NSDictionary *) dumpListens;
- (void) dispose;
- (void) setHijackHash:(BOOL)hijack;
@property (nonatomic, strong, readonly) FAuthenticationManager *auth;
@property (nonatomic, strong, readonly) FIRDatabase *database;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
/*
* 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>
@interface FRepoInfo : NSObject <NSCopying>
@property (nonatomic, readonly, strong) NSString* host;
@property (nonatomic, readonly, strong) NSString* namespace;
@property (nonatomic, strong) NSString* internalHost;
@property (nonatomic, readonly) bool secure;
- (id) initWithHost:(NSString*)host isSecure:(bool)secure withNamespace:(NSString*)namespace;
- (NSString *) connectionURLWithLastSessionID:(NSString*)lastSessionID;
- (NSString *) connectionURL;
- (void) clearInternalHostCache;
- (BOOL) isDemoHost;
- (BOOL) isCustomHost;
- (id)copyWithZone:(NSZone *)zone;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;
@end

View File

@@ -0,0 +1,134 @@
/*
* 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 "FRepoInfo.h"
#import "FConstants.h"
@interface FRepoInfo ()
@property (nonatomic, strong) NSString *domain;
@end
@implementation FRepoInfo
@synthesize namespace;
@synthesize host;
@synthesize internalHost;
@synthesize secure;
@synthesize domain;
- (id) initWithHost:(NSString*)aHost isSecure:(bool)isSecure withNamespace:(NSString*)aNamespace {
self = [super init];
if (self) {
host = aHost;
domain = [host substringFromIndex:[host rangeOfString:@"."].location+1];
secure = isSecure;
namespace = aNamespace;
// Get cached internal host if it exists
NSString* internalHostKey = [NSString stringWithFormat:@"firebase:host:%@", self.host];
NSString* cachedInternalHost = [[NSUserDefaults standardUserDefaults] stringForKey:internalHostKey];
if (cachedInternalHost != nil) {
internalHost = cachedInternalHost;
} else {
internalHost = self.host;
}
}
return self;
}
- (NSString *)description {
// The namespace is encoded in the hostname, so we can just return this.
return [NSString stringWithFormat:@"http%@://%@", (self.secure ? @"s" : @""), self.host];
}
- (void) setInternalHost:(NSString *)newHost {
if (![internalHost isEqualToString:newHost]) {
internalHost = newHost;
// Cache the internal host so we don't need to redirect later on
NSString* internalHostKey = [NSString stringWithFormat:@"firebase:host:%@", self.host];
NSUserDefaults* cache = [NSUserDefaults standardUserDefaults];
[cache setObject:internalHost forKey:internalHostKey];
[cache synchronize];
}
}
- (void) clearInternalHostCache {
internalHost = self.host;
// Remove the cached entry
NSString* internalHostKey = [NSString stringWithFormat:@"firebase:host:%@", self.host];
NSUserDefaults* cache = [NSUserDefaults standardUserDefaults];
[cache removeObjectForKey:internalHostKey];
[cache synchronize];
}
- (BOOL) isDemoHost {
return [self.domain isEqualToString:@"firebaseio-demo.com"];
}
- (BOOL) isCustomHost {
return ![self.domain isEqualToString:@"firebaseio-demo.com"] && ![self.domain isEqualToString:@"firebaseio.com"];
}
- (NSString *) connectionURL {
return [self connectionURLWithLastSessionID:nil];
}
- (NSString *) connectionURLWithLastSessionID:(NSString*)lastSessionID {
NSString *scheme;
if (self.secure) {
scheme = @"wss";
} else {
scheme = @"ws";
}
NSString *url = [NSString stringWithFormat:@"%@://%@/.ws?%@=%@&ns=%@",
scheme,
self.internalHost,
kWireProtocolVersionParam,
kWebsocketProtocolVersion,
self.namespace];
if (lastSessionID != nil) {
url = [NSString stringWithFormat:@"%@&ls=%@", url, lastSessionID];
}
return url;
}
- (id)copyWithZone:(NSZone *)zone; {
return self; // Immutable
}
- (NSUInteger)hash {
NSUInteger result = host.hash;
result = 31 * result + (secure ? 1 : 0);
result = 31 * result + namespace.hash;
result = 31 * result + host.hash;
return result;
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[FRepoInfo class]]) return NO;
FRepoInfo *other = (FRepoInfo *)anObject;
return secure == other.secure && [host isEqualToString:other.host] &&
[namespace isEqualToString:other.namespace];
}
@end

View File

@@ -0,0 +1,32 @@
/*
* 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 "FRepoInfo.h"
#import "FRepo.h"
#import "FIRDatabaseConfig.h"
@interface FRepoManager : NSObject
+ (FRepo *) getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config;
+ (FRepo *) createRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config database:(FIRDatabase *)database;
+ (void) interruptAll;
+ (void) interrupt:(FIRDatabaseConfig *)config;
+ (void) resumeAll;
+ (void) resume:(FIRDatabaseConfig *)config;
+ (void) disposeRepos:(FIRDatabaseConfig *)config;
@end

View File

@@ -0,0 +1,135 @@
/*
* 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 <FirebaseCore/FIRLogger.h>
#import "FRepoManager.h"
#import "FRepo.h"
#import "FIRDatabaseQuery_Private.h"
#import "FAtomicNumber.h"
#import "FIRDatabaseConfig_Private.h"
#import "FIRDatabase_Private.h"
@implementation FRepoManager
typedef NSMutableDictionary<NSString *,
NSMutableDictionary<FRepoInfo *, FRepo *> *>
FRepoDictionary;
+ (FRepoDictionary *)configs {
static dispatch_once_t pred = 0;
static FRepoDictionary *configs;
dispatch_once(&pred, ^{
configs = [NSMutableDictionary dictionary];
});
return configs;
}
/**
* Used for legacy unit tests. The public API should go through FirebaseDatabase which
* calls createRepo.
*/
+ (FRepo *) getRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config {
[config freeze];
FRepoDictionary *configs = [FRepoManager configs];
@synchronized(configs) {
NSMutableDictionary<FRepoInfo *, FRepo *> *repos = configs[config.sessionIdentifier];
if (!repos || repos[repoInfo] == nil) {
// Calling this should create the repo.
[FIRDatabase createDatabaseForTests:repoInfo config:config];
}
return configs[config.sessionIdentifier][repoInfo];
}
}
+ (FRepo *) createRepo:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config database:(FIRDatabase *)database {
[config freeze];
FRepoDictionary *configs = [FRepoManager configs];
@synchronized(configs) {
NSMutableDictionary<FRepoInfo *, FRepo *> *repos =
configs[config.sessionIdentifier];
if (!repos) {
repos = [NSMutableDictionary dictionary];
configs[config.sessionIdentifier] = repos;
}
FRepo *repo = repos[repoInfo];
if (repo == nil) {
repo = [[FRepo alloc] initWithRepoInfo:repoInfo config:config database:database];
repos[repoInfo] = repo;
return repo;
} else {
[NSException raise:@"RepoExists" format:@"createRepo called for Repo that already exists."];
return nil;
}
}
}
+ (void) interrupt:(FIRDatabaseConfig *)config {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
NSMutableDictionary<FRepoInfo *, FRepo *> *repos = configs[config.sessionIdentifier];
for (FRepo *repo in [repos allValues]) {
[repo interrupt];
}
});
}
+ (void) interruptAll {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
for (NSMutableDictionary<FRepoInfo *, FRepo *> *repos in [configs allValues]) {
for (FRepo *repo in [repos allValues]) {
[repo interrupt];
}
}
});
}
+ (void) resume:(FIRDatabaseConfig *)config {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
NSMutableDictionary<FRepoInfo *, FRepo *> *repos = configs[config.sessionIdentifier];
for (FRepo *repo in [repos allValues]) {
[repo resume];
}
});
}
+ (void) resumeAll {
dispatch_async([FIRDatabaseQuery sharedQueue], ^{
FRepoDictionary *configs = [FRepoManager configs];
for (NSMutableDictionary<FRepoInfo *, FRepo *> *repos in [configs allValues]) {
for (FRepo *repo in [repos allValues]) {
[repo resume];
}
}
});
}
+ (void)disposeRepos:(FIRDatabaseConfig *)config {
// Do this synchronously to make sure we release our references to LevelDB before returning, allowing LevelDB
// to close and release its exclusive locks.
dispatch_sync([FIRDatabaseQuery sharedQueue], ^{
FFLog(@"I-RDB040001", @"Disposing all repos for Config with name %@", config.sessionIdentifier);
NSMutableDictionary *configs = [FRepoManager configs];
for (FRepo* repo in [configs[config.sessionIdentifier] allValues]) {
[repo dispose];
}
[configs removeObjectForKey:config.sessionIdentifier];
});
}
@end

View File

@@ -0,0 +1,42 @@
/*
* 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 "FRepo.h"
#import "FSparseSnapshotTree.h"
@class FSyncTree;
@class FAtomicNumber;
@class FEventRaiser;
@class FSnapshotHolder;
@interface FRepo ()
- (void) runOnDisconnectEvents;
@property (nonatomic, strong) FRepoInfo* repoInfo;
@property (nonatomic, strong) FPersistentConnection* connection;
@property (nonatomic, strong) FSnapshotHolder* infoData;
@property (nonatomic, strong) FSparseSnapshotTree* onDisconnect;
@property (nonatomic, strong) FEventRaiser *eventRaiser;
@property (nonatomic, strong) FSyncTree *serverSyncTree;
// For testing.
@property (nonatomic) long dataUpdateCount;
@property (nonatomic) long rangeMergeUpdateCount;
- (NSInteger)nextWriteId;
@end

View File

@@ -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 <Foundation/Foundation.h>
#import "FSparseSnapshotTree.h"
#import "FNode.h"
#import "FCompoundWrite.h"
#import "FClock.h"
@interface FServerValues : NSObject
+ (NSDictionary*) generateServerValues:(id<FClock>)clock;
+ (id) resolveDeferredValueCompoundWrite:(FCompoundWrite*)write withServerValues:(NSDictionary*)serverValues;
+ (id<FNode>) resolveDeferredValueSnapshot:(id<FNode>)node withServerValues:(NSDictionary*)serverValues;
+ (id) resolveDeferredValueTree:(FSparseSnapshotTree*)tree withServerValues:(NSDictionary*)serverValues;
@end

View File

@@ -0,0 +1,93 @@
/*
* 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 "FServerValues.h"
#import "FConstants.h"
#import "FLeafNode.h"
#import "FChildrenNode.h"
#import "FSnapshotUtilities.h"
@implementation FServerValues
+ (NSDictionary*) generateServerValues:(id<FClock>)clock {
long long millis = (long long)([clock currentTime] * 1000);
return @{ @"timestamp": [NSNumber numberWithLongLong:millis] };
}
+ (id) resolveDeferredValue:(id)val withServerValues:(NSDictionary*)serverValues {
if ([val isKindOfClass:[NSDictionary class]]) {
NSDictionary* dict = val;
if (dict[kServerValueSubKey] != nil) {
NSString* serverValueType = [dict objectForKey:kServerValueSubKey];
if (serverValues[serverValueType] != nil) {
return [serverValues objectForKey:serverValueType];
} else {
// TODO: Throw unrecognizedServerValue error here
}
}
}
return val;
}
+ (FCompoundWrite *) resolveDeferredValueCompoundWrite:(FCompoundWrite *)write withServerValues:(NSDictionary *)serverValues {
__block FCompoundWrite *resolved = write;
[write enumerateWrites:^(FPath *path, id<FNode> node, BOOL *stop) {
id<FNode> resolvedNode = [FServerValues resolveDeferredValueSnapshot:node withServerValues:serverValues];
// Node actually changed, use pointer inequality here
if (resolvedNode != node) {
resolved = [resolved addWrite:resolvedNode atPath:path];
}
}];
return resolved;
}
+ (id) resolveDeferredValueTree:(FSparseSnapshotTree*)tree withServerValues:(NSDictionary*)serverValues {
FSparseSnapshotTree* resolvedTree = [[FSparseSnapshotTree alloc] init];
[tree forEachTreeAtPath:[FPath empty] do:^(FPath* path, id<FNode> node) {
[resolvedTree rememberData:[FServerValues resolveDeferredValueSnapshot:node withServerValues:serverValues] onPath:path];
}];
return resolvedTree;
}
+ (id<FNode>) resolveDeferredValueSnapshot:(id<FNode>)node withServerValues:(NSDictionary*)serverValues {
id priorityVal = [FServerValues resolveDeferredValue:[[node getPriority] val] withServerValues:serverValues];
id<FNode> priority = [FSnapshotUtilities nodeFrom:priorityVal];
if ([node isLeafNode]) {
id value = [self resolveDeferredValue:[node val] withServerValues:serverValues];
if (![value isEqual:[node val]] || ![priority isEqual:[node getPriority]]) {
return [[FLeafNode alloc] initWithValue:value withPriority:priority];
} else {
return node;
}
} else {
__block FChildrenNode* newNode = node;
if (![priority isEqual:[node getPriority]]) {
newNode = [newNode updatePriority:priority];
}
[node enumerateChildrenUsingBlock:^(NSString *childKey, id<FNode> childNode, BOOL *stop) {
id newChildNode = [FServerValues resolveDeferredValueSnapshot:childNode withServerValues:serverValues];
if (![newChildNode isEqual:childNode]) {
newNode = [newNode updateImmediateChild:childKey withNewChild:newChildNode];
}
}];
return newNode;
}
}
@end

View File

@@ -0,0 +1,27 @@
/*
* 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 "FNode.h"
@interface FSnapshotHolder : NSObject
- (id<FNode>) getNode:(FPath *)path;
- (void) updateSnapshot:(FPath *)path withNewSnapshot:(id<FNode>)newSnapshotNode;
@property (nonatomic, strong) id<FNode> rootNode;
@end

View File

@@ -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 "FSnapshotHolder.h"
#import "FEmptyNode.h"
@interface FSnapshotHolder()
@end
@implementation FSnapshotHolder
@synthesize rootNode;
- (id)init
{
self = [super init];
if (self) {
self.rootNode = [FEmptyNode emptyNode];
}
return self;
}
- (id<FNode>) getNode:(FPath *)path {
return [self.rootNode getChild:path];
}
- (void) updateSnapshot:(FPath *)path withNewSnapshot:(id<FNode>)newSnapshotNode {
self.rootNode = [self.rootNode updateChild:path withNewChild:newSnapshotNode];
}
@end

View File

@@ -0,0 +1,34 @@
/*
* 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 "FNode.h"
#import "FPath.h"
#import "FTypedefs_Private.h"
@class FSparseSnapshotTree;
typedef void (^fbt_void_nsstring_sstree) (NSString*, FSparseSnapshotTree*);
@interface FSparseSnapshotTree : NSObject
- (id<FNode>) findPath:(FPath *)path;
- (void) rememberData:(id<FNode>)data onPath:(FPath *)path;
- (BOOL) forgetPath:(FPath *)path;
- (void) forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func;
- (void) forEachChild:(fbt_void_nsstring_sstree)func;
@end

View File

@@ -0,0 +1,144 @@
/*
* 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 "FSparseSnapshotTree.h"
#import "FChildrenNode.h"
@interface FSparseSnapshotTree () {
id<FNode> value;
NSMutableDictionary* children;
}
@end
@implementation FSparseSnapshotTree
- (id) init {
self = [super init];
if (self) {
value = nil;
children = nil;
}
return self;
}
- (id<FNode>) findPath:(FPath *)path {
if (value != nil) {
return [value getChild:path];
} else if (![path isEmpty] && children != nil) {
NSString* childKey = [path getFront];
path = [path popFront];
FSparseSnapshotTree* childTree = children[childKey];
if (childTree != nil) {
return [childTree findPath:path];
} else {
return nil;
}
} else {
return nil;
}
}
- (void) rememberData:(id<FNode>)data onPath:(FPath *)path {
if ([path isEmpty]) {
value = data;
children = nil;
} else if (value != nil) {
value = [value updateChild:path withNewChild:data];
} else {
if (children == nil) {
children = [[NSMutableDictionary alloc] init];
}
NSString* childKey = [path getFront];
if (children[childKey] == nil) {
children[childKey] = [[FSparseSnapshotTree alloc] init];
}
FSparseSnapshotTree* child = children[childKey];
path = [path popFront];
[child rememberData:data onPath:path];
}
}
- (BOOL) forgetPath:(FPath *)path {
if ([path isEmpty]) {
value = nil;
children = nil;
return YES;
} else {
if (value != nil) {
if ([value isLeafNode]) {
// non-empty path at leaf. the path leads to nowhere
return NO;
} else {
id<FNode> tmp = value;
value = nil;
[tmp enumerateChildrenUsingBlock:^(NSString *key, id<FNode> node, BOOL *stop) {
[self rememberData:node onPath:[[FPath alloc] initWith:key]];
}];
// we've cleared out the value and set children. Call ourself again to hit the next case
return [self forgetPath:path];
}
} else if (children != nil) {
NSString* childKey = [path getFront];
path = [path popFront];
if (children[childKey] != nil) {
FSparseSnapshotTree* child = children[childKey];
BOOL safeToRemove = [child forgetPath:path];
if (safeToRemove) {
[children removeObjectForKey:childKey];
}
}
if ([children count] == 0) {
children = nil;
return YES;
} else {
return NO;
}
} else {
return YES;
}
}
}
- (void) forEachTreeAtPath:(FPath *)prefixPath do:(fbt_void_path_node)func {
if (value != nil) {
func(prefixPath, value);
} else {
[self forEachChild:^(NSString* key, FSparseSnapshotTree* tree) {
FPath* path = [prefixPath childFromString:key];
[tree forEachTreeAtPath:path do:func];
}];
}
}
- (void) forEachChild:(fbt_void_nsstring_sstree)func {
if (children != nil) {
for (NSString* key in children) {
FSparseSnapshotTree* tree = [children objectForKey:key];
func(key, tree);
}
}
}
@end

Some files were not shown because too many files have changed in this diff Show More