+
+/// @file FIRInteropParameterNames.h
+///
+/// Predefined event parameter names used by Firebase. This file is a subset of the
+/// FirebaseAnalytics FIRParameterNames.h public header.
+///
+/// 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).
+///
+/// NSDictionary *params = @{
+/// kFIRParameterSource : @"InMobi",
+/// // ...
+/// };
+///
+static NSString *const kFIRIParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source";
+
+/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended
+/// (NSString).
+///
+/// NSDictionary *params = @{
+/// kFIRParameterMedium : @"email",
+/// // ...
+/// };
+///
+static NSString *const kFIRIParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium";
+
+/// 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).
+///
+/// NSDictionary *params = @{
+/// kFIRParameterCampaign : @"winter_promotion",
+/// // ...
+/// };
+///
+static NSString *const kFIRIParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) =
+ @"campaign";
+
+/// Message identifier.
+static NSString *const kFIRIParameterMessageIdentifier = @"_nmid";
+
+/// Message name.
+static NSString *const kFIRIParameterMessageName = @"_nmn";
+
+/// Message send time.
+static NSString *const kFIRIParameterMessageTime = @"_nmt";
+
+/// Message device time.
+static NSString *const kFIRIParameterMessageDeviceTime = @"_ndt";
+
+/// Topic message.
+static NSString *const kFIRIParameterTopic = @"_nt";
+
+/// Stores the message_id of the last notification opened by the app.
+static NSString *const kFIRIUserPropertyLastNotification = @"_ln";
diff --git a/ios/Pods/FirebaseAnalyticsInterop/LICENSE b/ios/Pods/FirebaseAnalyticsInterop/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/ios/Pods/FirebaseAnalyticsInterop/LICENSE
@@ -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.
diff --git a/ios/Pods/FirebaseAnalyticsInterop/README.md b/ios/Pods/FirebaseAnalyticsInterop/README.md
new file mode 100644
index 00000000..bfaceebc
--- /dev/null
+++ b/ios/Pods/FirebaseAnalyticsInterop/README.md
@@ -0,0 +1,193 @@
+# Firebase iOS Open Source Development [](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, FirebaseInAppMessagingDisplay, FirebaseMessaging and
+FirebaseStorage.
+
+The repository also includes GoogleUtilities source. The
+[GoogleUtilities](GoogleUtilities/README.md) pod is
+a set of utilities used by Firebase and other Google products.
+
+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.
+
+#### Accessing Firebase Source Snapshots
+
+All of the official releases are tagged in this repo and available via CocoaPods. To access a local
+source snapshot or unreleased branch, use Podfile directives like the following:
+
+To access FirebaseFirestore via a branch:
+```
+pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+```
+
+To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
+
+```
+pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
+pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
+```
+
+### Carthage (iOS only)
+
+Instructions for the experimental Carthage distribution are at
+[Carthage](Carthage.md).
+
+### Rome
+
+Instructions for installing binary frameworks via
+[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.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).
+
+### Code Formatting
+
+Travis will verify that any code changes are done in a style compliant way. Install
+`clang-format` and `swiftformat`.
+This command will get the right `clang-format` version:
+
+`brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb`
+
+### 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
+[the Auth Sample README](Example/Auth/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#accessing-firebase-source-snapshots).
+
+Note that the Firebase pod is not available for macOS and tvOS. Install a selection of the
+`FirebaseAuth`, `FirebaseCore`, `FirebaseDatabase` and `FirebaseStorage` CocoaPods.
+
+## 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/).
diff --git a/ios/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h b/ios/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h
new file mode 100644
index 00000000..5c365a3c
--- /dev/null
+++ b/ios/Pods/FirebaseAuthInterop/Interop/Auth/Public/FIRAuthInterop.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 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 FIRAuthInterop_h
+#define FIRAuthInterop_h
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** @typedef FIRTokenCallback
+ @brief The type of block which gets called when a token is ready.
+ */
+typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error)
+ NS_SWIFT_NAME(TokenCallback);
+
+/// Common methods for Auth interoperability.
+NS_SWIFT_NAME(AuthInterop)
+@protocol FIRAuthInterop
+
+/// Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
+- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback;
+
+/// Get the current Auth user's UID. Returns nil if there is no user signed in.
+- (nullable NSString *)getUserID;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* FIRAuthInterop_h */
diff --git a/ios/Pods/FirebaseAuthInterop/LICENSE b/ios/Pods/FirebaseAuthInterop/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/ios/Pods/FirebaseAuthInterop/LICENSE
@@ -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.
diff --git a/ios/Pods/FirebaseAuthInterop/README.md b/ios/Pods/FirebaseAuthInterop/README.md
new file mode 100644
index 00000000..4414b3e3
--- /dev/null
+++ b/ios/Pods/FirebaseAuthInterop/README.md
@@ -0,0 +1,179 @@
+# Firebase iOS Open Source Development [](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.
+
+The repository also includes GoogleUtilities source. The
+[GoogleUtilities](GoogleUtilities/README.md) pod is
+a set of utilities used by Firebase and other Google products.
+
+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.
+
+#### Accessing Firebase Source Snapshots
+
+All of the official releases are tagged in this repo and available via CocoaPods. To access a local
+source snapshot or unreleased branch, use Podfile directives like the following:
+
+To access FirebaseFirestore via a branch:
+```
+pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
+```
+
+To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
+
+```
+pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
+pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
+```
+
+### 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#accessing-firebase-source-snapshots).
+
+Note that the Firebase pod is not available for macOS and tvOS. Install a selection of the
+`FirebaseAuth`, `FirebaseCore`, `FirebaseDatabase` and `FirebaseStorage` CocoaPods.
+
+## 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/).
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m
new file mode 100644
index 00000000..b7744939
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDataSnapshot.m
@@ -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 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 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 *) 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m
new file mode 100644
index 00000000..1a2ba888
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabase.m
@@ -0,0 +1,211 @@
+/*
+ * 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
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#import "FIRDatabase.h"
+#import "FIRDatabaseComponent.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FIRDatabaseReference_Private.h"
+#import "FIRDatabase_Private.h"
+#import "FRepoInfo.h"
+#import "FValidation.h"
+
+@implementation FIRDatabase
+
+// 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);
+
++ (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."];
+ }
+ return [FIRDatabase databaseForApp:[FIRApp defaultApp]];
+}
+
++ (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."];
+ }
+ id provider = FIR_COMPONENT(FIRDatabaseProvider, app.container);
+ return [provider databaseForApp:app URL:url];
+}
+
++ (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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h
new file mode 100644
index 00000000..37840a1f
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 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
+
+@class FIRApp;
+@class FIRDatabase;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// This protocol is used in the interop registration process to register an instance provider for
+/// individual FIRApps.
+@protocol FIRDatabaseProvider
+
+/// Gets a FirebaseDatabase instance for the specified URL, using the specified FirebaseApp.
+- (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url;
+
+@end
+
+/// A concrete implementation for FIRDatabaseProvider to create Database instances.
+@interface FIRDatabaseComponent : NSObject
+
+/// The FIRApp that instances will be set up with.
+@property(nonatomic, weak, readonly) FIRApp *app;
+
+/// Unavailable, use `databaseForApp:URL:` instead.
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m
new file mode 100644
index 00000000..eee0b337
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseComponent.m
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2018 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 "FIRDatabaseComponent.h"
+
+#import "FIRDatabase_Private.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "FRepoManager.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** A NSMutableDictionary of FirebaseApp name and FRepoInfo to FirebaseDatabase instance. */
+typedef NSMutableDictionary FIRDatabaseDictionary;
+
+@interface FIRDatabaseComponent ()
+@property (nonatomic) FIRDatabaseDictionary *instances;
+/// Internal intializer.
+- (instancetype)initWithApp:(FIRApp *)app;
+@end
+
+@implementation FIRDatabaseComponent
+
+#pragma mark - Initialization
+
+- (instancetype)initWithApp:(FIRApp *)app {
+ self = [super init];
+ if (self) {
+ _app = app;
+ _instances = [NSMutableDictionary dictionary];
+ }
+ return self;
+}
+
+#pragma mark - Lifecycle
+
++ (void)load {
+ [FIRComponentContainer registerAsComponentRegistrant:self];
+}
+
+#pragma mark - FIRComponentRegistrant
+
++ (NSArray *)componentsToRegister {
+ FIRDependency *authDep =
+ [FIRDependency dependencyWithProtocol:@protocol(FIRAuthInterop) isRequired:NO];
+ FIRComponentCreationBlock creationBlock =
+ ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
+ *isCacheable = YES;
+ return [[FIRDatabaseComponent alloc] initWithApp:container.app];
+ };
+ FIRComponent *databaseProvider =
+ [FIRComponent componentWithProtocol:@protocol(FIRDatabaseProvider)
+ instantiationTiming:FIRInstantiationTimingLazy
+ dependencies:@[ authDep ]
+ creationBlock:creationBlock];
+ return @[ databaseProvider ];
+}
+
+#pragma mark - Instance management.
+
+- (void)appWillBeDeleted:(FIRApp *)app {
+ NSString *appName = app.name;
+ if (appName == nil) {
+ return;
+ }
+ FIRDatabaseDictionary* instances = [self instances];
+ @synchronized (instances) {
+ // 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 [instances allValues]) {
+ [FRepoManager disposeRepos:database.config];
+ }
+ [instances removeAllObjects];
+ }
+}
+
+#pragma mark - FIRDatabaseProvider Conformance
+
+
+- (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) {
+ FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl.absoluteString];
+ NSString *urlIndex =
+ [NSString stringWithFormat:@"%@:%@", parsedUrl.repoInfo.host, [parsedUrl.path toString]];
+ FIRDatabase *database = instances[urlIndex];
+ if (!database) {
+ id authTokenProvider =
+ [FAuthTokenProvider authTokenProviderWithAuth:
+ FIR_COMPONENT(FIRAuthInterop, app.container)];
+
+ // 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];
+ instances[urlIndex] = database;
+ }
+
+ return database;
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h
new file mode 100644
index 00000000..d41f3a81
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.h
@@ -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
+
+@protocol FAuthTokenProvider;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * TODO: Merge FIRDatabaseConfig into FIRDatabase.
+ */
+@interface FIRDatabaseConfig : NSObject
+
+- (id)initWithSessionIdentifier:(NSString *)identifier authTokenProvider:(id)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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m
new file mode 100644
index 00000000..a49d4a0a
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseConfig.m
@@ -0,0 +1,84 @@
+/*
+ * 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 "FIRDatabaseConfig.h"
+
+#import "FAuthTokenProvider.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "FIRNoopAuthTokenProvider.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)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)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;
+}
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m
new file mode 100644
index 00000000..de18a7c0
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRDatabaseQuery.m
@@ -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)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 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 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 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 >= 1ul<<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 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m
new file mode 100644
index 00000000..77a022ab
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRMutableData.m
@@ -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)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 node = [FSnapshotUtilities nodeFrom:aValue withValidationFrom:@"setValue:"];
+ [self.data updateSnapshot:self.prefixPath withNewSnapshot:node];
+}
+
+- (void) setPriority:(id)aPriority {
+ id node = [self.data getNode:self.prefixPath];
+ id 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 node = [self.data getNode:self.prefixPath];
+ return ![node isLeafNode] && ![(FChildrenNode*)node isEmpty];
+}
+
+- (BOOL) hasChildAtPath:(NSString *)path {
+ id 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) nodeValue {
+ return [self.data getNode:self.prefixPath];
+}
+
+- (NSEnumerator *) 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m
new file mode 100644
index 00000000..14bb745f
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRServerValue.m
@@ -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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m
new file mode 100644
index 00000000..8afc5b7b
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/FIRTransactionResult.m
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h
new file mode 100644
index 00000000..c2d3871b
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h
@@ -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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h
new file mode 100644
index 00000000..3a10fe3e
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseQuery_Private.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h
new file mode 100644
index 00000000..6063cb83
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabaseReference_Private.h
@@ -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 "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;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h
new file mode 100644
index 00000000..5e294519
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRDatabase_Private.h
@@ -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 "FIRDatabase.h"
+
+@class FRepo;
+@class FRepoInfo;
+@class FIRDatabaseConfig;
+
+@interface FIRDatabase ()
+
+@property (nonatomic, strong) FRepoInfo *repoInfo;
+@property (nonatomic, strong) FIRDatabaseConfig *config;
+@property (nonatomic, strong) FRepo *repo;
+
+- (id)initWithApp:(FIRApp *)app repoInfo:(FRepoInfo *)info config:(FIRDatabaseConfig *)config;
+
++ (NSString *) buildVersion;
++ (FIRDatabase *) createDatabaseForTests:(FRepoInfo *)repoInfo config:(FIRDatabaseConfig *)config;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h
new file mode 100644
index 00000000..ee3aa967
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRMutableData_Private.h
@@ -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)node;
+- (id) nodeValue;
+- (BOOL) isEqualToData:(FIRMutableData *)other;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h
new file mode 100644
index 00000000..82290f27
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FIRTransactionResult_Private.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRTransactionResult.h"
+#import "FIRMutableData.h"
+
+@interface FIRTransactionResult ()
+
+@property (nonatomic) BOOL isSuccess;
+@property (nonatomic, strong) FIRMutableData * update;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h
new file mode 100644
index 00000000..73f4c9a3
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Api/Private/FTypedefs_Private.h
@@ -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
+
+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);
+typedef void (^fbt_void_nsstring) (NSString *);
+typedef BOOL (^fbt_bool_nsstring_node) (NSString *, id);
+typedef void (^fbt_void_path_node_marray) (FPath *, id, 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h
new file mode 100644
index 00000000..e97a8a11
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.h
@@ -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
+
+#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”: “”}}
+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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m
new file mode 100644
index 00000000..e492ba1e
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Constants/FConstants.m
@@ -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”: “”}}
+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";
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h
new file mode 100644
index 00000000..cd5240e9
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.h
@@ -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
+
+#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)node;
++ (FCompoundHash *)fromNode:(id)node splitStrategy:(FCompoundHashSplitStrategy)strategy;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m
new file mode 100644
index 00000000..b4f72cd2
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FCompoundHash.m
@@ -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)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)node {
+ return [FCompoundHash fromNode:node splitStrategy:[FCompoundHash simpleSizeSplitStrategyForNode:node]];
+}
+
++ (FCompoundHash *)fromNode:(id)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)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 node, BOOL *stop) {
+ [builder startChild:key];
+ [self processNode:node builder:builder];
+ [builder endChild];
+ }];
+ }
+}
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h
new file mode 100644
index 00000000..7a41754c
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.h
@@ -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 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m
new file mode 100644
index 00000000..7a49609c
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FListenProvider.m
@@ -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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h
new file mode 100644
index 00000000..412c8747
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.h
@@ -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
+#import "FConnection.h"
+#import "FRepoInfo.h"
+#import "FTypedefs.h"
+#import "FTypedefs_Private.h"
+
+@protocol FPersistentConnectionDelegate;
+@protocol FSyncTreeHash;
+@class FQuerySpec;
+@class FIRDatabaseConfig;
+
+@interface FPersistentConnection : NSObject
+
+@property (nonatomic, weak) id 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)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
+
+- (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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m
new file mode 100644
index 00000000..f341bb10
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FPersistentConnection.m
@@ -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
+
+#import
+#import
+#import
+#import
+#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 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 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)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 != nil) {
+ [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* 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 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 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h
new file mode 100644
index 00000000..e9728e7b
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.h
@@ -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
+
+@protocol FIndex, FNodeFilter, FNode;
+
+@interface FQueryParams : NSObject
+
+@property (nonatomic, readonly) BOOL limitSet;
+@property (nonatomic, readonly) NSInteger limit;
+
+@property (nonatomic, strong, readonly) NSString *viewFrom;
+@property (nonatomic, strong, readonly) id indexStartValue;
+@property (nonatomic, strong, readonly) NSString *indexStartKey;
+@property (nonatomic, strong, readonly) id indexEndValue;
+@property (nonatomic, strong, readonly) NSString *indexEndKey;
+
+@property (nonatomic, strong, readonly) id index;
+
+- (BOOL)loadsAllData;
+- (BOOL)isDefault;
+- (BOOL)isValid;
+- (BOOL)hasAnchoredLimit;
+
+- (FQueryParams *) limitTo:(NSInteger) limit;
+- (FQueryParams *) limitToFirst:(NSInteger) newLimit;
+- (FQueryParams *) limitToLast:(NSInteger) newLimit;
+
+- (FQueryParams *) startAt:(id)indexValue childKey:(NSString *)key;
+- (FQueryParams *) startAt:(id)indexValue;
+- (FQueryParams *) endAt:(id)indexValue childKey:(NSString *)key;
+- (FQueryParams *) endAt:(id)indexValue;
+
+- (FQueryParams *) orderBy:(id) index;
+
++ (FQueryParams *) defaultInstance;
++ (FQueryParams *) fromQueryObject:(NSDictionary *)dict;
+
+- (BOOL)hasStart;
+- (BOOL)hasEnd;
+
+- (NSDictionary *) wireProtocolParams;
+- (BOOL) isViewFromLeft;
+- (id) nodeFilter;
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m
new file mode 100644
index 00000000..79203580
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQueryParams.m
@@ -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 indexStartValue;
+@property (nonatomic, strong, readwrite) NSString *indexStartKey;
+/**
+* indexStartValue is anything you can store as a priority / value.
+*/
+@property (nonatomic, strong, readwrite) id indexEndValue;
+@property (nonatomic, strong, readwrite) NSString *indexEndKey;
+
+@property (nonatomic, strong, readwrite) id 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)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)indexValue {
+ return [self startAt:indexValue childKey:nil];
+}
+
+- (FQueryParams *) endAt:(id)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)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) 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h
new file mode 100644
index 00000000..49ed5367
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.h
@@ -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
+
+#import "FQueryParams.h"
+#import "FPath.h"
+#import "FIndex.h"
+
+@interface FQuerySpec : NSObject
+
+@property (nonatomic, strong, readonly) FPath* path;
+@property (nonatomic, strong, readonly) FQueryParams *params;
+
+- (id)initWithPath:(FPath *)path params:(FQueryParams *)params;
+
++ (FQuerySpec *)defaultQueryAtPath:(FPath *)path;
+
+- (id)index;
+- (BOOL)isDefault;
+- (BOOL)loadsAllData;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m
new file mode 100644
index 00000000..24be4338
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FQuerySpec.m
@@ -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)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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h
new file mode 100644
index 00000000..8825e0e0
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.h
@@ -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
+
+#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)updates;
+
+- (id)applyToNode:(id)node;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m
new file mode 100644
index 00000000..8bc67bf3
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRangeMerge.m
@@ -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 updates;
+
+@end
+
+@implementation FRangeMerge
+
+- (instancetype)initWithStart:(FPath *)start end:(FPath *)end updates:(id)updates {
+ self = [super init];
+ if (self != nil) {
+ self->_optExclusiveStart = start;
+ self->_optInclusiveEnd = end;
+ self->_updates = updates;
+ }
+ return self;
+}
+
+- (id)applyToNode:(id)node {
+ return [self updateRangeInNode:[FPath empty] node:node updates:self.updates];
+}
+
+- (id)updateRangeInNode:(FPath *)currentPath node:(id)node updates:(id)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 node, BOOL *stop) {
+ [allChildren addObject:key];
+ }];
+ [updates enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) {
+ [allChildren addObject:key];
+ }];
+
+ __block id newNode = node;
+ void (^action)(id, BOOL *) = ^void(NSString *key, BOOL *stop) {
+ id currentChild = [node getImmediateChild:key];
+ id 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h
new file mode 100644
index 00000000..ab0b0745
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.h
@@ -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
+#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
+
+@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)eventRegistration forQuery:(FQuerySpec *)query;
+- (void) removeEventRegistration:(id)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)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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m
new file mode 100644
index 00000000..ae1d8e81
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo.m
@@ -0,0 +1,1119 @@
+/*
+ * 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
+
+#import
+#import
+#import "FRepo.h"
+#import "FSnapshotUtilities.h"
+#import "FConstants.h"
+#import "FIRDatabaseQuery_Private.h"
+#import "FQuerySpec.h"
+#import "FTupleNodePath.h"
+#import "FRepo_Private.h"
+#import "FRepoManager.h"
+#import "FServerValues.h"
+#import "FTupleSetIdPath.h"
+#import "FSyncTree.h"
+#import "FEventRegistration.h"
+#import "FAtomicNumber.h"
+#import "FSyncTree.h"
+#import "FListenProvider.h"
+#import "FEventRaiser.h"
+#import "FSnapshotHolder.h"
+#import "FIRDatabaseConfig_Private.h"
+#import "FLevelDBStorageEngine.h"
+#import "FPersistenceManager.h"
+#import "FWriteRecord.h"
+#import "FCachePolicy.h"
+#import "FClock.h"
+#import "FIRDatabase_Private.h"
+#import "FTree.h"
+#import "FTupleTransaction.h"
+#import "FIRTransactionResult.h"
+#import "FIRTransactionResult_Private.h"
+#import "FIRMutableData.h"
+#import "FIRMutableData_Private.h"
+#import "FIRDataSnapshot.h"
+#import "FIRDataSnapshot_Private.h"
+#import "FValueEventRegistration.h"
+#import "FEmptyNode.h"
+
+#if TARGET_OS_IOS || TARGET_OS_TV
+#import
+#endif
+
+@interface FRepo()
+
+@property (nonatomic, strong) FOffsetClock *serverClock;
+@property (nonatomic, strong) FPersistenceManager* persistenceManager;
+@property (nonatomic, strong) FIRDatabase *database;
+@property (nonatomic, strong, readwrite) FAuthenticationManager *auth;
+@property (nonatomic, strong) FSyncTree *infoSyncTree;
+@property (nonatomic) NSInteger writeIdCounter;
+@property (nonatomic) BOOL hijackHash;
+@property (nonatomic, strong) FTree *transactionQueueTree;
+@property (nonatomic) BOOL loggedTransactionPersistenceWarning;
+
+/**
+* Test only. For load testing the server.
+*/
+@property (nonatomic, strong) id (^interceptServerDataCallback)(NSString *pathString, id data);
+@end
+
+
+@implementation FRepo
+
+- (id)initWithRepoInfo:(FRepoInfo*)info config:(FIRDatabaseConfig *)config database:(FIRDatabase *)database {
+ self = [super init];
+ if (self) {
+ self.repoInfo = info;
+ self.config = config;
+ self.database = database;
+
+ // Access can occur outside of shared queue, so the clock needs to be initialized here
+ self.serverClock = [[FOffsetClock alloc] initWithClock:[FSystemClock clock] offset:0];
+
+ self.connection = [[FPersistentConnection alloc] initWithRepoInfo:self.repoInfo dispatchQueue:[FIRDatabaseQuery sharedQueue] config:self.config];
+
+ // Needs to be called before authentication manager is instantiated
+ self.eventRaiser = [[FEventRaiser alloc] initWithQueue:self.config.callbackQueue];
+
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ [self deferredInit];
+ });
+ }
+ return self;
+}
+
+- (void)deferredInit {
+ // TODO: cleanup on dealloc
+ __weak FRepo *weakSelf = self;
+ [self.config.authTokenProvider listenForTokenChanges:^(NSString *token) {
+ [weakSelf.connection refreshAuthToken:token];
+ }];
+
+ // Open connection now so that by the time we are connected the deferred init has run
+ // This relies on the fact that all callbacks run on repos queue
+ self.connection.delegate = self;
+ [self.connection open];
+
+ self.dataUpdateCount = 0;
+ self.rangeMergeUpdateCount = 0;
+ self.interceptServerDataCallback = nil;
+
+ if (self.config.persistenceEnabled) {
+ NSString* repoHashString = [NSString stringWithFormat:@"%@_%@", self.repoInfo.host, self.repoInfo.namespace];
+ NSString* persistencePrefix = [NSString stringWithFormat:@"%@/%@", self.config.sessionIdentifier, repoHashString];
+
+ id cachePolicy = [[FLRUCachePolicy alloc] initWithMaxSize:self.config.persistenceCacheSizeBytes];
+
+ id engine;
+ if (self.config.forceStorageEngine != nil) {
+ engine = self.config.forceStorageEngine;
+ } else {
+ FLevelDBStorageEngine *levelDBEngine = [[FLevelDBStorageEngine alloc] initWithPath:persistencePrefix];
+ // We need the repo info to run the legacy migration. Future migrations will be managed by the database itself
+ // Remove this once we are confident that no-one is using legacy migration anymore...
+ [levelDBEngine runLegacyMigration:self.repoInfo];
+ engine = levelDBEngine;
+ }
+
+ self.persistenceManager = [[FPersistenceManager alloc] initWithStorageEngine:engine cachePolicy:cachePolicy];
+ } else {
+ self.persistenceManager = nil;
+ }
+
+ [self initTransactions];
+
+ // A list of data pieces and paths to be set when this client disconnects
+ self.onDisconnect = [[FSparseSnapshotTree alloc] init];
+ self.infoData = [[FSnapshotHolder alloc] init];
+
+ FListenProvider *infoListenProvider = [[FListenProvider alloc] init];
+ infoListenProvider.startListening = ^(FQuerySpec *query,
+ NSNumber *tagId,
+ id hash,
+ fbt_nsarray_nsstring onComplete) {
+ NSArray *infoEvents = @[];
+ FRepo *strongSelf = weakSelf;
+ id node = [strongSelf.infoData getNode:query.path];
+ // This is possibly a hack, but we have different semantics for .info endpoints. We don't raise null events
+ // on initial data...
+ if (![node isEmpty]) {
+ infoEvents = [strongSelf.infoSyncTree applyServerOverwriteAtPath:query.path newData:node];
+ [strongSelf.eventRaiser raiseCallback:^{
+ onComplete(kFWPResponseForActionStatusOk);
+ }];
+ }
+ return infoEvents;
+ };
+ infoListenProvider.stopListening = ^(FQuerySpec *query, NSNumber *tagId) {};
+ self.infoSyncTree = [[FSyncTree alloc] initWithListenProvider:infoListenProvider];
+
+ FListenProvider *serverListenProvider = [[FListenProvider alloc] init];
+ serverListenProvider.startListening = ^(FQuerySpec *query,
+ NSNumber *tagId,
+ id hash,
+ fbt_nsarray_nsstring onComplete) {
+ [weakSelf.connection listen:query tagId:tagId hash:hash onComplete:^(NSString *status) {
+ NSArray *events = onComplete(status);
+ [weakSelf.eventRaiser raiseEvents:events];
+ }];
+ // No synchronous events for network-backed sync trees
+ return @[];
+ };
+ serverListenProvider.stopListening = ^(FQuerySpec *query, NSNumber *tag) {
+ [weakSelf.connection unlisten:query tagId:tag];
+ };
+ self.serverSyncTree = [[FSyncTree alloc] initWithPersistenceManager:self.persistenceManager
+ listenProvider:serverListenProvider];
+
+ [self restoreWrites];
+
+ [self updateInfo:kDotInfoConnected withValue:@NO];
+
+ [self setupNotifications];
+}
+
+
+- (void) restoreWrites {
+ NSArray *writes = self.persistenceManager.userWrites;
+
+ NSDictionary *serverValues = [FServerValues generateServerValues:self.serverClock];
+ __block NSInteger lastWriteId = NSIntegerMin;
+ [writes enumerateObjectsUsingBlock:^(FWriteRecord *write, NSUInteger idx, BOOL *stop) {
+ NSInteger writeId = write.writeId;
+ fbt_void_nsstring_nsstring callback = ^(NSString *status, NSString *errorReason) {
+ [self warnIfWriteFailedAtPath:write.path status:status message:@"Persisted write"];
+ [self ackWrite:writeId rerunTransactionsAtPath:write.path status:status];
+ };
+ if (lastWriteId >= writeId) {
+ [NSException raise:NSInternalInconsistencyException format:@"Restored writes were not in order!"];
+ }
+ lastWriteId = writeId;
+ self.writeIdCounter = writeId + 1;
+ if ([write isOverwrite]) {
+ FFLog(@"I-RDB038001", @"Restoring overwrite with id %ld", (long)write.writeId);
+ [self.connection putData:[write.overwrite valForExport:YES]
+ forPath:[write.path toString]
+ withHash:nil
+ withCallback:callback];
+ id resolved = [FServerValues resolveDeferredValueSnapshot:write.overwrite withServerValues:serverValues];
+ [self.serverSyncTree applyUserOverwriteAtPath:write.path newData:resolved writeId:writeId isVisible:YES];
+ } else {
+ FFLog(@"I-RDB038002", @"Restoring merge with id %ld", (long)write.writeId);
+ [self.connection mergeData:[write.merge valForExport:YES]
+ forPath:[write.path toString]
+ withCallback:callback];
+ FCompoundWrite *resolved = [FServerValues resolveDeferredValueCompoundWrite:write.merge withServerValues:serverValues];
+ [self.serverSyncTree applyUserMergeAtPath:write.path changedChildren:resolved writeId:writeId];
+ }
+ }];
+}
+
+- (NSString*)name {
+ return self.repoInfo.namespace;
+}
+
+- (NSString *) description {
+ return [self.repoInfo description];
+}
+
+- (void) interrupt {
+ [self.connection interruptForReason:kFInterruptReasonRepoInterrupt];
+}
+
+- (void) resume {
+ [self.connection resumeForReason:kFInterruptReasonRepoInterrupt];
+}
+
+// NOTE: Typically if you're calling this, you should be in an @autoreleasepool block to make sure that ARC kicks
+// in and cleans up things no longer referenced (i.e. pendingPutsDB).
+- (void) dispose {
+ [self.connection interruptForReason:kFInterruptReasonRepoInterrupt];
+
+ // We need to nil out any references to LevelDB, to make sure the
+ // LevelDB exclusive locks are released.
+ [self.persistenceManager close];
+}
+
+- (NSInteger) nextWriteId {
+ return self->_writeIdCounter++;
+}
+
+- (NSTimeInterval) serverTime {
+ return [self.serverClock currentTime];
+}
+
+- (void) set:(FPath *)path withNode:(id)node withCallback:(fbt_void_nserror_ref)onComplete {
+ id value = [node valForExport:YES];
+ FFLog(@"I-RDB038003", @"Setting: %@ with %@ pri: %@", [path toString], [value description], [[node getPriority] val]);
+
+ // TODO: Optimize this behavior to either (a) store flag to skip resolving where possible and / or
+ // (b) store unresolved paths on JSON parse
+ NSDictionary* serverValues = [FServerValues generateServerValues:self.serverClock];
+ id newNode = [FServerValues resolveDeferredValueSnapshot:node withServerValues:serverValues];
+
+ NSInteger writeId = [self nextWriteId];
+ [self.persistenceManager saveUserOverwrite:node atPath:path writeId:writeId];
+ NSArray *events = [self.serverSyncTree applyUserOverwriteAtPath:path newData:newNode writeId:writeId isVisible:YES];
+ [self.eventRaiser raiseEvents:events];
+
+ [self.connection putData:value forPath:[path toString] withHash:nil withCallback:^(NSString *status, NSString *errorReason) {
+ [self warnIfWriteFailedAtPath:path status:status message:@"setValue: or removeValue:"];
+ [self ackWrite:writeId rerunTransactionsAtPath:path status:status];
+ [self callOnComplete:onComplete withStatus:status errorReason:errorReason andPath:path];
+ }];
+
+ FPath* affectedPath = [self abortTransactionsAtPath:path error:kFTransactionSet];
+ [self rerunTransactionsForPath:affectedPath];
+}
+
+- (void) update:(FPath *)path withNodes:(FCompoundWrite *)nodes withCallback:(fbt_void_nserror_ref)callback {
+ NSDictionary *values = [nodes valForExport:YES];
+
+ FFLog(@"I-RDB038004", @"Updating: %@ with %@", [path toString], [values description]);
+ NSDictionary* serverValues = [FServerValues generateServerValues:self.serverClock];
+ FCompoundWrite *resolved = [FServerValues resolveDeferredValueCompoundWrite:nodes withServerValues:serverValues];
+
+ if (!resolved.isEmpty) {
+ NSInteger writeId = [self nextWriteId];
+ [self.persistenceManager saveUserMerge:nodes atPath:path writeId:writeId];
+ NSArray *events = [self.serverSyncTree applyUserMergeAtPath:path changedChildren:resolved writeId:writeId];
+ [self.eventRaiser raiseEvents:events];
+
+ [self.connection mergeData:values forPath:[path description] withCallback:^(NSString *status, NSString *errorReason) {
+ [self warnIfWriteFailedAtPath:path status:status message:@"updateChildValues:"];
+ [self ackWrite:writeId rerunTransactionsAtPath:path status:status];
+ [self callOnComplete:callback withStatus:status errorReason:errorReason andPath:path];
+ }];
+
+ [nodes enumerateWrites:^(FPath *childPath, id node, BOOL *stop) {
+ FPath* pathFromRoot = [path child:childPath];
+ FFLog(@"I-RDB038005", @"Cancelling transactions at path: %@", pathFromRoot);
+ FPath *affectedPath = [self abortTransactionsAtPath:pathFromRoot error:kFTransactionSet];
+ [self rerunTransactionsForPath:affectedPath];
+ }];
+ } else {
+ FFLog(@"I-RDB038006", @"update called with empty data. Doing nothing");
+ // Do nothing, just call the callback
+ [self callOnComplete:callback withStatus:@"ok" errorReason:nil andPath:path];
+ }
+}
+
+- (void) onDisconnectCancel:(FPath *)path withCallback:(fbt_void_nserror_ref)callback {
+ [self.connection onDisconnectCancelPath:path withCallback:^(NSString *status, NSString *errorReason) {
+ BOOL success = [status isEqualToString:kFWPResponseForActionStatusOk];
+ if (success) {
+ [self.onDisconnect forgetPath:path];
+ } else {
+ FFLog(@"I-RDB038007", @"cancelDisconnectOperations: at %@ failed: %@", path, status);
+ }
+
+ [self callOnComplete:callback withStatus:status errorReason:errorReason andPath:path];
+ }];
+}
+
+- (void) onDisconnectSet:(FPath *)path withNode:(id)node withCallback:(fbt_void_nserror_ref)callback {
+ [self.connection onDisconnectPutData:[node valForExport:YES] forPath:path withCallback:^(NSString *status, NSString *errorReason) {
+ BOOL success = [status isEqualToString:kFWPResponseForActionStatusOk];
+ if (success) {
+ [self.onDisconnect rememberData:node onPath:path];
+ } else {
+ FFWarn(@"I-RDB038008", @"onDisconnectSetValue: or onDisconnectRemoveValue: at %@ failed: %@", path, status);
+ }
+
+ [self callOnComplete:callback withStatus:status errorReason:errorReason andPath:path];
+ }];
+}
+
+- (void) onDisconnectUpdate:(FPath *)path withNodes:(FCompoundWrite *)nodes withCallback:(fbt_void_nserror_ref)callback {
+ if (!nodes.isEmpty) {
+ NSDictionary *values = [nodes valForExport:YES];
+
+ [self.connection onDisconnectMergeData:values forPath:path withCallback:^(NSString *status, NSString *errorReason) {
+ BOOL success = [status isEqualToString:kFWPResponseForActionStatusOk];
+ if (success) {
+ [nodes enumerateWrites:^(FPath *relativePath, id nodeUnresolved, BOOL *stop) {
+ FPath* childPath = [path child:relativePath];
+ [self.onDisconnect rememberData:nodeUnresolved onPath:childPath];
+ }];
+ } else {
+ FFWarn(@"I-RDB038009", @"onDisconnectUpdateChildValues: at %@ failed %@", path, status);
+ }
+
+ [self callOnComplete:callback withStatus:status errorReason:errorReason andPath:path];
+ }];
+ } else {
+ // Do nothing, just call the callback
+ [self callOnComplete:callback withStatus:@"ok" errorReason:nil andPath:path];
+ }
+}
+
+- (void) purgeOutstandingWrites {
+ FFLog(@"I-RDB038010", @"Purging outstanding writes");
+ NSArray *events = [self.serverSyncTree removeAllWrites];
+ [self.eventRaiser raiseEvents:events];
+ // Abort any transactions
+ [self abortTransactionsAtPath:[FPath empty] error:kFErrorWriteCanceled];
+ // Remove outstanding writes from connection
+ [self.connection purgeOutstandingWrites];
+}
+
+- (void) addEventRegistration:(id )eventRegistration forQuery:(FQuerySpec *)query {
+ NSArray *events = nil;
+ if ([[query.path getFront] isEqualToString:kDotInfoPrefix]) {
+ events = [self.infoSyncTree addEventRegistration:eventRegistration forQuery:query];
+ } else {
+ events = [self.serverSyncTree addEventRegistration:eventRegistration forQuery:query];
+ }
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (void) removeEventRegistration:(id)eventRegistration forQuery:(FQuerySpec *)query {
+ // These are guaranteed not to raise events, since we're not passing in a cancelError. However we can future-proof
+ // a little bit by handling the return values anyways.
+ FFLog(@"I-RDB038011", @"Removing event registration with hande: %lu", (unsigned long)eventRegistration.handle);
+ NSArray *events = nil;
+ if ([[query.path getFront] isEqualToString:kDotInfoPrefix]) {
+ events = [self.infoSyncTree removeEventRegistration:eventRegistration forQuery:query cancelError:nil];
+ } else {
+ events = [self.serverSyncTree removeEventRegistration:eventRegistration forQuery:query cancelError:nil];
+ }
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (void) keepQuery:(FQuerySpec *)query synced:(BOOL)synced {
+ NSAssert(![[query.path getFront] isEqualToString:kDotInfoPrefix], @"Can't keep .info tree synced!");
+ [self.serverSyncTree keepQuery:query synced:synced];
+}
+
+- (void) updateInfo:(NSString *) pathString withValue:(id)value {
+ // hack to make serverTimeOffset available in a threadsafe way. Property is marked as atomic
+ if ([pathString isEqualToString:kDotInfoServerTimeOffset]) {
+ NSTimeInterval offset = [(NSNumber *)value doubleValue]/1000.0;
+ self.serverClock = [[FOffsetClock alloc] initWithClock:[FSystemClock clock] offset:offset];
+ }
+
+ FPath* path = [[FPath alloc] initWith:[NSString stringWithFormat:@"%@/%@", kDotInfoPrefix, pathString]];
+ id newNode = [FSnapshotUtilities nodeFrom:value];
+ [self.infoData updateSnapshot:path withNewSnapshot:newNode];
+ NSArray *events = [self.infoSyncTree applyServerOverwriteAtPath:path newData:newNode];
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (void) callOnComplete:(fbt_void_nserror_ref)onComplete withStatus:(NSString *)status errorReason:(NSString *)errorReason andPath:(FPath *)path {
+ if (onComplete) {
+ FIRDatabaseReference * ref = [[FIRDatabaseReference alloc] initWithRepo:self path:path];
+ BOOL statusOk = [status isEqualToString:kFWPResponseForActionStatusOk];
+ NSError* err = nil;
+ if (!statusOk) {
+ err = [FUtilities errorForStatus:status andReason:errorReason];
+ }
+ [self.eventRaiser raiseCallback:^{
+ onComplete(err, ref);
+ }];
+ }
+}
+
+- (void)ackWrite:(NSInteger)writeId rerunTransactionsAtPath:(FPath *)path status:(NSString *)status {
+ if ([status isEqualToString:kFErrorWriteCanceled]) {
+ // This write was already removed, we just need to ignore it...
+ } else {
+ BOOL success = [status isEqualToString:kFWPResponseForActionStatusOk];
+ NSArray *clearEvents = [self.serverSyncTree ackUserWriteWithWriteId:writeId revert:!success persist:YES clock:self.serverClock];
+ if ([clearEvents count] > 0) {
+ [self rerunTransactionsForPath:path];
+ }
+ [self.eventRaiser raiseEvents:clearEvents];
+ }
+}
+
+- (void) warnIfWriteFailedAtPath:(FPath *)path status:(NSString *)status message:(NSString *)message {
+ if (!([status isEqualToString:kFWPResponseForActionStatusOk] || [status isEqualToString:kFErrorWriteCanceled])) {
+ FFWarn(@"I-RDB038012", @"%@ at %@ failed: %@", message, path, status);
+ }
+}
+
+#pragma mark -
+#pragma mark FPersistentConnectionDelegate methods
+
+- (void) onDataUpdate:(FPersistentConnection *)fpconnection forPath:(NSString *)pathString message:(id)data isMerge:(BOOL)isMerge tagId:(NSNumber *)tagId {
+ FFLog(@"I-RDB038013", @"onDataUpdateForPath: %@ withMessage: %@", pathString, data);
+
+ // For testing.
+ self.dataUpdateCount++;
+
+ FPath* path = [[FPath alloc] initWith:pathString];
+ data = self.interceptServerDataCallback ? self.interceptServerDataCallback(pathString, data) : data;
+ NSArray *events = nil;
+
+ if (tagId != nil) {
+ if (isMerge) {
+ NSDictionary *message = data;
+ FCompoundWrite *taggedChildren = [FCompoundWrite compoundWriteWithValueDictionary:message];
+ events = [self.serverSyncTree applyTaggedQueryMergeAtPath:path changedChildren:taggedChildren tagId:tagId];
+ } else {
+ id taggedSnap = [FSnapshotUtilities nodeFrom:data];
+ events = [self.serverSyncTree applyTaggedQueryOverwriteAtPath:path newData:taggedSnap tagId:tagId];
+ }
+ } else if (isMerge) {
+ NSDictionary *message = data;
+ FCompoundWrite *changedChildren = [FCompoundWrite compoundWriteWithValueDictionary:message];
+ events = [self.serverSyncTree applyServerMergeAtPath:path changedChildren:changedChildren];
+ } else {
+ id snap = [FSnapshotUtilities nodeFrom:data];
+ events = [self.serverSyncTree applyServerOverwriteAtPath:path newData:snap];
+ }
+
+ if ([events count] > 0) {
+ // Since we have a listener outstanding for each transaction, receiving any events
+ // is a proxy for some change having occurred.
+ [self rerunTransactionsForPath:path];
+ }
+
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (void)onRangeMerge:(NSArray *)ranges forPath:(NSString *)pathString tagId:(NSNumber *)tag {
+ FFLog(@"I-RDB038014", @"onRangeMerge: %@ => %@", pathString, ranges);
+
+ // For testing
+ self.rangeMergeUpdateCount++;
+
+ FPath* path = [[FPath alloc] initWith:pathString];
+ NSArray *events;
+ if (tag != nil) {
+ events = [self.serverSyncTree applyTaggedServerRangeMergeAtPath:path updates:ranges tagId:tag];
+ } else {
+ events = [self.serverSyncTree applyServerRangeMergeAtPath:path updates:ranges];
+ }
+ if (events.count > 0) {
+ // Since we have a listener outstanding for each transaction, receiving any events
+ // is a proxy for some change having occurred.
+ [self rerunTransactionsForPath:path];
+ }
+
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (void)onConnect:(FPersistentConnection *)fpconnection {
+ [self updateInfo:kDotInfoConnected withValue:@YES];
+}
+
+- (void)onDisconnect:(FPersistentConnection *)fpconnection {
+ [self updateInfo:kDotInfoConnected withValue:@NO];
+ [self runOnDisconnectEvents];
+}
+
+- (void)onServerInfoUpdate:(FPersistentConnection *)fpconnection updates:(NSDictionary *)updates {
+ for (NSString* key in updates) {
+ id val = [updates objectForKey:key];
+ [self updateInfo:key withValue:val];
+ }
+}
+
+- (void) setupNotifications {
+ NSString * const *backgroundConstant = (NSString * const *) dlsym(RTLD_DEFAULT, "UIApplicationDidEnterBackgroundNotification");
+ if (backgroundConstant) {
+ FFLog(@"I-RDB038015", @"Registering for background notification.");
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(didEnterBackground)
+ name:*backgroundConstant
+ object:nil];
+ } else {
+ FFLog(@"I-RDB038016", @"Skipped registering for background notification.");
+ }
+}
+
+- (void) didEnterBackground {
+ if (!self.config.persistenceEnabled)
+ return;
+
+ // Targetted compilation is ONLY for testing. UIKit is weak-linked in actual release build.
+ #if TARGET_OS_IOS || TARGET_OS_TV
+ // The idea is to wait until any outstanding sets get written to disk. Since the sets might still be in our
+ // dispatch queue, we wait for the dispatch queue to catch up and for persistence to catch up.
+ // This may be undesirable though. The dispatch queue might just be processing a bunch of incoming data or
+ // something. We might want to keep track of whether there are any unpersisted sets or something.
+ FFLog(@"I-RDB038017", @"Entering background. Starting background task to finish work.");
+ Class uiApplicationClass = NSClassFromString(@"UIApplication");
+ assert(uiApplicationClass); // If we are here, we should be on iOS and UIApplication should be available.
+
+ UIApplication *application = [uiApplicationClass sharedApplication];
+ __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
+ [application endBackgroundTask:bgTask];
+ }];
+
+ NSDate *start = [NSDate date];
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ NSTimeInterval finishTime = [start timeIntervalSinceNow]*-1;
+ FFLog(@"I-RDB038018", @"Background task completed. Queue time: %f", finishTime);
+ [application endBackgroundTask:bgTask];
+ });
+ #endif
+}
+
+#pragma mark -
+#pragma mark Internal methods
+
+/**
+* Applies all the changes stored up in the onDisconnect tree
+*/
+- (void) runOnDisconnectEvents {
+ FFLog(@"I-RDB038019", @"Running onDisconnectEvents");
+ NSDictionary* serverValues = [FServerValues generateServerValues:self.serverClock];
+ FSparseSnapshotTree* resolvedTree = [FServerValues resolveDeferredValueTree:self.onDisconnect withServerValues:serverValues];
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+
+ [resolvedTree forEachTreeAtPath:[FPath empty] do:^(FPath *path, id node) {
+ [events addObjectsFromArray:[self.serverSyncTree applyServerOverwriteAtPath:path newData:node]];
+ FPath* affectedPath = [self abortTransactionsAtPath:path error:kFTransactionSet];
+ [self rerunTransactionsForPath:affectedPath];
+ }];
+
+ self.onDisconnect = [[FSparseSnapshotTree alloc] init];
+ [self.eventRaiser raiseEvents:events];
+}
+
+- (NSDictionary *) dumpListens {
+ return [self.connection dumpListens];
+}
+
+#pragma mark -
+#pragma mark Transactions
+
+/**
+ * Setup the transaction data structures
+ */
+- (void) initTransactions {
+ self.transactionQueueTree = [[FTree alloc] init];
+ self.hijackHash = NO;
+ self.loggedTransactionPersistenceWarning = NO;
+}
+
+/**
+ * Creates a new transaction, add its to the transactions we're tracking, and sends it to the server if possible
+ */
+- (void) startTransactionOnPath:(FPath *)path update:(fbt_transactionresult_mutabledata)update onComplete:(fbt_void_nserror_bool_datasnapshot)onComplete withLocalEvents:(BOOL)applyLocally {
+ if (self.config.persistenceEnabled && !self.loggedTransactionPersistenceWarning) {
+ self.loggedTransactionPersistenceWarning = YES;
+ FFInfo(@"I-RDB038020", @"runTransactionBlock: usage detected while persistence is enabled. Please be aware that transactions "
+ @"*will not* be persisted across app restarts. "
+ @"See https://www.firebase.com/docs/ios/guide/offline-capabilities.html#section-handling-transactions-offline for more details.");
+ }
+
+ FIRDatabaseReference * watchRef = [[FIRDatabaseReference alloc] initWithRepo:self path:path];
+ // make sure we're listening on this node
+ // Note: we can't do this asynchronously. To preserve event ordering, it has to be done in this block.
+ // This is ok, this block is guaranteed to be our own event loop
+ NSUInteger handle = [[FUtilities LUIDGenerator] integerValue];
+ fbt_void_datasnapshot cb = ^(FIRDataSnapshot *snapshot) {};
+ FValueEventRegistration *registration = [[FValueEventRegistration alloc] initWithRepo:self
+ handle:handle
+ callback:cb
+ cancelCallback:nil];
+ [watchRef.repo addEventRegistration:registration forQuery:watchRef.querySpec];
+ fbt_void_void unwatcher = ^{ [watchRef removeObserverWithHandle:handle]; };
+
+ // Save all the data that represents this transaction
+ FTupleTransaction* transaction = [[FTupleTransaction alloc] init];
+ transaction.path = path;
+ transaction.update = update;
+ transaction.onComplete = onComplete;
+ transaction.status = FTransactionInitializing;
+ transaction.order = [FUtilities LUIDGenerator];
+ transaction.applyLocally = applyLocally;
+ transaction.retryCount = 0;
+ transaction.unwatcher = unwatcher;
+ transaction.currentWriteId = nil;
+ transaction.currentInputSnapshot = nil;
+ transaction.currentOutputSnapshotRaw = nil;
+ transaction.currentOutputSnapshotResolved = nil;
+
+ // Run transaction initially
+ id currentState = [self latestStateAtPath:path excludeWriteIds:nil];
+ transaction.currentInputSnapshot = currentState;
+ FIRMutableData * mutableCurrent = [[FIRMutableData alloc] initWithNode:currentState];
+ FIRTransactionResult * result = transaction.update(mutableCurrent);
+
+ if (!result.isSuccess) {
+ // Abort the transaction
+ transaction.unwatcher();
+ transaction.currentOutputSnapshotRaw = nil;
+ transaction.currentOutputSnapshotResolved = nil;
+ if (transaction.onComplete) {
+ FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] initWithRepo:self path:transaction.path];
+ FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:transaction.currentInputSnapshot];
+ FIRDataSnapshot *snap = [[FIRDataSnapshot alloc] initWithRef:ref indexedNode:indexedNode];
+ [self.eventRaiser raiseCallback:^{
+ transaction.onComplete(nil, NO, snap);
+ }];
+ }
+ } else {
+ // Note: different from js. We don't need to validate, FIRMutableData does validation.
+ // We also don't have to worry about priorities. Just mark as run and add to queue.
+ transaction.status = FTransactionRun;
+ FTree* queueNode = [self.transactionQueueTree subTree:transaction.path];
+ NSMutableArray* nodeQueue = [queueNode getValue];
+ if (nodeQueue == nil) {
+ nodeQueue = [[NSMutableArray alloc] init];
+ }
+ [nodeQueue addObject:transaction];
+ [queueNode setValue:nodeQueue];
+
+ // Update visibleData and raise events
+ // Note: We intentionally raise events after updating all of our transaction state, since the user could
+ // start new transactions from the event callbacks
+ NSDictionary* serverValues = [FServerValues generateServerValues:self.serverClock];
+ id newValUnresolved = [result.update nodeValue];
+ id newVal = [FServerValues resolveDeferredValueSnapshot:newValUnresolved withServerValues:serverValues];
+ transaction.currentOutputSnapshotRaw = newValUnresolved;
+ transaction.currentOutputSnapshotResolved = newVal;
+ transaction.currentWriteId = [NSNumber numberWithInteger:[self nextWriteId]];
+
+ NSArray *events = [self.serverSyncTree applyUserOverwriteAtPath:path newData:newVal
+ writeId:[transaction.currentWriteId integerValue]
+ isVisible:transaction.applyLocally];
+ [self.eventRaiser raiseEvents:events];
+
+ [self sendAllReadyTransactions];
+ }
+}
+
+/**
+ * @param writeIdsToExclude A specific set to exclude
+ */
+- (id) latestStateAtPath:(FPath *)path excludeWriteIds:(NSArray *)writeIdsToExclude {
+ id latestState = [self.serverSyncTree calcCompleteEventCacheAtPath:path excludeWriteIds:writeIdsToExclude];
+ return latestState ? latestState : [FEmptyNode emptyNode];
+}
+
+/**
+ * Sends any already-run transactions that aren't waiting for outstanding transactions to complete.
+ *
+ * Externally, call the version with no arguments.
+ * Internally, calls itself recursively with a particular transactionQueueTree node to recurse through the tree
+ */
+- (void) sendAllReadyTransactions {
+ FTree* node = self.transactionQueueTree;
+
+ [self pruneCompletedTransactionsBelowNode:node];
+ [self sendReadyTransactionsForTree:node];
+}
+
+- (void) sendReadyTransactionsForTree:(FTree *)node {
+ NSMutableArray* queue = [node getValue];
+ if (queue != nil) {
+ queue = [self buildTransactionQueueAtNode:node];
+ NSAssert([queue count] > 0, @"Sending zero length transaction queue");
+
+ NSUInteger notRunIndex = [queue indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
+ return ((FTupleTransaction*)obj).status != FTransactionRun;
+ }];
+
+ // If they're all run (and not sent), we can send them. Else, we must wait.
+ if (notRunIndex == NSNotFound) {
+ [self sendTransactionQueue:queue atPath:node.path];
+ }
+ } else if ([node hasChildren]) {
+ [node forEachChild:^(FTree *child) {
+ [self sendReadyTransactionsForTree:child];
+ }];
+ }
+}
+
+/**
+ * Given a list of run transactions, send them to the server and then handle the result (success or failure).
+ */
+- (void) sendTransactionQueue:(NSMutableArray *)queue atPath:(FPath *)path {
+ // Mark transactions as sent and bump the retry count
+ NSMutableArray *writeIdsToExclude = [[NSMutableArray alloc] init];
+ for (FTupleTransaction *transaction in queue) {
+ [writeIdsToExclude addObject:transaction.currentWriteId];
+ }
+ id latestState = [self latestStateAtPath:path excludeWriteIds:writeIdsToExclude];
+ id snapToSend = latestState;
+ NSString *latestHash = [latestState dataHash];
+ for (FTupleTransaction* transaction in queue) {
+ NSAssert(transaction.status == FTransactionRun, @"[FRepo sendTransactionQueue:] items in queue should all be run.");
+ FFLog(@"I-RDB038021", @"Transaction at %@ set to SENT", transaction.path);
+ transaction.status = FTransactionSent;
+ transaction.retryCount++;
+ FPath *relativePath = [FPath relativePathFrom:path to:transaction.path];
+ // If we've gotten to this point, the output snapshot must be defined.
+ snapToSend = [snapToSend updateChild:relativePath withNewChild:transaction.currentOutputSnapshotRaw];
+ }
+
+ id dataToSend = [snapToSend valForExport:YES];
+ NSString *pathToSend = [path description];
+ latestHash = self.hijackHash ? @"badhash" : latestHash;
+
+ // Send the put
+ [self.connection putData:dataToSend forPath:pathToSend withHash:latestHash withCallback:^(NSString *status, NSString *errorReason) {
+ FFLog(@"I-RDB038022", @"Transaction put response: %@ : %@", pathToSend, status);
+
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+ if ([status isEqualToString:kFWPResponseForActionStatusOk]) {
+ // Queue up the callbacks and fire them after cleaning up all of our transaction state, since
+ // the callback could trigger more transactions or sets.
+ NSMutableArray *callbacks = [[NSMutableArray alloc] init];
+ for (FTupleTransaction *transaction in queue) {
+ transaction.status = FTransactionCompleted;
+ [events addObjectsFromArray:[self.serverSyncTree ackUserWriteWithWriteId:[transaction.currentWriteId integerValue]
+ revert:NO
+ persist:NO
+ clock:self.serverClock]];
+ if (transaction.onComplete) {
+ // We never unset the output snapshot, and given that this transaction is complete, it should be set
+ id node = transaction.currentOutputSnapshotResolved;
+ FIndexedNode *indexedNode = [FIndexedNode indexedNodeWithNode:node];
+ FIRDatabaseReference *ref = [[FIRDatabaseReference alloc] initWithRepo:self path:transaction.path];
+ FIRDataSnapshot *snapshot = [[FIRDataSnapshot alloc] initWithRef:ref indexedNode:indexedNode];
+ fbt_void_void cb = ^{
+ transaction.onComplete(nil, YES, snapshot);
+ };
+ [callbacks addObject:[cb copy]];
+ }
+ transaction.unwatcher();
+ }
+
+ // Now remove the completed transactions.
+ [self pruneCompletedTransactionsBelowNode:[self.transactionQueueTree subTree:path]];
+ // There may be pending transactions that we can now send.
+ [self sendAllReadyTransactions];
+
+ // Finally, trigger onComplete callbacks
+ [self.eventRaiser raiseCallbacks:callbacks];
+ } else {
+ // transactions are no longer sent. Update their status appropriately.
+ if ([status isEqualToString:kFWPResponseForActionStatusDataStale]) {
+ for (FTupleTransaction *transaction in queue) {
+ if (transaction.status == FTransactionSentNeedsAbort) {
+ transaction.status = FTransactionNeedsAbort;
+ } else {
+ transaction.status = FTransactionRun;
+ }
+ }
+ } else {
+ FFWarn(@"I-RDB038023", @"runTransactionBlock: at %@ failed: %@", path, status);
+ for (FTupleTransaction *transaction in queue) {
+ transaction.status = FTransactionNeedsAbort;
+ [transaction setAbortStatus:status reason:errorReason];
+ }
+ }
+ }
+
+ [self rerunTransactionsForPath:path];
+ [self.eventRaiser raiseEvents:events];
+ }];
+}
+
+/**
+ * Finds all transactions dependent on the data at changed Path and reruns them.
+ *
+ * Should be called any time cached data changes.
+ *
+ * Return the highest path that was affected by rerunning transactions. This is the path at which events need to
+ * be raised for.
+ */
+- (FPath *) rerunTransactionsForPath:(FPath *)changedPath {
+ // For the common case that there are no transactions going on, skip all this!
+ if ([self.transactionQueueTree isEmpty]) {
+ return changedPath;
+ } else {
+ FTree* rootMostTransactionNode = [self getAncestorTransactionNodeForPath:changedPath];
+ FPath* path = rootMostTransactionNode.path;
+
+ NSArray* queue = [self buildTransactionQueueAtNode:rootMostTransactionNode];
+ [self rerunTransactionQueue:queue atPath:path];
+
+ return path;
+ }
+}
+
+/**
+ * Does all the work of rerunning transactions (as well as cleans up aborted transactions and whatnot).
+ */
+- (void) rerunTransactionQueue:(NSArray *)queue atPath:(FPath *)path {
+ if (queue.count == 0) {
+ return; // nothing to do
+ }
+
+ // Queue up the callbacks and fire them after cleaning up all of our transaction state, since
+ // the callback could trigger more transactions or sets.
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+ NSMutableArray *callbacks = [[NSMutableArray alloc] init];
+
+ // Ignore, by default, all of the sets in this queue, since we're re-running all of them. However, we want to include
+ // the results of new sets triggered as part of this re-run, so we don't want to ignore a range, just these specific
+ // sets.
+ NSMutableArray *writeIdsToExclude = [[NSMutableArray alloc] init];
+ for (FTupleTransaction *transaction in queue) {
+ [writeIdsToExclude addObject:transaction.currentWriteId];
+ }
+
+ for (FTupleTransaction* transaction in queue) {
+ FPath* relativePath __unused = [FPath relativePathFrom:path to:transaction.path];
+ BOOL abortTransaction = NO;
+ NSAssert(relativePath != nil, @"[FRepo rerunTransactionsQueue:] relativePath should not be null.");
+
+ if (transaction.status == FTransactionNeedsAbort) {
+ abortTransaction = YES;
+ if (![transaction.abortStatus isEqualToString:kFErrorWriteCanceled]) {
+ NSArray *ackEvents = [self.serverSyncTree ackUserWriteWithWriteId:[transaction.currentWriteId integerValue]
+ revert:YES
+ persist:NO
+ clock:self.serverClock];
+ [events addObjectsFromArray:ackEvents];
+ }
+ } else if (transaction.status == FTransactionRun) {
+ if (transaction.retryCount >= kFTransactionMaxRetries) {
+ abortTransaction = YES;
+ [transaction setAbortStatus:kFTransactionTooManyRetries reason:nil];
+ [events addObjectsFromArray:[self.serverSyncTree ackUserWriteWithWriteId:[transaction.currentWriteId integerValue]
+ revert:YES
+ persist:NO
+ clock:self.serverClock]];
+ } else {
+ // This code reruns a transaction
+ id currentNode = [self latestStateAtPath:transaction.path excludeWriteIds:writeIdsToExclude];
+ transaction.currentInputSnapshot = currentNode;
+ FIRMutableData * mutableCurrent = [[FIRMutableData alloc] initWithNode:currentNode];
+ FIRTransactionResult * result = transaction.update(mutableCurrent);
+ if (result.isSuccess) {
+ NSNumber *oldWriteId = transaction.currentWriteId;
+ NSDictionary* serverValues = [FServerValues generateServerValues:self.serverClock];
+
+ id newVal = [result.update nodeValue];
+ id newValResolved = [FServerValues resolveDeferredValueSnapshot:newVal withServerValues:serverValues];
+
+ transaction.currentOutputSnapshotRaw = newVal;
+ transaction.currentOutputSnapshotResolved = newValResolved;
+
+ transaction.currentWriteId = [NSNumber numberWithInteger:[self nextWriteId]];
+ // Mutates writeIdsToExclude in place
+ [writeIdsToExclude removeObject:oldWriteId];
+ [events addObjectsFromArray:[self.serverSyncTree applyUserOverwriteAtPath:transaction.path
+ newData:transaction.currentOutputSnapshotResolved
+ writeId:[transaction.currentWriteId integerValue]
+ isVisible:transaction.applyLocally]];
+ [events addObjectsFromArray:[self.serverSyncTree ackUserWriteWithWriteId:[oldWriteId integerValue]
+ revert:YES
+ persist:NO
+ clock:self.serverClock]];
+ } else {
+ abortTransaction = YES;
+ // The user aborted the transaction. JS treats ths as a "nodata" abort, but it's not an error, so we don't send them an error.
+ [transaction setAbortStatus:nil reason:nil];
+ [events addObjectsFromArray:[self.serverSyncTree ackUserWriteWithWriteId:[transaction.currentWriteId integerValue]
+ revert:YES
+ persist:NO
+ clock:self.serverClock]];
+ }
+ }
+ }
+
+ [self.eventRaiser raiseEvents:events];
+ events = nil;
+
+ if (abortTransaction) {
+ // Abort
+ transaction.status = FTransactionCompleted;
+ transaction.unwatcher();
+ if (transaction.onComplete) {
+ FIRDatabaseReference * ref = [[FIRDatabaseReference alloc] initWithRepo:self path:transaction.path];
+ FIndexedNode *lastInput = [FIndexedNode indexedNodeWithNode:transaction.currentInputSnapshot];
+ FIRDataSnapshot * snap = [[FIRDataSnapshot alloc] initWithRef:ref indexedNode:lastInput];
+ fbt_void_void cb = ^{
+ // Unlike JS, no need to check for "nodata" because ObjC has abortError = nil
+ transaction.onComplete(transaction.abortError, NO, snap);
+ };
+ [callbacks addObject:[cb copy]];
+ }
+ }
+ }
+
+ // Note: unlike current js client, we don't need to preserve priority. Users can set priority via FIRMutableData
+
+ // Clean up completed transactions.
+ [self pruneCompletedTransactionsBelowNode:self.transactionQueueTree];
+
+ // Now fire callbacks, now that we're in a good, known state.
+ [self.eventRaiser raiseCallbacks:callbacks];
+
+ // Try to send the transaction result to the server
+ [self sendAllReadyTransactions];
+}
+
+- (FTree *) getAncestorTransactionNodeForPath:(FPath *)path {
+ FTree* transactionNode = self.transactionQueueTree;
+
+ while (![path isEmpty] && [transactionNode getValue] == nil) {
+ NSString* front = [path getFront];
+ transactionNode = [transactionNode subTree:[[FPath alloc] initWith:front]];
+ path = [path popFront];
+ }
+
+ return transactionNode;
+}
+
+- (NSMutableArray *) buildTransactionQueueAtNode:(FTree *)node {
+ NSMutableArray* queue = [[NSMutableArray alloc] init];
+ [self aggregateTransactionQueuesForNode:node andQueue:queue];
+
+ [queue sortUsingComparator:^NSComparisonResult(FTupleTransaction* obj1, FTupleTransaction* obj2) {
+ return [obj1.order compare:obj2.order];
+ }];
+
+ return queue;
+}
+
+- (void) aggregateTransactionQueuesForNode:(FTree *)node andQueue:(NSMutableArray *)queue {
+ NSArray* nodeQueue = [node getValue];
+ [queue addObjectsFromArray:nodeQueue];
+
+ [node forEachChild:^(FTree *child) {
+ [self aggregateTransactionQueuesForNode:child andQueue:queue];
+ }];
+}
+
+/**
+ * Remove COMPLETED transactions at or below this node in the transactionQueueTree
+ */
+- (void) pruneCompletedTransactionsBelowNode:(FTree *)node {
+ NSMutableArray* queue = [node getValue];
+ if (queue != nil) {
+ int i = 0;
+ // remove all of the completed transactions from the queue
+ while (i < queue.count) {
+ FTupleTransaction* transaction = [queue objectAtIndex:i];
+ if (transaction.status == FTransactionCompleted) {
+ [queue removeObjectAtIndex:i];
+ } else {
+ i++;
+ }
+ }
+ if (queue.count > 0) {
+ [node setValue:queue];
+ } else {
+ [node setValue:nil];
+ }
+ }
+
+ [node forEachChildMutationSafe:^(FTree *child) {
+ [self pruneCompletedTransactionsBelowNode:child];
+ }];
+}
+
+/**
+ * Aborts all transactions on ancestors or descendants of the specified path. Called when doing a setValue: or
+ * updateChildValues: since we consider them incompatible with transactions
+ *
+ * @param path path for which we want to abort related transactions.
+ */
+- (FPath *) abortTransactionsAtPath:(FPath *)path error:(NSString *)error {
+ // For the common case that there are no transactions going on, skip all this!
+ if ([self.transactionQueueTree isEmpty]) {
+ return path;
+ } else {
+ FPath* affectedPath = [self getAncestorTransactionNodeForPath:path].path;
+
+ FTree* transactionNode = [self.transactionQueueTree subTree:path];
+ [transactionNode forEachAncestor:^BOOL(FTree *ancestor) {
+ [self abortTransactionsAtNode:ancestor error:error];
+ return NO;
+ }];
+
+ [self abortTransactionsAtNode:transactionNode error:error];
+
+ [transactionNode forEachDescendant:^(FTree *child) {
+ [self abortTransactionsAtNode:child error:error];
+ }];
+
+ return affectedPath;
+ }
+}
+
+/**
+ * Abort transactions stored in this transactions queue node.
+ *
+ * @param node Node to abort transactions for.
+ */
+- (void) abortTransactionsAtNode:(FTree *)node error:(NSString *)error {
+ NSMutableArray* queue = [node getValue];
+ if (queue != nil) {
+
+ // Queue up the callbacks and fire them after cleaning up all of our transaction state, since
+ // can be immediately aborted and removed.
+ NSMutableArray* callbacks = [[NSMutableArray alloc] init];
+
+ // Go through queue. Any already-sent transactions must be marked for abort, while the unsent ones
+ // can be immediately aborted and removed
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+ int lastSent = -1;
+ // Note: all of the sent transactions will be at the front of the queue, so safe to increment lastSent
+ for (FTupleTransaction* transaction in queue) {
+ if (transaction.status == FTransactionSentNeedsAbort) {
+ // No-op. already marked.
+ } else if (transaction.status == FTransactionSent) {
+ // Mark this transaction for abort when it returns
+ lastSent++;
+ transaction.status = FTransactionSentNeedsAbort;
+ [transaction setAbortStatus:error reason:nil];
+ } else {
+ // we can abort this immediately
+ transaction.unwatcher();
+ if ([error isEqualToString:kFTransactionSet]) {
+ [events addObjectsFromArray:[self.serverSyncTree ackUserWriteWithWriteId:[transaction.currentWriteId integerValue]
+ revert:YES
+ persist:NO
+ clock:self.serverClock]];
+ } else {
+ // If it was cancelled it was already removed from the sync tree, no need to ack
+ NSAssert([error isEqualToString:kFErrorWriteCanceled], nil);
+ }
+
+ if (transaction.onComplete) {
+ NSError* abortReason = [FUtilities errorForStatus:error andReason:nil];
+ FIRDataSnapshot * snapshot = nil;
+ fbt_void_void cb = ^{
+ transaction.onComplete(abortReason, NO, snapshot);
+ };
+ [callbacks addObject:[cb copy]];
+ }
+ }
+ }
+ if (lastSent == -1) {
+ // We're not waiting for any sent transactions. We can clear the queue.
+ [node setValue:nil];
+ } else {
+ // Remove the transactions we aborted
+ NSRange theRange;
+ theRange.location = lastSent + 1;
+ theRange.length = queue.count - theRange.location;
+ [queue removeObjectsInRange:theRange];
+ }
+
+ // Now fire the callbacks
+ [self.eventRaiser raiseEvents:events];
+ [self.eventRaiser raiseCallbacks:callbacks];
+ }
+}
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h
new file mode 100644
index 00000000..433bf352
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.h
@@ -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
+
+@interface FRepoInfo : NSObject
+
+@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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m
new file mode 100644
index 00000000..925163e8
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoInfo.m
@@ -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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h
new file mode 100644
index 00000000..c492861c
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.h
@@ -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
+#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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m
new file mode 100644
index 00000000..31c3efc5
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepoManager.m
@@ -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
+#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 *>
+ 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 *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 *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 *repos = configs[config.sessionIdentifier];
+ for (FRepo *repo in [repos allValues]) {
+ [repo interrupt];
+ }
+ });
+}
+
++ (void) interruptAll {
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ FRepoDictionary *configs = [FRepoManager configs];
+ for (NSMutableDictionary *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 *repos = configs[config.sessionIdentifier];
+ for (FRepo *repo in [repos allValues]) {
+ [repo resume];
+ }
+ });
+}
+
++ (void) resumeAll {
+ dispatch_async([FIRDatabaseQuery sharedQueue], ^{
+ FRepoDictionary *configs = [FRepoManager configs];
+ for (NSMutableDictionary *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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h
new file mode 100644
index 00000000..109edacc
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FRepo_Private.h
@@ -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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h
new file mode 100644
index 00000000..2540c12e
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+#import "FSparseSnapshotTree.h"
+#import "FNode.h"
+#import "FCompoundWrite.h"
+#import "FClock.h"
+
+@interface FServerValues : NSObject
+
++ (NSDictionary*) generateServerValues:(id)clock;
++ (id) resolveDeferredValueCompoundWrite:(FCompoundWrite*)write withServerValues:(NSDictionary*)serverValues;
++ (id) resolveDeferredValueSnapshot:(id)node withServerValues:(NSDictionary*)serverValues;
++ (id) resolveDeferredValueTree:(FSparseSnapshotTree*)tree withServerValues:(NSDictionary*)serverValues;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m
new file mode 100644
index 00000000..89ee5d0f
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FServerValues.m
@@ -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)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 node, BOOL *stop) {
+ id 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 node) {
+ [resolvedTree rememberData:[FServerValues resolveDeferredValueSnapshot:node withServerValues:serverValues] onPath:path];
+ }];
+ return resolvedTree;
+}
+
++ (id) resolveDeferredValueSnapshot:(id)node withServerValues:(NSDictionary*)serverValues {
+ id priorityVal = [FServerValues resolveDeferredValue:[[node getPriority] val] withServerValues:serverValues];
+ id 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 childNode, BOOL *stop) {
+ id newChildNode = [FServerValues resolveDeferredValueSnapshot:childNode withServerValues:serverValues];
+ if (![newChildNode isEqual:childNode]) {
+ newNode = [newNode updateImmediateChild:childKey withNewChild:newChildNode];
+ }
+ }];
+ return newNode;
+ }
+}
+
+@end
+
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h
new file mode 100644
index 00000000..9a1d871a
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.h
@@ -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
+#import "FNode.h"
+
+@interface FSnapshotHolder : NSObject
+
+- (id) getNode:(FPath *)path;
+- (void) updateSnapshot:(FPath *)path withNewSnapshot:(id)newSnapshotNode;
+
+@property (nonatomic, strong) id rootNode;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m
new file mode 100644
index 00000000..25c46257
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSnapshotHolder.m
@@ -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) getNode:(FPath *)path {
+ return [self.rootNode getChild:path];
+}
+
+- (void) updateSnapshot:(FPath *)path withNewSnapshot:(id)newSnapshotNode {
+ self.rootNode = [self.rootNode updateChild:path withNewChild:newSnapshotNode];
+}
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h
new file mode 100644
index 00000000..b860c9d0
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.h
@@ -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
+#import "FNode.h"
+#import "FPath.h"
+#import "FTypedefs_Private.h"
+
+@class FSparseSnapshotTree;
+
+typedef void (^fbt_void_nsstring_sstree) (NSString*, FSparseSnapshotTree*);
+
+@interface FSparseSnapshotTree : NSObject
+
+- (id) findPath:(FPath *)path;
+- (void) rememberData:(id)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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m
new file mode 100644
index 00000000..1f16888c
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSparseSnapshotTree.m
@@ -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 value;
+ NSMutableDictionary* children;
+}
+
+@end
+
+@implementation FSparseSnapshotTree
+
+- (id) init {
+ self = [super init];
+ if (self) {
+ value = nil;
+ children = nil;
+ }
+ return self;
+}
+
+- (id) 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)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 tmp = value;
+ value = nil;
+
+ [tmp enumerateChildrenUsingBlock:^(NSString *key, id 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
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h
new file mode 100644
index 00000000..4e5a4e28
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.h
@@ -0,0 +1,66 @@
+/*
+ * 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
+
+@protocol FOperation;
+@class FWriteTreeRef;
+@protocol FNode;
+@protocol FEventRegistration;
+@class FQuerySpec;
+@class FChildrenNode;
+@class FTupleRemovedQueriesEvents;
+@class FView;
+@class FPath;
+@class FCacheNode;
+@class FPersistenceManager;
+
+@interface FSyncPoint : NSObject
+
+- (id)initWithPersistenceManager:(FPersistenceManager *)persistence;
+
+- (BOOL) isEmpty;
+
+/**
+* Returns array of FEvent
+*/
+- (NSArray *) applyOperation:(id)operation writesCache:(FWriteTreeRef *)writesCache serverCache:(id)optCompleteServerCache;
+
+/**
+* Returns array of FEvent
+*/
+- (NSArray *) addEventRegistration:(id )eventRegistration
+ forNonExistingViewForQuery:(FQuerySpec *)query
+ writesCache:(FWriteTreeRef *)writesCache
+ serverCache:(FCacheNode *)serverCache;
+
+- (NSArray *) addEventRegistration:(id )eventRegistration
+ forExistingViewForQuery:(FQuerySpec *)query;
+
+- (FTupleRemovedQueriesEvents *) removeEventRegistration:(id )eventRegistration
+ forQuery:(FQuerySpec *)query
+ cancelError:(NSError *)cancelError;
+/**
+* Returns array of FViews
+*/
+- (NSArray *) queryViews;
+- (id) completeServerCacheAtPath:(FPath *)path;
+- (FView *) viewForQuery:(FQuerySpec *)query;
+- (BOOL) viewExistsForQuery:(FQuerySpec *)query;
+- (BOOL) hasCompleteView;
+- (FView *) completeView;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m
new file mode 100644
index 00000000..cd429f18
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncPoint.m
@@ -0,0 +1,257 @@
+/*
+ * 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 "FSyncPoint.h"
+#import "FOperation.h"
+#import "FWriteTreeRef.h"
+#import "FNode.h"
+#import "FEventRegistration.h"
+#import "FIRDatabaseQuery.h"
+#import "FChildrenNode.h"
+#import "FTupleRemovedQueriesEvents.h"
+#import "FView.h"
+#import "FOperationSource.h"
+#import "FQuerySpec.h"
+#import "FQueryParams.h"
+#import "FPath.h"
+#import "FEmptyNode.h"
+#import "FViewCache.h"
+#import "FCacheNode.h"
+#import "FPersistenceManager.h"
+#import "FDataEvent.h"
+
+/**
+* SyncPoint represents a single location in a SyncTree with 1 or more event registrations, meaning we need to
+* maintain 1 or more Views at this location to cache server data and raise appropriate events for server changes
+* and user writes (set, transaction, update).
+*
+* It's responsible for:
+* - Maintaining the set of 1 or more views necessary at this location (a SyncPoint with 0 views should be removed).
+* - Proxying user / server operations to the views as appropriate (i.e. applyServerOverwrite,
+* applyUserOverwrite, etc.)
+*/
+@interface FSyncPoint ()
+/**
+* The Views being tracked at this location in the tree, stored as a map where the key is a
+* queryParams and the value is the View for that query.
+*
+* NOTE: This list will be quite small (usually 1, but perhaps 2 or 3; any more is an odd use case).
+*
+* Maps NSString -> FView
+*/
+@property (nonatomic, strong) NSMutableDictionary *views;
+
+@property (nonatomic, strong) FPersistenceManager *persistenceManager;
+@end
+
+@implementation FSyncPoint
+
+- (id) initWithPersistenceManager:(FPersistenceManager *)persistence {
+ self = [super init];
+ if (self) {
+ self.persistenceManager = persistence;
+ self.views = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+- (BOOL) isEmpty {
+ return [self.views count] == 0;
+}
+
+- (NSArray *) applyOperation:(id)operation
+ toView:(FView *)view
+ writesCache:(FWriteTreeRef *)writesCache
+ serverCache:(id)optCompleteServerCache {
+ FViewOperationResult *result = [view applyOperation:operation writesCache:writesCache serverCache:optCompleteServerCache];
+ if (!view.query.loadsAllData) {
+ NSMutableSet *removed = [NSMutableSet set];
+ NSMutableSet *added = [NSMutableSet set];
+ [result.changes enumerateObjectsUsingBlock:^(FChange *change, NSUInteger idx, BOOL *stop) {
+ if (change.type == FIRDataEventTypeChildAdded) {
+ [added addObject:change.childKey];
+ } else if (change.type == FIRDataEventTypeChildRemoved) {
+ [removed addObject:change.childKey];
+ }
+ }];
+ if ([removed count] > 0 || [added count] > 0) {
+ [self.persistenceManager updateTrackedQueryKeysWithAddedKeys:added removedKeys:removed forQuery:view.query];
+ }
+ }
+ return result.events;
+}
+
+- (NSArray *) applyOperation:(id )operation writesCache:(FWriteTreeRef *)writesCache serverCache:(id )optCompleteServerCache {
+ FQueryParams *queryParams = operation.source.queryParams;
+ if (queryParams != nil) {
+ FView *view = [self.views objectForKey:queryParams];
+ NSAssert(view != nil, @"SyncTree gave us an op for an invalid query.");
+ return [self applyOperation:operation toView:view writesCache:writesCache serverCache:optCompleteServerCache];
+ } else {
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+ [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, FView *view, BOOL *stop) {
+ NSArray *eventsForView = [self applyOperation:operation toView:view writesCache:writesCache serverCache:optCompleteServerCache];
+ [events addObjectsFromArray:eventsForView];
+ }];
+ return events;
+ }
+}
+
+/**
+* Add an event callback for the specified query
+* Returns Array of FEvent events to raise.
+*/
+- (NSArray *) addEventRegistration:(id )eventRegistration
+ forNonExistingViewForQuery:(FQuerySpec *)query
+ writesCache:(FWriteTreeRef *)writesCache
+ serverCache:(FCacheNode *)serverCache {
+ NSAssert(self.views[query.params] == nil, @"Found view for query: %@", query.params);
+ // TODO: make writesCache take flag for complete server node
+ id eventCache = [writesCache calculateCompleteEventCacheWithCompleteServerCache:serverCache.isFullyInitialized ? serverCache.node : nil];
+ BOOL eventCacheComplete;
+ if (eventCache != nil) {
+ eventCacheComplete = YES;
+ } else {
+ eventCache = [writesCache calculateCompleteEventChildrenWithCompleteServerChildren:serverCache.node];
+ eventCacheComplete = NO;
+ }
+
+ FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:eventCache index:query.index];
+ FCacheNode *eventCacheNode = [[FCacheNode alloc] initWithIndexedNode:indexed
+ isFullyInitialized:eventCacheComplete
+ isFiltered:NO];
+ FViewCache *viewCache = [[FViewCache alloc] initWithEventCache:eventCacheNode serverCache:serverCache];
+ FView *view = [[FView alloc] initWithQuery:query initialViewCache:viewCache];
+ // If this is a non-default query we need to tell persistence our current view of the data
+ if (!query.loadsAllData) {
+ NSMutableSet *allKeys = [NSMutableSet set];
+ [view.eventCache enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) {
+ [allKeys addObject:key];
+ }];
+ [self.persistenceManager setTrackedQueryKeys:allKeys forQuery:query];
+ }
+ self.views[query.params] = view;
+ return [self addEventRegistration:eventRegistration forExistingViewForQuery:query];
+}
+
+- (NSArray *)addEventRegistration:(id)eventRegistration
+ forExistingViewForQuery:(FQuerySpec *)query {
+ FView *view = self.views[query.params];
+ NSAssert(view != nil, @"No view for query: %@", query);
+ [view addEventRegistration:eventRegistration];
+ return [view initialEvents:eventRegistration];
+}
+
+/**
+* Remove event callback(s). Return cancelEvents if a cancelError is specified.
+*
+* If query is the default query, we'll check all views for the specified eventRegistration.
+* If eventRegistration is nil, we'll remove all callbacks for the specified view(s).
+*
+* @return FTupleRemovedQueriesEvents removed queries and any cancel events
+*/
+- (FTupleRemovedQueriesEvents *) removeEventRegistration:(id )eventRegistration
+ forQuery:(FQuerySpec *)query
+ cancelError:(NSError *)cancelError {
+ NSMutableArray *removedQueries = [[NSMutableArray alloc] init];
+ __block NSMutableArray *cancelEvents = [[NSMutableArray alloc] init];
+ BOOL hadCompleteView = [self hasCompleteView];
+ if ([query isDefault]) {
+ // When you do [ref removeObserverWithHandle:], we search all views for the registration to remove.
+ [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *viewQueryParams, FView *view, BOOL *stop) {
+ [cancelEvents addObjectsFromArray:[view removeEventRegistration:eventRegistration cancelError:cancelError]];
+ if ([view isEmpty]) {
+ [self.views removeObjectForKey:viewQueryParams];
+
+ // We'll deal with complete views later
+ if (![view.query loadsAllData]) {
+ [removedQueries addObject:view.query];
+ }
+ }
+ }];
+ } else {
+ // remove the callback from the specific view
+ FView *view = [self.views objectForKey:query.params];
+ if (view != nil) {
+ [cancelEvents addObjectsFromArray:[view removeEventRegistration:eventRegistration cancelError:cancelError]];
+
+ if ([view isEmpty]) {
+ [self.views removeObjectForKey:query.params];
+
+ // We'll deal with complete views later
+ if (![view.query loadsAllData]) {
+ [removedQueries addObject:view.query];
+ }
+ }
+ }
+ }
+
+ if (hadCompleteView && ![self hasCompleteView]) {
+ // We removed our last complete view
+ [removedQueries addObject:[FQuerySpec defaultQueryAtPath:query.path]];
+ }
+
+ return [[FTupleRemovedQueriesEvents alloc] initWithRemovedQueries:removedQueries cancelEvents:cancelEvents];
+}
+
+- (NSArray *) queryViews {
+ __block NSMutableArray *filteredViews = [[NSMutableArray alloc] init];
+
+ [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, FView *view, BOOL *stop) {
+ if (![view.query loadsAllData]) {
+ [filteredViews addObject:view];
+ }
+ }];
+
+ return filteredViews;
+}
+
+- (id ) completeServerCacheAtPath:(FPath *)path {
+ __block id serverCache = nil;
+ [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, FView *view, BOOL *stop) {
+ serverCache = [view completeServerCacheFor:path];
+ *stop = (serverCache != nil);
+ }];
+ return serverCache;
+}
+
+- (FView *) viewForQuery:(FQuerySpec *)query {
+ return [self.views objectForKey:query.params];
+}
+
+- (BOOL) viewExistsForQuery:(FQuerySpec *)query {
+ return [self viewForQuery:query] != nil;
+}
+
+- (BOOL) hasCompleteView {
+ return [self completeView] != nil;
+}
+
+- (FView *) completeView {
+ __block FView *completeView = nil;
+
+ [self.views enumerateKeysAndObjectsUsingBlock:^(FQueryParams *key, FView *view, BOOL *stop) {
+ if ([view.query loadsAllData]) {
+ completeView = view;
+ *stop = YES;
+ }
+ }];
+
+ return completeView;
+}
+
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h
new file mode 100644
index 00000000..887f7219
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import
+
+@class FListenProvider;
+@protocol FNode;
+@class FPath;
+@protocol FEventRegistration;
+@protocol FPersistedServerCache;
+@class FQuerySpec;
+@class FCompoundWrite;
+@class FPersistenceManager;
+@class FCompoundHash;
+@protocol FClock;
+
+@protocol FSyncTreeHash
+
+- (NSString *)simpleHash;
+- (FCompoundHash *)compoundHash;
+- (BOOL)includeCompoundHash;
+
+@end
+
+@interface FSyncTree : NSObject
+
+- (id) initWithListenProvider:(FListenProvider *)provider;
+- (id) initWithPersistenceManager:(FPersistenceManager *)persistenceManager
+ listenProvider:(FListenProvider *)provider;
+
+// These methods all return NSArray of FEvent
+- (NSArray *) applyUserOverwriteAtPath:(FPath *)path newData:(id )newData writeId:(NSInteger)writeId isVisible:(BOOL)visible;
+- (NSArray *) applyUserMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren writeId:(NSInteger)writeId;
+- (NSArray *) ackUserWriteWithWriteId:(NSInteger)writeId revert:(BOOL)revert persist:(BOOL)persist clock:(id)clock;
+- (NSArray *) applyServerOverwriteAtPath:(FPath *)path newData:(id)newData;
+- (NSArray *) applyServerMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren;
+- (NSArray *) applyServerRangeMergeAtPath:(FPath *)path updates:(NSArray *)ranges;
+- (NSArray *) applyTaggedQueryOverwriteAtPath:(FPath *)path newData:(id )newData tagId:(NSNumber *)tagId;
+- (NSArray *) applyTaggedQueryMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren tagId:(NSNumber *)tagId;
+- (NSArray *) applyTaggedServerRangeMergeAtPath:(FPath *)path updates:(NSArray *)ranges tagId:(NSNumber *)tagId;
+- (NSArray *) addEventRegistration:(id)eventRegistration forQuery:(FQuerySpec *)query;
+- (NSArray *) removeEventRegistration:(id )eventRegistration forQuery:(FQuerySpec *)query cancelError:(NSError *)cancelError;
+- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced;
+- (NSArray *) removeAllWrites;
+
+- (id) calcCompleteEventCacheAtPath:(FPath *)path excludeWriteIds:(NSArray *)writeIdsToExclude;
+
+@end
diff --git a/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m
new file mode 100644
index 00000000..688a43b2
--- /dev/null
+++ b/ios/Pods/FirebaseDatabase/Firebase/Database/Core/FSyncTree.m
@@ -0,0 +1,818 @@
+/*
+ * 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
+#import "FSyncTree.h"
+#import "FListenProvider.h"
+#import "FWriteTree.h"
+#import "FNode.h"
+#import "FPath.h"
+#import "FEventRegistration.h"
+#import "FImmutableTree.h"
+#import "FOperation.h"
+#import "FWriteTreeRef.h"
+#import "FOverwrite.h"
+#import "FOperationSource.h"
+#import "FMerge.h"
+#import "FAckUserWrite.h"
+#import "FView.h"
+#import "FSyncPoint.h"
+#import "FEmptyNode.h"
+#import "FQueryParams.h"
+#import "FQuerySpec.h"
+#import "FSnapshotHolder.h"
+#import "FChildrenNode.h"
+#import "FTupleRemovedQueriesEvents.h"
+#import "FAtomicNumber.h"
+#import "FEventRaiser.h"
+#import "FListenComplete.h"
+#import "FSnapshotUtilities.h"
+#import "FCacheNode.h"
+#import "FUtilities.h"
+#import "FCompoundWrite.h"
+#import "FWriteRecord.h"
+#import "FPersistenceManager.h"
+#import "FKeepSyncedEventRegistration.h"
+#import "FServerValues.h"
+#import "FCompoundHash.h"
+#import "FRangeMerge.h"
+
+// Size after which we start including the compound hash
+static const NSUInteger kFSizeThresholdForCompoundHash = 1024;
+
+@interface FListenContainer : NSObject
+
+@property (nonatomic, strong) FView *view;
+@property (nonatomic, copy) fbt_nsarray_nsstring onComplete;
+
+@end
+
+@implementation FListenContainer
+
+- (instancetype)initWithView:(FView *)view onComplete:(fbt_nsarray_nsstring)onComplete {
+ self = [super init];
+ if (self != nil) {
+ self->_view = view;
+ self->_onComplete = onComplete;
+ }
+ return self;
+}
+
+- (id)serverCache {
+ return self.view.serverCache;
+}
+
+- (FCompoundHash *)compoundHash {
+ return [FCompoundHash fromNode:[self serverCache]];
+}
+
+- (NSString *)simpleHash {
+ return [[self serverCache] dataHash];
+}
+
+- (BOOL)includeCompoundHash {
+ return [FSnapshotUtilities estimateSerializedNodeSize:[self serverCache]] > kFSizeThresholdForCompoundHash;
+}
+
+@end
+
+@interface FSyncTree ()
+
+/**
+* Tree of SyncPoints. There's a SyncPoint at any location that has 1 or more views.
+*/
+@property (nonatomic, strong) FImmutableTree *syncPointTree;
+
+/**
+* A tree of all pending user writes (user-initiated set, transactions, updates, etc)
+*/
+@property (nonatomic, strong) FWriteTree *pendingWriteTree;
+
+/**
+* Maps tagId -> FTuplePathQueryParams
+*/
+@property (nonatomic, strong) NSMutableDictionary *tagToQueryMap;
+@property (nonatomic, strong) NSMutableDictionary *queryToTagMap;
+@property (nonatomic, strong) FListenProvider *listenProvider;
+@property (nonatomic, strong) FPersistenceManager *persistenceManager;
+@property (nonatomic, strong) FAtomicNumber *queryTagCounter;
+@property (nonatomic, strong) NSMutableSet *keepSyncedQueries;
+
+@end
+
+/**
+* SyncTree is the central class for managing event callback registration, data caching, views
+* (query processing), and event generation. There are typically two SyncTree instances for
+* each Repo, one for the normal Firebase data, and one for the .info data.
+*
+* It has a number of responsibilities, including:
+* - Tracking all user event callbacks (registered via addEventRegistration: and removeEventRegistration:).
+* - Applying and caching data changes for user setValue:, runTransactionBlock:, and updateChildValues: calls
+* (applyUserOverwriteAtPath:, applyUserMergeAtPath:).
+* - Applying and caching data changes for server data changes (applyServerOverwriteAtPath:,
+* applyServerMergeAtPath:).
+* - Generating user-facing events for server and user changes (all of the apply* methods
+* return the set of events that need to be raised as a result).
+* - Maintaining the appropriate set of server listens to ensure we are always subscribed
+* to the correct set of paths and queries to satisfy the current set of user event
+* callbacks (listens are started/stopped using the provided listenProvider).
+*
+* NOTE: Although SyncTree tracks event callbacks and calculates events to raise, the actual
+* events are returned to the caller rather than raised synchronously.
+*/
+@implementation FSyncTree
+
+- (id) initWithListenProvider:(FListenProvider *)provider {
+ return [self initWithPersistenceManager:nil listenProvider:provider];
+}
+
+- (id) initWithPersistenceManager:(FPersistenceManager *)persistenceManager listenProvider:(FListenProvider *)provider {
+ self = [super init];
+ if (self) {
+ self.syncPointTree = [FImmutableTree empty];
+ self.pendingWriteTree = [[FWriteTree alloc] init];
+ self.tagToQueryMap = [[NSMutableDictionary alloc] init];
+ self.queryToTagMap = [[NSMutableDictionary alloc] init];
+ self.listenProvider = provider;
+ self.persistenceManager = persistenceManager;
+ self.queryTagCounter = [[FAtomicNumber alloc] init];
+ self.keepSyncedQueries = [NSMutableSet set];
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Apply Operations
+
+/**
+* Apply data changes for a user-generated setValue: runTransactionBlock: updateChildValues:, etc.
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyUserOverwriteAtPath:(FPath *)path newData:(id )newData writeId:(NSInteger)writeId isVisible:(BOOL)visible {
+ // Record pending write
+ [self.pendingWriteTree addOverwriteAtPath:path newData:newData writeId:writeId isVisible:visible];
+ if (!visible) {
+ return @[];
+ } else {
+ FOverwrite *operation = [[FOverwrite alloc] initWithSource:[FOperationSource userInstance] path:path snap:newData];
+ return [self applyOperationToSyncPoints:operation];
+ }
+}
+
+/**
+* Apply the data from a user-generated updateChildValues: call
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyUserMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren writeId:(NSInteger)writeId {
+ // Record pending merge
+ [self.pendingWriteTree addMergeAtPath:path changedChildren:changedChildren writeId:writeId];
+
+ FMerge *operation = [[FMerge alloc] initWithSource:[FOperationSource userInstance] path:path children:changedChildren];
+ return [self applyOperationToSyncPoints:operation];
+}
+
+/**
+ * Acknowledge a pending user write that was previously registered with applyUserOverwriteAtPath: or applyUserMergeAtPath:
+ * TODO[offline]: Taking a serverClock here is awkward, but server values are awkward. :-(
+ * @return NSArray of FEvent to raise.
+ */
+- (NSArray *) ackUserWriteWithWriteId:(NSInteger)writeId revert:(BOOL)revert persist:(BOOL)persist clock:(id)clock {
+ FWriteRecord *write = [self.pendingWriteTree writeForId:writeId];
+ BOOL needToReevaluate = [self.pendingWriteTree removeWriteId:writeId];
+ if (write.visible) {
+ if (persist) {
+ [self.persistenceManager removeUserWrite:writeId];
+ }
+ if (!revert) {
+ NSDictionary *serverValues = [FServerValues generateServerValues:clock];
+ if ([write isOverwrite]) {
+ id resolvedNode = [FServerValues resolveDeferredValueSnapshot:write.overwrite withServerValues:serverValues];
+ [self.persistenceManager applyUserWrite:resolvedNode toServerCacheAtPath:write.path];
+ } else {
+ FCompoundWrite *resolvedMerge = [FServerValues resolveDeferredValueCompoundWrite:write.merge withServerValues:serverValues];
+ [self.persistenceManager applyUserMerge:resolvedMerge toServerCacheAtPath:write.path];
+ }
+ }
+ }
+ if (!needToReevaluate) {
+ return @[];
+ } else {
+ __block FImmutableTree *affectedTree = [FImmutableTree empty];
+ if (write.isOverwrite) {
+ affectedTree = [affectedTree setValue:@YES atPath:[FPath empty]];
+ } else {
+ [write.merge enumerateWrites:^(FPath *path, id node, BOOL *stop) {
+ affectedTree = [affectedTree setValue:@YES atPath:path];
+ }];
+ }
+ FAckUserWrite *operation = [[FAckUserWrite alloc] initWithPath:write.path affectedTree:affectedTree revert:revert];
+ return [self applyOperationToSyncPoints:operation];
+ }
+}
+
+/**
+* Apply new server data for the specified path
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyServerOverwriteAtPath:(FPath *)path newData:(id )newData {
+ [self.persistenceManager updateServerCacheWithNode:newData forQuery:[FQuerySpec defaultQueryAtPath:path]];
+ FOverwrite *operation = [[FOverwrite alloc] initWithSource:[FOperationSource serverInstance] path:path snap:newData];
+ return [self applyOperationToSyncPoints:operation];
+}
+
+/**
+* Applied new server data to be merged in at the specified path
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyServerMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren {
+ [self.persistenceManager updateServerCacheWithMerge:changedChildren atPath:path];
+ FMerge *operation = [[FMerge alloc] initWithSource:[FOperationSource serverInstance] path:path children:changedChildren];
+ return [self applyOperationToSyncPoints:operation];
+}
+
+- (NSArray *) applyServerRangeMergeAtPath:(FPath *)path updates:(NSArray *)ranges {
+ FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
+ if (syncPoint == nil) {
+ // Removed view, so it's safe to just ignore this update
+ return @[];
+ } else {
+ // This could be for any "complete" (unfiltered) view, and if there is more than one complete view, they should
+ // each have the same cache so it doesn't matter which one we use.
+ FView *view = [syncPoint completeView];
+ if (view != nil) {
+ id serverNode = [view serverCache];
+ for (FRangeMerge *merge in ranges) {
+ serverNode = [merge applyToNode:serverNode];
+ }
+ return [self applyServerOverwriteAtPath:path newData:serverNode];
+ } else {
+ // There doesn't exist a view for this update, so it was removed and it's safe to just ignore this range
+ // merge
+ return @[];
+ }
+ }
+}
+
+/**
+* Apply a listen complete to a path
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyListenCompleteAtPath:(FPath *)path {
+ [self.persistenceManager setQueryComplete:[FQuerySpec defaultQueryAtPath:path]];
+ id operation = [[FListenComplete alloc] initWithSource:[FOperationSource serverInstance] path:path];
+ return [self applyOperationToSyncPoints:operation];
+}
+
+/**
+* Apply a listen complete to a path
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyTaggedListenCompleteAtPath:(FPath *)path tagId:(NSNumber *)tagId {
+ FQuerySpec *query = [self queryForTag:tagId];
+ if (query != nil) {
+ [self.persistenceManager setQueryComplete:query];
+ FPath *relativePath = [FPath relativePathFrom:query.path to:path];
+ id op = [[FListenComplete alloc] initWithSource:[FOperationSource forServerTaggedQuery:query.params]
+ path:relativePath];
+ return [self applyTaggedOperation:op atPath:query.path];
+ } else {
+ // We've already removed the query. No big deal, ignore the update.
+ return @[];
+ }
+}
+
+/**
+* Internal helper method to apply tagged operation
+*/
+- (NSArray *) applyTaggedOperation:(id)operation atPath:(FPath *)path {
+ FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
+ NSAssert(syncPoint != nil, @"Missing sync point for query tag that we're tracking.");
+ FWriteTreeRef *writesCache = [self.pendingWriteTree childWritesForPath:path];
+ return [syncPoint applyOperation:operation writesCache:writesCache serverCache:nil];
+}
+
+/**
+* Apply new server data for the specified tagged query
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyTaggedQueryOverwriteAtPath:(FPath *)path newData:(id )newData tagId:(NSNumber *)tagId {
+ FQuerySpec *query = [self queryForTag:tagId];
+ if (query != nil) {
+ FPath *relativePath = [FPath relativePathFrom:query.path to:path];
+ FQuerySpec *queryToOverwrite = relativePath.isEmpty ? query : [FQuerySpec defaultQueryAtPath:path];
+ [self.persistenceManager updateServerCacheWithNode:newData forQuery:queryToOverwrite];
+ FOverwrite *operation = [[FOverwrite alloc] initWithSource:[FOperationSource forServerTaggedQuery:query.params]
+ path:relativePath snap:newData];
+ return [self applyTaggedOperation:operation atPath:query.path];
+ } else {
+ // Query must have been removed already
+ return @[];
+ }
+}
+
+/**
+* Apply server data to be merged in for the specified tagged query
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) applyTaggedQueryMergeAtPath:(FPath *)path changedChildren:(FCompoundWrite *)changedChildren tagId:(NSNumber *)tagId {
+ FQuerySpec *query = [self queryForTag:tagId];
+ if (query != nil) {
+ FPath *relativePath = [FPath relativePathFrom:query.path to:path];
+ [self.persistenceManager updateServerCacheWithMerge:changedChildren atPath:path];
+ FMerge *operation = [[FMerge alloc] initWithSource:[FOperationSource forServerTaggedQuery:query.params]
+ path:relativePath
+ children:changedChildren];
+ return [self applyTaggedOperation:operation atPath:query.path];
+ } else {
+ // We've already removed the query. No big deal, ignore the update.
+ return @[];
+ }
+}
+
+- (NSArray *) applyTaggedServerRangeMergeAtPath:(FPath *)path updates:(NSArray *)ranges tagId:(NSNumber *)tagId {
+ FQuerySpec *query = [self queryForTag:tagId];
+ if (query != nil) {
+ NSAssert([path isEqual:query.path], @"Tagged update path and query path must match");
+ FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
+ NSAssert(syncPoint != nil, @"Missing sync point for query tag that we're tracking.");
+ FView *view = [syncPoint viewForQuery:query];
+ NSAssert(view != nil, @"Missing view for query tag that we're tracking");
+ id serverNode = [view serverCache];
+ for (FRangeMerge *merge in ranges) {
+ serverNode = [merge applyToNode:serverNode];
+ }
+ return [self applyTaggedQueryOverwriteAtPath:path newData:serverNode tagId:tagId];
+ } else {
+ // We've already removed the query. No big deal, ignore the update.
+ return @[];
+ }
+}
+
+/**
+* Add an event callback for the specified query
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) addEventRegistration:(id)eventRegistration forQuery:(FQuerySpec *)query {
+ FPath *path = query.path;
+
+ __block BOOL foundAncestorDefaultView = NO;
+ [self.syncPointTree forEachOnPath:query.path whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
+ foundAncestorDefaultView = foundAncestorDefaultView || [syncPoint hasCompleteView];
+ return !foundAncestorDefaultView;
+ }];
+
+ [self.persistenceManager setQueryActive:query];
+
+ FSyncPoint *syncPoint = [self.syncPointTree valueAtPath:path];
+ if (syncPoint == nil) {
+ syncPoint = [[FSyncPoint alloc] initWithPersistenceManager:self.persistenceManager];
+ self.syncPointTree = [self.syncPointTree setValue:syncPoint atPath:path];
+ }
+
+ BOOL viewAlreadyExists = [syncPoint viewExistsForQuery:query];
+ NSArray *events;
+ if (viewAlreadyExists) {
+ events = [syncPoint addEventRegistration:eventRegistration forExistingViewForQuery:query];
+ } else {
+ if (![query loadsAllData]) {
+ // We need to track a tag for this query
+ NSAssert(self.queryToTagMap[query] == nil, @"View does not exist, but we have a tag");
+ NSNumber *tagId = [self.queryTagCounter getAndIncrement];
+ self.queryToTagMap[query] = tagId;
+ self.tagToQueryMap[tagId] = query;
+ }
+
+ FWriteTreeRef *writesCache = [self.pendingWriteTree childWritesForPath:path];
+ FCacheNode *serverCache = [self serverCacheForQuery:query];
+ events = [syncPoint addEventRegistration:eventRegistration
+ forNonExistingViewForQuery:query
+ writesCache:writesCache
+ serverCache:serverCache];
+
+ // There was no view and no default listen
+ if (!foundAncestorDefaultView) {
+ FView *view = [syncPoint viewForQuery:query];
+ NSMutableArray *mutableEvents = [events mutableCopy];
+ [mutableEvents addObjectsFromArray:[self setupListenerOnQuery:query view:view]];
+ events = mutableEvents;
+ }
+ }
+
+ return events;
+}
+
+- (FCacheNode *)serverCacheForQuery:(FQuerySpec *)query {
+ __block id serverCacheNode = nil;
+
+ [self.syncPointTree forEachOnPath:query.path whileBlock:^BOOL(FPath *pathToSyncPoint, FSyncPoint *syncPoint) {
+ FPath *relativePath = [FPath relativePathFrom:pathToSyncPoint to:query.path];
+ serverCacheNode = [syncPoint completeServerCacheAtPath:relativePath];
+ return serverCacheNode == nil;
+ }];
+
+ FCacheNode *serverCache;
+ if (serverCacheNode != nil) {
+ FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:serverCacheNode index:query.index];
+ serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed isFullyInitialized:YES isFiltered:NO];
+ } else {
+ FCacheNode *persistenceServerCache = [self.persistenceManager serverCacheForQuery:query];
+ if (persistenceServerCache.isFullyInitialized) {
+ serverCache = persistenceServerCache;
+ } else {
+ serverCacheNode = [FEmptyNode emptyNode];
+
+ FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:query.path];
+ [subtree forEachChild:^(NSString *childKey, FSyncPoint *childSyncPoint) {
+ id completeCache = [childSyncPoint completeServerCacheAtPath:[FPath empty]];
+ if (completeCache) {
+ serverCacheNode = [serverCacheNode updateImmediateChild:childKey withNewChild:completeCache];
+ }
+ }];
+ // Fill the node with any available children we have
+ [persistenceServerCache.node enumerateChildrenUsingBlock:^(NSString *key, id node, BOOL *stop) {
+ if (![serverCacheNode hasChild:key]) {
+ serverCacheNode = [serverCacheNode updateImmediateChild:key withNewChild:node];
+ }
+ }];
+ FIndexedNode *indexed = [FIndexedNode indexedNodeWithNode:serverCacheNode index:query.index];
+ serverCache = [[FCacheNode alloc] initWithIndexedNode:indexed isFullyInitialized:NO isFiltered:NO];
+ }
+ }
+
+ return serverCache;
+}
+
+/**
+* Remove event callback(s).
+*
+* If query is the default query, we'll check all queries for the specified eventRegistration.
+* If eventRegistration is null, we'll remove all callbacks for the specified query/queries.
+*
+* @param eventRegistration if nil, all callbacks are removed
+* @param cancelError If provided, appropriate cancel events will be returned
+* @return NSArray of FEvent to raise.
+*/
+- (NSArray *) removeEventRegistration:(id )eventRegistration
+ forQuery:(FQuerySpec *)query
+ cancelError:(NSError *)cancelError {
+ // Find the syncPoint first. Then deal with whether or not it has matching listeners
+ FPath *path = query.path;
+ FSyncPoint *maybeSyncPoint = [self.syncPointTree valueAtPath:path];
+ NSArray *cancelEvents = @[];
+
+ // A removal on a default query affects all queries at that location. A removal on an indexed query, even one without
+ // other query constraints, does *not* affect all queries at that location. So this check must be for 'default', and
+ // not loadsAllData:
+ if (maybeSyncPoint && ([query isDefault] || [maybeSyncPoint viewExistsForQuery:query])) {
+ FTupleRemovedQueriesEvents *removedAndEvents = [maybeSyncPoint removeEventRegistration:eventRegistration forQuery:query cancelError:cancelError];
+ if ([maybeSyncPoint isEmpty]) {
+ self.syncPointTree = [self.syncPointTree removeValueAtPath:path];
+ }
+ NSArray *removed = removedAndEvents.removedQueries;
+ cancelEvents = removedAndEvents.cancelEvents;
+
+ // We may have just removed one of many listeners and can short-circuit this whole process
+ // We may also not have removed a default listener, in which case all of the descendant listeners should already
+ // be properly set up.
+ //
+ // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData: instead
+ // of isDefault:
+ NSUInteger defaultQueryIndex = [removed indexOfObjectPassingTest:^BOOL(FQuerySpec *q, NSUInteger idx, BOOL *stop) {
+ return [q loadsAllData];
+ }];
+ BOOL removingDefault = defaultQueryIndex != NSNotFound;
+ [removed enumerateObjectsUsingBlock:^(FQuerySpec *query, NSUInteger idx, BOOL *stop) {
+ [self.persistenceManager setQueryInactive:query];
+ }];
+ NSNumber *covered = [self.syncPointTree findOnPath:path andApplyBlock:^id(FPath *relativePath, FSyncPoint *parentSyncPoint) {
+ return [NSNumber numberWithBool:[parentSyncPoint hasCompleteView]];
+ }];
+
+ if (removingDefault && ![covered boolValue]) {
+ FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
+ // There are potentially child listeners. Determine what if any listens we need to send before executing
+ // the removal
+ if (![subtree isEmpty]) {
+ // We need to fold over our subtree and collect the listeners to send
+ NSArray *newViews = [self collectDistinctViewsForSubTree:subtree];
+
+ // Ok, we've collected all the listens we need. Set them up.
+ [newViews enumerateObjectsUsingBlock:^(FView *view, NSUInteger idx, BOOL *stop) {
+ FQuerySpec *newQuery = view.query;
+ FListenContainer *listenContainer = [self createListenerForView:view];
+ self.listenProvider.startListening([self queryForListening:newQuery], [self tagForQuery:newQuery],
+ listenContainer, listenContainer.onComplete);
+ }];
+ } else {
+ // There's nothing below us, so nothing we need to start listening on
+ }
+ }
+
+ // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query.
+ // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
+ // Also, note that if we have a cancelError, it's already been removed at the provider level.
+ if (![covered boolValue] && [removed count] > 0 && cancelError == nil) {
+ // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
+ // default. Otherwise, we need to iterate through and cancel each individual query
+ if (removingDefault) {
+ // We don't tag default listeners
+ self.listenProvider.stopListening([self queryForListening:query], nil);
+ } else {
+ [removed enumerateObjectsUsingBlock:^(FQuerySpec *queryToRemove, NSUInteger idx, BOOL *stop) {
+ NSNumber *tagToRemove = [self.queryToTagMap objectForKey:queryToRemove];
+ self.listenProvider.stopListening([self queryForListening:queryToRemove], tagToRemove);
+ }];
+ }
+ }
+ // Now, clear all the tags we're tracking for the removed listens.
+ [self removeTags:removed];
+ } else {
+ // No-op, this listener must've been already removed
+ }
+ return cancelEvents;
+}
+
+- (void)keepQuery:(FQuerySpec *)query synced:(BOOL)keepSynced {
+ // Only do something if we actually need to add/remove an event registration
+ if (keepSynced && ![self.keepSyncedQueries containsObject:query]) {
+ [self addEventRegistration:[FKeepSyncedEventRegistration instance] forQuery:query];
+ [self.keepSyncedQueries addObject:query];
+ } else if (!keepSynced && [self.keepSyncedQueries containsObject:query]) {
+ [self removeEventRegistration:[FKeepSyncedEventRegistration instance] forQuery:query cancelError:nil];
+ [self.keepSyncedQueries removeObject:query];
+ }
+}
+
+- (NSArray *) removeAllWrites {
+ [self.persistenceManager removeAllUserWrites];
+ NSArray *removedWrites = [self.pendingWriteTree removeAllWrites];
+ if (removedWrites.count > 0) {
+ FImmutableTree *affectedTree = [[FImmutableTree empty] setValue:@YES atPath:[FPath empty]];
+ return [self applyOperationToSyncPoints:[[FAckUserWrite alloc] initWithPath:[FPath empty]
+ affectedTree:affectedTree revert:YES]];
+ } else {
+ return @[];
+ }
+}
+
+/**
+* Returns a complete cache, if we have one, of the data at a particular path. The location must have a listener above
+* it, but as this is only used by transaction code, that should always be the case anyways.
+*
+* Note: this method will *include* hidden writes from transaction with applyLocally set to false.
+* @param path The path to the data we want
+* @param writeIdsToExclude A specific set to be excluded
+*/
+- (id ) calcCompleteEventCacheAtPath:(FPath *)path excludeWriteIds:(NSArray *)writeIdsToExclude {
+ BOOL includeHiddenSets = YES;
+ FWriteTree *writeTree = self.pendingWriteTree;
+ id serverCache = [self.syncPointTree findOnPath:path andApplyBlock:^id(FPath *pathSoFar, FSyncPoint *syncPoint) {
+ FPath *relativePath = [FPath relativePathFrom:pathSoFar to:path];
+ id serverCache = [syncPoint completeServerCacheAtPath:relativePath];
+ if (serverCache) {
+ return serverCache;
+ } else {
+ return nil;
+ }
+ }];
+ return [writeTree calculateCompleteEventCacheAtPath:path completeServerCache:serverCache excludeWriteIds:writeIdsToExclude includeHiddenWrites:includeHiddenSets];
+}
+
+#pragma mark -
+#pragma mark Private Methods
+/**
+* This collapses multiple unfiltered views into a single view, since we only need a single
+* listener for them.
+* @return NSArray of FView
+*/
+- (NSArray *) collectDistinctViewsForSubTree:(FImmutableTree *)subtree {
+ return [subtree foldWithBlock:^NSArray *(FPath *relativePath, FSyncPoint *maybeChildSyncPoint, NSDictionary *childMap) {
+ if (maybeChildSyncPoint && [maybeChildSyncPoint hasCompleteView]) {
+ FView *completeView = [maybeChildSyncPoint completeView];
+ return @[completeView];
+ } else {
+ // No complete view here, flatten any deeper listens into an array
+ NSMutableArray *views = [[NSMutableArray alloc] init];
+ if (maybeChildSyncPoint) {
+ views = [[maybeChildSyncPoint queryViews] mutableCopy];
+ }
+ [childMap enumerateKeysAndObjectsUsingBlock:^(NSString *childKey, NSArray *childViews, BOOL *stop) {
+ [views addObjectsFromArray:childViews];
+ }];
+ return views;
+ }
+ }];
+}
+
+/**
+* @param queries NSArray of FQuerySpec
+*/
+- (void) removeTags:(NSArray *)queries {
+ [queries enumerateObjectsUsingBlock:^(FQuerySpec *removedQuery, NSUInteger idx, BOOL *stop) {
+ if (![removedQuery loadsAllData]) {
+ // We should have a tag for this
+ NSNumber *removedQueryTag = self.queryToTagMap[removedQuery];
+ [self.queryToTagMap removeObjectForKey:removedQuery];
+ [self.tagToQueryMap removeObjectForKey:removedQueryTag];
+ }
+ }];
+}
+
+- (FQuerySpec *) queryForListening:(FQuerySpec *)query {
+ if (query.loadsAllData && !query.isDefault) {
+ // We treat queries that load all data as default queries
+ return [FQuerySpec defaultQueryAtPath:query.path];
+ } else {
+ return query;
+ }
+}
+
+/**
+* For a given new listen, manage the de-duplication of outstanding subscriptions.
+* @return NSArray of FEvent events to support synchronous data sources
+*/
+- (NSArray *) setupListenerOnQuery:(FQuerySpec *)query view:(FView *)view {
+ FPath *path = query.path;
+ NSNumber *tagId = [self tagForQuery:query];
+ FListenContainer *listenContainer = [self createListenerForView:view];
+
+ NSArray *events = self.listenProvider.startListening([self queryForListening:query], tagId, listenContainer,
+ listenContainer.onComplete);
+
+ FImmutableTree *subtree = [self.syncPointTree subtreeAtPath:path];
+ // The root of this subtree has our query. We're here because we definitely need to send a listen for that, but we
+ // may need to shadow other listens as well.
+ if (tagId != nil) {
+ NSAssert(![subtree.value hasCompleteView], @"If we're adding a query, it shouldn't be shadowed");
+ } else {
+ // Shadow everything at or below this location, this is a default listener.
+ NSArray *queriesToStop = [subtree foldWithBlock:^id(FPath *relativePath, FSyncPoint *maybeChildSyncPoint, NSDictionary *childMap) {
+ if (![relativePath isEmpty] && maybeChildSyncPoint != nil && [maybeChildSyncPoint hasCompleteView]) {
+ return @[[maybeChildSyncPoint completeView].query];
+ } else {
+ // No default listener here, flatten any deeper queries into an array
+ NSMutableArray *queries = [[NSMutableArray alloc] init];
+ if (maybeChildSyncPoint != nil) {
+ for (FView *view in [maybeChildSyncPoint queryViews]) {
+ [queries addObject:view.query];
+ }
+ }
+ [childMap enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSArray *childQueries, BOOL *stop) {
+ [queries addObjectsFromArray:childQueries];
+ }];
+ return queries;
+ }
+ }];
+ for (FQuerySpec *queryToStop in queriesToStop) {
+ self.listenProvider.stopListening([self queryForListening:queryToStop], [self tagForQuery:queryToStop]);
+ }
+ }
+ return events;
+}
+
+- (FListenContainer *) createListenerForView:(FView *)view {
+ FQuerySpec *query = view.query;
+ NSNumber *tagId = [self tagForQuery:query];
+
+ FListenContainer *listenContainer = [[FListenContainer alloc] initWithView:view
+ onComplete:^(NSString *status) {
+ if ([status isEqualToString:@"ok"]) {
+ if (tagId != nil) {
+ return [self applyTaggedListenCompleteAtPath:query.path tagId:tagId];
+ } else {
+ return [self applyListenCompleteAtPath:query.path];
+ }
+ } else {
+ // If a listen failed, kill all of the listeners here, not just the one that triggered the error.
+ // Note that this may need to be scoped to just this listener if we change permissions on filtered children
+ NSError *error = [FUtilities errorForStatus:status andReason:nil];
+ FFWarn(@"I-RDB038012", @"Listener at %@ failed: %@", query.path, status);
+ return [self removeEventRegistration:nil forQuery:query cancelError:error];
+ }
+ }];
+
+ return listenContainer;
+}
+
+/**
+* @return The query associated with the given tag, if we have one
+*/
+- (FQuerySpec *) queryForTag:(NSNumber *)tagId {
+ return self.tagToQueryMap[tagId];
+}
+
+/**
+* @return The tag associated with the given query
+*/
+- (NSNumber *) tagForQuery:(FQuerySpec *)query {
+ return self.queryToTagMap[query];
+}
+
+#pragma mark -
+#pragma mark applyOperation Helpers
+
+/**
+* A helper method that visits all descendant and ancestor SyncPoints, applying the operation.
+*
+* NOTES:
+* - Descendant SyncPoints will be visited first (since we raise events depth-first).
+
+* - We call applyOperation: on each SyncPoint passing three things:
+* 1. A version of the Operation that has been made relative to the SyncPoint location.
+* 2. A WriteTreeRef of any writes we have cached at the SyncPoint location.
+* 3. A snapshot Node with cached server data, if we have it.
+
+* - We concatenate all of the events returned by each SyncPoint and return the result.
+*
+* @return Array of FEvent
+*/
+- (NSArray *) applyOperationToSyncPoints:(id)operation {
+ return [self applyOperationHelper:operation syncPointTree:self.syncPointTree serverCache:nil
+ writesCache:[self.pendingWriteTree childWritesForPath:[FPath empty]]];
+}
+
+/**
+* Recursive helper for applyOperationToSyncPoints_
+*/
+- (NSArray *) applyOperationHelper:(id)operation syncPointTree:(FImmutableTree *)syncPointTree
+ serverCache:(id)serverCache writesCache:(FWriteTreeRef *)writesCache {
+ if ([operation.path isEmpty]) {
+ return [self applyOperationDescendantsHelper:operation syncPointTree:syncPointTree serverCache:serverCache writesCache:writesCache];
+ } else {
+ FSyncPoint *syncPoint = syncPointTree.value;
+
+ // If we don't have cached server data, see if we can get it from this SyncPoint
+ if (serverCache == nil && syncPoint != nil) {
+ serverCache = [syncPoint completeServerCacheAtPath:[FPath empty]];
+ }
+
+ NSMutableArray *events = [[NSMutableArray alloc] init];
+ NSString *childKey = [operation.path getFront];
+ id childOperation = [operation operationForChild:childKey];
+ FImmutableTree *childTree = [syncPointTree.children get:childKey];
+ if (childTree != nil && childOperation != nil) {
+ id