From 045858b77ac0a39f7232ea6aedb1223be4abd7b3 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:08:54 +0000 Subject: [PATCH 001/275] Create installation.android.md --- docs/installation.android.md | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/installation.android.md diff --git a/docs/installation.android.md b/docs/installation.android.md new file mode 100644 index 0000000..2078ed7 --- /dev/null +++ b/docs/installation.android.md @@ -0,0 +1,55 @@ +# Android Installation + +The simplest way of installing on Android is to use React Native linker: + +``` +react-native link react-native-firestack +``` + +## Manually + +To install `react-native-firestack` manually in our project, we'll need to import the package from `io.fullstack.firestack` in our project's `android/app/src/main/java/com/[app name]/MainApplication.java` and list it as a package for ReactNative in the `getPackages()` function: + +```java +package com.appName; +// ... +import io.fullstack.firestack.FirestackPackage; +// ... +public class MainApplication extends Application implements ReactApplication { + // ... + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new FirestackPackage() // <-- Add this line + ); + } + }; + // ... +} +``` + +We'll also need to list it in our `android/app/build.gradle` file as a dependency that we want React Native to compile. In the `dependencies` listing, add the `compile` line: + +```java +dependencies { + compile project(':react-native-firestack') +} +``` + +Add to `AndroidManifest.xml` file +```diff + ++ ++ ++ ++ ++ + ++ ++ ++ ++ ++ +``` From 419320b391a96d52abb9f30dad4ecfe8248f9199 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:13:30 +0000 Subject: [PATCH 002/275] Create installation.ios.md --- docs/installation.ios.md | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/installation.ios.md diff --git a/docs/installation.ios.md b/docs/installation.ios.md new file mode 100644 index 0000000..f400772 --- /dev/null +++ b/docs/installation.ios.md @@ -0,0 +1,67 @@ +#iOS Installation + +### cocoapods + +Unfortunately, due to AppStore restrictions, we currently do _not_ package Firebase libraries in with Firestack. However, the good news is we've automated the process (with many thanks to the Auth0 team for inspiration) of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`. + +**Remember to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on**. + +We need to link the package with our development packaging. We have two options to handle linking: + +#### Automatically with react-native-cli + +React native ships with a `link` command that can be used to link the projects together, which can help automate the process of linking our package environments. + +```bash +react-native link react-native-firestack +``` + +Update the newly installed pods once the linking is done: + +```bash +cd ios && pod update --verbose +``` + +#### Manually + +If you prefer not to use `react-native link`, we can manually link the package together with the following steps, after `npm install`: + +A. In XCode, right click on `Libraries` and find the `Add Files to [project name]`. + +![Add library to project](http://d.pr/i/2gEH.png) + +B. Add the `node_modules/react-native-firestack/ios/Firestack.xcodeproj` + +![Firebase.xcodeproj in Libraries listing](http://d.pr/i/19ktP.png) + +C. Ensure that the `Build Settings` of the `Firestack.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_: + + 1. `$(SRCROOT)/../../react-native/React` + 2. `$(SRCROOT)/../node_modules/react-native/React` + 3. `${PROJECT_DIR}/../../../ios/Pods` + +![Recursive paths](http://d.pr/i/1hAr1.png) + +D. Setting up cocoapods + +Since we're dependent upon cocoapods (or at least the Firebase libraries being available at the root project -- i.e. your application), we have to make them available for Firestack to find them. + +Using cocoapods is the easiest way to get started with this linking. Add or update a `Podfile` at `ios/Podfile` in your app with the following: + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +[ + 'Firebase/Core', + 'Firebase/Auth', + 'Firebase/Storage', + 'Firebase/Database', + 'Firebase/RemoteConfig', + 'Firebase/Messaging' +].each do |lib| + pod lib +end +``` + +Then you can run `(cd ios && pod install)` to get the pods opened. If you do use this route, remember to use the `.xcworkspace` file. + +If you don't want to use cocoapods, you don't need to use it! Just make sure you link the Firebase libraries in your project manually. For more information, check out the relevant Firebase docs at [https://firebase.google.com/docs/ios/setup#frameworks](https://firebase.google.com/docs/ios/setup#frameworks). From 96d676b337cc31c910cb16a39172ba087cf9b443 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:14:14 +0000 Subject: [PATCH 003/275] Update installation.ios.md --- docs/installation.ios.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.ios.md b/docs/installation.ios.md index f400772..7eb4b51 100644 --- a/docs/installation.ios.md +++ b/docs/installation.ios.md @@ -1,6 +1,8 @@ #iOS Installation -### cocoapods +If you don't want to use cocoapods, you don't need to use it! Just make sure you link the Firebase libraries in your project manually. For more information, check out the relevant Firebase docs at [https://firebase.google.com/docs/ios/setup#frameworks](https://firebase.google.com/docs/ios/setup#frameworks). + +## cocoapods Unfortunately, due to AppStore restrictions, we currently do _not_ package Firebase libraries in with Firestack. However, the good news is we've automated the process (with many thanks to the Auth0 team for inspiration) of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`. @@ -63,5 +65,3 @@ end ``` Then you can run `(cd ios && pod install)` to get the pods opened. If you do use this route, remember to use the `.xcworkspace` file. - -If you don't want to use cocoapods, you don't need to use it! Just make sure you link the Firebase libraries in your project manually. For more information, check out the relevant Firebase docs at [https://firebase.google.com/docs/ios/setup#frameworks](https://firebase.google.com/docs/ios/setup#frameworks). From b9fb5a37feae3619d1a0e5894a011c81d0c9ba72 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:19:26 +0000 Subject: [PATCH 004/275] Create firebase-setup.md --- docs/firebase-setup.md | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/firebase-setup.md diff --git a/docs/firebase-setup.md b/docs/firebase-setup.md new file mode 100644 index 0000000..af3dc67 --- /dev/null +++ b/docs/firebase-setup.md @@ -0,0 +1,88 @@ +# Firebase Setup + +The Firestack library is intended on making it easy to work with [Firebase](https://firebase.google.com/) and provides a small native shim to the Firebase native code. + +To add Firebase to your project, make sure to create a project in the [Firebase console](https://firebase.google.com/console) + +![Create a new project](http://d.pr/i/17cJ2.png) + +Each platform uses a different setup method after creating the project. + +## iOS + +After creating a Firebase project, click on the [Add Firebase to your iOS app](http://d.pr/i/3sEL.png) and follow the steps from there to add the configuration file. You do _not_ need to set up a cocoapods project (this is already done through firestack). Make sure not to forget the `Copy Files` phase in iOS. + +[Download the Firebase config file](https://support.google.com/firebase/answer/7015592) and place it in your app directory next to your app source code: + +![GoogleService-Info.plist](http://d.pr/i/1eGev.png) + +Once you download the configuration file, make sure you place it in the root of your Xcode project. Every different Bundle ID (aka, even different project variants needs their own configuration file). + +Lastly, due to some dependencies requirements, Firestack supports iOS versions 8.0 and up. Make sure to update the minimum version of your iOS app to `8.0`. + +## Android + +There are several ways to setup Firebase on Android. The _easiest_ way is to pass the configuration settings in JavaScript. In that way, there is no setup for the native platform. + +### google-services.json setup +If you prefer to include the default settings in the source of your app, download the `google-services.json` file provided by Firebase in the _Add Firebase to Android_ platform menu in your Firebase configuration console. + +Next you'll have to add the google-services gradle plugin in order to parse it. + +Add the google-services gradle plugin as a dependency in the *project* level build.gradle +`android/build.gradle` +```java +buildscript { + // ... + dependencies { + // ... + classpath 'com.google.gms:google-services:3.0.0' + } +} +``` + +In your app build.gradle file, add the gradle plugin at the VERY BOTTOM of the file (below all dependencies) +`android/app/build.gradle` +```java +apply plugin: 'com.google.gms.google-services' +``` + +## Usage + +After creating a Firebase project and installing the library, we can use it in our project by importing the library in our JavaScript: + +```javascript +import Firestack from 'react-native-firestack' +``` + +We need to tell the Firebase library we want to _configure_ the project. Firestack provides a way to configure both the native and the JavaScript side of the project at the same time with a single command: + +```javascript +const firestack = new Firestack(); +``` + +We can pass _custom_ options by passing an object with configuration options. The configuration object will be generated first by the native configuration object, if set and then will be overridden if passed in JS. That is, all of the following key/value pairs are optional if the native configuration is set. + +| option | type | Default Value | Description | +|----------------|----------|-------------------------|----------------------------------------| +| debug | bool | false | When set to true, Firestack will log messages to the console and fire `debug` events we can listen to in `js` | +| bundleID | string | Default from app `[NSBundle mainBundle]` | The bundle ID for the app to be bundled with | +| googleAppID | string | "" | The Google App ID that is used to uniquely identify an instance of an app. | +| databaseURL | string | "" | The database root (i.e. https://my-app.firebaseio.com) | +| deepLinkURLScheme | string | "" | URL scheme to set up durable deep link service | +| storageBucket | string | "" | The Google Cloud storage bucket name | +| androidClientID | string | "" | The Android client ID used in Google AppInvite when an iOS app has it's android version | +| GCMSenderID | string | "" | The Project number from the Google Developer's console used to configure Google Cloud Messaging | +| trackingID | string | "" | The tracking ID for Google Analytics | +| clientID | string | "" | The OAuth2 client ID for iOS application used to authenticate Google Users for signing in with Google | +| APIKey | string | "" | The secret iOS API key used for authenticating requests from our app | + +For instance: + +```javascript +const configurationOptions = { + debug: true +}; +const firestack = new Firestack(configurationOptions); +firestack.on('debug', msg => console.log('Received debug message', msg)) +``` From 2a167810ed6238ffd997fa6c219b845845e406a6 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:44:53 +0000 Subject: [PATCH 005/275] Update README.md --- README.md | 909 ++---------------------------------------------------- 1 file changed, 18 insertions(+), 891 deletions(-) diff --git a/README.md b/README.md index 84c3333..bf3e867 100644 --- a/README.md +++ b/README.md @@ -1,903 +1,30 @@ ## Firestack -Firestack makes using the latest [Firebase](http://firebase.com) straight-forward. +Firestack makes using the latest [Firebase](http://firebase.com) with React Native straight-forward. [![Gitter](https://badges.gitter.im/fullstackreact/react-native-firestack.svg)](https://gitter.im/fullstackreact/react-native-firestack?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -## What - -Firestack is a _light-weight_ layer sitting atop the native Firebase libraries for iOS and Android and mirrors the React Native JS api as closely as possible. +[![npm version](https://img.shields.io/npm/v/react-native-firestack.svg)](https://www.npmjs.com/package/react-native-firestack) +[![License](https://img.shields.io/npm/l/react-native-firestack.svg)](/LICENSE) + +Firestack is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the React Native JS api as closely as possible. It features: + +* Authentication +* Storage + * upload/download files + * download urls +* Real-time database +* Presence +* Analytics +* Cloud Messaging (currently Android only) +* Remote configuration +* Redux support (not required) For a detailed discussion of how Firestack works as well as how to contribute, check out our [contribution guide](https://github.com/fullstackreact/react-native-firestack/blob/master/Contributing.md). -## Features +## Firestack vs Firebase JS lib -* Nearly automatic, rapid setup on Firebase -* Covers lots of awesome features of Firebase: - * authentication - * username and password - * social auth (implemented, but need to add providers) - * storage handling - * upload files - * download urls - * download files - * real-time database - * presence out-of-the-box - * analytics - * Remote configuration -* Redux support built-in (but not required) -* Android and iOS support -* Community supported and professionally backed -* Intended on being as drop-dead simple as possible -* And so much more +Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it's designed for the web and/or server. The native SDKs provide much needed features specifically for mobile applications such as offline persistance. Firestack provides a JavaScript interface into the native SDKs to allow your React Native application to utilise these features, and more! ## Example app We have a working application example available in at [fullstackreact/FirestackApp](https://github.com/fullstackreact/FirestackApp). Check it out for more details about how to use Firestack. - -## Why? - -Firebase is awesome and it's combination with the Google Cloud Platform makes it super awesome. Sadly, the latest version of Firebase requires the `window` object. That's where Firestack comes in! Firestack provides a really thin layer that sits on top of the native Firebase SDKs and attempts to use the JavaScript library as much as possible rather than reinventing the wheel. - -## Installing - -Getting `react-native-firestack` up and running in your app should be a 2 step process + 1 for each platform. - -1. Install the `npm` package -2. Link the project with `react-native link react-native-firestack` -3. To ensure Android is setup, check your `MainApplication.java` for the `FirestackPackage()` line. - -Those steps in more detail: - -Install the `npm` package with: - -```bash -npm install react-native-firestack --save -``` - -To use Firestack, we'll need to have a development environment that includes the same prerequisites of Firebase. - -### iOS (with cocoapods) - -Unfortunately, due to AppStore restrictions, we currently do _not_ package Firebase libraries in with Firestack. However, the good news is we've automated the process (with many thanks to the Auth0 team for inspiration) of setting up with cocoapods. This will happen automatically upon linking the package with `react-native-cli`. - -**Remember to use the `ios/[YOUR APP NAME].xcworkspace` instead of the `ios/[YOUR APP NAME].xcproj` file from now on**. - -We need to link the package with our development packaging. We have two options to handle linking: - -#### Automatically with react-native-cli - -React native ships with a `link` command that can be used to link the projects together, which can help automate the process of linking our package environments. - -```bash -react-native link react-native-firestack -``` - -Update the newly installed pods once the linking is done: - -```bash -cd ios && pod update --verbose -``` - -#### Manually - -If you prefer not to use `rnpm`, we can manually link the package together with the following steps, after `npm install`: - -1. In XCode, right click on `Libraries` and find the `Add Files to [project name]`. - -![Add library to project](http://d.pr/i/2gEH.png) - -2. Add the `node_modules/react-native-firestack/ios/Firestack.xcodeproj` - -![Firebase.xcodeproj in Libraries listing](http://d.pr/i/19ktP.png) - -3. Ensure that the `Build Settings` of the `Firestack.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_: - - 1. `$(SRCROOT)/../../react-native/React` - 2. `$(SRCROOT)/../node_modules/react-native/React` - 3. `${PROJECT_DIR}/../../../ios/Pods` - -![Recursive paths](http://d.pr/i/1hAr1.png) - -4. Setting up cocoapods - -Since we're dependent upon cocoapods (or at least the Firebase libraries being available at the root project -- i.e. your application), we have to make them available for Firestack to find them. - -Using cocoapods is the easiest way to get started with this linking. Add or update a `Podfile` at `ios/Podfile` in your app with the following: - -```ruby -source 'https://github.com/CocoaPods/Specs.git' -[ - 'Firebase/Core', - 'Firebase/Auth', - 'Firebase/Storage', - 'Firebase/Database', - 'Firebase/RemoteConfig', - 'Firebase/Messaging' -].each do |lib| - pod lib -end -``` - -Then you can run `(cd ios && pod install)` to get the pods opened. If you do use this route, remember to use the `.xcworkspace` file. - -If you don't want to use cocoapods, you don't need to use it! Just make sure you link the Firebase libraries in your project manually. For more information, check out the relevant Firebase docs at [https://firebase.google.com/docs/ios/setup#frameworks](https://firebase.google.com/docs/ios/setup#frameworks). - -### Android - -Full Android support is coming soon, as it currently supports a smaller feature-set than the iOS version. Just as we do with iOS, we'll need to install the library using `npm` and call `link` on the library: - -```bash -react-native link react-native-firestack -``` - -Firestack includes the Firebase libraries and will link those directly into our project automatically. - -#### Manually - -To install `react-native-firestack` manually in our project, we'll need to import the package from `io.fullstack.firestack` in our project's `android/app/src/main/java/com/[app name]/MainApplication.java` and list it as a package for ReactNative in the `getPackages()` function: - -```java -package com.appName; -// ... -import io.fullstack.firestack.FirestackPackage; -// ... -public class MainApplication extends Application implements ReactApplication { - // ... - - @Override - protected List getPackages() { - return Arrays.asList( - new MainReactPackage(), - new FirestackPackage() - ); - } - }; - // ... -} -``` - -We'll also need to list it in our `android/app/build.gradle` file as a dependency that we want React Native to compile. In the `dependencies` listing, add the `compile` line: - -```java -dependencies { - compile project(':react-native-firestack') -} -``` - -Add to `AndroidManifest.xml` file -```diff - -+ -+ -+ -+ -+ - -+ -+ -+ -+ -+ -``` - -## Firebase setup - -The Firestack library is intended on making it easy to work with [Firebase](https://firebase.google.com/) and provides a small native shim to the Firebase native code. - -To add Firebase to your project, make sure to create a project in the [Firebase console](https://firebase.google.com/console) - -![Create a new project](http://d.pr/i/17cJ2.png) - -Each platform uses a different setup method after creating the project. - -### iOS - -After creating a Firebase project, click on the [Add Firebase to your iOS app](http://d.pr/i/3sEL.png) and follow the steps from there to add the configuration file. You do _not_ need to set up a cocoapods project (this is already done through firestack). Make sure not to forget the `Copy Files` phase in iOS. - -[Download the Firebase config file](https://support.google.com/firebase/answer/7015592) and place it in your app directory next to your app source code: - -![GoogleService-Info.plist](http://d.pr/i/1eGev.png) - -Once you download the configuration file, make sure you place it in the root of your Xcode project. Every different Bundle ID (aka, even different project variants needs their own configuration file). - -Lastly, due to some dependencies requirements, Firestack supports iOS versions 8.0 and up. Make sure to update the minimum version of your iOS app to `8.0`. - -### Android - -There are several ways to setup Firebase on Android. The _easiest_ way is to pass the configuration settings in JavaScript. In that way, there is no setup for the native platform. - -#### google-services.json setup -If you prefer to include the default settings in the source of your app, download the `google-services.json` file provided by Firebase in the _Add Firebase to Android_ platform menu in your Firebase configuration console. - -Next you'll have to add the google-services gradle plugin in order to parse it. - -Add the google-services gradle plugin as a dependency in the *project* level build.gradle -`android/build.gradle` -```java -buildscript { - // ... - dependencies { - // ... - classpath 'com.google.gms:google-services:3.0.0' - } -} -``` - -In your app build.gradle file, add the gradle plugin at the VERY BOTTOM of the file (below all dependencies) -`android/app/build.gradle` -```java -apply plugin: 'com.google.gms.google-services' -``` - -## Usage - -After creating a Firebase project and installing the library, we can use it in our project by importing the library in our JavaScript: - -```javascript -import Firestack from 'react-native-firestack' -``` - -We need to tell the Firebase library we want to _configure_ the project. Firestack provides a way to configure both the native and the JavaScript side of the project at the same time with a single command: - -```javascript -const firestack = new Firestack(); -``` - -We can pass _custom_ options by passing an object with configuration options. The configuration object will be generated first by the native configuration object, if set and then will be overridden if passed in JS. That is, all of the following key/value pairs are optional if the native configuration is set. - -| option | type | Default Value | Description | -|----------------|----------|-------------------------|----------------------------------------| -| debug | bool | false | When set to true, Firestack will log messages to the console and fire `debug` events we can listen to in `js` | -| bundleID | string | Default from app `[NSBundle mainBundle]` | The bundle ID for the app to be bundled with | -| googleAppID | string | "" | The Google App ID that is used to uniquely identify an instance of an app. | -| databaseURL | string | "" | The database root (i.e. https://my-app.firebaseio.com) | -| deepLinkURLScheme | string | "" | URL scheme to set up durable deep link service | -| storageBucket | string | "" | The Google Cloud storage bucket name | -| androidClientID | string | "" | The Android client ID used in Google AppInvite when an iOS app has it's android version | -| GCMSenderID | string | "" | The Project number from the Google Developer's console used to configure Google Cloud Messaging | -| trackingID | string | "" | The tracking ID for Google Analytics | -| clientID | string | "" | The OAuth2 client ID for iOS application used to authenticate Google Users for signing in with Google | -| APIKey | string | "" | The secret iOS API key used for authenticating requests from our app | - -For instance: - -```javascript -const configurationOptions = { - debug: true -}; -const firestack = new Firestack(configurationOptions); -firestack.on('debug', msg => console.log('Received debug message', msg)) -``` - -## API documentation - -Firestack is broken up into multiple parts, based upon the different API features that Firebase provides. - -All methods return a promise. - -### Authentication - -Firestack handles authentication for us out of the box, both with email/password-based authentication and through oauth providers (with a separate library to handle oauth providers). - -> Android requires the Google Play services to installed for authentication to function. - -#### listenForAuth() - -Firebase gives us a reactive method for listening for authentication. That is we can set up a listener to call a method when the user logs in and out. To set up the listener, call the `listenForAuth()` method: - -```javascript -firestack.auth.listenForAuth(function(evt) { - // evt is the authentication event - // it contains an `error` key for carrying the - // error message in case of an error - // and a `user` key upon successful authentication - if (!evt.authenticated) { - // There was an error or there is no user - console.error(evt.error) - } else { - // evt.user contains the user details - console.log('User details', evt.user); - } -}) -.then(() => console.log('Listening for authentication changes')) -``` - -#### unlistenForAuth() - -We can remove this listener by calling the `unlistenForAuth()` method. This is important to release resources from our app when we don't need to hold on to the listener any longer. - -```javascript -firestack.auth.unlistenForAuth() -``` - -#### createUserWithEmail() - -We can create a user by calling the `createUserWithEmail()` function. The `createUserWithEmail()` accepts two parameters, an email and a password. - -```javascript -firestack.auth.createUserWithEmail('ari@fullstack.io', '123456') - .then((user) => { - console.log('user created', user) - }) - .catch((err) => { - console.error('An error occurred', err); - }) -``` - -#### signInWithEmail() - -To sign a user in with their email and password, use the `signInWithEmail()` function. It accepts two parameters, the user's email and password: - -```javascript -firestack.auth.signInWithEmail('ari@fullstack.io', '123456') - .then((user) => { - console.log('User successfully logged in', user) - }) - .catch((err) => { - console.error('User signin error', err); - }) -``` - -#### signInWithCustomToken() - -To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: - -```javascript -firestack.auth.signInWithCustomToken(TOKEN) - .then((user) => { - console.log('User successfully logged in', user) - }) - .catch((err) => { - console.error('User signin error', err); - }) -``` - -#### signInWithProvider() - -We can use an external authentication provider, such as twitter/facebook for authentication. In order to use an external provider, we need to include another library to handle authentication. - -> By using a separate library, we can keep our dependencies a little lower and the size of the application down. - -### OAuth setup with library - -[Currently undergoing updates] - -### socialLogin with custom Library -If you don't want to use [react-native-oauth](https://github.com/fullstackreact/react-native-oauth), you can use other library such as [react-native-facebook-login](https://github.com/magus/react-native-facebook-login). - -```javascript -var {FBLogin, FBLoginManager} = require('react-native-facebook-login'); - -var Login = React.createClass({ - render: function() { - return ( - { - console.log(user) - }) - }} - /> - ); - } -}); -``` - -If the `signInWithProvider()` method resolves correct and we have already set up our `listenForAuth()` method properly, it will fire and we'll have a logged in user through Firebase. - -### reauthenticateWithCredentialForProvider() - -When the auth token has expired, we can ask firebase to reauthenticate with the provider. This method accepts the _same_ arguments as `signInWithProvider()` accepts. - -#### updateUserEmail() - -We can update the current user's email by using the command: `updateUserEmail()`. It accepts a single argument: the user's new email: - -```javascript -firestack.auth.updateUserEmail('ari+rocks@fullstack.io') - .then((res) => console.log('Updated user email')) - .catch(err => console.error('There was an error updating user email')) -``` - -#### updateUserPassword() - -We can update the current user's password using the `updateUserPassword()` method. It accepts a single parameter: the new password for the current user - -```javascript -firestack.auth.updateUserPassword('somethingReallyS3cr3t733t') - .then(res => console.log('Updated user password')) - .catch(err => console.error('There was an error updating your password')) -``` - -### sendPasswordResetWithEmail() - -To send a password reset for a user based upon their email, we can call the `sendPasswordResetWithEmail()` method. It accepts a single parameter: the email of the user to send a reset email. - -```javascript -firestack.auth.sendPasswordResetWithEmail('ari+rocks@fullstack.io') - .then(res => console.log('Check your inbox for further instructions')) - .catch(err => console.error('There was an error :(')) -``` - -#### updateUserProfile() - -To update the current user's profile, we can call the `updateUserProfile()` method. - -It accepts a single parameter: - -* object which contains updated key/values for the user's profile. Possible keys are listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile). - -```javascript -firestack.auth.updateUserProfile({ - displayName: 'Ari Lerner' -}) - .then(res => console.log('Your profile has been updated')) - .catch(err => console.error('There was an error :(')) -``` - -#### deleteUser() - -It's possible to delete a user completely from your account on Firebase. Calling the `deleteUser()` method will take care of this for you. - -```javascript -firestack.auth.deleteUser() -.then(res => console.log('Sad to see you go')) -.catch(err => console.error('There was an error - Now you are trapped!')) -``` - -#### getToken() - -If you want user's token, use `getToken()` method. - -```javascript -firestack.auth.getToken() -.then(res => console.log(res.token)) -.catch(err => console.error('error')) -``` - -#### signOut() - -To sign the current user out, use the `signOut()` method. It accepts no parameters - -```javascript -firestack.auth.signOut() -.then(res => console.log('You have been signed out')) -.catch(err => console.error('Uh oh... something weird happened')) -``` - -#### getCurrentUser() - -Although you _can_ get the current user using the `getCurrentUser()` method, it's better to use this from within the callback function provided by `listenForAuth()`. However, if you need to get the current user, call the `getCurrentUser()` method: - -```javascript -firestack.auth.getCurrentUser() -.then(user => console.log('The currently logged in user', user)) -.catch(err => console.error('An error occurred')) -``` - -### Analytics - -Wouldn't it be nice to send analytics about your app usage from your users? Well, you totally can! The Firebase analytics console is incredibly useful and Firestack has a method for interacting with it. You can send any event with contextual information, which automatically includes the currently logged in user using the `logEventWithName()` method. It accepts two parameters: the name of the event and an object containing any contextual information. The values should be serializable (i.e. no complex instance objects). - -#### logEventWithName() - -```javascript -firestack.analytics.logEventWithName("launch", { - 'screen': 'Main screen' -}) -.then(res => console.log('Sent event named launch')) -.catch(err => console.error('You should never end up here')); -``` - -### Storage - -Firebase's integration with the Google platform expanded it's features to include hosting user-generated files, like photos. Firestack provides a thin layer to handle uploading files to Firebase's storage service. - -#### setStorageUrl() - -In order to store anything on Firebase, we need to set the storage url provided by Firebase. This can be set by using the `setStorageUrl()` method. Your storageUrl can be found on the firebase console. - -![Storage url](http://d.pr/i/1lKjQ.png) - -The `setStorageUrl()` method accepts a single parameter: your root storage url (without leading "gs://"). - -```javascript -firestack.storage.setStorageUrl(`${config.firebase.storageBucket}`) -``` - -If the `storageBucket` key is passed as a configuration option, this method is automatically called by default. - -#### uploadFile() - -We can upload a file using the `uploadFile()` method. Using the `uploadFile()` method, we can set the name of the destination file, the path where we want to store it, as well as any metadata along with the file. - -```javascript -firestack.storage.uploadFile(`photos/${auth.user.uid}/${filename}`, path, { - contentType: 'image/jpeg', - contentEncoding: 'base64', -}) -.then((res) => console.log('The file has been uploaded')) -.catch(err => console.error('There was an error uploading the file', err)) -``` - -To upload camera photos, we can combine this method with the `react-native-camera` plugin, for instance: - -```javascript -this.camera.capture() -.then(({path}) => { - firestack.storage.uploadFile(`photos/${auth.user.uid}/${filename}`, path, { - contentType: 'image/jpeg', - contentEncoding: 'base64', - }) -}) -.catch(err => console.error(err)); -``` - -To combine the `react-native-camera` plugin with firestack, we recommend setting the `captureTarget` to the `temp` storage path, like so: - -```javascript - { - this.camera = cam; - }} - captureTarget={Camera.constants.CaptureTarget.temp} - style={styles.preview} - aspect={Camera.constants.Aspect.fill}> - [CAPTURE] - -``` - -Firestack also gives you the ability to listen for database events on upload. The final parameter the `uploadFile()` function accepts is a callback that will be called anytime a storage event is fired. - -The following events are supported: - -* upload_progress -* upload_paused -* upload_resumed - -For example, the `takePicture` function from the example above might look something similar to: - -```javascript -takePicture() { - this.camera.capture() - .then(({path}) => { - const filename = 'photo.jpg' - firestack.storage.uploadFile(`photos/${filename}`, path, { - contentType: 'image/jpeg', - contentEncoding: 'base64', - }, (evt) => { - console.log('Got an event in JS', evt); - }) - .then((res) => { - console.log('result from upload file: ', res); - }) - .catch((err) => { - console.log('error happened with uploadFile', err); - }) - }) - .catch(err => console.error(err)); -} -``` - -#### downloadUrl() - -The `downloadUrl()` method allows us to fetch the URL from the storage obejct in Firebase. It's defined on the `storageRef` object and can be used like so: - -```javascript -const storageRef = data.firestack.storage.ref('photos/photo.jpg'); -storageRef.downloadUrl() -.then(res => { - // res is an object that contains - // the `url` as well as the path to the file in `path` -}) -``` - -#### download() - -It's possible to download remote files as well. The `download()` method will take a remote file and download and save it to the user's device. It is implemented on the `storageRef`: - -```javascript -const storageRef = data.firestack.storage.ref('photos/photo.jpg'); -const localPath = `downloadedFile.jpg`; -storageRef.download(localPath, (msg) => { - // downloading state callback -}) -.then(res => { - // res contains details about the downloaded file -}) -.catch(err => { - // error contains any errors in downloading -}); -``` - -The method accepts a callback that gets called with any download events: - -* download_progress ({eventName: 'download_progress', progress: float }); -* download_paused ({eventName: 'download_paused'}) -* download_resumed ({eventName: 'download_resumed'}) - -As helpful constants, Firestack exports a few storage constants on the `firestack.constants` getter: - -* MAIN_BUNDLE_PATH -* CACHES_DIRECTORY_PATH -* DOCUMENT_DIRECTORY_PATH -* EXTERNAL_DIRECTORY_PATH -* EXTERNAL_STORAGE_DIRECTORY_PATH -* TEMP_DIRECTORY_PATH -* LIBRARY_DIRECTORY_PATH - -And we also export the filetype constants as well: - -* FILETYPE_REGULAR -* FILETYPE_DIRECTORY - -> Note: this idea comes almost directory from [react-native-fs](https://github.com/johanneslumpe/react-native-fs), so we don't claim credit for coming up with this fantastic idea. - -### Realtime Database - -The native Firebase JavaScript library provides a featureful realtime database that works out of the box. Firestack provides an attribute to interact with the database without needing to configure the JS library. - -Ranking strategy - -Add a new record with timestamp using this solution: - -firebaseApp.database.ref('posts').push().then((res) => { - let newPostKey = res.key; - firebaseApp.ServerValue.then(map => { - const postData = { - name: name, - timestamp: map.TIMESTAMP, - text: this.state.postText, - title: this.state.postTitle, - puid: newPostKey - } - let updates = {} - updates['/posts/' + newPostKey] = postData - firebaseApp.database.ref().update(updates).then(() => { - this.setState({ - postStatus: 'Posted! Thank You.', - postText: '', - }); - }).catch(() => { - this.setState({ postStatus: 'Something went wrong!!!' }); - }) - }) -}) - -Then retrieve the feed using this: - -firebaseApp.database.ref('posts').orderByChild('timestamp').limitToLast(30).once('value') -.then((snapshot) => { - this.props.savePosts(snapshot.val()) - const val = snapshot.val(); - console.log(val); -}) - -#### DatabaseRef - -Firestack attempts to provide the same API as the JS Firebase library for both Android and iOS platforms. [Check out the firebase guide](https://firebase.google.com/docs/database/web/read-and-write) for more information on how to use the JS library. - -#### Example - -```javascript - -function handleValueChange(snapshot) { - if (snapshot.val()) { - console.log('The list was updated'); - } -} - -const LIST_KEY = 'path/to/data'; -firestack.database.ref(LIST_KEY).on('value', handleValueChange); - -// Calling `.off` with a reference to the callback function will only remove that specific listener. -// This is useful if multiple components are listening and unlistening to the same ref path. -firestack.database.ref(LIST_KEY).off('value', handleValueChange); - -// Calling `.off` without passing the callback function will remove *all* 'value' listeners for that ref -firestack.database.ref(LIST_KEY).off('value'); - -``` - -// TODO: Finish documenting - -#### Offline data persistence - -For handling offline operations, you can enable persistence by using the `setPersistence()` command. You can turn it on and off by passing the boolean of `true` or `false`. - -```javascript -firestack.database.setPersistence(true); -``` - -The database refs has a `keepSynced()` function to tell the firestack library to keep the data at the `ref` in sync. - -```javascript -const ref = firestack.database - .ref('chat-messages') - .child('roomId'); -ref.keepSynced(true); -``` - -### Presence - -Firestack comes in with a built-in method for handling user connections. We just need to set the presence ref url and tell Firestack to keep track of the user by their child path. - -```javascript -firestack.presence // the presence api - .on('users/connections') // set the users/connections as the - // root for presence handling - .setOnline('auser') // Set the child of auser as online -``` - -While the _device_ is online (the connection), the value of the child object at `users/connections/auser` will be: - -```javascript -{ - online: true, - lastOnline: TIMESTAMP -} -``` - -When the device is offline, the value will be updated with `online: false`: - -```javascript -{ - online: false, - lastOnline: TIMESTAMP -} -``` - -To set up your own handlers on the presence object, you can call `onConnect()` and pass a callback. The method will be called with the `connectedDevice` database reference and you can set up your own handlers: - -```javascript -const presence = firestack.presence - .on('users/connections'); -presence.onConnect((ref) => { - ref.onDisconnect().remove(); // Remove the entry - // or - ref.set({ - location: someLocation - }); - // or whatever you want as it's called with the database - // reference. All methods on the DatabaseRef object are - // available here on the `ref` -}) -``` - -### ServerValue - -Firebase provides some static values based upon the server. We can use the `ServerValue` constant to retrieve these. For instance, to grab the TIMESTAMP on the server, use the `TIMESTAMP` value: - -```javascript -const timestamp = firestack.ServerValue.TIMESTAMP -``` - -### Cloud Messaging - -Access the device registration token - -```javascript - firestack.cloudMessaging.getToken().then(function (token) { - console.log('device token', token); - }); -``` - -Monitor token generation - -```javascript - // add listener - firestack.cloudMessaging.listenForTokenRefresh(function (token) { - console.log('refresh device token', token); - }); - - // remove listener - firestack.cloudMessaging.unlistenForTokenRefresh(); -``` - -Subscribe to topic - -```javascript - firestack.cloudMessaging.subscribeToTopic("topic_name").then(function (topic) { - console.log('Subscribe:'+topic); - }).catch(function(err){ - console.error(err); - }); -``` - -Unsubscribe from topic - -```javascript - firestack.cloudMessaging.unsubscribeFromTopic("topic_name").then(function (topic) { - console.log('unsubscribe:'+topic); - }).catch(function(err){ - console.error(err); - }); -``` - -Receive Messages - -```javascript - firestack.cloudMessaging.listenForReceiveNotification((msg) =>{ - console.log('Receive Messages:'+msg.data); - console.log('Receive Messages:'+msg.notification); - - }); -``` - -### Events - -#### on() - -We can listen to arbitrary events fired by the Firebase library using the `on()` method. The `on()` method accepts a name and a function callback: - -```javascript -firestack.on('listenForAuth', (evt) => console.log('Got an event')); -``` - -#### off() - -To unsubscribe to events fired by Firebase, we can call the `off()` method with the name of the event we want to unsubscribe. - -```javascript -firestack.off('listenForAuth'); -``` - -## FirestackModule - -Firestack provides a built-in way to connect your Redux app using the `FirestackModule` export from Firestack. - -## Running with the `master` branch - -Most of our work is committed to the master branch. If you want to run the bleeding-edge version of Firestack, you'll need to follow these instructions. - -Since `react-native` doesn't like symlinks, we need to clone the raw repository into our `node_modules/` manually. First, in order to tell `react-native` we are using the package `react-native-firestack`, make sure to install the `npm` version: - -```bash -npm install --save react-native-firestack -``` - -After the `npm` version is installed, you can either clone the repo directly into our `node_modules/` directory: - -```bash -git clone https://github.com/fullstackreact/react-native-firestack.git ./node_modules/react-native-firestack -``` - -Alternatively, you can clone the repo somewhere else and `rsync` the directory over to the `node_modules/` directory. - -> This is the method I use as it allows me to separate the codebases: - -```bash -git clone https://github.com/fullstackreact/react-native-firestack.git \ - ~/Development/react-native/mine/react-native-firestack/ - -## And rsync -rsync -avhW --delete \ - --exclude='node_modules' \ - --exclude='.git' \ - ~/Development/react-native/mine/react-native-firestack/ \ - ./node_modules/react-native-firestack/ -``` - -## Contributing - -This is _open-source_ software and we can make it rock for everyone through contributions. - -How do you contribute? Check out our contribution guide at [CONTRIBUTING.md](https://github.com/fullstackreact/react-native-firestack/blob/master/Contributing.md) - -## TODO - -The following is left to be done: - -- [x] Complete FirebaseModule functionality -- [ ] Document FirebaseModule -- [X] Add Android support - - auth/analytics/database/storage/presence are feature-complete. remoteconfig/messaging are mostly-there. -- [x] Add Cloud Messaging - - [ ] Add JS api -- [ ] Move to use swift (cleaner syntax) -- [ ] TODO: Finish Facebook integration From ed38c757a8bcf6ce58da9450b3e2e88d89c49fcf Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:51:05 +0000 Subject: [PATCH 006/275] Update README.md --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf3e867..fe420ad 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ Firestack is a _light-weight_ layer sitting on-top of the native Firebase librar * Remote configuration * Redux support (not required) -For a detailed discussion of how Firestack works as well as how to contribute, check out our [contribution guide](https://github.com/fullstackreact/react-native-firestack/blob/master/Contributing.md). - ## Firestack vs Firebase JS lib Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it's designed for the web and/or server. The native SDKs provide much needed features specifically for mobile applications such as offline persistance. Firestack provides a JavaScript interface into the native SDKs to allow your React Native application to utilise these features, and more! @@ -28,3 +26,24 @@ Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript libra ## Example app We have a working application example available in at [fullstackreact/FirestackApp](https://github.com/fullstackreact/FirestackApp). Check it out for more details about how to use Firestack. + +## Documentation + +* Installation + * iOS + * Android +* Firebase Setup +* API + * Authentication + * Analytics + * Storage + * Realtime Database + * Presence + * ServerValue + * Cloud Messaging + * Events +* Redux + +## Contributing + +For a detailed discussion of how Firestack works as well as how to contribute, check out our [contribution guide](https://github.com/fullstackreact/react-native-firestack/blob/master/Contributing.md). From c8aad05e60c5e1062c2a12923d4b02813b2d60f2 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:51:14 +0000 Subject: [PATCH 007/275] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe420ad..346fc48 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Firestack +# Firestack Firestack makes using the latest [Firebase](http://firebase.com) with React Native straight-forward. From f68355f78f3db2ba00b169caa26ae87916298b3b Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:52:04 +0000 Subject: [PATCH 008/275] Create authentication.md --- docs/api/authentication.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/authentication.md diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1 @@ + From 43786ef738134ddbe6ed473d7cba3fa144c5cff1 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:52:15 +0000 Subject: [PATCH 009/275] Create storage --- docs/api/storage | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/storage diff --git a/docs/api/storage b/docs/api/storage new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/storage @@ -0,0 +1 @@ + From 066ed19fee7daf252e377505d427c04acd14b89f Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:52:33 +0000 Subject: [PATCH 010/275] Create analytics.md --- docs/api/analytics.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/analytics.md diff --git a/docs/api/analytics.md b/docs/api/analytics.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/analytics.md @@ -0,0 +1 @@ + From 339eb097f28398136c69cd35910ffb122a78fdff Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:52:49 +0000 Subject: [PATCH 011/275] Create database.md --- docs/api/database.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/database.md diff --git a/docs/api/database.md b/docs/api/database.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/database.md @@ -0,0 +1 @@ + From 31b5b4948f053d59574fab41d91c85f3254e3ba6 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:53:07 +0000 Subject: [PATCH 012/275] Create presence.md --- docs/api/presence.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/presence.md diff --git a/docs/api/presence.md b/docs/api/presence.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/presence.md @@ -0,0 +1 @@ + From fbf4ea864b00c24cea9c356f3dffa6973d1cd60f Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:53:19 +0000 Subject: [PATCH 013/275] Create server-value.md --- docs/api/server-value.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/server-value.md diff --git a/docs/api/server-value.md b/docs/api/server-value.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/server-value.md @@ -0,0 +1 @@ + From f626aa1c8833e7c4ea5ea3d8c30c5d7643b2df63 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:53:37 +0000 Subject: [PATCH 014/275] Create cloud-messaging.md --- docs/api/cloud-messaging.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/cloud-messaging.md diff --git a/docs/api/cloud-messaging.md b/docs/api/cloud-messaging.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/cloud-messaging.md @@ -0,0 +1 @@ + From 3c2ab82d58292216df66e3da94ed6786480f234b Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:53:47 +0000 Subject: [PATCH 015/275] Create events.md --- docs/api/events.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/events.md diff --git a/docs/api/events.md b/docs/api/events.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/api/events.md @@ -0,0 +1 @@ + From 3d93f856ffdb66031f80c572189091a9b2334378 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sun, 6 Nov 2016 19:53:58 +0000 Subject: [PATCH 016/275] Create redux.md --- docs/redux.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/redux.md diff --git a/docs/redux.md b/docs/redux.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/redux.md @@ -0,0 +1 @@ + From 4617ac5428ec99d0da512921a8cc2f9633a14a1f Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Mon, 7 Nov 2016 08:41:58 +0000 Subject: [PATCH 017/275] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 346fc48..df93ce1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Firestack makes using the latest [Firebase](http://firebase.com) with React Native straight-forward. +``` +npm i react-native-firestack --save +``` + [![Gitter](https://badges.gitter.im/fullstackreact/react-native-firestack.svg)](https://gitter.im/fullstackreact/react-native-firestack?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![npm version](https://img.shields.io/npm/v/react-native-firestack.svg)](https://www.npmjs.com/package/react-native-firestack) [![License](https://img.shields.io/npm/l/react-native-firestack.svg)](/LICENSE) From eb3cd18edd49e3ab4c4882702c74c9040a5dcc76 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Tue, 8 Nov 2016 08:48:35 +0000 Subject: [PATCH 018/275] Update installation.ios.md --- docs/installation.ios.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.ios.md b/docs/installation.ios.md index 7eb4b51..688379a 100644 --- a/docs/installation.ios.md +++ b/docs/installation.ios.md @@ -28,15 +28,15 @@ cd ios && pod update --verbose If you prefer not to use `react-native link`, we can manually link the package together with the following steps, after `npm install`: -A. In XCode, right click on `Libraries` and find the `Add Files to [project name]`. +**A.** In XCode, right click on `Libraries` and find the `Add Files to [project name]`. ![Add library to project](http://d.pr/i/2gEH.png) -B. Add the `node_modules/react-native-firestack/ios/Firestack.xcodeproj` +**B.** Add the `node_modules/react-native-firestack/ios/Firestack.xcodeproj` ![Firebase.xcodeproj in Libraries listing](http://d.pr/i/19ktP.png) -C. Ensure that the `Build Settings` of the `Firestack.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_: +**C.** Ensure that the `Build Settings` of the `Firestack.xcodeproj` project is ticked to _All_ and it's `Header Search Paths` include both of the following paths _and_ are set to _recursive_: 1. `$(SRCROOT)/../../react-native/React` 2. `$(SRCROOT)/../node_modules/react-native/React` @@ -44,7 +44,7 @@ C. Ensure that the `Build Settings` of the `Firestack.xcodeproj` project is tick ![Recursive paths](http://d.pr/i/1hAr1.png) -D. Setting up cocoapods +**D.** Setting up cocoapods Since we're dependent upon cocoapods (or at least the Firebase libraries being available at the root project -- i.e. your application), we have to make them available for Firestack to find them. From 90b72613b7a88ea05312821ecc491a0ed27b55ee Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Tue, 8 Nov 2016 09:21:20 +0000 Subject: [PATCH 019/275] Update auth docs --- docs/api/authentication.md | 198 +++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 8b13789..443545d 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -1 +1,199 @@ +# Authentication +Firestack handles authentication for us out of the box, both with email/password-based authentication and through oauth providers (with a separate library to handle oauth providers). + +> Android requires the Google Play services to installed for authentication to function. + +## Local Auth + +#### [listenForAuth()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) + +Listen for changes in the users auth state (logging in and out). + +```javascript +firestack.auth.listenForAuth((evt) => { + // evt is the authentication event, it contains an `error` key for carrying the + // error message in case of an error and a `user` key upon successful authentication + if (!evt.authenticated) { + // There was an error or there is no user + console.error(evt.error) + } else { + // evt.user contains the user details + console.log('User details', evt.user); + } +}) +.then(() => console.log('Listening for authentication changes')) +``` + +#### unlistenForAuth() + +Remove the `listenForAuth` listener. +This is important to release resources from our app when we don't need to hold on to the listener any longer. + +```javascript +firestack.auth.unlistenForAuth() +``` + +#### [createUserWithEmail()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) + +We can create a user by calling the `createUserWithEmail()` function. +The method accepts two parameters, an email and a password. + +```javascript +firestack.auth.createUserWithEmail('ari@fullstack.io', '123456') + .then((user) => { + console.log('user created', user) + }) + .catch((err) => { + console.error('An error occurred', err); + }) +``` + +#### [signInWithEmail()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) + +To sign a user in with their email and password, use the `signInWithEmail()` function. +It accepts two parameters, the user's email and password: + +```javascript +firestack.auth.signInWithEmail('ari@fullstack.io', '123456') + .then((user) => { + console.log('User successfully logged in', user) + }) + .catch((err) => { + console.error('User signin error', err); + }) +``` + +#### [signInAnonymously()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInAnonymously) + +Sign an anonymous user. If the user has already signed in, that user will be returned. + +```javascript +firestack.auth.signInAnonymously() + .then((user) => { + console.log('Anonymous user successfully logged in', user) + }) + .catch((err) => { + console.error('Anonymous user signin error', err); + }) + +#### signInWithProvider() + +We can use an external authentication provider, such as twitter/facebook for authentication. In order to use an external provider, we need to include another library to handle authentication. + +> By using a separate library, we can keep our dependencies a little lower and the size of the application down. + +#### signInWithCustomToken() + +To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: + +```javascript +firestack.auth.signInWithCustomToken(TOKEN) + .then((user) => { + console.log('User successfully logged in', user) + }) + .catch((err) => { + console.error('User signin error', err); + }) +``` + +#### [updateUserEmail()](https://firebase.google.com/docs/reference/js/firebase.User#updateEmail) + +We can update the current user's email by using the command: `updateUserEmail()`. +It accepts a single argument: the user's new email: + +```javascript +firestack.auth.updateUserEmail('ari+rocks@fullstack.io') + .then((res) => console.log('Updated user email')) + .catch(err => console.error('There was an error updating user email')) +``` + +#### [updateUserPassword()](https://firebase.google.com/docs/reference/js/firebase.User#updatePassword) + +We can update the current user's password using the `updateUserPassword()` method. +It accepts a single parameter: the new password for the current user + +```javascript +firestack.auth.updateUserPassword('somethingReallyS3cr3t733t') + .then(res => console.log('Updated user password')) + .catch(err => console.error('There was an error updating your password')) +``` + +#### [updateUserProfile()](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile) + +To update the current user's profile, we can call the `updateUserProfile()` method. +It accepts a single parameter: + +* object which contains updated key/values for the user's profile. +Possible keys are listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile). + +```javascript +firestack.auth + .updateUserProfile({ + displayName: 'Ari Lerner' + }) + .then(res => console.log('Your profile has been updated')) + .catch(err => console.error('There was an error :(')) +``` + +#### [sendPasswordResetWithEmail()](https://firebase.google.com/docs/auth/web/manage-users#send_a_password_reset_email) + +To send a password reset for a user based upon their email, we can call the `sendPasswordResetWithEmail()` method. +It accepts a single parameter: the email of the user to send a reset email. + +```javascript +firestack.auth.sendPasswordResetWithEmail('ari+rocks@fullstack.io') + .then(res => console.log('Check your inbox for further instructions')) + .catch(err => console.error('There was an error :(')) +``` +#### [deleteUser()](https://firebase.google.com/docs/auth/web/manage-users#delete_a_user) + +It's possible to delete a user completely from your account on Firebase. +Calling the `deleteUser()` method will take care of this for you. + +```javascript +firestack.auth + .deleteUser() + .then(res => console.log('Sad to see you go')) + .catch(err => console.error('There was an error - Now you are trapped!')) +``` + +#### getToken() + +If you want user's token, use `getToken()` method. + +```javascript +firestack.auth + .getToken() + .then(res => console.log(res.token)) + .catch(err => console.error('error')) +``` + +#### [signOut()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signOut) + +To sign the current user out, use the `signOut()` method. +It accepts no parameters + +```javascript +firestack.auth + .signOut() + .then(res => console.log('You have been signed out')) + .catch(err => console.error('Uh oh... something weird happened')) +``` + + +#### getCurrentUser() + +Although you _can_ get the current user using the `getCurrentUser()` method, it's better to use this from within the callback function provided by `listenForAuth()`. +However, if you need to get the current user, call the `getCurrentUser()` method: + +```javascript +firestack.auth + .getCurrentUser() + .then(user => console.log('The currently logged in user', user)) + .catch(err => console.error('An error occurred')) +``` + +## Social Auth + +TODO From 5c3e108f7070dee749792044b77c2b4f683d188d Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 9 Nov 2016 19:07:31 +0000 Subject: [PATCH 020/275] added getInstance() .apps, intializeApp() , .database(), .auth(). .messaging() (formally cloudMessaging) .remoteConfig() - added support for multi instances via locally scoped 'instances' object. --- .editorconfig | 10 +++++ lib/firestack.js | 101 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f09989 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/lib/firestack.js b/lib/firestack.js index 8bcbbfa..1ebcf4d 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -4,13 +4,17 @@ */ import Log from './utils/log' +const instances = { + default: null, +}; + // const firebase = require('firebase'); // const app = require('firebase/app'); // const storage = require('firebase/storage'); // const db = require('firebase/database'); -import {NativeModules, NativeEventEmitter, AsyncStorage} from 'react-native'; +import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; // TODO: Break out modules into component pieces // i.e. auth component, storage component, etc. const FirestackModule = NativeModules.Firestack; @@ -20,17 +24,17 @@ import promisify from './utils/promisify' import Singleton from './utils/singleton' import RemoteConfig from './modules/remoteConfig' -import {Authentication} from './modules/authentication' -import {Database} from './modules/database' -import {Analytics} from './modules/analytics' -import {Storage} from './modules/storage' -import {Presence} from './modules/presence' -import {CloudMessaging} from './modules/cloudmessaging' +import { Authentication } from './modules/authentication' +import { Database } from './modules/database' +import { Analytics } from './modules/analytics' +import { Storage } from './modules/storage' +import { Presence } from './modules/presence' +import { CloudMessaging } from './modules/cloudmessaging' let log; export class Firestack extends Singleton { - constructor(options) { + constructor(options, name) { var instance = super(options); instance.options = options || {}; @@ -55,10 +59,27 @@ export class Firestack extends Singleton { instance._auth = new Authentication(instance, instance.options); } + /** + * Support web version of initApp. + * @param options + * @param name + * @returns {*} + */ + static initializeApp(options, name = 'default') { + if (!instances[name]) instances[name] = new Firestack(options); + return instances[name]; + } + + + /** + * + * @param opts + * @returns {Promise.|*|Promise.} + */ configure(opts = {}) { if (!this.configurePromise) { const firestackOptions = Object.assign({}, this.options, opts); - + this.configurePromise = promisify('configureWithOptions', FirestackModule)(firestackOptions) .then((configuredProperties) => { log.info('Native configureWithOptions success', configuredProperties); @@ -82,56 +103,71 @@ export class Firestack extends Singleton { * when they are needed. Not sure if this is a good * idea or not (imperative vs. direct manipulation/proxy) */ - get auth() { - if (!this._auth) { this._auth = new Authentication(this); } + auth() { + if (!this._auth) { + this._auth = new Authentication(this); + } return this._auth; } + // database - get database() { - if (!this._db) { this._db = new Database(this); } + database() { + if (!this._db) { + this._db = new Database(this); + } return this._db; // db.enableLogging(this._debug); // return this.appInstance.database(); } // analytics - get analytics() { - if (!this._analytics) { this._analytics = new Analytics(this); } + analytics() { + if (!this._analytics) { + this._analytics = new Analytics(this); + } return this._analytics; } // storage - get storage() { - if (!this._storage) { this._storage = new Storage(this); } + storage() { + if (!this._storage) { + this._storage = new Storage(this); + } return this._storage; } // presence - get presence() { - if (!this._presence) { this._presence = new Presence(this); } + presence() { + if (!this._presence) { + this._presence = new Presence(this); + } return this._presence; } + // CloudMessaging - get cloudMessaging() { - if (!this._cloudMessaging) { this._cloudMessaging = new CloudMessaging(this); } + messaging() { + if (!this._cloudMessaging) { + this._cloudMessaging = new CloudMessaging(this); + } return this._cloudMessaging; } - // other - get ServerValue() { - return promisify('serverValue', FirestackModule)(); - } - /** * remote config */ - get remoteConfig() { + remoteConfig() { if (!this.remoteConfig) { this.remoteConfig = new RemoteConfig(this._remoteConfig); } return this.remoteConfig; } + // other + get ServerValue() { + return promisify('serverValue', FirestackModule)(); + } + + /** * app instance **/ @@ -139,6 +175,17 @@ export class Firestack extends Singleton { return this.appInstance; } + /** + * app instance + **/ + getInstance() { + return this.appInstance; + } + + get apps() { + return Object.keys(instances); + } + /** * Logger */ From 95ffce82be7f791fccf998bb02add336fa192340 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 12 Nov 2016 11:58:06 +0000 Subject: [PATCH 021/275] Update Documentation links --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index df93ce1..93bd20e 100644 --- a/README.md +++ b/README.md @@ -34,19 +34,19 @@ We have a working application example available in at [fullstackreact/FirestackA ## Documentation * Installation - * iOS - * Android -* Firebase Setup + * [iOS](docs/installation.ios.md) + * [Android](docs/installation.android.md) +* [Firebase Setup](docs/firebase-setup.md) * API - * Authentication - * Analytics - * Storage - * Realtime Database - * Presence - * ServerValue - * Cloud Messaging - * Events -* Redux + * [Authentication](docs/api/authentication.md) + * [Analytics](docs/api/analytics.md) + * [Storage](docs/api/storage.md) + * [Realtime Database](docs/api/database.md) + * [Presence](docs/api/presence.md) + * [ServerValue](docs/api/server-value.md) + * [Cloud Messaging](docs/api/cloud-messaging.md) + * [Events](docs/api/events.md) +* [Redux](docs/redux.md) ## Contributing From 8047c4712831811a71f27cb9cb7d78717e2d6a05 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 12 Nov 2016 12:03:46 +0000 Subject: [PATCH 022/275] Inline list of features --- README.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/README.md b/README.md index 93bd20e..ca064bd 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,7 @@ npm i react-native-firestack --save Firestack is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the React Native JS api as closely as possible. It features: -* Authentication -* Storage - * upload/download files - * download urls -* Real-time database -* Presence -* Analytics -* Cloud Messaging (currently Android only) -* Remote configuration -* Redux support (not required) +Featuring; authentication, storage, real-time database, presence, analytics, cloud messaging, remote configuration, redux support and more! ## Firestack vs Firebase JS lib From 65091f55e3ab1385bdfbcb8df94ecf7bc26afcbc Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 14 Nov 2016 19:03:35 +0000 Subject: [PATCH 023/275] updated .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1347dc0..7e7abfa 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ android/.gradle/ android/.signing/ # User-specific configurations +android/.idea/gradle.xml android/.idea/libraries/ android/.idea/workspace.xml android/.idea/tasks.xml @@ -56,3 +57,4 @@ android/gradle/ .idea .idea coverage +yarn.lock From 287c1f2cd68f6453c5aaebfe55546b5196ba0213 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 14 Nov 2016 19:12:38 +0000 Subject: [PATCH 024/275] Created watch copy script - can now work on the module on your own projects \o/ --- bin/watchCopy.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 bin/watchCopy.js diff --git a/bin/watchCopy.js b/bin/watchCopy.js new file mode 100644 index 0000000..3ccbd51 --- /dev/null +++ b/bin/watchCopy.js @@ -0,0 +1,41 @@ +const { watch } = require('cpx'); +const { resolve } = require('path'); +const packageJson = require('./../package.json'); + +const readline = require('readline'); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +const PROJECT_DIR = resolve(__dirname, './../'); +let TARGET_DIR = process.env.TARGET_DIR; + +if (!TARGET_DIR) { + console.error('Missing TARGET_DIR process env, aborting!'); + console.error('EXAMPLE USAGE: TARGET_DIR=/Users/YOU/Documents/someproject npm run watchcpx'); + process.exit(1); +} + +if (!TARGET_DIR.includes('node_modules')) { + TARGET_DIR = `${TARGET_DIR}/node_modules/${packageJson.name}`; +} + + +rl.question(`Watch for changes in '${PROJECT_DIR}' and copy to '${TARGET_DIR}'? (y/n): `, (answer) => { + if (answer.toLowerCase() === 'y') { + console.log('For the watch! (watching has begun)'); + const watcher = watch(PROJECT_DIR + '/**/*.*', TARGET_DIR, { verbose: true}); + watcher.on('copy', (e) => { + if (!e.srcPath.startsWith('node_modules')) { + console.log(`Copied ${e.srcPath} to ${e.dstPath}`); + } + }); + } else { + console.log('Aborting watch.'); + process.exit(); + } + rl.close(); +}); + From e30e0d40958815f7d29ba4685de2e0fe0b023437 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 14 Nov 2016 19:13:47 +0000 Subject: [PATCH 025/275] start of refactor - will add a detailed commit log on PR. --- android/.idea/gradle.xml | 11 - android/build.gradle | 2 +- .../io/fullstack/firestack/FirestackAuth.java | 732 +++++++++++------- lib/firestack.js | 3 +- lib/modules/authentication.js | 189 ++++- lib/modules/base.js | 19 +- lib/modules/cloudmessaging.js | 203 +++-- lib/modules/database.js | 28 +- lib/modules/user.js | 118 +++ lib/utils/eventEmitter.js | 313 ++++++++ lib/utils/promisify.js | 6 +- package.json | 4 +- 12 files changed, 1194 insertions(+), 434 deletions(-) delete mode 100644 android/.idea/gradle.xml create mode 100644 lib/modules/user.js create mode 100644 lib/utils/eventEmitter.js diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml deleted file mode 100644 index f4f39e8..0000000 --- a/android/.idea/gradle.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index a06d876..34eefc5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -20,7 +20,7 @@ android { dependencies { compile 'com.facebook.react:react-native:0.20.+' - compile 'com.google.android.gms:play-services-base:9.8.0' + compile 'com.google.android.gms:play-services-base:9.8.1' compile 'com.google.firebase:firebase-core:9.8.0' compile 'com.google.firebase:firebase-auth:9.8.0' diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 7c0c2bb..562097b 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -1,8 +1,11 @@ + package io.fullstack.firestack; import android.content.Context; import android.util.Log; + import java.util.Map; + import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -34,86 +37,96 @@ import com.google.firebase.auth.GoogleAuthProvider; class FirestackAuthModule extends ReactContextBaseJavaModule { - private final int NO_CURRENT_USER = 100; - private final int ERROR_FETCHING_TOKEN = 101; + private final int NO_CURRENT_USER = 100; + private final int ERROR_FETCHING_TOKEN = 101; + private final int ERROR_SENDING_VERIFICATION_EMAIL = 102; - private static final String TAG = "FirestackAuth"; + private static final String TAG = "FirestackAuth"; - private Context context; - private ReactContext mReactContext; - private FirebaseAuth mAuth; - private FirebaseApp app; - private FirebaseUser user; - private FirebaseAuth.AuthStateListener mAuthListener; + private Context context; + private ReactContext mReactContext; + private FirebaseAuth mAuth; + private FirebaseApp app; + private FirebaseUser user; + private FirebaseAuth.AuthStateListener mAuthListener; - public FirestackAuthModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - mReactContext = reactContext; + public FirestackAuthModule(ReactApplicationContext reactContext) { + super(reactContext); + this.context = reactContext; + mReactContext = reactContext; - Log.d(TAG, "New FirestackAuth instance"); - } + Log.d(TAG, "New FirestackAuth instance"); + } - @Override - public String getName() { - return TAG; - } + @Override + public String getName() { + return TAG; + } - @ReactMethod - public void listenForAuth() { - mAuthListener = new FirebaseAuth.AuthStateListener() { + @ReactMethod + public void listenForAuth() { + mAuthListener = new FirebaseAuth.AuthStateListener() { - @Override - public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { - WritableMap msgMap = Arguments.createMap(); - msgMap.putString("eventName", "listenForAuth"); + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + WritableMap msgMap = Arguments.createMap(); + msgMap.putString("eventName", "listenForAuth"); - if (FirestackAuthModule.this.user != null) { - WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + WritableMap userMap = getUserMap(); - msgMap.putBoolean("authenticated", true); - msgMap.putMap("user", userMap); + msgMap.putBoolean("authenticated", true); + msgMap.putMap("user", userMap); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } else { - msgMap.putBoolean("authenticated", false); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } - } - }; + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } else { + msgMap.putBoolean("authenticated", false); + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } + } + }; - mAuth = FirebaseAuth.getInstance(); - mAuth.addAuthStateListener(mAuthListener); + mAuth = FirebaseAuth.getInstance(); + mAuth.addAuthStateListener(mAuthListener); } @ReactMethod public void unlistenForAuth(final Callback callback) { - if (mAuthListener != null) { - mAuth.removeAuthStateListener(mAuthListener); + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); - callback.invoke(null, resp); - } + callback.invoke(null, resp); + } } @ReactMethod - public void createUserWithEmail(final String email, final String password, final Callback onComplete) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.createUserWithEmailAndPassword(email, password) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, onComplete); - }else{ - userErrorCallback(task, onComplete); - } - } - }); + public void createUserWithEmail(final String email, final String password, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + mAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -121,28 +134,38 @@ public void signInWithEmail(final String email, final String password, final Cal mAuth = FirebaseAuth.getInstance(); mAuth.signInWithEmailAndPassword(email, password) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } - }); + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { - if (provider.equals("facebook")) { - this.facebookLogin(authToken,callback); - } else if (provider.equals("google")) { - this.googleLogin(authToken,callback); - } else - // TODO - FirestackUtils.todoNote(TAG, "signInWithProvider", callback); + if (provider.equals("facebook")) { + this.facebookLogin(authToken, callback); + } else if (provider.equals("google")) { + this.googleLogin(authToken, callback); + } else + // TODO + FirestackUtils.todoNote(TAG, "signInWithProvider", callback); } @ReactMethod @@ -155,95 +178,130 @@ public void signInAnonymously(final Callback callback) { public void onComplete(@NonNull Task task) { Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - anonymousUserCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + anonymousUserCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } } - }); - + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod public void signInWithCustomToken(final String customToken, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); + mAuth = FirebaseAuth.getInstance(); - mAuth.signInWithCustomToken(customToken) - .addOnCompleteListener(new OnCompleteListener() { + mAuth.signInWithCustomToken(customToken) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { @Override - public void onComplete(@NonNull Task task) { - Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } }); } @ReactMethod public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { - // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); - // AuthCredential credential; - // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); + // TODO: + FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); + // AuthCredential credential; + // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); } @ReactMethod public void updateUserEmail(final String email, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { - user.updateEmail(email) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "User email address updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); - } - } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); - } + if (user != null) { + user.updateEmail(email) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User email address updated"); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); + } } @ReactMethod public void updateUserPassword(final String newPassword, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { - user.updatePassword(newPassword) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "User password updated"); - - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); - } - } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); - } + if (user != null) { + user.updatePassword(newPassword) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User password updated"); + + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); + } } @ReactMethod @@ -254,114 +312,188 @@ public void sendPasswordResetWithEmail(final String email, final Callback callba .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if(task.isSuccessful()){ - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - callback.invoke(null, resp); - }else{ - callback.invoke(task.getException().toString()); + try { + if (task.isSuccessful()) { + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + callback.invoke(null, resp); + } else { + callback.invoke(task.getException().toString()); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod public void deleteUser(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { - user.delete() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "User account deleted"); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User account deleted"); - callback.invoke(null, resp); - } else { - userErrorCallback(task, callback); - } - } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); - } + if (user != null) { + user.delete() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User account deleted"); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User account deleted"); + callback.invoke(null, resp); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); + } } + + @ReactMethod + public void sendEmailVerification(final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + // TODO check user exists + user.sendEmailVerification() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User verification email sent"); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + //userExceptionCallback(ex, callback); + } + }); + } + + @ReactMethod public void getToken(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - user.getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - String token = task.getResult().getToken(); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("token", token); - callback.invoke(null, resp); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", ERROR_FETCHING_TOKEN); - err.putString("errorMessage", task.getException().getMessage()); - callback.invoke(err); + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + // TODO check user exists + user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + String token = task.getResult().getToken(); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("token", token); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_FETCHING_TOKEN); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } - } }); } @ReactMethod public void updateUserProfile(ReadableMap props, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); - - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); - - if (m.containsKey("displayName")) { - String displayName = (String) m.get("displayName"); - profileBuilder.setDisplayName(displayName); - } - - if (m.containsKey("photoUri")) { - String photoUriStr = (String) m.get("photoUri"); - Uri uri = Uri.parse(photoUriStr); - profileBuilder.setPhotoUri(uri); - } - - UserProfileChangeRequest profileUpdates = profileBuilder.build(); - - user.updateProfile(profileUpdates) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "User profile updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); + + Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + + if (m.containsKey("displayName")) { + String displayName = (String) m.get("displayName"); + profileBuilder.setDisplayName(displayName); + } + + if (m.containsKey("photoUri")) { + String photoUriStr = (String) m.get("photoUri"); + Uri uri = Uri.parse(photoUriStr); + profileBuilder.setPhotoUri(uri); + } + + UserProfileChangeRequest profileUpdates = profileBuilder.build(); + + // TODO check user exists + user.updateProfile(profileUpdates) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User profile updated"); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } - } }); } @ReactMethod public void signOut(final Callback callback) { - FirebaseAuth.getInstance().signOut(); - this.user = null; + FirebaseAuth.getInstance().signOut(); + this.user = null; - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User signed out"); - callback.invoke(null, resp); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User signed out"); + callback.invoke(null, resp); } @ReactMethod @@ -369,9 +501,10 @@ public void getCurrentUser(final Callback callback) { mAuth = FirebaseAuth.getInstance(); this.user = mAuth.getCurrentUser(); - if(this.user == null){ + if (this.user == null) { noUserCallback(callback); - }else{ + } else { + Log.d("USRC", this.user.getDisplayName()); userCallback(this.user, callback); } } @@ -386,14 +519,23 @@ public void googleLogin(String IdToken, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -405,26 +547,36 @@ public void facebookLogin(String Token, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - }else{ - userErrorCallback(task, callback); + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } // Internal helpers - public void userCallback(FirebaseUser passedUser, final Callback onComplete) { + public void userCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); + mAuth = FirebaseAuth.getInstance(); + this.user = mAuth.getCurrentUser(); } else { - this.user = passedUser; + this.user = passedUser; } + // TODO check user exists this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { @@ -439,37 +591,48 @@ public void onComplete(@NonNull Task task) { msgMap.putMap("user", userMap); - onComplete.invoke(null, msgMap); + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } }); } // TODO: Reduce to one method - public void anonymousUserCallback(FirebaseUser passedUser, final Callback onComplete) { + public void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); + mAuth = FirebaseAuth.getInstance(); + this.user = mAuth.getCurrentUser(); } else { - this.user = passedUser; + this.user = passedUser; } - this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); + this.user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - } + userMap.putString("token", token); + userMap.putBoolean("anonymous", true); + } - msgMap.putMap("user", userMap); + msgMap.putMap("user", userMap); - onComplete.invoke(null, msgMap); + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } }); } @@ -494,31 +657,40 @@ public void userErrorCallback(Task task, final Callback onFail) { onFail.invoke(error); } + public void userExceptionCallback(Exception ex, final Callback onFail) { + WritableMap error = Arguments.createMap(); + error.putInt("errorCode", ex.hashCode()); + error.putString("errorMessage", ex.getMessage()); + error.putString("allErrorMessage", ex.toString()); + + onFail.invoke(error); + } + private WritableMap getUserMap() { WritableMap userMap = Arguments.createMap(); FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); if (user != null) { - final String email = user.getEmail(); - final String uid = user.getUid(); - final String provider = user.getProviderId(); - final String name = user.getDisplayName(); - final Uri photoUrl = user.getPhotoUrl(); - - userMap.putString("email", email); - userMap.putString("uid", uid); - userMap.putString("providerId", provider); - - if (name != null) { - userMap.putString("name", name); - } - - if (photoUrl != null) { - userMap.putString("photoUrl", photoUrl.toString()); - } + final String email = user.getEmail(); + final String uid = user.getUid(); + final String provider = user.getProviderId(); + final String name = user.getDisplayName(); + final Uri photoUrl = user.getPhotoUrl(); + + userMap.putString("email", email); + userMap.putString("uid", uid); + userMap.putString("providerId", provider); + + if (name != null) { + userMap.putString("name", name); + } + + if (photoUrl != null) { + userMap.putString("photoURL", photoUrl.toString()); + } } else { - userMap.putString("msg", "no user"); + userMap.putString("msg", "no user"); } return userMap; diff --git a/lib/firestack.js b/lib/firestack.js index 1ebcf4d..c865792 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -49,7 +49,7 @@ export class Firestack extends Singleton { delete instance.options.remoteConfig; instance.configured = instance.options.configure || false; - instance.auth = null; + instance._auth = null; instance.eventHandlers = {}; @@ -104,6 +104,7 @@ export class Firestack extends Singleton { * idea or not (imperative vs. direct manipulation/proxy) */ auth() { + console.log('auth GET'); if (!this._auth) { this._auth = new Authentication(this); } diff --git a/lib/modules/authentication.js b/lib/modules/authentication.js index c06edfc..c00856b 100644 --- a/lib/modules/authentication.js +++ b/lib/modules/authentication.js @@ -1,27 +1,59 @@ - -import {NativeModules, NativeEventEmitter} from 'react-native'; +import { NativeModules, NativeEventEmitter } from 'react-native'; const FirestackAuth = NativeModules.FirestackAuth const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); + import promisify from '../utils/promisify' import { Base } from './base' +import { default as User} from './user'; export class Authentication extends Base { - constructor(firestack, options={}) { + constructor(firestack, options = {}) { super(firestack, options); + this._authResult = null; + this.authenticated = false; + this._user = null; + + // always track auth changes internall so we can access them synchronously + FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); + FirestackAuth.listenForAuth(); } - // Auth - listenForAuth(callback) { - this.log.info('Setting up listenForAuth callback'); - const sub = this._on('listenForAuth', callback, FirestackAuthEvt); + /** + * Internal auth changed listener + * @param auth + * @private + */ + _onAuthStateChanged(auth) { + this._authResult = auth; + this.authenticated = auth ? auth.authenticated || false : false + if (auth && !this._user) this._user = new User(this, auth); + else if (!auth && this._user) this._user = null; + else this._user._updateValues(auth); + } + + /* + * WEB API + */ + + /** + * Listen for auth changes. + * @param callback + */ + onAuthStateChanged(listener) { + this.log.info('Creating onAuthStateChanged listener'); + const sub = this._on('listenForAuth', listener, FirestackAuthEvt); FirestackAuth.listenForAuth(); - this.log.info('Listening for auth...'); + this.log.info('Listening for onAuthStateChanged events...'); return promisify(() => sub, FirestackAuth)(sub); } - unlistenForAuth() { - this.log.info('Unlistening for auth'); + /** + * Remove auth change listener + * @param listener + */ + offAuthStateChanged(listener) { + this.log.info('Removing onAuthStateChanged listener'); this._off('listenForAuth'); return promisify('unlistenForAuth', FirestackAuth)(); } @@ -32,8 +64,8 @@ export class Authentication extends Base { * @param {string} password The user's password * @return {Promise} A promise indicating the completion */ - createUserWithEmail(email, password) { - this.log.info('Creating user with email', email); + createUserWithEmailAndPassword(email, password) { + this.log.info('Creating user with email and password', email); return promisify('createUserWithEmail', FirestackAuth)(email, password); } @@ -43,19 +75,44 @@ export class Authentication extends Base { * @param {string} password The user's password * @return {Promise} A promise that is resolved upon completion */ - signInWithEmail(email, password) { + signInWithEmailAndPassword(email, password) { + this.log.info('Signing in user with email and password', email); return promisify('signInWithEmail', FirestackAuth)(email, password) } + /** - * Sign the user in with a third-party authentication provider - * @param {string} provider The name of the provider to use for login - * @param {string} authToken The authToken granted by the provider - * @param {string} authSecret The authToken secret granted by the provider - * @return {Promise} A promise resolved upon completion + * Update the current user's email + * @param {string} email The user's _new_ email + * @return {Promise} A promise resolved upon completion */ - signInWithProvider(provider, authToken, authSecret) { - return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret) + updateEmail(email) { + return promisify('updateUserEmail', FirestackAuth)(email); + } + + /** + * Send verification email to current user. + */ + sendEmailVerification() { + return promisify('sendEmailVerification', FirestackAuth)(); + } + + /** + * Update the current user's password + * @param {string} email the new password + * @return {Promise} + */ + updatePassword(password) { + return promisify('updateUserPassword', FirestackAuth)(password); + } + + /** + * Update the current user's profile + * @param {Object} obj An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) + * @return {Promise} + */ + updateProfile(updates) { + return promisify('updateUserProfile', FirestackAuth)(updates); } /** @@ -68,11 +125,14 @@ export class Authentication extends Base { } /** - * Sign a user in anonymously - * @return {Promise} A promise resolved upon completion + * Sign the user in with a third-party authentication provider + * @param {string} provider The name of the provider to use for login + * @param {string} authToken The authToken granted by the provider + * @param {string} authSecret The authToken secret granted by the provider + * @return {Promise} A promise resolved upon completion */ - signInAnonymously() { - return promisify('signInAnonymously', FirestackAuth)(); + signInWithProvider(provider, authToken, authSecret) { + return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret) } /** @@ -86,22 +146,72 @@ export class Authentication extends Base { return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(provider, token, secret) } + /** - * Update the current user's email - * @param {string} email The user's _new_ email - * @return {Promise} A promise resolved upon completion + * Sign a user in anonymously + * @return {Promise} A promise resolved upon completion */ - updateUserEmail(email) { - return promisify('updateUserEmail', FirestackAuth)(email); + signInAnonymously() { + return promisify('signInAnonymously', FirestackAuth)(); } + + /* + * Old deprecated api stubs + */ + + /** - * Update the current user's password - * @param {string} email the new password - * @return {Promise} + * @deprecated + * @param args */ - updatePassword(password) { - return promisify('updateUserPassword', FirestackAuth)(password); + listenForAuth(...args) { + console.warn('Firestack: listenForAuth is now deprecated, please use onAuthStateChanged'); + this.onAuthStateChanged(...args); + } + + /** + * @deprecated + * @param args + */ + unlistenForAuth(...args) { + console.warn('Firestack: unlistenForAuth is now deprecated, please use offAuthStateChanged'); + this.offAuthStateChanged(...args); + } + + /** + * Create a user with the email/password functionality + * @deprecated + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise indicating the completion + */ + createUserWithEmail(...args) { + console.warn('Firestack: createUserWithEmail is now deprecated, please use createUserWithEmailAndPassword'); + this.createUserWithEmailAndPassword(...args); + } + + /** + * Sign a user in with email/password + * @deprecated + * @param {string} email The user's email + * @param {string} password The user's password + * @return {Promise} A promise that is resolved upon completion + */ + signInWithEmail(...args) { + console.warn('Firestack: signInWithEmail is now deprecated, please use signInWithEmailAndPassword'); + this.signInWithEmailAndPassword(...args); + } + + /** + * Update the current user's email + * @deprecated + * @param {string} email The user's _new_ email + * @return {Promise} A promise resolved upon completion + */ + updateUserEmail(...args) { + console.warn('Firestack: updateUserEmail is now deprecated, please use updateEmail'); + this.updateEmail(...args); } /** @@ -119,6 +229,7 @@ export class Authentication extends Base { deleteUser() { return promisify('deleteUser', FirestackAuth)() } + /** * get the token of current user * @return {Promise} @@ -129,11 +240,13 @@ export class Authentication extends Base { /** * Update the current user's profile + * @deprecated * @param {Object} obj An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) * @return {Promise} */ - updateUserProfile(obj) { - return promisify('updateUserProfile', FirestackAuth)(obj); + updateUserProfile(...args) { + console.warn('Firestack: updateUserProfile is now deprecated, please use updateProfile'); + this.updateProfile(...args); } /** @@ -148,8 +261,8 @@ export class Authentication extends Base { * Get the currently signed in user * @return {Promise} */ - getCurrentUser() { - return promisify('getCurrentUser', FirestackAuth)(); + get currentUser() { + return this._user; } get namespace() { diff --git a/lib/modules/base.js b/lib/modules/base.js index 56bf538..ae6c893 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -3,15 +3,18 @@ */ import Log from '../utils/log' -import {NativeModules, NativeEventEmitter, AsyncStorage} from 'react-native'; +import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; +import { default as EventEmitter } from './../utils/eventEmitter'; + const FirestackModule = NativeModules.Firestack; const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); import promisify from '../utils/promisify' let logs = {}; -export class Base { - constructor(firestack, options={}) { +export class Base extends EventEmitter { + constructor(firestack, options = {}) { + super(); this.firestack = firestack; this.eventHandlers = {}; @@ -88,13 +91,13 @@ export class ReferenceBase extends Base { super(firestack); this.path = Array.isArray(path) ? - path : - (typeof path == 'string' ? - [path] : []); + path : + (typeof path == 'string' ? + [path] : []); // sanitize path, just in case this.path = this.path - .filter(str => str !== "" ); + .filter(str => str !== ""); } get key() { @@ -110,4 +113,4 @@ export class ReferenceBase extends Base { } return pathStr; } -} \ No newline at end of file +} diff --git a/lib/modules/cloudmessaging.js b/lib/modules/cloudmessaging.js index a03a2ed..983e44a 100644 --- a/lib/modules/cloudmessaging.js +++ b/lib/modules/cloudmessaging.js @@ -1,86 +1,133 @@ -import {NativeModules, NativeEventEmitter} from 'react-native'; +import { NativeModules, NativeEventEmitter } from 'react-native'; const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); import promisify from '../utils/promisify' import { Base, ReferenceBase } from './base' export class CloudMessaging extends Base { - constructor(firestack, options = {}) { - super(firestack, options); - } - get namespace() { - return 'firestack:cloudMessaging' - } - getToken() { - this.log.info('getToken for cloudMessaging'); - return promisify('getToken', FirestackCloudMessaging)(); - } - - sendMessage(details:Object = {}, type:string='local') { - const methodName = `send${type == 'local' ? 'Local' : 'Remote'}` - this.log.info('sendMessage', methodName, details); - return promisify(methodName, FirestackCloudMessaging)(details); - } - scheduleMessage(details:Object = {}, type:string='local') { - const methodName = `schedule${type == 'local' ? 'Local' : 'Remote'}` - return promisify(methodName, FirestackCloudMessaging)(details); - } - // OLD - send(senderId, messageId, messageType, msg){ - return promisify('send', FirestackCloudMessaging)(senderId, messageId, messageType, msg); - } - // - listenForTokenRefresh(callback) { - this.log.info('Setting up listenForTokenRefresh callback'); - const sub = this._on('FirestackRefreshToken', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); - } - unlistenForTokenRefresh() { - this.log.info('Unlistening for TokenRefresh'); - this._off('FirestackRefreshToken'); - } - subscribeToTopic(topic) { - this.log.info('subscribeToTopic ' + topic); - const finalTopic = `/topics/${topic}` - return promisify('subscribeToTopic', FirestackCloudMessaging)(finalTopic); - } - unsubscribeFromTopic(topic) { - this.log.info('unsubscribeFromTopic ' + topic); - const finalTopic = `/topics/${topic}` - return promisify('unsubscribeFromTopic', FirestackCloudMessaging)(finalTopic); - } - // New api - onRemoteMessage(callback) { - this.log.info('On remote message callback'); - const sub = this._on('messaging_remote_event_received', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); - } - - onLocalMessage(callback) { - this.log.info('on local callback'); - const sub = this._on('messaging_local_event_received', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); - } - - // Original API - listenForReceiveNotification(callback) { - this.log.info('Setting up listenForReceiveNotification callback'); - const sub = this._on('FirestackReceiveNotification', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); - } - unlistenForReceiveNotification() { - this.log.info('Unlistening for ReceiveNotification'); - this._off('FirestackRefreshToken'); - } - listenForReceiveUpstreamSend(callback) { - this.log.info('Setting up send callback'); - const sub = this._on('FirestackUpstreamSend', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); - } - unlistenForReceiveUpstreamSend() { - this.log.info('Unlistening for send'); - this._off('FirestackUpstreamSend'); - } + constructor(firestack, options = {}) { + super(firestack, options); + } + + /* + * WEB API + */ + onMessage(callback) { + this.log.info('Setting up onMessage callback'); + const sub = this._on('FirestackReceiveNotification', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + + // android & ios api + onMessageReceived(...args) { + return this.onMessage(...args); + } + + // there is no 'off' for this on api's but it's needed here for react + // so followed the general 'off' / 'on' naming convention + offMessage() { + this.log.info('Unlistening from onMessage (offMessage)'); + this._off('FirestackRefreshToken'); + } + + offMessageReceived(...args) { + return this.offMessage(...args); + } + + + get namespace() { + return 'firestack:cloudMessaging' + } + + getToken() { + this.log.info('getToken for cloudMessaging'); + return promisify('getToken', FirestackCloudMessaging)(); + } + + sendMessage(details: Object = {}, type: string = 'local') { + const methodName = `send${type == 'local' ? 'Local' : 'Remote'}`; + this.log.info('sendMessage', methodName, details); + return promisify(methodName, FirestackCloudMessaging)(details); + } + + scheduleMessage(details: Object = {}, type: string = 'local') { + const methodName = `schedule${type == 'local' ? 'Local' : 'Remote'}`; + return promisify(methodName, FirestackCloudMessaging)(details); + } + + // OLD + send(senderId, messageId, messageType, msg) { + return promisify('send', FirestackCloudMessaging)(senderId, messageId, messageType, msg); + } + + // + listenForTokenRefresh(callback) { + this.log.info('Setting up listenForTokenRefresh callback'); + const sub = this._on('FirestackRefreshToken', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + + unlistenForTokenRefresh() { + this.log.info('Unlistening for TokenRefresh'); + this._off('FirestackRefreshToken'); + } + + subscribeToTopic(topic) { + this.log.info('subscribeToTopic ' + topic); + const finalTopic = `/topics/${topic}`; + return promisify('subscribeToTopic', FirestackCloudMessaging)(finalTopic); + } + + unsubscribeFromTopic(topic) { + this.log.info('unsubscribeFromTopic ' + topic); + const finalTopic = `/topics/${topic}`; + return promisify('unsubscribeFromTopic', FirestackCloudMessaging)(finalTopic); + } + + // New api + onRemoteMessage(callback) { + this.log.info('On remote message callback'); + const sub = this._on('messaging_remote_event_received', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + + onLocalMessage(callback) { + this.log.info('on local callback'); + const sub = this._on('messaging_local_event_received', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + + // old API + /** + * @deprecated + * @param args + * @returns {*} + */ + listenForReceiveNotification(...args) { + console.warn('Firestack: listenForReceiveNotification is now deprecated, please use onMessage'); + return this.onMessage(...args); + } + + /** + * @deprecated + * @param args + * @returns {*} + */ + unlistenForReceiveNotification(...args) { + console.warn('Firestack: unlistenForReceiveNotification is now deprecated, please use offMessage'); + return this.offMessage(...args); + } + + listenForReceiveUpstreamSend(callback) { + this.log.info('Setting up send callback'); + const sub = this._on('FirestackUpstreamSend', callback, FirestackCloudMessagingEvt); + return promisify(() => sub, FirestackCloudMessaging)(sub); + } + + unlistenForReceiveUpstreamSend() { + this.log.info('Unlistening for send'); + this._off('FirestackUpstreamSend'); + } } -export default CloudMessaging \ No newline at end of file +export default CloudMessaging diff --git a/lib/modules/database.js b/lib/modules/database.js index f7f91fe..06f022f 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -207,12 +207,12 @@ class DatabaseRef extends ReferenceBase { return this.db.on(path, evt, cb) .then(({callback, subscriptions}) => { return promisify('on', FirestackDatabase)(path, modifiers, evt) - .then(() => { - this.listeners[evt] = subscriptions; - callback(this); - return subscriptions; - }) - }); + .then(() => { + this.listeners[evt] = subscriptions; + callback(this); + return subscriptions; + }) + }); } once(evt='once', cb) { @@ -251,12 +251,12 @@ class DatabaseRef extends ReferenceBase { cleanup() { let promises = Object.keys(this.listeners) - .map(key => this.off(key)) + .map(key => this.off(key)) return Promise.all(promises); } // Sanitize value - // As Firebase cannot store date objects. + // As Firebase cannot store date objects. _serializeValue(obj={}) { return Object.keys(obj).reduce((sum, key) => { let val = obj[key]; @@ -434,14 +434,14 @@ export class Database extends Base { if (!this.successListener) { this.successListener = FirestackDatabaseEvt .addListener( - 'database_event', + 'database_event', this.handleDatabaseEvent.bind(this)); } if (!this.errorListener) { this.errorListener = FirestackDatabaseEvt .addListener( - 'database_error', + 'database_error', this.handleDatabaseError.bind(this)); } @@ -479,7 +479,7 @@ export class Database extends Base { } if (this.errorListener) { this.errorListener.remove(); - this.errorListener = null; + this.errorListener = null; } } } @@ -493,8 +493,8 @@ export class Database extends Base { cleanup() { let promises = Object.keys(this.refs) - .map(key => this.refs[key]) - .map(ref => ref.cleanup()) + .map(key => this.refs[key]) + .map(ref => ref.cleanup()) return Promise.all(promises); } @@ -514,4 +514,4 @@ export class Database extends Base { } } -export default Database \ No newline at end of file +export default Database diff --git a/lib/modules/user.js b/lib/modules/user.js new file mode 100644 index 0000000..605795f --- /dev/null +++ b/lib/modules/user.js @@ -0,0 +1,118 @@ +import promisify from '../utils/promisify'; +import { Base } from './base'; + + +export default class User { + constructor(authClass, authObj) { + this._auth = authClass; + this._user = null; + this._updateValues(authObj); + } + + /** + * INTERNALS + */ + + /** + * + * @param authObj + * @private + */ + _updateValues(authObj) { + this._authObj = authObj; + if (authObj.user) { + this._user = authObj.user; + } else { + this._user = null; + } + } + + /** + * Returns a user property or null if does not exist + * @param prop + * @returns {*} + * @private + */ + _getOrNull(prop) { + if (!this._user) return null; + if (!Object.hasOwnProperty.call(this._user, prop)) return null; + return this._user[prop]; + } + + /** + * PROPERTIES + */ + + get displayName() { + return this._getOrNull('displayName'); + } + + get email() { + return this._getOrNull('email'); + } + + get emailVerified() { + return this._getOrNull('emailVerified'); + } + + get isAnonymous() { + return !this._getOrNull('email') && this._getOrNull('providerId') === 'firebase'; + } + + get photoURL() { + return this._getOrNull('photoURL'); + } + + get photoUrl() { + return this._getOrNull('photoURL'); + } + + // TODO no android method yet, the SDK does have .getProviderData but returns as a List. + // get providerData() { + // return this._getOrNull('providerData'); + // } + + get providerId() { + return this._getOrNull('providerId'); + } + + // TODO no + // get refreshToken() { + // return this._getOrNull('refreshToken'); + // } + + get uid() { + return this._getOrNull('uid'); + } + + /** + * METHODS + */ + + delete(...args) { + return this._auth.deleteUser(...args); + } + + getToken(...args) { + return this._auth.getToken(...args); + } + + get updateEmail() { + if (this.isAnonymous) return () => Promise.reject(new Error('Can not update email on an annonymous user.')); + return this._auth.updateEmail; + } + + get updateProfile() { + return this._auth.updateProfile; + } + + get updatePassword() { + if (this.isAnonymous) return () => Promise.reject(new Error('Can not update password on an annonymous user.')); + return this._auth.updatePassword; + } + + get sendEmailVerification() { + if (this.isAnonymous) return () => Promise.reject(new Error('Can not verify email on an annonymous user.')); + return this._auth.sendEmailVerification; + } +} diff --git a/lib/utils/eventEmitter.js b/lib/utils/eventEmitter.js new file mode 100644 index 0000000..67c15ba --- /dev/null +++ b/lib/utils/eventEmitter.js @@ -0,0 +1,313 @@ +// TODO - this is just a raw copy of eventEmitter3 - until i can implement a lightweight version + +'use strict'; + +var has = Object.prototype.hasOwnProperty + , prefix = '~'; + +/** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @api private + */ +function Events() {} + +// +// We try to not inherit from `Object.prototype`. In some engines creating an +// instance in this way is faster than calling `Object.create(null)` directly. +// If `Object.create(null)` is not supported we prefix the event names with a +// character to make sure that the built-in object properties are not +// overridden or used as an attack vector. +// +if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; +} + +/** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {Mixed} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @api private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @api public + */ +function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; +} + +/** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @api public + */ +EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; +}; + +/** + * Return the listeners registered for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Boolean} exists Only check if there are listeners. + * @returns {Array|Boolean} + * @api public + */ +EventEmitter.prototype.listeners = function listeners(event, exists) { + var evt = prefix ? prefix + event : event + , available = this._events[evt]; + + if (exists) return !!available; + if (!available) return []; + if (available.fn) return [available.fn]; + + for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { + ee[i] = available[i].fn; + } + + return ee; +}; + +/** + * Calls each of the listeners registered for a given event. + * + * @param {String|Symbol} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @api public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Add a listener for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn The listener function. + * @param {Mixed} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @api public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + var listener = new EE(fn, context || this) + , evt = prefix ? prefix + event : event; + + if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++; + else if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [this._events[evt], listener]; + + return this; +}; + +/** + * Add a one-time listener for a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn The listener function. + * @param {Mixed} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @api public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + var listener = new EE(fn, context || this, true) + , evt = prefix ? prefix + event : event; + + if (!this._events[evt]) this._events[evt] = listener, this._eventsCount++; + else if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [this._events[evt], listener]; + + return this; +}; + +/** + * Remove the listeners of a given event. + * + * @param {String|Symbol} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {Mixed} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @api public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn + && (!once || listeners.once) + && (!context || listeners.context === context) + ) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn + || (once && !listeners[i].once) + || (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + + return this; +}; + +/** + * Remove all listeners, or those of the specified event. + * + * @param {String|Symbol} [event] The event name. + * @returns {EventEmitter} `this`. + * @api public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) { + if (--this._eventsCount === 0) this._events = new Events(); + else delete this._events[evt]; + } + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// This function doesn't apply anymore. +// +EventEmitter.prototype.setMaxListeners = function setMaxListeners() { + return this; +}; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Allow `EventEmitter` to be imported as module namespace. +// +EventEmitter.EventEmitter = EventEmitter; + +// +// Expose the module. +// +if ('undefined' !== typeof module) { + module.exports = EventEmitter; +} diff --git a/lib/utils/promisify.js b/lib/utils/promisify.js index b8fbd4b..a967760 100644 --- a/lib/utils/promisify.js +++ b/lib/utils/promisify.js @@ -1,7 +1,9 @@ export const promisify = (fn, NativeModule) => (...args) => { return new Promise((resolve, reject) => { const handler = (err, resp) => { - err ? reject(err) : resolve(resp); + setTimeout(() => { + err ? reject(err) : resolve(resp); + }, 0); } args.push(handler); (typeof fn === 'function' ? fn : NativeModule[fn]) @@ -9,4 +11,4 @@ export const promisify = (fn, NativeModule) => (...args) => { }); }; -export default promisify \ No newline at end of file +export default promisify diff --git a/package.json b/package.json index e611094..ae8a168 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "dev": "npm run compile -- --watch", "lint": "eslint ./src", "publish_pages": "gh-pages -d public/", - "test": "./node_modules/.bin/mocha" + "test": "./node_modules/.bin/mocha", + "watchcpx": "node ./bin/watchCopy" }, "repository": { "type": "git", @@ -67,6 +68,7 @@ }, "dependencies": { "bows": "^1.6.0", + "cpx": "^1.5.0", "es6-symbol": "^3.1.0" } } From 6f9b46cfecf39d0e07fc46dac851c9c18aef001a Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 14 Nov 2016 20:12:03 +0000 Subject: [PATCH 026/275] watchcpx no longer watches node_modules --- bin/watchCopy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/watchCopy.js b/bin/watchCopy.js index 3ccbd51..d1c665d 100644 --- a/bin/watchCopy.js +++ b/bin/watchCopy.js @@ -26,11 +26,11 @@ if (!TARGET_DIR.includes('node_modules')) { rl.question(`Watch for changes in '${PROJECT_DIR}' and copy to '${TARGET_DIR}'? (y/n): `, (answer) => { if (answer.toLowerCase() === 'y') { console.log('For the watch! (watching has begun)'); - const watcher = watch(PROJECT_DIR + '/**/*.*', TARGET_DIR, { verbose: true}); + const watcher = watch(PROJECT_DIR + '/{ios,android,lib}/**/*.*', TARGET_DIR, { verbose: true}); watcher.on('copy', (e) => { - if (!e.srcPath.startsWith('node_modules')) { + // if (!e.srcPath.startsWith('node_modules')) { console.log(`Copied ${e.srcPath} to ${e.dstPath}`); - } + // } }); } else { console.log('Aborting watch.'); From 51a323f96c8527b85a65ec893fc0edc71008c431 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 14 Nov 2016 21:58:02 +0000 Subject: [PATCH 027/275] copy node modules once in watchcpx --- bin/watchCopy.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/bin/watchCopy.js b/bin/watchCopy.js index d1c665d..7e025f4 100644 --- a/bin/watchCopy.js +++ b/bin/watchCopy.js @@ -1,4 +1,4 @@ -const { watch } = require('cpx'); +const { watch, copy } = require('cpx'); const { resolve } = require('path'); const packageJson = require('./../package.json'); @@ -22,15 +22,19 @@ if (!TARGET_DIR.includes('node_modules')) { TARGET_DIR = `${TARGET_DIR}/node_modules/${packageJson.name}`; } - rl.question(`Watch for changes in '${PROJECT_DIR}' and copy to '${TARGET_DIR}'? (y/n): `, (answer) => { if (answer.toLowerCase() === 'y') { - console.log('For the watch! (watching has begun)'); - const watcher = watch(PROJECT_DIR + '/{ios,android,lib}/**/*.*', TARGET_DIR, { verbose: true}); - watcher.on('copy', (e) => { - // if (!e.srcPath.startsWith('node_modules')) { + // flat copy node_modules as we're not watching it + console.log('Copying node_modules directory...'); + copy(PROJECT_DIR + '/node_modules/**/*.*', TARGET_DIR.replace(`/${packageJson.name}`), { clean: true }, () => { + console.log('Copy complete.'); + console.log('Watching for changes in project directory... (excludes node_modules)'); + const watcher = watch(PROJECT_DIR + '/{ios,android,lib}/**/*.*', TARGET_DIR, { verbose: true }); + watcher.on('copy', (e) => { + // if (!e.srcPath.startsWith('node_modules')) { console.log(`Copied ${e.srcPath} to ${e.dstPath}`); - // } + // } + }); }); } else { console.log('Aborting watch.'); From cbf6304d852d56b9c6b9bd53b68012539b67fb4b Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:09:22 +0000 Subject: [PATCH 028/275] manual merge of @BlooJeans pr #132 - needed a major cleanup though, unused variables & functions, duplications, unused imports, unnecessary variable creations etc - added TODO notes for everything else that needs cleaning up here --- .../firestack/FirestackDatabase.java | 218 +++----- lib/modules/database.js | 517 ------------------ lib/modules/database/disconnect.js | 33 ++ lib/modules/database/index.js | 225 ++++++++ lib/modules/database/query.js | 101 ++++ lib/modules/database/reference.js | 284 ++++++++++ lib/modules/database/snapshot.js | 52 ++ 7 files changed, 783 insertions(+), 647 deletions(-) delete mode 100644 lib/modules/database.js create mode 100644 lib/modules/database/disconnect.js create mode 100644 lib/modules/database/index.js create mode 100644 lib/modules/database/query.js create mode 100644 lib/modules/database/reference.js create mode 100644 lib/modules/database/snapshot.js diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 61cda5d..45505c7 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -1,6 +1,7 @@ package io.fullstack.firestack; import android.content.Context; +import android.text.TextUtils; import android.util.Log; import java.util.HashMap; import java.util.List; @@ -13,6 +14,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -49,29 +51,29 @@ public void setModifiers(final ReadableArray modifiers) { mModifiers = modifiers; } - public void addChildEventListener(final String name, final ReadableArray modifiers) { + public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { final FirestackDBReference self = this; if (mEventListener == null) { mEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_added", mPath, dataSnapshot); + self.handleDatabaseEvent("child_added", mPath, modifiersString, dataSnapshot); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_changed", mPath, dataSnapshot); + self.handleDatabaseEvent("child_changed", mPath, modifiersString, dataSnapshot); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("child_removed", mPath, dataSnapshot); + self.handleDatabaseEvent("child_removed", mPath, modifiersString, dataSnapshot); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_moved", mPath, dataSnapshot); + self.handleDatabaseEvent("child_moved", mPath, modifiersString, dataSnapshot); } @Override @@ -81,18 +83,18 @@ public void onCancelled(DatabaseError error) { }; } - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addChildEventListener(mEventListener); - this.setListeningTo(mPath, name); + //this.setListeningTo(mPath, modifiersString, name); } - public void addValueEventListener(final String name, final ReadableArray modifiers) { + public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { final FirestackDBReference self = this; mValueListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("value", mPath, dataSnapshot); + self.handleDatabaseEvent("value", mPath, modifiersString, dataSnapshot); } @Override @@ -101,19 +103,20 @@ public void onCancelled(DatabaseError error) { } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addValueEventListener(mValueListener); - this.setListeningTo(mPath, "value"); + //this.setListeningTo(mPath, modifiersString, "value"); } - public void addOnceValueEventListener(final ReadableArray modifiers, + public void addOnceValueEventListener(final ReadableArray modifiersArray, + final String modifiersString, final Callback callback) { final FirestackDBReference self = this; mOnceValueListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, dataSnapshot); + WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); callback.invoke(null, data); } @@ -127,31 +130,31 @@ public void onCancelled(DatabaseError error) { } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); ref.addListenerForSingleValueEvent(mOnceValueListener); } - public Boolean isListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - return mListeners.containsKey(key); - } + //public Boolean isListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // return mListeners.containsKey(key); + //} /** * Note: these path/eventType listeners only get removed when javascript calls .off() and cleanup is run on the entire path */ - public void setListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - mListeners.put(key, true); - } + //public void setListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.put(key, true); + //} - public void notListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - mListeners.remove(key); - } + //public void notListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.remove(key); + //} - private String pathListeningKey(final String path, final String eventName) { - return "listener/" + path + "/" + eventName; - } + //private String pathListeningKey(final String path, String modifiersString, final String eventName) { + //return "listener/" + path + "/" + modifiersString + "/" + eventName; + //} public void cleanup() { Log.d(TAG, "cleaning up database reference " + this); @@ -163,10 +166,10 @@ public void removeChildEventListener() { if (mEventListener != null) { DatabaseReference ref = this.getDatabaseRef(); ref.removeEventListener(mEventListener); - this.notListeningTo(mPath, "child_added"); - this.notListeningTo(mPath, "child_changed"); - this.notListeningTo(mPath, "child_removed"); - this.notListeningTo(mPath, "child_moved"); + //this.notListeningTo(mPath, "child_added"); + //this.notListeningTo(mPath, "child_changed"); + //this.notListeningTo(mPath, "child_removed"); + //this.notListeningTo(mPath, "child_moved"); mEventListener = null; } } @@ -175,7 +178,7 @@ public void removeValueEventListener() { DatabaseReference ref = this.getDatabaseRef(); if (mValueListener != null) { ref.removeEventListener(mValueListener); - this.notListeningTo(mPath, "value"); + //this.notListeningTo(mPath, "value"); mValueListener = null; } if (mOnceValueListener != null) { @@ -184,16 +187,17 @@ public void removeValueEventListener() { } } - private void handleDatabaseEvent(final String name, final String path, final DataSnapshot dataSnapshot) { - if (!FirestackDBReference.this.isListeningTo(path, name)) { - return; - } - WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); + private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { + //if (!FirestackDBReference.this.isListeningTo(path, modifiersString, name)) { + //return; + //} + WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); evt.putString("path", path); + evt.putString("modifiersString", modifiersString); evt.putMap("body", data); - + FirestackUtils.sendEvent(mReactContext, "database_event", evt); } @@ -304,8 +308,12 @@ public String getName() { public void enablePersistence( final Boolean enable, final Callback callback) { - FirebaseDatabase.getInstance() + try { + FirebaseDatabase.getInstance() .setPersistenceEnabled(enable); + } catch (Throwable t) { + Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); + } WritableMap res = Arguments.createMap(); res.putString("status", "success"); @@ -429,35 +437,37 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { @ReactMethod public void on(final String path, - final ReadableArray modifiers, + final String modifiersString, + final ReadableArray modifiersArray, final String name, final Callback callback) { - FirestackDBReference ref = this.getDBHandle(path); + FirestackDBReference ref = this.getDBHandle(path, modifiersString); WritableMap resp = Arguments.createMap(); if (name.equals("value")) { - ref.addValueEventListener(name, modifiers); + ref.addValueEventListener(name, modifiersArray, modifiersString); } else { - ref.addChildEventListener(name, modifiers); + ref.addChildEventListener(name, modifiersArray, modifiersString); } - this.saveDBHandle(path, ref); + this.saveDBHandle(path, modifiersString, ref); resp.putString("result", "success"); - Log.d(TAG, "Added listener " + name + " for " + ref); - + Log.d(TAG, "Added listener " + name + " for " + ref + "with modifiers: "+ modifiersString); + resp.putString("handle", path); callback.invoke(null, resp); } @ReactMethod public void onOnce(final String path, - final ReadableArray modifiers, + final String modifiersString, + final ReadableArray modifiersArray, final String name, final Callback callback) { Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - FirestackDBReference ref = this.getDBHandle(path); - ref.addOnceValueEventListener(modifiers, callback); + FirestackDBReference ref = this.getDBHandle(path, modifiersString); + ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); } /** @@ -467,9 +477,13 @@ public void onOnce(final String path, * off() should therefore clean *everything* up */ @ReactMethod - public void off(final String path, @Deprecated final String name, final Callback callback) { - this.removeDBHandle(path); - Log.d(TAG, "Removed listener " + path); + public void off( + final String path, + final String modifiersString, + @Deprecated final String name, + final Callback callback) { + this.removeDBHandle(path, modifiersString); + Log.d(TAG, "Removed listener " + path + "/" + modifiersString); WritableMap resp = Arguments.createMap(); resp.putString("handle", path); resp.putString("result", "success"); @@ -569,24 +583,31 @@ private void handleCallback( } } - private FirestackDBReference getDBHandle(final String path) { - if (!mDBListeners.containsKey(path)) { + private FirestackDBReference getDBHandle(final String path, final String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (!mDBListeners.containsKey(key)) { ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(path, new FirestackDBReference(ctx, path)); + mDBListeners.put(key, new FirestackDBReference(ctx, path)); } - return mDBListeners.get(path); + return mDBListeners.get(key); } - private void saveDBHandle(final String path, final FirestackDBReference dbRef) { - mDBListeners.put(path, dbRef); + private void saveDBHandle(final String path, String modifiersString, final FirestackDBReference dbRef) { + String key = this.getDBListenerKey(path, modifiersString); + mDBListeners.put(key, dbRef); } - private void removeDBHandle(final String path) { - if (mDBListeners.containsKey(path)) { - FirestackDBReference r = mDBListeners.get(path); + private String getDBListenerKey(String path, String modifiersString) { + return path + " | " + modifiersString; + } + + private void removeDBHandle(final String path, String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (mDBListeners.containsKey(key)) { + FirestackDBReference r = mDBListeners.get(key); r.cleanup(); - mDBListeners.remove(path); + mDBListeners.remove(key); } } @@ -600,73 +621,10 @@ private DatabaseReference getDatabaseReferenceAtPath(final String path) { return mDatabase; } - private Query getDatabaseQueryAtPathAndModifiers( - final String path, - final ReadableArray modifiers) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - List strModifiers = FirestackUtils.recursivelyDeconstructReadableArray(modifiers); - ListIterator it = strModifiers.listIterator(); - Query query = ref.orderByKey(); - - while(it.hasNext()) { - String str = (String) it.next(); - String[] strArr = str.split(":"); - String methStr = strArr[0]; - - if (methStr.equalsIgnoreCase("orderByKey")) { - query = ref.orderByKey(); - } else if (methStr.equalsIgnoreCase("orderByValue")) { - query = ref.orderByValue(); - } else if (methStr.equalsIgnoreCase("orderByPriority")) { - query = ref.orderByPriority(); - } else if (methStr.contains("orderByChild")) { - String key = strArr[1]; - Log.d(TAG, "orderByChild: " + key); - query = ref.orderByChild(key); - } else if (methStr.contains("limitToLast")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToLast: " + limit); - query = query.limitToLast(limit); - } else if (methStr.contains("limitToFirst")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToFirst: " + limit); - query = query.limitToFirst(limit); - } else if (methStr.contains("equalTo")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.equalTo(value); - } else { - query = query.equalTo(value, key); - } - } else if (methStr.contains("endAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.endAt(value); - } else { - query = query.endAt(value, key); - } - } else if (methStr.contains("startAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.startAt(value); - } else { - query = query.startAt(value, key); - } - } - } - - return query; - } - private WritableMap dataSnapshotToMap(String name, String path, DataSnapshot dataSnapshot) { - return FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); - } + //private WritableMap dataSnapshotToMap(String name, String path, DataSnapshot dataSnapshot) { + // return FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); + //} private Any castSnapshotValue(DataSnapshot snapshot) { if (snapshot.hasChildren()) { diff --git a/lib/modules/database.js b/lib/modules/database.js deleted file mode 100644 index 06f022f..0000000 --- a/lib/modules/database.js +++ /dev/null @@ -1,517 +0,0 @@ -/** - * Database representation wrapper - */ -import {NativeModules, NativeEventEmitter} from 'react-native'; -const FirestackDatabase = NativeModules.FirestackDatabase; -const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); - -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' - -let dbSubscriptions = {}; - -class DataSnapshot { - static key:String; - static value:Object; - static exists:boolean; - static hasChildren:boolean; - static childrenCount:Number; - static childKeys:String[]; - - constructor(ref, snapshot) { - this.ref = ref; - this.key = snapshot.key; - this.value = snapshot.value; - this.exists = snapshot.exists || true; - this.priority = snapshot.priority; - this.hasChildren = snapshot.hasChildren || false; - this.childrenCount = snapshot.childrenCount || 0; - this.childKeys = snapshot.childKeys || []; - } - - val() { - return this.value; - } - - forEach(fn) { - (this.childKeys || []) - .forEach(key => fn(this.value[key])) - } - - map(fn) { - let arr = []; - this.forEach(item => arr.push(fn(item))) - return arr; - } - - reverseMap(fn) { - return this.map(fn).reverse(); - } -} - -class DatabaseOnDisconnect { - constructor(ref) { - this.ref = ref; - } - - setValue(val) { - const path = this.ref.dbPath(); - if (typeof val == 'string') { - return promisify('onDisconnectSetString', FirestackDatabase)(path, val); - } else if (typeof val == 'object') { - return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); - } - } - - remove() { - const path = this.ref.dbPath(); - return promisify('onDisconnectRemove', FirestackDatabase)(path); - } - - cancel() { - const path = this.ref.dbPath(); - return promisify('onDisconnectCancel', FirestackDatabase)(path); - } -} - -class DatabaseQuery { - static ref: DatabaseRef; - static orderBy: String[]; - static limit: String[]; - static filters: Object; - - constructor(ref) { - this.ref = ref; - this.reset(); - } - - setOrderBy(name, ...args) { - this.orderBy = [name].concat(args); - return this.ref; - } - - setLimit(name, ...args) { - this.limit = [name].concat(args); - return this.ref; - } - - setFilter(name, ...args) { - this.filters[name] = args; - return this.ref; - } - - build() { - const argsSeparator = ':' - let modifiers = []; - if (this.orderBy) { - modifiers.push(this.orderBy.join(argsSeparator)); - } - if (this.limit) { - modifiers.push(this.limit.join(argsSeparator)); - } - Object.keys(this.filters) - .forEach(key => { - const filter = this.filters[key]; - if (filter) { - const filterArgs = [key, filter].join(argsSeparator) - modifiers.push(filterArgs); - } - }) - return modifiers; - } - - reset() { - this.orderBy = null; - this.limit = null; - this.filters = {}; - ['startAt', 'endAt', 'equalTo'] - .forEach(key => this.filters[key] = null); - return this.ref; - } -} - -// https://firebase.google.com/docs/reference/js/firebase.database.Reference -const separator = '/'; -class DatabaseRef extends ReferenceBase { - constructor(db, path) { - super(db.firestack, path); - - this.db = db; - this.query = new DatabaseQuery(this); - this.listeners = {}; - - // Aliases - this.get = this.getAt; - this.set = this.setAt; - this.update = this.updateAt; - this.remove = this.removeAt; - - this.log.debug('Created new DatabaseRef', this.dbPath()); - } - - // Parent roots - parent() { - const parentPaths = this.path.slice(0, -1); - return new DatabaseRef(this.db, parentPaths); - } - - root() { - return new DatabaseRef(this.db, []); - } - - child(...paths) { - return new DatabaseRef(this.db, this.path.concat(paths)); - } - - keepSynced(bool) { - const path = this.dbPath(); - return promisify('keepSynced', FirestackDatabase)(path, bool); - } - - // Get the value of a ref either with a key - getAt() { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); - } - - setAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value) - } - - updateAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('update', FirestackDatabase)(path, value) - } - - removeAt(key) { - const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path) - } - - push(val={}) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('push', FirestackDatabase)(path, value) - .then(({ref}) => { - return new DatabaseRef(this.db, ref.split(separator)) - }) - } - - on(evt, cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return this.db.on(path, evt, cb) - .then(({callback, subscriptions}) => { - return promisify('on', FirestackDatabase)(path, modifiers, evt) - .then(() => { - this.listeners[evt] = subscriptions; - callback(this); - return subscriptions; - }) - }); - } - - once(evt='once', cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) - .then(({snapshot}) => new DataSnapshot(this, snapshot)) - .then(snapshot => { - if (cb && typeof cb === 'function') { - cb(snapshot); - } - return snapshot; - }) - } - - off(evt='', origCB) { - const path = this.dbPath(); - return this.db.off(path, evt, origCB) - .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { - return subscriptions; - } - - return promisify('off', FirestackDatabase)(path, evt) - .then(() => { - // subscriptions.forEach(sub => sub.remove()); - delete this.listeners[evt]; - callback(this); - return subscriptions; - }) - }) - .catch(err => { - console.error('Never get here', err); - }) - } - - cleanup() { - let promises = Object.keys(this.listeners) - .map(key => this.off(key)) - return Promise.all(promises); - } - - // Sanitize value - // As Firebase cannot store date objects. - _serializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.toISOString(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - _deserializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.getTime(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - // Modifiers - orderByKey() { - return this.query.setOrderBy('orderByKey'); - } - - orderByPriority() { - return this.query.setOrderBy('orderByPriority'); - } - - orderByValue() { - return this.query.setOrderBy('orderByValue'); - } - - orderByChild(key) { - return this.query.setOrderBy('orderByChild', key); - } - - // Limits - limitToLast(limit) { - return this.query.setLimit('limitToLast', limit); - } - - limitToFirst(limit) { - return this.query.setLimit('limitToFirst', limit); - } - - // Filters - equalTo(value, key) { - return this.query.setFilter('equalTo', value, key); - } - - endAt(value, key) { - return this.query.setFilter('endAt', value, key); - } - - startAt(value, key) { - return this.query.setFilter('startAt', value, key); - } - - presence(path) { - const presence = this.firestack.presence; - const ref = path ? this.child(path) : this; - return presence.ref(ref, this.dbPath()); - } - - // onDisconnect - onDisconnect() { - return new DatabaseOnDisconnect(this); - } - - // attributes - get fullPath() { - return this.dbPath(); - } - - get name() { - return this.path.splice(-1); - } - - dbPath() { - let path = this.path; - let pathStr = (path.length > 0 ? path.join('/') : '/'); - if (pathStr[0] != '/') { - pathStr = `/${pathStr}` - } - return pathStr; - } - - dbModifiers() { - const modifiers = this.query.build(); - this.query.reset(); // should we reset this - return modifiers; - } - - get namespace() { - return `firestack:dbRef` - } -} - -export class Database extends Base { - - constructor(firestack, options={}) { - super(firestack, options); - this.log.debug('Created new Database instance', this.options); - - this.persistenceEnabled = false; - this.successListener = null; - this.errorListener = null; - this.refs = {}; - } - - ref(...path) { - const key = this._pathKey(path); - if (!this.refs[key]) { - const ref = new DatabaseRef(this, path); - this.refs[key] = ref; - } - return this.refs[key]; - } - - setPersistence(enable=true) { - let promise; - if (this.persistenceEnabled !== enable) { - this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); - promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); - this.persistenceEnabled = enable; - } else { - promise = this.whenReady(Promise.resolve({status: "Already enabled"})) - } - - return promise; - } - - handleDatabaseEvent(evt) { - const body = evt.body; - const path = body.path; - const evtName = body.eventName; - - const subscriptions = dbSubscriptions[path]; - - if (subscriptions) { - const cbs = subscriptions[evtName]; - cbs.forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new DataSnapshot(this, body.snapshot); - this.log.debug('database_event received', path, evtName); - cb(snap, body); - } - }); - } - } - - handleDatabaseError(evt) { - this.log.debug('handleDatabaseError ->', evt); - } - - on(path, evt, cb) { - const key = this._pathKey(path); - - if (!dbSubscriptions[key]) { - dbSubscriptions[key] = {}; - } - - if (!dbSubscriptions[key][evt]) { - dbSubscriptions[key][evt] = []; - } - dbSubscriptions[key][evt].push(cb); - - if (!this.successListener) { - this.successListener = FirestackDatabaseEvt - .addListener( - 'database_event', - this.handleDatabaseEvent.bind(this)); - } - - if (!this.errorListener) { - this.errorListener = FirestackDatabaseEvt - .addListener( - 'database_error', - this.handleDatabaseError.bind(this)); - } - - const callback = (ref) => { - const key = this._pathKey(ref.path); - this.refs[key] = ref; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - off(path, evt, origCB) { - const key = this._pathKey(path); - // Remove subscription - if (dbSubscriptions[key]) { - if (!evt || evt === "") { - dbSubscriptions[key] = {}; - } else if (dbSubscriptions[key][evt]) { - if (origCB) { - dbSubscriptions[key][evt].splice(dbSubscriptions[key][evt].indexOf(origCB), 1); - } else { - delete dbSubscriptions[key][evt]; - } - } - - if (Object.keys(dbSubscriptions[key]).length <= 0) { - // there are no more subscriptions - // so we can unwatch - delete dbSubscriptions[key] - } - if (Object.keys(dbSubscriptions).length == 0) { - if (this.successListener) { - this.successListener.remove(); - this.successListener = null; - } - if (this.errorListener) { - this.errorListener.remove(); - this.errorListener = null; - } - } - } - const callback = (ref) => { - const key = this._pathKey(ref.path); - delete this.refs[key]; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - cleanup() { - let promises = Object.keys(this.refs) - .map(key => this.refs[key]) - .map(ref => ref.cleanup()) - return Promise.all(promises); - } - - release(...path) { - const key = this._pathKey(path); - if (this.refs[key]) { - delete this.refs[key]; - } - } - - _pathKey(...path) { - return path.join('-'); - } - - get namespace() { - return 'firestack:database' - } -} - -export default Database diff --git a/lib/modules/database/disconnect.js b/lib/modules/database/disconnect.js new file mode 100644 index 0000000..4b1733e --- /dev/null +++ b/lib/modules/database/disconnect.js @@ -0,0 +1,33 @@ + +import { NativeModules } from 'react-native'; +import Reference from './reference'; + +const FirestackDatabase = NativeModules.FirestackDatabase; + +/** + * @class Disconnect + */ +export default class Disconnect { + ref: Reference; + + constructor(ref: Reference) { + this.ref = ref; + } + + setValue(val: string | Object) { + const path = this.ref.dbPath(); + if (typeof val === 'string') { + return promisify('onDisconnectSetString', FirestackDatabase)(path, val); + } else if (typeof val === 'object') { + return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); + } + } + + remove() { + return promisify('onDisconnectRemove', FirestackDatabase)(this.ref.dbPath()); + } + + cancel() { + return promisify('onDisconnectCancel', FirestackDatabase)(this.ref.dbPath()); + } +} diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js new file mode 100644 index 0000000..0ab99c6 --- /dev/null +++ b/lib/modules/database/index.js @@ -0,0 +1,225 @@ +/** + * @flow + * Database representation wrapper + */ +'use strict'; +import { NativeModules, NativeEventEmitter } from 'react-native'; +const FirestackDatabase = NativeModules.FirestackDatabase; +const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); + +import promisify from './../../utils/promisify'; + +import { Base } from './../base'; +import Reference from './reference.js'; +import Snapshot from './snapshot.js'; + +/** + * TODO ? why is it here and in reference? make it a util + * @param modifiers + * @returns {*} + */ +function getModifiersString(modifiers) { + if (!modifiers || !Array.isArray(modifiers)) { + return ''; + } + return modifiers.join('|'); +} + +/** + * @class Database + */ +export default class Database extends Base { + constructor(firestack: Object, options: Object = {}) { + super(firestack, options); + this.log.debug('Created new Database instance', this.options); + + this.persistenceEnabled = false; + this.successListener = null; + this.errorListener = null; + this.refs = {}; + this.dbSubscriptions = {}; // { path: { modifier: { eventType: [Subscriptions] } } } + } + + ref(...path: Array) { + return new Reference(this, path); + } + + storeRef(key: string, instance: Reference): Promise { + if (!this.refs[key]) { + this.refs[key] = instance; + } + return Promise.resolve(this.refs[key]); + } + + unstoreRef(key: string): Promise { + if (this.refs[key]) { + delete this.refs[key]; + } + return Promise.resolve(); + } + + setPersistence(enable: boolean = true) { + let promise; + if (this.persistenceEnabled !== enable) { + this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); + promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); + this.persistenceEnabled = enable; + } else { + promise = this.whenReady(Promise.resolve({ status: 'Already enabled' })) + } + + return promise; + } + + handleDatabaseEvent(evt: Object) { + const body = evt.body || {}; + const path = body.path; + const modifiersString = body.modifiersString || ''; + const modifier = modifiersString; + const eventName = body.eventName; + this.log.debug('handleDatabaseEvent: ', path, modifiersString, eventName, body.snapshot && body.snapshot.key); + + // subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } + const modifierMap = this.dbSubscriptions[path]; + if (modifierMap) { + const eventTypeMap = modifierMap[modifier]; + if (eventTypeMap) { + const callbacks = eventTypeMap[eventName] || []; + this.log.debug(' -- about to fire its ' + callbacks.length + ' callbacks'); + callbacks.forEach(cb => { + if (cb && typeof(cb) === 'function') { + const snap = new Snapshot(this, body.snapshot); + cb(snap, body); + } + }); + } + } + } + + handleDatabaseError(evt: Object) { + this.log.debug('handleDatabaseError ->', evt); + } + + on(referenceKey: string, path: string, modifiers: Array, evt: string, cb: () => void) { + this.log.debug('adding on listener', referenceKey, path, modifiers, evt); + const key = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; + + if (!this.dbSubscriptions[key]) { + this.dbSubscriptions[key] = {}; + } + + if (!this.dbSubscriptions[key][modifier]) { + this.dbSubscriptions[key][modifier] = {}; + } + + if (!this.dbSubscriptions[key][modifier][evt]) { + this.dbSubscriptions[key][modifier][evt] = []; + } + + this.dbSubscriptions[key][modifier][evt].push(cb); + + if (!this.successListener) { + this.successListener = FirestackDatabaseEvt + .addListener( + 'database_event', + this.handleDatabaseEvent.bind(this)); + } + + if (!this.errorListener) { + this.errorListener = FirestackDatabaseEvt + .addListener( + 'database_error', + this.handleDatabaseError.bind(this)); + } + + return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, evt).then(() => { + return [this.successListener, this.errorListener]; + }); + } + + off(referenceKey: string, path: string, modifiers: Array, eventName: string, origCB?: () => void) { + const pathKey = this._pathKey(path); + const modifiersString = getModifiersString(modifiers); + const modifier = modifiersString; + this.log.debug('off() : ', referenceKey, pathKey, modifiersString, eventName); + // Remove subscription + if (this.dbSubscriptions[pathKey]) { + + if (!eventName || eventName === '') { + // remove all listeners for this pathKey + this.dbSubscriptions[pathKey] = {}; + } + + // TODO clean me - no need for this many conditionals + if (this.dbSubscriptions[pathKey][modifier]) { + if (this.dbSubscriptions[pathKey][modifier][eventName]) { + if (origCB) { + // remove only the given callback + this.dbSubscriptions[pathKey][modifier][eventName].splice(this.dbSubscriptions[pathKey][modifier][eventName].indexOf(origCB), 1); + } else { + // remove all callbacks for this path:modifier:eventType + delete this.dbSubscriptions[pathKey][modifier][eventName]; + } + } else { + this.log.warn('off() called, but not currently listening at that location (bad eventName)', pathKey, modifiersString, eventName); + } + } else { + this.log.warn('off() called, but not currently listening at that location (bad modifier)', pathKey, modifiersString, eventName); + } + + if (Object.keys(this.dbSubscriptions[pathKey]).length <= 0) { + // there are no more subscriptions so we can unwatch + delete this.dbSubscriptions[pathKey]; + } + if (Object.keys(this.dbSubscriptions).length === 0) { + if (this.successListener) { + this.successListener.remove(); + this.successListener = null; + } + if (this.errorListener) { + this.errorListener.remove(); + this.errorListener = null; + } + } + } else { + this.log.warn('off() called, but not currently listening at that location (bad path)', pathKey, modifiersString, eventName); + } + + const subscriptions = [this.successListener, this.errorListener]; + const modifierMap = this.dbSubscriptions[path]; + + if (modifierMap && modifierMap[modifier] && modifierMap[modifier][eventName] && modifierMap[modifier][eventName].length > 0) { + return Promise.resolve(subscriptions); + } + + return promisify('off', FirestackDatabase)(path, modifiersString, eventName).then(() => { + // subscriptions.forEach(sub => sub.remove()); + // delete this.listeners[eventName]; + return subscriptions; + }); + } + + cleanup() { + let promises = Object.keys(this.refs) + .map(key => this.refs[key]) + .map(ref => ref.cleanup()); + return Promise.all(promises); + } + + release(...path: Array) { + const key = this._pathKey(...path); + if (this.refs[key]) { + delete this.refs[key]; + } + } + + _pathKey(...path: Array): string { + return path.join('-'); + } + + get namespace(): string { + return 'firestack:database'; + } +} diff --git a/lib/modules/database/query.js b/lib/modules/database/query.js new file mode 100644 index 0000000..ea8fc43 --- /dev/null +++ b/lib/modules/database/query.js @@ -0,0 +1,101 @@ +/** + * @flow + */ +'use strict'; + +import { ReferenceBase } from './../base'; +import Reference from './reference.js'; + +// TODO why randomly 1000000? comments? +let uid = 1000000; + +/** + * @class Query + */ +export default class Query extends ReferenceBase { + static ref: Reference; + + static orderBy: Array; + static limit: Array; + static filters: Object;// { [key: string]: Array }; + + ref: Reference; + + constructor(ref: Reference, path: Array, existingModifiers?: { [key: string]: string }) { + super(ref.db, path); + this.log.debug('creating Query ', path, existingModifiers); + this.uid = uid++; // uuid.v4(); + this.ref = ref; + this.orderBy = undefined; + this.limit = undefined; + this.filters = {}; + + // parse exsitingModifiers + if (existingModifiers) { + this.import(existingModifiers); + } + } + + // noinspection ReservedWordAsName + export(): { [key: string]: string } { + const argsSeparator = ':'; + const ret = {}; + if (this.orderBy) { + ret.orderBy = this.orderBy.join(argsSeparator); + } + if (this.limit) { + ret.limit = this.limit.join(argsSeparator); + } + if (this.filters && Object.keys(this.filters).length > 0) { + let filters = Object.keys(this.filters).map(key => { + const filter = this.filters[key]; + if (filter) { + return [key, filter].join(argsSeparator); + } + }).filter(Boolean); + if (filters.length > 0) { + ret.filters = filters.join('|'); + } + } + return ret; + } + + // noinspection ReservedWordAsName + import(modifiers: { [key: string]: string }) { + const argsSeparator = ':'; + if (modifiers.orderBy) { + this.setOrderBy(...modifiers.orderBy.split(argsSeparator)); + } + + if (modifiers.limit) { + const [name, value] = modifiers.limit.split(argsSeparator); + this.setLimit(name, parseInt(value, 10)); + } + + if (modifiers.filters) { + modifiers.filters.split('|').forEach(filter => { + this.setFilter(...filter.split(argsSeparator)); + }); + } + } + + setOrderBy(name: string, ...args: Array) { + this.orderBy = [name].concat(args); + } + + setLimit(name: string, limit: number) { + this.limit = [name, limit]; + } + + setFilter(name: string, ...args: Array) { + let vals = args.filter(str => !!str); + if (vals.length > 0) { + this.filters[name] = vals; + } + } + + build() { + let exportObj = this.export(); + return Object.keys(exportObj).map(exportKey => exportObj[exportKey]); + } +} diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js new file mode 100644 index 0000000..ece4f74 --- /dev/null +++ b/lib/modules/database/reference.js @@ -0,0 +1,284 @@ +/** + * @flow + */ +import { NativeModules } from 'react-native'; +import promisify from './../../utils/promisify'; +import { ReferenceBase } from './../base'; +import Snapshot from './snapshot.js'; +import Query from './query.js'; + +const FirestackDatabase = NativeModules.FirestackDatabase; + +/** + * TODO ? why is it here and in index? make it a util + * @param modifiers + * @returns {*} + */ +function getModifiersString(modifiers) { + if (!modifiers || !Array.isArray(modifiers)) { + return ''; + } + return modifiers.join('|'); +} + +// https://firebase.google.com/docs/reference/js/firebase.database.Reference +let uid = 0; + +/** + * @class Reference + */ +export default class Reference extends ReferenceBase { + + db: FirestackDatabase; + query: Query; + uid: number; + + constructor(db: FirestackDatabase, path: Array, existingModifiers?: { [key: string]: string }) { + super(db.firestack, path); + + this.db = db; + this.query = new Query(this, path, existingModifiers); + this.uid = uid++; // uuid.v4(); + this.listeners = {}; + + // Aliases + this.get = this.getAt; + this.set = this.setAt; + this.update = this.updateAt; + this.remove = this.removeAt; + + this.log.debug('Created new Reference', this.dbPath(), this.uid); + } + + // Parent roots + parent() { + const parentPaths = this.path.slice(0, -1); + return new Reference(this.db, parentPaths); + } + + root() { + return new Reference(this.db, []); + } + + child(...paths: Array) { + return new Reference(this.db, this.path.concat(paths)); + } + + keepSynced(bool: boolean) { + const path = this.dbPath(); + return promisify('keepSynced', FirestackDatabase)(path, bool); + } + + // Get the value of a ref either with a key + getAt() { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); + } + + setAt(val: any) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('set', FirestackDatabase)(path, value); + } + + updateAt(val: any) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('update', FirestackDatabase)(path, value); + } + + // TODO - what is key even for here? + removeAt(key: string) { + const path = this.dbPath(); + return promisify('remove', FirestackDatabase)(path); + } + + push(val: any = {}) { + const path = this.dbPath(); + const value = this._serializeValue(val); + return promisify('push', FirestackDatabase)(path, value) + .then(({ ref }) => { + const separator = '/'; + return new Reference(this.db, ref.split(separator)); + }); + } + + on(evt?: string, cb: () => any) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + this.log.debug('adding reference.on', path, modifiersString, evt); + return this.db.storeRef(this.uid, this).then(() => { + return this.db.on(this.uid, path, modifiers, evt, cb).then(subscriptions => { + this.listeners[evt] = subscriptions; + }); + }); + } + + once(evt?: string = 'once', cb: (snapshot: Object) => void) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + const modifiersString = getModifiersString(modifiers); + return this.db.storeRef(this.uid, this).then(() => { + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) + .then(({ snapshot }) => new Snapshot(this, snapshot)) + .then(snapshot => { + if (cb && typeof cb === 'function') { + cb(snapshot); + } + return snapshot; + }); + }); + } + + off(evt: string = '', origCB?: () => any) { + const path = this.dbPath(); + const modifiers = this.dbModifiers(); + this.log.debug('ref.off(): ', path, modifiers, evt); + return this.db.unstoreRef(this.uid).then(() => { + return this.db.off(this.uid, path, modifiers, evt, origCB).then(subscriptions => { + // delete this.listeners[eventName]; + // this.listeners[evt] = subscriptions; + }); + }); + } + + cleanup() { + let promises = Object.keys(this.listeners) + .map(key => this.off(key)); + return Promise.all(promises); + } + + // Sanitize value + // As Firebase cannot store date objects. + _serializeValue(obj: Object = {}) { + return Object.keys(obj).reduce((sum, key) => { + let val = obj[key]; + if (val instanceof Date) { + val = val.toISOString(); + } + return { + ...sum, + [key]: val, + }; + }, {}); + } + + // TODO this function isn't used anywhere - why is it here? + _deserializeValue(obj: Object = {}) { + return Object.keys(obj).reduce((sum, key) => { + let val = obj[key]; + if (val instanceof Date) { + val = val.getTime(); + } + return { + ...sum, + [key]: val, + }; + }, {}); + } + + // class Query extends Reference {} + + // let ref = firestack.database().ref('/timeline'); + // ref.limitToLast(1).on('child_added', () => {}); + // ref.limitToFirst(1).on('child_added', () => {}); + // ref.on('child_added', () => {}) + + // Modifiers + orderByKey(): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByKey'); + return newRef; + } + + orderByPriority(): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByPriority'); + return newRef; + } + + orderByValue(): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByValue'); + return newRef; + } + + orderByChild(key: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setOrderBy('orderByChild', key); + return newRef; + } + + // Limits + limitToLast(limit: number): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setLimit('limitToLast', limit); + return newRef; + } + + limitToFirst(limit: number): Reference { + // return this.query.setLimit('limitToFirst', limit); + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setLimit('limitToFirst', limit); + return newRef; + } + + // Filters + equalTo(value: any, key?: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setFilter('equalTo', value, key); + return newRef; + } + + endAt(value: any, key?: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setFilter('endAt', value, key); + return newRef; + } + + startAt(value: any, key?: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.export()); + newRef.query.setFilter('startAt', value, key); + return newRef; + } + + presence(path: string) { + const presence = this.firestack.presence; + const ref = path ? this.child(path) : this; + return presence.ref(ref, this.dbPath()); + } + + // onDisconnect + onDisconnect() { + return new Disconnect(this); + } + + // attributes + get fullPath(): string { + return this.dbPath(); + } + + get name(): string { + return this.path.splice(-1); + } + + dbPath(): string { + let path = this.path; + let pathStr = (path.length > 0 ? path.join('/') : '/'); + if (pathStr[0] !== '/') { + pathStr = `/${pathStr}`; + } + return pathStr; + } + + dbModifiers(): Array { + return this.query.build(); + } + + get namespace(): string { + return 'firestack:dbRef'; + } +} diff --git a/lib/modules/database/snapshot.js b/lib/modules/database/snapshot.js new file mode 100644 index 0000000..c1f2d8c --- /dev/null +++ b/lib/modules/database/snapshot.js @@ -0,0 +1,52 @@ +/** + * @flow + */ +import Reference from './reference.js'; + +export default class Snapshot { + static key:String; + static value:Object; + static exists:boolean; + static hasChildren:boolean; + static childrenCount:Number; + static childKeys:String[]; + + ref: Object; + key: string; + value: any; + exists: boolean; + priority: any; + hasChildren: boolean; + childrenCount: number; + childKeys: Array; + + constructor(ref: Reference, snapshot: Object) { + this.ref = ref; + this.key = snapshot.key; + this.value = snapshot.value; + this.exists = snapshot.exists || true; + this.priority = snapshot.priority; + this.hasChildren = snapshot.hasChildren || false; + this.childrenCount = snapshot.childrenCount || 0; + this.childKeys = snapshot.childKeys || []; + } + + val() { + return this.value; + } + + forEach(fn: (key: any) => any) { + (this.childKeys || []) + .forEach(key => fn(this.value[key])); + } + + map(fn: (key: string) => mixed) { + let arr = []; + this.forEach(item => arr.push(fn(item))); + return arr; + } + + reverseMap(fn: (key: string) => mixed) { + return this.map(fn).reverse(); + } +} From 88a8a7c2058d60789bff258a726b7729e8ba4ee6 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:12:09 +0000 Subject: [PATCH 029/275] added missing android/build git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7e7abfa..b5f5b47 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ android/*.iml ehthumbs.db Thumbs.dbandroid/gradle android/gradlew +android/build android/gradlew.bat android/gradle/ .idea From 6049d07ba4dc1c224912d2a65f1281987bbce1c2 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:16:50 +0000 Subject: [PATCH 030/275] fixed build.gradle to allow working on project in Android Studio --- android/build.gradle | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 34eefc5..d8f4001 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,3 +1,16 @@ +// START - required to allow working on this project inside Android Studio +// YES, jcenter is required twice - it somehow tricks studio into compiling deps below +// doesn't break anything anywhere else and projects using this lib work as normal +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.3' + } +} +// END + apply plugin: 'com.android.library' android { @@ -18,10 +31,20 @@ android { } } +// START - required to allow working on this project inside Android Studio +// YES, jcenter is required twice - it somehow tricks studio into compiling deps below +// doesn't break anything anywhere else and projects using this lib work as normal +// you'll now have code completion/validation and all the other AS goodies. +allprojects { + repositories { + jcenter() + } +} +// END + dependencies { compile 'com.facebook.react:react-native:0.20.+' - compile 'com.google.android.gms:play-services-base:9.8.1' - + compile 'com.google.android.gms:play-services-base:9.8.0' compile 'com.google.firebase:firebase-core:9.8.0' compile 'com.google.firebase:firebase-auth:9.8.0' compile 'com.google.firebase:firebase-analytics:9.8.0' From 2d680c30fbec999df19311598214aa88001486b7 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:25:26 +0000 Subject: [PATCH 031/275] renamed and cleaned up cloud messaging module - inline with web api --- lib/modules/{cloudmessaging.js => messaging.js} | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) rename lib/modules/{cloudmessaging.js => messaging.js} (95%) diff --git a/lib/modules/cloudmessaging.js b/lib/modules/messaging.js similarity index 95% rename from lib/modules/cloudmessaging.js rename to lib/modules/messaging.js index 983e44a..d8a0e3c 100644 --- a/lib/modules/cloudmessaging.js +++ b/lib/modules/messaging.js @@ -1,10 +1,14 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; +import { Base } from './base'; +import promisify from '../utils/promisify'; + const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' -export class CloudMessaging extends Base { +/** + * @class Messaging + */ +export class Messaging extends Base { constructor(firestack, options = {}) { super(firestack, options); } @@ -18,7 +22,7 @@ export class CloudMessaging extends Base { return promisify(() => sub, FirestackCloudMessaging)(sub); } - // android & ios api + // android & ios api dfgsdfs onMessageReceived(...args) { return this.onMessage(...args); } @@ -129,5 +133,3 @@ export class CloudMessaging extends Base { this._off('FirestackUpstreamSend'); } } - -export default CloudMessaging From 7040d7641d3654bb58a968592dbf5108cdddfbc7 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:32:02 +0000 Subject: [PATCH 032/275] more fixes and refactoring - fixed unnecessary imports, duplications, renamed modules to match web api, linting issues, fixed several crashes --- .babelrc | 2 +- .../io/fullstack/firestack/FirestackAuth.java | 1187 ++++++++--------- .../fullstack/firestack/FirestackStorage.java | 7 +- .../fullstack/firestack/FirestackUtils.java | 230 ++-- lib/firestack.js | 58 +- lib/modules/analytics.js | 8 +- lib/modules/{authentication.js => auth.js} | 40 +- lib/modules/base.js | 13 +- lib/modules/messaging.js | 2 +- lib/modules/presence.js | 4 +- lib/modules/remoteConfig.js | 9 +- lib/modules/storage.js | 3 +- package.json | 5 +- 13 files changed, 785 insertions(+), 783 deletions(-) rename lib/modules/{authentication.js => auth.js} (87%) diff --git a/.babelrc b/.babelrc index 6c1e0ce..a9ce136 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { "presets": ["react-native"] -} \ No newline at end of file +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 562097b..9e945fc 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -37,662 +37,657 @@ import com.google.firebase.auth.GoogleAuthProvider; class FirestackAuthModule extends ReactContextBaseJavaModule { - private final int NO_CURRENT_USER = 100; - private final int ERROR_FETCHING_TOKEN = 101; - private final int ERROR_SENDING_VERIFICATION_EMAIL = 102; + private final int NO_CURRENT_USER = 100; + private final int ERROR_FETCHING_TOKEN = 101; + private final int ERROR_SENDING_VERIFICATION_EMAIL = 102; - private static final String TAG = "FirestackAuth"; + private static final String TAG = "FirestackAuth"; - private Context context; - private ReactContext mReactContext; - private FirebaseAuth mAuth; - private FirebaseApp app; - private FirebaseUser user; - private FirebaseAuth.AuthStateListener mAuthListener; + private Context context; + private ReactContext mReactContext; + private FirebaseAuth mAuth; + private FirebaseApp app; + private FirebaseUser user; + private FirebaseAuth.AuthStateListener mAuthListener; - public FirestackAuthModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - mReactContext = reactContext; + public FirestackAuthModule(ReactApplicationContext reactContext) { + super(reactContext); + this.context = reactContext; + mReactContext = reactContext; - Log.d(TAG, "New FirestackAuth instance"); - } + Log.d(TAG, "New FirestackAuth instance"); + } - @Override - public String getName() { - return TAG; - } + @Override + public String getName() { + return TAG; + } - @ReactMethod - public void listenForAuth() { - mAuthListener = new FirebaseAuth.AuthStateListener() { + @ReactMethod + public void listenForAuth() { + mAuthListener = new FirebaseAuth.AuthStateListener() { - @Override - public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { - WritableMap msgMap = Arguments.createMap(); - msgMap.putString("eventName", "listenForAuth"); + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + WritableMap msgMap = Arguments.createMap(); + msgMap.putString("eventName", "listenForAuth"); - if (FirestackAuthModule.this.user != null) { - WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + WritableMap userMap = getUserMap(); - msgMap.putBoolean("authenticated", true); - msgMap.putMap("user", userMap); + msgMap.putBoolean("authenticated", true); + msgMap.putMap("user", userMap); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } else { - msgMap.putBoolean("authenticated", false); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } - } - }; + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } else { + msgMap.putBoolean("authenticated", false); + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } + } + }; - mAuth = FirebaseAuth.getInstance(); - mAuth.addAuthStateListener(mAuthListener); - } + mAuth = FirebaseAuth.getInstance(); + mAuth.addAuthStateListener(mAuthListener); + } - @ReactMethod - public void unlistenForAuth(final Callback callback) { - if (mAuthListener != null) { - mAuth.removeAuthStateListener(mAuthListener); + @ReactMethod + public void unlistenForAuth(final Callback callback) { + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); - callback.invoke(null, resp); - } + callback.invoke(null, resp); } - - @ReactMethod - public void createUserWithEmail(final String email, final String password, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.createUserWithEmailAndPassword(email, password) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + } + + @ReactMethod + public void createUserWithEmail(final String email, final String password, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + mAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } + } }); - } - - @ReactMethod - public void signInWithEmail(final String email, final String password, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.signInWithEmailAndPassword(email, password) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); - } - - @ReactMethod - public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { - if (provider.equals("facebook")) { - this.facebookLogin(authToken, callback); - } else if (provider.equals("google")) { - this.googleLogin(authToken, callback); - } else - // TODO - FirestackUtils.todoNote(TAG, "signInWithProvider", callback); - } - - @ReactMethod - public void signInAnonymously(final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.signInAnonymously() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); - - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - anonymousUserCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + } + + @ReactMethod + public void signInWithEmail(final String email, final String password, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + mAuth.signInWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } }); - } - - @ReactMethod - public void signInWithCustomToken(final String customToken, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.signInWithCustomToken(customToken) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + } + + @ReactMethod + public void signInWithProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { + if (provider.equals("facebook")) { + this.facebookLogin(authToken, callback); + } else if (provider.equals("google")) { + this.googleLogin(authToken, callback); + } else + // TODO + FirestackUtils.todoNote(TAG, "signInWithProvider", callback); + } + + @ReactMethod + public void signInAnonymously(final Callback callback) { + Log.d(TAG, "signInAnonymously:called:"); + mAuth = FirebaseAuth.getInstance(); + + + mAuth.signInAnonymously() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); + + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + anonymousUserCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } + } }); - } - - @ReactMethod - public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { - // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); - // AuthCredential credential; - // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); - } - - @ReactMethod - public void updateUserEmail(final String email, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - if (user != null) { - user.updateEmail(email) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - Log.d(TAG, "User email address updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + } + + @ReactMethod + public void signInWithCustomToken(final String customToken, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + mAuth.signInWithCustomToken(customToken) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + @ReactMethod + public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { + // TODO: + FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); + // AuthCredential credential; + // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); + } + + @ReactMethod + public void updateUserEmail(final String email, final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + if (user != null) { + user.updateEmail(email) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User email address updated"); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); } + } - @ReactMethod - public void updateUserPassword(final String newPassword, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - if (user != null) { - user.updatePassword(newPassword) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - Log.d(TAG, "User password updated"); - - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); - } - } + @ReactMethod + public void updateUserPassword(final String newPassword, final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - @ReactMethod - public void sendPasswordResetWithEmail(final String email, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.sendPasswordResetEmail(email) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - callback.invoke(null, resp); - } else { - callback.invoke(task.getException().toString()); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { + if (user != null) { + user.updatePassword(newPassword) + .addOnCompleteListener(new OnCompleteListener() { @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); - } + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User password updated"); - @ReactMethod - public void deleteUser(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - if (user != null) { - user.delete() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - Log.d(TAG, "User account deleted"); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User account deleted"); - callback.invoke(null, resp); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); } - }); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); } - - - @ReactMethod - public void sendEmailVerification(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - // TODO check user exists - user.sendEmailVerification() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User verification email sent"); - callback.invoke(null, resp); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL); - err.putString("errorMessage", task.getException().getMessage()); - callback.invoke(err); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - //userExceptionCallback(ex, callback); - } - }); - } - - - @ReactMethod - public void getToken(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - // TODO check user exists - user.getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - String token = task.getResult().getToken(); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("token", token); - callback.invoke(null, resp); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", ERROR_FETCHING_TOKEN); - err.putString("errorMessage", task.getException().getMessage()); - callback.invoke(err); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { + } + + @ReactMethod + public void sendPasswordResetWithEmail(final String email, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + mAuth.sendPasswordResetEmail(email) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + callback.invoke(null, resp); + } else { + callback.invoke(task.getException().toString()); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + @ReactMethod + public void deleteUser(final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + if (user != null) { + user.delete() + .addOnCompleteListener(new OnCompleteListener() { @Override - public void onFailure(@NonNull Exception ex) { + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User account deleted"); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User account deleted"); + callback.invoke(null, resp); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { userExceptionCallback(ex, callback); + } } - }); - } - - @ReactMethod - public void updateUserProfile(ReadableMap props, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); - - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); - - if (m.containsKey("displayName")) { - String displayName = (String) m.get("displayName"); - profileBuilder.setDisplayName(displayName); - } - - if (m.containsKey("photoUri")) { - String photoUriStr = (String) m.get("photoUri"); - Uri uri = Uri.parse(photoUriStr); - profileBuilder.setPhotoUri(uri); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); } - - UserProfileChangeRequest profileUpdates = profileBuilder.build(); - - // TODO check user exists - user.updateProfile(profileUpdates) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - Log.d(TAG, "User profile updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + }); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + callback.invoke(err); + } + } + + + @ReactMethod + public void sendEmailVerification(final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + // TODO check user exists + user.sendEmailVerification() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User verification email sent"); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + //userExceptionCallback(ex, callback); + } }); - } + } + + + @ReactMethod + public void getToken(final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + // TODO check user exists + user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + String token = task.getResult().getToken(); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("token", token); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_FETCHING_TOKEN); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } - @ReactMethod - public void signOut(final Callback callback) { - FirebaseAuth.getInstance().signOut(); - this.user = null; + @ReactMethod + public void updateUserProfile(ReadableMap props, final Callback callback) { + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User signed out"); - callback.invoke(null, resp); - } + UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); - @ReactMethod - public void getCurrentUser(final Callback callback) { - mAuth = FirebaseAuth.getInstance(); + Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); - this.user = mAuth.getCurrentUser(); - if (this.user == null) { - noUserCallback(callback); - } else { - Log.d("USRC", this.user.getDisplayName()); - userCallback(this.user, callback); - } + if (m.containsKey("displayName")) { + String displayName = (String) m.get("displayName"); + profileBuilder.setDisplayName(displayName); } - // TODO: Check these things - @ReactMethod - public void googleLogin(String IdToken, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null); - mAuth.signInWithCredential(credential) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + if (m.containsKey("photoUri")) { + String photoUriStr = (String) m.get("photoUri"); + Uri uri = Uri.parse(photoUriStr); + profileBuilder.setPhotoUri(uri); } - @ReactMethod - public void facebookLogin(String Token, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - AuthCredential credential = FacebookAuthProvider.getCredential(Token); - mAuth.signInWithCredential(credential) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + UserProfileChangeRequest profileUpdates = profileBuilder.build(); + + // TODO check user exists + user.updateProfile(profileUpdates) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User profile updated"); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - }); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + @ReactMethod + public void signOut(final Callback callback) { + FirebaseAuth.getInstance().signOut(); + this.user = null; + + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User signed out"); + callback.invoke(null, resp); + } + + @ReactMethod + public void getCurrentUser(final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + this.user = mAuth.getCurrentUser(); + if (this.user == null) { + noUserCallback(callback); + } else { + Log.d("USRC", this.user.getUid()); + userCallback(this.user, callback); } - - // Internal helpers - public void userCallback(FirebaseUser passedUser, final Callback callback) { - - if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); - } else { - this.user = passedUser; - } - - // TODO check user exists - this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", false); - } - - msgMap.putMap("user", userMap); - - callback.invoke(null, msgMap); + } + + // TODO: Check these things + @ReactMethod + public void googleLogin(String IdToken, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null); + mAuth.signInWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } + } }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + @ReactMethod + public void facebookLogin(String Token, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); + + AuthCredential credential = FacebookAuthProvider.getCredential(Token); + mAuth.signInWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - }); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + // Internal helpers + public void userCallback(FirebaseUser passedUser, final Callback callback) { + + if (passedUser == null) { + mAuth = FirebaseAuth.getInstance(); + this.user = mAuth.getCurrentUser(); + } else { + this.user = passedUser; } - // TODO: Reduce to one method - public void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { - - if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); - } else { - this.user = passedUser; + // TODO check user exists + this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); + + userMap.putString("token", token); + userMap.putBoolean("anonymous", false); } - this.user.getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - } - - msgMap.putMap("user", userMap); - - callback.invoke(null, msgMap); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + msgMap.putMap("user", userMap); + + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + // TODO: Reduce to one method + public void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { + + if (passedUser == null) { + mAuth = FirebaseAuth.getInstance(); + this.user = mAuth.getCurrentUser(); + } else { + this.user = passedUser; } + this.user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); - public void noUserCallback(final Callback callback) { - WritableMap message = Arguments.createMap(); - - message.putString("errorMessage", "no_user"); - message.putString("eventName", "no_user"); - message.putBoolean("authenticated", false); - - callback.invoke(null, message); - } + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); - public void userErrorCallback(Task task, final Callback onFail) { - WritableMap error = Arguments.createMap(); - error.putInt("errorCode", task.getException().hashCode()); - error.putString("errorMessage", task.getException().getMessage()); - error.putString("allErrorMessage", task.getException().toString()); - - onFail.invoke(error); - } - - public void userExceptionCallback(Exception ex, final Callback onFail) { - WritableMap error = Arguments.createMap(); - error.putInt("errorCode", ex.hashCode()); - error.putString("errorMessage", ex.getMessage()); - error.putString("allErrorMessage", ex.toString()); - - onFail.invoke(error); - } - - private WritableMap getUserMap() { - WritableMap userMap = Arguments.createMap(); - - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - - if (user != null) { - final String email = user.getEmail(); - final String uid = user.getUid(); - final String provider = user.getProviderId(); - final String name = user.getDisplayName(); - final Uri photoUrl = user.getPhotoUrl(); - - userMap.putString("email", email); - userMap.putString("uid", uid); - userMap.putString("providerId", provider); - - if (name != null) { - userMap.putString("name", name); + userMap.putString("token", token); + userMap.putBoolean("anonymous", true); } - if (photoUrl != null) { - userMap.putString("photoURL", photoUrl.toString()); - } - } else { - userMap.putString("msg", "no user"); - } + msgMap.putMap("user", userMap); - return userMap; + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } + + + public void noUserCallback(final Callback callback) { + WritableMap message = Arguments.createMap(); + + message.putString("errorMessage", "no_user"); + message.putString("eventName", "no_user"); + message.putBoolean("authenticated", false); + + callback.invoke(null, message); + } + + public void userErrorCallback(Task task, final Callback onFail) { + WritableMap error = Arguments.createMap(); + error.putInt("errorCode", task.getException().hashCode()); + error.putString("errorMessage", task.getException().getMessage()); + error.putString("allErrorMessage", task.getException().toString()); + + onFail.invoke(error); + } + + public void userExceptionCallback(Exception ex, final Callback onFail) { + WritableMap error = Arguments.createMap(); + error.putInt("errorCode", ex.hashCode()); + error.putString("errorMessage", ex.getMessage()); + error.putString("allErrorMessage", ex.toString()); + + onFail.invoke(error); + } + + private WritableMap getUserMap() { + WritableMap userMap = Arguments.createMap(); + + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + + if (user != null) { + final String email = user.getEmail(); + final String uid = user.getUid(); + final String provider = user.getProviderId(); + final String name = user.getDisplayName(); + final Boolean verified = user.isEmailVerified(); + final Uri photoUrl = user.getPhotoUrl(); + + userMap.putString("email", email); + userMap.putString("uid", uid); + userMap.putString("providerId", provider); + userMap.putBoolean("emailVerified", verified || false); + + + if (name != null) { + userMap.putString("name", name); + } + + if (photoUrl != null) { + userMap.putString("photoURL", photoUrl.toString()); + } + } else { + userMap.putString("msg", "no user"); } + + return userMap; + } } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 25ca218..a91a6a9 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -96,8 +96,9 @@ public void onSuccess(Uri uri) { res.putString("bucket", storageRef.getBucket()); res.putString("fullPath", uri.toString()); res.putString("path", uri.getPath()); + res.putString("url", uri.toString()); - storageRef.getMetadata() + fileRef.getMetadata() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(final StorageMetadata storageMetadata) { @@ -230,9 +231,9 @@ public void getRealPathFromURI(final String uri, final Callback callback) { Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); - String path = cursor.getString(column_index); + String path = cursor.getString(column_index); cursor.close(); - + callback.invoke(null, path); } catch (Exception ex) { ex.printStackTrace(); diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java index 32c871a..ee50649 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java @@ -23,7 +23,7 @@ public class FirestackUtils { private static final String TAG = "FirestackUtils"; - // TODO NOTE + // TODO NOTE public static void todoNote(final String tag, final String name, final Callback callback) { Log.e(tag, "The method " + name + " has not yet been implemented."); Log.e(tag, "Feel free to contribute to finish the method in the source."); @@ -34,11 +34,11 @@ public static void todoNote(final String tag, final String name, final Callback } /** - * send a JS event - **/ + * send a JS event + **/ public static void sendEvent(final ReactContext context, - final String eventName, - final WritableMap params) { + final String eventName, + final WritableMap params) { if (context.hasActiveCatalystInstance()) { context .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) @@ -49,57 +49,61 @@ public static void sendEvent(final ReactContext context, } // snapshot - public static WritableMap dataSnapshotToMap(String name, - String path, - DataSnapshot dataSnapshot) { - WritableMap data = Arguments.createMap(); - - data.putString("key", dataSnapshot.getKey()); - data.putBoolean("exists", dataSnapshot.exists()); - data.putBoolean("hasChildren", dataSnapshot.hasChildren()); - - data.putDouble("childrenCount", dataSnapshot.getChildrenCount()); - if (!dataSnapshot.hasChildren()) { - Object value = dataSnapshot.getValue(); - String type = value!=null ? value.getClass().getName() : ""; - switch (type) { - case "java.lang.Boolean": - data.putBoolean("value", (Boolean)value); - break; - case "java.lang.Long": - Long longVal = (Long) value; - data.putDouble("value", (double)longVal); - break; - case "java.lang.Double": - data.putDouble("value", (Double) value); - break; - case "java.lang.String": - data.putString("value",(String) value); - break; - default: - data.putString("value", null); - } - } else{ - WritableMap valueMap = FirestackUtils.castSnapshotValue(dataSnapshot); - data.putMap("value", valueMap); + public static WritableMap dataSnapshotToMap( + String name, + String path, + String modifiersString, + DataSnapshot dataSnapshot + ) { + WritableMap data = Arguments.createMap(); + + data.putString("key", dataSnapshot.getKey()); + data.putBoolean("exists", dataSnapshot.exists()); + data.putBoolean("hasChildren", dataSnapshot.hasChildren()); + + data.putDouble("childrenCount", dataSnapshot.getChildrenCount()); + if (!dataSnapshot.hasChildren()) { + Object value = dataSnapshot.getValue(); + String type = value != null ? value.getClass().getName() : ""; + switch (type) { + case "java.lang.Boolean": + data.putBoolean("value", (Boolean) value); + break; + case "java.lang.Long": + Long longVal = (Long) value; + data.putDouble("value", (double) longVal); + break; + case "java.lang.Double": + data.putDouble("value", (Double) value); + break; + case "java.lang.String": + data.putString("value", (String) value); + break; + default: + data.putString("value", null); } + } else { + WritableMap valueMap = FirestackUtils.castSnapshotValue(dataSnapshot); + data.putMap("value", valueMap); + } - // Child keys - WritableArray childKeys = FirestackUtils.getChildKeys(dataSnapshot); - data.putArray("childKeys", childKeys); + // Child keys + WritableArray childKeys = FirestackUtils.getChildKeys(dataSnapshot); + data.putArray("childKeys", childKeys); - Object priority = dataSnapshot.getPriority(); - if (priority == null) { - data.putString("priority", null); - } else { - data.putString("priority", priority.toString()); - } + Object priority = dataSnapshot.getPriority(); + if (priority == null) { + data.putString("priority", null); + } else { + data.putString("priority", priority.toString()); + } - WritableMap eventMap = Arguments.createMap(); - eventMap.putString("eventName", name); - eventMap.putMap("snapshot", data); - eventMap.putString("path", path); - return eventMap; + WritableMap eventMap = Arguments.createMap(); + eventMap.putString("eventName", name); + eventMap.putMap("snapshot", data); + eventMap.putString("path", path); + eventMap.putString("modifiersString", modifiersString); + return eventMap; } public static Any castSnapshotValue(DataSnapshot snapshot) { @@ -113,7 +117,7 @@ public static Any castSnapshotValue(DataSnapshot snapshot) { break; case "java.lang.Long": Long longVal = (Long) castedChild; - data.putDouble(child.getKey(), (double)longVal); + data.putDouble(child.getKey(), (double) longVal); break; case "java.lang.Double": data.putDouble(child.getKey(), (Double) castedChild); @@ -135,15 +139,15 @@ public static Any castSnapshotValue(DataSnapshot snapshot) { String type = snapshot.getValue().getClass().getName(); switch (type) { case "java.lang.Boolean": - return (Any)(snapshot.getValue()); + return (Any) (snapshot.getValue()); case "java.lang.Long": - return (Any)(snapshot.getValue()); + return (Any) (snapshot.getValue()); case "java.lang.Double": - return (Any)(snapshot.getValue()); + return (Any) (snapshot.getValue()); case "java.lang.String": - return (Any)(snapshot.getValue()); + return (Any) (snapshot.getValue()); default: - Log.w(TAG, "Invalid type: "+type); + Log.w(TAG, "Invalid type: " + type); return (Any) null; } } @@ -164,65 +168,65 @@ public static WritableArray getChildKeys(DataSnapshot snapshot) { } public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { - ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); - Map deconstructedMap = new HashMap<>(); - while (iterator.hasNextKey()) { - String key = iterator.nextKey(); - ReadableType type = readableMap.getType(key); - switch (type) { - case Null: - deconstructedMap.put(key, null); - break; - case Boolean: - deconstructedMap.put(key, readableMap.getBoolean(key)); - break; - case Number: - deconstructedMap.put(key, readableMap.getDouble(key)); - break; - case String: - deconstructedMap.put(key, readableMap.getString(key)); - break; - case Map: - deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); - break; - case Array: - deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); - break; - default: - throw new IllegalArgumentException("Could not convert object with key: " + key + "."); - } - + ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); + Map deconstructedMap = new HashMap<>(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + ReadableType type = readableMap.getType(key); + switch (type) { + case Null: + deconstructedMap.put(key, null); + break; + case Boolean: + deconstructedMap.put(key, readableMap.getBoolean(key)); + break; + case Number: + deconstructedMap.put(key, readableMap.getDouble(key)); + break; + case String: + deconstructedMap.put(key, readableMap.getString(key)); + break; + case Map: + deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); + break; + case Array: + deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); + break; + default: + throw new IllegalArgumentException("Could not convert object with key: " + key + "."); } - return deconstructedMap; + + } + return deconstructedMap; } public static List recursivelyDeconstructReadableArray(ReadableArray readableArray) { - List deconstructedList = new ArrayList<>(readableArray.size()); - for (int i = 0; i < readableArray.size(); i++) { - ReadableType indexType = readableArray.getType(i); - switch(indexType) { - case Null: - deconstructedList.add(i, null); - break; - case Boolean: - deconstructedList.add(i, readableArray.getBoolean(i)); - break; - case Number: - deconstructedList.add(i, readableArray.getDouble(i)); - break; - case String: - deconstructedList.add(i, readableArray.getString(i)); - break; - case Map: - deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); - break; - case Array: - deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); - break; - default: - throw new IllegalArgumentException("Could not convert object at index " + i + "."); - } + List deconstructedList = new ArrayList<>(readableArray.size()); + for (int i = 0; i < readableArray.size(); i++) { + ReadableType indexType = readableArray.getType(i); + switch (indexType) { + case Null: + deconstructedList.add(i, null); + break; + case Boolean: + deconstructedList.add(i, readableArray.getBoolean(i)); + break; + case Number: + deconstructedList.add(i, readableArray.getDouble(i)); + break; + case String: + deconstructedList.add(i, readableArray.getString(i)); + break; + case Map: + deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); + break; + case Array: + deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); + break; + default: + throw new IllegalArgumentException("Could not convert object at index " + i + "."); } - return deconstructedList; + } + return deconstructedList; } } diff --git a/lib/firestack.js b/lib/firestack.js index c865792..6e0d854 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -2,40 +2,40 @@ * @providesModule Firestack * @flow */ -import Log from './utils/log' -const instances = { - default: null, -}; -// const firebase = require('firebase'); +import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; -// const app = require('firebase/app'); -// const storage = require('firebase/storage'); -// const db = require('firebase/database'); +import Log from './utils/log' +import promisify from './utils/promisify'; +import Singleton from './utils/singleton'; + +// modules +import Auth from './modules/auth'; +import Storage from './modules/storage'; +import Database from './modules/database'; +import Presence from './modules/presence'; +import Messaging from './modules/messaging'; +import Analytics from './modules/analytics'; +import RemoteConfig from './modules/remoteConfig'; -import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; -// TODO: Break out modules into component pieces -// i.e. auth component, storage component, etc. +let log; +const instances = { default: null }; const FirestackModule = NativeModules.Firestack; const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); -import promisify from './utils/promisify' -import Singleton from './utils/singleton' - -import RemoteConfig from './modules/remoteConfig' -import { Authentication } from './modules/authentication' -import { Database } from './modules/database' -import { Analytics } from './modules/analytics' -import { Storage } from './modules/storage' -import { Presence } from './modules/presence' -import { CloudMessaging } from './modules/cloudmessaging' - -let log; -export class Firestack extends Singleton { +/** + * @class Firestack + */ +export default class Firestack extends Singleton { + /** + * + * @param options + * @param name - TODO support naming multiple instances + */ constructor(options, name) { - var instance = super(options); + const instance = super(options); instance.options = options || {}; instance._debug = instance.options.debug || false; @@ -56,7 +56,7 @@ export class Firestack extends Singleton { log.info('Calling configure with options', instance.options); instance.configurePromise = instance.configure(instance.options); - instance._auth = new Authentication(instance, instance.options); + instance._auth = new Auth(instance, instance.options); } /** @@ -106,7 +106,7 @@ export class Firestack extends Singleton { auth() { console.log('auth GET'); if (!this._auth) { - this._auth = new Authentication(this); + this._auth = new Auth(this); } return this._auth; } @@ -148,7 +148,7 @@ export class Firestack extends Singleton { // CloudMessaging messaging() { if (!this._cloudMessaging) { - this._cloudMessaging = new CloudMessaging(this); + this._cloudMessaging = new Messaging(this); } return this._cloudMessaging; } @@ -240,5 +240,3 @@ export class Firestack extends Singleton { } } } - -export default Firestack diff --git a/lib/modules/analytics.js b/lib/modules/analytics.js index 70fac43..44b712c 100644 --- a/lib/modules/analytics.js +++ b/lib/modules/analytics.js @@ -1,10 +1,10 @@ import {NativeModules, NativeAppEventEmitter} from 'react-native'; -const FirestackAnalytics = NativeModules.FirestackAnalytics; - import promisify from '../utils/promisify' import { Base } from './base' -export class Analytics extends Base { +const FirestackAnalytics = NativeModules.FirestackAnalytics; + +export default class Analytics extends Base { constructor(firestack, options={}) { super(firestack, options); @@ -38,5 +38,3 @@ export class Analytics extends Base { return 'firestack:analytics' } } - -export default Analytics \ No newline at end of file diff --git a/lib/modules/authentication.js b/lib/modules/auth.js similarity index 87% rename from lib/modules/authentication.js rename to lib/modules/auth.js index c00856b..d0d5229 100644 --- a/lib/modules/authentication.js +++ b/lib/modules/auth.js @@ -1,5 +1,5 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; -const FirestackAuth = NativeModules.FirestackAuth +const FirestackAuth = NativeModules.FirestackAuth; const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); @@ -7,13 +7,14 @@ import promisify from '../utils/promisify' import { Base } from './base' import { default as User} from './user'; -export class Authentication extends Base { +export default class Auth extends Base { constructor(firestack, options = {}) { super(firestack, options); this._authResult = null; this.authenticated = false; this._user = null; + this.getCurrentUser().then(this._onAuthStateChanged.bind(this)).catch(()=>{}); // always track auth changes internall so we can access them synchronously FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); FirestackAuth.listenForAuth(); @@ -26,10 +27,11 @@ export class Authentication extends Base { */ _onAuthStateChanged(auth) { this._authResult = auth; - this.authenticated = auth ? auth.authenticated || false : false - if (auth && !this._user) this._user = new User(this, auth); - else if (!auth && this._user) this._user = null; - else this._user._updateValues(auth); + this.emit('onAuthStateChanged', this._authResult); + this.authenticated = auth ? auth.authenticated || false : false; + if (auth && auth.user && !this._user) this._user = new User(this, auth); + else if ((!auth || !auth.user) && this._user) this._user = null; + else this._user ? this._user._updateValues(auth) : null; } /* @@ -38,14 +40,15 @@ export class Authentication extends Base { /** * Listen for auth changes. - * @param callback + * @param listener */ onAuthStateChanged(listener) { this.log.info('Creating onAuthStateChanged listener'); - const sub = this._on('listenForAuth', listener, FirestackAuthEvt); - FirestackAuth.listenForAuth(); - this.log.info('Listening for onAuthStateChanged events...'); - return promisify(() => sub, FirestackAuth)(sub); + this.on('onAuthStateChanged', listener); + // const sub = this._on('listenForAuth', listener, FirestackAuthEvt); + // FirestackAuth.listenForAuth(); + // this.log.info('Listening for onAuthStateChanged events...'); + // return promisify(() => sub, FirestackAuth)(sub); } /** @@ -54,8 +57,9 @@ export class Authentication extends Base { */ offAuthStateChanged(listener) { this.log.info('Removing onAuthStateChanged listener'); - this._off('listenForAuth'); - return promisify('unlistenForAuth', FirestackAuth)(); + this.removeListener('onAuthStateChanged', listener); + // this._off('listenForAuth'); + // return promisify('unlistenForAuth', FirestackAuth)(); } /** @@ -257,6 +261,14 @@ export class Authentication extends Base { return promisify('signOut', FirestackAuth)(); } + /** + * Get the currently signed in user + * @return {Promise} + */ + getCurrentUser() { + return promisify('getCurrentUser', FirestackAuth)(); + } + /** * Get the currently signed in user * @return {Promise} @@ -269,5 +281,3 @@ export class Authentication extends Base { return 'firestack:auth'; } } - -export default Authentication diff --git a/lib/modules/base.js b/lib/modules/base.js index ae6c893..7aee499 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -1,17 +1,16 @@ /** * @flow */ -import Log from '../utils/log' - import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; -import { default as EventEmitter } from './../utils/eventEmitter'; + +import Log from '../utils/log' +import EventEmitter from './../utils/eventEmitter'; const FirestackModule = NativeModules.Firestack; const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); -import promisify from '../utils/promisify' +const logs = {}; -let logs = {}; export class Base extends EventEmitter { constructor(firestack, options = {}) { super(); @@ -31,12 +30,14 @@ export class Base extends EventEmitter { return logs[this.namespace]; } + // TODO unused - do we need this anymore? _addConstantExports(constants) { Object.keys(constants).forEach(name => { FirestackModule[name] = constants[name]; }); } + // TODO unused - do we need this anymore? _addToFirestackInstance(...methods) { methods.forEach(name => { this.firestack[name] = this[name].bind(this); @@ -75,7 +76,7 @@ export class Base extends EventEmitter { } _off(name) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (this.eventHandlers[name]) { const subscription = this.eventHandlers[name]; subscription.remove(); // Remove subscription diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index d8a0e3c..877cf29 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -8,7 +8,7 @@ const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessagin /** * @class Messaging */ -export class Messaging extends Base { +export default class Messaging extends Base { constructor(firestack, options = {}) { super(firestack, options); } diff --git a/lib/modules/presence.js b/lib/modules/presence.js index cac57ae..b91382b 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -71,7 +71,7 @@ class PresenceRef extends ReferenceBase { } -export class Presence extends Base { +export default class Presence extends Base { constructor(firestack, options={}) { super(firestack, options); @@ -113,5 +113,3 @@ export class Presence extends Base { return 'firestack:presence' } } - -export default Presence; \ No newline at end of file diff --git a/lib/modules/remoteConfig.js b/lib/modules/remoteConfig.js index 385caf5..cd17d64 100644 --- a/lib/modules/remoteConfig.js +++ b/lib/modules/remoteConfig.js @@ -2,19 +2,20 @@ * Configuration class */ const defaultExpiration = 60 * 60 * 24; // one day -export class RemoteConfig { + +export default class RemoteConfig { constructor(options) { this.config = options || {}; this.setDefaultRemoteConfig(options) - .then(() => this.configured = true); + .then(() => this.configured = true); } setDefaultRemoteConfig(options) { return promisify('setDefaultRemoteConfig')(options); } - fetchWithExpiration(expirationSeconds=defaultExpiration) { + fetchWithExpiration(expirationSeconds = defaultExpiration) { return promisify('fetchWithExpiration')(expirationSeconds) } @@ -26,5 +27,3 @@ export class RemoteConfig { return promisify('setDev')(); } } - -export default RemoteConfig; \ No newline at end of file diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 6e5f46a..c5fc6c2 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -47,7 +47,7 @@ class StorageRef extends ReferenceBase { } } -export class Storage extends Base { +export default class Storage extends Base { constructor(firestack, options={}) { super(firestack, options); @@ -131,4 +131,3 @@ export class Storage extends Base { } } -export default Storage diff --git a/package.json b/package.json index ae8a168..f209700 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,7 @@ "firebase" ], "peerDependencies": { - "react": "*", - "react-native": "*" + "react": "*" }, "rnpm": { "commands": { @@ -54,6 +53,7 @@ "devDependencies": { "babel-jest": "^14.1.0", "babel-preset-react-native": "^1.9.0", + "cpx": "^1.5.0", "debug": "^2.2.0", "enzyme": "^2.4.1", "jest": "^14.1.0", @@ -68,7 +68,6 @@ }, "dependencies": { "bows": "^1.6.0", - "cpx": "^1.5.0", "es6-symbol": "^3.1.0" } } From 378fd7db4a180fb10032c2e81ad28ba9fb2aaa9a Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:32:49 +0000 Subject: [PATCH 033/275] fixed watchcpx script - now correctly copies node modules at start, woops. --- bin/watchCopy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/watchCopy.js b/bin/watchCopy.js index 7e025f4..9518a85 100644 --- a/bin/watchCopy.js +++ b/bin/watchCopy.js @@ -26,7 +26,7 @@ rl.question(`Watch for changes in '${PROJECT_DIR}' and copy to '${TARGET_DIR}'? if (answer.toLowerCase() === 'y') { // flat copy node_modules as we're not watching it console.log('Copying node_modules directory...'); - copy(PROJECT_DIR + '/node_modules/**/*.*', TARGET_DIR.replace(`/${packageJson.name}`), { clean: true }, () => { + copy(PROJECT_DIR + '/node_modules/**/*.*', TARGET_DIR + '/node_modules', { clean: true }, () => { console.log('Copy complete.'); console.log('Watching for changes in project directory... (excludes node_modules)'); const watcher = watch(PROJECT_DIR + '/{ios,android,lib}/**/*.*', TARGET_DIR, { verbose: true }); From 0265dddc9e9d3b3c79c0e8798b5c5e8d22189a28 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:42:21 +0000 Subject: [PATCH 034/275] misc copy script --- bin/watchCopy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/watchCopy.js b/bin/watchCopy.js index 9518a85..b7e9a58 100644 --- a/bin/watchCopy.js +++ b/bin/watchCopy.js @@ -14,7 +14,7 @@ let TARGET_DIR = process.env.TARGET_DIR; if (!TARGET_DIR) { console.error('Missing TARGET_DIR process env, aborting!'); - console.error('EXAMPLE USAGE: TARGET_DIR=/Users/YOU/Documents/someproject npm run watchcpx'); + console.error('EXAMPLE USAGE: TARGET_DIR=/Users/YOU/Documents/SomeReactApp npm run watchcpx'); process.exit(1); } From cb1cd842d90b0ac3e5b75b7387d19e4c2229327a Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:43:25 +0000 Subject: [PATCH 035/275] removed unused imports in FirestackAuth.java --- .../src/main/java/io/fullstack/firestack/FirestackAuth.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 9e945fc..a660107 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -8,7 +8,6 @@ import android.net.Uri; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; @@ -17,15 +16,12 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableNativeMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.ReactContext; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; import com.google.firebase.auth.AuthCredential; import com.google.firebase.auth.AuthResult; From 5ef924aef6dca50816e3500e47d4f3b1294123bd Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:45:31 +0000 Subject: [PATCH 036/275] removed unused context/this.context --- .../src/main/java/io/fullstack/firestack/FirestackAuth.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index a660107..8593938 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -39,7 +39,7 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAuth"; - private Context context; + // private Context context; private ReactContext mReactContext; private FirebaseAuth mAuth; private FirebaseApp app; @@ -48,7 +48,7 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { public FirestackAuthModule(ReactApplicationContext reactContext) { super(reactContext); - this.context = reactContext; + // this.context = reactContext; mReactContext = reactContext; Log.d(TAG, "New FirestackAuth instance"); From 5d6a218987382e7f9e8e8f8cb372e3505deb4841 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:47:26 +0000 Subject: [PATCH 037/275] only listenForAuth if we're not already listening --- .../io/fullstack/firestack/FirestackAuth.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 8593938..0b3feb5 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -61,29 +61,30 @@ public String getName() { @ReactMethod public void listenForAuth() { - mAuthListener = new FirebaseAuth.AuthStateListener() { - - @Override - public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { - WritableMap msgMap = Arguments.createMap(); - msgMap.putString("eventName", "listenForAuth"); + if (mAuthListener == null) { + mAuthListener = new FirebaseAuth.AuthStateListener() { + @Override + public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + WritableMap msgMap = Arguments.createMap(); + msgMap.putString("eventName", "listenForAuth"); - if (FirestackAuthModule.this.user != null) { - WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + WritableMap userMap = getUserMap(); - msgMap.putBoolean("authenticated", true); - msgMap.putMap("user", userMap); + msgMap.putBoolean("authenticated", true); + msgMap.putMap("user", userMap); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } else { - msgMap.putBoolean("authenticated", false); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } else { + msgMap.putBoolean("authenticated", false); + FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + } } - } - }; + }; - mAuth = FirebaseAuth.getInstance(); - mAuth.addAuthStateListener(mAuthListener); + mAuth = FirebaseAuth.getInstance(); + mAuth.addAuthStateListener(mAuthListener); + } } @ReactMethod From 4df5f2e3aed1421161139111893e9b538808c228 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:51:56 +0000 Subject: [PATCH 038/275] fix double callback issue on signInWithEmail --- .../java/io/fullstack/firestack/FirestackAuth.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 0b3feb5..d05426e 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -61,7 +61,7 @@ public String getName() { @ReactMethod public void listenForAuth() { - if (mAuthListener == null) { + if (mAuthListener == null || mAuth == null) { mAuthListener = new FirebaseAuth.AuthStateListener() { @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { @@ -69,8 +69,8 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { msgMap.putString("eventName", "listenForAuth"); if (FirestackAuthModule.this.user != null) { + // TODO move to helper WritableMap userMap = getUserMap(); - msgMap.putBoolean("authenticated", true); msgMap.putMap("user", userMap); @@ -92,6 +92,7 @@ public void unlistenForAuth(final Callback callback) { if (mAuthListener != null) { mAuth.removeAuthStateListener(mAuthListener); + // TODO move to helper WritableMap resp = Arguments.createMap(); resp.putString("status", "complete"); @@ -140,12 +141,6 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } }); } From e9b44bbcfa091b2b40cf20141a6b8f485a37ff3d Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:52:55 +0000 Subject: [PATCH 039/275] fix double callback issue on signInWithCustomToken --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index d05426e..6c9c369 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -201,12 +201,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } @ReactMethod From 7a7d44c1296caa106e1d0acbf46c12a33814ceba Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:53:27 +0000 Subject: [PATCH 040/275] fix double callback issue on updateUserEmail --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 6c9c369..52b6925 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -233,12 +233,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); From 32ed7fda38339c384a11fc058e1c887deff58e8a Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:53:49 +0000 Subject: [PATCH 041/275] fix double callback issue on updateUserPassword --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 52b6925..d975e33 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -264,12 +264,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); From 27a5a33e6ecc31806bb579b44bc7b564ef17f7cf Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:54:53 +0000 Subject: [PATCH 042/275] fix double callback issue on sendPasswordResetWithEmail --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index d975e33..45e60f2 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -293,12 +293,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } @ReactMethod From 39a58c77cfe7867eae4fce0b5bcc93f5ca531fb2 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:55:17 +0000 Subject: [PATCH 043/275] fix double callback issue on deleteUser --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 45e60f2..4b68aa1 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -319,12 +319,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } else { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); From 3275bd621682fdb3e3ddcf80391f8327d7e4d395 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:55:58 +0000 Subject: [PATCH 044/275] fix double callback issue on sendEmailVerification --- .../src/main/java/io/fullstack/firestack/FirestackAuth.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 4b68aa1..63bce9a 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -354,12 +354,6 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - //userExceptionCallback(ex, callback); - } }); } From 311df63d5d57e99dfb2524d466edb05547629002 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:56:21 +0000 Subject: [PATCH 045/275] fix double callback issue on getToken --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 63bce9a..6ca1862 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -384,12 +384,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } @ReactMethod From 4209f571078c03ee1e76cf23a832682852dafc40 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:57:37 +0000 Subject: [PATCH 046/275] fix double callback issue on updateUserProfile --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 6ca1862..6fdbbb2 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -425,12 +425,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } @ReactMethod From e03569de9cb6b707099cbf6cf6c75ba71e6ced6d Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:57:59 +0000 Subject: [PATCH 047/275] fix double callback issue on googleLogin --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 6fdbbb2..a1a968a 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -473,12 +473,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } @ReactMethod From e914f44ff851602ccb4226f9ad3deac909dc2f9d Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 17:58:21 +0000 Subject: [PATCH 048/275] fix double callback issue on facebookLogin --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index a1a968a..31426c8 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -496,12 +496,7 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } // Internal helpers From b4c56c6bc1f0450a679d43c45c389e16063c4c65 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 18:09:52 +0000 Subject: [PATCH 049/275] added callbackNoUser helper and refactored functions to use it --- .../io/fullstack/firestack/FirestackAuth.java | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 31426c8..4fe7441 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -1,7 +1,6 @@ package io.fullstack.firestack; -import android.content.Context; import android.util.Log; import java.util.Map; @@ -59,6 +58,23 @@ public String getName() { return TAG; } + /** + * Returns a no user error. + * + * @param callback JS callback + */ + public void callbackNoUser(Callback callback, Boolean isError) { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", NO_CURRENT_USER); + err.putString("errorMessage", "No current user"); + + if (isError) { + callback.invoke(err); + } else { + callback.invoke(null, err); + } + } + @ReactMethod public void listenForAuth() { if (mAuthListener == null || mAuth == null) { @@ -217,7 +233,8 @@ public void updateUserEmail(final String email, final Callback callback) { FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); if (user != null) { - user.updateEmail(email) + user + .updateEmail(email) .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { @@ -235,10 +252,7 @@ public void onComplete(@NonNull Task task) { } }); } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); + callbackNoUser(callback, true); } } @@ -266,10 +280,7 @@ public void onComplete(@NonNull Task task) { } }); } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); + callbackNoUser(callback, true); } } @@ -299,7 +310,6 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void deleteUser(final Callback callback) { FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { user.delete() .addOnCompleteListener(new OnCompleteListener() { @@ -321,10 +331,7 @@ public void onComplete(@NonNull Task task) { } }); } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", NO_CURRENT_USER); - err.putString("errorMessage", "No current user"); - callback.invoke(err); + callbackNoUser(callback, true); } } @@ -445,7 +452,7 @@ public void getCurrentUser(final Callback callback) { this.user = mAuth.getCurrentUser(); if (this.user == null) { - noUserCallback(callback); + callbackNoUser(callback, false); } else { Log.d("USRC", this.user.getUid()); userCallback(this.user, callback); @@ -513,6 +520,7 @@ public void userCallback(FirebaseUser passedUser, final Callback callback) { this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { + // TODO - no task is successful check... WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); if (FirestackAuthModule.this.user != null) { @@ -548,6 +556,8 @@ public void anonymousUserCallback(FirebaseUser passedUser, final Callback callba .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { + // TODO - no task is successful check... + WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); @@ -570,17 +580,6 @@ public void onFailure(@NonNull Exception ex) { }); } - - public void noUserCallback(final Callback callback) { - WritableMap message = Arguments.createMap(); - - message.putString("errorMessage", "no_user"); - message.putString("eventName", "no_user"); - message.putBoolean("authenticated", false); - - callback.invoke(null, message); - } - public void userErrorCallback(Task task, final Callback onFail) { WritableMap error = Arguments.createMap(); error.putInt("errorCode", task.getException().hashCode()); From ca4c8e718432d9d00956097c14d407ca2f7dc484 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 18:15:25 +0000 Subject: [PATCH 050/275] fixed several instances where 'user' was not being checked for - would of thrown null exceptions when no user --- .../io/fullstack/firestack/FirestackAuth.java | 240 ++++++++++-------- 1 file changed, 128 insertions(+), 112 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 4fe7441..c4a1f30 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -340,28 +340,31 @@ public void onComplete(@NonNull Task task) { public void sendEmailVerification(final Callback callback) { FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - // TODO check user exists - user.sendEmailVerification() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("msg", "User verification email sent"); - callback.invoke(null, resp); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL); - err.putString("errorMessage", task.getException().getMessage()); - callback.invoke(err); + if (user != null) { + user.sendEmailVerification() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User verification email sent"); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_SENDING_VERIFICATION_EMAIL); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - } catch (Exception ex) { - userExceptionCallback(ex, callback); } - } - }); + }); + } else { + callbackNoUser(callback, true); + } } @@ -369,70 +372,76 @@ public void onComplete(@NonNull Task task) { public void getToken(final Callback callback) { FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - // TODO check user exists - user.getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - String token = task.getResult().getToken(); - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - resp.putString("token", token); - callback.invoke(null, resp); - } else { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", ERROR_FETCHING_TOKEN); - err.putString("errorMessage", task.getException().getMessage()); - callback.invoke(err); + if (user != null) { + user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + String token = task.getResult().getToken(); + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("token", token); + callback.invoke(null, resp); + } else { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", ERROR_FETCHING_TOKEN); + err.putString("errorMessage", task.getException().getMessage()); + callback.invoke(err); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - } catch (Exception ex) { - userExceptionCallback(ex, callback); } - } - }); + }); + } else { + callbackNoUser(callback, true); + } } @ReactMethod public void updateUserProfile(ReadableMap props, final Callback callback) { FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); + if (user != null) { + UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); - if (m.containsKey("displayName")) { - String displayName = (String) m.get("displayName"); - profileBuilder.setDisplayName(displayName); - } + if (m.containsKey("displayName")) { + String displayName = (String) m.get("displayName"); + profileBuilder.setDisplayName(displayName); + } - if (m.containsKey("photoUri")) { - String photoUriStr = (String) m.get("photoUri"); - Uri uri = Uri.parse(photoUriStr); - profileBuilder.setPhotoUri(uri); - } + if (m.containsKey("photoUri")) { + String photoUriStr = (String) m.get("photoUri"); + Uri uri = Uri.parse(photoUriStr); + profileBuilder.setPhotoUri(uri); + } - UserProfileChangeRequest profileUpdates = profileBuilder.build(); + UserProfileChangeRequest profileUpdates = profileBuilder.build(); - // TODO check user exists - user.updateProfile(profileUpdates) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - Log.d(TAG, "User profile updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - userErrorCallback(task, callback); + user.updateProfile(profileUpdates) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User profile updated"); + FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); + userCallback(u, callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - } catch (Exception ex) { - userExceptionCallback(ex, callback); } - } - }); + }); + } else { + callbackNoUser(callback, true); + } } @ReactMethod @@ -516,30 +525,33 @@ public void userCallback(FirebaseUser passedUser, final Callback callback) { this.user = passedUser; } - // TODO check user exists - this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - // TODO - no task is successful check... - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", false); - } + if (this.user != null) { + this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + // TODO - no task is successful check... + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); - msgMap.putMap("user", userMap); + userMap.putString("token", token); + userMap.putBoolean("anonymous", false); + } - callback.invoke(null, msgMap); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + msgMap.putMap("user", userMap); + + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } else { + callbackNoUser(callback, true); + } } // TODO: Reduce to one method @@ -552,32 +564,36 @@ public void anonymousUserCallback(FirebaseUser passedUser, final Callback callba this.user = passedUser; } - this.user.getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - // TODO - no task is successful check... + if (this.user != null) { + this.user.getToken(true) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + // TODO - no task is successful check... - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - } + userMap.putString("token", token); + userMap.putBoolean("anonymous", true); + } - msgMap.putMap("user", userMap); + msgMap.putMap("user", userMap); - callback.invoke(null, msgMap); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + callback.invoke(null, msgMap); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); + } else { + callbackNoUser(callback, true); + } } public void userErrorCallback(Task task, final Callback onFail) { From 34faf48dc2eeb13ff5d2ef33636b8cca80216c96 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 18:17:49 +0000 Subject: [PATCH 051/275] misc --- android/src/main/java/io/fullstack/firestack/FirestackAuth.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index c4a1f30..173e864 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -630,7 +630,7 @@ private WritableMap getUserMap() { userMap.putString("email", email); userMap.putString("uid", uid); userMap.putString("providerId", provider); - userMap.putBoolean("emailVerified", verified || false); + userMap.putBoolean("emailVerified", verified); if (name != null) { From 40c7f56253cd3ab25751602148ff0262a829e3fb Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 18:22:02 +0000 Subject: [PATCH 052/275] suppress throwable warnings --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 173e864..791bb32 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -31,6 +31,7 @@ import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; +@SuppressWarnings("ThrowableResultOfMethodCallIgnored") class FirestackAuthModule extends ReactContextBaseJavaModule { private final int NO_CURRENT_USER = 100; private final int ERROR_FETCHING_TOKEN = 101; @@ -63,7 +64,7 @@ public String getName() { * * @param callback JS callback */ - public void callbackNoUser(Callback callback, Boolean isError) { + private void callbackNoUser(Callback callback, Boolean isError) { WritableMap err = Arguments.createMap(); err.putInt("errorCode", NO_CURRENT_USER); err.putString("errorMessage", "No current user"); @@ -596,7 +597,7 @@ public void onFailure(@NonNull Exception ex) { } } - public void userErrorCallback(Task task, final Callback onFail) { + private void userErrorCallback(Task task, final Callback onFail) { WritableMap error = Arguments.createMap(); error.putInt("errorCode", task.getException().hashCode()); error.putString("errorMessage", task.getException().getMessage()); @@ -605,7 +606,7 @@ public void userErrorCallback(Task task, final Callback onFail) { onFail.invoke(error); } - public void userExceptionCallback(Exception ex, final Callback onFail) { + private void userExceptionCallback(Exception ex, final Callback onFail) { WritableMap error = Arguments.createMap(); error.putInt("errorCode", ex.hashCode()); error.putString("errorMessage", ex.getMessage()); From bd8d4b222a27d1bb0aa0823cc3150d2880d469b4 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 15 Nov 2016 18:26:37 +0000 Subject: [PATCH 053/275] make internal class methods private --- .../src/main/java/io/fullstack/firestack/FirestackAuth.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 791bb32..aed1b3d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -517,7 +517,7 @@ public void onComplete(@NonNull Task task) { } // Internal helpers - public void userCallback(FirebaseUser passedUser, final Callback callback) { + private void userCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { mAuth = FirebaseAuth.getInstance(); @@ -556,7 +556,7 @@ public void onFailure(@NonNull Exception ex) { } // TODO: Reduce to one method - public void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { + private void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { mAuth = FirebaseAuth.getInstance(); From 7442e89d0d7d45533c2ff33ec8ab03258b45efa5 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Tue, 15 Nov 2016 20:53:32 +0000 Subject: [PATCH 054/275] Update authentication.md --- docs/api/authentication.md | 43 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 443545d..0e1ecc6 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -6,12 +6,12 @@ Firestack handles authentication for us out of the box, both with email/password ## Local Auth -#### [listenForAuth()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) +#### [onAuthStateChanged()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) Listen for changes in the users auth state (logging in and out). ```javascript -firestack.auth.listenForAuth((evt) => { +firestack.auth().onAuthStateChanged((evt) => { // evt is the authentication event, it contains an `error` key for carrying the // error message in case of an error and a `user` key upon successful authentication if (!evt.authenticated) { @@ -25,22 +25,22 @@ firestack.auth.listenForAuth((evt) => { .then(() => console.log('Listening for authentication changes')) ``` -#### unlistenForAuth() +#### offAuthStateChanged() -Remove the `listenForAuth` listener. +Remove the `onAuthStateChanged` listener. This is important to release resources from our app when we don't need to hold on to the listener any longer. ```javascript -firestack.auth.unlistenForAuth() +firestack.auth().offAuthStateChanged() ``` -#### [createUserWithEmail()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) +#### [createUserWithEmailAndPassword()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) -We can create a user by calling the `createUserWithEmail()` function. +We can create a user by calling the `createUserWithEmailAndPassword()` function. The method accepts two parameters, an email and a password. ```javascript -firestack.auth.createUserWithEmail('ari@fullstack.io', '123456') +firestack.auth().createUserWithEmailAndPassword('ari@fullstack.io', '123456') .then((user) => { console.log('user created', user) }) @@ -49,13 +49,13 @@ firestack.auth.createUserWithEmail('ari@fullstack.io', '123456') }) ``` -#### [signInWithEmail()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) +#### [signInWithEmailAndPassword()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) -To sign a user in with their email and password, use the `signInWithEmail()` function. +To sign a user in with their email and password, use the `signInWithEmailAndPassword()` function. It accepts two parameters, the user's email and password: ```javascript -firestack.auth.signInWithEmail('ari@fullstack.io', '123456') +firestack.auth().signInWithEmailAndPassword('ari@fullstack.io', '123456') .then((user) => { console.log('User successfully logged in', user) }) @@ -69,13 +69,14 @@ firestack.auth.signInWithEmail('ari@fullstack.io', '123456') Sign an anonymous user. If the user has already signed in, that user will be returned. ```javascript -firestack.auth.signInAnonymously() +firestack.auth().signInAnonymously() .then((user) => { console.log('Anonymous user successfully logged in', user) }) .catch((err) => { console.error('Anonymous user signin error', err); }) +``` #### signInWithProvider() @@ -88,7 +89,7 @@ We can use an external authentication provider, such as twitter/facebook for aut To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: ```javascript -firestack.auth.signInWithCustomToken(TOKEN) +firestack.auth().signInWithCustomToken(TOKEN) .then((user) => { console.log('User successfully logged in', user) }) @@ -103,7 +104,7 @@ We can update the current user's email by using the command: `updateUserEmail()` It accepts a single argument: the user's new email: ```javascript -firestack.auth.updateUserEmail('ari+rocks@fullstack.io') +firestack.auth().updateUserEmail('ari+rocks@fullstack.io') .then((res) => console.log('Updated user email')) .catch(err => console.error('There was an error updating user email')) ``` @@ -114,7 +115,7 @@ We can update the current user's password using the `updateUserPassword()` metho It accepts a single parameter: the new password for the current user ```javascript -firestack.auth.updateUserPassword('somethingReallyS3cr3t733t') +firestack.auth().updateUserPassword('somethingReallyS3cr3t733t') .then(res => console.log('Updated user password')) .catch(err => console.error('There was an error updating your password')) ``` @@ -128,7 +129,7 @@ It accepts a single parameter: Possible keys are listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile). ```javascript -firestack.auth +firestack.auth() .updateUserProfile({ displayName: 'Ari Lerner' }) @@ -142,7 +143,7 @@ To send a password reset for a user based upon their email, we can call the `sen It accepts a single parameter: the email of the user to send a reset email. ```javascript -firestack.auth.sendPasswordResetWithEmail('ari+rocks@fullstack.io') +firestack.auth().sendPasswordResetWithEmail('ari+rocks@fullstack.io') .then(res => console.log('Check your inbox for further instructions')) .catch(err => console.error('There was an error :(')) ``` @@ -152,7 +153,7 @@ It's possible to delete a user completely from your account on Firebase. Calling the `deleteUser()` method will take care of this for you. ```javascript -firestack.auth +firestack.auth() .deleteUser() .then(res => console.log('Sad to see you go')) .catch(err => console.error('There was an error - Now you are trapped!')) @@ -163,7 +164,7 @@ firestack.auth If you want user's token, use `getToken()` method. ```javascript -firestack.auth +firestack.auth() .getToken() .then(res => console.log(res.token)) .catch(err => console.error('error')) @@ -175,7 +176,7 @@ To sign the current user out, use the `signOut()` method. It accepts no parameters ```javascript -firestack.auth +firestack.auth() .signOut() .then(res => console.log('You have been signed out')) .catch(err => console.error('Uh oh... something weird happened')) @@ -188,7 +189,7 @@ Although you _can_ get the current user using the `getCurrentUser()` method, it' However, if you need to get the current user, call the `getCurrentUser()` method: ```javascript -firestack.auth +firestack.auth() .getCurrentUser() .then(user => console.log('The currently logged in user', user)) .catch(err => console.error('An error occurred')) From 6bd76c5fd7ef01c94a8c07a1b942e9418a013bf3 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 09:50:23 +0000 Subject: [PATCH 055/275] added missing tryCatch and task successful checks on userCallback - app will crash on no network activity or disabled user. --- .../io/fullstack/firestack/FirestackAuth.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index aed1b3d..69d28a5 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -530,19 +530,23 @@ private void userCallback(FirebaseUser passedUser, final Callback callback) { this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - // TODO - no task is successful check... - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", false); + try { + if (task.isSuccessful()) { + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); + userMap.putString("token", token); + userMap.putBoolean("anonymous", false); + } + msgMap.putMap("user", userMap); + callback.invoke(null, msgMap); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); } - - msgMap.putMap("user", userMap); - - callback.invoke(null, msgMap); } }).addOnFailureListener(new OnFailureListener() { @Override From b216385b2c5f3d281b1afe282f52f23c7ad064eb Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 09:52:04 +0000 Subject: [PATCH 056/275] remove redundant if user check --- .../main/java/io/fullstack/firestack/FirestackAuth.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 69d28a5..8807897 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -534,11 +534,9 @@ public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - userMap.putString("token", token); - userMap.putBoolean("anonymous", false); - } + final String token = task.getResult().getToken(); + userMap.putString("token", token); + userMap.putBoolean("anonymous", false); msgMap.putMap("user", userMap); callback.invoke(null, msgMap); } else { From 7ee0d5523888374f756bb5af39b284da7db893a0 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 09:54:43 +0000 Subject: [PATCH 057/275] remove redundant if user check - another one --- .../io/fullstack/firestack/FirestackAuth.java | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 8807897..2399bd8 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -576,24 +576,14 @@ public void onComplete(@NonNull Task task) { WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); - - if (FirestackAuthModule.this.user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - } - + final String token = task.getResult().getToken(); + userMap.putString("token", token); + userMap.putBoolean("anonymous", true); msgMap.putMap("user", userMap); callback.invoke(null, msgMap); } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } - }); + }); } else { callbackNoUser(callback, true); } From 8a486eb0d54f38eff92907058f67c6358aca1262 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 10:02:54 +0000 Subject: [PATCH 058/275] added missing tryCatch and task successful checks on anonymousUserCallback - app will crash on no network activity or disabled user. --- .../io/fullstack/firestack/FirestackAuth.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 2399bd8..963559d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -535,6 +535,7 @@ public void onComplete(@NonNull Task task) { WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); final String token = task.getResult().getToken(); + // todo clean this up - standardise it userMap.putString("token", token); userMap.putBoolean("anonymous", false); msgMap.putMap("user", userMap); @@ -568,20 +569,27 @@ private void anonymousUserCallback(FirebaseUser passedUser, final Callback callb } if (this.user != null) { - this.user.getToken(true) + this.user + .getToken(true) .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - // TODO - no task is successful check... - - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - final String token = task.getResult().getToken(); - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - msgMap.putMap("user", userMap); - - callback.invoke(null, msgMap); + try { + if (task.isSuccessful()) { + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); + final String token = task.getResult().getToken(); + // todo clean this up - standardise it + userMap.putString("token", token); + userMap.putBoolean("anonymous", true); + msgMap.putMap("user", userMap); + callback.invoke(null, msgMap); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } } }); } else { From b0d079dc1f674bc9dd2167ada8322714cdfac6eb Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 11:49:42 +0000 Subject: [PATCH 059/275] removed unused imports --- .../fullstack/firestack/FirestackModule.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java index ac2418f..6baf627 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -1,9 +1,8 @@ package io.fullstack.firestack; -import android.content.Context; -import android.util.Log; import java.util.Map; -import android.support.annotation.NonNull; +import android.util.Log; +import android.content.Context; import android.support.annotation.Nullable; import com.facebook.react.bridge.Arguments; @@ -14,12 +13,8 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.ReactContext; -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.OnFailureListener; -import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.database.ServerValue; @@ -76,43 +71,43 @@ public String setKeyOrDefault( } }; - String val = fn.setKeyOrDefault("applicationId", + String val = fn.setKeyOrDefault("applicationId", defaultOptions.getApplicationId()); if (val != null) { builder.setApplicationId(val); } - val = fn.setKeyOrDefault("apiKey", + val = fn.setKeyOrDefault("apiKey", defaultOptions.getApiKey()); if (val != null) { builder.setApiKey(val); } - val = fn.setKeyOrDefault("gcmSenderID", + val = fn.setKeyOrDefault("gcmSenderID", defaultOptions.getGcmSenderId()); if (val != null) { builder.setGcmSenderId(val); } - val = fn.setKeyOrDefault("storageBucket", + val = fn.setKeyOrDefault("storageBucket", defaultOptions.getStorageBucket()); if (val != null) { builder.setStorageBucket(val); } - val = fn.setKeyOrDefault("databaseURL", + val = fn.setKeyOrDefault("databaseURL", defaultOptions.getDatabaseUrl()); if (val != null) { builder.setDatabaseUrl(val); } - val = fn.setKeyOrDefault("databaseUrl", + val = fn.setKeyOrDefault("databaseUrl", defaultOptions.getDatabaseUrl()); if (val != null) { builder.setDatabaseUrl(val); } - val = fn.setKeyOrDefault("clientId", + val = fn.setKeyOrDefault("clientId", defaultOptions.getApplicationId()); if (val != null) { builder.setApplicationId(val); @@ -208,4 +203,4 @@ public void onHostPause() { public void onHostDestroy() { } -} \ No newline at end of file +} From 0cba18c4229effb7cb8373fd63ed2efd641eb50b Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 11:54:29 +0000 Subject: [PATCH 060/275] cleanup FirestackDatabase imports --- .../firestack/FirestackDatabase.java | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 45505c7..4cdf613 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -1,34 +1,32 @@ package io.fullstack.firestack; -import android.content.Context; -import android.text.TextUtils; +import java.util.Map; +import java.util.List; +import android.net.Uri; import android.util.Log; import java.util.HashMap; -import java.util.List; +import android.content.Context; import java.util.ListIterator; -import java.util.Map; -import android.net.Uri; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.google.firebase.database.Query; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.OnDisconnect; +import com.google.firebase.database.DatabaseError; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.ChildEventListener; -import com.google.firebase.database.OnDisconnect; -import com.google.firebase.database.Query; import com.google.firebase.database.ValueEventListener; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.DatabaseError; class FirestackDBReference { private static final String TAG = "FirestackDBReference"; From dc42106127bd4e5592d2a892daa8c04386c45b9e Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 11:56:53 +0000 Subject: [PATCH 061/275] remove unused imports in Analytics.java --- .../firestack/FirestackAnalytics.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index ec67022..47cbfc7 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -1,29 +1,19 @@ package io.fullstack.firestack; -import android.content.Context; +import java.util.Map; import android.util.Log; import android.os.Bundle; import java.util.Iterator; -import java.util.Map; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.content.Context; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.OnFailureListener; -import com.google.android.gms.tasks.Task; -import com.google.firebase.FirebaseApp; import com.google.firebase.analytics.FirebaseAnalytics; -import com.google.firebase.analytics.FirebaseAnalytics.Event.*; -import com.google.firebase.analytics.FirebaseAnalytics.Param; class FirestackAnalyticsModule extends ReactContextBaseJavaModule { @@ -60,8 +50,8 @@ public void logEventWithName(final String name, final ReadableMap props, final C private String getEventName(final String name) { if (name == FirebaseAnalytics.Event.ADD_PAYMENT_INFO) {return FirebaseAnalytics.Event.ADD_PAYMENT_INFO; } - else if (name == FirebaseAnalytics.Event.ADD_TO_CART) {return FirebaseAnalytics.Event.ADD_TO_CART;} - else if (name == FirebaseAnalytics.Event.ADD_TO_WISHLIST) {return FirebaseAnalytics.Event.ADD_TO_WISHLIST;} + else if (name == FirebaseAnalytics.Event.ADD_TO_CART) {return FirebaseAnalytics.Event.ADD_TO_CART;} + else if (name == FirebaseAnalytics.Event.ADD_TO_WISHLIST) {return FirebaseAnalytics.Event.ADD_TO_WISHLIST;} else if (name == FirebaseAnalytics.Event.APP_OPEN) {return FirebaseAnalytics.Event.APP_OPEN;} else if (name == FirebaseAnalytics.Event.BEGIN_CHECKOUT) {return FirebaseAnalytics.Event.BEGIN_CHECKOUT;} else if (name == FirebaseAnalytics.Event.ECOMMERCE_PURCHASE) {return FirebaseAnalytics.Event.ECOMMERCE_PURCHASE;} From 3bc125dfd4e7415ed2694c1ba75a1342a9d28492 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 12:00:45 +0000 Subject: [PATCH 062/275] remove unnecessary mReactContext assignment --- .../main/java/io/fullstack/firestack/FirestackAnalytics.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index 47cbfc7..f030a48 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -20,13 +20,11 @@ class FirestackAnalyticsModule extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAnalytics"; private Context context; - private ReactContext mReactContext; private FirebaseAnalytics mFirebaseAnalytics; public FirestackAnalyticsModule(ReactApplicationContext reactContext) { super(reactContext); this.context = reactContext; - mReactContext = reactContext; Log.d(TAG, "New instance"); mFirebaseAnalytics = FirebaseAnalytics.getInstance(this.context); From ab593656b0ddb8b2b459d8c3536cc095b8e2c413 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 12:03:30 +0000 Subject: [PATCH 063/275] switch to forEach --- .../main/java/io/fullstack/firestack/FirestackAnalytics.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index f030a48..3b57af4 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -202,9 +202,7 @@ private Bundle makeEventBundle(final String name, final Map map) bundle.putString(FirebaseAnalytics.Param.FLIGHT_NUMBER, val); } - Iterator> entries = map.entrySet().iterator(); - while (entries.hasNext()) { - Map.Entry entry = entries.next(); + for (Map.Entry entry : map.entrySet()) { if (bundle.getBundle(entry.getKey()) == null) { bundle.putString(entry.getKey(), entry.getValue().toString()); } From 8ab33a9eef6b991c29287db258e611430182d4a5 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 12:28:38 +0000 Subject: [PATCH 064/275] added eslint --- .eslintrc | 40 ++++++++++++++++++++++++++++++++++++++++ package.json | 7 +++++++ 2 files changed, 47 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..c9d71b9 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,40 @@ +{ + "extends": "airbnb", + "parser": "babel-eslint", + "ecmaFeatures": { + "jsx": true + }, + "plugins": [ + "flowtype" + ], + "env": { + "es6": true, + "jasmine": true + }, + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + }, + "rules": { + "class-methods-use-this": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "arrow-body-style": 0, + "import/prefer-default-export": 0, + "radix": 0, + "new-cap": 0, + "max-len": 0, + "no-continue": 0, + "no-console": 0, + "global-require": 0, + "import/extensions": 0, + "import/no-unresolved": 0, + "import/no-extraneous-dependencies": 0, + "react/jsx-filename-extension": 0 + }, + "globals": { + "__DEV__": true, + "window": true + } +} diff --git a/package.json b/package.json index f209700..96e0a25 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,13 @@ } }, "devDependencies": { + "babel-eslint": "^7.0.0", + "eslint": "^3.8.1", + "eslint-config-airbnb": "^12.0.0", + "eslint-plugin-flowtype": "^2.20.0", + "eslint-plugin-import": "^2.0.1", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.4.1", "babel-jest": "^14.1.0", "babel-preset-react-native": "^1.9.0", "cpx": "^1.5.0", From 69646e381bc7fcaa97f9d55b06de514728634548 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 13:00:41 +0000 Subject: [PATCH 065/275] added rn peer dep --- package.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 96e0a25..005fd6e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "firebase" ], "peerDependencies": { - "react": "*" + "react": "*", + "react-native": "*" }, "rnpm": { "commands": { @@ -52,17 +53,18 @@ }, "devDependencies": { "babel-eslint": "^7.0.0", + "babel-jest": "^14.1.0", + "babel-preset-react-native": "^1.9.0", + "cpx": "^1.5.0", + "debug": "^2.2.0", + "enzyme": "^2.4.1", "eslint": "^3.8.1", "eslint-config-airbnb": "^12.0.0", "eslint-plugin-flowtype": "^2.20.0", "eslint-plugin-import": "^2.0.1", "eslint-plugin-jsx-a11y": "^2.2.3", "eslint-plugin-react": "^6.4.1", - "babel-jest": "^14.1.0", - "babel-preset-react-native": "^1.9.0", - "cpx": "^1.5.0", - "debug": "^2.2.0", - "enzyme": "^2.4.1", + "flow-bin": "^0.35.0", "jest": "^14.1.0", "jest-react-native": "^14.1.3", "mocha": "^3.0.2", From e5c3ea61e61faba549f30d80439c5c3732067d55 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 13:00:57 +0000 Subject: [PATCH 066/275] added flowconfig --- .flowconfig | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 .flowconfig diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..020e032 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,105 @@ +[ignore] + + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + + +# React Native problems +.*/node_modules/react-native/Libraries/Animated/src/AnimatedInterpolation.js +.*/node_modules/react-native/Libraries/Animated/src/Interpolation.js +.*/node_modules/react-native/Libraries/BugReporting/dumpReactTree.js +.*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js +.*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolater.js +.*/node_modules/react-native/Libraries/Experimental/WindowedListView.js +.*/node_modules/react-native/Libraries/Image/Image.io.js +.*/node_modules/react-native/Libraries/NavigationExperimental/NavigationExperimental.js +.*/node_modules/react-native/Libraries/NavigationExperimental/NavigationHeaderStyleInterpolator.js +.*/node_modules/react-native/Libraries/Network/FormData.js +.*/node_modules/react-native/Libraries/ReactIOS/YellowBox.js + + + +# Ignore react and fbjs where there are overlaps, but don't ignore +# anything that react-native relies on +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js + +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js + +# Ignore commoner tests +.*/node_modules/commoner/test/.* + +# See https://github.com/facebook/flow/issues/442 +.*/react-tools/node_modules/commoner/lib/reader.js + +# Ignore jest +.*/node_modules/jest-cli/.* + +# Ignore Website +.*/website/.* + +# Ignore generators +.*/local-cli/generator.* + +# Ignore BUCK generated folders +.*\.buckd/ + +.*/node_modules/is-my-json-valid/test/.*\.json +.*/node_modules/iconv-lite/encodings/tables/.*\.json +.*/node_modules/y18n/test/.*\.json +.*/node_modules/spdx-license-ids/spdx-license-ids.json +.*/node_modules/spdx-exceptions/index.json +.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json +.*/node_modules/resolve/lib/core.json +.*/node_modules/jsonparse/samplejson/.*\.json +.*/node_modules/json5/test/.*\.json +.*/node_modules/ua-parser-js/test/.*\.json +.*/node_modules/builtin-modules/builtin-modules.json +.*/node_modules/binary-extensions/binary-extensions.json +.*/node_modules/url-regex/tlds.json +.*/node_modules/joi/.*\.json +.*/node_modules/isemail/.*\.json +.*/node_modules/tr46/.*\.json +.*/node_modules/protobufjs/src/bower.json +.*/node_modules/grpc/node_modules/protobufjs/src/bower.json + +[include] +node_modules/fbjs/lib + +[libs] +js/flow-lib.js +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +node_modules/fbjs/flow/lib + +[options] +module.system=haste + +experimental.strict_type_args=true +unsafe.enable_getters_and_setters=true + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy From 627b545f57cc8ff33ee98105dea0142f0028be5b Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 14:15:31 +0000 Subject: [PATCH 067/275] implemented all firebase analytics module methods - android only --- .../firestack/FirestackAnalytics.java | 89 +++++++++++-------- lib/modules/analytics.js | 75 +++++++++++----- 2 files changed, 104 insertions(+), 60 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index 3b57af4..61af7bc 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -1,15 +1,14 @@ package io.fullstack.firestack; import java.util.Map; + +import android.app.Activity; import android.util.Log; import android.os.Bundle; -import java.util.Iterator; -import android.content.Context; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -19,13 +18,12 @@ class FirestackAnalyticsModule extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAnalytics"; - private Context context; + private ReactApplicationContext context; private FirebaseAnalytics mFirebaseAnalytics; public FirestackAnalyticsModule(ReactApplicationContext reactContext) { super(reactContext); - this.context = reactContext; - + context = reactContext; Log.d(TAG, "New instance"); mFirebaseAnalytics = FirebaseAnalytics.getInstance(this.context); } @@ -36,42 +34,56 @@ public String getName() { } @ReactMethod - public void logEventWithName(final String name, final ReadableMap props, final Callback callback) { - // TODO - // FirestackUtils.todoNote(TAG, "logEventWithName", callback); - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); - final String eventName = getEventName(name); + public void logEvent(final String name, final ReadableMap params) { + Map m = FirestackUtils.recursivelyDeconstructReadableMap(params); final Bundle bundle = makeEventBundle(name, m); - Log.d(TAG, "Logging event " + eventName); + Log.d(TAG, "Logging event " + name); mFirebaseAnalytics.logEvent(name, bundle); } - private String getEventName(final String name) { - if (name == FirebaseAnalytics.Event.ADD_PAYMENT_INFO) {return FirebaseAnalytics.Event.ADD_PAYMENT_INFO; } - else if (name == FirebaseAnalytics.Event.ADD_TO_CART) {return FirebaseAnalytics.Event.ADD_TO_CART;} - else if (name == FirebaseAnalytics.Event.ADD_TO_WISHLIST) {return FirebaseAnalytics.Event.ADD_TO_WISHLIST;} - else if (name == FirebaseAnalytics.Event.APP_OPEN) {return FirebaseAnalytics.Event.APP_OPEN;} - else if (name == FirebaseAnalytics.Event.BEGIN_CHECKOUT) {return FirebaseAnalytics.Event.BEGIN_CHECKOUT;} - else if (name == FirebaseAnalytics.Event.ECOMMERCE_PURCHASE) {return FirebaseAnalytics.Event.ECOMMERCE_PURCHASE;} - else if (name == FirebaseAnalytics.Event.GENERATE_LEAD) {return FirebaseAnalytics.Event.GENERATE_LEAD;} - else if (name == FirebaseAnalytics.Event.JOIN_GROUP) {return FirebaseAnalytics.Event.JOIN_GROUP;} - else if (name == FirebaseAnalytics.Event.LEVEL_UP) {return FirebaseAnalytics.Event.LEVEL_UP;} - else if (name == FirebaseAnalytics.Event.LOGIN) {return FirebaseAnalytics.Event.LOGIN;} - else if (name == FirebaseAnalytics.Event.POST_SCORE) {return FirebaseAnalytics.Event.POST_SCORE;} - else if (name == FirebaseAnalytics.Event.PRESENT_OFFER) {return FirebaseAnalytics.Event.PRESENT_OFFER;} - else if (name == FirebaseAnalytics.Event.PURCHASE_REFUND) {return FirebaseAnalytics.Event.PURCHASE_REFUND;} - else if (name == FirebaseAnalytics.Event.SEARCH) {return FirebaseAnalytics.Event.SEARCH;} - else if (name == FirebaseAnalytics.Event.SELECT_CONTENT) {return FirebaseAnalytics.Event.SELECT_CONTENT;} - else if (name == FirebaseAnalytics.Event.SHARE) {return FirebaseAnalytics.Event.SHARE;} - else if (name == FirebaseAnalytics.Event.SIGN_UP) {return FirebaseAnalytics.Event.SIGN_UP;} - else if (name == FirebaseAnalytics.Event.SPEND_VIRTUAL_CURRENCY) {return FirebaseAnalytics.Event.SPEND_VIRTUAL_CURRENCY;} - else if (name == FirebaseAnalytics.Event.TUTORIAL_BEGIN) {return FirebaseAnalytics.Event.TUTORIAL_BEGIN;} - else if (name == FirebaseAnalytics.Event.TUTORIAL_COMPLETE) {return FirebaseAnalytics.Event.TUTORIAL_COMPLETE;} - else if (name == FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT) {return FirebaseAnalytics.Event.UNLOCK_ACHIEVEMENT;} - else if (name == FirebaseAnalytics.Event.VIEW_ITEM) {return FirebaseAnalytics.Event.VIEW_ITEM;} - else if (name == FirebaseAnalytics.Event.VIEW_ITEM_LIST) {return FirebaseAnalytics.Event.VIEW_ITEM_LIST;} - else if (name == FirebaseAnalytics.Event.VIEW_SEARCH_RESULTS) {return FirebaseAnalytics.Event.VIEW_SEARCH_RESULTS;} - else return name; + /** + * + * @param enabled + */ + @ReactMethod + public void setAnalyticsCollectionEnabled(final Boolean enabled) { + mFirebaseAnalytics.setAnalyticsCollectionEnabled(enabled); + } + + @ReactMethod + public void setCurrentScreen(final String screenName, final String screenClassOverride) { + final Activity activity = getCurrentActivity(); + if (activity != null) { + Log.d(TAG, "setCurrentScreen " + screenName + " " + screenClassOverride); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + mFirebaseAnalytics.setCurrentScreen(activity, screenName, screenClassOverride); + } + }); + + } + } + + + @ReactMethod + public void setMinimumSessionDuration(final double milliseconds) { + mFirebaseAnalytics.setMinimumSessionDuration((long) milliseconds); + } + + @ReactMethod + public void setSessionTimeoutDuration(final double milliseconds) { + mFirebaseAnalytics.setSessionTimeoutDuration((long) milliseconds); + } + + @ReactMethod + public void setUserId(final String id) { + mFirebaseAnalytics.setUserId(id); + } + + @ReactMethod + public void setUserProperty(final String name, final String value) { + mFirebaseAnalytics.setUserProperty(name, value); } private Bundle makeEventBundle(final String name, final Map map) { @@ -207,6 +219,7 @@ private Bundle makeEventBundle(final String name, final Map map) bundle.putString(entry.getKey(), entry.getValue().toString()); } } + return bundle; } } diff --git a/lib/modules/analytics.js b/lib/modules/analytics.js index 44b712c..2b23d36 100644 --- a/lib/modules/analytics.js +++ b/lib/modules/analytics.js @@ -1,40 +1,71 @@ -import {NativeModules, NativeAppEventEmitter} from 'react-native'; -import promisify from '../utils/promisify' -import { Base } from './base' +// @flow +import { NativeModules } from 'react-native'; +import { Base } from './base'; const FirestackAnalytics = NativeModules.FirestackAnalytics; export default class Analytics extends Base { - constructor(firestack, options={}) { - super(firestack, options); + /** + * Logs an app event. + * @param {string} name + * @param params + * @return {Promise} + */ + logEvent(name: string, params: Object = {}): void { + return FirestackAnalytics.logEvent(name, params); + } - this._addToFirestackInstance( - 'logEventWithName' - ) + /** + * Sets whether analytics collection is enabled for this app on this device. + * @param enabled + */ + setAnalyticsCollectionEnabled(enabled: boolean): void { + return FirestackAnalytics.setAnalyticsCollectionEnabled(enabled); } + /** - * Log an event - * @param {string} name The name of the event - * @param {object} props An object containing string-keys - * @return {Promise} + * Sets the current screen name, which specifies the current visual context in your app. + * @param screenName + * @param screenClassOverride + */ + setCurrentScreen(screenName: string, screenClassOverride: string): void { + return FirestackAnalytics.setCurrentScreen(screenName, screenClassOverride); + } + + /** + * Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds). + * @param milliseconds */ - logEventWithName(name, props) { - return promisify('logEventWithName', FirestackAnalytics)(name, props); + setMinimumSessionDuration(milliseconds: number = 10000): void { + return FirestackAnalytics.setMinimumSessionDuration(milliseconds); } - enable() { - return promisify('setEnabled', FirestackAnalytics)(true); + /** + * Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes). + * @param milliseconds + */ + setSessionTimeoutDuration(milliseconds: number = 1800000): void { + return FirestackAnalytics.setSessionTimeoutDuration(milliseconds); } - disable() { - return promisify('setEnabled', FirestackAnalytics)(false); + /** + * Sets the user ID property. + * @param id + */ + setUserId(id: string): void { + return FirestackAnalytics.setUserId(id); } - setUser(id, properties={}) { - return promisify('setUserId', FirestackAnalytics)(id, properties); + /** + * Sets a user property to a given value. + * @param name + * @param value + */ + setUserProperty(name: string, value: string): void { + return FirestackAnalytics.setUserProperty(name, value); } - get namespace() { - return 'firestack:analytics' + get namespace(): string { + return 'firestack:analytics'; } } From a1b16f1ed6acdb07a510661694d5cfd979d1bef2 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 14:27:57 +0000 Subject: [PATCH 068/275] misc cleanup --- .../firestack/FirestackAnalytics.java | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index 61af7bc..9580877 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -1,19 +1,16 @@ package io.fullstack.firestack; import java.util.Map; - -import android.app.Activity; import android.util.Log; import android.os.Bundle; +import android.app.Activity; -import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.google.firebase.analytics.FirebaseAnalytics; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.google.firebase.analytics.FirebaseAnalytics; - class FirestackAnalyticsModule extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAnalytics"; @@ -28,6 +25,10 @@ public FirestackAnalyticsModule(ReactApplicationContext reactContext) { mFirebaseAnalytics = FirebaseAnalytics.getInstance(this.context); } + /** + * + * @return + */ @Override public String getName() { return TAG; @@ -50,42 +51,64 @@ public void setAnalyticsCollectionEnabled(final Boolean enabled) { mFirebaseAnalytics.setAnalyticsCollectionEnabled(enabled); } + /** + * + * @param screenName + * @param screenClassOverride + */ @ReactMethod public void setCurrentScreen(final String screenName, final String screenClassOverride) { final Activity activity = getCurrentActivity(); if (activity != null) { - Log.d(TAG, "setCurrentScreen " + screenName + " " + screenClassOverride); + Log.d(TAG, "setCurrentScreen " + screenName + " - " + screenClassOverride); + // needs to be run on main thread activity.runOnUiThread(new Runnable() { @Override public void run() { mFirebaseAnalytics.setCurrentScreen(activity, screenName, screenClassOverride); } }); - } } - + /** + * + * @param milliseconds + */ @ReactMethod public void setMinimumSessionDuration(final double milliseconds) { mFirebaseAnalytics.setMinimumSessionDuration((long) milliseconds); } + /** + * + * @param milliseconds + */ @ReactMethod public void setSessionTimeoutDuration(final double milliseconds) { mFirebaseAnalytics.setSessionTimeoutDuration((long) milliseconds); } + /** + * + * @param id + */ @ReactMethod public void setUserId(final String id) { mFirebaseAnalytics.setUserId(id); } + /** + * + * @param name + * @param value + */ @ReactMethod public void setUserProperty(final String name, final String value) { mFirebaseAnalytics.setUserProperty(name, value); } + // todo refactor/clean me private Bundle makeEventBundle(final String name, final Map map) { Bundle bundle = new Bundle(); // Available from the Analytics event From e2c94ceb2980c7ebd4c8fb4f065a4d4b7b53702a Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 14:56:33 +0000 Subject: [PATCH 069/275] remove double callback on userCallback --- .../src/main/java/io/fullstack/firestack/FirestackAuth.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 963559d..7305aec 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -547,11 +547,6 @@ public void onComplete(@NonNull Task task) { userExceptionCallback(ex, callback); } } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - userExceptionCallback(ex, callback); - } }); } else { callbackNoUser(callback, true); From e5da75229a574618e862787cdb552f841184ff4d Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 15:18:18 +0000 Subject: [PATCH 070/275] added validations to logEvent --- lib/modules/analytics.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/modules/analytics.js b/lib/modules/analytics.js index 2b23d36..ac2b062 100644 --- a/lib/modules/analytics.js +++ b/lib/modules/analytics.js @@ -3,6 +3,23 @@ import { NativeModules } from 'react-native'; import { Base } from './base'; const FirestackAnalytics = NativeModules.FirestackAnalytics; +const AlphaNumericUnderscore = /^[a-zA-Z0-9_]+$/; + +const ReservedEventNames = [ + 'app_clear_data', + 'app_uninstall', + 'app_update', + 'error', + 'first_open', + 'in_app_purchase', + 'notification_dismiss', + 'notification_foreground', + 'notification_open', + 'notification_receive', + 'os_update', + 'session_start', + 'user_engagement', +]; export default class Analytics extends Base { /** @@ -12,6 +29,25 @@ export default class Analytics extends Base { * @return {Promise} */ logEvent(name: string, params: Object = {}): void { + // check name is not a reserved event name + if (ReservedEventNames.includes(name)) { + throw new Error(`event name '${name}' is a reserved event name and can not be used.`); + } + + // name format validation + if (!AlphaNumericUnderscore.test(name)) { + throw new Error(`Event name '${name}' is invalid. Names should contain 1 to 32 alphanumeric characters or underscores.`); + } + + // maximum number of allowed params check + if (Object.keys(params).length > 25) throw new Error('Maximum number of parameters exceeded (25).'); + + // TODO validate param names and values + // Parameter names can be up to 24 characters long and must start with an alphabetic character + // and contain only alphanumeric characters and underscores. Only String, long and double param + // types are supported. String parameter values can be up to 36 characters long. The "firebase_" + // prefix is reserved and should not be used for parameter names. + return FirestackAnalytics.logEvent(name, params); } From 89675b3921af208c1f707accac333d745f8f9523 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 16:21:11 +0000 Subject: [PATCH 071/275] remove log --- .../io/fullstack/firestack/FirestackAuth.java | 16 ++- lib/modules/auth.js | 99 +++---------------- 2 files changed, 28 insertions(+), 87 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 7305aec..2a47ddb 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -18,7 +18,6 @@ import com.facebook.react.bridge.ReactContext; import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.Task; import com.google.firebase.FirebaseApp; @@ -30,6 +29,7 @@ import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; +import com.google.firebase.remoteconfig.FirebaseRemoteConfig; @SuppressWarnings("ThrowableResultOfMethodCallIgnored") class FirestackAuthModule extends ReactContextBaseJavaModule { @@ -41,6 +41,7 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { // private Context context; private ReactContext mReactContext; + private FirebaseRemoteConfig mRemoteConfig; private FirebaseAuth mAuth; private FirebaseApp app; private FirebaseUser user; @@ -51,6 +52,19 @@ public FirestackAuthModule(ReactApplicationContext reactContext) { // this.context = reactContext; mReactContext = reactContext; + mRemoteConfig.fetch() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(Task task) { + if (task.isSuccessful()) { + // task successful. Activate the fetched data + mRemoteConfig.activateFetched(); + + } else { + //task failed + } + } + }); Log.d(TAG, "New FirestackAuth instance"); } diff --git a/lib/modules/auth.js b/lib/modules/auth.js index d0d5229..8755595 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -1,11 +1,11 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; -const FirestackAuth = NativeModules.FirestackAuth; -const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); +import { Base } from './base'; +import { default as User } from './user'; +import promisify from '../utils/promisify'; -import promisify from '../utils/promisify' -import { Base } from './base' -import { default as User} from './user'; +const FirestackAuth = NativeModules.FirestackAuth; +const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); export default class Auth extends Base { constructor(firestack, options = {}) { @@ -14,8 +14,10 @@ export default class Auth extends Base { this.authenticated = false; this._user = null; - this.getCurrentUser().then(this._onAuthStateChanged.bind(this)).catch(()=>{}); - // always track auth changes internall so we can access them synchronously + this.getCurrentUser().then(this._onAuthStateChanged.bind(this)).catch(() => { + }); + + // always track auth changes internally so we can access them synchronously FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); FirestackAuth.listenForAuth(); } @@ -45,10 +47,6 @@ export default class Auth extends Base { onAuthStateChanged(listener) { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); - // const sub = this._on('listenForAuth', listener, FirestackAuthEvt); - // FirestackAuth.listenForAuth(); - // this.log.info('Listening for onAuthStateChanged events...'); - // return promisify(() => sub, FirestackAuth)(sub); } /** @@ -58,8 +56,6 @@ export default class Auth extends Base { offAuthStateChanged(listener) { this.log.info('Removing onAuthStateChanged listener'); this.removeListener('onAuthStateChanged', listener); - // this._off('listenForAuth'); - // return promisify('unlistenForAuth', FirestackAuth)(); } /** @@ -103,7 +99,7 @@ export default class Auth extends Base { /** * Update the current user's password - * @param {string} email the new password + * @param {string} password the new password * @return {Promise} */ updatePassword(password) { @@ -112,7 +108,7 @@ export default class Auth extends Base { /** * Update the current user's profile - * @param {Object} obj An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) + * @param {Object} updates An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) * @return {Promise} */ updateProfile(updates) { @@ -159,65 +155,6 @@ export default class Auth extends Base { return promisify('signInAnonymously', FirestackAuth)(); } - - /* - * Old deprecated api stubs - */ - - - /** - * @deprecated - * @param args - */ - listenForAuth(...args) { - console.warn('Firestack: listenForAuth is now deprecated, please use onAuthStateChanged'); - this.onAuthStateChanged(...args); - } - - /** - * @deprecated - * @param args - */ - unlistenForAuth(...args) { - console.warn('Firestack: unlistenForAuth is now deprecated, please use offAuthStateChanged'); - this.offAuthStateChanged(...args); - } - - /** - * Create a user with the email/password functionality - * @deprecated - * @param {string} email The user's email - * @param {string} password The user's password - * @return {Promise} A promise indicating the completion - */ - createUserWithEmail(...args) { - console.warn('Firestack: createUserWithEmail is now deprecated, please use createUserWithEmailAndPassword'); - this.createUserWithEmailAndPassword(...args); - } - - /** - * Sign a user in with email/password - * @deprecated - * @param {string} email The user's email - * @param {string} password The user's password - * @return {Promise} A promise that is resolved upon completion - */ - signInWithEmail(...args) { - console.warn('Firestack: signInWithEmail is now deprecated, please use signInWithEmailAndPassword'); - this.signInWithEmailAndPassword(...args); - } - - /** - * Update the current user's email - * @deprecated - * @param {string} email The user's _new_ email - * @return {Promise} A promise resolved upon completion - */ - updateUserEmail(...args) { - console.warn('Firestack: updateUserEmail is now deprecated, please use updateEmail'); - this.updateEmail(...args); - } - /** * Send reset password instructions via email * @param {string} email The email to send password reset instructions @@ -231,7 +168,7 @@ export default class Auth extends Base { * @return {Promise} */ deleteUser() { - return promisify('deleteUser', FirestackAuth)() + return promisify('deleteUser', FirestackAuth)(); } /** @@ -239,19 +176,9 @@ export default class Auth extends Base { * @return {Promise} */ getToken() { - return promisify('getToken', FirestackAuth)() + return promisify('getToken', FirestackAuth)(); } - /** - * Update the current user's profile - * @deprecated - * @param {Object} obj An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) - * @return {Promise} - */ - updateUserProfile(...args) { - console.warn('Firestack: updateUserProfile is now deprecated, please use updateProfile'); - this.updateProfile(...args); - } /** * Sign the current user out From 1a46988a0da4a8cbd53ee5d8dfcb2e0619081a07 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 16 Nov 2016 16:22:48 +0000 Subject: [PATCH 072/275] add remote-config to gradle deps --- android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/android/build.gradle b/android/build.gradle index d8f4001..760a0a0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -46,6 +46,7 @@ dependencies { compile 'com.facebook.react:react-native:0.20.+' compile 'com.google.android.gms:play-services-base:9.8.0' compile 'com.google.firebase:firebase-core:9.8.0' + compile 'com.google.firebase:firebase-config:9.8.0' compile 'com.google.firebase:firebase-auth:9.8.0' compile 'com.google.firebase:firebase-analytics:9.8.0' compile 'com.google.firebase:firebase-database:9.8.0' From 524fbf5534c6053c87fc80c5aa646c5d660efff7 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 16 Nov 2016 21:31:35 +0000 Subject: [PATCH 073/275] Update analytics.md --- docs/api/analytics.md | 87 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/docs/api/analytics.md b/docs/api/analytics.md index 8b13789..856e282 100644 --- a/docs/api/analytics.md +++ b/docs/api/analytics.md @@ -1 +1,88 @@ +# Analytics +Integrating Firebase analytics is super simple using Firestack. A number of methods are provided to help tailor analytics specifically for your +own app. The Firebase SDK includes a number of pre-set events which are automatically handled, and cannot be used with custom events: + +``` + 'app_clear_data', + 'app_uninstall', + 'app_update', + 'error', + 'first_open', + 'in_app_purchase', + 'notification_dismiss', + 'notification_foreground', + 'notification_open', + 'notification_receive', + 'os_update', + 'session_start', + 'user_engagement', +``` + +#### logEvent(event: string, params?: Object) + +Log a custom event with optional params. Returns a Promise. + +```javascript +firestack.analytics() + .logEvent('clicked_advert', { id: 1337 }) + .then(() => { + console.log('Event has been logged successfully'); + }); +``` + +#### setAnalyticsCollectionEnabled(enabled: boolean) + +Sets whether analytics collection is enabled for this app on this device. + +```javascript +firestack.analytics() + .setAnalyticsCollectionEnabled(false); +``` + +#### setCurrentScreen(screenName: string, screenClassOverride: string) + +Sets the current screen name, which specifies the current visual context in your app. + +```javascript +firestack.analytics() + .setCurrentScreen('user_profile'); +``` + +#### setMinimumSessionDuration(miliseconds: number) + +Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds). + +```javascript +firestack.analytics() + .setMinimumSessionDuration(15000); +``` + +#### setSessionTimeoutDuration(miliseconds: number) + +Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes). + +```javascript +firestack.analytics() + .setSessionTimeoutDuration(900000); +``` + +#### setUserId(id: string) + +Gives a user a uniqiue identificaition. + +```javascript +const id = firestack.auth().currentUser.uid; + +firestack.analytics() + .setUserId(id); +``` + +#### setUserProperty(name: string, value: string) + +Sets a key/value pair of data on the current user. + +```javascript +firestack.analytics() + .setUserProperty('nickname', 'foobar'); +``` From c93bb8d9fe043c84ff8497f3149055cc715dd84d Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Wed, 16 Nov 2016 21:34:54 +0000 Subject: [PATCH 074/275] Update authentication.md --- docs/api/authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 0e1ecc6..23162b3 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -6,7 +6,7 @@ Firestack handles authentication for us out of the box, both with email/password ## Local Auth -#### [onAuthStateChanged()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) +#### [onAuthStateChanged(event: Function)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) Listen for changes in the users auth state (logging in and out). @@ -34,7 +34,7 @@ This is important to release resources from our app when we don't need to hold o firestack.auth().offAuthStateChanged() ``` -#### [createUserWithEmailAndPassword()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) +#### [createUserWithEmailAndPassword(email: string, password: string)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) We can create a user by calling the `createUserWithEmailAndPassword()` function. The method accepts two parameters, an email and a password. @@ -49,7 +49,7 @@ firestack.auth().createUserWithEmailAndPassword('ari@fullstack.io', '123456') }) ``` -#### [signInWithEmailAndPassword()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) +#### [signInWithEmailAndPassword(email: string, password: string)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) To sign a user in with their email and password, use the `signInWithEmailAndPassword()` function. It accepts two parameters, the user's email and password: From 9213d40ddb133c6107755ffc008cb3e8562f0341 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 17 Nov 2016 09:15:53 +0000 Subject: [PATCH 075/275] Update analytics.md --- docs/api/analytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/analytics.md b/docs/api/analytics.md index 856e282..ab1e469 100644 --- a/docs/api/analytics.md +++ b/docs/api/analytics.md @@ -21,7 +21,7 @@ own app. The Firebase SDK includes a number of pre-set events which are automati #### logEvent(event: string, params?: Object) -Log a custom event with optional params. Returns a Promise. +Log a custom event with optional params. Can be synchronous or return a Promise ```javascript firestack.analytics() From 5968ee154c22eae3ffa994a8e5498f9dce7d205c Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 12:00:19 +0000 Subject: [PATCH 076/275] fixes #141 --- .../fullstack/firestack/FirestackStorage.java | 141 +++++++++--------- 1 file changed, 69 insertions(+), 72 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index a91a6a9..3f3a40a 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -4,6 +4,7 @@ import android.os.StatFs; import android.content.Context; import android.util.Log; + import java.util.Map; import java.util.HashMap; import java.io.File; @@ -79,27 +80,27 @@ public String getName() { public void downloadUrl(final String javascriptStorageBucket, final String path, final Callback callback) { - FirebaseStorage storage = FirebaseStorage.getInstance(); - String storageBucket = storage.getApp().getOptions().getStorageBucket(); - String storageUrl = "gs://"+storageBucket; - Log.d(TAG, "Storage url " + storageUrl + path); - final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); - final StorageReference fileRef = storageRef.child(path); - - Task downloadTask = fileRef.getDownloadUrl(); - downloadTask.addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(Uri uri) { - final WritableMap res = Arguments.createMap(); - - res.putString("status", "success"); - res.putString("bucket", storageRef.getBucket()); - res.putString("fullPath", uri.toString()); - res.putString("path", uri.getPath()); - res.putString("url", uri.toString()); - - fileRef.getMetadata() - .addOnSuccessListener(new OnSuccessListener() { + FirebaseStorage storage = FirebaseStorage.getInstance(); + String storageBucket = storage.getApp().getOptions().getStorageBucket(); + String storageUrl = "gs://" + storageBucket; + Log.d(TAG, "Storage url " + storageUrl + path); + final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); + final StorageReference fileRef = storageRef.child(path); + + Task downloadTask = fileRef.getDownloadUrl(); + downloadTask.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Uri uri) { + final WritableMap res = Arguments.createMap(); + + res.putString("status", "success"); + res.putString("bucket", storageRef.getBucket()); + res.putString("fullPath", uri.toString()); + res.putString("path", uri.getPath()); + res.putString("url", uri.toString()); + + fileRef.getMetadata() + .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(final StorageMetadata storageMetadata) { Log.d(TAG, "getMetadata success " + storageMetadata); @@ -118,27 +119,27 @@ public void onSuccess(final StorageMetadata storageMetadata) { res.putMap("metadata", metadata); callback.invoke(null, res); } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception exception) { - Log.e(TAG, "Failure in download " + exception); - callback.invoke(makeErrorPayload(1, exception)); - } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.e(TAG, "Failure in download " + exception); + callback.invoke(makeErrorPayload(1, exception)); + } + }); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception exception) { - Log.e(TAG, "Failed to download file " + exception.getMessage()); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.e(TAG, "Failed to download file " + exception.getMessage()); - WritableMap err = Arguments.createMap(); - err.putString("status", "error"); - err.putString("description", exception.getLocalizedMessage()); + WritableMap err = Arguments.createMap(); + err.putString("status", "error"); + err.putString("description", exception.getLocalizedMessage()); - callback.invoke(err); - } - }); + callback.invoke(err); + } + }); } // STORAGE @@ -149,17 +150,21 @@ public void uploadFile(final String urlStr, final String name, final String file StorageReference storageRef = storage.getReferenceFromUrl(urlStr); StorageReference fileRef = storageRef.child(name); -Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); + Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); + try { - // InputStream stream = new FileInputStream(new File(filepath)); Uri file = Uri.fromFile(new File(filepath)); StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder(); Map m = FirestackUtils.recursivelyDeconstructReadableMap(metadata); + for (Map.Entry entry : m.entrySet()) { + metadataBuilder.setCustomMetadata(entry.getKey(), entry.getValue().toString()); + } + StorageMetadata md = metadataBuilder.build(); UploadTask uploadTask = fileRef.putFile(file, md); - // UploadTask uploadTask = fileRef.putStream(stream, md); + // uploadTask uploadTask = fileRef.putStream(stream, md); // Register observers to listen for when the download is done or if it fails uploadTask.addOnFailureListener(new OnFailureListener() { @@ -179,33 +184,26 @@ public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { Log.d(TAG, "Successfully uploaded file " + taskSnapshot); // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL. WritableMap resp = getDownloadData(taskSnapshot); - // NSDictionary *props = @{ - // @"fullPath": ref.fullPath, - // @"bucket": ref.bucket, - // @"name": ref.name, - // @"metadata": [snapshot.metadata dictionaryRepresentation] - // }; - callback.invoke(null, resp); } }) - .addOnProgressListener(new OnProgressListener() { - @Override - public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { - double totalBytes = taskSnapshot.getTotalByteCount(); - double bytesTransferred = taskSnapshot.getBytesTransferred(); - double progress = (100.0 * bytesTransferred) / totalBytes; - - System.out.println("Transferred " + bytesTransferred + "/" + totalBytes + "("+progress + "% complete)"); - - if (progress >= 0) { - WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_progress"); - data.putDouble("progress", progress); - FirestackUtils.sendEvent(mReactContext, "upload_progress", data); - } - } - }).addOnPausedListener(new OnPausedListener() { + .addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { + double totalBytes = taskSnapshot.getTotalByteCount(); + double bytesTransferred = taskSnapshot.getBytesTransferred(); + double progress = (100.0 * bytesTransferred) / totalBytes; + + System.out.println("Transferred " + bytesTransferred + "/" + totalBytes + "(" + progress + "% complete)"); + + if (progress >= 0) { + WritableMap data = Arguments.createMap(); + data.putString("eventName", "upload_progress"); + data.putDouble("progress", progress); + FirestackUtils.sendEvent(mReactContext, "upload_progress", data); + } + } + }).addOnPausedListener(new OnPausedListener() { @Override public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { System.out.println("Upload is paused"); @@ -217,8 +215,7 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { FirestackUtils.sendEvent(mReactContext, "upload_paused", data); } }); - } - catch (Exception ex) { + } catch (Exception ex) { callback.invoke(makeErrorPayload(2, ex)); } } @@ -227,8 +224,8 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { public void getRealPathFromURI(final String uri, final Callback callback) { try { Context context = getReactApplicationContext(); - String [] proj = {MediaStore.Images.Media.DATA}; - Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj, null, null, null); + String[] proj = {MediaStore.Images.Media.DATA}; + Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj, null, null, null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); String path = cursor.getString(column_index); @@ -282,9 +279,9 @@ public Map getConstants() { File externalStorageDirectory = Environment.getExternalStorageDirectory(); if (externalStorageDirectory != null) { - constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath()); + constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath()); } else { - constants.put(ExternalStorageDirectoryPath, null); + constants.put(ExternalStorageDirectoryPath, null); } File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null); From 15c55a9426cfc8013de4fb7139b86a1be695a22e Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 12:24:37 +0000 Subject: [PATCH 077/275] remove test code --- .../io/fullstack/firestack/FirestackAuth.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 2a47ddb..dc21969 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -29,7 +29,7 @@ import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; -import com.google.firebase.remoteconfig.FirebaseRemoteConfig; + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") class FirestackAuthModule extends ReactContextBaseJavaModule { @@ -41,7 +41,6 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { // private Context context; private ReactContext mReactContext; - private FirebaseRemoteConfig mRemoteConfig; private FirebaseAuth mAuth; private FirebaseApp app; private FirebaseUser user; @@ -52,20 +51,7 @@ public FirestackAuthModule(ReactApplicationContext reactContext) { // this.context = reactContext; mReactContext = reactContext; - mRemoteConfig.fetch() - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(Task task) { - if (task.isSuccessful()) { - // task successful. Activate the fetched data - mRemoteConfig.activateFetched(); - - } else { - //task failed - } - } - }); - Log.d(TAG, "New FirestackAuth instance"); + Log.d(TAG, "New FirestackAuth instance"); } @Override From 08e0ead4b87d48cc2e87fec2283d93e46fe58fde Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 12:25:38 +0000 Subject: [PATCH 078/275] cleanup and add flow annotations to firestack class --- lib/firestack.js | 87 ++++++++++++++++++++++-------------------------- lib/utils/log.js | 14 ++++---- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/lib/firestack.js b/lib/firestack.js index 6e0d854..1bf9153 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -4,9 +4,9 @@ */ -import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; +import { NativeModules, NativeEventEmitter } from 'react-native'; -import Log from './utils/log' +import Log from './utils/log'; import promisify from './utils/promisify'; import Singleton from './utils/singleton'; @@ -34,7 +34,7 @@ export default class Firestack extends Singleton { * @param options * @param name - TODO support naming multiple instances */ - constructor(options, name) { + constructor(options: Object, name: string) { const instance = super(options); instance.options = options || {}; @@ -49,7 +49,6 @@ export default class Firestack extends Singleton { delete instance.options.remoteConfig; instance.configured = instance.options.configure || false; - instance._auth = null; instance.eventHandlers = {}; @@ -59,13 +58,24 @@ export default class Firestack extends Singleton { instance._auth = new Auth(instance, instance.options); } + _db: ?Object; + _log: ?Object; + _auth: ?Object; + _store: ?Object; + _storage: ?Object; + _presence: ?Object; + _analytics: ?Object; + _constants: ?Object; + _messaging: ?Object; + _remoteConfig: ?Object; + /** * Support web version of initApp. * @param options * @param name * @returns {*} */ - static initializeApp(options, name = 'default') { + static initializeApp(options: Object = {}, name: string = 'default') { if (!instances[name]) instances[name] = new Firestack(options); return instances[name]; } @@ -76,7 +86,7 @@ export default class Firestack extends Singleton { * @param opts * @returns {Promise.|*|Promise.} */ - configure(opts = {}) { + configure(opts: Object = {}) { if (!this.configurePromise) { const firestackOptions = Object.assign({}, this.options, opts); @@ -88,12 +98,13 @@ export default class Firestack extends Singleton { return configuredProperties; }).catch((err) => { log.info('Native error occurred while calling configure', err); - }) + }); } return this.configurePromise; } - onReady(cb) { + onReady(cb: Function) { + // TODO wut o.O return this.configurePromise = this.configurePromise.then(cb); } @@ -104,24 +115,18 @@ export default class Firestack extends Singleton { * idea or not (imperative vs. direct manipulation/proxy) */ auth() { - console.log('auth GET'); - if (!this._auth) { - this._auth = new Auth(this); - } return this._auth; } - // database + database() { if (!this._db) { this._db = new Database(this); } return this._db; - // db.enableLogging(this._debug); - // return this.appInstance.database(); } - // analytics + analytics() { if (!this._analytics) { this._analytics = new Analytics(this); @@ -137,7 +142,6 @@ export default class Firestack extends Singleton { return this._storage; } - // presence presence() { if (!this._presence) { this._presence = new Presence(this); @@ -145,63 +149,54 @@ export default class Firestack extends Singleton { return this._presence; } - // CloudMessaging messaging() { - if (!this._cloudMessaging) { - this._cloudMessaging = new Messaging(this); + if (!this._messaging) { + this._messaging = new Messaging(this); } - return this._cloudMessaging; + return this._messaging; } - /** - * remote config - */ remoteConfig() { - if (!this.remoteConfig) { - this.remoteConfig = new RemoteConfig(this._remoteConfig); + if (!this._remoteConfig) { + this._remoteConfig = new RemoteConfig(this); } - return this.remoteConfig; + return this._remoteConfig; } // other - get ServerValue() { + get ServerValue(): Promise<*> { return promisify('serverValue', FirestackModule)(); } - - /** - * app instance - **/ - get app() { + // TODO what are these for? + get app(): Object { return this.appInstance; } - /** - * app instance - **/ - getInstance() { + // TODO what are these for? + getInstance(): Object { return this.appInstance; } - get apps() { + get apps(): Array { return Object.keys(instances); } /** * Logger */ - get log() { + get log(): Log { return this._log; } /** * Redux store **/ - get store() { + get store(): Object { return this._store; } - get constants() { + get constants(): Object { if (!this._constants) { this._constants = Object.assign({}, Storage.constants) } @@ -211,7 +206,7 @@ export default class Firestack extends Singleton { /** * Set the redux store helper */ - setStore(store) { + setStore(store: Object) { if (store) { this.log.info('Setting the store for Firestack instance'); this._store = store; @@ -221,19 +216,17 @@ export default class Firestack extends Singleton { /** * Global event handlers for the single Firestack instance */ - on(name, cb, nativeModule) { + on(name: string, cb: Function, nativeModule: Object = FirestackModuleEvt) { if (!this.eventHandlers[name]) { this.eventHandlers[name] = []; } - if (!nativeModule) { - nativeModule = FirestackModuleEvt; - } + const sub = nativeModule.addListener(name, cb); this.eventHandlers[name].push(sub); return sub; } - off(name) { + off(name: string) { if (this.eventHandlers[name]) { this.eventHandlers[name] .forEach(subscription => subscription.remove()); diff --git a/lib/utils/log.js b/lib/utils/log.js index 12b0258..0ce363c 100644 --- a/lib/utils/log.js +++ b/lib/utils/log.js @@ -1,17 +1,17 @@ // document hack -import root from './window-or-global' +import root from './window-or-global'; let bows; (function (base) { window = base || window - if(!window.localStorage) window.localStorage = {}; + if (!window.localStorage) window.localStorage = {}; })(root); const levels = [ 'warn', 'info', 'error', 'debug' ]; -export class Log { +class Log { constructor(namespace) { this._namespace = namespace || 'firestack'; this.loggers = {}; @@ -22,8 +22,8 @@ export class Log { static enable(booleanOrStringDebug) { window.localStorage.debug = - typeof booleanOrStringDebug === 'string' ? - (booleanOrStringDebug === '*' ? true : booleanOrStringDebug) : + typeof booleanOrStringDebug === 'string' ? + (booleanOrStringDebug === '*' ? true : booleanOrStringDebug) : (booleanOrStringDebug instanceof RegExp ? booleanOrStringDebug.toString() : booleanOrStringDebug); window.localStorage.debugColors = !!window.localStorage.debug; @@ -31,7 +31,7 @@ export class Log { _log(level) { if (!this.loggers[level]) { - (function() { + (function () { const bows = require('bows'); bows.config({ padLength: 20 }); this.loggers[level] = bows(this._namespace, `[${level}]`); @@ -41,4 +41,4 @@ export class Log { } } -export default Log; \ No newline at end of file +export default Log; From 15a183c6e9a0e7c0e017dd09f039367bb0ba9495 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 12:51:59 +0000 Subject: [PATCH 079/275] update flow config to declare react-native imports --- .flowconfig | 2 +- lib/flow.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 lib/flow.js diff --git a/.flowconfig b/.flowconfig index 020e032..f43b490 100644 --- a/.flowconfig +++ b/.flowconfig @@ -77,7 +77,7 @@ node_modules/fbjs/lib [libs] -js/flow-lib.js +lib/flow.js node_modules/react-native/Libraries/react-native/react-native-interface.js node_modules/react-native/flow node_modules/fbjs/flow/lib diff --git a/lib/flow.js b/lib/flow.js new file mode 100644 index 0000000..2db69a4 --- /dev/null +++ b/lib/flow.js @@ -0,0 +1,4 @@ +declare module 'react-native' { + // noinspection ES6ConvertVarToLetConst + declare var exports: any; +} From c65d53eb1bd82cbc78e8b408e4881a22b9fc47de Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 19:53:55 +0000 Subject: [PATCH 080/275] removed unused local context variable --- .../src/main/java/io/fullstack/firestack/FirestackStorage.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 3f3a40a..48ea6fc 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -58,14 +58,11 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; - - private Context context; private ReactContext mReactContext; private FirebaseApp app; public FirestackStorageModule(ReactApplicationContext reactContext) { super(reactContext); - this.context = reactContext; mReactContext = reactContext; Log.d(TAG, "New instance"); From 06db70cf16830f28e8bb29a90b32bdb1a7de421b Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 20:01:18 +0000 Subject: [PATCH 081/275] cleanup formatting --- .../fullstack/firestack/FirestackStorage.java | 168 +++++++++--------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 48ea6fc..46a20f5 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -85,65 +85,67 @@ public void downloadUrl(final String javascriptStorageBucket, final StorageReference fileRef = storageRef.child(path); Task downloadTask = fileRef.getDownloadUrl(); - downloadTask.addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(Uri uri) { - final WritableMap res = Arguments.createMap(); - - res.putString("status", "success"); - res.putString("bucket", storageRef.getBucket()); - res.putString("fullPath", uri.toString()); - res.putString("path", uri.getPath()); - res.putString("url", uri.toString()); - - fileRef.getMetadata() - .addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(final StorageMetadata storageMetadata) { - Log.d(TAG, "getMetadata success " + storageMetadata); - res.putString("name", storageMetadata.getName()); - - WritableMap metadata = Arguments.createMap(); - metadata.putString("getBucket", storageMetadata.getBucket()); - metadata.putString("getName", storageMetadata.getName()); - metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); - metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); - metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); - metadata.putString("md5hash", storageMetadata.getMd5Hash()); - metadata.putString("encoding", storageMetadata.getContentEncoding()); - res.putString("url", storageMetadata.getDownloadUrl().toString()); - - res.putMap("metadata", metadata); - callback.invoke(null, res); - } - }).addOnFailureListener(new OnFailureListener() { + downloadTask + .addOnSuccessListener(new OnSuccessListener() { @Override - public void onFailure(@NonNull Exception exception) { - Log.e(TAG, "Failure in download " + exception); - callback.invoke(makeErrorPayload(1, exception)); - } - }); + public void onSuccess(Uri uri) { + final WritableMap res = Arguments.createMap(); + + res.putString("status", "success"); + res.putString("bucket", storageRef.getBucket()); + res.putString("fullPath", uri.toString()); + res.putString("path", uri.getPath()); + res.putString("url", uri.toString()); + + fileRef.getMetadata() + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(final StorageMetadata storageMetadata) { + Log.d(TAG, "getMetadata success " + storageMetadata); + res.putString("name", storageMetadata.getName()); + + WritableMap metadata = Arguments.createMap(); + metadata.putString("getBucket", storageMetadata.getBucket()); + metadata.putString("getName", storageMetadata.getName()); + metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); + metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); + metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); + metadata.putString("md5hash", storageMetadata.getMd5Hash()); + metadata.putString("encoding", storageMetadata.getContentEncoding()); + res.putString("url", storageMetadata.getDownloadUrl().toString()); + + res.putMap("metadata", metadata); + callback.invoke(null, res); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.e(TAG, "Failure in download " + exception); + callback.invoke(makeErrorPayload(1, exception)); + } + }); - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception exception) { - Log.e(TAG, "Failed to download file " + exception.getMessage()); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.e(TAG, "Failed to download file " + exception.getMessage()); - WritableMap err = Arguments.createMap(); - err.putString("status", "error"); - err.putString("description", exception.getLocalizedMessage()); + WritableMap err = Arguments.createMap(); + err.putString("status", "error"); + err.putString("description", exception.getLocalizedMessage()); - callback.invoke(err); - } - }); + callback.invoke(err); + } + }); } // STORAGE @ReactMethod public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { FirebaseStorage storage = FirebaseStorage.getInstance(); - StorageReference storageRef = storage.getReferenceFromUrl(urlStr); StorageReference fileRef = storageRef.child(name); @@ -161,29 +163,30 @@ public void uploadFile(final String urlStr, final String name, final String file StorageMetadata md = metadataBuilder.build(); UploadTask uploadTask = fileRef.putFile(file, md); - // uploadTask uploadTask = fileRef.putStream(stream, md); // Register observers to listen for when the download is done or if it fails - uploadTask.addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception exception) { - // Handle unsuccessful uploads - Log.e(TAG, "Failed to upload file " + exception.getMessage()); - - WritableMap err = Arguments.createMap(); - err.putString("description", exception.getLocalizedMessage()); - - callback.invoke(err); - } - }).addOnSuccessListener(new OnSuccessListener() { - @Override - public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { - Log.d(TAG, "Successfully uploaded file " + taskSnapshot); - // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL. - WritableMap resp = getDownloadData(taskSnapshot); - callback.invoke(null, resp); - } - }) + uploadTask + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + // Handle unsuccessful uploads + Log.e(TAG, "Failed to upload file " + exception.getMessage()); + + WritableMap err = Arguments.createMap(); + err.putString("description", exception.getLocalizedMessage()); + + callback.invoke(err); + } + }) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Successfully uploaded file " + taskSnapshot); + // taskSnapshot.getMetadata() contains file metadata such as size, content-type, and download URL. + WritableMap resp = getDownloadData(taskSnapshot); + callback.invoke(null, resp); + } + }) .addOnProgressListener(new OnProgressListener() { @Override public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { @@ -200,18 +203,19 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { FirestackUtils.sendEvent(mReactContext, "upload_progress", data); } } - }).addOnPausedListener(new OnPausedListener() { - @Override - public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { - System.out.println("Upload is paused"); - StorageMetadata d = taskSnapshot.getMetadata(); - String bucket = d.getBucket(); - WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_paused"); - data.putString("ref", bucket); - FirestackUtils.sendEvent(mReactContext, "upload_paused", data); - } - }); + }) + .addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { + System.out.println("Upload is paused"); + StorageMetadata d = taskSnapshot.getMetadata(); + String bucket = d.getBucket(); + WritableMap data = Arguments.createMap(); + data.putString("eventName", "upload_paused"); + data.putString("ref", bucket); + FirestackUtils.sendEvent(mReactContext, "upload_paused", data); + } + }); } catch (Exception ex) { callback.invoke(makeErrorPayload(2, ex)); } From f0a6f0ba6597733672f89e34fda4f270c5f964db Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 20:03:16 +0000 Subject: [PATCH 082/275] remove unused imports --- .../io/fullstack/firestack/FirestackStorage.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 46a20f5..cacbf4d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -1,22 +1,17 @@ package io.fullstack.firestack; import android.os.Environment; -import android.os.StatFs; import android.content.Context; import android.util.Log; import java.util.Map; import java.util.HashMap; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.io.FileNotFoundException; import android.net.Uri; import android.provider.MediaStore; import android.database.Cursor; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; @@ -25,10 +20,8 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.ReactContext; -import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; @@ -164,12 +157,12 @@ public void uploadFile(final String urlStr, final String name, final String file StorageMetadata md = metadataBuilder.build(); UploadTask uploadTask = fileRef.putFile(file, md); - // Register observers to listen for when the download is done or if it fails + // register observers to listen for when the download is done or if it fails uploadTask .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception exception) { - // Handle unsuccessful uploads + // handle unsuccessful uploads Log.e(TAG, "Failed to upload file " + exception.getMessage()); WritableMap err = Arguments.createMap(); @@ -265,7 +258,7 @@ private WritableMap makeErrorPayload(double code, Exception ex) { return error; } - // Comes almost directory from react-native-fs + // comes almost directly from react-native-fs @Override public Map getConstants() { final Map constants = new HashMap<>(); From 72d108583ad46fd4e2a4e6886ad1faaf74d7ea36 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 20:07:00 +0000 Subject: [PATCH 083/275] cleanup imports --- .../fullstack/firestack/FirestackStorage.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index cacbf4d..e8f14b0 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -1,41 +1,39 @@ package io.fullstack.firestack; +import android.util.Log; import android.os.Environment; import android.content.Context; -import android.util.Log; +import java.io.File; import java.util.Map; import java.util.HashMap; -import java.io.File; import android.net.Uri; -import android.provider.MediaStore; import android.database.Cursor; +import android.provider.MediaStore; import android.support.annotation.NonNull; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; -import com.google.android.gms.tasks.Task; - -import com.google.firebase.storage.OnProgressListener; -import com.google.firebase.storage.OnPausedListener; import com.google.firebase.FirebaseApp; - -import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.UploadTask; - +import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageMetadata; import com.google.firebase.storage.StorageReference; +import com.google.firebase.storage.OnPausedListener; +import com.google.firebase.storage.OnProgressListener; + class FirestackStorageModule extends ReactContextBaseJavaModule { From 61e83cc2a3a7453ed27d193f67c2f0d393a28f57 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 20:07:53 +0000 Subject: [PATCH 084/275] remove unused FireBase app local reference --- .../src/main/java/io/fullstack/firestack/FirestackStorage.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index e8f14b0..1298e90 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -26,7 +26,6 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; -import com.google.firebase.FirebaseApp; import com.google.firebase.storage.UploadTask; import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageMetadata; @@ -50,7 +49,6 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; private ReactContext mReactContext; - private FirebaseApp app; public FirestackStorageModule(ReactApplicationContext reactContext) { super(reactContext); From cb4a19af2d9e69d3aff6e6afd19526d6c472bb74 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 17 Nov 2016 20:17:27 +0000 Subject: [PATCH 085/275] misc --- .../fullstack/firestack/FirestackModule.java | 118 ++++++++---------- .../fullstack/firestack/FirestackPackage.java | 6 +- .../fullstack/firestack/FirestackStorage.java | 7 +- 3 files changed, 56 insertions(+), 75 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java index 6baf627..0376e08 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -1,6 +1,7 @@ package io.fullstack.firestack; import java.util.Map; + import android.util.Log; import android.content.Context; import android.support.annotation.Nullable; @@ -23,6 +24,7 @@ interface KeySetterFn { String setKeyOrDefault(String a, String b); } +@SuppressWarnings("WeakerAccess") class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { private static final String TAG = "Firestack"; private Context context; @@ -55,8 +57,8 @@ public void configureWithOptions(final ReadableMap params, @Nullable final Callb KeySetterFn fn = new KeySetterFn() { public String setKeyOrDefault( - final String key, - final String defaultValue) { + final String key, + final String defaultValue) { if (params.hasKey(key)) { // User-set key final String val = params.getString(key); @@ -71,47 +73,26 @@ public String setKeyOrDefault( } }; - String val = fn.setKeyOrDefault("applicationId", - defaultOptions.getApplicationId()); - if (val != null) { - builder.setApplicationId(val); - } + String val = fn.setKeyOrDefault("applicationId", defaultOptions.getApplicationId()); + if (val != null) builder.setApplicationId(val); - val = fn.setKeyOrDefault("apiKey", - defaultOptions.getApiKey()); - if (val != null) { - builder.setApiKey(val); - } + val = fn.setKeyOrDefault("apiKey", defaultOptions.getApiKey()); + if (val != null) builder.setApiKey(val); - val = fn.setKeyOrDefault("gcmSenderID", - defaultOptions.getGcmSenderId()); - if (val != null) { - builder.setGcmSenderId(val); - } + val = fn.setKeyOrDefault("gcmSenderID", defaultOptions.getGcmSenderId()); + if (val != null) builder.setGcmSenderId(val); - val = fn.setKeyOrDefault("storageBucket", - defaultOptions.getStorageBucket()); - if (val != null) { - builder.setStorageBucket(val); - } + val = fn.setKeyOrDefault("storageBucket", defaultOptions.getStorageBucket()); + if (val != null) builder.setStorageBucket(val); - val = fn.setKeyOrDefault("databaseURL", - defaultOptions.getDatabaseUrl()); - if (val != null) { - builder.setDatabaseUrl(val); - } + val = fn.setKeyOrDefault("databaseURL", defaultOptions.getDatabaseUrl()); + if (val != null) builder.setDatabaseUrl(val); - val = fn.setKeyOrDefault("databaseUrl", - defaultOptions.getDatabaseUrl()); - if (val != null) { - builder.setDatabaseUrl(val); - } + val = fn.setKeyOrDefault("databaseUrl", defaultOptions.getDatabaseUrl()); + if (val != null) builder.setDatabaseUrl(val); - val = fn.setKeyOrDefault("clientId", - defaultOptions.getApplicationId()); - if (val != null) { - builder.setApplicationId(val); - } + val = fn.setKeyOrDefault("clientId", defaultOptions.getApplicationId()); + if (val != null) builder.setApplicationId(val); // if (params.hasKey("applicationId")) { @@ -151,24 +132,23 @@ public String setKeyOrDefault( // } try { - Log.i(TAG, "Configuring app"); - if (app == null) { - app = FirebaseApp.initializeApp(this.context, builder.build()); - } - Log.i(TAG, "Configured"); + Log.i(TAG, "Configuring app"); + if (app == null) { + app = FirebaseApp.initializeApp(this.context, builder.build()); + } + Log.i(TAG, "Configured"); - WritableMap resp = Arguments.createMap(); - resp.putString("msg", "success"); - onComplete.invoke(null, resp); - } - catch (Exception ex){ - Log.e(TAG, "ERROR configureWithOptions"); - Log.e(TAG, ex.getMessage()); + WritableMap resp = Arguments.createMap(); + resp.putString("msg", "success"); + onComplete.invoke(null, resp); + } catch (Exception ex) { + Log.e(TAG, "ERROR configureWithOptions"); + Log.e(TAG, ex.getMessage()); - WritableMap resp = Arguments.createMap(); - resp.putString("msg", ex.getMessage()); + WritableMap resp = Arguments.createMap(); + resp.putString("msg", ex.getMessage()); - onComplete.invoke(resp); + onComplete.invoke(resp); } } @@ -181,26 +161,26 @@ public void serverValue(@Nullable final Callback onComplete) { WritableMap map = Arguments.createMap(); map.putMap("TIMESTAMP", timestampMap); - onComplete.invoke(null, map); + if (onComplete != null) onComplete.invoke(null, map); } - // Internal helpers - @Override - public void onHostResume() { - WritableMap params = Arguments.createMap(); - params.putBoolean("isForground", true); - FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); - } + // Internal helpers + @Override + public void onHostResume() { + WritableMap params = Arguments.createMap(); + params.putBoolean("isForground", true); + FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); + } - @Override - public void onHostPause() { - WritableMap params = Arguments.createMap(); - params.putBoolean("isForground", false); - FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); - } + @Override + public void onHostPause() { + WritableMap params = Arguments.createMap(); + params.putBoolean("isForground", false); + FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); + } - @Override - public void onHostDestroy() { + @Override + public void onHostDestroy() { - } + } } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java index ea5927d..4ab5e00 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -8,10 +8,11 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; +import java.util.List; import java.util.ArrayList; import java.util.Collections; -import java.util.List; +@SuppressWarnings("unused") public class FirestackPackage implements ReactPackage { private Context mContext; @@ -24,7 +25,6 @@ public FirestackPackage() { @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new FirestackModule(reactContext, reactContext.getBaseContext())); modules.add(new FirestackAuthModule(reactContext)); modules.add(new FirestackDatabaseModule(reactContext)); @@ -54,4 +54,4 @@ public List> createJSModules() { public List createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } -} \ No newline at end of file +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 1298e90..0794f65 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -34,6 +34,7 @@ import com.google.firebase.storage.OnProgressListener; +@SuppressWarnings("WeakerAccess") class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String TAG = "FirestackStorage"; @@ -91,7 +92,6 @@ public void onSuccess(Uri uri) { @Override public void onSuccess(final StorageMetadata storageMetadata) { Log.d(TAG, "getMetadata success " + storageMetadata); - res.putString("name", storageMetadata.getName()); WritableMap metadata = Arguments.createMap(); metadata.putString("getBucket", storageMetadata.getBucket()); @@ -101,9 +101,10 @@ public void onSuccess(final StorageMetadata storageMetadata) { metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); metadata.putString("md5hash", storageMetadata.getMd5Hash()); metadata.putString("encoding", storageMetadata.getContentEncoding()); - res.putString("url", storageMetadata.getDownloadUrl().toString()); res.putMap("metadata", metadata); + res.putString("name", storageMetadata.getName()); + res.putString("url", storageMetadata.getDownloadUrl().toString()); callback.invoke(null, res); } }) @@ -254,7 +255,7 @@ private WritableMap makeErrorPayload(double code, Exception ex) { return error; } - // comes almost directly from react-native-fs + @Override public Map getConstants() { final Map constants = new HashMap<>(); From 2657136c37361229563bb918e888d0d788f0c144 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 09:41:08 +0000 Subject: [PATCH 086/275] #147 - Fix possible Null Pointer Exception when logging an event on Android --- .../main/java/io/fullstack/firestack/FirestackUtils.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java index ee50649..ae1bb95 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java @@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReadableType; import com.google.firebase.database.DataSnapshot; +@SuppressWarnings("WeakerAccess") public class FirestackUtils { private static final String TAG = "FirestackUtils"; @@ -168,8 +169,12 @@ public static WritableArray getChildKeys(DataSnapshot snapshot) { } public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { - ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); Map deconstructedMap = new HashMap<>(); + if (readableMap == null) { + return deconstructedMap; + } + + ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); while (iterator.hasNextKey()) { String key = iterator.nextKey(); ReadableType type = readableMap.getType(key); From bd08bd79caf99f0d3ebdf2376d8a04a9a15b5901 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 12:10:38 +0000 Subject: [PATCH 087/275] Trigger auth event when listening for AuthStateChanged --- lib/modules/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 8755595..3b49e86 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -47,6 +47,7 @@ export default class Auth extends Base { onAuthStateChanged(listener) { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); + FirestackAuth.listenForAuth(); } /** From 8ffa0eeb0e06ed02727a4039ba2597d0cb6af99c Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 12:31:24 +0000 Subject: [PATCH 088/275] Bring iOS Analytics inline with Android version --- ios/Firestack/FirestackAnalytics.m | 44 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/ios/Firestack/FirestackAnalytics.m b/ios/Firestack/FirestackAnalytics.m index cf69e0b..202c912 100644 --- a/ios/Firestack/FirestackAnalytics.m +++ b/ios/Firestack/FirestackAnalytics.m @@ -21,38 +21,48 @@ - (void)dealloc RCT_EXPORT_MODULE(FirestackAnalytics); // Implementation -RCT_EXPORT_METHOD(logEventWithName:(NSString *)name - props:(NSDictionary *)props - callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(logEvent:(NSString *)name + props:(NSDictionary *)props) { NSString *debugMsg = [NSString stringWithFormat:@"%@: %@ with %@", @"FirestackAnalytics", name, props]; [[Firestack sharedInstance] debugLog:@"logEventWithName called" msg:debugMsg]; - [FIRAnalytics logEventWithName:name parameters:props]; - callback(@[[NSNull null], @YES]); + [FIRAnalytics logEventWithName:name parameters:props]; } -RCT_EXPORT_METHOD(setEnabled:(BOOL) enabled - callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(setAnalyticsCollectionEnabled:(BOOL) enabled) { [[FIRAnalyticsConfiguration sharedInstance] setAnalyticsCollectionEnabled:enabled]; - callback(@[[NSNull null], @YES]); } -RCT_EXPORT_METHOD(setUser: (NSString *) id - props:(NSDictionary *) props - callback:(RCTResponseSenderBlock) callback) +RCT_EXPORT_METHOD(setCurrentScreen:(NSString *) screenName + screenClass:(NSString *) screenClassOverriew) +{ + [FIRAnalytics setScreenName:screenName screenClass:screenClassOverriew]; +} + +RCT_EXPORT_METHOD(setMinimumSessionDuration:(NSNumber *) milliseconds) +{ + //Not implemented on iOS +} + +RCT_EXPORT_METHOD(setSessionTimeoutDuration:(NSNumber *) milliseconds) +{ + //Not implemented on iOS +} + +RCT_EXPORT_METHOD(setUserId: (NSString *) id + props:(NSDictionary *) props) { [FIRAnalytics setUserID:id]; - NSMutableArray *allKeys = [[props allKeys] mutableCopy]; - for (NSString *key in allKeys) { - NSString *val = [props valueForKey:key]; - [FIRAnalytics setUserPropertyString:val forName:key]; - } +} - callback(@[[NSNull null], @YES]); +RCT_EXPORT_METHOD(setUserProperty: (NSString *) name + value:(NSString *) value) +{ + [FIRAnalytics setUserPropertyString:value forName:name]; } @end From e475c6d616b60e2c7910a5c07ffcd647e166a6e5 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 13:41:50 +0000 Subject: [PATCH 089/275] Only send the current auth event to the new listener --- lib/modules/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 3b49e86..c92f79a 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -47,7 +47,7 @@ export default class Auth extends Base { onAuthStateChanged(listener) { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); - FirestackAuth.listenForAuth(); + if (this._authResult) listener(this._authResult); } /** From af5b8f549a526c04ee6e2b787a02aeae45d1db8b Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 13:41:59 +0000 Subject: [PATCH 090/275] fix .currentUser logic - the initial event doesn't have authenticated flag --- .../io/fullstack/firestack/FirestackAuth.java | 3 +-- lib/modules/auth.js | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index dc21969..1b1d00e 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -627,8 +627,7 @@ private WritableMap getUserMap() { userMap.putString("uid", uid); userMap.putString("providerId", provider); userMap.putBoolean("emailVerified", verified); - - + if (name != null) { userMap.putString("name", name); } diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 8755595..d53bdd3 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -14,12 +14,30 @@ export default class Auth extends Base { this.authenticated = false; this._user = null; - this.getCurrentUser().then(this._onAuthStateChanged.bind(this)).catch(() => { + // start listening straight away + // generally though the initial event fired will get ignored + // but this is ok as we fake it with the getCurrentUser below + FirestackAuth.listenForAuth(); + + this.getCurrentUser().then((u) => { + const authResult = { authenticated: !!u }; + if (u) authResult.user = u; + this._onAuthStateChanged(authResult); + this._startListening(); + }).catch(() => { + // todo check if error contains user disabled message maybe and add a disabled flag? + this._onAuthStateChanged({ authenticated: false }); + this._startListening(); }); + } - // always track auth changes internally so we can access them synchronously + /** + * Internal function begin listening for auth changes + * only called after getting current user. + * @private + */ + _startListening() { FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); - FirestackAuth.listenForAuth(); } /** From 6259c4dddf3863b25f983532c089149564e9f564 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 13:48:44 +0000 Subject: [PATCH 091/275] onAuthChanged now only returns the user - or null if no authenticated user --- lib/modules/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index d53bdd3..4fc234e 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -47,7 +47,7 @@ export default class Auth extends Base { */ _onAuthStateChanged(auth) { this._authResult = auth; - this.emit('onAuthStateChanged', this._authResult); + this.emit('onAuthStateChanged', this._authResult.user || null); this.authenticated = auth ? auth.authenticated || false : false; if (auth && auth.user && !this._user) this._user = new User(this, auth); else if ((!auth || !auth.user) && this._user) this._user = null; From db0511d0d53e4d9dd6ff90ffb820bfec87449395 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 13:52:50 +0000 Subject: [PATCH 092/275] initial onAuthChanged event now returns null or user when beginning listening - re #2 --- lib/modules/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 61c558a..352bf4e 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -65,7 +65,7 @@ export default class Auth extends Base { onAuthStateChanged(listener) { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); - if (this._authResult) listener(this._authResult); + if (this._authResult) listener(this._authResult.user || null); } /** From 9697a870f1727913d091b40282408d1e2f4b38fd Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 14:33:57 +0000 Subject: [PATCH 093/275] flow type defs for auth() --- .../io/fullstack/firestack/FirestackAuth.java | 2 +- lib/modules/auth.js | 74 ++++++++++--------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 1b1d00e..2d19953 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -627,7 +627,7 @@ private WritableMap getUserMap() { userMap.putString("uid", uid); userMap.putString("providerId", provider); userMap.putBoolean("emailVerified", verified); - + if (name != null) { userMap.putString("name", name); } diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 352bf4e..301aabf 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -1,3 +1,4 @@ +// @flow import { NativeModules, NativeEventEmitter } from 'react-native'; import { Base } from './base'; @@ -7,26 +8,30 @@ import promisify from '../utils/promisify'; const FirestackAuth = NativeModules.FirestackAuth; const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); +type AuthResultType = { authenticated: boolean, user: Object|null }; + export default class Auth extends Base { - constructor(firestack, options = {}) { + _user: User|null; + _authResult: AuthResultType | null; + authenticated: boolean; + + constructor(firestack: Object, options: Object = {}) { super(firestack, options); + this._user = null; this._authResult = null; this.authenticated = false; - this._user = null; // start listening straight away // generally though the initial event fired will get ignored // but this is ok as we fake it with the getCurrentUser below FirestackAuth.listenForAuth(); - this.getCurrentUser().then((u) => { - const authResult = { authenticated: !!u }; - if (u) authResult.user = u; - this._onAuthStateChanged(authResult); + this.getCurrentUser().then((u: Object) => { + this._onAuthStateChanged({ authenticated: !!u, user: u || null }); this._startListening(); }).catch(() => { // todo check if error contains user disabled message maybe and add a disabled flag? - this._onAuthStateChanged({ authenticated: false }); + this._onAuthStateChanged({ authenticated: false, user: null }); this._startListening(); }); } @@ -45,13 +50,13 @@ export default class Auth extends Base { * @param auth * @private */ - _onAuthStateChanged(auth) { + _onAuthStateChanged(auth: AuthResultType) { this._authResult = auth; - this.emit('onAuthStateChanged', this._authResult.user || null); this.authenticated = auth ? auth.authenticated || false : false; if (auth && auth.user && !this._user) this._user = new User(this, auth); else if ((!auth || !auth.user) && this._user) this._user = null; - else this._user ? this._user._updateValues(auth) : null; + else if (this._user) this._user._updateValues(auth); + this.emit('onAuthStateChanged', this._authResult.user || null); } /* @@ -62,7 +67,7 @@ export default class Auth extends Base { * Listen for auth changes. * @param listener */ - onAuthStateChanged(listener) { + onAuthStateChanged(listener: Function) { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); if (this._authResult) listener(this._authResult.user || null); @@ -72,7 +77,7 @@ export default class Auth extends Base { * Remove auth change listener * @param listener */ - offAuthStateChanged(listener) { + offAuthStateChanged(listener: Function) { this.log.info('Removing onAuthStateChanged listener'); this.removeListener('onAuthStateChanged', listener); } @@ -83,7 +88,7 @@ export default class Auth extends Base { * @param {string} password The user's password * @return {Promise} A promise indicating the completion */ - createUserWithEmailAndPassword(email, password) { + createUserWithEmailAndPassword(email: string, password: string): Promise { this.log.info('Creating user with email and password', email); return promisify('createUserWithEmail', FirestackAuth)(email, password); } @@ -94,9 +99,9 @@ export default class Auth extends Base { * @param {string} password The user's password * @return {Promise} A promise that is resolved upon completion */ - signInWithEmailAndPassword(email, password) { + signInWithEmailAndPassword(email: string, password: string): Promise { this.log.info('Signing in user with email and password', email); - return promisify('signInWithEmail', FirestackAuth)(email, password) + return promisify('signInWithEmail', FirestackAuth)(email, password); } @@ -105,14 +110,14 @@ export default class Auth extends Base { * @param {string} email The user's _new_ email * @return {Promise} A promise resolved upon completion */ - updateEmail(email) { + updateEmail(email: string): Promise { return promisify('updateUserEmail', FirestackAuth)(email); } /** * Send verification email to current user. */ - sendEmailVerification() { + sendEmailVerification(): Promise { return promisify('sendEmailVerification', FirestackAuth)(); } @@ -121,7 +126,7 @@ export default class Auth extends Base { * @param {string} password the new password * @return {Promise} */ - updatePassword(password) { + updatePassword(password: string): Promise { return promisify('updateUserPassword', FirestackAuth)(password); } @@ -130,7 +135,7 @@ export default class Auth extends Base { * @param {Object} updates An object containing the keys listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile) * @return {Promise} */ - updateProfile(updates) { + updateProfile(updates: Object = {}): Promise { return promisify('updateUserProfile', FirestackAuth)(updates); } @@ -139,10 +144,11 @@ export default class Auth extends Base { * @param {string} customToken A self-signed custom auth token. * @return {Promise} A promise resolved upon completion */ - signInWithCustomToken(customToken) { - return promisify('signInWithCustomToken', FirestackAuth)(customToken) + signInWithCustomToken(customToken: string): Promise { + return promisify('signInWithCustomToken', FirestackAuth)(customToken); } + // TODO - below methods are not in web api / i think the're signInWithCredential /** * Sign the user in with a third-party authentication provider * @param {string} provider The name of the provider to use for login @@ -150,19 +156,19 @@ export default class Auth extends Base { * @param {string} authSecret The authToken secret granted by the provider * @return {Promise} A promise resolved upon completion */ - signInWithProvider(provider, authToken, authSecret) { - return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret) + signInWithProvider(provider: string, authToken: string, authSecret: string): Promise { + return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret); } /** - * Reauthenticate a user with a third-party authentication provider + * Re-authenticate a user with a third-party authentication provider * @param {string} provider The provider name * @param {string} token The authToken granted by the provider * @param {string} secret The authTokenSecret granted by the provider * @return {Promise} A promise resolved upon completion */ - reauthenticateWithCredentialForProvider(provider, token, secret) { - return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(provider, token, secret) + reauthenticateWithCredentialForProvider(provider: string, token: string, secret: string): Promise { + return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(provider, token, secret); } @@ -170,7 +176,7 @@ export default class Auth extends Base { * Sign a user in anonymously * @return {Promise} A promise resolved upon completion */ - signInAnonymously() { + signInAnonymously(): Promise { return promisify('signInAnonymously', FirestackAuth)(); } @@ -178,7 +184,7 @@ export default class Auth extends Base { * Send reset password instructions via email * @param {string} email The email to send password reset instructions */ - sendPasswordResetWithEmail(email) { + sendPasswordResetWithEmail(email: string): Promise { return promisify('sendPasswordResetWithEmail', FirestackAuth)(email); } @@ -186,7 +192,7 @@ export default class Auth extends Base { * Delete the current user * @return {Promise} */ - deleteUser() { + deleteUser(): Promise { return promisify('deleteUser', FirestackAuth)(); } @@ -194,7 +200,7 @@ export default class Auth extends Base { * get the token of current user * @return {Promise} */ - getToken() { + getToken(): Promise { return promisify('getToken', FirestackAuth)(); } @@ -203,7 +209,7 @@ export default class Auth extends Base { * Sign the current user out * @return {Promise} */ - signOut() { + signOut(): Promise { return promisify('signOut', FirestackAuth)(); } @@ -211,7 +217,7 @@ export default class Auth extends Base { * Get the currently signed in user * @return {Promise} */ - getCurrentUser() { + getCurrentUser(): Promise { return promisify('getCurrentUser', FirestackAuth)(); } @@ -219,11 +225,11 @@ export default class Auth extends Base { * Get the currently signed in user * @return {Promise} */ - get currentUser() { + get currentUser(): User|null { return this._user; } - get namespace() { + get namespace(): string { return 'firestack:auth'; } } From 19018c10bfeb8a8d15069f9336896ec8e8632598 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 14:50:04 +0000 Subject: [PATCH 094/275] Bring iOS database interface inline with Android --- ios/Firestack/FirestackDatabase.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index fb684fc..918feae 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -350,12 +350,13 @@ @implementation FirestackDatabase RCT_EXPORT_METHOD(on:(NSString *) path - modifiers:(NSArray *) modifiers + modifiersString:(NSString *) modifiersString + modifiersArray:(NSArray *) modifiersArray name:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { FirestackDBReference *r = [self getDBHandle:path]; - FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiers]; + FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiersArray]; if (![r isListeningTo:eventName]) { id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) { @@ -398,13 +399,14 @@ @implementation FirestackDatabase } RCT_EXPORT_METHOD(onOnce:(NSString *) path - modifiers:(NSArray *) modifiers + modifiersString:(NSString *) modifiersString + modifiersArray:(NSArray *) modifiersArray name:(NSString *) name callback:(RCTResponseSenderBlock) callback) { FirestackDBReference *r = [self getDBHandle:path]; int eventType = [r eventTypeFromName:name]; - FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiers]; + FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiersArray]; [ref observeSingleEventOfType:eventType withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { @@ -425,6 +427,7 @@ @implementation FirestackDatabase } RCT_EXPORT_METHOD(off:(NSString *)path + modifiersString:(NSString *) modifiersString eventName:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { From 87760cc29ac189b972ba9b67d0061f101464c5fb Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:10:26 +0000 Subject: [PATCH 095/275] added auth listener before calling listen --- lib/modules/auth.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 301aabf..8377b54 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -24,27 +24,17 @@ export default class Auth extends Base { // start listening straight away // generally though the initial event fired will get ignored // but this is ok as we fake it with the getCurrentUser below + FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); FirestackAuth.listenForAuth(); this.getCurrentUser().then((u: Object) => { this._onAuthStateChanged({ authenticated: !!u, user: u || null }); - this._startListening(); }).catch(() => { // todo check if error contains user disabled message maybe and add a disabled flag? this._onAuthStateChanged({ authenticated: false, user: null }); - this._startListening(); }); } - /** - * Internal function begin listening for auth changes - * only called after getting current user. - * @private - */ - _startListening() { - FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); - } - /** * Internal auth changed listener * @param auth From 1b730b16f8d58e70513f415cc59561d11a06e1b7 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:11:28 +0000 Subject: [PATCH 096/275] implemented new promisify util --- lib/utils/promisify.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/utils/promisify.js b/lib/utils/promisify.js index a967760..d416274 100644 --- a/lib/utils/promisify.js +++ b/lib/utils/promisify.js @@ -1,14 +1,14 @@ -export const promisify = (fn, NativeModule) => (...args) => { - return new Promise((resolve, reject) => { - const handler = (err, resp) => { - setTimeout(() => { - err ? reject(err) : resolve(resp); - }, 0); - } - args.push(handler); - (typeof fn === 'function' ? fn : NativeModule[fn]) - .call(NativeModule, ...args); +const handler = (resolve, reject, err, resp) => { + setImmediate(() => { + if (err) return reject(err); + return resolve(resp); }); }; -export default promisify +export default(fn, NativeModule) => (...args) => { + return new Promise((resolve, reject) => { + const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; + if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.')); + return fn.apply(NativeModule, ...args, handler.bind(handler, resolve, reject)); + }); +}; From 912fd0e1527e7f7a3bb48712f9b01d764c726294 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:14:11 +0000 Subject: [PATCH 097/275] fixed a derp in promisify --- lib/utils/promisify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/promisify.js b/lib/utils/promisify.js index d416274..2753b6b 100644 --- a/lib/utils/promisify.js +++ b/lib/utils/promisify.js @@ -9,6 +9,6 @@ export default(fn, NativeModule) => (...args) => { return new Promise((resolve, reject) => { const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.')); - return fn.apply(NativeModule, ...args, handler.bind(handler, resolve, reject)); + return _fn.apply(NativeModule, ...args, handler.bind(handler, resolve, reject)); }); }; From 145690fc32503b315f68dc2607f4faa7f6ef1bd3 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:17:24 +0000 Subject: [PATCH 098/275] fixed a derp in promisify - apply args should be an array --- lib/utils/promisify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/promisify.js b/lib/utils/promisify.js index 2753b6b..9f0e089 100644 --- a/lib/utils/promisify.js +++ b/lib/utils/promisify.js @@ -9,6 +9,6 @@ export default(fn, NativeModule) => (...args) => { return new Promise((resolve, reject) => { const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.')); - return _fn.apply(NativeModule, ...args, handler.bind(handler, resolve, reject)); + return _fn.apply(NativeModule, [...args, handler.bind(handler, resolve, reject)]); }); }; From 7e03cfff4c8da0ae195b868ecd1ab7cde864c28a Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:28:51 +0000 Subject: [PATCH 099/275] Android: getCurrentUser shouldn't return an error - just null if no users --- android/src/main/java/io/fullstack/firestack/FirestackAuth.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 2d19953..9561e0d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -72,7 +72,7 @@ private void callbackNoUser(Callback callback, Boolean isError) { if (isError) { callback.invoke(err); } else { - callback.invoke(null, err); + callback.invoke(null, null); } } From 58507f0782d5f7767eff5090278dba868446f62e Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 15:37:24 +0000 Subject: [PATCH 100/275] Update analytics.md --- docs/api/analytics.md | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/docs/api/analytics.md b/docs/api/analytics.md index ab1e469..daefe5c 100644 --- a/docs/api/analytics.md +++ b/docs/api/analytics.md @@ -19,70 +19,62 @@ own app. The Firebase SDK includes a number of pre-set events which are automati 'user_engagement', ``` -#### logEvent(event: string, params?: Object) +#### `logEvent(event: string, params?: Object): void` -Log a custom event with optional params. Can be synchronous or return a Promise +Log a custom event with optional params. ```javascript -firestack.analytics() - .logEvent('clicked_advert', { id: 1337 }) - .then(() => { - console.log('Event has been logged successfully'); - }); +firestack.analytics().logEvent('clicked_advert', { id: 1337 }); ``` -#### setAnalyticsCollectionEnabled(enabled: boolean) +#### `setAnalyticsCollectionEnabled(enabled: boolean): void` Sets whether analytics collection is enabled for this app on this device. ```javascript -firestack.analytics() - .setAnalyticsCollectionEnabled(false); +firestack.analytics().setAnalyticsCollectionEnabled(false); ``` -#### setCurrentScreen(screenName: string, screenClassOverride: string) +#### `setCurrentScreen(screenName: string, screenClassOverride?: string): void` Sets the current screen name, which specifies the current visual context in your app. +> Whilst `screenClassOverride` is optional, it is recommended it is always sent as your current class name, for example on Android it will always show as 'MainActivity' if not specified. + ```javascript -firestack.analytics() - .setCurrentScreen('user_profile'); +firestack.analytics().setCurrentScreen('user_profile'); ``` -#### setMinimumSessionDuration(miliseconds: number) +#### `setMinimumSessionDuration(miliseconds: number): void` Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds). ```javascript -firestack.analytics() - .setMinimumSessionDuration(15000); +firestack.analytics().setMinimumSessionDuration(15000); ``` -#### setSessionTimeoutDuration(miliseconds: number) +#### `setSessionTimeoutDuration(miliseconds: number): void` Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes). ```javascript -firestack.analytics() - .setSessionTimeoutDuration(900000); +firestack.analytics().setSessionTimeoutDuration(900000); ``` -#### setUserId(id: string) +#### `setUserId(id: string): void` Gives a user a uniqiue identificaition. ```javascript const id = firestack.auth().currentUser.uid; -firestack.analytics() - .setUserId(id); +firestack.analytics().setUserId(id); ``` -#### setUserProperty(name: string, value: string) +#### `setUserProperty(name: string, value: string): void` Sets a key/value pair of data on the current user. ```javascript -firestack.analytics() - .setUserProperty('nickname', 'foobar'); +firestack.analytics().setUserProperty('nickname', 'foobar'); ``` From 7e3500767ed5331c6a404ef4348cdd32bb0f4c7e Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 15:45:35 +0000 Subject: [PATCH 101/275] Update authentication.md --- docs/api/authentication.md | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 23162b3..af3f797 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -4,36 +4,36 @@ Firestack handles authentication for us out of the box, both with email/password > Android requires the Google Play services to installed for authentication to function. -## Local Auth +#### [`onAuthStateChanged(event: Function): Function`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) -#### [onAuthStateChanged(event: Function)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) - -Listen for changes in the users auth state (logging in and out). +Listen for changes in the users auth state (logging in and out). This method returns a unsubscribe function to stop listening to events. Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use. ```javascript -firestack.auth().onAuthStateChanged((evt) => { - // evt is the authentication event, it contains an `error` key for carrying the - // error message in case of an error and a `user` key upon successful authentication - if (!evt.authenticated) { - // There was an error or there is no user - console.error(evt.error) - } else { - // evt.user contains the user details - console.log('User details', evt.user); - } -}) -.then(() => console.log('Listening for authentication changes')) -``` +class Example extends React.Component { -#### offAuthStateChanged() - -Remove the `onAuthStateChanged` listener. -This is important to release resources from our app when we don't need to hold on to the listener any longer. + constructor() { + super(); + this.unsubscribe = null; + } + + componentDidMount() { + this.unsubscribe = firebase.auth().onAuthStateChanged(function(user) { + if (user) { + // User is signed in. + } + }); + } + + componentWillUnmount() { + if (this.listener) { + this.unsubscribe(); + } + } -```javascript -firestack.auth().offAuthStateChanged() +} ``` + #### [createUserWithEmailAndPassword(email: string, password: string)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) We can create a user by calling the `createUserWithEmailAndPassword()` function. From 4cb24bd738dc3ae00c5362dd913bff670e5f6e94 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 15:47:06 +0000 Subject: [PATCH 102/275] offAuthStateChanged is now internal only - onAuthStateChanged now returns the unsubribe fn - as per web api --- lib/modules/auth.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 8377b54..6ce64f7 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -61,13 +61,14 @@ export default class Auth extends Base { this.log.info('Creating onAuthStateChanged listener'); this.on('onAuthStateChanged', listener); if (this._authResult) listener(this._authResult.user || null); + return this._offAuthStateChanged.bind(this, listener); } /** * Remove auth change listener * @param listener */ - offAuthStateChanged(listener: Function) { + _offAuthStateChanged(listener: Function) { this.log.info('Removing onAuthStateChanged listener'); this.removeListener('onAuthStateChanged', listener); } From cd40d7333a9e94cc833fcf03f6b3f0eabe4d648d Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 16:19:42 +0000 Subject: [PATCH 103/275] Bring iOS auth interface inline with Android interface; Introduce callback methods to simplify duplicated code --- ios/Firestack/FirestackAuth.m | 322 +++++++++++++++++----------------- 1 file changed, 164 insertions(+), 158 deletions(-) diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index 0eba61c..a7ac803 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -20,24 +20,23 @@ @implementation FirestackAuth (RCTResponseSenderBlock) callBack) { @try { - [[FIRAuth auth] signInAnonymouslyWithCompletion - :^(FIRUser *user, NSError *error) { - if (!user) { - NSDictionary *evt = @{ - @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, - @"msg": [error localizedDescription] - }; - - - [self sendJSEvent:AUTH_CHANGED_EVENT - props: evt]; - - callBack(@[evt]); - } else { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callBack(@[[NSNull null], userProps]); - } - }]; + [[FIRAuth auth] signInAnonymouslyWithCompletion + :^(FIRUser *user, NSError *error) { + if (!user) { + NSDictionary *evt = @{ + @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, + @"msg": [error localizedDescription] + }; + + + [self sendJSEvent:AUTH_CHANGED_EVENT + props: evt]; + + callBack(@[evt]); + } else { + [self userCallback:callBack user:user]; + } + }]; } @catch(NSException *ex) { NSDictionary *eventError = @{ @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, @@ -58,16 +57,11 @@ @implementation FirestackAuth [[FIRAuth auth] signInWithCustomToken:customToken completion:^(FIRUser *user, NSError *error) { - + if (user != nil) { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + [self userCallback:callback user:user]; } else { - NSDictionary *err = - [FirestackErrors handleFirebaseError:AUTH_ERROR_EVENT - error:error - withUser:user]; - callback(@[err]); + [self userErrorCallback:callback error:error user:user msg:AUTH_ERROR_EVENT]; } }]; } @@ -87,14 +81,13 @@ @implementation FirestackAuth }; return callback(@[err]); } - + @try { [[FIRAuth auth] signInWithCredential:credential completion:^(FIRUser *user, NSError *error) { if (user != nil) { // User is signed in. - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + [self userCallback:callback user:user]; } else { NSLog(@"An error occurred: %@", [error localizedDescription]); // No user is signed in. @@ -134,7 +127,7 @@ @implementation FirestackAuth self->authListenerHandle = [[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth *_Nonnull auth, FIRUser *_Nullable user) { - + if (user != nil) { // User is signed in. [self userPropsFromFIRUserWithToken:user @@ -183,10 +176,9 @@ @implementation FirestackAuth RCT_EXPORT_METHOD(getCurrentUser:(RCTResponseSenderBlock)callback) { FIRUser *user = [FIRAuth auth].currentUser; - + if (user != nil) { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + [self userCallback:callback user:user]; } else { // No user is signed in. NSDictionary *err = @{ @@ -206,8 +198,7 @@ @implementation FirestackAuth completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { if (user != nil) { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + [self userCallback:callback user:user]; } else { NSDictionary *err = @{ @"error": @"createUserWithEmailError", @@ -227,17 +218,9 @@ @implementation FirestackAuth password:password completion:^(FIRUser *user, NSError *error) { if (user != nil) { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - - callback(@[[NSNull null], @{ - @"user": userProps - }]); + [self userCallback:callback user:user]; } else { - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"signinError" - error:error - withUser:user]; - callback(@[err]); + [self userErrorCallback:callback error:error user:user msg:@"signinError"]; } }]; } @@ -246,49 +229,47 @@ @implementation FirestackAuth callback:(RCTResponseSenderBlock) callback) { FIRUser *user = [FIRAuth auth].currentUser; - - [user updateEmail:email completion:^(NSError *_Nullable error) { - if (error) { - // An error happened. - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"updateEmailError" - error:error - withUser:user]; - callback(@[err]); - } else { - // Email updated. - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); - } - }]; + + if (user) { + [user updateEmail:email completion:^(NSError *_Nullable error) { + if (error) { + // An error happened. + [self userErrorCallback:callback error:error user:user msg:@"updateEmailError"]; + } else { + // Email updated. + [self userCallback:callback user:user]; + } + }]; + } else { + [self noUserCallback:callback isError:true]; + } } RCT_EXPORT_METHOD(updateUserPassword:(NSString *)newPassword callback:(RCTResponseSenderBlock) callback) { - + FIRUser *user = [FIRAuth auth].currentUser; - - [user updatePassword:newPassword completion:^(NSError *_Nullable error) { - if (error) { - // An error happened. - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"updateUserPasswordError" - error:error - withUser:user]; - callback(@[err]); - } else { - // Email updated. - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); - } - }]; + + if (user) { + [user updatePassword:newPassword completion:^(NSError *_Nullable error) { + if (error) { + // An error happened. + [self userErrorCallback:callback error:error user:user msg:@"updateUserPasswordError"]; + } else { + // Email updated. + [self userCallback:callback user:user]; + } + }]; + } else { + [self noUserCallback:callback isError:true]; + } } RCT_EXPORT_METHOD(sendPasswordResetWithEmail:(NSString *)email callback:(RCTResponseSenderBlock) callback) { - + [[FIRAuth auth] sendPasswordResetWithEmail:email completion:^(NSError *_Nullable error) { if (error) { @@ -310,54 +291,52 @@ @implementation FirestackAuth RCT_EXPORT_METHOD(deleteUser:(RCTResponseSenderBlock) callback) { FIRUser *user = [FIRAuth auth].currentUser; - - [user deleteWithCompletion:^(NSError *_Nullable error) { - if (error) { - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"deleteUserError" - error:error - withUser:user]; - callback(@[err]); - } else { - callback(@[[NSNull null], @{@"result": @(true)}]); - } - }]; + + if (user) { + [user deleteWithCompletion:^(NSError *_Nullable error) { + if (error) { + [self userErrorCallback:callback error:error user:user msg:@"deleteUserError"]; + } else { + callback(@[[NSNull null], @{@"result": @(true)}]); + } + }]; + } else { + [self noUserCallback:callback isError:true]; + } } RCT_EXPORT_METHOD(getToken:(RCTResponseSenderBlock) callback) { FIRUser *user = [FIRAuth auth].currentUser; - - [user getTokenWithCompletion:^(NSString *token, NSError *_Nullable error) { - if (error) { - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"getTokenError" - error:error - withUser:user]; - callback(@[err]); - } else { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], @{@"token": token, @"user": userProps}]); - } - }]; + + if (user) { + [user getTokenWithCompletion:^(NSString *token, NSError *_Nullable error) { + if (error) { + [self userErrorCallback:callback error:error user:user msg:@"getTokenError"]; + } else { + [self userCallback:callback user:user]; + } + }]; + } else { + [self noUserCallback:callback isError:true]; + } } RCT_EXPORT_METHOD(getTokenWithCompletion:(RCTResponseSenderBlock) callback) { FIRUser *user = [FIRAuth auth].currentUser; - - [user getTokenWithCompletion:^(NSString *token , NSError *_Nullable error) { - if (error) { - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"getTokenWithCompletion" - error:error - withUser:user]; - callback(@[err]); - } else { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], @{@"token": token, @"user": userProps}]); - } - }]; + + if (user) { + [user getTokenWithCompletion:^(NSString *token , NSError *_Nullable error) { + if (error) { + [self userErrorCallback:callback error:error user:user msg:@"getTokenWithCompletion"]; + } else { + [self userCallback:callback user:user]; + } + }]; + } else { + [self noUserCallback:callback isError:true]; + } } RCT_EXPORT_METHOD(reauthenticateWithCredentialForProvider: @@ -375,16 +354,12 @@ @implementation FirestackAuth }; return callback(@[err]); } - + FIRUser *user = [FIRAuth auth].currentUser; - + [user reauthenticateWithCredential:credential completion:^(NSError *_Nullable error) { if (error) { - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"reauthenticateWithCredentialForProviderError" - error:error - withUser:user]; - callback(@[err]); + [self userErrorCallback:callback error:error user:user msg:@"reauthenticateWithCredentialForProviderError"]; } else { callback(@[[NSNull null], @{@"result": @(true)}]); } @@ -396,38 +371,38 @@ @implementation FirestackAuth callback:(RCTResponseSenderBlock) callback) { FIRUser *user = [FIRAuth auth].currentUser; - FIRUserProfileChangeRequest *changeRequest = [user profileChangeRequest]; - - NSMutableArray *allKeys = [[userProps allKeys] mutableCopy]; - for (NSString *key in allKeys) { - // i.e. changeRequest.displayName = userProps[displayName]; - @try { - if ([key isEqualToString:@"photoURL"]) { - NSURL *url = [NSURL URLWithString:[userProps valueForKey:key]]; - [changeRequest setValue:url forKey:key]; - } else { - [changeRequest setValue:[userProps objectForKey:key] forKey:key]; - } - } - @catch (NSException *exception) { - NSLog(@"Exception occurred while configuring: %@", exception); - } - @finally { - [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) { - if (error) { - // An error happened. - NSDictionary *err = - [FirestackErrors handleFirebaseError:@"updateEmailError" - error:error - withUser:user]; - callback(@[err]); + + if (user) { + FIRUserProfileChangeRequest *changeRequest = [user profileChangeRequest]; + + NSMutableArray *allKeys = [[userProps allKeys] mutableCopy]; + for (NSString *key in allKeys) { + // i.e. changeRequest.displayName = userProps[displayName]; + @try { + if ([key isEqualToString:@"photoURL"]) { + NSURL *url = [NSURL URLWithString:[userProps valueForKey:key]]; + [changeRequest setValue:url forKey:key]; } else { - // Profile updated. - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + [changeRequest setValue:[userProps objectForKey:key] forKey:key]; } - }]; + } + @catch (NSException *exception) { + NSLog(@"Exception occurred while configuring: %@", exception); + } + @finally { + [changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) { + if (error) { + // An error happened. + [self userErrorCallback:callback error:error user:user msg:@"updateEmailError"]; + } else { + // Profile updated. + [self userCallback:callback user:user]; + } + }]; + } } + } else { + [self noUserCallback:callback isError:true]; } } @@ -442,12 +417,12 @@ - (NSDictionary *) userPropsFromFIRUser:(FIRUser *) user @"refreshToken": user.refreshToken, @"providerID": user.providerID } mutableCopy]; - + if ([user valueForKey:@"photoURL"] != nil) { [userProps setValue: [NSString stringWithFormat:@"%@", user.photoURL] forKey:@"photoURL"]; } - + return userProps; } @@ -459,7 +434,7 @@ - (void) userPropsFromFIRUserWithToken:(FIRUser *) user if (error != nil) { return callback(nil, error); } - + [userProps setValue:token forKey:@"idToken"]; callback(userProps, nil); }]; @@ -493,15 +468,46 @@ - (void) sendJSEvent:(NSString *)title props:(NSDictionary *)props { @try { - if (self->listening) { - [self sendEventWithName:title - body:props]; - } + if (self->listening) { + [self sendEventWithName:title + body:props]; + } } @catch (NSException *err) { NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); } } +- (void) userCallback:(RCTResponseSenderBlock) callback + user:(FIRUser *) user { + NSDictionary *userProps = @{ + @"user": [self userPropsFromFIRUser:user] + }; + callback(@[[NSNull null], userProps]); +} + +- (void) noUserCallback:(RCTResponseSenderBlock) callback + isError:(Boolean) isError { + if (isError) { + NSDictionary *err = @{ + @"error": @"Unhandled provider" + }; + return callback(@[err]); + + } + return callback(@[[NSNull null], [NSNull null]]); +} + +- (void) userErrorCallback:(RCTResponseSenderBlock) callback + error:(NSError *)error + user:(FIRUser *) user + msg:(NSString *) msg { + // An error happened. + NSDictionary *err = [FirestackErrors handleFirebaseError:msg + error:error + withUser:user]; + callback(@[err]); +} + @end From 77d36d3c35a165427e3ef6e843bd1fef50775fcc Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 16:27:29 +0000 Subject: [PATCH 104/275] Update authentication.md --- docs/api/authentication.md | 241 ++++++++++++++++++++++++++----------- 1 file changed, 172 insertions(+), 69 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index af3f797..b5595f0 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -4,6 +4,20 @@ Firestack handles authentication for us out of the box, both with email/password > Android requires the Google Play services to installed for authentication to function. +## Auth + +### Properties + +##### `authenticated: boolean` + +Returns the current Firebase authentication state. + +##### `currentUser: [User](#) | null` + +Returns the currently signed-in user (or null). + +### Methods + #### [`onAuthStateChanged(event: Function): Function`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) Listen for changes in the users auth state (logging in and out). This method returns a unsubscribe function to stop listening to events. Always ensure you unsubscribe from the listener when no longer needed to prevent updates to components no longer in use. @@ -33,8 +47,7 @@ class Example extends React.Component { } ``` - -#### [createUserWithEmailAndPassword(email: string, password: string)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) +#### [`createUserWithEmailAndPassword(email: string, password: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) We can create a user by calling the `createUserWithEmailAndPassword()` function. The method accepts two parameters, an email and a password. @@ -46,10 +59,10 @@ firestack.auth().createUserWithEmailAndPassword('ari@fullstack.io', '123456') }) .catch((err) => { console.error('An error occurred', err); - }) + }); ``` -#### [signInWithEmailAndPassword(email: string, password: string)](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) +#### [`signInWithEmailAndPassword(email: string, password: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) To sign a user in with their email and password, use the `signInWithEmailAndPassword()` function. It accepts two parameters, the user's email and password: @@ -61,10 +74,10 @@ firestack.auth().signInWithEmailAndPassword('ari@fullstack.io', '123456') }) .catch((err) => { console.error('User signin error', err); - }) + }); ``` -#### [signInAnonymously()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInAnonymously) +#### [`signInAnonymously(): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInAnonymously) Sign an anonymous user. If the user has already signed in, that user will be returned. @@ -75,126 +88,216 @@ firestack.auth().signInAnonymously() }) .catch((err) => { console.error('Anonymous user signin error', err); - }) + }); +``` + +#### [`signInWithCredential(credential: Object): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithCredential) + +Sign in the user with a 3rd party credential provider. `credential` requires the following properties: + +```javascript +{ + provider: string, + token: string, + secret: string +} ``` -#### signInWithProvider() +```javascript +const credential = { + provider: 'facebook.com', + token: '12345', + secret: '6789', +}; -We can use an external authentication provider, such as twitter/facebook for authentication. In order to use an external provider, we need to include another library to handle authentication. +firestack.auth().signInWithCredential(credential) + .then((user) => { + console.log('User successfully signed in', user) + }) + .catch((err) => { + console.error('User signin error', err); + }) +``` -> By using a separate library, we can keep our dependencies a little lower and the size of the application down. +#### [`signInWithCustomToken(token: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithCustomToken) -#### signInWithCustomToken() +Sign a user in with a self-signed [JWT](https://jwt.io) token. To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: ```javascript -firestack.auth().signInWithCustomToken(TOKEN) +firestack.auth().signInWithCustomToken('12345') .then((user) => { console.log('User successfully logged in', user) }) .catch((err) => { console.error('User signin error', err); + }); +``` + +#### [`sendPasswordResetEmail(email: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#sendPasswordResetEmail) + +Sends a password reset email to the given email address. + +```javascript +firestack.auth().sendPasswordResetEmail('foo@bar.com') + .then(() => { + console.log('Password reset email sent'); }) + .catch((error) => { + console.error('Unable send password reset email', error); + }); ``` -#### [updateUserEmail()](https://firebase.google.com/docs/reference/js/firebase.User#updateEmail) +#### [`confirmPasswordReset(code: string, newPassword: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset) -We can update the current user's email by using the command: `updateUserEmail()`. -It accepts a single argument: the user's new email: +Completes the password reset process, given a confirmation code and new password. ```javascript -firestack.auth().updateUserEmail('ari+rocks@fullstack.io') - .then((res) => console.log('Updated user email')) - .catch(err => console.error('There was an error updating user email')) +firestack.auth().confirmPasswordReset('1234', 'barfoo4321') + .then(() => { + console.log('Password reset successfully'); + }) + .catch((error) => { + console.error('Unable to reset password', error); + }); ``` -#### [updateUserPassword()](https://firebase.google.com/docs/reference/js/firebase.User#updatePassword) +#### [`signOut(): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset) -We can update the current user's password using the `updateUserPassword()` method. -It accepts a single parameter: the new password for the current user +Completes the password reset process, given a confirmation code and new password. ```javascript -firestack.auth().updateUserPassword('somethingReallyS3cr3t733t') - .then(res => console.log('Updated user password')) - .catch(err => console.error('There was an error updating your password')) +firestack.auth().signOut() + .then(() => { + console.log('User signed out successfully'); + }) + .catch(); ``` -#### [updateUserProfile()](https://firebase.google.com/docs/auth/web/manage-users#update_a_users_profile) +## User + +User class returned from `firestack.auth().currentUser`. -To update the current user's profile, we can call the `updateUserProfile()` method. -It accepts a single parameter: +### Properties -* object which contains updated key/values for the user's profile. -Possible keys are listed [here](https://firebase.google.com/docs/auth/ios/manage-users#update_a_users_profile). +##### `displayName: string | null` - The user's display name (if available). +##### `email: string | null` - The user's email address (if available). +##### `emailVerified: boolean` - True if the user's email address has been verified. +##### `isAnonymous: boolean` +##### `photoURL: string | null` - The URL of the user's profile picture (if available). +##### `providerData: Object | null` - Additional provider-specific information about the user. +##### `providerId: string | null` - The authentication provider ID for the current user. For example, 'facebook.com', or 'google.com'. +##### `uid: string` - The user's unique ID. + +### Methods + +#### [`delete(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#delete) + +Delete the current user. ```javascript -firestack.auth() - .updateUserProfile({ - displayName: 'Ari Lerner' - }) - .then(res => console.log('Your profile has been updated')) - .catch(err => console.error('There was an error :(')) +firestack.auth().currentUser + .delete() + .then() + .catch(); ``` -#### [sendPasswordResetWithEmail()](https://firebase.google.com/docs/auth/web/manage-users#send_a_password_reset_email) +#### [`getToken(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#getToken) -To send a password reset for a user based upon their email, we can call the `sendPasswordResetWithEmail()` method. -It accepts a single parameter: the email of the user to send a reset email. +Returns the users authentication token. ```javascript -firestack.auth().sendPasswordResetWithEmail('ari+rocks@fullstack.io') - .then(res => console.log('Check your inbox for further instructions')) - .catch(err => console.error('There was an error :(')) +firestack.auth().currentUser + .getToken() + .then((token) => {}) + .catch(); ``` -#### [deleteUser()](https://firebase.google.com/docs/auth/web/manage-users#delete_a_user) -It's possible to delete a user completely from your account on Firebase. -Calling the `deleteUser()` method will take care of this for you. + +#### [`reauthenticate(credential: Object): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reauthenticate) + +Reauthenticate the current user with credentials: ```javascript -firestack.auth() - .deleteUser() - .then(res => console.log('Sad to see you go')) - .catch(err => console.error('There was an error - Now you are trapped!')) +{ + provider: string, + token: string, + secret: string +} +``` + +```javascript +const credentials = { + provider: 'facebook.com', + token: '12345', + secret: '6789', +}; + +firestack.auth().currentUser + .reauthenticate(credentials) + .then() + .catch(); ``` -#### getToken() +#### [`reload(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reload) -If you want user's token, use `getToken()` method. +Refreshes the current user. ```javascript -firestack.auth() +firestack.auth().currentUser .getToken() - .then(res => console.log(res.token)) - .catch(err => console.error('error')) + .then((user) => {}) + .catch(); ``` -#### [signOut()](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signOut) +#### [`sendEmailVerification(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#sendEmailVerification) -To sign the current user out, use the `signOut()` method. -It accepts no parameters +Sends a verification email to a user. This will Promise reject is the user is anonymous. ```javascript -firestack.auth() - .signOut() - .then(res => console.log('You have been signed out')) - .catch(err => console.error('Uh oh... something weird happened')) +firestack.auth().currentUser + .sendEmailVerification() + .then() + .catch(); ``` +#### [updateEmail(email: string)](https://firebase.google.com/docs/reference/js/firebase.User#updateEmail) -#### getCurrentUser() +Updates the user's email address. See Firebase docs for more information on security & email validation. This will Promise reject is the user is anonymous. -Although you _can_ get the current user using the `getCurrentUser()` method, it's better to use this from within the callback function provided by `listenForAuth()`. -However, if you need to get the current user, call the `getCurrentUser()` method: +```javascript +firestack.auth().updateUserEmail('foo@bar.com') + .then() + .catch(); +``` + +#### [updatePassword(password: string)](https://firebase.google.com/docs/reference/js/firebase.User#updatePassword) + +Important: this is a security sensitive operation that requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call firebase.User#reauthenticate. This will Promise reject is the user is anonymous. ```javascript -firestack.auth() - .getCurrentUser() - .then(user => console.log('The currently logged in user', user)) - .catch(err => console.error('An error occurred')) +firestack.auth().updateUserPassword('foobar1234') + .then() + .catch(); ``` -## Social Auth +#### [updateProfile(profile: Object)](https://firebase.google.com/docs/reference/js/firebase.User#updateProfile) + +Updates a user's profile data. Profile data should be an object of fields to update: -TODO +```javascript +{ + displayName: string, + photoURL: string, +} +``` + +```javascript +firestack.auth() + .updateProfile({ + displayName: 'Ari Lerner' + }) + .then() + .catch(); +``` From 96e30147262660c972cfe090e82a50ce53d410c7 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 16:34:23 +0000 Subject: [PATCH 105/275] Update authentication.md --- docs/api/authentication.md | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index b5595f0..10e8e01 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -8,13 +8,8 @@ Firestack handles authentication for us out of the box, both with email/password ### Properties -##### `authenticated: boolean` - -Returns the current Firebase authentication state. - -##### `currentUser: [User](#) | null` - -Returns the currently signed-in user (or null). +##### `authenticated: boolean` - Returns the current Firebase authentication state. +##### `currentUser: [User](#) | null` - Returns the currently signed-in user (or null). ### Methods @@ -137,7 +132,7 @@ firestack.auth().signInWithCustomToken('12345') #### [`sendPasswordResetEmail(email: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#sendPasswordResetEmail) -Sends a password reset email to the given email address. +Sends a password reset email to the given email address. Unlike the web SDK, the email will contain a password reset link rather than a code. ```javascript firestack.auth().sendPasswordResetEmail('foo@bar.com') @@ -149,20 +144,6 @@ firestack.auth().sendPasswordResetEmail('foo@bar.com') }); ``` -#### [`confirmPasswordReset(code: string, newPassword: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset) - -Completes the password reset process, given a confirmation code and new password. - -```javascript -firestack.auth().confirmPasswordReset('1234', 'barfoo4321') - .then(() => { - console.log('Password reset successfully'); - }) - .catch((error) => { - console.error('Unable to reset password', error); - }); -``` - #### [`signOut(): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset) Completes the password reset process, given a confirmation code and new password. From 1664b53bc4a2ed6f7994d8c49fc17a6116f09e68 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 16:34:56 +0000 Subject: [PATCH 106/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 10e8e01..cdf09ed 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -9,7 +9,7 @@ Firestack handles authentication for us out of the box, both with email/password ### Properties ##### `authenticated: boolean` - Returns the current Firebase authentication state. -##### `currentUser: [User](#) | null` - Returns the currently signed-in user (or null). +##### `currentUser: [User](#user) | null` - Returns the currently signed-in user (or null). ### Methods From 443957fa3f1473a2d6fa54dbc01e03bc9b50fd79 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 16:35:30 +0000 Subject: [PATCH 107/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index cdf09ed..14437b1 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -9,7 +9,7 @@ Firestack handles authentication for us out of the box, both with email/password ### Properties ##### `authenticated: boolean` - Returns the current Firebase authentication state. -##### `currentUser: [User](#user) | null` - Returns the currently signed-in user (or null). +##### `currentUser: [User](/docs/api/authentication#user) | null` - Returns the currently signed-in user (or null). ### Methods From 5cfd01ff51fa45bc967ce28264b70eaec14c81a5 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Fri, 18 Nov 2016 16:36:26 +0000 Subject: [PATCH 108/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 14437b1..dd234cf 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -9,7 +9,7 @@ Firestack handles authentication for us out of the box, both with email/password ### Properties ##### `authenticated: boolean` - Returns the current Firebase authentication state. -##### `currentUser: [User](/docs/api/authentication#user) | null` - Returns the currently signed-in user (or null). +##### `currentUser: User | null` - Returns the currently signed-in user (or null). See the [User](/docs/api/authentication#user) class documentation for further usage. ### Methods From d237eb9d694e9f3f2f954fb184e33c8a5a0adc71 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 16:39:47 +0000 Subject: [PATCH 109/275] added missing reauthenticate method for currentUser and renamed `sendPasswordResetWithEmail` to `sendPasswordResetEmail` --- lib/modules/auth.js | 20 +++++++------------- lib/modules/user.js | 42 +++++++++++++++++++++++------------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 6ce64f7..6963fbf 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -95,6 +95,7 @@ export default class Auth extends Base { return promisify('signInWithEmail', FirestackAuth)(email, password); } + // TODO move user methods to User class /** * Update the current user's email @@ -139,30 +140,23 @@ export default class Auth extends Base { return promisify('signInWithCustomToken', FirestackAuth)(customToken); } - // TODO - below methods are not in web api / i think the're signInWithCredential + // TODO credential type definition /** * Sign the user in with a third-party authentication provider - * @param {string} provider The name of the provider to use for login - * @param {string} authToken The authToken granted by the provider - * @param {string} authSecret The authToken secret granted by the provider * @return {Promise} A promise resolved upon completion */ - signInWithProvider(provider: string, authToken: string, authSecret: string): Promise { - return promisify('signInWithProvider', FirestackAuth)(provider, authToken, authSecret); + signInWithCredential(credential): Promise { + return promisify('signInWithProvider', FirestackAuth)(credential.provider, credential.token, credential.secret); } /** * Re-authenticate a user with a third-party authentication provider - * @param {string} provider The provider name - * @param {string} token The authToken granted by the provider - * @param {string} secret The authTokenSecret granted by the provider * @return {Promise} A promise resolved upon completion */ - reauthenticateWithCredentialForProvider(provider: string, token: string, secret: string): Promise { - return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(provider, token, secret); + reauthenticateUser(credential): Promise { + return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(credential.provider, credential.token, credential.secret); } - /** * Sign a user in anonymously * @return {Promise} A promise resolved upon completion @@ -175,7 +169,7 @@ export default class Auth extends Base { * Send reset password instructions via email * @param {string} email The email to send password reset instructions */ - sendPasswordResetWithEmail(email: string): Promise { + sendPasswordResetEmail(email: string): Promise { return promisify('sendPasswordResetWithEmail', FirestackAuth)(email); } diff --git a/lib/modules/user.js b/lib/modules/user.js index 605795f..2292fbb 100644 --- a/lib/modules/user.js +++ b/lib/modules/user.js @@ -1,7 +1,5 @@ -import promisify from '../utils/promisify'; -import { Base } from './base'; - - +// TODO refreshToken property +// TODO reload() method export default class User { constructor(authClass, authObj) { this._auth = authClass; @@ -33,7 +31,7 @@ export default class User { * @returns {*} * @private */ - _getOrNull(prop) { + _valueOrNull(prop) { if (!this._user) return null; if (!Object.hasOwnProperty.call(this._user, prop)) return null; return this._user[prop]; @@ -44,47 +42,48 @@ export default class User { */ get displayName() { - return this._getOrNull('displayName'); + return this._valueOrNull('displayName'); } get email() { - return this._getOrNull('email'); + return this._valueOrNull('email'); } get emailVerified() { - return this._getOrNull('emailVerified'); + return this._valueOrNull('emailVerified'); } get isAnonymous() { - return !this._getOrNull('email') && this._getOrNull('providerId') === 'firebase'; + return !this._valueOrNull('email') && this._valueOrNull('providerId') === 'firebase'; } get photoURL() { - return this._getOrNull('photoURL'); + return this._valueOrNull('photoURL'); } get photoUrl() { - return this._getOrNull('photoURL'); + return this._valueOrNull('photoURL'); } // TODO no android method yet, the SDK does have .getProviderData but returns as a List. // get providerData() { - // return this._getOrNull('providerData'); + // return this._valueOrNull('providerData'); // } get providerId() { - return this._getOrNull('providerId'); + return this._valueOrNull('providerId'); } - // TODO no + // TODO no android method? // get refreshToken() { - // return this._getOrNull('refreshToken'); + // return this._valueOrNull('refreshToken'); // } get uid() { - return this._getOrNull('uid'); + return this._valueOrNull('uid'); } + // noinspection ReservedWordAsName /** * METHODS */ @@ -97,8 +96,13 @@ export default class User { return this._auth.getToken(...args); } + get reauthenticate() { + return this._auth.reauthenticateUser; + } + + // TODO match errors to auth/something errors from firebase web api get updateEmail() { - if (this.isAnonymous) return () => Promise.reject(new Error('Can not update email on an annonymous user.')); + if (this.isAnonymous) return () => Promise.reject(new Error('Can not update email on an anonymous user.')); return this._auth.updateEmail; } @@ -107,12 +111,12 @@ export default class User { } get updatePassword() { - if (this.isAnonymous) return () => Promise.reject(new Error('Can not update password on an annonymous user.')); + if (this.isAnonymous) return () => Promise.reject(new Error('Can not update password on an anonymous user.')); return this._auth.updatePassword; } get sendEmailVerification() { - if (this.isAnonymous) return () => Promise.reject(new Error('Can not verify email on an annonymous user.')); + if (this.isAnonymous) return () => Promise.reject(new Error('Can not verify email on an anonymous user.')); return this._auth.sendEmailVerification; } } From 9b6a6064f80d3157c24e9e6ebf5f21868cdfee76 Mon Sep 17 00:00:00 2001 From: salakar Date: Fri, 18 Nov 2016 17:00:41 +0000 Subject: [PATCH 110/275] fixed userCallback and removed anonymousUserCallback - js now decides if user is anon --- .../io/fullstack/firestack/FirestackAuth.java | 47 +------------------ lib/modules/user.js | 1 + 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 9561e0d..b4cc28e 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -187,7 +187,7 @@ public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { FirestackAuthModule.this.user = task.getResult().getUser(); - anonymousUserCallback(FirestackAuthModule.this.user, callback); + userCallback(FirestackAuthModule.this.user, callback); } else { userErrorCallback(task, callback); } @@ -532,14 +532,10 @@ private void userCallback(FirebaseUser passedUser, final Callback callback) { public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - WritableMap msgMap = Arguments.createMap(); WritableMap userMap = getUserMap(); final String token = task.getResult().getToken(); - // todo clean this up - standardise it userMap.putString("token", token); - userMap.putBoolean("anonymous", false); - msgMap.putMap("user", userMap); - callback.invoke(null, msgMap); + callback.invoke(null, userMap); } else { userErrorCallback(task, callback); } @@ -553,45 +549,6 @@ public void onComplete(@NonNull Task task) { } } - // TODO: Reduce to one method - private void anonymousUserCallback(FirebaseUser passedUser, final Callback callback) { - - if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); - } else { - this.user = passedUser; - } - - if (this.user != null) { - this.user - .getToken(true) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - try { - if (task.isSuccessful()) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = getUserMap(); - final String token = task.getResult().getToken(); - // todo clean this up - standardise it - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); - msgMap.putMap("user", userMap); - callback.invoke(null, msgMap); - } else { - userErrorCallback(task, callback); - } - } catch (Exception ex) { - userExceptionCallback(ex, callback); - } - } - }); - } else { - callbackNoUser(callback, true); - } - } - private void userErrorCallback(Task task, final Callback onFail) { WritableMap error = Arguments.createMap(); error.putInt("errorCode", task.getException().hashCode()); diff --git a/lib/modules/user.js b/lib/modules/user.js index 2292fbb..429bd67 100644 --- a/lib/modules/user.js +++ b/lib/modules/user.js @@ -92,6 +92,7 @@ export default class User { return this._auth.deleteUser(...args); } + // TODO valueOrNul token - optional promise getToken(...args) { return this._auth.getToken(...args); } From 44a9f4148d30b324c7cad9385f750e79add10e7f Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 18 Nov 2016 17:40:25 +0000 Subject: [PATCH 111/275] Merge iOS database changes from fullstackreact/react-native-firestack#132 --- ios/Firestack/FirestackDatabase.m | 59 +++++++++++++++++++------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 918feae..459e0d2 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -321,7 +321,8 @@ @implementation FirestackDatabase props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[self getRefAtPath:path] childByAutoId]; + FIRDatabaseReference *pathRef = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [pathRef childByAutoId]; NSURL *url = [NSURL URLWithString:ref.URL]; NSString *newPath = [url path]; @@ -351,12 +352,12 @@ @implementation FirestackDatabase RCT_EXPORT_METHOD(on:(NSString *) path modifiersString:(NSString *) modifiersString - modifiersArray:(NSArray *) modifiersArray + modifiers:(NSArray *) modifiers name:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; - FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiersArray]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; + FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiers]; if (![r isListeningTo:eventName]) { id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) { @@ -368,6 +369,7 @@ @implementation FirestackDatabase props: @{ @"eventName": eventName, @"path": path, + @"modifiersString": modifiersString, @"snapshot": props }]; }; @@ -399,14 +401,14 @@ @implementation FirestackDatabase } RCT_EXPORT_METHOD(onOnce:(NSString *) path - modifiersString:(NSString *) modifiersString - modifiersArray:(NSArray *) modifiersArray - name:(NSString *) name - callback:(RCTResponseSenderBlock) callback) + modifiersString:(NSString *) modifiersString + modifiers:(NSArray *) modifiers + name:(NSString *) name + callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; int eventType = [r eventTypeFromName:name]; - FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiersArray]; + FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiers]; [ref observeSingleEventOfType:eventType withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { @@ -414,6 +416,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"eventName": name, @"path": path, + @"modifiersString": modifiersString, @"snapshot": props }]); } @@ -431,14 +434,14 @@ @implementation FirestackDatabase eventName:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path]; + FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; if (eventName == nil || [eventName isEqualToString:@""]) { [r cleanup]; - [self removeDBHandle:path]; + [self removeDBHandle:path withModifiersString:modifiersString]; } else { [r removeEventHandler:eventName]; if (![r hasListeners]) { - [self removeDBHandle:path]; + [self removeDBHandle:path withModifiersString:modifiersString]; } } @@ -447,6 +450,7 @@ @implementation FirestackDatabase callback(@[[NSNull null], @{ @"result": @"success", @"path": path, + @"modifiersString": modifiersString, @"remainingListeners": [r listenerKeys], }]); } @@ -540,10 +544,9 @@ - (FIRDatabaseReference *) getRef return self.ref; } -- (FIRDatabaseReference *) getRefAtPath:(NSString *) str +- (FIRDatabaseReference *) getRefAtPath:(NSString *) path { - FirestackDBReference *r = [self getDBHandle:str]; - return [r getRef]; + return [[FIRDatabase database] referenceWithPath:path]; } // Handles @@ -555,36 +558,48 @@ - (NSDictionary *) storedDBHandles return __DBHandles; } +- (NSString *) getDBListenerKey:(NSString *) path + withModifiers:(NSString *) modifiersString +{ + return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil]; +} + - (FirestackDBReference *) getDBHandle:(NSString *) path + withModifiers:modifiersString { NSDictionary *stored = [self storedDBHandles]; - FirestackDBReference *r = [stored objectForKey:path]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + FirestackDBReference *r = [stored objectForKey:key]; if (r == nil) { r = [[FirestackDBReference alloc] initWithPath:path]; - [self saveDBHandle:path dbRef:r]; + [self saveDBHandle:path withModifiersString:modifiersString dbRef:r]; } return r; } - (void) saveDBHandle:(NSString *) path + withModifiersString:(NSString*)modifiersString dbRef:(FirestackDBReference *) dbRef { NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; - if ([stored objectForKey:path]) { - FirestackDBReference *r = [stored objectForKey:path]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + if ([stored objectForKey:key]) { + FirestackDBReference *r = [stored objectForKey:key]; [r cleanup]; } - [stored setObject:dbRef forKey:path]; + [stored setObject:dbRef forKey:key]; self._DBHandles = stored; } - (void) removeDBHandle:(NSString *) path + withModifiersString:(NSString*)modifiersString { NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; - FirestackDBReference *r = [stored objectForKey:path]; + FirestackDBReference *r = [stored objectForKey:key]; if (r != nil) { [r cleanup]; } From 92de517199b9c415e621aada0ddffed7b806a497 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Fri, 18 Nov 2016 18:18:57 +0000 Subject: [PATCH 112/275] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ca064bd..d4596ad 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,11 @@ Featuring; authentication, storage, real-time database, presence, analytics, clo ## Firestack vs Firebase JS lib -Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it's designed for the web and/or server. The native SDKs provide much needed features specifically for mobile applications such as offline persistance. Firestack provides a JavaScript interface into the native SDKs to allow your React Native application to utilise these features, and more! +Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it is mainly designed for the web. + +The native SDK's are much better for performance compared to the web SDK. The web SDK will run on the same thread as your apps ([JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate)) therefore limiting your JS framerate, potentially affecting things touch events and transitions/animations. + +The native SDK's also contains functionality that the web SDK's do not, for example [Analytics](/docs/api/analytics.md). ## Example app From d97c21e2e110d511ec1bf95885135e95727e6be4 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Fri, 18 Nov 2016 15:06:01 -0500 Subject: [PATCH 113/275] Fix Disconnect not being referenced in reference.js --- lib/modules/database/reference.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index ece4f74..a42a046 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -5,6 +5,7 @@ import { NativeModules } from 'react-native'; import promisify from './../../utils/promisify'; import { ReferenceBase } from './../base'; import Snapshot from './snapshot.js'; +import Disconnect from './disconnect.js'; import Query from './query.js'; const FirestackDatabase = NativeModules.FirestackDatabase; From 4494eeab6eb87663fe925816c438f2db69452e7b Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Fri, 18 Nov 2016 15:27:20 -0500 Subject: [PATCH 114/275] Fix missing promisify in disconnect.js, add flow --- lib/modules/database/disconnect.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/modules/database/disconnect.js b/lib/modules/database/disconnect.js index 4b1733e..4c9fdcb 100644 --- a/lib/modules/database/disconnect.js +++ b/lib/modules/database/disconnect.js @@ -1,5 +1,7 @@ +/* @flow */ import { NativeModules } from 'react-native'; +import promisify from './../../utils/promisify'; import Reference from './reference'; const FirestackDatabase = NativeModules.FirestackDatabase; From 2d7901add988ba9dc9bc7fb488a4039b7f162ba9 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 13:29:44 +0000 Subject: [PATCH 115/275] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d4596ad..ef887fe 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript libra The native SDK's are much better for performance compared to the web SDK. The web SDK will run on the same thread as your apps ([JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate)) therefore limiting your JS framerate, potentially affecting things touch events and transitions/animations. -The native SDK's also contains functionality that the web SDK's do not, for example [Analytics](/docs/api/analytics.md). +The native SDK's also contains functionality that the web SDK's do not, for example [Analytics](/docs/api/analytics.md) and [Remote Config](/docs/api/remote-config.md). ## Example app @@ -40,6 +40,7 @@ We have a working application example available in at [fullstackreact/FirestackA * [Presence](docs/api/presence.md) * [ServerValue](docs/api/server-value.md) * [Cloud Messaging](docs/api/cloud-messaging.md) + * [Remote Config](docs/api/remote-config.md) * [Events](docs/api/events.md) * [Redux](docs/redux.md) From 42bdbfb1d487fd0c1a1c69ed68961c7e02f4bc3c Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 13:31:35 +0000 Subject: [PATCH 116/275] Create remote-config.md --- docs/api/remote-config.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/api/remote-config.md diff --git a/docs/api/remote-config.md b/docs/api/remote-config.md new file mode 100644 index 0000000..413c634 --- /dev/null +++ b/docs/api/remote-config.md @@ -0,0 +1 @@ +# Remote Config From 15bd34346581c86068cbbc8898e4bcaffe4e2fff Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 13:42:00 +0000 Subject: [PATCH 117/275] Update authentication.md --- docs/api/authentication.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index dd234cf..9d8e9aa 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -26,7 +26,7 @@ class Example extends React.Component { } componentDidMount() { - this.unsubscribe = firebase.auth().onAuthStateChanged(function(user) { + this.unsubscribe = firebase.auth().onAuthStateChanged((user) => { if (user) { // User is signed in. } @@ -34,7 +34,7 @@ class Example extends React.Component { } componentWillUnmount() { - if (this.listener) { + if (this.unsubscribe) { this.unsubscribe(); } } @@ -48,7 +48,7 @@ We can create a user by calling the `createUserWithEmailAndPassword()` function. The method accepts two parameters, an email and a password. ```javascript -firestack.auth().createUserWithEmailAndPassword('ari@fullstack.io', '123456') +firestack.auth().createUserWithEmailAndPassword('foo@bar.com', '123456') .then((user) => { console.log('user created', user) }) @@ -63,7 +63,7 @@ To sign a user in with their email and password, use the `signInWithEmailAndPass It accepts two parameters, the user's email and password: ```javascript -firestack.auth().signInWithEmailAndPassword('ari@fullstack.io', '123456') +firestack.auth().signInWithEmailAndPassword('foo@bar.com', '123456') .then((user) => { console.log('User successfully logged in', user) }) @@ -111,7 +111,7 @@ firestack.auth().signInWithCredential(credential) }) .catch((err) => { console.error('User signin error', err); - }) + }); ``` #### [`signInWithCustomToken(token: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithCustomToken) From c97f4a7804c6bd46f522d654119b69ea703d7775 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 13:42:38 +0000 Subject: [PATCH 118/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 9d8e9aa..de481b4 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -9,7 +9,7 @@ Firestack handles authentication for us out of the box, both with email/password ### Properties ##### `authenticated: boolean` - Returns the current Firebase authentication state. -##### `currentUser: User | null` - Returns the currently signed-in user (or null). See the [User](/docs/api/authentication#user) class documentation for further usage. +##### `currentUser: User | null` - Returns the currently signed-in user (or null). See the [User](/docs/api/authentication.md#user) class documentation for further usage. ### Methods From 7329028073f744434f5fa6bfd049593cf1175baa Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 13:43:32 +0000 Subject: [PATCH 119/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index de481b4..0cc439b 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -2,7 +2,7 @@ Firestack handles authentication for us out of the box, both with email/password-based authentication and through oauth providers (with a separate library to handle oauth providers). -> Android requires the Google Play services to installed for authentication to function. +> Authentication requires Google Play services to be installed on Android. ## Auth From 99f1463bac776fd411dd7492274574287f7cfd34 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Sat, 19 Nov 2016 17:41:57 +0000 Subject: [PATCH 120/275] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef887fe..e4a609d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ npm i react-native-firestack --save [![npm version](https://img.shields.io/npm/v/react-native-firestack.svg)](https://www.npmjs.com/package/react-native-firestack) [![License](https://img.shields.io/npm/l/react-native-firestack.svg)](/LICENSE) -Firestack is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the React Native JS api as closely as possible. It features: +Firestack is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the React Native JS api as closely as possible. Featuring; authentication, storage, real-time database, presence, analytics, cloud messaging, remote configuration, redux support and more! From 91ace7a974b9ea2147e595647d111db4db8dd5e9 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 20 Nov 2016 15:25:18 +0000 Subject: [PATCH 121/275] Simplify Java auth and remove possibility of user synchronisation issues --- .../io/fullstack/firestack/FirestackAuth.java | 105 ++++++------------ lib/modules/auth.js | 7 -- 2 files changed, 31 insertions(+), 81 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index b4cc28e..b41ea12 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -19,7 +19,6 @@ import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; -import com.google.firebase.FirebaseApp; import com.google.firebase.auth.AuthCredential; import com.google.firebase.auth.AuthResult; @@ -42,14 +41,12 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { // private Context context; private ReactContext mReactContext; private FirebaseAuth mAuth; - private FirebaseApp app; - private FirebaseUser user; private FirebaseAuth.AuthStateListener mAuthListener; public FirestackAuthModule(ReactApplicationContext reactContext) { super(reactContext); - // this.context = reactContext; mReactContext = reactContext; + mAuth = FirebaseAuth.getInstance(); Log.d(TAG, "New FirestackAuth instance"); } @@ -78,16 +75,17 @@ private void callbackNoUser(Callback callback, Boolean isError) { @ReactMethod public void listenForAuth() { - if (mAuthListener == null || mAuth == null) { + if (mAuthListener == null) { mAuthListener = new FirebaseAuth.AuthStateListener() { @Override public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { + FirebaseUser user = firebaseAuth.getCurrentUser(); WritableMap msgMap = Arguments.createMap(); msgMap.putString("eventName", "listenForAuth"); - if (FirestackAuthModule.this.user != null) { + if (user != null) { // TODO move to helper - WritableMap userMap = getUserMap(); + WritableMap userMap = getUserMap(user); msgMap.putBoolean("authenticated", true); msgMap.putMap("user", userMap); @@ -98,8 +96,6 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { } } }; - - mAuth = FirebaseAuth.getInstance(); mAuth.addAuthStateListener(mAuthListener); } } @@ -119,16 +115,13 @@ public void unlistenForAuth(final Callback callback) { @ReactMethod public void createUserWithEmail(final String email, final String password, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - mAuth.createUserWithEmailAndPassword(email, password) .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -141,7 +134,6 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void signInWithEmail(final String email, final String password, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); mAuth.signInWithEmailAndPassword(email, password) .addOnCompleteListener(new OnCompleteListener() { @@ -149,8 +141,7 @@ public void signInWithEmail(final String email, final String password, final Cal public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -175,9 +166,6 @@ public void signInWithProvider(final String provider, final String authToken, fi @ReactMethod public void signInAnonymously(final Callback callback) { Log.d(TAG, "signInAnonymously:called:"); - mAuth = FirebaseAuth.getInstance(); - - mAuth.signInAnonymously() .addOnCompleteListener(new OnCompleteListener() { @Override @@ -186,8 +174,7 @@ public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -200,8 +187,6 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void signInWithCustomToken(final String customToken, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - mAuth.signInWithCustomToken(customToken) .addOnCompleteListener(new OnCompleteListener() { @Override @@ -209,8 +194,7 @@ public void onComplete(@NonNull Task task) { Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -231,7 +215,7 @@ public void reauthenticateWithCredentialForProvider(final String provider, final @ReactMethod public void updateUserEmail(final String email, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { user @@ -242,8 +226,7 @@ public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { Log.d(TAG, "User email address updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); + userCallback(mAuth.getCurrentUser(), callback); } else { userErrorCallback(task, callback); } @@ -259,7 +242,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void updateUserPassword(final String newPassword, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { user.updatePassword(newPassword) @@ -269,9 +252,7 @@ public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { Log.d(TAG, "User password updated"); - - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); + userCallback(mAuth.getCurrentUser(), callback); } else { userErrorCallback(task, callback); } @@ -287,8 +268,6 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void sendPasswordResetWithEmail(final String email, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - mAuth.sendPasswordResetEmail(email) .addOnCompleteListener(new OnCompleteListener() { @Override @@ -310,7 +289,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void deleteUser(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { user.delete() .addOnCompleteListener(new OnCompleteListener() { @@ -339,7 +318,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void sendEmailVerification(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { user.sendEmailVerification() @@ -371,7 +350,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void getToken(final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { user.getToken(true) @@ -403,7 +382,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void updateUserProfile(ReadableMap props, final Callback callback) { - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + FirebaseUser user = mAuth.getCurrentUser(); if (user != null) { UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); @@ -430,8 +409,7 @@ public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { Log.d(TAG, "User profile updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); + userCallback(mAuth.getCurrentUser(), callback); } else { userErrorCallback(task, callback); } @@ -447,8 +425,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void signOut(final Callback callback) { - FirebaseAuth.getInstance().signOut(); - this.user = null; + mAuth.signOut(); WritableMap resp = Arguments.createMap(); resp.putString("status", "complete"); @@ -458,22 +435,18 @@ public void signOut(final Callback callback) { @ReactMethod public void getCurrentUser(final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - this.user = mAuth.getCurrentUser(); - if (this.user == null) { + FirebaseUser user = mAuth.getCurrentUser(); + if (user == null) { callbackNoUser(callback, false); } else { - Log.d("USRC", this.user.getUid()); - userCallback(this.user, callback); + Log.d("USRC", user.getUid()); + userCallback(user, callback); } } // TODO: Check these things @ReactMethod public void googleLogin(String IdToken, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null); mAuth.signInWithCredential(credential) .addOnCompleteListener(new OnCompleteListener() { @@ -481,8 +454,7 @@ public void googleLogin(String IdToken, final Callback callback) { public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -495,8 +467,6 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void facebookLogin(String Token, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - AuthCredential credential = FacebookAuthProvider.getCredential(Token); mAuth.signInWithCredential(credential) .addOnCompleteListener(new OnCompleteListener() { @@ -504,8 +474,7 @@ public void facebookLogin(String Token, final Callback callback) { public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); + userCallback(task.getResult().getUser(), callback); } else { userErrorCallback(task, callback); } @@ -517,24 +486,15 @@ public void onComplete(@NonNull Task task) { } // Internal helpers - private void userCallback(FirebaseUser passedUser, final Callback callback) { - - if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - this.user = mAuth.getCurrentUser(); - } else { - this.user = passedUser; - } - - if (this.user != null) { - this.user.getToken(true).addOnCompleteListener(new OnCompleteListener() { + private void userCallback(final FirebaseUser user, final Callback callback) { + if (user != null) { + user.getToken(true).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { try { if (task.isSuccessful()) { - WritableMap userMap = getUserMap(); - final String token = task.getResult().getToken(); - userMap.putString("token", token); + WritableMap userMap = getUserMap(user); + userMap.putString("token", task.getResult().getToken()); callback.invoke(null, userMap); } else { userErrorCallback(task, callback); @@ -567,11 +527,8 @@ private void userExceptionCallback(Exception ex, final Callback onFail) { onFail.invoke(error); } - private WritableMap getUserMap() { + private WritableMap getUserMap(FirebaseUser user) { WritableMap userMap = Arguments.createMap(); - - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { final String email = user.getEmail(); final String uid = user.getUid(); diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 6963fbf..04e5775 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -26,13 +26,6 @@ export default class Auth extends Base { // but this is ok as we fake it with the getCurrentUser below FirestackAuthEvt.addListener('listenForAuth', this._onAuthStateChanged.bind(this)); FirestackAuth.listenForAuth(); - - this.getCurrentUser().then((u: Object) => { - this._onAuthStateChanged({ authenticated: !!u, user: u || null }); - }).catch(() => { - // todo check if error contains user disabled message maybe and add a disabled flag? - this._onAuthStateChanged({ authenticated: false, user: null }); - }); } /** From 911924f4f7ab4914e7db20cc48c299a147fcb5b0 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 21 Nov 2016 08:10:41 +0000 Subject: [PATCH 122/275] Allow Java database module to deal with Array data types in the same way as iOS --- .../firestack/FirestackDatabase.java | 50 -------- .../fullstack/firestack/FirestackUtils.java | 111 ++++++++++++++---- 2 files changed, 86 insertions(+), 75 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 4cdf613..334857d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -618,54 +618,4 @@ private DatabaseReference getDatabaseReferenceAtPath(final String path) { DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); return mDatabase; } - - - //private WritableMap dataSnapshotToMap(String name, String path, DataSnapshot dataSnapshot) { - // return FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); - //} - - private Any castSnapshotValue(DataSnapshot snapshot) { - if (snapshot.hasChildren()) { - WritableMap data = Arguments.createMap(); - for (DataSnapshot child : snapshot.getChildren()) { - Any castedChild = castSnapshotValue(child); - switch (castedChild.getClass().getName()) { - case "java.lang.Boolean": - data.putBoolean(child.getKey(), (Boolean) castedChild); - break; - case "java.lang.Long": - data.putDouble(child.getKey(), (Long) castedChild); - break; - case "java.lang.Double": - data.putDouble(child.getKey(), (Double) castedChild); - break; - case "java.lang.String": - data.putString(child.getKey(), (String) castedChild); - break; - case "com.facebook.react.bridge.WritableNativeMap": - data.putMap(child.getKey(), (WritableMap) castedChild); - break; - } - } - return (Any) data; - } else { - if (snapshot.getValue() != null) { - String type = snapshot.getValue().getClass().getName(); - switch (type) { - case "java.lang.Boolean": - return (Any)((Boolean) snapshot.getValue()); - case "java.lang.Long": - return (Any) ((Long) snapshot.getValue()); - case "java.lang.Double": - return (Any)((Double) snapshot.getValue()); - case "java.lang.String": - return (Any)((String) snapshot.getValue()); - default: - return (Any) null; - } - } else { - return (Any) null; - } - } - } } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java index ae1bb95..669e337 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java @@ -109,32 +109,11 @@ public static WritableMap dataSnapshotToMap( public static Any castSnapshotValue(DataSnapshot snapshot) { if (snapshot.hasChildren()) { - WritableMap data = Arguments.createMap(); - for (DataSnapshot child : snapshot.getChildren()) { - Any castedChild = castSnapshotValue(child); - switch (castedChild.getClass().getName()) { - case "java.lang.Boolean": - data.putBoolean(child.getKey(), (Boolean) castedChild); - break; - case "java.lang.Long": - Long longVal = (Long) castedChild; - data.putDouble(child.getKey(), (double) longVal); - break; - case "java.lang.Double": - data.putDouble(child.getKey(), (Double) castedChild); - break; - case "java.lang.String": - data.putString(child.getKey(), (String) castedChild); - break; - case "com.facebook.react.bridge.WritableNativeMap": - data.putMap(child.getKey(), (WritableMap) castedChild); - break; - default: - Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); - break; - } + if (isArray(snapshot)) { + return (Any) buildArray(snapshot); + } else { + return (Any) buildMap(snapshot); } - return (Any) data; } else { if (snapshot.getValue() != null) { String type = snapshot.getValue().getClass().getName(); @@ -156,6 +135,88 @@ public static Any castSnapshotValue(DataSnapshot snapshot) { } } + private static boolean isArray(DataSnapshot snapshot) { + long expectedKey = 0; + for (DataSnapshot child : snapshot.getChildren()) { + try { + long key = Long.parseLong(child.getKey()); + if (key == expectedKey) { + expectedKey++; + } else { + return false; + } + } catch (NumberFormatException ex) { + return false; + } + } + return true; + } + + private static WritableArray buildArray(DataSnapshot snapshot) { + WritableArray array = Arguments.createArray(); + for (DataSnapshot child : snapshot.getChildren()) { + Any castedChild = castSnapshotValue(child); + switch (castedChild.getClass().getName()) { + case "java.lang.Boolean": + array.pushBoolean((Boolean) castedChild); + break; + case "java.lang.Long": + Long longVal = (Long) castedChild; + array.pushDouble((double) longVal); + break; + case "java.lang.Double": + array.pushDouble((Double) castedChild); + break; + case "java.lang.String": + array.pushString((String) castedChild); + break; + case "com.facebook.react.bridge.WritableNativeMap": + array.pushMap((WritableMap) castedChild); + break; + case "com.facebook.react.bridge.WritableNativeArray": + array.pushArray((WritableArray) castedChild); + break; + default: + Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); + break; + } + } + return array; + } + + private static WritableMap buildMap(DataSnapshot snapshot) { + WritableMap map = Arguments.createMap(); + for (DataSnapshot child : snapshot.getChildren()) { + Any castedChild = castSnapshotValue(child); + + switch (castedChild.getClass().getName()) { + case "java.lang.Boolean": + map.putBoolean(child.getKey(), (Boolean) castedChild); + break; + case "java.lang.Long": + Long longVal = (Long) castedChild; + map.putDouble(child.getKey(), (double) longVal); + break; + case "java.lang.Double": + map.putDouble(child.getKey(), (Double) castedChild); + break; + case "java.lang.String": + map.putString(child.getKey(), (String) castedChild); + break; + case "com.facebook.react.bridge.WritableNativeMap": + map.putMap(child.getKey(), (WritableMap) castedChild); + break; + case "com.facebook.react.bridge.WritableNativeArray": + map.putArray(child.getKey(), (WritableArray) castedChild); + break; + default: + Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); + break; + } + } + return map; + } + public static WritableArray getChildKeys(DataSnapshot snapshot) { WritableArray childKeys = Arguments.createArray(); From b3ea010c75b9824e73aa950b5a01c04f7723e58e Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 21 Nov 2016 11:15:40 +0000 Subject: [PATCH 123/275] Allow Java file upload to work with Camera Roll asset URLs in the same way that iOS does --- .../fullstack/firestack/FirestackStorage.java | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 0794f65..b41301d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -49,11 +49,8 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; - private ReactContext mReactContext; - public FirestackStorageModule(ReactApplicationContext reactContext) { super(reactContext); - mReactContext = reactContext; Log.d(TAG, "New instance"); } @@ -142,7 +139,13 @@ public void uploadFile(final String urlStr, final String name, final String file Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); try { - Uri file = Uri.fromFile(new File(filepath)); + Uri file; + if (filepath.startsWith("content://")) { + String realPath = getRealPathFromURI(filepath); + file = Uri.fromFile(new File(realPath)); + } else { + file = Uri.fromFile(new File(filepath)); + } StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder(); Map m = FirestackUtils.recursivelyDeconstructReadableMap(metadata); @@ -190,7 +193,7 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { WritableMap data = Arguments.createMap(); data.putString("eventName", "upload_progress"); data.putDouble("progress", progress); - FirestackUtils.sendEvent(mReactContext, "upload_progress", data); + FirestackUtils.sendEvent(getReactApplicationContext(), "upload_progress", data); } } }) @@ -203,7 +206,7 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { WritableMap data = Arguments.createMap(); data.putString("eventName", "upload_paused"); data.putString("ref", bucket); - FirestackUtils.sendEvent(mReactContext, "upload_paused", data); + FirestackUtils.sendEvent(getReactApplicationContext(), "upload_paused", data); } }); } catch (Exception ex) { @@ -214,14 +217,7 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { @ReactMethod public void getRealPathFromURI(final String uri, final Callback callback) { try { - Context context = getReactApplicationContext(); - String[] proj = {MediaStore.Images.Media.DATA}; - Cursor cursor = context.getContentResolver().query(Uri.parse(uri), proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - cursor.moveToFirst(); - String path = cursor.getString(column_index); - cursor.close(); - + String path = getRealPathFromURI(uri); callback.invoke(null, path); } catch (Exception ex) { ex.printStackTrace(); @@ -229,6 +225,21 @@ public void getRealPathFromURI(final String uri, final Callback callback) { } } + private String getRealPathFromURI(final String uri) { + Cursor cursor = null; + try { + String[] proj = {MediaStore.Images.Media.DATA}; + cursor = getReactApplicationContext().getContentResolver().query(Uri.parse(uri), proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + private WritableMap getDownloadData(final UploadTask.TaskSnapshot taskSnapshot) { Uri downloadUrl = taskSnapshot.getDownloadUrl(); StorageMetadata d = taskSnapshot.getMetadata(); From 363c1b6ce29c5b3b9d625817827d73b6b864759b Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 21 Nov 2016 11:17:10 +0000 Subject: [PATCH 124/275] Quick fix to prevent duplicate child event listeners on Java database module --- .../java/io/fullstack/firestack/FirestackDatabase.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 334857d..b12289c 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -10,6 +10,7 @@ import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReactMethod; @@ -79,11 +80,9 @@ public void onCancelled(DatabaseError error) { self.handleDatabaseError(name, mPath, error); } }; + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); + ref.addChildEventListener(mEventListener); } - - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addChildEventListener(mEventListener); - //this.setListeningTo(mPath, modifiersString, name); } public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { From 1882af742444606ea391acc5730856d479f8b39e Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Mon, 21 Nov 2016 12:20:12 +0000 Subject: [PATCH 125/275] Update authentication.md --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index 0cc439b..d787374 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -258,7 +258,7 @@ firestack.auth().updateUserEmail('foo@bar.com') Important: this is a security sensitive operation that requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call firebase.User#reauthenticate. This will Promise reject is the user is anonymous. ```javascript -firestack.auth().updateUserPassword('foobar1234') +firestack.auth().updatePassword('foobar1234') .then() .catch(); ``` From 458cf04d9bb6e21b161ea8acc869d380e59a7b03 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 21 Nov 2016 12:32:25 +0000 Subject: [PATCH 126/275] cleanup module names --- .../firestack/FirestackAnalytics.java | 6 +- .../io/fullstack/firestack/FirestackAuth.java | 14 +- .../firestack/FirestackDatabase.java | 141 +++++++++--------- .../fullstack/firestack/FirestackStorage.java | 10 +- ...eIdService.java => InstanceIdService.java} | 7 +- ...tackCloudMessaging.java => Messaging.java} | 13 +- ...gingService.java => MessagingService.java} | 8 +- .../{FirestackModule.java => Module.java} | 23 ++- .../{FirestackPackage.java => Package.java} | 16 +- .../{FirestackUtils.java => Utils.java} | 26 ++-- 10 files changed, 137 insertions(+), 127 deletions(-) rename android/src/main/java/io/fullstack/firestack/{FirestackInstanceIdService.java => InstanceIdService.java} (79%) rename android/src/main/java/io/fullstack/firestack/{FirestackCloudMessaging.java => Messaging.java} (94%) rename android/src/main/java/io/fullstack/firestack/{FirestackMessagingService.java => MessagingService.java} (89%) rename android/src/main/java/io/fullstack/firestack/{FirestackModule.java => Module.java} (86%) rename android/src/main/java/io/fullstack/firestack/{FirestackPackage.java => Package.java} (76%) rename android/src/main/java/io/fullstack/firestack/{FirestackUtils.java => Utils.java} (93%) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java index 9580877..6a80a32 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -11,14 +11,14 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -class FirestackAnalyticsModule extends ReactContextBaseJavaModule { +class Analytics extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAnalytics"; private ReactApplicationContext context; private FirebaseAnalytics mFirebaseAnalytics; - public FirestackAnalyticsModule(ReactApplicationContext reactContext) { + public Analytics(ReactApplicationContext reactContext) { super(reactContext); context = reactContext; Log.d(TAG, "New instance"); @@ -36,7 +36,7 @@ public String getName() { @ReactMethod public void logEvent(final String name, final ReadableMap params) { - Map m = FirestackUtils.recursivelyDeconstructReadableMap(params); + Map m = Utils.recursivelyDeconstructReadableMap(params); final Bundle bundle = makeEventBundle(name, m); Log.d(TAG, "Logging event " + name); mFirebaseAnalytics.logEvent(name, bundle); diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index b41ea12..8e18dd1 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -31,7 +31,7 @@ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") -class FirestackAuthModule extends ReactContextBaseJavaModule { +class Auth extends ReactContextBaseJavaModule { private final int NO_CURRENT_USER = 100; private final int ERROR_FETCHING_TOKEN = 101; private final int ERROR_SENDING_VERIFICATION_EMAIL = 102; @@ -43,7 +43,7 @@ class FirestackAuthModule extends ReactContextBaseJavaModule { private FirebaseAuth mAuth; private FirebaseAuth.AuthStateListener mAuthListener; - public FirestackAuthModule(ReactApplicationContext reactContext) { + public Auth(ReactApplicationContext reactContext) { super(reactContext); mReactContext = reactContext; mAuth = FirebaseAuth.getInstance(); @@ -89,10 +89,10 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { msgMap.putBoolean("authenticated", true); msgMap.putMap("user", userMap); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + Utils.sendEvent(mReactContext, "listenForAuth", msgMap); } else { msgMap.putBoolean("authenticated", false); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); + Utils.sendEvent(mReactContext, "listenForAuth", msgMap); } } }; @@ -160,7 +160,7 @@ public void signInWithProvider(final String provider, final String authToken, fi this.googleLogin(authToken, callback); } else // TODO - FirestackUtils.todoNote(TAG, "signInWithProvider", callback); + Utils.todoNote(TAG, "signInWithProvider", callback); } @ReactMethod @@ -208,7 +208,7 @@ public void onComplete(@NonNull Task task) { @ReactMethod public void reauthenticateWithCredentialForProvider(final String provider, final String authToken, final String authSecret, final Callback callback) { // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); + Utils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); // AuthCredential credential; // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); } @@ -387,7 +387,7 @@ public void updateUserProfile(ReadableMap props, final Callback callback) { if (user != null) { UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + Map m = Utils.recursivelyDeconstructReadableMap(props); if (m.containsKey("displayName")) { String displayName = (String) m.get("displayName"); diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 334857d..b2379ac 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -24,33 +24,32 @@ import com.google.firebase.database.OnDisconnect; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.ChildEventListener; import com.google.firebase.database.ValueEventListener; -class FirestackDBReference { - private static final String TAG = "FirestackDBReference"; +class DatabaseReference { + private static final String TAG = "DatabaseReference"; private String mPath; - private ReadableArray mModifiers; +// private ReadableArray mModifiers; private HashMap mListeners = new HashMap(); - private FirestackDatabaseModule mDatabase; + private Database mDatabase; private ChildEventListener mEventListener; private ValueEventListener mValueListener; private ValueEventListener mOnceValueListener; private ReactContext mReactContext; - public FirestackDBReference(final ReactContext context, final String path) { + public DatabaseReference(final ReactContext context, final String path) { mReactContext = context; mPath = path; } - public void setModifiers(final ReadableArray modifiers) { - mModifiers = modifiers; - } +// public void setModifiers(final ReadableArray modifiers) { +// mModifiers = modifiers; +// } public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final FirestackDBReference self = this; + final DatabaseReference self = this; if (mEventListener == null) { mEventListener = new ChildEventListener() { @@ -87,7 +86,7 @@ public void onCancelled(DatabaseError error) { } public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final FirestackDBReference self = this; + final DatabaseReference self = this; mValueListener = new ValueEventListener() { @Override @@ -109,12 +108,12 @@ public void onCancelled(DatabaseError error) { public void addOnceValueEventListener(final ReadableArray modifiersArray, final String modifiersString, final Callback callback) { - final FirestackDBReference self = this; + final DatabaseReference self = this; mOnceValueListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); + WritableMap data = Utils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); callback.invoke(null, data); } @@ -162,7 +161,7 @@ public void cleanup() { public void removeChildEventListener() { if (mEventListener != null) { - DatabaseReference ref = this.getDatabaseRef(); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); ref.removeEventListener(mEventListener); //this.notListeningTo(mPath, "child_added"); //this.notListeningTo(mPath, "child_changed"); @@ -173,7 +172,7 @@ public void removeChildEventListener() { } public void removeValueEventListener() { - DatabaseReference ref = this.getDatabaseRef(); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); if (mValueListener != null) { ref.removeEventListener(mValueListener); //this.notListeningTo(mPath, "value"); @@ -186,17 +185,17 @@ public void removeValueEventListener() { } private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { - //if (!FirestackDBReference.this.isListeningTo(path, modifiersString, name)) { + //if (!DatabaseReference.this.isListeningTo(path, modifiersString, name)) { //return; //} - WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); + WritableMap data = Utils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); evt.putString("path", path); evt.putString("modifiersString", modifiersString); evt.putMap("body", data); - FirestackUtils.sendEvent(mReactContext, "database_event", evt); + Utils.sendEvent(mReactContext, "database_event", evt); } private void handleDatabaseError(final String name, final String path, final DatabaseError error) { @@ -210,17 +209,17 @@ private void handleDatabaseError(final String name, final String path, final Dat evt.putString("path", path); evt.putMap("body", err); - FirestackUtils.sendEvent(mReactContext, "database_error", evt); + Utils.sendEvent(mReactContext, "database_error", evt); } - public DatabaseReference getDatabaseRef() { + public com.google.firebase.database.DatabaseReference getDatabaseRef() { return FirebaseDatabase.getInstance().getReference(mPath); } private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { - DatabaseReference ref = this.getDatabaseRef(); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); - List strModifiers = FirestackUtils.recursivelyDeconstructReadableArray(modifiers); + List strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers); ListIterator it = strModifiers.listIterator(); Query query = ref.orderByKey(); @@ -282,15 +281,15 @@ private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) } -class FirestackDatabaseModule extends ReactContextBaseJavaModule { +class Database extends ReactContextBaseJavaModule { private static final String TAG = "FirestackDatabase"; private Context context; private ReactContext mReactContext; - private HashMap mDBListeners = new HashMap(); + private HashMap mDBListeners = new HashMap(); - public FirestackDatabaseModule(ReactApplicationContext reactContext) { + public Database(ReactApplicationContext reactContext) { super(reactContext); this.context = reactContext; mReactContext = reactContext; @@ -323,7 +322,7 @@ public void keepSynced( final String path, final Boolean enable, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); ref.keepSynced(enable); WritableMap res = Arguments.createMap(); @@ -338,14 +337,14 @@ public void set( final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final FirestackDatabaseModule self = this; - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + final Database self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); - DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError error, DatabaseReference ref) { + public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { handleCallback("set", callback, error, ref); } }; @@ -357,13 +356,13 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { public void update(final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final FirestackDatabaseModule self = this; - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + final Database self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); - DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError error, DatabaseReference ref) { + public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { handleCallback("update", callback, error, ref); } }; @@ -374,11 +373,11 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { @ReactMethod public void remove(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final FirestackDatabaseModule self = this; - DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + final Database self = this; + com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError error, DatabaseReference ref) { + public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { handleCallback("remove", callback, error, ref); } }; @@ -392,8 +391,8 @@ public void push(final String path, final Callback callback) { Log.d(TAG, "Called push with " + path); - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - DatabaseReference newRef = ref.push(); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference newRef = ref.push(); final Uri url = Uri.parse(newRef.toString()); final String newPath = url.getPath(); @@ -402,12 +401,12 @@ public void push(final String path, if (iterator.hasNextKey()) { Log.d(TAG, "Passed value to push"); // lame way to check if the `props` are empty - final FirestackDatabaseModule self = this; - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + final Database self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); - DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError error, DatabaseReference ref) { + public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { if (error != null) { WritableMap err = Arguments.createMap(); err.putInt("errorCode", error.getCode()); @@ -439,7 +438,7 @@ public void on(final String path, final ReadableArray modifiersArray, final String name, final Callback callback) { - FirestackDBReference ref = this.getDBHandle(path, modifiersString); + DatabaseReference ref = this.getDBHandle(path, modifiersString); WritableMap resp = Arguments.createMap(); @@ -464,7 +463,7 @@ public void onOnce(final String path, final String name, final Callback callback) { Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - FirestackDBReference ref = this.getDBHandle(path, modifiersString); + DatabaseReference ref = this.getDBHandle(path, modifiersString); ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); } @@ -491,13 +490,13 @@ public void off( // On Disconnect @ReactMethod public void onDisconnectSetObject(final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - Map m = FirestackUtils.recursivelyDeconstructReadableMap(props); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + Map m = Utils.recursivelyDeconstructReadableMap(props); OnDisconnect od = ref.onDisconnect(); - od.setValue(m, new DatabaseReference.CompletionListener() { + od.setValue(m, new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { handleCallback("onDisconnectSetObject", callback, databaseError, databaseReference); } }); @@ -505,12 +504,12 @@ public void onComplete(DatabaseError databaseError, DatabaseReference databaseRe @ReactMethod public void onDisconnectSetString(final String path, final String value, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); OnDisconnect od = ref.onDisconnect(); - od.setValue(value, new DatabaseReference.CompletionListener() { + od.setValue(value, new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { handleCallback("onDisconnectSetString", callback, databaseError, databaseReference); } }); @@ -518,24 +517,24 @@ public void onComplete(DatabaseError databaseError, DatabaseReference databaseRe @ReactMethod public void onDisconnectRemove(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); OnDisconnect od = ref.onDisconnect(); - od.removeValue(new DatabaseReference.CompletionListener() { + od.removeValue(new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { handleCallback("onDisconnectRemove", callback, databaseError, databaseReference); } }); } @ReactMethod public void onDisconnectCancel(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); OnDisconnect od = ref.onDisconnect(); - od.cancel(new DatabaseReference.CompletionListener() { + od.cancel(new com.google.firebase.database.DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { handleCallback("onDisconnectCancel", callback, databaseError, databaseReference); } }); @@ -547,7 +546,7 @@ public void onComplete(DatabaseError databaseError, DatabaseReference databaseRe // WritableMap evt = Arguments.createMap(); // evt.putString("eventName", name); // evt.putMap("body", data); - // FirestackUtils.sendEvent(mReactContext, "database_event", evt); + // Utils.sendEvent(mReactContext, "database_event", evt); // } // private void handleDatabaseError(final String name, final DatabaseError error) { @@ -559,14 +558,14 @@ public void onComplete(DatabaseError databaseError, DatabaseReference databaseRe // WritableMap evt = Arguments.createMap(); // evt.putString("eventName", name); // evt.putMap("body", err); - // FirestackUtils.sendEvent(mReactContext, "database_error", evt); + // Utils.sendEvent(mReactContext, "database_error", evt); // } private void handleCallback( final String methodName, final Callback callback, final DatabaseError databaseError, - final DatabaseReference databaseReference) { + final com.google.firebase.database.DatabaseReference databaseReference) { if (databaseError != null) { WritableMap err = Arguments.createMap(); err.putInt("errorCode", databaseError.getCode()); @@ -581,17 +580,17 @@ private void handleCallback( } } - private FirestackDBReference getDBHandle(final String path, final String modifiersString) { + private DatabaseReference getDBHandle(final String path, final String modifiersString) { String key = this.getDBListenerKey(path, modifiersString); if (!mDBListeners.containsKey(key)) { ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(key, new FirestackDBReference(ctx, path)); + mDBListeners.put(key, new DatabaseReference(ctx, path)); } return mDBListeners.get(key); } - private void saveDBHandle(final String path, String modifiersString, final FirestackDBReference dbRef) { + private void saveDBHandle(final String path, String modifiersString, final DatabaseReference dbRef) { String key = this.getDBListenerKey(path, modifiersString); mDBListeners.put(key, dbRef); } @@ -603,7 +602,7 @@ private String getDBListenerKey(String path, String modifiersString) { private void removeDBHandle(final String path, String modifiersString) { String key = this.getDBListenerKey(path, modifiersString); if (mDBListeners.containsKey(key)) { - FirestackDBReference r = mDBListeners.get(key); + DatabaseReference r = mDBListeners.get(key); r.cleanup(); mDBListeners.remove(key); } @@ -613,9 +612,9 @@ private String keyPath(final String path, final String eventName) { return path + "-" + eventName; } - // TODO: move to FirestackDBReference? - private DatabaseReference getDatabaseReferenceAtPath(final String path) { - DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); + // TODO: move to DatabaseReference? + private com.google.firebase.database.DatabaseReference getDatabaseReferenceAtPath(final String path) { + com.google.firebase.database.DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); return mDatabase; } } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 0794f65..0d03386 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -35,7 +35,7 @@ @SuppressWarnings("WeakerAccess") -class FirestackStorageModule extends ReactContextBaseJavaModule { +class Storage extends ReactContextBaseJavaModule { private static final String TAG = "FirestackStorage"; private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; @@ -51,7 +51,7 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private ReactContext mReactContext; - public FirestackStorageModule(ReactApplicationContext reactContext) { + public Storage(ReactApplicationContext reactContext) { super(reactContext); mReactContext = reactContext; @@ -145,7 +145,7 @@ public void uploadFile(final String urlStr, final String name, final String file Uri file = Uri.fromFile(new File(filepath)); StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder(); - Map m = FirestackUtils.recursivelyDeconstructReadableMap(metadata); + Map m = Utils.recursivelyDeconstructReadableMap(metadata); for (Map.Entry entry : m.entrySet()) { metadataBuilder.setCustomMetadata(entry.getKey(), entry.getValue().toString()); @@ -190,7 +190,7 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { WritableMap data = Arguments.createMap(); data.putString("eventName", "upload_progress"); data.putDouble("progress", progress); - FirestackUtils.sendEvent(mReactContext, "upload_progress", data); + Utils.sendEvent(mReactContext, "upload_progress", data); } } }) @@ -203,7 +203,7 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { WritableMap data = Arguments.createMap(); data.putString("eventName", "upload_paused"); data.putString("ref", bucket); - FirestackUtils.sendEvent(mReactContext, "upload_paused", data); + Utils.sendEvent(mReactContext, "upload_paused", data); } }); } catch (Exception ex) { diff --git a/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java b/android/src/main/java/io/fullstack/firestack/InstanceIdService.java similarity index 79% rename from android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java rename to android/src/main/java/io/fullstack/firestack/InstanceIdService.java index 8922ef4..d413336 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java +++ b/android/src/main/java/io/fullstack/firestack/InstanceIdService.java @@ -10,7 +10,7 @@ import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceIdService; -public class FirestackInstanceIdService extends FirebaseInstanceIdService { +public class InstanceIdService extends FirebaseInstanceIdService { private static final String TAG = "FSInstanceIdService"; @@ -21,10 +21,7 @@ public class FirestackInstanceIdService extends FirebaseInstanceIdService { public void onTokenRefresh() { String refreshedToken = FirebaseInstanceId.getInstance().getToken(); Log.d(TAG, "Refreshed token: " + refreshedToken); - - - // send Intent - Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_TOKEN); + Intent i = new Intent(Messaging.INTENT_NAME_TOKEN); Bundle bundle = new Bundle(); bundle.putString("token", refreshedToken); i.putExtras(bundle); diff --git a/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java b/android/src/main/java/io/fullstack/firestack/Messaging.java similarity index 94% rename from android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java rename to android/src/main/java/io/fullstack/firestack/Messaging.java index ef17a88..e6ae796 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java +++ b/android/src/main/java/io/fullstack/firestack/Messaging.java @@ -13,7 +13,6 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; @@ -27,9 +26,9 @@ /** * Created by nori on 2016/09/12. */ -public class FirestackCloudMessaging extends ReactContextBaseJavaModule { +public class Messaging extends ReactContextBaseJavaModule { - private static final String TAG = "FirestackCloudMessaging"; + private static final String TAG = "Messaging"; private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; @@ -43,7 +42,7 @@ public class FirestackCloudMessaging extends ReactContextBaseJavaModule { private IntentFilter mReceiveNotificationIntentFilter; private IntentFilter mReceiveSendIntentFilter; - public FirestackCloudMessaging(ReactApplicationContext reactContext) { + public Messaging(ReactApplicationContext reactContext) { super(reactContext); mReactContext = reactContext; mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); @@ -85,7 +84,7 @@ public void onReceive(Context context, Intent intent) { params.putString("token", intent.getStringExtra("token")); ReactContext ctx = getReactApplicationContext(); Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN); - FirestackUtils.sendEvent(ctx, EVENT_NAME_TOKEN, params); + Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params); } ; @@ -151,7 +150,7 @@ public void onReceive(Context context, Intent intent) { params.putNull("notification"); } ReactContext ctx = getReactApplicationContext(); - FirestackUtils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); + Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); } }, mReceiveNotificationIntentFilter); } @@ -200,7 +199,7 @@ public void onReceive(Context context, Intent intent) { params.putNull("err"); } ReactContext ctx = getReactApplicationContext(); - FirestackUtils.sendEvent(ctx, EVENT_NAME_SEND, params); + Utils.sendEvent(ctx, EVENT_NAME_SEND, params); } }, mReceiveSendIntentFilter); } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java b/android/src/main/java/io/fullstack/firestack/MessagingService.java similarity index 89% rename from android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java rename to android/src/main/java/io/fullstack/firestack/MessagingService.java index 485e762..6204fb3 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java +++ b/android/src/main/java/io/fullstack/firestack/MessagingService.java @@ -7,7 +7,7 @@ import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.SendException; -public class FirestackMessagingService extends FirebaseMessagingService { +public class MessagingService extends FirebaseMessagingService { private static final String TAG = "FSMessagingService"; @@ -25,7 +25,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) { if (remoteMessage.getNotification() != null) { } - Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_NOTIFICATION); + Intent i = new Intent(Messaging.INTENT_NAME_NOTIFICATION); i.putExtra("data", remoteMessage); sendOrderedBroadcast(i, null); @@ -35,7 +35,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) { public void onMessageSent(String msgId) { // Called when an upstream message has been successfully sent to the GCM connection server. Log.d(TAG, "upstream message has been successfully sent"); - Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_SEND); + Intent i = new Intent(Messaging.INTENT_NAME_SEND); i.putExtra("msgId", msgId); sendOrderedBroadcast(i, null); } @@ -44,7 +44,7 @@ public void onMessageSent(String msgId) { public void onSendError(String msgId, Exception exception) { // Called when there was an error sending an upstream message. Log.d(TAG, "error sending an upstream message"); - Intent i = new Intent(FirestackCloudMessaging.INTENT_NAME_SEND); + Intent i = new Intent(Messaging.INTENT_NAME_SEND); i.putExtra("msgId", msgId); i.putExtra("hasError", true); SendException sendException = (SendException) exception; diff --git a/android/src/main/java/io/fullstack/firestack/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/Module.java similarity index 86% rename from android/src/main/java/io/fullstack/firestack/FirestackModule.java rename to android/src/main/java/io/fullstack/firestack/Module.java index 0376e08..40c7123 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/Module.java @@ -16,6 +16,8 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReactContext; +import com.google.android.gms.common.ConnectionResult; +//import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.database.ServerValue; @@ -25,13 +27,13 @@ interface KeySetterFn { } @SuppressWarnings("WeakerAccess") -class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { +class Module extends ReactContextBaseJavaModule implements LifecycleEventListener { private static final String TAG = "Firestack"; private Context context; private ReactContext mReactContext; private FirebaseApp app; - public FirestackModule(ReactApplicationContext reactContext, Context context) { + public Module(ReactApplicationContext reactContext, Context context) { super(reactContext); this.context = context; mReactContext = reactContext; @@ -44,6 +46,19 @@ public String getName() { return TAG; } +// private static Boolean hasValidPlayServices() { +// final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this.context); +// if (status != ConnectionResult.SUCCESS) { +// Log.e(TAG, GooglePlayServicesUtil.getErrorString(status)); +// Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, 1); +// dialog.show(); +// return false; +// } else { +// Log.i(TAG, GooglePlayServicesUtil.getErrorString(status)); +// return true; +// } +// } + @ReactMethod public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) { Log.i(TAG, "configureWithOptions"); @@ -169,14 +184,14 @@ public void serverValue(@Nullable final Callback onComplete) { public void onHostResume() { WritableMap params = Arguments.createMap(); params.putBoolean("isForground", true); - FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); + Utils.sendEvent(mReactContext, "FirestackAppState", params); } @Override public void onHostPause() { WritableMap params = Arguments.createMap(); params.putBoolean("isForground", false); - FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); + Utils.sendEvent(mReactContext, "FirestackAppState", params); } @Override diff --git a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/Package.java similarity index 76% rename from android/src/main/java/io/fullstack/firestack/FirestackPackage.java rename to android/src/main/java/io/fullstack/firestack/Package.java index 4ab5e00..bb8cc7c 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/Package.java @@ -13,10 +13,10 @@ import java.util.Collections; @SuppressWarnings("unused") -public class FirestackPackage implements ReactPackage { +public class Package implements ReactPackage { private Context mContext; - public FirestackPackage() { + public Package() { } /** * @param reactContext react application context that can be used to create modules @@ -25,12 +25,12 @@ public FirestackPackage() { @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new FirestackModule(reactContext, reactContext.getBaseContext())); - modules.add(new FirestackAuthModule(reactContext)); - modules.add(new FirestackDatabaseModule(reactContext)); - modules.add(new FirestackAnalyticsModule(reactContext)); - modules.add(new FirestackStorageModule(reactContext)); - modules.add(new FirestackCloudMessaging(reactContext)); + modules.add(new Module(reactContext, reactContext.getBaseContext())); + modules.add(new Auth(reactContext)); + modules.add(new Database(reactContext)); + modules.add(new Analytics(reactContext)); + modules.add(new Storage(reactContext)); + modules.add(new Messaging(reactContext)); return modules; } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/Utils.java similarity index 93% rename from android/src/main/java/io/fullstack/firestack/FirestackUtils.java rename to android/src/main/java/io/fullstack/firestack/Utils.java index 669e337..de1c4e9 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ b/android/src/main/java/io/fullstack/firestack/Utils.java @@ -7,22 +7,22 @@ import java.util.List; import java.util.Map; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.ReadableArray; import com.google.firebase.database.DataSnapshot; +import com.facebook.react.bridge.ReadableMapKeySetIterator; @SuppressWarnings("WeakerAccess") -public class FirestackUtils { - private static final String TAG = "FirestackUtils"; +public class Utils { + private static final String TAG = "Utils"; // TODO NOTE public static void todoNote(final String tag, final String name, final Callback callback) { @@ -84,12 +84,12 @@ public static WritableMap dataSnapshotToMap( data.putString("value", null); } } else { - WritableMap valueMap = FirestackUtils.castSnapshotValue(dataSnapshot); + WritableMap valueMap = Utils.castSnapshotValue(dataSnapshot); data.putMap("value", valueMap); } // Child keys - WritableArray childKeys = FirestackUtils.getChildKeys(dataSnapshot); + WritableArray childKeys = Utils.getChildKeys(dataSnapshot); data.putArray("childKeys", childKeys); Object priority = dataSnapshot.getPriority(); @@ -253,10 +253,10 @@ public static Map recursivelyDeconstructReadableMap(ReadableMap deconstructedMap.put(key, readableMap.getString(key)); break; case Map: - deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); + deconstructedMap.put(key, Utils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); break; case Array: - deconstructedMap.put(key, FirestackUtils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); + deconstructedMap.put(key, Utils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); break; default: throw new IllegalArgumentException("Could not convert object with key: " + key + "."); @@ -284,10 +284,10 @@ public static List recursivelyDeconstructReadableArray(ReadableArray rea deconstructedList.add(i, readableArray.getString(i)); break; case Map: - deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); + deconstructedList.add(i, Utils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); break; case Array: - deconstructedList.add(i, FirestackUtils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); + deconstructedList.add(i, Utils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); break; default: throw new IllegalArgumentException("Could not convert object at index " + i + "."); From e9489c12d65f9e8ed78df774415908b82fc3d0f9 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 21 Nov 2016 12:33:06 +0000 Subject: [PATCH 127/275] misc utils/constants --- lib/constants.js | 27 +++++++++++++++++++++++++++ lib/utils/index.js | 12 ++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 lib/constants.js create mode 100644 lib/utils/index.js diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 0000000..83efd38 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,27 @@ +import { reverseKeyValues } from './utils'; + +export const ConnectionResult = { + SUCCESS: 0, + SERVICE_MISSING: 1, + SERVICE_VERSION_UPDATE_REQUIRED: 2, + SERVICE_DISABLED: 3, + SIGN_IN_REQUIRED: 4, + INVALID_ACCOUNT: 5, + RESOLUTION_REQUIRED: 6, + NETWORK_ERROR: 7, + INTERNAL_ERROR: 8, + SERVICE_INVALID: 9, + DEVELOPER_ERROR: 10, + LICENSE_CHECK_FAILED: 11, + CANCELED: 13, + TIMEOUT: 14, + INTERRUPTED: 15, + API_UNAVAILABLE: 16, + SIGN_IN_FAILED: 17, + SERVICE_UPDATING: 18, + SERVICE_MISSING_PERMISSION: 19, + RESTRICTED_PROFILE: 20, +}; + + +export const ConnectionResultReverse = reverseKeyValues(ConnectionResult); diff --git a/lib/utils/index.js b/lib/utils/index.js new file mode 100644 index 0000000..9bec3ec --- /dev/null +++ b/lib/utils/index.js @@ -0,0 +1,12 @@ +/** + * Makes an objects keys it's values + * @param object + * @returns {{}} + */ +export function reverseKeyValues(object) { + const output = {}; + for (const key in object) { + output[object[key]] = key; + } + return output; +} From 6915be5fd7f65185e0c2e25773c05275e2a005ac Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 21 Nov 2016 13:04:37 +0000 Subject: [PATCH 128/275] added credential type annotations --- .../firestack/{Package.java => FirestackPackage.java} | 4 ++-- lib/modules/auth.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) rename android/src/main/java/io/fullstack/firestack/{Package.java => FirestackPackage.java} (95%) diff --git a/android/src/main/java/io/fullstack/firestack/Package.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java similarity index 95% rename from android/src/main/java/io/fullstack/firestack/Package.java rename to android/src/main/java/io/fullstack/firestack/FirestackPackage.java index bb8cc7c..8f8c2f7 100644 --- a/android/src/main/java/io/fullstack/firestack/Package.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -13,10 +13,10 @@ import java.util.Collections; @SuppressWarnings("unused") -public class Package implements ReactPackage { +public class FirestackPackage implements ReactPackage { private Context mContext; - public Package() { + public FirestackPackage() { } /** * @param reactContext react application context that can be used to create modules diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 04e5775..054df67 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -9,6 +9,7 @@ const FirestackAuth = NativeModules.FirestackAuth; const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); type AuthResultType = { authenticated: boolean, user: Object|null }; +type CredentialType = { provider: string, token: string, secret: string }; export default class Auth extends Base { _user: User|null; @@ -138,7 +139,7 @@ export default class Auth extends Base { * Sign the user in with a third-party authentication provider * @return {Promise} A promise resolved upon completion */ - signInWithCredential(credential): Promise { + signInWithCredential(credential: CredentialType): Promise { return promisify('signInWithProvider', FirestackAuth)(credential.provider, credential.token, credential.secret); } @@ -146,7 +147,7 @@ export default class Auth extends Base { * Re-authenticate a user with a third-party authentication provider * @return {Promise} A promise resolved upon completion */ - reauthenticateUser(credential): Promise { + reauthenticateUser(credential: CredentialType): Promise { return promisify('reauthenticateWithCredentialForProvider', FirestackAuth)(credential.provider, credential.token, credential.secret); } From e4774b22772a6035d24354456deefcad6ccc2916 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 21 Nov 2016 13:05:47 +0000 Subject: [PATCH 129/275] remove completed todo --- lib/modules/auth.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 054df67..10598c0 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -134,7 +134,6 @@ export default class Auth extends Base { return promisify('signInWithCustomToken', FirestackAuth)(customToken); } - // TODO credential type definition /** * Sign the user in with a third-party authentication provider * @return {Promise} A promise resolved upon completion From d3d4432de2e24e1c14e2b94115ccea40b0a0f784 Mon Sep 17 00:00:00 2001 From: florianbepunkt Date: Mon, 21 Nov 2016 14:27:03 +0100 Subject: [PATCH 130/275] Fix syntax error in authListener use firestack instead of firebase in auth listener code example --- docs/api/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/authentication.md b/docs/api/authentication.md index d787374..05f2ffe 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -26,7 +26,7 @@ class Example extends React.Component { } componentDidMount() { - this.unsubscribe = firebase.auth().onAuthStateChanged((user) => { + this.unsubscribe = firestack.auth().onAuthStateChanged((user) => { if (user) { // User is signed in. } From 038bfb2a854c0f3f92531680f447ef8cd2243c1a Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 21 Nov 2016 14:58:52 +0000 Subject: [PATCH 131/275] Fix iOS auth exception --- ios/Firestack/FirestackAuth.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index a7ac803..62dc53a 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -137,7 +137,7 @@ @implementation FirestackAuth sendJSEvent:AUTH_CHANGED_EVENT props: @{ @"eventName": @"userTokenError", - @"msg": [error localizedFailureReason] + @"msg": [error localizedDescription] }]; } else { [self From b569ff7e823ec522776bdbd6980e016eaaf81fc3 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 21 Nov 2016 19:28:29 +0000 Subject: [PATCH 132/275] restructure java files to match similar format as FireBase --- .../java/io/fullstack/firestack/Database.java | 619 ------------------ ...e.java => FirestackInstanceIdService.java} | 13 +- ...ce.java => FirestackMessagingService.java} | 10 +- .../{Module.java => FirestackModule.java} | 4 +- .../fullstack/firestack/FirestackPackage.java | 19 +- .../java/io/fullstack/firestack/Utils.java | 4 +- .../FirestackAnalytics.java} | 10 +- .../{Auth.java => auth/FirestackAuth.java} | 8 +- .../firestack/database/FirestackDatabase.java | 357 ++++++++++ .../database/FirestackDatabaseReference.java | 272 ++++++++ .../FirestackMessaging.java} | 15 +- .../FirestackStorage.java} | 10 +- 12 files changed, 680 insertions(+), 661 deletions(-) delete mode 100644 android/src/main/java/io/fullstack/firestack/Database.java rename android/src/main/java/io/fullstack/firestack/{InstanceIdService.java => FirestackInstanceIdService.java} (75%) rename android/src/main/java/io/fullstack/firestack/{MessagingService.java => FirestackMessagingService.java} (88%) rename android/src/main/java/io/fullstack/firestack/{Module.java => FirestackModule.java} (97%) rename android/src/main/java/io/fullstack/firestack/{Analytics.java => analytics/FirestackAnalytics.java} (96%) rename android/src/main/java/io/fullstack/firestack/{Auth.java => auth/FirestackAuth.java} (98%) create mode 100644 android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java create mode 100644 android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java rename android/src/main/java/io/fullstack/firestack/{Messaging.java => messaging/FirestackMessaging.java} (96%) rename android/src/main/java/io/fullstack/firestack/{Storage.java => storage/FirestackStorage.java} (97%) diff --git a/android/src/main/java/io/fullstack/firestack/Database.java b/android/src/main/java/io/fullstack/firestack/Database.java deleted file mode 100644 index 8c98663..0000000 --- a/android/src/main/java/io/fullstack/firestack/Database.java +++ /dev/null @@ -1,619 +0,0 @@ -package io.fullstack.firestack; - -import java.util.Map; -import java.util.List; -import android.net.Uri; -import android.util.Log; -import java.util.HashMap; -import android.content.Context; -import java.util.ListIterator; - -import com.facebook.react.bridge.Callback; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReadableMapKeySetIterator; - -import com.google.firebase.database.Query; -import com.google.firebase.database.DataSnapshot; -import com.google.firebase.database.OnDisconnect; -import com.google.firebase.database.DatabaseError; -import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.ChildEventListener; -import com.google.firebase.database.ValueEventListener; - -class DatabaseReference { - private static final String TAG = "DatabaseReference"; - - private String mPath; -// private ReadableArray mModifiers; - private HashMap mListeners = new HashMap(); - private Database mDatabase; - private ChildEventListener mEventListener; - private ValueEventListener mValueListener; - private ValueEventListener mOnceValueListener; - private ReactContext mReactContext; - - public DatabaseReference(final ReactContext context, final String path) { - mReactContext = context; - mPath = path; - } - -// public void setModifiers(final ReadableArray modifiers) { -// mModifiers = modifiers; -// } - - public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final DatabaseReference self = this; - - if (mEventListener == null) { - mEventListener = new ChildEventListener() { - @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_added", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_changed", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("child_removed", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_moved", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError(name, mPath, error); - } - }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addChildEventListener(mEventListener); - } - } - - public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final DatabaseReference self = this; - - mValueListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("value", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError("value", mPath, error); - } - }; - - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addValueEventListener(mValueListener); - //this.setListeningTo(mPath, modifiersString, "value"); - } - - public void addOnceValueEventListener(final ReadableArray modifiersArray, - final String modifiersString, - final Callback callback) { - final DatabaseReference self = this; - - mOnceValueListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = Utils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); - callback.invoke(null, data); - } - - @Override - public void onCancelled(DatabaseError error) { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", error.getCode()); - err.putString("errorDetails", error.getDetails()); - err.putString("description", error.getMessage()); - callback.invoke(err); - } - }; - - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addListenerForSingleValueEvent(mOnceValueListener); - } - - //public Boolean isListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // return mListeners.containsKey(key); - //} - - /** - * Note: these path/eventType listeners only get removed when javascript calls .off() and cleanup is run on the entire path - */ - //public void setListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // mListeners.put(key, true); - //} - - //public void notListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // mListeners.remove(key); - //} - - //private String pathListeningKey(final String path, String modifiersString, final String eventName) { - //return "listener/" + path + "/" + modifiersString + "/" + eventName; - //} - - public void cleanup() { - Log.d(TAG, "cleaning up database reference " + this); - this.removeChildEventListener(); - this.removeValueEventListener(); - } - - public void removeChildEventListener() { - if (mEventListener != null) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); - ref.removeEventListener(mEventListener); - //this.notListeningTo(mPath, "child_added"); - //this.notListeningTo(mPath, "child_changed"); - //this.notListeningTo(mPath, "child_removed"); - //this.notListeningTo(mPath, "child_moved"); - mEventListener = null; - } - } - - public void removeValueEventListener() { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); - if (mValueListener != null) { - ref.removeEventListener(mValueListener); - //this.notListeningTo(mPath, "value"); - mValueListener = null; - } - if (mOnceValueListener != null) { - ref.removeEventListener(mOnceValueListener); - mOnceValueListener = null; - } - } - - private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { - //if (!DatabaseReference.this.isListeningTo(path, modifiersString, name)) { - //return; - //} - WritableMap data = Utils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); - WritableMap evt = Arguments.createMap(); - evt.putString("eventName", name); - evt.putString("path", path); - evt.putString("modifiersString", modifiersString); - evt.putMap("body", data); - - Utils.sendEvent(mReactContext, "database_event", evt); - } - - private void handleDatabaseError(final String name, final String path, final DatabaseError error) { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", error.getCode()); - err.putString("errorDetails", error.getDetails()); - err.putString("description", error.getMessage()); - - WritableMap evt = Arguments.createMap(); - evt.putString("eventName", name); - evt.putString("path", path); - evt.putMap("body", err); - - Utils.sendEvent(mReactContext, "database_error", evt); - } - - public com.google.firebase.database.DatabaseReference getDatabaseRef() { - return FirebaseDatabase.getInstance().getReference(mPath); - } - - private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); - - List strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers); - ListIterator it = strModifiers.listIterator(); - Query query = ref.orderByKey(); - - while(it.hasNext()) { - String str = (String) it.next(); - - String[] strArr = str.split(":"); - String methStr = strArr[0]; - - if (methStr.equalsIgnoreCase("orderByKey")) { - query = ref.orderByKey(); - } else if (methStr.equalsIgnoreCase("orderByValue")) { - query = ref.orderByValue(); - } else if (methStr.equalsIgnoreCase("orderByPriority")) { - query = ref.orderByPriority(); - } else if (methStr.contains("orderByChild")) { - String key = strArr[1]; - Log.d(TAG, "orderByChild: " + key); - query = ref.orderByChild(key); - } else if (methStr.contains("limitToLast")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToLast: " + limit); - query = query.limitToLast(limit); - } else if (methStr.contains("limitToFirst")) { - String key = strArr[1]; - int limit = Integer.parseInt(key); - Log.d(TAG, "limitToFirst: " + limit); - query = query.limitToFirst(limit); - } else if (methStr.contains("equalTo")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.equalTo(value); - } else { - query = query.equalTo(value, key); - } - } else if (methStr.contains("endAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.endAt(value); - } else { - query = query.endAt(value, key); - } - } else if (methStr.contains("startAt")) { - String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.startAt(value); - } else { - query = query.startAt(value, key); - } - } - } - - return query; - } - -} - -class Database extends ReactContextBaseJavaModule { - - private static final String TAG = "FirestackDatabase"; - - private Context context; - private ReactContext mReactContext; - private HashMap mDBListeners = new HashMap(); - - public Database(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - mReactContext = reactContext; - } - - @Override - public String getName() { - return TAG; - } - - // Persistence - @ReactMethod - public void enablePersistence( - final Boolean enable, - final Callback callback) { - try { - FirebaseDatabase.getInstance() - .setPersistenceEnabled(enable); - } catch (Throwable t) { - Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); - } - - WritableMap res = Arguments.createMap(); - res.putString("status", "success"); - callback.invoke(null, res); - } - - @ReactMethod - public void keepSynced( - final String path, - final Boolean enable, - final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - ref.keepSynced(enable); - - WritableMap res = Arguments.createMap(); - res.putString("status", "success"); - res.putString("path", path); - callback.invoke(null, res); - } - - // Database - @ReactMethod - public void set( - final String path, - final ReadableMap props, - final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - final Database self = this; - Map m = Utils.recursivelyDeconstructReadableMap(props); - - com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { - handleCallback("set", callback, error, ref); - } - }; - - ref.setValue(m, listener); - } - - @ReactMethod - public void update(final String path, - final ReadableMap props, - final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final Database self = this; - Map m = Utils.recursivelyDeconstructReadableMap(props); - - com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { - handleCallback("update", callback, error, ref); - } - }; - - ref.updateChildren(m, listener); - } - - @ReactMethod - public void remove(final String path, - final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final Database self = this; - com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { - handleCallback("remove", callback, error, ref); - } - }; - - ref.removeValue(listener); - } - - @ReactMethod - public void push(final String path, - final ReadableMap props, - final Callback callback) { - - Log.d(TAG, "Called push with " + path); - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - com.google.firebase.database.DatabaseReference newRef = ref.push(); - - final Uri url = Uri.parse(newRef.toString()); - final String newPath = url.getPath(); - - ReadableMapKeySetIterator iterator = props.keySetIterator(); - if (iterator.hasNextKey()) { - Log.d(TAG, "Passed value to push"); - // lame way to check if the `props` are empty - final Database self = this; - Map m = Utils.recursivelyDeconstructReadableMap(props); - - com.google.firebase.database.DatabaseReference.CompletionListener listener = new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError error, com.google.firebase.database.DatabaseReference ref) { - if (error != null) { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", error.getCode()); - err.putString("errorDetails", error.getDetails()); - err.putString("description", error.getMessage()); - callback.invoke(err); - } else { - WritableMap res = Arguments.createMap(); - res.putString("status", "success"); - res.putString("ref", newPath); - callback.invoke(null, res); - } - } - }; - - newRef.setValue(m, listener); - } else { - Log.d(TAG, "No value passed to push: " + newPath); - WritableMap res = Arguments.createMap(); - res.putString("result", "success"); - res.putString("ref", newPath); - callback.invoke(null, res); - } - } - - @ReactMethod - public void on(final String path, - final String modifiersString, - final ReadableArray modifiersArray, - final String name, - final Callback callback) { - DatabaseReference ref = this.getDBHandle(path, modifiersString); - - WritableMap resp = Arguments.createMap(); - - if (name.equals("value")) { - ref.addValueEventListener(name, modifiersArray, modifiersString); - } else { - ref.addChildEventListener(name, modifiersArray, modifiersString); - } - - this.saveDBHandle(path, modifiersString, ref); - resp.putString("result", "success"); - Log.d(TAG, "Added listener " + name + " for " + ref + "with modifiers: "+ modifiersString); - - resp.putString("handle", path); - callback.invoke(null, resp); - } - - @ReactMethod - public void onOnce(final String path, - final String modifiersString, - final ReadableArray modifiersArray, - final String name, - final Callback callback) { - Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - DatabaseReference ref = this.getDBHandle(path, modifiersString); - ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); - } - - /** - * At the time of this writing, off() only gets called when there are no more subscribers to a given path. - * `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so - * it doesn't really matter- just polluting the RN bridge a little more than necessary. - * off() should therefore clean *everything* up - */ - @ReactMethod - public void off( - final String path, - final String modifiersString, - @Deprecated final String name, - final Callback callback) { - this.removeDBHandle(path, modifiersString); - Log.d(TAG, "Removed listener " + path + "/" + modifiersString); - WritableMap resp = Arguments.createMap(); - resp.putString("handle", path); - resp.putString("result", "success"); - callback.invoke(null, resp); - } - - // On Disconnect - @ReactMethod - public void onDisconnectSetObject(final String path, final ReadableMap props, final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - Map m = Utils.recursivelyDeconstructReadableMap(props); - - OnDisconnect od = ref.onDisconnect(); - od.setValue(m, new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { - handleCallback("onDisconnectSetObject", callback, databaseError, databaseReference); - } - }); - } - - @ReactMethod - public void onDisconnectSetString(final String path, final String value, final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - OnDisconnect od = ref.onDisconnect(); - od.setValue(value, new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { - handleCallback("onDisconnectSetString", callback, databaseError, databaseReference); - } - }); - } - - @ReactMethod - public void onDisconnectRemove(final String path, final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - OnDisconnect od = ref.onDisconnect(); - od.removeValue(new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { - handleCallback("onDisconnectRemove", callback, databaseError, databaseReference); - } - }); - } - @ReactMethod - public void onDisconnectCancel(final String path, final Callback callback) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - OnDisconnect od = ref.onDisconnect(); - od.cancel(new com.google.firebase.database.DatabaseReference.CompletionListener() { - @Override - public void onComplete(DatabaseError databaseError, com.google.firebase.database.DatabaseReference databaseReference) { - handleCallback("onDisconnectCancel", callback, databaseError, databaseReference); - } - }); - } - - // Private helpers - // private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) { - // WritableMap data = this.dataSnapshotToMap(name, dataSnapshot); - // WritableMap evt = Arguments.createMap(); - // evt.putString("eventName", name); - // evt.putMap("body", data); - // Utils.sendEvent(mReactContext, "database_event", evt); - // } - - // private void handleDatabaseError(final String name, final DatabaseError error) { - // WritableMap err = Arguments.createMap(); - // err.putInt("errorCode", error.getCode()); - // err.putString("errorDetails", error.getDetails()); - // err.putString("description", error.getMessage()); - - // WritableMap evt = Arguments.createMap(); - // evt.putString("eventName", name); - // evt.putMap("body", err); - // Utils.sendEvent(mReactContext, "database_error", evt); - // } - - private void handleCallback( - final String methodName, - final Callback callback, - final DatabaseError databaseError, - final com.google.firebase.database.DatabaseReference databaseReference) { - if (databaseError != null) { - WritableMap err = Arguments.createMap(); - err.putInt("errorCode", databaseError.getCode()); - err.putString("errorDetails", databaseError.getDetails()); - err.putString("description", databaseError.getMessage()); - callback.invoke(err); - } else { - WritableMap res = Arguments.createMap(); - res.putString("status", "success"); - res.putString("method", methodName); - callback.invoke(null, res); - } - } - - private DatabaseReference getDBHandle(final String path, final String modifiersString) { - String key = this.getDBListenerKey(path, modifiersString); - if (!mDBListeners.containsKey(key)) { - ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(key, new DatabaseReference(ctx, path)); - } - - return mDBListeners.get(key); - } - - private void saveDBHandle(final String path, String modifiersString, final DatabaseReference dbRef) { - String key = this.getDBListenerKey(path, modifiersString); - mDBListeners.put(key, dbRef); - } - - private String getDBListenerKey(String path, String modifiersString) { - return path + " | " + modifiersString; - } - - private void removeDBHandle(final String path, String modifiersString) { - String key = this.getDBListenerKey(path, modifiersString); - if (mDBListeners.containsKey(key)) { - DatabaseReference r = mDBListeners.get(key); - r.cleanup(); - mDBListeners.remove(key); - } - } - - private String keyPath(final String path, final String eventName) { - return path + "-" + eventName; - } - - // TODO: move to DatabaseReference? - private com.google.firebase.database.DatabaseReference getDatabaseReferenceAtPath(final String path) { - com.google.firebase.database.DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); - return mDatabase; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/InstanceIdService.java b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java similarity index 75% rename from android/src/main/java/io/fullstack/firestack/InstanceIdService.java rename to android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java index d413336..f27f8c9 100644 --- a/android/src/main/java/io/fullstack/firestack/InstanceIdService.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java @@ -1,16 +1,15 @@ package io.fullstack.firestack; -/** - * Created by nori on 2016/09/12. - */ -import android.content.Intent; -import android.os.Bundle; import android.util.Log; +import android.os.Bundle; +import android.content.Intent; import com.google.firebase.iid.FirebaseInstanceId; import com.google.firebase.iid.FirebaseInstanceIdService; -public class InstanceIdService extends FirebaseInstanceIdService { +import io.fullstack.firestack.messaging.FirestackMessaging; + +public class FirestackInstanceIdService extends FirebaseInstanceIdService { private static final String TAG = "FSInstanceIdService"; @@ -21,7 +20,7 @@ public class InstanceIdService extends FirebaseInstanceIdService { public void onTokenRefresh() { String refreshedToken = FirebaseInstanceId.getInstance().getToken(); Log.d(TAG, "Refreshed token: " + refreshedToken); - Intent i = new Intent(Messaging.INTENT_NAME_TOKEN); + Intent i = new Intent(FirestackMessaging.INTENT_NAME_TOKEN); Bundle bundle = new Bundle(); bundle.putString("token", refreshedToken); i.putExtras(bundle); diff --git a/android/src/main/java/io/fullstack/firestack/MessagingService.java b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java similarity index 88% rename from android/src/main/java/io/fullstack/firestack/MessagingService.java rename to android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java index 6204fb3..07ae241 100644 --- a/android/src/main/java/io/fullstack/firestack/MessagingService.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java @@ -7,7 +7,9 @@ import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.SendException; -public class MessagingService extends FirebaseMessagingService { +import io.fullstack.firestack.messaging.FirestackMessaging; + +public class FirestackMessagingService extends FirebaseMessagingService { private static final String TAG = "FSMessagingService"; @@ -25,7 +27,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) { if (remoteMessage.getNotification() != null) { } - Intent i = new Intent(Messaging.INTENT_NAME_NOTIFICATION); + Intent i = new Intent(FirestackMessaging.INTENT_NAME_NOTIFICATION); i.putExtra("data", remoteMessage); sendOrderedBroadcast(i, null); @@ -35,7 +37,7 @@ public void onMessageReceived(RemoteMessage remoteMessage) { public void onMessageSent(String msgId) { // Called when an upstream message has been successfully sent to the GCM connection server. Log.d(TAG, "upstream message has been successfully sent"); - Intent i = new Intent(Messaging.INTENT_NAME_SEND); + Intent i = new Intent(FirestackMessaging.INTENT_NAME_SEND); i.putExtra("msgId", msgId); sendOrderedBroadcast(i, null); } @@ -44,7 +46,7 @@ public void onMessageSent(String msgId) { public void onSendError(String msgId, Exception exception) { // Called when there was an error sending an upstream message. Log.d(TAG, "error sending an upstream message"); - Intent i = new Intent(Messaging.INTENT_NAME_SEND); + Intent i = new Intent(FirestackMessaging.INTENT_NAME_SEND); i.putExtra("msgId", msgId); i.putExtra("hasError", true); SendException sendException = (SendException) exception; diff --git a/android/src/main/java/io/fullstack/firestack/Module.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java similarity index 97% rename from android/src/main/java/io/fullstack/firestack/Module.java rename to android/src/main/java/io/fullstack/firestack/FirestackModule.java index 40c7123..f8c6647 100644 --- a/android/src/main/java/io/fullstack/firestack/Module.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -27,13 +27,13 @@ interface KeySetterFn { } @SuppressWarnings("WeakerAccess") -class Module extends ReactContextBaseJavaModule implements LifecycleEventListener { +class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { private static final String TAG = "Firestack"; private Context context; private ReactContext mReactContext; private FirebaseApp app; - public Module(ReactApplicationContext reactContext, Context context) { + public FirestackModule(ReactApplicationContext reactContext, Context context) { super(reactContext); this.context = context; mReactContext = reactContext; diff --git a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java index 8f8c2f7..6a71080 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -6,12 +6,19 @@ import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.ViewManager; import java.util.List; import java.util.ArrayList; import java.util.Collections; +import io.fullstack.firestack.auth.FirestackAuth; +import io.fullstack.firestack.storage.FirestackStorage; +import io.fullstack.firestack.database.FirestackDatabase; +import io.fullstack.firestack.analytics.FirestackAnalytics; +import io.fullstack.firestack.messaging.FirestackMessaging; + @SuppressWarnings("unused") public class FirestackPackage implements ReactPackage { private Context mContext; @@ -25,12 +32,12 @@ public FirestackPackage() { @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - modules.add(new Module(reactContext, reactContext.getBaseContext())); - modules.add(new Auth(reactContext)); - modules.add(new Database(reactContext)); - modules.add(new Analytics(reactContext)); - modules.add(new Storage(reactContext)); - modules.add(new Messaging(reactContext)); + modules.add(new FirestackModule(reactContext, reactContext.getBaseContext())); + modules.add(new FirestackAuth(reactContext)); + modules.add(new FirestackDatabase(reactContext)); + modules.add(new FirestackAnalytics(reactContext)); + modules.add(new FirestackStorage(reactContext)); + modules.add(new FirestackMessaging(reactContext)); return modules; } diff --git a/android/src/main/java/io/fullstack/firestack/Utils.java b/android/src/main/java/io/fullstack/firestack/Utils.java index de1c4e9..cd50c5a 100644 --- a/android/src/main/java/io/fullstack/firestack/Utils.java +++ b/android/src/main/java/io/fullstack/firestack/Utils.java @@ -37,9 +37,7 @@ public static void todoNote(final String tag, final String name, final Callback /** * send a JS event **/ - public static void sendEvent(final ReactContext context, - final String eventName, - final WritableMap params) { + public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) { if (context.hasActiveCatalystInstance()) { context .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) diff --git a/android/src/main/java/io/fullstack/firestack/Analytics.java b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java similarity index 96% rename from android/src/main/java/io/fullstack/firestack/Analytics.java rename to android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java index 6a80a32..2b98f27 100644 --- a/android/src/main/java/io/fullstack/firestack/Analytics.java +++ b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java @@ -1,4 +1,4 @@ -package io.fullstack.firestack; +package io.fullstack.firestack.analytics; import java.util.Map; import android.util.Log; @@ -11,14 +11,16 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; -class Analytics extends ReactContextBaseJavaModule { +import io.fullstack.firestack.Utils; + +public class FirestackAnalytics extends ReactContextBaseJavaModule { private static final String TAG = "FirestackAnalytics"; private ReactApplicationContext context; private FirebaseAnalytics mFirebaseAnalytics; - public Analytics(ReactApplicationContext reactContext) { + public FirestackAnalytics(ReactApplicationContext reactContext) { super(reactContext); context = reactContext; Log.d(TAG, "New instance"); @@ -111,7 +113,7 @@ public void setUserProperty(final String name, final String value) { // todo refactor/clean me private Bundle makeEventBundle(final String name, final Map map) { Bundle bundle = new Bundle(); - // Available from the Analytics event + // Available from the FirestackAnalytics event if (map.containsKey("id")) { String id = (String) map.get("id"); bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id); diff --git a/android/src/main/java/io/fullstack/firestack/Auth.java b/android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java similarity index 98% rename from android/src/main/java/io/fullstack/firestack/Auth.java rename to android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java index 8e18dd1..89cce37 100644 --- a/android/src/main/java/io/fullstack/firestack/Auth.java +++ b/android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java @@ -1,5 +1,5 @@ -package io.fullstack.firestack; +package io.fullstack.firestack.auth; import android.util.Log; @@ -29,9 +29,11 @@ import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; +import io.fullstack.firestack.Utils; + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") -class Auth extends ReactContextBaseJavaModule { +public class FirestackAuth extends ReactContextBaseJavaModule { private final int NO_CURRENT_USER = 100; private final int ERROR_FETCHING_TOKEN = 101; private final int ERROR_SENDING_VERIFICATION_EMAIL = 102; @@ -43,7 +45,7 @@ class Auth extends ReactContextBaseJavaModule { private FirebaseAuth mAuth; private FirebaseAuth.AuthStateListener mAuthListener; - public Auth(ReactApplicationContext reactContext) { + public FirestackAuth(ReactApplicationContext reactContext) { super(reactContext); mReactContext = reactContext; mAuth = FirebaseAuth.getInstance(); diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java new file mode 100644 index 0000000..ced179f --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -0,0 +1,357 @@ +package io.fullstack.firestack.database; + +import java.util.Map; +import android.net.Uri; +import android.util.Log; +import java.util.HashMap; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReadableMapKeySetIterator; + +import com.google.firebase.database.OnDisconnect; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + + +import io.fullstack.firestack.Utils; + +public class FirestackDatabase extends ReactContextBaseJavaModule { + private static final String TAG = "FirestackDatabase"; + private HashMap mDBListeners = new HashMap(); + + public FirestackDatabase(ReactApplicationContext reactContext) { + super(reactContext); + } + + @Override + public String getName() { + return TAG; + } + + // Persistence + @ReactMethod + public void enablePersistence( + final Boolean enable, + final Callback callback) { + try { + FirebaseDatabase.getInstance() + .setPersistenceEnabled(enable); + } catch (Throwable t) { + Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); + } + + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + callback.invoke(null, res); + } + + @ReactMethod + public void keepSynced( + final String path, + final Boolean enable, + final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + ref.keepSynced(enable); + + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + res.putString("path", path); + callback.invoke(null, res); + } + + // FirestackDatabase + @ReactMethod + public void set( + final String path, + final ReadableMap props, + final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + + final FirestackDatabase self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); + + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("set", callback, error, ref); + } + }; + + ref.setValue(m, listener); + } + + @ReactMethod + public void update(final String path, + final ReadableMap props, + final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + final FirestackDatabase self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); + + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("update", callback, error, ref); + } + }; + + ref.updateChildren(m, listener); + } + + @ReactMethod + public void remove(final String path, + final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + final FirestackDatabase self = this; + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("remove", callback, error, ref); + } + }; + + ref.removeValue(listener); + } + + @ReactMethod + public void push(final String path, + final ReadableMap props, + final Callback callback) { + + Log.d(TAG, "Called push with " + path); + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference newRef = ref.push(); + + final Uri url = Uri.parse(newRef.toString()); + final String newPath = url.getPath(); + + ReadableMapKeySetIterator iterator = props.keySetIterator(); + if (iterator.hasNextKey()) { + Log.d(TAG, "Passed value to push"); + // lame way to check if the `props` are empty + final FirestackDatabase self = this; + Map m = Utils.recursivelyDeconstructReadableMap(props); + + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + if (error != null) { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", error.getCode()); + err.putString("errorDetails", error.getDetails()); + err.putString("description", error.getMessage()); + callback.invoke(err); + } else { + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + res.putString("ref", newPath); + callback.invoke(null, res); + } + } + }; + + newRef.setValue(m, listener); + } else { + Log.d(TAG, "No value passed to push: " + newPath); + WritableMap res = Arguments.createMap(); + res.putString("result", "success"); + res.putString("ref", newPath); + callback.invoke(null, res); + } + } + + @ReactMethod + public void on(final String path, + final String modifiersString, + final ReadableArray modifiersArray, + final String name, + final Callback callback) { + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersString); + + WritableMap resp = Arguments.createMap(); + + if (name.equals("value")) { + ref.addValueEventListener(name, modifiersArray, modifiersString); + } else { + ref.addChildEventListener(name, modifiersArray, modifiersString); + } + + this.saveDBHandle(path, modifiersString, ref); + resp.putString("result", "success"); + Log.d(TAG, "Added listener " + name + " for " + ref + "with modifiers: "+ modifiersString); + + resp.putString("handle", path); + callback.invoke(null, resp); + } + + @ReactMethod + public void onOnce(final String path, + final String modifiersString, + final ReadableArray modifiersArray, + final String name, + final Callback callback) { + Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersString); + ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); + } + + /** + * At the time of this writing, off() only gets called when there are no more subscribers to a given path. + * `mListeners` might therefore be out of sync (though javascript isnt listening for those eventTypes, so + * it doesn't really matter- just polluting the RN bridge a little more than necessary. + * off() should therefore clean *everything* up + */ + @ReactMethod + public void off( + final String path, + final String modifiersString, + @Deprecated final String name, + final Callback callback) { + this.removeDBHandle(path, modifiersString); + Log.d(TAG, "Removed listener " + path + "/" + modifiersString); + WritableMap resp = Arguments.createMap(); + resp.putString("handle", path); + resp.putString("result", "success"); + callback.invoke(null, resp); + } + + // On Disconnect + @ReactMethod + public void onDisconnectSetObject(final String path, final ReadableMap props, final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + Map m = Utils.recursivelyDeconstructReadableMap(props); + + OnDisconnect od = ref.onDisconnect(); + od.setValue(m, new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + handleCallback("onDisconnectSetObject", callback, databaseError, databaseReference); + } + }); + } + + @ReactMethod + public void onDisconnectSetString(final String path, final String value, final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + + OnDisconnect od = ref.onDisconnect(); + od.setValue(value, new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + handleCallback("onDisconnectSetString", callback, databaseError, databaseReference); + } + }); + } + + @ReactMethod + public void onDisconnectRemove(final String path, final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + + OnDisconnect od = ref.onDisconnect(); + od.removeValue(new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + handleCallback("onDisconnectRemove", callback, databaseError, databaseReference); + } + }); + } + @ReactMethod + public void onDisconnectCancel(final String path, final Callback callback) { + DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + + OnDisconnect od = ref.onDisconnect(); + od.cancel(new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { + handleCallback("onDisconnectCancel", callback, databaseError, databaseReference); + } + }); + } + + // Private helpers + // private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) { + // WritableMap data = this.dataSnapshotToMap(name, dataSnapshot); + // WritableMap evt = Arguments.createMap(); + // evt.putString("eventName", name); + // evt.putMap("body", data); + // Utils.sendEvent(mReactContext, "database_event", evt); + // } + + // private void handleDatabaseError(final String name, final DatabaseError error) { + // WritableMap err = Arguments.createMap(); + // err.putInt("errorCode", error.getCode()); + // err.putString("errorDetails", error.getDetails()); + // err.putString("description", error.getMessage()); + + // WritableMap evt = Arguments.createMap(); + // evt.putString("eventName", name); + // evt.putMap("body", err); + // Utils.sendEvent(mReactContext, "database_error", evt); + // } + + private void handleCallback( + final String methodName, + final Callback callback, + final DatabaseError databaseError, + final DatabaseReference databaseReference) { + if (databaseError != null) { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", databaseError.getCode()); + err.putString("errorDetails", databaseError.getDetails()); + err.putString("description", databaseError.getMessage()); + callback.invoke(err); + } else { + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + res.putString("method", methodName); + callback.invoke(null, res); + } + } + + private FirestackDatabaseReference getDBHandle(final String path, final String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (!mDBListeners.containsKey(key)) { + ReactContext ctx = getReactApplicationContext(); + mDBListeners.put(key, new FirestackDatabaseReference(ctx, path)); + } + + return mDBListeners.get(key); + } + + private void saveDBHandle(final String path, String modifiersString, final FirestackDatabaseReference dbRef) { + String key = this.getDBListenerKey(path, modifiersString); + mDBListeners.put(key, dbRef); + } + + private String getDBListenerKey(String path, String modifiersString) { + return path + " | " + modifiersString; + } + + private void removeDBHandle(final String path, String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + if (mDBListeners.containsKey(key)) { + FirestackDatabaseReference r = mDBListeners.get(key); + r.cleanup(); + mDBListeners.remove(key); + } + } + + private String keyPath(final String path, final String eventName) { + return path + "-" + eventName; + } + + // TODO: move to FirestackDatabaseReference? + private DatabaseReference getDatabaseReferenceAtPath(final String path) { + DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); + return mDatabase; + } +} diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java new file mode 100644 index 0000000..0a93d54 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -0,0 +1,272 @@ +package io.fullstack.firestack.database; + +import java.util.List; +import android.util.Log; +import java.util.HashMap; +import java.util.ListIterator; + +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; + +import com.google.firebase.database.Query; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ChildEventListener; +import com.google.firebase.database.ValueEventListener; + +import io.fullstack.firestack.Utils; + +public class FirestackDatabaseReference { + private static final String TAG = "FirestackDatabaseReference"; + + private String mPath; + // private ReadableArray mModifiers; + private HashMap mListeners = new HashMap(); + private ChildEventListener mEventListener; + private ValueEventListener mValueListener; + private ValueEventListener mOnceValueListener; + private ReactContext mReactContext; + + public FirestackDatabaseReference(final ReactContext context, final String path) { + mReactContext = context; + mPath = path; + } + +// public void setModifiers(final ReadableArray modifiers) { +// mModifiers = modifiers; +// } + + public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { + final FirestackDatabaseReference self = this; + + if (mEventListener == null) { + mEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + self.handleDatabaseEvent("child_added", mPath, modifiersString, dataSnapshot); + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { + self.handleDatabaseEvent("child_changed", mPath, modifiersString, dataSnapshot); + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + self.handleDatabaseEvent("child_removed", mPath, modifiersString, dataSnapshot); + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { + self.handleDatabaseEvent("child_moved", mPath, modifiersString, dataSnapshot); + } + + @Override + public void onCancelled(DatabaseError error) { + self.handleDatabaseError(name, mPath, error); + } + }; + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); + ref.addChildEventListener(mEventListener); + } + } + + public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { + final FirestackDatabaseReference self = this; + + mValueListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + self.handleDatabaseEvent("value", mPath, modifiersString, dataSnapshot); + } + + @Override + public void onCancelled(DatabaseError error) { + self.handleDatabaseError("value", mPath, error); + } + }; + + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); + ref.addValueEventListener(mValueListener); + //this.setListeningTo(mPath, modifiersString, "value"); + } + + public void addOnceValueEventListener(final ReadableArray modifiersArray, + final String modifiersString, + final Callback callback) { + final FirestackDatabaseReference self = this; + + mOnceValueListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + WritableMap data = Utils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); + callback.invoke(null, data); + } + + @Override + public void onCancelled(DatabaseError error) { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", error.getCode()); + err.putString("errorDetails", error.getDetails()); + err.putString("description", error.getMessage()); + callback.invoke(err); + } + }; + + Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); + ref.addListenerForSingleValueEvent(mOnceValueListener); + } + + //public Boolean isListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // return mListeners.containsKey(key); + //} + + /** + * Note: these path/eventType listeners only get removed when javascript calls .off() and cleanup is run on the entire path + */ + //public void setListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.put(key, true); + //} + + //public void notListeningTo(final String path, String modifiersString, final String evtName) { + // String key = this.pathListeningKey(path, modifiersString, evtName); + // mListeners.remove(key); + //} + + //private String pathListeningKey(final String path, String modifiersString, final String eventName) { + //return "listener/" + path + "/" + modifiersString + "/" + eventName; + //} + + public void cleanup() { + Log.d(TAG, "cleaning up database reference " + this); + this.removeChildEventListener(); + this.removeValueEventListener(); + } + + public void removeChildEventListener() { + if (mEventListener != null) { + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + ref.removeEventListener(mEventListener); + //this.notListeningTo(mPath, "child_added"); + //this.notListeningTo(mPath, "child_changed"); + //this.notListeningTo(mPath, "child_removed"); + //this.notListeningTo(mPath, "child_moved"); + mEventListener = null; + } + } + + public void removeValueEventListener() { + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + if (mValueListener != null) { + ref.removeEventListener(mValueListener); + //this.notListeningTo(mPath, "value"); + mValueListener = null; + } + if (mOnceValueListener != null) { + ref.removeEventListener(mOnceValueListener); + mOnceValueListener = null; + } + } + + private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { + //if (!FirestackDatabaseReference.this.isListeningTo(path, modifiersString, name)) { + //return; + //} + WritableMap data = Utils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); + WritableMap evt = Arguments.createMap(); + evt.putString("eventName", name); + evt.putString("path", path); + evt.putString("modifiersString", modifiersString); + evt.putMap("body", data); + + Utils.sendEvent(mReactContext, "database_event", evt); + } + + private void handleDatabaseError(final String name, final String path, final DatabaseError error) { + WritableMap err = Arguments.createMap(); + err.putInt("errorCode", error.getCode()); + err.putString("errorDetails", error.getDetails()); + err.putString("description", error.getMessage()); + + WritableMap evt = Arguments.createMap(); + evt.putString("eventName", name); + evt.putString("path", path); + evt.putMap("body", err); + + Utils.sendEvent(mReactContext, "database_error", evt); + } + + public com.google.firebase.database.DatabaseReference getDatabaseRef() { + return FirebaseDatabase.getInstance().getReference(mPath); + } + + private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { + com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + + List strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers); + ListIterator it = strModifiers.listIterator(); + Query query = ref.orderByKey(); + + while(it.hasNext()) { + String str = (String) it.next(); + + String[] strArr = str.split(":"); + String methStr = strArr[0]; + + if (methStr.equalsIgnoreCase("orderByKey")) { + query = ref.orderByKey(); + } else if (methStr.equalsIgnoreCase("orderByValue")) { + query = ref.orderByValue(); + } else if (methStr.equalsIgnoreCase("orderByPriority")) { + query = ref.orderByPriority(); + } else if (methStr.contains("orderByChild")) { + String key = strArr[1]; + Log.d(TAG, "orderByChild: " + key); + query = ref.orderByChild(key); + } else if (methStr.contains("limitToLast")) { + String key = strArr[1]; + int limit = Integer.parseInt(key); + Log.d(TAG, "limitToLast: " + limit); + query = query.limitToLast(limit); + } else if (methStr.contains("limitToFirst")) { + String key = strArr[1]; + int limit = Integer.parseInt(key); + Log.d(TAG, "limitToFirst: " + limit); + query = query.limitToFirst(limit); + } else if (methStr.contains("equalTo")) { + String value = strArr[1]; + String key = strArr.length >= 3 ? strArr[2] : null; + if (key == null) { + query = query.equalTo(value); + } else { + query = query.equalTo(value, key); + } + } else if (methStr.contains("endAt")) { + String value = strArr[1]; + String key = strArr.length >= 3 ? strArr[2] : null; + if (key == null) { + query = query.endAt(value); + } else { + query = query.endAt(value, key); + } + } else if (methStr.contains("startAt")) { + String value = strArr[1]; + String key = strArr.length >= 3 ? strArr[2] : null; + if (key == null) { + query = query.startAt(value); + } else { + query = query.startAt(value, key); + } + } + } + + return query; + } + +} diff --git a/android/src/main/java/io/fullstack/firestack/Messaging.java b/android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java similarity index 96% rename from android/src/main/java/io/fullstack/firestack/Messaging.java rename to android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java index e6ae796..d4e2ff9 100644 --- a/android/src/main/java/io/fullstack/firestack/Messaging.java +++ b/android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java @@ -1,4 +1,4 @@ -package io.fullstack.firestack; +package io.fullstack.firestack.messaging; import java.util.Map; @@ -23,12 +23,11 @@ import com.google.firebase.messaging.FirebaseMessaging; import com.google.firebase.messaging.RemoteMessage; -/** - * Created by nori on 2016/09/12. - */ -public class Messaging extends ReactContextBaseJavaModule { +import io.fullstack.firestack.Utils; - private static final String TAG = "Messaging"; +public class FirestackMessaging extends ReactContextBaseJavaModule { + + private static final String TAG = "FirestackMessaging"; private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; @@ -37,14 +36,12 @@ public class Messaging extends ReactContextBaseJavaModule { public static final String INTENT_NAME_NOTIFICATION = "io.fullstack.firestack.ReceiveNotification"; public static final String INTENT_NAME_SEND = "io.fullstack.firestack.Upstream"; - private ReactContext mReactContext; private IntentFilter mRefreshTokenIntentFilter; private IntentFilter mReceiveNotificationIntentFilter; private IntentFilter mReceiveSendIntentFilter; - public Messaging(ReactApplicationContext reactContext) { + public FirestackMessaging(ReactApplicationContext reactContext) { super(reactContext); - mReactContext = reactContext; mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); diff --git a/android/src/main/java/io/fullstack/firestack/Storage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java similarity index 97% rename from android/src/main/java/io/fullstack/firestack/Storage.java rename to android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index 32f0a3d..112858e 100644 --- a/android/src/main/java/io/fullstack/firestack/Storage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -1,4 +1,4 @@ -package io.fullstack.firestack; +package io.fullstack.firestack.storage; import android.util.Log; import android.os.Environment; @@ -31,9 +31,11 @@ import com.google.firebase.storage.OnPausedListener; import com.google.firebase.storage.OnProgressListener; +import io.fullstack.firestack.Utils; + @SuppressWarnings("WeakerAccess") -class Storage extends ReactContextBaseJavaModule { +public class FirestackStorage extends ReactContextBaseJavaModule { private static final String TAG = "FirestackStorage"; private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; @@ -47,7 +49,7 @@ class Storage extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; - public Storage(ReactApplicationContext reactContext) { + public FirestackStorage(ReactApplicationContext reactContext) { super(reactContext); Log.d(TAG, "New instance"); @@ -65,7 +67,7 @@ public void downloadUrl(final String javascriptStorageBucket, FirebaseStorage storage = FirebaseStorage.getInstance(); String storageBucket = storage.getApp().getOptions().getStorageBucket(); String storageUrl = "gs://" + storageBucket; - Log.d(TAG, "Storage url " + storageUrl + path); + Log.d(TAG, "FirestackStorage url " + storageUrl + path); final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); final StorageReference fileRef = storageRef.child(path); From dc5e5299a3b430413a82f9264c546a1712108caf Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 22 Nov 2016 11:57:53 +0000 Subject: [PATCH 133/275] Fix nested user object in iOS with userCallback response --- ios/Firestack/FirestackAuth.m | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index 62dc53a..3419083 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -480,9 +480,7 @@ - (void) sendJSEvent:(NSString *)title - (void) userCallback:(RCTResponseSenderBlock) callback user:(FIRUser *) user { - NSDictionary *userProps = @{ - @"user": [self userPropsFromFIRUser:user] - }; + NSDictionary *userProps = [self userPropsFromFIRUser:user]; callback(@[[NSNull null], userProps]); } From 7b1830951b9851c6d5490540551ddb1bfbc4a34f Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 11:19:21 +0000 Subject: [PATCH 134/275] cleanup utils --- lib/firestack.js | 7 ++--- lib/modules/auth.js | 4 +-- lib/modules/base.js | 6 ++--- lib/modules/database/disconnect.js | 2 +- lib/modules/database/index.js | 2 +- lib/modules/database/reference.js | 2 +- lib/modules/messaging.js | 2 +- lib/modules/presence.js | 42 +++++++++++++++--------------- lib/utils/index.js | 31 ++++++++++++++++++++++ lib/utils/promisify.js | 14 ---------- 10 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 lib/utils/promisify.js diff --git a/lib/firestack.js b/lib/firestack.js index 1bf9153..eae2312 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -2,12 +2,10 @@ * @providesModule Firestack * @flow */ - - import { NativeModules, NativeEventEmitter } from 'react-native'; import Log from './utils/log'; -import promisify from './utils/promisify'; +import { promisify } from './utils'; import Singleton from './utils/singleton'; // modules @@ -32,9 +30,8 @@ export default class Firestack extends Singleton { /** * * @param options - * @param name - TODO support naming multiple instances */ - constructor(options: Object, name: string) { + constructor(options: Object) { const instance = super(options); instance.options = options || {}; diff --git a/lib/modules/auth.js b/lib/modules/auth.js index 10598c0..a067952 100644 --- a/lib/modules/auth.js +++ b/lib/modules/auth.js @@ -1,9 +1,9 @@ // @flow import { NativeModules, NativeEventEmitter } from 'react-native'; +import User from './user'; import { Base } from './base'; -import { default as User } from './user'; -import promisify from '../utils/promisify'; +import { promisify } from '../utils'; const FirestackAuth = NativeModules.FirestackAuth; const FirestackAuthEvt = new NativeEventEmitter(FirestackAuth); diff --git a/lib/modules/base.js b/lib/modules/base.js index 7aee499..23db072 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -1,9 +1,9 @@ /** * @flow */ -import { NativeModules, NativeEventEmitter, AsyncStorage } from 'react-native'; +import { NativeModules, NativeEventEmitter } from 'react-native'; -import Log from '../utils/log' +import Log from '../utils/log'; import EventEmitter from './../utils/eventEmitter'; const FirestackModule = NativeModules.Firestack; @@ -32,7 +32,7 @@ export class Base extends EventEmitter { // TODO unused - do we need this anymore? _addConstantExports(constants) { - Object.keys(constants).forEach(name => { + Object.keys(constants).forEach((name) => { FirestackModule[name] = constants[name]; }); } diff --git a/lib/modules/database/disconnect.js b/lib/modules/database/disconnect.js index 4c9fdcb..275380e 100644 --- a/lib/modules/database/disconnect.js +++ b/lib/modules/database/disconnect.js @@ -1,7 +1,7 @@ /* @flow */ import { NativeModules } from 'react-native'; -import promisify from './../../utils/promisify'; +import { promisify } from './../../utils'; import Reference from './reference'; const FirestackDatabase = NativeModules.FirestackDatabase; diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index 0ab99c6..3e386b8 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -7,7 +7,7 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; const FirestackDatabase = NativeModules.FirestackDatabase; const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); -import promisify from './../../utils/promisify'; +import { promisify } from './../../utils'; import { Base } from './../base'; import Reference from './reference.js'; diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index a42a046..c4b77a1 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -2,7 +2,7 @@ * @flow */ import { NativeModules } from 'react-native'; -import promisify from './../../utils/promisify'; +import { promisify } from './../../utils'; import { ReferenceBase } from './../base'; import Snapshot from './snapshot.js'; import Disconnect from './disconnect.js'; diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index 877cf29..5022cc2 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -1,6 +1,6 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; import { Base } from './base'; -import promisify from '../utils/promisify'; +import { promisify } from '../utils'; const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); diff --git a/lib/modules/presence.js b/lib/modules/presence.js index b91382b..fc53a71 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -1,6 +1,6 @@ -import invariant from 'invariant' -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' +import invariant from 'invariant'; +import { promisify } from '../utils'; +import { Base, ReferenceBase } from './base'; class PresenceRef extends ReferenceBase { constructor(presence, ref, pathParts) { @@ -18,24 +18,24 @@ class PresenceRef extends ReferenceBase { } setOnline() { - this.ref.setAt({online: true}) + this.ref.setAt({ online: true }); this._connectedRef.on('value', (snapshot) => { const val = snapshot.val(); if (val) { // add self to connection list // this.ref.push() this.ref.setAt({ - online: true - }) - .then(() => { - this._disconnect(); - - this._onConnect.forEach(fn => { - if (fn && typeof fn === 'function') { - fn.bind(this)(this.ref); - } - }) + online: true, }) + .then(() => { + this._disconnect(); + + this._onConnect.forEach((fn) => { + if (fn && typeof fn === 'function') { + fn.bind(this)(this.ref); + } + }); + }); } }); return this; @@ -43,8 +43,8 @@ class PresenceRef extends ReferenceBase { setOffline() { if (this.ref) { - this.ref.setAt({online: false}) - .then(() => this.ref.off('value')) + this.ref.setAt({ online: false }) + .then(() => this.ref.off('value')); this.presence.off(this._pathParts); } return this; @@ -53,10 +53,10 @@ class PresenceRef extends ReferenceBase { _disconnect() { if (this.ref) { this.ref.onDisconnect() - .setValue({online: false}); + .setValue({ online: false }); // set last online time this.lastOnlineRef.onDisconnect() - .setValue(this.firestack.ServerValue.TIMESTAMP) + .setValue(this.firestack.ServerValue.TIMESTAMP); } } @@ -72,7 +72,7 @@ class PresenceRef extends ReferenceBase { } export default class Presence extends Base { - constructor(firestack, options={}) { + constructor(firestack, options = {}) { super(firestack, options); this.instances = {}; @@ -85,7 +85,7 @@ export default class Presence extends Base { const pathKey = this._presenceKey(path); if (!this.instances[pathKey]) { const _ref = this.firestack.database.ref(pathKey); - this.log.debug('Created new presence object for ', pathKey) + this.log.debug('Created new presence object for ', pathKey); const inst = new PresenceRef(this, _ref, path); this.instances[pathKey] = inst; @@ -110,6 +110,6 @@ export default class Presence extends Base { } get namespace() { - return 'firestack:presence' + return 'firestack:presence'; } } diff --git a/lib/utils/index.js b/lib/utils/index.js index 9bec3ec..31d07f5 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -10,3 +10,34 @@ export function reverseKeyValues(object) { } return output; } + +/** + * No operation func + */ +export function noop() { +} + + +// internal promise handler +const _handler = (resolve, reject, err, resp) => { + // resolve / reject after events etc + setImmediate(() => { + if (err) return reject(err); + return resolve(resp); + }); +}; + +/** + * Wraps a native module method to support promises. + * @param fn + * @param NativeModule + */ +export function promisify(fn, NativeModule) { + return (...args) => { + return new Promise((resolve, reject) => { + const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; + if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.')); + return _fn.apply(NativeModule, [...args, _handler.bind(_handler, resolve, reject)]); + }); + }; +} diff --git a/lib/utils/promisify.js b/lib/utils/promisify.js deleted file mode 100644 index 9f0e089..0000000 --- a/lib/utils/promisify.js +++ /dev/null @@ -1,14 +0,0 @@ -const handler = (resolve, reject, err, resp) => { - setImmediate(() => { - if (err) return reject(err); - return resolve(resp); - }); -}; - -export default(fn, NativeModule) => (...args) => { - return new Promise((resolve, reject) => { - const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; - if (!_fn || typeof _fn !== 'function') return reject(new Error('Missing function for promisify.')); - return _fn.apply(NativeModule, [...args, handler.bind(handler, resolve, reject)]); - }); -}; From 7b38889e215677c6ba7c3b5f71a02e08f9d49e1e Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 11:20:45 +0000 Subject: [PATCH 135/275] cleanup storage / listeners --- lib/modules/storage.js | 81 ++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/lib/modules/storage.js b/lib/modules/storage.js index c5fc6c2..f4fc47a 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -1,11 +1,11 @@ +import { NativeModules, NativeEventEmitter } from 'react-native'; + +import { promisify, noop } from '../utils'; +import { Base, ReferenceBase } from './base'; -import {NativeModules, NativeEventEmitter} from 'react-native'; const FirestackStorage = NativeModules.FirestackStorage; const FirestackStorageEvt = new NativeEventEmitter(FirestackStorage); -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' - class StorageRef extends ReferenceBase { constructor(storage, path) { super(storage.firestack, path); @@ -21,34 +21,32 @@ class StorageRef extends ReferenceBase { /** * Downloads a reference to the device * @param {String} downloadPath Where to store the file + * @param listener * @return {Promise} */ - download (downloadPath, cb) { - let callback = cb; - if (!callback || typeof callback !== 'function') { - callback = (evt) => {}; - } - - const listeners = []; - listeners.push(this.storage._addListener('download_progress', callback)); - listeners.push(this.storage._addListener('download_paused', callback)); - listeners.push(this.storage._addListener('download_resumed', callback)); - + download(downloadPath: string, listener: Function = noop) { const path = this.pathToString(); + const listeners = [ + this.storage._addListener('download_progress', listener), + this.storage._addListener('download_paused', listener), + this.storage._addListener('download_resumed', listener), + ]; + return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) .then((res) => { console.log('res --->', res); listeners.forEach(this.storage._removeListener); return res; }) - .catch(err => { - console.log('Got an error ->', err); - }) + .catch((downloadError) => { + console.log('Got an error ->', downloadError); + return Promise.reject(downloadError); + }); } } export default class Storage extends Base { - constructor(firestack, options={}) { + constructor(firestack, options = {}) { super(firestack, options); if (this.options.storageBucket) { @@ -70,23 +68,20 @@ export default class Storage extends Base { /** * Upload a filepath * @param {string} name The destination for the file - * @param {string} filepath The local path of the file + * @param {string} filePath The local path of the file * @param {object} metadata An object containing metadata + * @param listener * @return {Promise} */ - uploadFile(name, filepath, metadata={}, cb) { - let callback = cb; - if (!callback || typeof callback !== 'function') { - callback = (evt) => {} - } - - filepath = filepath.replace("file://", ""); - - const listeners = []; - listeners.push(this._addListener('upload_progress', callback)); - listeners.push(this._addListener('upload_paused', callback)); - listeners.push(this._addListener('upload_resumed', callback)); - return promisify('uploadFile', FirestackStorage)(this.storageUrl, name, filepath, metadata) + uploadFile(name: string, filePath: string, metadata: Object = {}, listener: Function = noop) { + const _filePath = filePath.replace('file://', ''); + const listeners = [ + this._addListener('upload_paused', listener), + this._addListener('upload_resumed', listener), + this._addListener('upload_progress', listener), + ]; + + return promisify('uploadFile', FirestackStorage)(this.storageUrl, name, _filePath, metadata) .then((res) => { listeners.forEach(this._removeListener); return res; @@ -115,19 +110,19 @@ export default class Storage extends Base { } static constants = { - 'MAIN_BUNDLE_PATH': FirestackStorage.MAIN_BUNDLE_PATH, - 'CACHES_DIRECTORY_PATH': FirestackStorage.CACHES_DIRECTORY_PATH, - 'DOCUMENT_DIRECTORY_PATH': FirestackStorage.DOCUMENT_DIRECTORY_PATH, - 'EXTERNAL_DIRECTORY_PATH': FirestackStorage.EXTERNAL_DIRECTORY_PATH, - 'EXTERNAL_STORAGE_DIRECTORY_PATH': FirestackStorage.EXTERNAL_STORAGE_DIRECTORY_PATH, - 'TEMP_DIRECTORY_PATH': FirestackStorage.TEMP_DIRECTORY_PATH, - 'LIBRARY_DIRECTORY_PATH': FirestackStorage.LIBRARY_DIRECTORY_PATH, - 'FILETYPE_REGULAR': FirestackStorage.FILETYPE_REGULAR, - 'FILETYPE_DIRECTORY': FirestackStorage.FILETYPE_DIRECTORY + MAIN_BUNDLE_PATH: FirestackStorage.MAIN_BUNDLE_PATH, + CACHES_DIRECTORY_PATH: FirestackStorage.CACHES_DIRECTORY_PATH, + DOCUMENT_DIRECTORY_PATH: FirestackStorage.DOCUMENT_DIRECTORY_PATH, + EXTERNAL_DIRECTORY_PATH: FirestackStorage.EXTERNAL_DIRECTORY_PATH, + EXTERNAL_STORAGE_DIRECTORY_PATH: FirestackStorage.EXTERNAL_STORAGE_DIRECTORY_PATH, + TEMP_DIRECTORY_PATH: FirestackStorage.TEMP_DIRECTORY_PATH, + LIBRARY_DIRECTORY_PATH: FirestackStorage.LIBRARY_DIRECTORY_PATH, + FILETYPE_REGULAR: FirestackStorage.FILETYPE_REGULAR, + FILETYPE_DIRECTORY: FirestackStorage.FILETYPE_DIRECTORY, }; get namespace() { - return 'firestack:storage' + return 'firestack:storage'; } } From 963d6d3b829ec08cd3529a64bffbd138ad25eb5d Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 11:24:46 +0000 Subject: [PATCH 136/275] import #157 --- .../firestack/analytics/FirestackAnalytics.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java index 2b98f27..fc85ba8 100644 --- a/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java @@ -127,16 +127,16 @@ private Bundle makeEventBundle(final String name, final Map map) bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); } if (map.containsKey("quantity")) { - long val = (long) map.get("quantity"); - bundle.putLong(FirebaseAnalytics.Param.QUANTITY, val); + double val = (double) map.get("quantity"); + bundle.putDouble(FirebaseAnalytics.Param.QUANTITY, val); } if (map.containsKey("price")) { - long val = (long) map.get("price"); - bundle.putLong(FirebaseAnalytics.Param.PRICE, val); + double val = (double) map.get("price"); + bundle.putDouble(FirebaseAnalytics.Param.PRICE, val); } if (map.containsKey("value")) { - long val = (long) map.get("value"); - bundle.putLong(FirebaseAnalytics.Param.VALUE, val); + double val = (double) map.get("value"); + bundle.putDouble(FirebaseAnalytics.Param.VALUE, val); } if (map.containsKey("currency")) { String val = (String) map.get("currency"); @@ -196,7 +196,7 @@ private Bundle makeEventBundle(final String name, final Map map) } if (map.containsKey("shipping")) { double val = (double) map.get("shipping"); - bundle.putDouble(FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS, val); + bundle.putDouble(FirebaseAnalytics.Param.SHIPPING, val); } if (map.containsKey("group_id")) { String val = (String) map.get("group_id"); From 2b90c0b31774c94901ec6b5f68e672f9333a1e2f Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 11:32:15 +0000 Subject: [PATCH 137/275] import #109 --- .../firestack/storage/FirestackStorage.java | 169 ++++++++++++++++-- 1 file changed, 151 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index 112858e..e8e20b3 100644 --- a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -4,6 +4,9 @@ import android.os.Environment; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; import java.util.HashMap; @@ -17,6 +20,7 @@ import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -24,6 +28,8 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.storage.StorageException; +import com.google.firebase.storage.StreamDownloadTask; import com.google.firebase.storage.UploadTask; import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageMetadata; @@ -49,6 +55,18 @@ public class FirestackStorage extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; + private static final String STORAGE_UPLOAD_PROGRESS = "upload_progress"; + private static final String STORAGE_UPLOAD_PAUSED = "upload_paused"; + private static final String STORAGE_UPLOAD_RESUMED = "upload_resumed"; + + private static final String STORAGE_DOWNLOAD_PROGRESS = "download_progress"; + private static final String STORAGE_DOWNLOAD_PAUSED = "download_paused"; + private static final String STORAGE_DOWNLOAD_RESUMED = "download_resumed"; + private static final String STORAGE_DOWNLOAD_SUCCESS = "download_success"; + private static final String STORAGE_DOWNLOAD_FAILURE = "download_failure"; + + private ReactContext mReactContext; + public FirestackStorage(ReactApplicationContext reactContext) { super(reactContext); @@ -60,6 +78,115 @@ public String getName() { return TAG; } + + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + + @ReactMethod + public void downloadFile(final String urlStr, + final String fbPath, + final String localFile, + final Callback callback) { + Log.d(TAG, "downloadFile: " + urlStr + ", " + localFile); + if (!isExternalStorageWritable()) { + Log.w(TAG, "downloadFile failed: external storage not writable"); + WritableMap error = Arguments.createMap(); + final int errorCode = 1; + error.putDouble("code", errorCode); + error.putString("description", "downloadFile failed: external storage not writable"); + callback.invoke(error); + return; + } + FirebaseStorage storage = FirebaseStorage.getInstance(); + String storageBucket = storage.getApp().getOptions().getStorageBucket(); + String storageUrl = "gs://" + storageBucket; + Log.d(TAG, "Storage url " + storageUrl + fbPath); + + StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); + StorageReference fileRef = storageRef.child(fbPath); + + fileRef.getStream(new StreamDownloadTask.StreamProcessor() { + @Override + public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException { + int indexOfLastSlash = localFile.lastIndexOf("/"); + String pathMinusFileName = localFile.substring(0, indexOfLastSlash) + "/"; + String filename = localFile.substring(indexOfLastSlash + 1); + File fileWithJustPath = new File(pathMinusFileName); + if (!fileWithJustPath.mkdirs()) { + Log.e(TAG, "Directory not created"); + WritableMap error = Arguments.createMap(); + error.putString("message", "Directory not created"); + callback.invoke(error); + return; + } + File fileWithFullPath = new File(pathMinusFileName, filename); + FileOutputStream output = new FileOutputStream(fileWithFullPath); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, len); + } + output.close(); + } + }).addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + double percentComplete = taskSnapshot.getTotalByteCount() == 0 ? 0.0f : 100.0f * (taskSnapshot.getBytesTransferred()) / (taskSnapshot.getTotalByteCount()); + data.putDouble("progress", percentComplete); + Utils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PROGRESS, data); + } + }).addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + Utils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PAUSED, data); + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { + final WritableMap data = Arguments.createMap(); + StorageReference ref = taskSnapshot.getStorage(); + data.putString("fullPath", ref.getPath()); + data.putString("bucket", ref.getBucket()); + data.putString("name", ref.getName()); + ref.getMetadata().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(final StorageMetadata storageMetadata) { + data.putMap("metadata", getMetadataAsMap(storageMetadata)); + callback.invoke(null, data); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + @ReactMethod public void downloadUrl(final String javascriptStorageBucket, final String path, @@ -67,7 +194,7 @@ public void downloadUrl(final String javascriptStorageBucket, FirebaseStorage storage = FirebaseStorage.getInstance(); String storageBucket = storage.getApp().getOptions().getStorageBucket(); String storageUrl = "gs://" + storageBucket; - Log.d(TAG, "FirestackStorage url " + storageUrl + path); + Log.d(TAG, "Storage url " + storageUrl + path); final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); final StorageReference fileRef = storageRef.child(path); @@ -90,16 +217,7 @@ public void onSuccess(Uri uri) { public void onSuccess(final StorageMetadata storageMetadata) { Log.d(TAG, "getMetadata success " + storageMetadata); - WritableMap metadata = Arguments.createMap(); - metadata.putString("getBucket", storageMetadata.getBucket()); - metadata.putString("getName", storageMetadata.getName()); - metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); - metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); - metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); - metadata.putString("md5hash", storageMetadata.getMd5Hash()); - metadata.putString("encoding", storageMetadata.getContentEncoding()); - - res.putMap("metadata", metadata); + res.putMap("metadata", getMetadataAsMap(storageMetadata)); res.putString("name", storageMetadata.getName()); res.putString("url", storageMetadata.getDownloadUrl().toString()); callback.invoke(null, res); @@ -109,7 +227,8 @@ public void onSuccess(final StorageMetadata storageMetadata) { @Override public void onFailure(@NonNull Exception exception) { Log.e(TAG, "Failure in download " + exception); - callback.invoke(makeErrorPayload(1, exception)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, exception)); } }); @@ -129,6 +248,18 @@ public void onFailure(@NonNull Exception exception) { }); } + private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) { + WritableMap metadata = Arguments.createMap(); + metadata.putString("getBucket", storageMetadata.getBucket()); + metadata.putString("getName", storageMetadata.getName()); + metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); + metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); + metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); + metadata.putString("md5hash", storageMetadata.getMd5Hash()); + metadata.putString("encoding", storageMetadata.getContentEncoding()); + return metadata; + } + // STORAGE @ReactMethod public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { @@ -191,9 +322,9 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { if (progress >= 0) { WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_progress"); + data.putString("eventName", STORAGE_UPLOAD_PROGRESS); data.putDouble("progress", progress); - Utils.sendEvent(getReactApplicationContext(), "upload_progress", data); + Utils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PROGRESS, data); } } }) @@ -204,13 +335,14 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { StorageMetadata d = taskSnapshot.getMetadata(); String bucket = d.getBucket(); WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_paused"); + data.putString("eventName", STORAGE_UPLOAD_PAUSED); data.putString("ref", bucket); - Utils.sendEvent(getReactApplicationContext(), "upload_paused", data); + Utils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PAUSED, data); } }); } catch (Exception ex) { - callback.invoke(makeErrorPayload(2, ex)); + final int errorCode = 2; + callback.invoke(makeErrorPayload(errorCode, ex)); } } @@ -221,7 +353,8 @@ public void getRealPathFromURI(final String uri, final Callback callback) { callback.invoke(null, path); } catch (Exception ex) { ex.printStackTrace(); - callback.invoke(makeErrorPayload(1, ex)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, ex)); } } From e30ee8fff661a2b0af049b06fd3078b32ea9126c Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 09:38:19 -0500 Subject: [PATCH 138/275] Fix IndexOutOfBounds when downloadFile doesnt have a / --- .../java/io/fullstack/firestack/storage/FirestackStorage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index e8e20b3..cf5ca0a 100644 --- a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -111,8 +111,8 @@ public void downloadFile(final String urlStr, @Override public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException { int indexOfLastSlash = localFile.lastIndexOf("/"); - String pathMinusFileName = localFile.substring(0, indexOfLastSlash) + "/"; - String filename = localFile.substring(indexOfLastSlash + 1); + String pathMinusFileName = indexOfLastSlash>0 ? localFile.substring(0, indexOfLastSlash) + "/" : "/"; + String filename = indexOfLastSlash>0 ? localFile.substring(indexOfLastSlash+1) : localFile; File fileWithJustPath = new File(pathMinusFileName); if (!fileWithJustPath.mkdirs()) { Log.e(TAG, "Directory not created"); From d973929d109ef6d0a5851e67eece338db748eca8 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 09:38:42 -0500 Subject: [PATCH 139/275] Fix NPE when context isn't available --- android/src/main/java/io/fullstack/firestack/Utils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/io/fullstack/firestack/Utils.java b/android/src/main/java/io/fullstack/firestack/Utils.java index cd50c5a..c84a81d 100644 --- a/android/src/main/java/io/fullstack/firestack/Utils.java +++ b/android/src/main/java/io/fullstack/firestack/Utils.java @@ -38,7 +38,7 @@ public static void todoNote(final String tag, final String name, final Callback * send a JS event **/ public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) { - if (context.hasActiveCatalystInstance()) { + if (context != null && context.hasActiveCatalystInstance()) { context .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); From 5689f3f4a7f1fcd5162bb85fd93a682d042273a1 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 09:51:56 -0500 Subject: [PATCH 140/275] Fix broken window-or-global --- lib/utils/window-or-global.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/window-or-global.js b/lib/utils/window-or-global.js index 3228c06..f6273f0 100644 --- a/lib/utils/window-or-global.js +++ b/lib/utils/window-or-global.js @@ -2,4 +2,4 @@ // https://github.com/purposeindustries/window-or-global module.exports = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || - this \ No newline at end of file + this; From bee88718e8fa892424f85acdf0ae61f182683558 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:00:55 -0500 Subject: [PATCH 141/275] Fix duplicate module Firestack --- firestack.android.js | 3 +-- firestack.ios.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/firestack.android.js b/firestack.android.js index 15d7185..3ebc35e 100644 --- a/firestack.android.js +++ b/firestack.android.js @@ -1,7 +1,6 @@ /** - * @providesModule Firestack * @flow */ import Firestack from './lib/firestack' -export default Firestack \ No newline at end of file +export default Firestack diff --git a/firestack.ios.js b/firestack.ios.js index bc1e69b..3ebc35e 100644 --- a/firestack.ios.js +++ b/firestack.ios.js @@ -1,5 +1,4 @@ /** - * @providesModule Firestack * @flow */ import Firestack from './lib/firestack' From 31aebc64c15373378de93681bbbaf3dc508c5277 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:17:10 -0500 Subject: [PATCH 142/275] Add flow to storage.js --- lib/modules/storage.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/modules/storage.js b/lib/modules/storage.js index f4fc47a..0c7d53a 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -1,3 +1,4 @@ +/* @flow */ import { NativeModules, NativeEventEmitter } from 'react-native'; import { promisify, noop } from '../utils'; @@ -13,7 +14,7 @@ class StorageRef extends ReferenceBase { this.storage = storage; } - downloadUrl() { + downloadUrl(): Promise { const path = this.pathToString(); return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path); } @@ -24,7 +25,7 @@ class StorageRef extends ReferenceBase { * @param listener * @return {Promise} */ - download(downloadPath: string, listener: Function = noop) { + download(downloadPath: string, listener: Function = noop): Promise { const path = this.pathToString(); const listeners = [ this.storage._addListener('download_progress', listener), @@ -35,7 +36,7 @@ class StorageRef extends ReferenceBase { return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) .then((res) => { console.log('res --->', res); - listeners.forEach(this.storage._removeListener); + listeners.forEach(listener => listener.remove()); return res; }) .catch((downloadError) => { @@ -45,8 +46,11 @@ class StorageRef extends ReferenceBase { } } +type StorageOptionsType = { + storageBucket?: ?string, +}; export default class Storage extends Base { - constructor(firestack, options = {}) { + constructor(firestack: Object, options:StorageOptionsType={}) { super(firestack, options); if (this.options.storageBucket) { @@ -56,8 +60,8 @@ export default class Storage extends Base { this.refs = {}; } - ref(...path) { - const key = this._pathKey(path); + ref(...path: Array): StorageRef { + const key = this._pathKey(...path); if (!this.refs[key]) { const ref = new StorageRef(this, path); this.refs[key] = ref; @@ -73,7 +77,7 @@ export default class Storage extends Base { * @param listener * @return {Promise} */ - uploadFile(name: string, filePath: string, metadata: Object = {}, listener: Function = noop) { + uploadFile(name: string, filePath: string, metadata: Object = {}, listener: Function = noop): Promise { const _filePath = filePath.replace('file://', ''); const listeners = [ this._addListener('upload_paused', listener), @@ -83,29 +87,26 @@ export default class Storage extends Base { return promisify('uploadFile', FirestackStorage)(this.storageUrl, name, _filePath, metadata) .then((res) => { - listeners.forEach(this._removeListener); + listeners.forEach(listener => listener.remove()); return res; }); } - getRealPathFromURI(uri) { + getRealPathFromURI(uri: string): Promise { return promisify('getRealPathFromURI', FirestackStorage)(uri); } - _addListener(evt, cb) { - return FirestackStorageEvt.addListener(evt, cb); + _addListener(evt: string, cb: (evt: Object) => Object): {remove: () => void} { + let listener = FirestackStorageEvt.addListener(evt, cb); + return listener; } - _removeListener(evt) { - return FirestackStorageEvt.removeListener(evt); - } - - setStorageUrl(url) { + setStorageUrl(url: string): void { // return promisify('setStorageUrl', FirestackStorage)(url); this.storageUrl = `gs://${url}`; } - _pathKey(...path) { + _pathKey(...path: Array): string { return path.join('-'); } @@ -121,8 +122,8 @@ export default class Storage extends Base { FILETYPE_DIRECTORY: FirestackStorage.FILETYPE_DIRECTORY, }; - get namespace() { - return 'firestack:storage'; + get namespace(): string { + return 'firestack:storage' } } From 3b8efaf8e59d4fcdb1c2d4183427d78d698e1bd9 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:23:45 -0500 Subject: [PATCH 143/275] Better debugging logs for storage.js --- lib/modules/storage.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 0c7d53a..8b89cfa 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -16,7 +16,12 @@ class StorageRef extends ReferenceBase { downloadUrl(): Promise { const path = this.pathToString(); - return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path); + this.log.debug('downloadUrl(', path, ')'); + return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path) + .catch(err => { + this.log.error('Error downloading URL for ', path, '. Error: ', err); + throw err; + }); } /** @@ -27,6 +32,7 @@ class StorageRef extends ReferenceBase { */ download(downloadPath: string, listener: Function = noop): Promise { const path = this.pathToString(); + this.log.debug('download(', path, ') -> ', downloadPath); const listeners = [ this.storage._addListener('download_progress', listener), this.storage._addListener('download_paused', listener), @@ -35,13 +41,13 @@ class StorageRef extends ReferenceBase { return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) .then((res) => { - console.log('res --->', res); + this.log.debug('res --->', res); listeners.forEach(listener => listener.remove()); return res; }) - .catch((downloadError) => { - console.log('Got an error ->', downloadError); - return Promise.reject(downloadError); + .catch(err => { + this.log.error('Error downloading ', path, ' to ', downloadPath, '. Error: ', err); + throw err; }); } } @@ -79,6 +85,7 @@ export default class Storage extends Base { */ uploadFile(name: string, filePath: string, metadata: Object = {}, listener: Function = noop): Promise { const _filePath = filePath.replace('file://', ''); + this.log.debug('uploadFile(', _filepath, ') -> ', name); const listeners = [ this._addListener('upload_paused', listener), this._addListener('upload_resumed', listener), @@ -89,6 +96,10 @@ export default class Storage extends Base { .then((res) => { listeners.forEach(listener => listener.remove()); return res; + }) + .catch(err => { + this.log.error('Error uploading file ', name, ' to ', filepath, '. Error: ', err); + throw err; }); } From 8130c9999a0d80714749d691cde371ce1d2215aa Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:27:20 -0500 Subject: [PATCH 144/275] Fix flow definition for firestack.store() --- lib/firestack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/firestack.js b/lib/firestack.js index eae2312..70ab174 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -189,7 +189,7 @@ export default class Firestack extends Singleton { /** * Redux store **/ - get store(): Object { + get store(): ?Object { return this._store; } From 44067f650c3775301396d0505a16dc1f6a74fa7f Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:34:57 -0500 Subject: [PATCH 145/275] Fix flow definitions for base.js --- lib/modules/base.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/modules/base.js b/lib/modules/base.js index 23db072..7d3b14d 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -11,8 +11,9 @@ const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); const logs = {}; +type FirestackOptions = {}; export class Base extends EventEmitter { - constructor(firestack, options = {}) { + constructor(firestack: Object, options: FirestackOptions = {}) { super(); this.firestack = firestack; this.eventHandlers = {}; @@ -22,7 +23,7 @@ export class Base extends EventEmitter { } // Logger - get log() { + get log(): Log { if (!logs[this.namespace]) { const debug = this.firestack._debug; logs[this.namespace] = new Log(this.namespace, debug); @@ -38,7 +39,7 @@ export class Base extends EventEmitter { } // TODO unused - do we need this anymore? - _addToFirestackInstance(...methods) { + _addToFirestackInstance(...methods: Array) { methods.forEach(name => { this.firestack[name] = this[name].bind(this); }) @@ -47,15 +48,17 @@ export class Base extends EventEmitter { /** * app instance **/ - get app() { + get app(): Object { return this.firestack.app; } - whenReady(fn) { - return this.firestack.configurePromise.then(fn); + whenReady(promise: Promise<*>): Promise<*> { + return this.firestack.configurePromise.then((result) => { + return promise; + }); } - get namespace() { + get namespace(): string { return 'firestack:base'; } @@ -88,25 +91,21 @@ export class Base extends EventEmitter { } export class ReferenceBase extends Base { - constructor(firestack, path) { + constructor(firestack: Object, path: Array | string) { super(firestack); - this.path = Array.isArray(path) ? - path : - (typeof path == 'string' ? - [path] : []); + this.path = Array.isArray(path) ? path : (typeof path == 'string' ? [path] : []); // sanitize path, just in case - this.path = this.path - .filter(str => str !== ""); + this.path = this.path.filter(str => str !== ''); } - get key() { + get key(): string { const path = this.path; return path.length === 0 ? '/' : path[path.length - 1]; } - pathToString() { + pathToString(): string { let path = this.path; let pathStr = (path.length > 0 ? path.join('/') : '/'); if (pathStr[0] != '/') { From 7ce61bd3d5a3f4b5ec3ede9e83247b5486b166aa Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Tue, 22 Nov 2016 10:43:59 -0500 Subject: [PATCH 146/275] Remove error if the directory already exists --- .../io/fullstack/firestack/storage/FirestackStorage.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index cf5ca0a..a5f01b7 100644 --- a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -114,13 +114,7 @@ public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputSt String pathMinusFileName = indexOfLastSlash>0 ? localFile.substring(0, indexOfLastSlash) + "/" : "/"; String filename = indexOfLastSlash>0 ? localFile.substring(indexOfLastSlash+1) : localFile; File fileWithJustPath = new File(pathMinusFileName); - if (!fileWithJustPath.mkdirs()) { - Log.e(TAG, "Directory not created"); - WritableMap error = Arguments.createMap(); - error.putString("message", "Directory not created"); - callback.invoke(error); - return; - } + fileWithJustPath.mkdirs(); File fileWithFullPath = new File(pathMinusFileName, filename); FileOutputStream output = new FileOutputStream(fileWithFullPath); int bufferSize = 1024; From 16fd792d3e608b80cadb991610074bb84955667c Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Mon, 21 Nov 2016 17:04:44 -0500 Subject: [PATCH 147/275] Fix indentation for android Storage.downloadFile --- .../firestack/storage/FirestackStorage.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index a5f01b7..26e1c34 100644 --- a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -156,17 +156,17 @@ public void onSuccess(final StorageMetadata storageMetadata) { callback.invoke(null, data); } }) - .addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception exception) { - final int errorCode = 1; - WritableMap data = Arguments.createMap(); - StorageException storageException = StorageException.fromException(exception); - data.putString("description", storageException.getMessage()); - data.putInt("code", errorCode); - callback.invoke(makeErrorPayload(errorCode, exception)); - } - }); + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); } }).addOnFailureListener(new OnFailureListener() { @Override From 31c9ccf2fb293151c63bc4c4d7ca25b68afa0a7f Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 15:56:54 +0000 Subject: [PATCH 148/275] added android play services availability checks - will throw a js error if not available, still need to add the makePlayServicesAvailable functionality --- .../fullstack/firestack/FirestackModule.java | 39 ++++++++++++------- lib/firestack.js | 24 +++++++++++- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java index f8c6647..dfbd526 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -1,5 +1,6 @@ package io.fullstack.firestack; +import java.util.HashMap; import java.util.Map; import android.util.Log; @@ -17,7 +18,8 @@ import com.facebook.react.bridge.ReactContext; import com.google.android.gms.common.ConnectionResult; -//import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.GoogleApiAvailability; + import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.database.ServerValue; @@ -27,7 +29,7 @@ interface KeySetterFn { } @SuppressWarnings("WeakerAccess") -class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { +public class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { private static final String TAG = "Firestack"; private Context context; private ReactContext mReactContext; @@ -46,18 +48,20 @@ public String getName() { return TAG; } -// private static Boolean hasValidPlayServices() { -// final int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this.context); -// if (status != ConnectionResult.SUCCESS) { -// Log.e(TAG, GooglePlayServicesUtil.getErrorString(status)); -// Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, 1); -// dialog.show(); -// return false; -// } else { -// Log.i(TAG, GooglePlayServicesUtil.getErrorString(status)); -// return true; -// } -// } + private WritableMap getPlayServicesStatus() { + GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); + final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); + WritableMap result = Arguments.createMap(); + result.putInt("status", status); + if (status == ConnectionResult.SUCCESS) { + result.putBoolean("isAvailable", true); + } else { + result.putBoolean("isAvailable", false); + result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status)); + result.putString("error", gapi.getErrorString(status)); + } + return result; + } @ReactMethod public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) { @@ -198,4 +202,11 @@ public void onHostPause() { public void onHostDestroy() { } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("googleApiAvailability", getPlayServicesStatus()); + return constants; + } } diff --git a/lib/firestack.js b/lib/firestack.js index eae2312..127a6a3 100644 --- a/lib/firestack.js +++ b/lib/firestack.js @@ -22,6 +22,13 @@ const instances = { default: null }; const FirestackModule = NativeModules.Firestack; const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); +type GoogleApiAvailabilityType = { + status: number, + isAvailable: boolean, + isUserResolvableError?: boolean, + error?: string +}; + /** * @class Firestack */ @@ -31,10 +38,10 @@ export default class Firestack extends Singleton { * * @param options */ - constructor(options: Object) { + constructor(options: Object = {}) { const instance = super(options); - instance.options = options || {}; + instance.options = Object.assign({ errorOnMissingPlayServices: true }, options); instance._debug = instance.options.debug || false; Log.enable(instance._debug); @@ -53,6 +60,10 @@ export default class Firestack extends Singleton { instance.configurePromise = instance.configure(instance.options); instance._auth = new Auth(instance, instance.options); + + if (instance.options.errorOnMissingPlayServices && !this.googleApiAvailability.isAvailable) { + throw new Error(`Google Play Services is required to run this application but no valid installation was found (Code ${this.googleApiAvailability.status}).`); + } } _db: ?Object; @@ -179,6 +190,15 @@ export default class Firestack extends Singleton { return Object.keys(instances); } + /** + * Returns androids GoogleApiAvailability status and message if available. + * @returns {GoogleApiAvailabilityType|{isAvailable: boolean, status: number}} + */ + get googleApiAvailability(): GoogleApiAvailabilityType { + // if not available then return a fake object for ios - saves doing platform specific logic. + return FirestackModule.googleApiAvailability || { isAvailable: true, status: 0 }; + } + /** * Logger */ From acb5445a8fa5c963dc805035da557a89c194d2f6 Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 16:16:47 +0000 Subject: [PATCH 149/275] rename FirestackCloudMessaging to FirestackMessaging --- lib/modules/messaging.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index 5022cc2..ecdb6d7 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -2,8 +2,8 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; import { Base } from './base'; import { promisify } from '../utils'; -const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; -const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); +const FirestackMessaging = NativeModules.FirestackMessaging; +const FirestackMessagingEvt = new NativeEventEmitter(FirestackMessaging); /** * @class Messaging @@ -18,8 +18,8 @@ export default class Messaging extends Base { */ onMessage(callback) { this.log.info('Setting up onMessage callback'); - const sub = this._on('FirestackReceiveNotification', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); + const sub = this._on('FirestackReceiveNotification', callback, FirestackMessagingEvt); + return promisify(() => sub, FirestackMessaging)(sub); } // android & ios api dfgsdfs @@ -45,30 +45,30 @@ export default class Messaging extends Base { getToken() { this.log.info('getToken for cloudMessaging'); - return promisify('getToken', FirestackCloudMessaging)(); + return promisify('getToken', FirestackMessaging)(); } sendMessage(details: Object = {}, type: string = 'local') { const methodName = `send${type == 'local' ? 'Local' : 'Remote'}`; this.log.info('sendMessage', methodName, details); - return promisify(methodName, FirestackCloudMessaging)(details); + return promisify(methodName, FirestackMessaging)(details); } scheduleMessage(details: Object = {}, type: string = 'local') { const methodName = `schedule${type == 'local' ? 'Local' : 'Remote'}`; - return promisify(methodName, FirestackCloudMessaging)(details); + return promisify(methodName, FirestackMessaging)(details); } // OLD send(senderId, messageId, messageType, msg) { - return promisify('send', FirestackCloudMessaging)(senderId, messageId, messageType, msg); + return promisify('send', FirestackMessaging)(senderId, messageId, messageType, msg); } // listenForTokenRefresh(callback) { this.log.info('Setting up listenForTokenRefresh callback'); - const sub = this._on('FirestackRefreshToken', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); + const sub = this._on('FirestackRefreshToken', callback, FirestackMessagingEvt); + return promisify(() => sub, FirestackMessaging)(sub); } unlistenForTokenRefresh() { @@ -79,26 +79,26 @@ export default class Messaging extends Base { subscribeToTopic(topic) { this.log.info('subscribeToTopic ' + topic); const finalTopic = `/topics/${topic}`; - return promisify('subscribeToTopic', FirestackCloudMessaging)(finalTopic); + return promisify('subscribeToTopic', FirestackMessaging)(finalTopic); } unsubscribeFromTopic(topic) { this.log.info('unsubscribeFromTopic ' + topic); const finalTopic = `/topics/${topic}`; - return promisify('unsubscribeFromTopic', FirestackCloudMessaging)(finalTopic); + return promisify('unsubscribeFromTopic', FirestackMessaging)(finalTopic); } // New api onRemoteMessage(callback) { this.log.info('On remote message callback'); - const sub = this._on('messaging_remote_event_received', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); + const sub = this._on('messaging_remote_event_received', callback, FirestackMessagingEvt); + return promisify(() => sub, FirestackMessaging)(sub); } onLocalMessage(callback) { this.log.info('on local callback'); - const sub = this._on('messaging_local_event_received', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); + const sub = this._on('messaging_local_event_received', callback, FirestackMessagingEvt); + return promisify(() => sub, FirestackMessaging)(sub); } // old API @@ -124,8 +124,8 @@ export default class Messaging extends Base { listenForReceiveUpstreamSend(callback) { this.log.info('Setting up send callback'); - const sub = this._on('FirestackUpstreamSend', callback, FirestackCloudMessagingEvt); - return promisify(() => sub, FirestackCloudMessaging)(sub); + const sub = this._on('FirestackUpstreamSend', callback, FirestackMessagingEvt); + return promisify(() => sub, FirestackMessaging)(sub); } unlistenForReceiveUpstreamSend() { From 95e7fadf05f36f9599b34362e8352d7fdcd9fb9e Mon Sep 17 00:00:00 2001 From: salakar Date: Wed, 23 Nov 2016 16:19:08 +0000 Subject: [PATCH 150/275] added in support for ios - until renamed. --- lib/modules/messaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index ecdb6d7..65f2286 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -2,7 +2,7 @@ import { NativeModules, NativeEventEmitter } from 'react-native'; import { Base } from './base'; import { promisify } from '../utils'; -const FirestackMessaging = NativeModules.FirestackMessaging; +const FirestackMessaging = NativeModules.FirestackMessaging || NativeModules.FirestackCloudMessaging; const FirestackMessagingEvt = new NativeEventEmitter(FirestackMessaging); /** From 691f1b70ae5a690367314cd0765096ec45f30896 Mon Sep 17 00:00:00 2001 From: Matt Jeanes Date: Wed, 23 Nov 2016 17:36:47 -0500 Subject: [PATCH 151/275] Fix storage.uploadFile --- lib/modules/storage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 8b89cfa..5817759 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -85,7 +85,7 @@ export default class Storage extends Base { */ uploadFile(name: string, filePath: string, metadata: Object = {}, listener: Function = noop): Promise { const _filePath = filePath.replace('file://', ''); - this.log.debug('uploadFile(', _filepath, ') -> ', name); + this.log.debug('uploadFile(', _filePath, ') -> ', name); const listeners = [ this._addListener('upload_paused', listener), this._addListener('upload_resumed', listener), @@ -98,7 +98,7 @@ export default class Storage extends Base { return res; }) .catch(err => { - this.log.error('Error uploading file ', name, ' to ', filepath, '. Error: ', err); + this.log.error('Error uploading file ', name, ' to ', _filePath, '. Error: ', err); throw err; }); } From 84f74e97dea04d42c87f34bb8000fa289053382f Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 24 Nov 2016 20:08:38 +0000 Subject: [PATCH 152/275] added new utils: each, map (these chunk iterations and yield to event loop every chunk) and generatePushID (for ref().push() --- lib/utils/index.js | 155 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 10 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 31d07f5..d2026c0 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,3 +1,18 @@ +// modeled after base64 web-safe chars, but ordered by ASCII +const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; + +const DEFAULT_CHUNK_SIZE = 50; + +// internal promise handler +const _handler = (resolve, reject, err, resp) => { + // resolve / reject after events etc + setImmediate(() => { + if (err) return reject(err); + return resolve(resp); + }); +}; + + /** * Makes an objects keys it's values * @param object @@ -17,16 +32,6 @@ export function reverseKeyValues(object) { export function noop() { } - -// internal promise handler -const _handler = (resolve, reject, err, resp) => { - // resolve / reject after events etc - setImmediate(() => { - if (err) return reject(err); - return resolve(resp); - }); -}; - /** * Wraps a native module method to support promises. * @param fn @@ -41,3 +46,133 @@ export function promisify(fn, NativeModule) { }); }; } + + +/** + * Delays chunks based on sizes per event loop. + * @param collection + * @param chunkSize + * @param operation + * @param callback + * @private + */ +function _delayChunk(collection, chunkSize, operation, callback) { + const length = collection.length; + const iterations = Math.ceil(length / chunkSize); + + // noinspection ES6ConvertVarToLetConst + let thisIteration = 0; + + setImmediate(function next() { + const start = thisIteration * chunkSize; + const _end = start + chunkSize; + const end = _end >= length ? length : _end; + const result = operation(collection.slice(start, end), start, end); + + if (thisIteration++ > iterations) { + callback(null, result); + } else { + setImmediate(next); + } + }); +} + +/** + * Async each with optional chunk size limit + * @param array + * @param chunkSize + * @param iterator + * @param cb + */ +export function each(array, chunkSize, iterator, cb) { + if (typeof chunkSize === 'function') { + cb = iterator; + iterator = chunkSize; + chunkSize = DEFAULT_CHUNK_SIZE; + } + + _delayChunk(array, chunkSize, (slice, start) => { + for (let ii = 0, jj = slice.length; ii < jj; ii += 1) { + iterator(slice[ii], start + ii); + } + }, cb); +} + +/** + * Async map with optional chunk size limit + * @param array + * @param chunkSize + * @param iterator + * @param cb + * @returns {*} + */ +export function map(array, chunkSize, iterator, cb) { + if (typeof chunkSize === 'function') { + cb = iterator; + iterator = chunkSize; + chunkSize = DEFAULT_CHUNK_SIZE; + } + + const result = []; + _delayChunk(array, chunkSize, (slice, start) => { + for (let ii = 0, jj = slice.length; ii < jj; ii += 1) { + result.push(iterator(slice[ii], start + ii, array)); + } + return result; + }, () => cb(result)); +} + + +// timestamp of last push, used to prevent local collisions if you push twice in one ms. +let lastPushTime = 0; + +// we generate 72-bits of randomness which get turned into 12 characters and appended to the +// timestamp to prevent collisions with other clients. We store the last characters we +// generated because in the event of a collision, we'll use those same characters except +// "incremented" by one. +const lastRandChars = []; + +/** + * Generate a firebase id - for use with ref().push(val, cb) - e.g. -KXMr7k2tXUFQqiaZRY4' + * @param serverTimeOffset - pass in server time offset from native side + * @returns {string} + */ +export function generatePushID(serverTimeOffset = 0) { + const timeStampChars = new Array(8); + let now = new Date().getTime() + serverTimeOffset; + const duplicateTime = (now === lastPushTime); + + lastPushTime = now; + + for (let i = 7; i >= 0; i -= 1) { + timeStampChars[i] = PUSH_CHARS.charAt(now % 64); + now = Math.floor(now / 64); + } + + if (now !== 0) throw new Error('We should have converted the entire timestamp.'); + + let id = timeStampChars.join(''); + + if (!duplicateTime) { + for (let i = 0; i < 12; i += 1) { + lastRandChars[i] = Math.floor(Math.random() * 64); + } + } else { + // if the timestamp hasn't changed since last push, + // use the same random number, but increment it by 1. + let i; + for (i = 11; i >= 0 && lastRandChars[i] === 63; i -= 1) { + lastRandChars[i] = 0; + } + + lastRandChars[i] += 1; + } + + for (let i = 0; i < 12; i++) { + id += PUSH_CHARS.charAt(lastRandChars[i]); + } + + if (id.length !== 20) throw new Error('Length should be 20.'); + + return id; +} From 36cfff274f90b18b60dea77de45c45d46e018d3c Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 24 Nov 2016 20:10:00 +0000 Subject: [PATCH 153/275] added missing import --- .../firestack/database/FirestackDatabaseReference.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 0a93d54..030980c 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -11,6 +11,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; @@ -151,7 +152,7 @@ public void cleanup() { public void removeChildEventListener() { if (mEventListener != null) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + DatabaseReference ref = this.getDatabaseRef(); ref.removeEventListener(mEventListener); //this.notListeningTo(mPath, "child_added"); //this.notListeningTo(mPath, "child_changed"); @@ -162,7 +163,7 @@ public void removeChildEventListener() { } public void removeValueEventListener() { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + DatabaseReference ref = this.getDatabaseRef(); if (mValueListener != null) { ref.removeEventListener(mValueListener); //this.notListeningTo(mPath, "value"); @@ -202,12 +203,12 @@ private void handleDatabaseError(final String name, final String path, final Dat Utils.sendEvent(mReactContext, "database_error", evt); } - public com.google.firebase.database.DatabaseReference getDatabaseRef() { + public DatabaseReference getDatabaseRef() { return FirebaseDatabase.getInstance().getReference(mPath); } private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { - com.google.firebase.database.DatabaseReference ref = this.getDatabaseRef(); + DatabaseReference ref = this.getDatabaseRef(); List strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers); ListIterator it = strModifiers.listIterator(); From 1905a8a4281aab47fc464f336a01ea0051022f5c Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 24 Nov 2016 20:10:52 +0000 Subject: [PATCH 154/275] misc todo's --- lib/modules/messaging.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index 65f2286..ec43536 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -16,22 +16,17 @@ export default class Messaging extends Base { /* * WEB API */ + // TODO move to new event emitter logic onMessage(callback) { this.log.info('Setting up onMessage callback'); const sub = this._on('FirestackReceiveNotification', callback, FirestackMessagingEvt); return promisify(() => sub, FirestackMessaging)(sub); } - // android & ios api dfgsdfs - onMessageReceived(...args) { - return this.onMessage(...args); - } - - // there is no 'off' for this on api's but it's needed here for react - // so followed the general 'off' / 'on' naming convention + // TODO this is wrong - also there is no 'off' onMessage should return the unsubscribe function offMessage() { this.log.info('Unlistening from onMessage (offMessage)'); - this._off('FirestackRefreshToken'); + this._off('FirestackReceiveNotification'); } offMessageReceived(...args) { From 8c5458a9206d9e3e9e541df4640e1c72837d4593 Mon Sep 17 00:00:00 2001 From: salakar Date: Thu, 24 Nov 2016 20:20:54 +0000 Subject: [PATCH 155/275] annotate utils --- lib/utils/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index d2026c0..a26aed6 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -18,7 +18,7 @@ const _handler = (resolve, reject, err, resp) => { * @param object * @returns {{}} */ -export function reverseKeyValues(object) { +export function reverseKeyValues(object: Object): Object { const output = {}; for (const key in object) { output[object[key]] = key; @@ -29,7 +29,7 @@ export function reverseKeyValues(object) { /** * No operation func */ -export function noop() { +export function noop(): void { } /** @@ -37,7 +37,7 @@ export function noop() { * @param fn * @param NativeModule */ -export function promisify(fn, NativeModule) { +export function promisify(fn: Function, NativeModule: Object): Function { return (...args) => { return new Promise((resolve, reject) => { const _fn = typeof fn === 'function' ? fn : NativeModule[fn]; @@ -56,7 +56,7 @@ export function promisify(fn, NativeModule) { * @param callback * @private */ -function _delayChunk(collection, chunkSize, operation, callback) { +function _delayChunk(collection, chunkSize, operation, callback): void { const length = collection.length; const iterations = Math.ceil(length / chunkSize); @@ -84,7 +84,7 @@ function _delayChunk(collection, chunkSize, operation, callback) { * @param iterator * @param cb */ -export function each(array, chunkSize, iterator, cb) { +export function each(array: Array, chunkSize?: number, iterator: Function, cb: Function): void { if (typeof chunkSize === 'function') { cb = iterator; iterator = chunkSize; @@ -106,7 +106,7 @@ export function each(array, chunkSize, iterator, cb) { * @param cb * @returns {*} */ -export function map(array, chunkSize, iterator, cb) { +export function map(array: Array, chunkSize?: number, iterator: Function, cb: Function): void { if (typeof chunkSize === 'function') { cb = iterator; iterator = chunkSize; @@ -137,7 +137,7 @@ const lastRandChars = []; * @param serverTimeOffset - pass in server time offset from native side * @returns {string} */ -export function generatePushID(serverTimeOffset = 0) { +export function generatePushID(serverTimeOffset?: number = 0): string { const timeStampChars = new Array(8); let now = new Date().getTime() + serverTimeOffset; const duplicateTime = (now === lastPushTime); From ec9f48855f6580b2838b60db201e26f4fcb32a87 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 29 Nov 2016 09:35:14 +0000 Subject: [PATCH 156/275] Update Java database module to maintain reference for each path/modifier combination that is in use rather than recreate it each time --- .../firestack/database/FirestackDatabase.java | 124 +++++------- .../database/FirestackDatabaseReference.java | 178 +++++++----------- 2 files changed, 115 insertions(+), 187 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java index ced179f..a5a93a8 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -27,9 +27,11 @@ public class FirestackDatabase extends ReactContextBaseJavaModule { private static final String TAG = "FirestackDatabase"; private HashMap mDBListeners = new HashMap(); + private FirebaseDatabase mFirebaseDatabase; public FirestackDatabase(ReactApplicationContext reactContext) { super(reactContext); + mFirebaseDatabase = FirebaseDatabase.getInstance(); } @Override @@ -43,8 +45,7 @@ public void enablePersistence( final Boolean enable, final Callback callback) { try { - FirebaseDatabase.getInstance() - .setPersistenceEnabled(enable); + mFirebaseDatabase.setPersistenceEnabled(enable); } catch (Throwable t) { Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); } @@ -59,7 +60,7 @@ public void keepSynced( final String path, final Boolean enable, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); ref.keepSynced(enable); WritableMap res = Arguments.createMap(); @@ -74,15 +75,13 @@ public void set( final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - final FirestackDatabase self = this; + DatabaseReference ref = mFirebaseDatabase.getReference(path); Map m = Utils.recursivelyDeconstructReadableMap(props); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { - handleCallback("set", callback, error, ref); + handleCallback("set", callback, error); } }; @@ -93,14 +92,13 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { public void update(final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final FirestackDatabase self = this; + DatabaseReference ref = mFirebaseDatabase.getReference(path); Map m = Utils.recursivelyDeconstructReadableMap(props); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { - handleCallback("update", callback, error, ref); + handleCallback("update", callback, error); } }; @@ -110,12 +108,11 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { @ReactMethod public void remove(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - final FirestackDatabase self = this; + DatabaseReference ref = mFirebaseDatabase.getReference(path); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { - handleCallback("remove", callback, error, ref); + handleCallback("remove", callback, error); } }; @@ -128,7 +125,7 @@ public void push(final String path, final Callback callback) { Log.d(TAG, "Called push with " + path); - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); DatabaseReference newRef = ref.push(); final Uri url = Uri.parse(newRef.toString()); @@ -138,7 +135,6 @@ public void push(final String path, if (iterator.hasNextKey()) { Log.d(TAG, "Passed value to push"); // lame way to check if the `props` are empty - final FirestackDatabase self = this; Map m = Utils.recursivelyDeconstructReadableMap(props); DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @@ -175,20 +171,16 @@ public void on(final String path, final ReadableArray modifiersArray, final String name, final Callback callback) { - FirestackDatabaseReference ref = this.getDBHandle(path, modifiersString); - - WritableMap resp = Arguments.createMap(); + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString); if (name.equals("value")) { - ref.addValueEventListener(name, modifiersArray, modifiersString); + ref.addValueEventListener(); } else { - ref.addChildEventListener(name, modifiersArray, modifiersString); + ref.addChildEventListener(name); } - this.saveDBHandle(path, modifiersString, ref); + WritableMap resp = Arguments.createMap(); resp.putString("result", "success"); - Log.d(TAG, "Added listener " + name + " for " + ref + "with modifiers: "+ modifiersString); - resp.putString("handle", path); callback.invoke(null, resp); } @@ -199,9 +191,8 @@ public void onOnce(final String path, final ReadableArray modifiersArray, final String name, final Callback callback) { - Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - FirestackDatabaseReference ref = this.getDBHandle(path, modifiersString); - ref.addOnceValueEventListener(modifiersArray, modifiersString, callback); + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString); + ref.addOnceValueEventListener(callback); } /** @@ -227,82 +218,60 @@ public void off( // On Disconnect @ReactMethod public void onDisconnectSetObject(final String path, final ReadableMap props, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); Map m = Utils.recursivelyDeconstructReadableMap(props); OnDisconnect od = ref.onDisconnect(); od.setValue(m, new DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { - handleCallback("onDisconnectSetObject", callback, databaseError, databaseReference); + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectSetObject", callback, error); } }); } @ReactMethod public void onDisconnectSetString(final String path, final String value, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); OnDisconnect od = ref.onDisconnect(); od.setValue(value, new DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { - handleCallback("onDisconnectSetString", callback, databaseError, databaseReference); + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectSetString", callback, error); } }); } @ReactMethod public void onDisconnectRemove(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); OnDisconnect od = ref.onDisconnect(); od.removeValue(new DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { - handleCallback("onDisconnectRemove", callback, databaseError, databaseReference); + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectRemove", callback, error); } }); } @ReactMethod public void onDisconnectCancel(final String path, final Callback callback) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); + DatabaseReference ref = mFirebaseDatabase.getReference(path); OnDisconnect od = ref.onDisconnect(); od.cancel(new DatabaseReference.CompletionListener() { @Override - public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { - handleCallback("onDisconnectCancel", callback, databaseError, databaseReference); + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectCancel", callback, error); } }); } - // Private helpers - // private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) { - // WritableMap data = this.dataSnapshotToMap(name, dataSnapshot); - // WritableMap evt = Arguments.createMap(); - // evt.putString("eventName", name); - // evt.putMap("body", data); - // Utils.sendEvent(mReactContext, "database_event", evt); - // } - - // private void handleDatabaseError(final String name, final DatabaseError error) { - // WritableMap err = Arguments.createMap(); - // err.putInt("errorCode", error.getCode()); - // err.putString("errorDetails", error.getDetails()); - // err.putString("description", error.getMessage()); - - // WritableMap evt = Arguments.createMap(); - // evt.putString("eventName", name); - // evt.putMap("body", err); - // Utils.sendEvent(mReactContext, "database_error", evt); - // } - private void handleCallback( final String methodName, final Callback callback, - final DatabaseError databaseError, - final DatabaseReference databaseReference) { + final DatabaseError databaseError) { if (databaseError != null) { WritableMap err = Arguments.createMap(); err.putInt("errorCode", databaseError.getCode()); @@ -317,19 +286,19 @@ private void handleCallback( } } - private FirestackDatabaseReference getDBHandle(final String path, final String modifiersString) { + private FirestackDatabaseReference getDBHandle(final String path, + final ReadableArray modifiersArray, + final String modifiersString) { String key = this.getDBListenerKey(path, modifiersString); - if (!mDBListeners.containsKey(key)) { + FirestackDatabaseReference r = mDBListeners.get(key); + + if (r == null) { ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(key, new FirestackDatabaseReference(ctx, path)); + r = new FirestackDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString); + mDBListeners.put(key, r); } - return mDBListeners.get(key); - } - - private void saveDBHandle(final String path, String modifiersString, final FirestackDatabaseReference dbRef) { - String key = this.getDBListenerKey(path, modifiersString); - mDBListeners.put(key, dbRef); + return r; } private String getDBListenerKey(String path, String modifiersString) { @@ -338,20 +307,11 @@ private String getDBListenerKey(String path, String modifiersString) { private void removeDBHandle(final String path, String modifiersString) { String key = this.getDBListenerKey(path, modifiersString); - if (mDBListeners.containsKey(key)) { - FirestackDatabaseReference r = mDBListeners.get(key); + FirestackDatabaseReference r = mDBListeners.get(key); + + if (r != null) { r.cleanup(); mDBListeners.remove(key); } } - - private String keyPath(final String path, final String eventName) { - return path + "-" + eventName; - } - - // TODO: move to FirestackDatabaseReference? - private DatabaseReference getDatabaseReferenceAtPath(final String path) { - DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference(path); - return mDatabase; - } } diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 030980c..5ad37d9 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -1,9 +1,10 @@ package io.fullstack.firestack.database; +import java.util.HashSet; import java.util.List; import android.util.Log; -import java.util.HashMap; import java.util.ListIterator; +import java.util.Set; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Arguments; @@ -11,7 +12,6 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; -import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; @@ -22,89 +22,89 @@ import io.fullstack.firestack.Utils; public class FirestackDatabaseReference { - private static final String TAG = "FirestackDatabaseReference"; + private static final String TAG = "FirestackDBReference"; + private Query mQuery; private String mPath; - // private ReadableArray mModifiers; - private HashMap mListeners = new HashMap(); + private String mModifiersString; private ChildEventListener mEventListener; private ValueEventListener mValueListener; - private ValueEventListener mOnceValueListener; + private Set mOnceValueListeners = new HashSet<>(); private ReactContext mReactContext; - public FirestackDatabaseReference(final ReactContext context, final String path) { + public FirestackDatabaseReference(final ReactContext context, + final FirebaseDatabase firebaseDatabase, + final String path, + final ReadableArray modifiersArray, + final String modifiersString) { mReactContext = context; mPath = path; + mModifiersString = modifiersString; + mQuery = this.buildDatabaseQueryAtPathAndModifiers(firebaseDatabase, path, modifiersArray); } -// public void setModifiers(final ReadableArray modifiers) { -// mModifiers = modifiers; -// } - - public void addChildEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final FirestackDatabaseReference self = this; - + public void addChildEventListener(final String name) { if (mEventListener == null) { mEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_added", mPath, modifiersString, dataSnapshot); + handleDatabaseEvent("child_added", dataSnapshot); } @Override public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_changed", mPath, modifiersString, dataSnapshot); + handleDatabaseEvent("child_changed", dataSnapshot); } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("child_removed", mPath, modifiersString, dataSnapshot); + handleDatabaseEvent("child_removed", dataSnapshot); } @Override public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_moved", mPath, modifiersString, dataSnapshot); + handleDatabaseEvent("child_moved", dataSnapshot); } @Override public void onCancelled(DatabaseError error) { - self.handleDatabaseError(name, mPath, error); + handleDatabaseError(name, error); } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addChildEventListener(mEventListener); + mQuery.addChildEventListener(mEventListener); + Log.d(TAG, "Added ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); + } else { + Log.w(TAG, "Trying to add duplicate ChildEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); } } - public void addValueEventListener(final String name, final ReadableArray modifiersArray, final String modifiersString) { - final FirestackDatabaseReference self = this; - - mValueListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("value", mPath, modifiersString, dataSnapshot); - } - - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError("value", mPath, error); - } - }; + public void addValueEventListener() { + if (mValueListener == null) { + mValueListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + handleDatabaseEvent("value", dataSnapshot); + } - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addValueEventListener(mValueListener); - //this.setListeningTo(mPath, modifiersString, "value"); + @Override + public void onCancelled(DatabaseError error) { + handleDatabaseError("value", error); + } + }; + mQuery.addValueEventListener(mValueListener); + Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); + //this.setListeningTo(mPath, modifiersString, "value"); + } else { + Log.w(TAG, "trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); + } } - public void addOnceValueEventListener(final ReadableArray modifiersArray, - final String modifiersString, - final Callback callback) { - final FirestackDatabaseReference self = this; - - mOnceValueListener = new ValueEventListener() { + public void addOnceValueEventListener(final Callback callback) { + final ValueEventListener onceValueEventListener = new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = Utils.dataSnapshotToMap("value", mPath, modifiersString, dataSnapshot); + WritableMap data = Utils.dataSnapshotToMap("value", mPath, mModifiersString, dataSnapshot); + mOnceValueListeners.remove(this); callback.invoke(null, data); } @@ -114,82 +114,54 @@ public void onCancelled(DatabaseError error) { err.putInt("errorCode", error.getCode()); err.putString("errorDetails", error.getDetails()); err.putString("description", error.getMessage()); + mOnceValueListeners.remove(this); callback.invoke(err); } }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiersArray); - ref.addListenerForSingleValueEvent(mOnceValueListener); + //TODO ? Is it really necessary to track this type of event listener as they are automatically + //removed by Firebase when the event is fired. Very slim chance of memory leak. + mOnceValueListeners.add(onceValueEventListener); + mQuery.addListenerForSingleValueEvent(onceValueEventListener); + Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString); } - //public Boolean isListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // return mListeners.containsKey(key); - //} - - /** - * Note: these path/eventType listeners only get removed when javascript calls .off() and cleanup is run on the entire path - */ - //public void setListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // mListeners.put(key, true); - //} - - //public void notListeningTo(final String path, String modifiersString, final String evtName) { - // String key = this.pathListeningKey(path, modifiersString, evtName); - // mListeners.remove(key); - //} - - //private String pathListeningKey(final String path, String modifiersString, final String eventName) { - //return "listener/" + path + "/" + modifiersString + "/" + eventName; - //} - public void cleanup() { Log.d(TAG, "cleaning up database reference " + this); this.removeChildEventListener(); - this.removeValueEventListener(); + this.removeValueEventListeners(); } - public void removeChildEventListener() { + private void removeChildEventListener() { if (mEventListener != null) { - DatabaseReference ref = this.getDatabaseRef(); - ref.removeEventListener(mEventListener); - //this.notListeningTo(mPath, "child_added"); - //this.notListeningTo(mPath, "child_changed"); - //this.notListeningTo(mPath, "child_removed"); - //this.notListeningTo(mPath, "child_moved"); + mQuery.removeEventListener(mEventListener); mEventListener = null; } } - public void removeValueEventListener() { - DatabaseReference ref = this.getDatabaseRef(); + private void removeValueEventListeners() { if (mValueListener != null) { - ref.removeEventListener(mValueListener); - //this.notListeningTo(mPath, "value"); + mQuery.removeEventListener(mValueListener); mValueListener = null; } - if (mOnceValueListener != null) { - ref.removeEventListener(mOnceValueListener); - mOnceValueListener = null; + for (ValueEventListener onceValueListener : mOnceValueListeners) { + mQuery.removeEventListener(onceValueListener); } + mOnceValueListeners = new HashSet<>(); } - private void handleDatabaseEvent(final String name, final String path, final String modifiersString, final DataSnapshot dataSnapshot) { - //if (!FirestackDatabaseReference.this.isListeningTo(path, modifiersString, name)) { - //return; - //} - WritableMap data = Utils.dataSnapshotToMap(name, path, modifiersString, dataSnapshot); + private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) { + WritableMap data = Utils.dataSnapshotToMap(name, mPath, mModifiersString, dataSnapshot); WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); - evt.putString("path", path); - evt.putString("modifiersString", modifiersString); + evt.putString("path", mPath); + evt.putString("modifiersString", mModifiersString); evt.putMap("body", data); Utils.sendEvent(mReactContext, "database_event", evt); } - private void handleDatabaseError(final String name, final String path, final DatabaseError error) { + private void handleDatabaseError(final String name, final DatabaseError error) { WritableMap err = Arguments.createMap(); err.putInt("errorCode", error.getCode()); err.putString("errorDetails", error.getDetails()); @@ -197,22 +169,18 @@ private void handleDatabaseError(final String name, final String path, final Dat WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); - evt.putString("path", path); + evt.putString("path", mPath); evt.putMap("body", err); Utils.sendEvent(mReactContext, "database_error", evt); } - public DatabaseReference getDatabaseRef() { - return FirebaseDatabase.getInstance().getReference(mPath); - } - - private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { - DatabaseReference ref = this.getDatabaseRef(); - + private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase firebaseDatabase, + final String path, + final ReadableArray modifiers) { + Query query = firebaseDatabase.getReference(path); List strModifiers = Utils.recursivelyDeconstructReadableArray(modifiers); ListIterator it = strModifiers.listIterator(); - Query query = ref.orderByKey(); while(it.hasNext()) { String str = (String) it.next(); @@ -221,15 +189,15 @@ private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) String methStr = strArr[0]; if (methStr.equalsIgnoreCase("orderByKey")) { - query = ref.orderByKey(); + query = query.orderByKey(); } else if (methStr.equalsIgnoreCase("orderByValue")) { - query = ref.orderByValue(); + query = query.orderByValue(); } else if (methStr.equalsIgnoreCase("orderByPriority")) { - query = ref.orderByPriority(); + query = query.orderByPriority(); } else if (methStr.contains("orderByChild")) { String key = strArr[1]; Log.d(TAG, "orderByChild: " + key); - query = ref.orderByChild(key); + query = query.orderByChild(key); } else if (methStr.contains("limitToLast")) { String key = strArr[1]; int limit = Integer.parseInt(key); From 5a95fb313fb52cc3db646fff7e5797b9ef93975d Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 29 Nov 2016 09:48:32 +0000 Subject: [PATCH 157/275] Remove unnecessary storage of once event listeners --- .../database/FirestackDatabaseReference.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 5ad37d9..778a186 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -1,10 +1,8 @@ package io.fullstack.firestack.database; -import java.util.HashSet; import java.util.List; import android.util.Log; import java.util.ListIterator; -import java.util.Set; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Arguments; @@ -29,7 +27,6 @@ public class FirestackDatabaseReference { private String mModifiersString; private ChildEventListener mEventListener; private ValueEventListener mValueListener; - private Set mOnceValueListeners = new HashSet<>(); private ReactContext mReactContext; public FirestackDatabaseReference(final ReactContext context, @@ -104,7 +101,6 @@ public void addOnceValueEventListener(final Callback callback) { @Override public void onDataChange(DataSnapshot dataSnapshot) { WritableMap data = Utils.dataSnapshotToMap("value", mPath, mModifiersString, dataSnapshot); - mOnceValueListeners.remove(this); callback.invoke(null, data); } @@ -114,14 +110,9 @@ public void onCancelled(DatabaseError error) { err.putInt("errorCode", error.getCode()); err.putString("errorDetails", error.getDetails()); err.putString("description", error.getMessage()); - mOnceValueListeners.remove(this); callback.invoke(err); } }; - - //TODO ? Is it really necessary to track this type of event listener as they are automatically - //removed by Firebase when the event is fired. Very slim chance of memory leak. - mOnceValueListeners.add(onceValueEventListener); mQuery.addListenerForSingleValueEvent(onceValueEventListener); Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString); } @@ -129,7 +120,7 @@ public void onCancelled(DatabaseError error) { public void cleanup() { Log.d(TAG, "cleaning up database reference " + this); this.removeChildEventListener(); - this.removeValueEventListeners(); + this.removeValueEventListener(); } private void removeChildEventListener() { @@ -139,15 +130,11 @@ private void removeChildEventListener() { } } - private void removeValueEventListeners() { + private void removeValueEventListener() { if (mValueListener != null) { mQuery.removeEventListener(mValueListener); mValueListener = null; } - for (ValueEventListener onceValueListener : mOnceValueListeners) { - mQuery.removeEventListener(onceValueListener); - } - mOnceValueListeners = new HashSet<>(); } private void handleDatabaseEvent(final String name, final DataSnapshot dataSnapshot) { From be896ba55ce6350d5831ae90a7c162fbdac9ed53 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 2 Dec 2016 10:21:29 +0000 Subject: [PATCH 158/275] Fix NPE when trying to clear a database value, i.e. set it to null --- lib/modules/database/reference.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index c4b77a1..35e5a9c 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -155,6 +155,9 @@ export default class Reference extends ReferenceBase { // Sanitize value // As Firebase cannot store date objects. _serializeValue(obj: Object = {}) { + if (!obj) { + return obj; + } return Object.keys(obj).reduce((sum, key) => { let val = obj[key]; if (val instanceof Date) { From e7713256370fb75a67d13c35ffb678eb24cf2a79 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Fri, 2 Dec 2016 17:18:22 +0000 Subject: [PATCH 159/275] Fix issue with number and boolean data types being used with startAt/endAt/equalTo in Java database module --- .../database/FirestackDatabaseReference.java | 78 ++++++++++++++++--- 1 file changed, 66 insertions(+), 12 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 778a186..53f312e 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -198,26 +198,80 @@ private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase fireba } else if (methStr.contains("equalTo")) { String value = strArr[1]; String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.equalTo(value); - } else { - query = query.equalTo(value, key); + try { + double doubleValue = Double.parseDouble(value); + if (key == null) { + query = query.equalTo(doubleValue); + } else { + query = query.equalTo(doubleValue, key); + } + } catch (NumberFormatException ex) { + if ("true".equals(value) || "false".equals(value)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (key == null) { + query = query.equalTo(booleanValue); + } else { + query = query.equalTo(booleanValue, key); + } + } else { + if (key == null) { + query = query.equalTo(value); + } else { + query = query.equalTo(value, key); + } + } } } else if (methStr.contains("endAt")) { String value = strArr[1]; String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.endAt(value); - } else { - query = query.endAt(value, key); + try { + double doubleValue = Double.parseDouble(value); + if (key == null) { + query = query.endAt(doubleValue); + } else { + query = query.endAt(doubleValue, key); + } + } catch (NumberFormatException ex) { + if ("true".equals(value) || "false".equals(value)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (key == null) { + query = query.endAt(booleanValue); + } else { + query = query.endAt(booleanValue, key); + } + } else { + if (key == null) { + query = query.endAt(value); + } else { + query = query.endAt(value, key); + } + } } } else if (methStr.contains("startAt")) { String value = strArr[1]; String key = strArr.length >= 3 ? strArr[2] : null; - if (key == null) { - query = query.startAt(value); - } else { - query = query.startAt(value, key); + try { + double doubleValue = Double.parseDouble(value); + if (key == null) { + query = query.startAt(doubleValue); + } else { + query = query.startAt(doubleValue, key); + } + } catch (NumberFormatException ex) { + if ("true".equals(value) || "false".equals(value)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (key == null) { + query = query.startAt(booleanValue); + } else { + query = query.startAt(booleanValue, key); + } + } else { + if (key == null) { + query = query.startAt(value); + } else { + query = query.startAt(value, key); + } + } } } } From f6aaee51a5b1b0673b0d8c9a486595f3078e89a8 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sat, 3 Dec 2016 09:57:34 +0000 Subject: [PATCH 160/275] Fix issue with number and boolean data types being used with startAt/endAt/equalTo in iOS database module; Store singleton query reference --- ios/Firestack/FirestackDatabase.m | 648 ++++++++++++++---------------- 1 file changed, 293 insertions(+), 355 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 459e0d2..0ffd855 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -11,8 +11,11 @@ #import "FirestackEvents.h" @interface FirestackDBReference : NSObject +@property RCTEventEmitter *emitter; +@property FIRDatabaseQuery *query; @property NSString *path; -@property NSDictionary *listeners; +@property NSString *modifiersString; +@property NSMutableDictionary *listeners; @property FIRDatabaseHandle childAddedHandler; @property FIRDatabaseHandle childModifiedHandler; @property FIRDatabaseHandle childRemovedHandler; @@ -22,38 +25,191 @@ @interface FirestackDBReference : NSObject @implementation FirestackDBReference -- (id) initWithPath:(NSString *) path +- (id) initWithPathAndModifiers:(RCTEventEmitter *) emitter + database:(FIRDatabase *) database + path:(NSString *) path + modifiers:(NSArray *) modifiers + modifiersString:(NSString *) modifiersString { self = [super init]; if (self) { - _path = path; - _listeners = [[NSDictionary alloc] init]; + _emitter = emitter; + _path = path; + _modifiersString = modifiersString; + _query = [self buildQueryAtPathWithModifiers:database path:path modifiers:modifiers]; + _listeners = [[NSMutableDictionary alloc] init]; } return self; } -- (FIRDatabaseReference *) getRef +- (void) addEventHandler:(NSString *) eventName { - FIRDatabaseReference *rootRef = [[FIRDatabase database] reference]; - return [rootRef child:self.path]; + if (![self isListeningTo:eventName]) { + id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) { + NSDictionary *props = [self snapshotToDict:snapshot]; + [self sendJSEvent:DATABASE_DATA_EVENT + title:eventName + props: @{ + @"eventName": eventName, + @"path": _path, + @"modifiersString": _modifiersString, + @"snapshot": props + }]; + }; + + id errorBlock = ^(NSError * _Nonnull error) { + NSLog(@"Error onDBEvent: %@", [error debugDescription]); + [self getAndSendDatabaseError:error withPath: _path]; + }; + + int eventType = [self eventTypeFromName:eventName]; + FIRDatabaseHandle handle = [_query observeEventType:eventType + withBlock:withBlock + withCancelBlock:errorBlock]; + [self setEventHandler:handle forName:eventName]; + } else { + NSLog(@"Warning Trying to add duplicate listener for type: %@ with modifiers: %@ for path: %@", eventName, _modifiersString, _path); + } +} + +- (void) addSingleEventHandler:(RCTResponseSenderBlock) callback +{ + [_query observeSingleEventOfType:FIRDataEventTypeValue + withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { + NSDictionary *props = [self snapshotToDict:snapshot]; + callback(@[[NSNull null], @{ + @"eventName": @"value", + @"path": _path, + @"modifiersString": _modifiersString, + @"snapshot": props + }]); + } + withCancelBlock:^(NSError * _Nonnull error) { + NSLog(@"Error onDBEventOnce: %@", [error debugDescription]); + callback(@[@{ + @"error": @"onceError", + @"msg": [error debugDescription] + }]); + }]; +} + +- (void) removeEventHandler:(NSString *) name +{ + int eventType = [self eventTypeFromName:name]; + switch (eventType) { + case FIRDataEventTypeValue: + if (self.childValueHandler != nil) { + [_query removeObserverWithHandle:self.childValueHandler]; + self.childValueHandler = nil; + } + break; + case FIRDataEventTypeChildAdded: + if (self.childAddedHandler != nil) { + [_query removeObserverWithHandle:self.childAddedHandler]; + self.childAddedHandler = nil; + } + break; + case FIRDataEventTypeChildChanged: + if (self.childModifiedHandler != nil) { + [_query removeObserverWithHandle:self.childModifiedHandler]; + self.childModifiedHandler = nil; + } + break; + case FIRDataEventTypeChildRemoved: + if (self.childRemovedHandler != nil) { + [_query removeObserverWithHandle:self.childRemovedHandler]; + self.childRemovedHandler = nil; + } + break; + case FIRDataEventTypeChildMoved: + if (self.childMovedHandler != nil) { + [_query removeObserverWithHandle:self.childMovedHandler]; + self.childMovedHandler = nil; + } + break; + default: + break; + } + [self unsetListeningOn:name]; } -- (FIRDatabaseQuery *) getQueryWithModifiers:(NSArray *) modifiers +- (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot { - FIRDatabaseReference *rootRef = [self getRef]; - FIRDatabaseQuery *query = [rootRef queryOrderedByKey]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [dict setValue:snapshot.key forKey:@"key"]; + NSDictionary *val = snapshot.value; + [dict setObject:val forKey:@"value"]; + + // Snapshot ordering + NSMutableArray *childKeys = [NSMutableArray array]; + if (snapshot.childrenCount > 0) { + // Since JS does not respect object ordering of keys + // we keep a list of the keys and their ordering + // in the snapshot event + NSEnumerator *children = [snapshot children]; + FIRDataSnapshot *child; + while(child = [children nextObject]) { + [childKeys addObject:child.key]; + } + } + + [dict setObject:childKeys forKey:@"childKeys"]; + [dict setValue:@(snapshot.hasChildren) forKey:@"hasChildren"]; + [dict setValue:@(snapshot.exists) forKey:@"exists"]; + [dict setValue:@(snapshot.childrenCount) forKey:@"childrenCount"]; + [dict setValue:snapshot.priority forKey:@"priority"]; + + return dict; +} + +- (NSDictionary *) getAndSendDatabaseError:(NSError *) error + withPath:(NSString *) path +{ + NSDictionary *evt = @{ + @"eventName": DATABASE_ERROR_EVENT, + @"path": path, + @"msg": [error debugDescription] + }; + [self sendJSEvent:DATABASE_ERROR_EVENT title:DATABASE_ERROR_EVENT props: evt]; + + return evt; +} + +- (void) sendJSEvent:(NSString *)type + title:(NSString *)title + props:(NSDictionary *)props +{ + @try { + [_emitter sendEventWithName:type + body:@{ + @"eventName": title, + @"body": props + }]; + } + @catch (NSException *err) { + NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); + NSLog(@"Tried to send: %@ with %@", title, props); + } +} + + +- (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database + path:(NSString*) path + modifiers:(NSArray *) modifiers +{ + FIRDatabaseQuery *query = [[database reference] child:path]; for (NSString *str in modifiers) { if ([str isEqualToString:@"orderByKey"]) { - query = [rootRef queryOrderedByKey]; + query = [query queryOrderedByKey]; } else if ([str isEqualToString:@"orderByPriority"]) { - query = [rootRef queryOrderedByPriority]; + query = [query queryOrderedByPriority]; } else if ([str isEqualToString:@"orderByValue"]) { - query = [rootRef queryOrderedByValue]; + query = [query queryOrderedByValue]; } else if ([str containsString:@"orderByChild"]) { NSArray *args = [str componentsSeparatedByString:@":"]; NSString *key = args[1]; - query = [rootRef queryOrderedByChild:key]; + query = [query queryOrderedByChild:key]; } else if ([str containsString:@"limitToLast"]) { NSArray *args = [str componentsSeparatedByString:@":"]; NSString *key = args[1]; @@ -67,43 +223,35 @@ - (FIRDatabaseQuery *) getQueryWithModifiers:(NSArray *) modifiers } else if ([str containsString:@"equalTo"]) { NSArray *args = [str componentsSeparatedByString:@":"]; int size = (int)[args count];; - + id value = [self getIdValue:args[1]]; if (size > 2) { - NSString *value = args[1]; - NSString *key = args[2]; - - query = [query queryEqualToValue:value + NSString *key = args[2]; + query = [query queryEqualToValue:value childKey:key]; } else { - NSString *value = args[1]; - query = [query queryEqualToValue:value]; + query = [query queryEqualToValue:value]; } } else if ([str containsString:@"endAt"]) { NSArray *args = [str componentsSeparatedByString:@":"]; int size = (int)[args count];; - + id value = [self getIdValue:args[1]]; if (size > 2) { - NSString *value = args[1]; - NSString *key = args[2]; - - query = [query queryEndingAtValue:value + NSString *key = args[2]; + query = [query queryEndingAtValue:value childKey:key]; } else { - NSString *value = args[1]; - query = [query queryEndingAtValue:value]; + query = [query queryEndingAtValue:value]; } } else if ([str containsString:@"startAt"]) { NSArray *args = [str componentsSeparatedByString:@":"]; + id value = [self getIdValue:args[1]]; int size = (int)[args count];; if (size > 2) { - NSString *value = args[1]; - NSString *key = args[2]; - - query = [query queryStartingAtValue:value + NSString *key = args[2]; + query = [query queryStartingAtValue:value childKey:key]; } else { - NSString *value = args[1]; - query = [query queryStartingAtValue:value]; + query = [query queryStartingAtValue:value]; } } } @@ -111,6 +259,17 @@ - (FIRDatabaseQuery *) getQueryWithModifiers:(NSArray *) modifiers return query; } +- (id) getIdValue:(NSString *) value +{ + if (value.integerValue != nil) { + return [NSNumber numberWithInteger:value.integerValue]; + } else if ([value isEqualToString:@"true"] || [value isEqualToString:@"false"]) { + return [NSNumber numberWithBool:value.boolValue]; + } else { + return value; + } +} + - (void) setEventHandler:(FIRDatabaseHandle) handle forName:(NSString *) name { @@ -137,52 +296,20 @@ - (void) setEventHandler:(FIRDatabaseHandle) handle [self setListeningOn:name withHandle:handle]; } -- (void) removeEventHandler:(NSString *) name -{ - FIRDatabaseReference *ref = [self getRef]; - int eventType = [self eventTypeFromName:name]; - - switch (eventType) { - case FIRDataEventTypeValue: - [ref removeObserverWithHandle:self.childValueHandler]; - break; - case FIRDataEventTypeChildAdded: - [ref removeObserverWithHandle:self.childAddedHandler]; - break; - case FIRDataEventTypeChildChanged: - [ref removeObserverWithHandle:self.childModifiedHandler]; - break; - case FIRDataEventTypeChildRemoved: - [ref removeObserverWithHandle:self.childRemovedHandler]; - break; - case FIRDataEventTypeChildMoved: - [ref removeObserverWithHandle:self.childMovedHandler]; - break; - default: - break; - } - [self unsetListeningOn:name]; -} - - (void) setListeningOn:(NSString *) name withHandle:(FIRDatabaseHandle) handle { - NSMutableDictionary *listeners = [_listeners mutableCopy]; - [listeners setValue:@(handle) forKey:name]; - _listeners = listeners; + [_listeners setValue:@(handle) forKey:name]; } - (void) unsetListeningOn:(NSString *) name { - NSMutableDictionary *listeners = [_listeners mutableCopy]; - [listeners removeObjectForKey:name]; - _listeners = listeners; + [_listeners removeObjectForKey:name]; } - (BOOL) isListeningTo:(NSString *) name { - id listener = [_listeners valueForKey:name]; - return listener != nil; + return [_listeners valueForKey:name] != nil; } - (BOOL) hasListeners @@ -237,47 +364,49 @@ @implementation FirestackDatabase RCT_EXPORT_MODULE(FirestackDatabase); +- (id) init +{ + self = [super init]; + if (self != nil) { + _database = [FIRDatabase database]; + _dbReferences = [[NSMutableDictionary alloc] init]; + } + return self; +} + RCT_EXPORT_METHOD(enablePersistence:(BOOL) enable callback:(RCTResponseSenderBlock) callback) { - BOOL isEnabled = [FIRDatabase database].persistenceEnabled; + BOOL isEnabled = _database.persistenceEnabled; if ( isEnabled != enable) { - [FIRDatabase database].persistenceEnabled = enable; + _database.persistenceEnabled = enable; } callback(@[[NSNull null], @{ - @"result": @"success" - }]); + @"result": @"success" + }]); } RCT_EXPORT_METHOD(keepSynced:(NSString *) path withEnable:(BOOL) enable callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; - [ref keepSynced:enable]; - callback(@[[NSNull null], @{ - @"result": @"success", - @"path": path - }]); + FIRDatabaseReference *ref = [[_database reference] child:path]; + [ref keepSynced:enable]; + callback(@[[NSNull null], @{ + @"status": @"success", + @"path": path + }]); } RCT_EXPORT_METHOD(set:(NSString *) path value:(NSDictionary *)value callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref setValue:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"set" callback:callback databaseError:error]; }]; } @@ -285,35 +414,19 @@ @implementation FirestackDatabase value:(NSDictionary *)value callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref updateChildValues:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"update" callback:callback databaseError:error]; }]; } RCT_EXPORT_METHOD(remove:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref removeValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"remove" callback:callback databaseError:error]; }]; } @@ -321,28 +434,33 @@ @implementation FirestackDatabase props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *pathRef = [self getRefAtPath:path]; - FIRDatabaseReference *ref = [pathRef childByAutoId]; + FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *newRef = [ref childByAutoId]; NSURL *url = [NSURL URLWithString:ref.URL]; NSString *newPath = [url path]; if ([props count] > 0) { - [ref setValue:props withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { + [newRef setValue:props withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { if (error != nil) { // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; + NSDictionary *evt = @{ + @"errorCode": [NSNumber numberWithInt:[error code]], + @"errorDetails": [error debugDescription], + @"description": [error description] + }; + callback(@[evt]); } else { callback(@[[NSNull null], @{ - @"result": @"success", + @"status": @"success", @"ref": newPath }]); } }]; } else { callback(@[[NSNull null], @{ - @"result": @"success", + @"status": @"success", @"ref": newPath }]); } @@ -356,48 +474,13 @@ @implementation FirestackDatabase name:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; - FIRDatabaseQuery *query = [r getQueryWithModifiers:modifiers]; - - if (![r isListeningTo:eventName]) { - id withBlock = ^(FIRDataSnapshot * _Nonnull snapshot) { - NSDictionary *props = - [self snapshotToDict:snapshot]; - [self - sendJSEvent:DATABASE_DATA_EVENT - title:eventName - props: @{ - @"eventName": eventName, - @"path": path, - @"modifiersString": modifiersString, - @"snapshot": props - }]; - }; - - id errorBlock = ^(NSError * _Nonnull error) { - NSLog(@"Error onDBEvent: %@", [error debugDescription]); - [self getAndSendDatabaseError:error withPath: path]; - }; - - int eventType = [r eventTypeFromName:eventName]; - FIRDatabaseHandle handle = [query observeEventType:eventType - withBlock:withBlock - withCancelBlock:errorBlock]; - [r setEventHandler:handle - forName:eventName]; - - // [self saveDBHandle:path dbRef:r]; - - callback(@[[NSNull null], @{ - @"result": @"success", - @"handle": @(handle) - }]); - } else { - callback(@[@{ - @"result": @"exists", - @"msg": @"Listener already exists" + FirestackDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString]; + [ref addEventHandler:eventName]; + + callback(@[[NSNull null], @{ + @"status": @"success", + @"handle": path }]); - } } RCT_EXPORT_METHOD(onOnce:(NSString *) path @@ -406,27 +489,8 @@ @implementation FirestackDatabase name:(NSString *) name callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; - int eventType = [r eventTypeFromName:name]; - FIRDatabaseQuery *ref = [r getQueryWithModifiers:modifiers]; - - [ref observeSingleEventOfType:eventType - withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { - NSDictionary *props = [self snapshotToDict:snapshot]; - callback(@[[NSNull null], @{ - @"eventName": name, - @"path": path, - @"modifiersString": modifiersString, - @"snapshot": props - }]); - } - withCancelBlock:^(NSError * _Nonnull error) { - NSLog(@"Error onDBEventOnce: %@", [error debugDescription]); - callback(@[@{ - @"error": @"onceError", - @"msg": [error debugDescription] - }]); - }]; + FirestackDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString]; + [ref addSingleEventHandler:callback]; } RCT_EXPORT_METHOD(off:(NSString *)path @@ -434,24 +498,26 @@ @implementation FirestackDatabase eventName:(NSString *) eventName callback:(RCTResponseSenderBlock) callback) { - FirestackDBReference *r = [self getDBHandle:path withModifiers:modifiersString]; - if (eventName == nil || [eventName isEqualToString:@""]) { - [r cleanup]; - [self removeDBHandle:path withModifiersString:modifiersString]; - } else { - [r removeEventHandler:eventName]; - if (![r hasListeners]) { - [self removeDBHandle:path withModifiersString:modifiersString]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + FirestackDBReference *ref = [_dbReferences objectForKey:key]; + + if (ref != nil) { + if (eventName == nil || [eventName isEqualToString:@""]) { + [ref cleanup]; + [_dbReferences removeObjectForKey:key]; + } else { + [ref removeEventHandler:eventName]; + if (![ref hasListeners]) { + [_dbReferences removeObjectForKey:key]; + } } } - - // [self saveDBHandle:path dbRef:r]; - + callback(@[[NSNull null], @{ @"result": @"success", - @"path": path, + @"handle": path, @"modifiersString": modifiersString, - @"remainingListeners": [r listenerKeys], + @"remainingListeners": [ref listenerKeys], }]); } @@ -460,19 +526,10 @@ @implementation FirestackDatabase props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; - + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref onDisconnectSetValue:props withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"onDisconnectSetObject" callback:callback databaseError:error]; }]; } @@ -480,35 +537,19 @@ @implementation FirestackDatabase val:(NSString *) val callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref onDisconnectSetValue:val withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"onDisconnectSetString" callback:callback databaseError:error]; }]; } RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref onDisconnectRemoveValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"onDisconnectRemove" callback:callback databaseError:error]; }]; } @@ -517,45 +558,47 @@ @implementation FirestackDatabase RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [self getRefAtPath:path]; + FIRDatabaseReference *ref = [[_database reference] child:path]; [ref cancelDisconnectOperationsWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { - if (error != nil) { - // Error handling - NSDictionary *evt = [self getAndSendDatabaseError:error withPath: path]; - callback(@[evt]); - } else { - callback(@[[NSNull null], @{ - @"result": @"success" - }]); - } + [self handleCallback:@"onDisconnectCancel" callback:callback databaseError:error]; }]; } - - - -// Helpers -- (FIRDatabaseReference *) getRef +- (void) handleCallback:(NSString *) methodName + callback:(RCTResponseSenderBlock) callback + databaseError:(NSError *) databaseError { - if (self.ref == nil) { - FIRDatabaseReference *rootRef = [[FIRDatabase database] reference]; - self.ref = rootRef; + if (databaseError != nil) { + NSDictionary *evt = @{ + @"errorCode": [NSNumber numberWithInt:[databaseError code]], + @"errorDetails": [databaseError debugDescription], + @"description": [databaseError description] + }; + callback(@[evt]); + } else { + callback(@[[NSNull null], @{ + @"status": @"success", + @"method": methodName + }]); } - return self.ref; } -- (FIRDatabaseReference *) getRefAtPath:(NSString *) path -{ - return [[FIRDatabase database] referenceWithPath:path]; -} - -// Handles -- (NSDictionary *) storedDBHandles +- (FirestackDBReference *) getDBHandle:(NSString *) path + modifiers:modifiers + modifiersString:modifiersString { - if (__DBHandles == nil) { - __DBHandles = [[NSDictionary alloc] init]; + NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; + FirestackDBReference *ref = [_dbReferences objectForKey:key]; + + if (ref == nil) { + ref = [[FirestackDBReference alloc] initWithPathAndModifiers:self + database:_database + path:path + modifiers:modifiers + modifiersString:modifiersString]; + [_dbReferences setObject:ref forKey:key]; } - return __DBHandles; + return ref; } - (NSString *) getDBListenerKey:(NSString *) path @@ -564,114 +607,9 @@ - (NSString *) getDBListenerKey:(NSString *) path return [NSString stringWithFormat:@"%@ | %@", path, modifiersString, nil]; } -- (FirestackDBReference *) getDBHandle:(NSString *) path - withModifiers:modifiersString -{ - NSDictionary *stored = [self storedDBHandles]; - NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; - FirestackDBReference *r = [stored objectForKey:key]; - - if (r == nil) { - r = [[FirestackDBReference alloc] initWithPath:path]; - [self saveDBHandle:path withModifiersString:modifiersString dbRef:r]; - } - return r; -} - -- (void) saveDBHandle:(NSString *) path - withModifiersString:(NSString*)modifiersString - dbRef:(FirestackDBReference *) dbRef -{ - NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; - NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; - if ([stored objectForKey:key]) { - FirestackDBReference *r = [stored objectForKey:key]; - [r cleanup]; - } - - [stored setObject:dbRef forKey:key]; - self._DBHandles = stored; -} - -- (void) removeDBHandle:(NSString *) path - withModifiersString:(NSString*)modifiersString -{ - NSMutableDictionary *stored = [[self storedDBHandles] mutableCopy]; - NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; - - FirestackDBReference *r = [stored objectForKey:key]; - if (r != nil) { - [r cleanup]; - } - [stored removeObjectForKey:path]; - self._DBHandles = [stored copy]; -} - -- (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot -{ - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - [dict setValue:snapshot.key forKey:@"key"]; - NSDictionary *val = snapshot.value; - [dict setObject:val forKey:@"value"]; - - // Snapshot ordering - NSMutableArray *childKeys = [NSMutableArray array]; - if (snapshot.childrenCount > 0) { - // Since JS does not respect object ordering of keys - // we keep a list of the keys and their ordering - // in the snapshot event - NSEnumerator *children = [snapshot children]; - FIRDataSnapshot *child; - while(child = [children nextObject]) { - [childKeys addObject:child.key]; - } - } - - [dict setObject:childKeys forKey:@"childKeys"]; - [dict setValue:@(snapshot.hasChildren) forKey:@"hasChildren"]; - [dict setValue:@(snapshot.exists) forKey:@"exists"]; - [dict setValue:@(snapshot.childrenCount) forKey:@"childrenCount"]; - [dict setValue:snapshot.priority forKey:@"priority"]; - - return dict; -} - -- (NSDictionary *) getAndSendDatabaseError:(NSError *) error - withPath:(NSString *) path -{ - NSDictionary *evt = @{ - @"eventName": DATABASE_ERROR_EVENT, - @"path": path, - @"msg": [error debugDescription] - }; - [self - sendJSEvent:DATABASE_ERROR_EVENT - title:DATABASE_ERROR_EVENT - props: evt]; - - return evt; -} - // Not sure how to get away from this... yet - (NSArray *)supportedEvents { return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT]; } -- (void) sendJSEvent:(NSString *)type - title:(NSString *)title - props:(NSDictionary *)props -{ - @try { - [self sendEventWithName:type - body:@{ - @"eventName": title, - @"body": props - }]; - } - @catch (NSException *err) { - NSLog(@"An error occurred in sendJSEvent: %@", [err debugDescription]); - NSLog(@"Tried to send: %@ with %@", title, props); - } -} - @end \ No newline at end of file From 6df6a8119c2315264a1cc5c6e2b6769d33520595 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sat, 3 Dec 2016 09:57:58 +0000 Subject: [PATCH 161/275] Bring Java responses more in line with iOS --- .../firestack/database/FirestackDatabase.java | 38 +++++++++++-------- .../database/FirestackDatabaseReference.java | 2 +- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java index a5a93a8..842afdf 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -159,7 +159,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { } else { Log.d(TAG, "No value passed to push: " + newPath); WritableMap res = Arguments.createMap(); - res.putString("result", "success"); + res.putString("status", "success"); res.putString("ref", newPath); callback.invoke(null, res); } @@ -180,7 +180,7 @@ public void on(final String path, } WritableMap resp = Arguments.createMap(); - resp.putString("result", "success"); + resp.putString("status", "success"); resp.putString("handle", path); callback.invoke(null, resp); } @@ -205,13 +205,31 @@ public void onOnce(final String path, public void off( final String path, final String modifiersString, - @Deprecated final String name, + final String name, final Callback callback) { - this.removeDBHandle(path, modifiersString); + + String key = this.getDBListenerKey(path, modifiersString); + FirestackDatabaseReference r = mDBListeners.get(key); + + if (r != null) { + if (name == null || "".equals(name)) { + r.cleanup(); + mDBListeners.remove(key); + } else { + //TODO: Remove individual listeners as per iOS code + //1) Remove event handler + //2) If no more listeners, remove from listeners map + r.cleanup(); + mDBListeners.remove(key); + } + } + Log.d(TAG, "Removed listener " + path + "/" + modifiersString); WritableMap resp = Arguments.createMap(); resp.putString("handle", path); - resp.putString("result", "success"); + resp.putString("status", "success"); + resp.putString("modifiersString", modifiersString); + //TODO: Remaining listeners callback.invoke(null, resp); } @@ -304,14 +322,4 @@ private FirestackDatabaseReference getDBHandle(final String path, private String getDBListenerKey(String path, String modifiersString) { return path + " | " + modifiersString; } - - private void removeDBHandle(final String path, String modifiersString) { - String key = this.getDBListenerKey(path, modifiersString); - FirestackDatabaseReference r = mDBListeners.get(key); - - if (r != null) { - r.cleanup(); - mDBListeners.remove(key); - } - } } diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 53f312e..2bfa56e 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -92,7 +92,7 @@ public void onCancelled(DatabaseError error) { Log.d(TAG, "Added ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); //this.setListeningTo(mPath, modifiersString, "value"); } else { - Log.w(TAG, "trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); + Log.w(TAG, "Trying to add duplicate ValueEventListener for path: " + mPath + " with modifiers: "+ mModifiersString); } } From 39c7ec7b73483cc79999299df192383e58f6419c Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sat, 3 Dec 2016 10:05:25 +0000 Subject: [PATCH 162/275] Update missing iOS header changes --- ios/Firestack/FirestackDatabase.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.h b/ios/Firestack/FirestackDatabase.h index bab8105..88b1c68 100644 --- a/ios/Firestack/FirestackDatabase.h +++ b/ios/Firestack/FirestackDatabase.h @@ -17,9 +17,9 @@ } -@property (nonatomic) NSDictionary *_DBHandles; -@property (nonatomic, weak) FIRDatabaseReference *ref; +@property NSMutableDictionary *dbReferences; +@property FIRDatabase *database; @end -#endif \ No newline at end of file +#endif From 09b3fe1a98cd37001c95e4ed6a6e781732c27867 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Sat, 3 Dec 2016 14:26:22 +0000 Subject: [PATCH 163/275] Fix hanging android app issue This is due to an unnecessary check causing events to get lost - therefore giving the appearance of apps hanging. --- android/src/main/java/io/fullstack/firestack/Utils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/Utils.java b/android/src/main/java/io/fullstack/firestack/Utils.java index c84a81d..24950a8 100644 --- a/android/src/main/java/io/fullstack/firestack/Utils.java +++ b/android/src/main/java/io/fullstack/firestack/Utils.java @@ -38,12 +38,12 @@ public static void todoNote(final String tag, final String name, final Callback * send a JS event **/ public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) { - if (context != null && context.hasActiveCatalystInstance()) { + if (context != null) { context .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(eventName, params); } else { - Log.d(TAG, "Waiting for CatalystInstance before sending event"); + Log.d(TAG, "Missing context - cannot send event!"); } } From 6caac60fb5a7dbe0788515120e6ac02bdee1d4f5 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 4 Dec 2016 09:13:05 +0000 Subject: [PATCH 164/275] Stop storing Firebase database reference in iOS module --- ios/Firestack/FirestackDatabase.h | 1 - ios/Firestack/FirestackDatabase.m | 35 +++++++++++++++---------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.h b/ios/Firestack/FirestackDatabase.h index 88b1c68..7659f4d 100644 --- a/ios/Firestack/FirestackDatabase.h +++ b/ios/Firestack/FirestackDatabase.h @@ -18,7 +18,6 @@ } @property NSMutableDictionary *dbReferences; -@property FIRDatabase *database; @end diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 0ffd855..24dc64a 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -368,7 +368,6 @@ - (id) init { self = [super init]; if (self != nil) { - _database = [FIRDatabase database]; _dbReferences = [[NSMutableDictionary alloc] init]; } return self; @@ -378,10 +377,7 @@ - (id) init callback:(RCTResponseSenderBlock) callback) { - BOOL isEnabled = _database.persistenceEnabled; - if ( isEnabled != enable) { - _database.persistenceEnabled = enable; - } + [FIRDatabase database].persistenceEnabled = enable; callback(@[[NSNull null], @{ @"result": @"success" }]); @@ -391,7 +387,7 @@ - (id) init withEnable:(BOOL) enable callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref keepSynced:enable]; callback(@[[NSNull null], @{ @"status": @"success", @@ -403,8 +399,7 @@ - (id) init value:(NSDictionary *)value callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; - + FIRDatabaseReference *ref = [self getPathRef:path]; [ref setValue:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"set" callback:callback databaseError:error]; }]; @@ -414,8 +409,7 @@ - (id) init value:(NSDictionary *)value callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; - + FIRDatabaseReference *ref = [self getPathRef:path]; [ref updateChildValues:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"update" callback:callback databaseError:error]; }]; @@ -424,7 +418,7 @@ - (id) init RCT_EXPORT_METHOD(remove:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref removeValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"remove" callback:callback databaseError:error]; }]; @@ -434,7 +428,7 @@ - (id) init props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; FIRDatabaseReference *newRef = [ref childByAutoId]; NSURL *url = [NSURL URLWithString:ref.URL]; @@ -526,7 +520,7 @@ - (id) init props:(NSDictionary *) props callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref onDisconnectSetValue:props withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"onDisconnectSetObject" callback:callback databaseError:error]; @@ -537,7 +531,7 @@ - (id) init val:(NSString *) val callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref onDisconnectSetValue:val withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"onDisconnectSetString" callback:callback databaseError:error]; @@ -547,7 +541,7 @@ - (id) init RCT_EXPORT_METHOD(onDisconnectRemove:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref onDisconnectRemoveValueWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"onDisconnectRemove" callback:callback databaseError:error]; }]; @@ -558,12 +552,17 @@ - (id) init RCT_EXPORT_METHOD(onDisconnectCancel:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRDatabaseReference *ref = [[_database reference] child:path]; + FIRDatabaseReference *ref = [self getPathRef:path]; [ref cancelDisconnectOperationsWithCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"onDisconnectCancel" callback:callback databaseError:error]; }]; } +- (FIRDatabaseReference *) getPathRef:(NSString *) path +{ + return [[[FIRDatabase database] reference] child:path]; +} + - (void) handleCallback:(NSString *) methodName callback:(RCTResponseSenderBlock) callback databaseError:(NSError *) databaseError @@ -592,7 +591,7 @@ - (FirestackDBReference *) getDBHandle:(NSString *) path if (ref == nil) { ref = [[FirestackDBReference alloc] initWithPathAndModifiers:self - database:_database + database:[FIRDatabase database] path:path modifiers:modifiers modifiersString:modifiersString]; @@ -612,4 +611,4 @@ - (NSString *) getDBListenerKey:(NSString *) path return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT]; } -@end \ No newline at end of file +@end From 6f44df2aed0a4b4a802c62fb53eb188c2ebaf655 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 4 Dec 2016 09:39:13 +0000 Subject: [PATCH 165/275] Fix incorrect push reference returned --- ios/Firestack/FirestackDatabase.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 24dc64a..9b7e1a8 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -431,7 +431,7 @@ - (id) init FIRDatabaseReference *ref = [self getPathRef:path]; FIRDatabaseReference *newRef = [ref childByAutoId]; - NSURL *url = [NSURL URLWithString:ref.URL]; + NSURL *url = [NSURL URLWithString:newRef.URL]; NSString *newPath = [url path]; if ([props count] > 0) { From 08eab356c5ac7893c5b0dee842cb5172c7bb3423 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Sun, 4 Dec 2016 11:45:33 +0000 Subject: [PATCH 166/275] Simplify storage modules to use the bucket specified when initialising Firebase --- .../firestack/storage/FirestackStorage.java | 33 ++++++----------- ios/Firestack/FirestackStorage.m | 35 ++++--------------- lib/modules/storage.js | 16 ++------- 3 files changed, 20 insertions(+), 64 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java index 26e1c34..19b23d7 100644 --- a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -85,11 +85,9 @@ public boolean isExternalStorageWritable() { } @ReactMethod - public void downloadFile(final String urlStr, - final String fbPath, + public void downloadFile(final String remotePath, final String localFile, final Callback callback) { - Log.d(TAG, "downloadFile: " + urlStr + ", " + localFile); if (!isExternalStorageWritable()) { Log.w(TAG, "downloadFile failed: external storage not writable"); WritableMap error = Arguments.createMap(); @@ -99,13 +97,9 @@ public void downloadFile(final String urlStr, callback.invoke(error); return; } - FirebaseStorage storage = FirebaseStorage.getInstance(); - String storageBucket = storage.getApp().getOptions().getStorageBucket(); - String storageUrl = "gs://" + storageBucket; - Log.d(TAG, "Storage url " + storageUrl + fbPath); + Log.d(TAG, "downloadFile from remote path: " + remotePath); - StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); - StorageReference fileRef = storageRef.child(fbPath); + StorageReference fileRef = FirebaseStorage.getInstance().getReference(remotePath); fileRef.getStream(new StreamDownloadTask.StreamProcessor() { @Override @@ -182,15 +176,10 @@ public void onFailure(@NonNull Exception exception) { } @ReactMethod - public void downloadUrl(final String javascriptStorageBucket, - final String path, + public void downloadUrl(final String remotePath, final Callback callback) { - FirebaseStorage storage = FirebaseStorage.getInstance(); - String storageBucket = storage.getApp().getOptions().getStorageBucket(); - String storageUrl = "gs://" + storageBucket; - Log.d(TAG, "Storage url " + storageUrl + path); - final StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); - final StorageReference fileRef = storageRef.child(path); + Log.d(TAG, "Download url for remote path: " + remotePath); + final StorageReference fileRef = FirebaseStorage.getInstance().getReference(remotePath); Task downloadTask = fileRef.getDownloadUrl(); downloadTask @@ -200,7 +189,7 @@ public void onSuccess(Uri uri) { final WritableMap res = Arguments.createMap(); res.putString("status", "success"); - res.putString("bucket", storageRef.getBucket()); + res.putString("bucket", FirebaseStorage.getInstance().getApp().getOptions().getStorageBucket()); res.putString("fullPath", uri.toString()); res.putString("path", uri.getPath()); res.putString("url", uri.toString()); @@ -256,12 +245,10 @@ private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) { // STORAGE @ReactMethod - public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { - FirebaseStorage storage = FirebaseStorage.getInstance(); - StorageReference storageRef = storage.getReferenceFromUrl(urlStr); - StorageReference fileRef = storageRef.child(name); + public void uploadFile(final String remotePath, final String filepath, final ReadableMap metadata, final Callback callback) { + StorageReference fileRef = FirebaseStorage.getInstance().getReference(remotePath); - Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); + Log.i(TAG, "Upload file: " + filepath + " to " + remotePath); try { Uri file; diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index d88a19a..cfcf2fa 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -21,17 +21,15 @@ - (dispatch_queue_t)methodQueue return dispatch_queue_create("io.fullstack.firestack.storage", DISPATCH_QUEUE_SERIAL); } -RCT_EXPORT_METHOD(downloadUrl: (NSString *) storageUrl - path:(NSString *) path +RCT_EXPORT_METHOD(downloadUrl: (NSString *) remotePath callback:(RCTResponseSenderBlock) callback) { - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; - FIRStorageReference *fileRef = [storageRef child:path]; + FIRStorageReference *fileRef = [[FIRStorage storage] referenceWithPath:remotePath]; [fileRef downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) { if (error != nil) { NSDictionary *evt = @{ @"status": @"error", - @"path": path, + @"path": remotePath, @"msg": [error debugDescription] }; callback(@[evt]); @@ -46,21 +44,12 @@ - (dispatch_queue_t)methodQueue }]; } -RCT_EXPORT_METHOD(uploadFile: (NSString *) urlStr - name: (NSString *) name +RCT_EXPORT_METHOD(uploadFile: (NSString *) remotePath path:(NSString *)path metadata:(NSDictionary *)metadata callback:(RCTResponseSenderBlock) callback) { - if (urlStr == nil) { - NSError *err = [[NSError alloc] init]; - [err setValue:@"Storage configuration error" forKey:@"name"]; - [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; - return callback(@[err]); - } - - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; - FIRStorageReference *uploadRef = [storageRef child:name]; + FIRStorageReference *uploadRef = [[FIRStorage storage] referenceWithPath:remotePath]; FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata]; if ([path hasPrefix:@"assets-library://"]) { @@ -164,21 +153,11 @@ - (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask }}]; } -RCT_EXPORT_METHOD(downloadFile: (NSString *) urlStr - path:(NSString *) path +RCT_EXPORT_METHOD(downloadFile: (NSString *) remotePath localFile:(NSString *) file callback:(RCTResponseSenderBlock) callback) { - if (urlStr == nil) { - NSError *err = [[NSError alloc] init]; - [err setValue:@"Storage configuration error" forKey:@"name"]; - [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; - return callback(@[err]); - } - - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; - FIRStorageReference *fileRef = [storageRef child:path]; - + FIRStorageReference *fileRef = [[FIRStorage storage] referenceWithPath:remotePath]; NSURL *localFile = [NSURL fileURLWithPath:file]; FIRStorageDownloadTask *downloadTask = [fileRef writeToFile:localFile]; diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 5817759..b22447a 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -17,7 +17,7 @@ class StorageRef extends ReferenceBase { downloadUrl(): Promise { const path = this.pathToString(); this.log.debug('downloadUrl(', path, ')'); - return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path) + return promisify('downloadUrl', FirestackStorage)(path) .catch(err => { this.log.error('Error downloading URL for ', path, '. Error: ', err); throw err; @@ -39,7 +39,7 @@ class StorageRef extends ReferenceBase { this.storage._addListener('download_resumed', listener), ]; - return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) + return promisify('downloadFile', FirestackStorage)(path, downloadPath) .then((res) => { this.log.debug('res --->', res); listeners.forEach(listener => listener.remove()); @@ -58,11 +58,6 @@ type StorageOptionsType = { export default class Storage extends Base { constructor(firestack: Object, options:StorageOptionsType={}) { super(firestack, options); - - if (this.options.storageBucket) { - this.setStorageUrl(this.options.storageBucket); - } - this.refs = {}; } @@ -92,7 +87,7 @@ export default class Storage extends Base { this._addListener('upload_progress', listener), ]; - return promisify('uploadFile', FirestackStorage)(this.storageUrl, name, _filePath, metadata) + return promisify('uploadFile', FirestackStorage)(name, _filePath, metadata) .then((res) => { listeners.forEach(listener => listener.remove()); return res; @@ -112,11 +107,6 @@ export default class Storage extends Base { return listener; } - setStorageUrl(url: string): void { - // return promisify('setStorageUrl', FirestackStorage)(url); - this.storageUrl = `gs://${url}`; - } - _pathKey(...path: Array): string { return path.join('-'); } From 810282f0f6d45bcea25927213a330ed7f30cdf36 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 6 Dec 2016 16:56:48 +0000 Subject: [PATCH 167/275] Simplify JS database to store array of modifiers rather than separate individual modifiers; Fixes issue with startAt and endAt being used together --- lib/modules/database/index.js | 40 ++++---------- lib/modules/database/query.js | 85 ++++++++-------------------- lib/modules/database/reference.js | 92 ++++++++++++------------------- 3 files changed, 68 insertions(+), 149 deletions(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index 3e386b8..bd5b63c 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -13,18 +13,6 @@ import { Base } from './../base'; import Reference from './reference.js'; import Snapshot from './snapshot.js'; -/** - * TODO ? why is it here and in reference? make it a util - * @param modifiers - * @returns {*} - */ -function getModifiersString(modifiers) { - if (!modifiers || !Array.isArray(modifiers)) { - return ''; - } - return modifiers.join('|'); -} - /** * @class Database */ @@ -100,25 +88,23 @@ export default class Database extends Base { this.log.debug('handleDatabaseError ->', evt); } - on(referenceKey: string, path: string, modifiers: Array, evt: string, cb: () => void) { + on(referenceKey: string, path: string, modifiersString: string, modifiers: Array, evt: string, cb: () => void) { this.log.debug('adding on listener', referenceKey, path, modifiers, evt); const key = this._pathKey(path); - const modifiersString = getModifiersString(modifiers); - const modifier = modifiersString; if (!this.dbSubscriptions[key]) { this.dbSubscriptions[key] = {}; } - if (!this.dbSubscriptions[key][modifier]) { - this.dbSubscriptions[key][modifier] = {}; + if (!this.dbSubscriptions[key][modifiersString]) { + this.dbSubscriptions[key][modifiersString] = {}; } - if (!this.dbSubscriptions[key][modifier][evt]) { - this.dbSubscriptions[key][modifier][evt] = []; + if (!this.dbSubscriptions[key][modifiersString][evt]) { + this.dbSubscriptions[key][modifiersString][evt] = []; } - this.dbSubscriptions[key][modifier][evt].push(cb); + this.dbSubscriptions[key][modifiersString][evt].push(cb); if (!this.successListener) { this.successListener = FirestackDatabaseEvt @@ -139,10 +125,8 @@ export default class Database extends Base { }); } - off(referenceKey: string, path: string, modifiers: Array, eventName: string, origCB?: () => void) { + off(referenceKey: string, path: string, modifiersString: string, modifiers: Array, eventName: string, origCB?: () => void) { const pathKey = this._pathKey(path); - const modifiersString = getModifiersString(modifiers); - const modifier = modifiersString; this.log.debug('off() : ', referenceKey, pathKey, modifiersString, eventName); // Remove subscription if (this.dbSubscriptions[pathKey]) { @@ -153,14 +137,14 @@ export default class Database extends Base { } // TODO clean me - no need for this many conditionals - if (this.dbSubscriptions[pathKey][modifier]) { - if (this.dbSubscriptions[pathKey][modifier][eventName]) { + if (this.dbSubscriptions[pathKey][modifiersString]) { + if (this.dbSubscriptions[pathKey][modifiersString][eventName]) { if (origCB) { // remove only the given callback - this.dbSubscriptions[pathKey][modifier][eventName].splice(this.dbSubscriptions[pathKey][modifier][eventName].indexOf(origCB), 1); + this.dbSubscriptions[pathKey][modifiersString][eventName].splice(this.dbSubscriptions[pathKey][modifiersString][eventName].indexOf(origCB), 1); } else { // remove all callbacks for this path:modifier:eventType - delete this.dbSubscriptions[pathKey][modifier][eventName]; + delete this.dbSubscriptions[pathKey][modifiersString][eventName]; } } else { this.log.warn('off() called, but not currently listening at that location (bad eventName)', pathKey, modifiersString, eventName); @@ -190,7 +174,7 @@ export default class Database extends Base { const subscriptions = [this.successListener, this.errorListener]; const modifierMap = this.dbSubscriptions[path]; - if (modifierMap && modifierMap[modifier] && modifierMap[modifier][eventName] && modifierMap[modifier][eventName].length > 0) { + if (modifierMap && modifierMap[modifiersString] && modifierMap[modifiersString][eventName] && modifierMap[modifiersString][eventName].length > 0) { return Promise.resolve(subscriptions); } diff --git a/lib/modules/database/query.js b/lib/modules/database/query.js index ea8fc43..65604e5 100644 --- a/lib/modules/database/query.js +++ b/lib/modules/database/query.js @@ -15,87 +15,46 @@ let uid = 1000000; export default class Query extends ReferenceBase { static ref: Reference; - static orderBy: Array; - static limit: Array; - static filters: Object;// { [key: string]: Array }; + static modifiers: Array; ref: Reference; - constructor(ref: Reference, path: Array, existingModifiers?: { [key: string]: string }) { + constructor(ref: Reference, path: Array, existingModifiers?: Array) { super(ref.db, path); this.log.debug('creating Query ', path, existingModifiers); this.uid = uid++; // uuid.v4(); this.ref = ref; - this.orderBy = undefined; - this.limit = undefined; - this.filters = {}; - - // parse exsitingModifiers - if (existingModifiers) { - this.import(existingModifiers); - } + this.modifiers = existingModifiers ? [...existingModifiers] : []; } - // noinspection ReservedWordAsName - export(): { [key: string]: string } { - const argsSeparator = ':'; - const ret = {}; - if (this.orderBy) { - ret.orderBy = this.orderBy.join(argsSeparator); - } - if (this.limit) { - ret.limit = this.limit.join(argsSeparator); - } - if (this.filters && Object.keys(this.filters).length > 0) { - let filters = Object.keys(this.filters).map(key => { - const filter = this.filters[key]; - if (filter) { - return [key, filter].join(argsSeparator); - } - }).filter(Boolean); - if (filters.length > 0) { - ret.filters = filters.join('|'); - } + setOrderBy(name: string, key?: string) { + if (key) { + this.modifiers.push(name + ':' + key); + } else { + this.modifiers.push(name); } - return ret; } - // noinspection ReservedWordAsName - import(modifiers: { [key: string]: string }) { - const argsSeparator = ':'; - if (modifiers.orderBy) { - this.setOrderBy(...modifiers.orderBy.split(argsSeparator)); - } - - if (modifiers.limit) { - const [name, value] = modifiers.limit.split(argsSeparator); - this.setLimit(name, parseInt(value, 10)); - } - - if (modifiers.filters) { - modifiers.filters.split('|').forEach(filter => { - this.setFilter(...filter.split(argsSeparator)); - }); - } + setLimit(name: string, limit: number) { + this.modifiers.push(name + ':' + limit); } - setOrderBy(name: string, ...args: Array) { - this.orderBy = [name].concat(args); + setFilter(name: string, value: any, key?:string) { + if (key) { + this.modifiers.push(name + ':' + value + ':' + key); + } else { + this.modifiers.push(name + ':' + value); + } } - setLimit(name: string, limit: number) { - this.limit = [name, limit]; + getModifiers(): Array { + return [...this.modifiers]; } - setFilter(name: string, ...args: Array) { - let vals = args.filter(str => !!str); - if (vals.length > 0) { - this.filters[name] = vals; + getModifiersString(): string { + if (!this.modifiers || !Array.isArray(this.modifiers)) { + return ''; } - } - - build() { - let exportObj = this.export(); - return Object.keys(exportObj).map(exportKey => exportObj[exportKey]); + return this.modifiers.join('|'); } } diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index 35e5a9c..cb6c915 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -10,18 +10,6 @@ import Query from './query.js'; const FirestackDatabase = NativeModules.FirestackDatabase; -/** - * TODO ? why is it here and in index? make it a util - * @param modifiers - * @returns {*} - */ -function getModifiersString(modifiers) { - if (!modifiers || !Array.isArray(modifiers)) { - return ''; - } - return modifiers.join('|'); -} - // https://firebase.google.com/docs/reference/js/firebase.database.Reference let uid = 0; @@ -34,7 +22,7 @@ export default class Reference extends ReferenceBase { query: Query; uid: number; - constructor(db: FirestackDatabase, path: Array, existingModifiers?: { [key: string]: string }) { + constructor(db: FirestackDatabase, path: Array, existingModifiers?: Array) { super(db.firestack, path); this.db = db; @@ -73,8 +61,8 @@ export default class Reference extends ReferenceBase { // Get the value of a ref either with a key getAt() { const path = this.dbPath(); - const modifiers = this.dbModifiers(); - const modifiersString = getModifiersString(modifiers); + const modifiers = this.query.getModifiers(); + const modifiersString = this.query.getModifiersString(); return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); } @@ -108,11 +96,11 @@ export default class Reference extends ReferenceBase { on(evt?: string, cb: () => any) { const path = this.dbPath(); - const modifiers = this.dbModifiers(); - const modifiersString = getModifiersString(modifiers); + const modifiers = this.query.getModifiers(); + const modifiersString = this.query.getModifiersString(); this.log.debug('adding reference.on', path, modifiersString, evt); return this.db.storeRef(this.uid, this).then(() => { - return this.db.on(this.uid, path, modifiers, evt, cb).then(subscriptions => { + return this.db.on(this.uid, path, modifiersString, modifiers, evt, cb).then(subscriptions => { this.listeners[evt] = subscriptions; }); }); @@ -120,8 +108,8 @@ export default class Reference extends ReferenceBase { once(evt?: string = 'once', cb: (snapshot: Object) => void) { const path = this.dbPath(); - const modifiers = this.dbModifiers(); - const modifiersString = getModifiersString(modifiers); + const modifiers = this.query.getModifiers(); + const modifiersString = this.query.getModifiersString(); return this.db.storeRef(this.uid, this).then(() => { return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) .then(({ snapshot }) => new Snapshot(this, snapshot)) @@ -136,10 +124,10 @@ export default class Reference extends ReferenceBase { off(evt: string = '', origCB?: () => any) { const path = this.dbPath(); - const modifiers = this.dbModifiers(); + const modifiers = this.query.getModifiers(); this.log.debug('ref.off(): ', path, modifiers, evt); return this.db.unstoreRef(this.uid).then(() => { - return this.db.off(this.uid, path, modifiers, evt, origCB).then(subscriptions => { + return this.db.off(this.uid, path, modifiersString, modifiers, evt, origCB).then(subscriptions => { // delete this.listeners[eventName]; // this.listeners[evt] = subscriptions; }); @@ -184,68 +172,60 @@ export default class Reference extends ReferenceBase { }, {}); } - // class Query extends Reference {} - - // let ref = firestack.database().ref('/timeline'); - // ref.limitToLast(1).on('child_added', () => {}); - // ref.limitToFirst(1).on('child_added', () => {}); - // ref.on('child_added', () => {}) - // Modifiers orderByKey(): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setOrderBy('orderByKey'); - return newRef; + return this.orderBy('orderByKey'); } orderByPriority(): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setOrderBy('orderByPriority'); - return newRef; + return this.orderBy('orderByPriority'); } orderByValue(): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setOrderBy('orderByValue'); - return newRef; + return this.orderBy('orderByValue'); } orderByChild(key: string): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setOrderBy('orderByChild', key); + return this.orderBy('orderByChild', key); + } + + orderBy(name: string, key?: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.getModifiers()); + newRef.query.setOrderBy(name, key); return newRef; } // Limits limitToLast(limit: number): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setLimit('limitToLast', limit); - return newRef; + return this.limit('limitToLast', limit); } limitToFirst(limit: number): Reference { - // return this.query.setLimit('limitToFirst', limit); - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setLimit('limitToFirst', limit); + return this.limit('limitToFirst', limit); + } + + limit(name: string, limit: number): Reference { + const newRef = new Reference(this.db, this.path, this.query.getModifiers()); + newRef.query.setLimit(name, limit); return newRef; } // Filters equalTo(value: any, key?: string): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setFilter('equalTo', value, key); - return newRef; + return this.filter('equalTo', value, key); } endAt(value: any, key?: string): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setFilter('endAt', value, key); - return newRef; + return this.filter('endAt', value, key); } startAt(value: any, key?: string): Reference { - const newRef = new Reference(this.db, this.path, this.query.export()); - newRef.query.setFilter('startAt', value, key); + return this.filter('startAt', value, key); + } + + filter(name: string, value: any, key?: string): Reference { + const newRef = new Reference(this.db, this.path, this.query.getModifiers()); + newRef.query.setFilter(name, value, key); return newRef; } @@ -278,10 +258,6 @@ export default class Reference extends ReferenceBase { return pathStr; } - dbModifiers(): Array { - return this.query.build(); - } - get namespace(): string { return 'firestack:dbRef'; } From 6183ab130d33edc5f90ba65327704e00ac5fc924 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 6 Dec 2016 17:23:59 +0000 Subject: [PATCH 168/275] Send the type of the value over to the native side to simplify logic --- .../database/FirestackDatabaseReference.java | 96 +++++++++---------- ios/Firestack/FirestackDatabase.m | 23 ++--- lib/modules/database/query.js | 4 +- 3 files changed, 59 insertions(+), 64 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java index 2bfa56e..6f77a76 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -197,80 +197,74 @@ private Query buildDatabaseQueryAtPathAndModifiers(final FirebaseDatabase fireba query = query.limitToFirst(limit); } else if (methStr.contains("equalTo")) { String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - try { + String type = strArr[2]; + if ("number".equals(type)) { double doubleValue = Double.parseDouble(value); - if (key == null) { + if (strArr.length > 3) { + query = query.equalTo(doubleValue, strArr[3]); + } else { query = query.equalTo(doubleValue); + } + } else if ("boolean".equals(type)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (strArr.length > 3) { + query = query.equalTo(booleanValue, strArr[3] ); } else { - query = query.equalTo(doubleValue, key); + query = query.equalTo(booleanValue); } - } catch (NumberFormatException ex) { - if ("true".equals(value) || "false".equals(value)) { - boolean booleanValue = Boolean.parseBoolean(value); - if (key == null) { - query = query.equalTo(booleanValue); - } else { - query = query.equalTo(booleanValue, key); - } + } else { + if (strArr.length > 3) { + query = query.equalTo(value, strArr[3]); } else { - if (key == null) { - query = query.equalTo(value); - } else { - query = query.equalTo(value, key); - } + query = query.equalTo(value); } } } else if (methStr.contains("endAt")) { String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - try { + String type = strArr[2]; + if ("number".equals(type)) { double doubleValue = Double.parseDouble(value); - if (key == null) { + if (strArr.length > 3) { + query = query.endAt(doubleValue, strArr[3]); + } else { query = query.endAt(doubleValue); + } + } else if ("boolean".equals(type)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (strArr.length > 3) { + query = query.endAt(booleanValue, strArr[3] ); } else { - query = query.endAt(doubleValue, key); + query = query.endAt(booleanValue); } - } catch (NumberFormatException ex) { - if ("true".equals(value) || "false".equals(value)) { - boolean booleanValue = Boolean.parseBoolean(value); - if (key == null) { - query = query.endAt(booleanValue); - } else { - query = query.endAt(booleanValue, key); - } + } else { + if (strArr.length > 3) { + query = query.endAt(value, strArr[3]); } else { - if (key == null) { - query = query.endAt(value); - } else { - query = query.endAt(value, key); - } + query = query.endAt(value); } } } else if (methStr.contains("startAt")) { String value = strArr[1]; - String key = strArr.length >= 3 ? strArr[2] : null; - try { + String type = strArr[2]; + if ("number".equals(type)) { double doubleValue = Double.parseDouble(value); - if (key == null) { + if (strArr.length > 3) { + query = query.startAt(doubleValue, strArr[3]); + } else { query = query.startAt(doubleValue); + } + } else if ("boolean".equals(type)) { + boolean booleanValue = Boolean.parseBoolean(value); + if (strArr.length > 3) { + query = query.startAt(booleanValue, strArr[3] ); } else { - query = query.startAt(doubleValue, key); + query = query.startAt(booleanValue); } - } catch (NumberFormatException ex) { - if ("true".equals(value) || "false".equals(value)) { - boolean booleanValue = Boolean.parseBoolean(value); - if (key == null) { - query = query.startAt(booleanValue); - } else { - query = query.startAt(booleanValue, key); - } + } else { + if (strArr.length > 3) { + query = query.startAt(value, strArr[3]); } else { - if (key == null) { - query = query.startAt(value); - } else { - query = query.startAt(value, key); - } + query = query.startAt(value); } } } diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 9b7e1a8..e6ad061 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -223,9 +223,9 @@ - (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database } else if ([str containsString:@"equalTo"]) { NSArray *args = [str componentsSeparatedByString:@":"]; int size = (int)[args count];; - id value = [self getIdValue:args[1]]; - if (size > 2) { - NSString *key = args[2]; + id value = [self getIdValue:args[1] type:args[2]]; + if (size > 3) { + NSString *key = args[3]; query = [query queryEqualToValue:value childKey:key]; } else { @@ -234,9 +234,9 @@ - (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database } else if ([str containsString:@"endAt"]) { NSArray *args = [str componentsSeparatedByString:@":"]; int size = (int)[args count];; - id value = [self getIdValue:args[1]]; - if (size > 2) { - NSString *key = args[2]; + id value = [self getIdValue:args[1] type:args[2]]; + if (size > 3) { + NSString *key = args[3]; query = [query queryEndingAtValue:value childKey:key]; } else { @@ -244,10 +244,10 @@ - (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database } } else if ([str containsString:@"startAt"]) { NSArray *args = [str componentsSeparatedByString:@":"]; - id value = [self getIdValue:args[1]]; + id value = [self getIdValue:args[1] type:args[2]]; int size = (int)[args count];; - if (size > 2) { - NSString *key = args[2]; + if (size > 3) { + NSString *key = args[3]; query = [query queryStartingAtValue:value childKey:key]; } else { @@ -260,10 +260,11 @@ - (FIRDatabaseQuery *) buildQueryAtPathWithModifiers:(FIRDatabase*) database } - (id) getIdValue:(NSString *) value + type:(NSString *) type { - if (value.integerValue != nil) { + if ([type isEqualToString:@"number"]) { return [NSNumber numberWithInteger:value.integerValue]; - } else if ([value isEqualToString:@"true"] || [value isEqualToString:@"false"]) { + } else if ([type isEqualToString:@"boolean"]) { return [NSNumber numberWithBool:value.boolValue]; } else { return value; diff --git a/lib/modules/database/query.js b/lib/modules/database/query.js index 65604e5..baa6942 100644 --- a/lib/modules/database/query.js +++ b/lib/modules/database/query.js @@ -41,9 +41,9 @@ export default class Query extends ReferenceBase { setFilter(name: string, value: any, key?:string) { if (key) { - this.modifiers.push(name + ':' + value + ':' + key); + this.modifiers.push(name + ':' + value + ':' + (typeof value) + ':' + key); } else { - this.modifiers.push(name + ':' + value); + this.modifiers.push(name + ':' + value + ':' + (typeof value)); } } From 50b709f91cf4e9fd141ef94964405f88d4d2779d Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Wed, 7 Dec 2016 09:46:29 +0000 Subject: [PATCH 169/275] Fix warning when running off() on databsae reference --- lib/modules/database/reference.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index cb6c915..63db82d 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -125,6 +125,7 @@ export default class Reference extends ReferenceBase { off(evt: string = '', origCB?: () => any) { const path = this.dbPath(); const modifiers = this.query.getModifiers(); + const modifiersString = this.query.getModifiersString(); this.log.debug('ref.off(): ', path, modifiers, evt); return this.db.unstoreRef(this.uid).then(() => { return this.db.off(this.uid, path, modifiersString, modifiers, evt, origCB).then(subscriptions => { From 77ef0a4026f59cd3d606c7ad3896c242b5851bbc Mon Sep 17 00:00:00 2001 From: florianbepunkt Date: Sun, 11 Dec 2016 13:13:44 +0100 Subject: [PATCH 170/275] match presence syntax with V3 database syntax --- lib/modules/presence.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/presence.js b/lib/modules/presence.js index fc53a71..7952acd 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -7,7 +7,7 @@ class PresenceRef extends ReferenceBase { super(presence.firestack); this.presence = presence; - const db = this.firestack.database; + const db = this.firestack.database(); this.ref = ref; this.lastOnlineRef = this.ref.child('lastOnline'); @@ -84,7 +84,7 @@ export default class Presence extends Base { const path = this.path.concat(key); const pathKey = this._presenceKey(path); if (!this.instances[pathKey]) { - const _ref = this.firestack.database.ref(pathKey); + const _ref = this.firestack.database().ref(pathKey); this.log.debug('Created new presence object for ', pathKey); const inst = new PresenceRef(this, _ref, path); From 1f1b7f16cf82ae70042538b2150519c40bf589ef Mon Sep 17 00:00:00 2001 From: florianbepunkt Date: Sun, 11 Dec 2016 19:08:41 +0100 Subject: [PATCH 171/275] implements database().goOnline and database().goOffline methods https://firebase.google.com/docs/reference/js/firebase.database.Database #goOffline --- .../firestack/database/FirestackDatabase.java | 10 ++ ios/Firestack/FirestackDatabase.m | 20 ++- lib/modules/database/index.js | 8 + package.json | 155 +++++++++++++----- 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java index 842afdf..4a79fa5 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -286,6 +286,16 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { }); } + @ReactMethod + public void goOnline() { + mFirebaseDatabase.goOnline(); + } + + @ReactMethod + public void goOffline() { + mFirebaseDatabase.goOffline(); + } + private void handleCallback( final String methodName, final Callback callback, diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index e6ad061..88356fc 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -56,12 +56,10 @@ - (void) addEventHandler:(NSString *) eventName @"snapshot": props }]; }; - id errorBlock = ^(NSError * _Nonnull error) { NSLog(@"Error onDBEvent: %@", [error debugDescription]); [self getAndSendDatabaseError:error withPath: _path]; }; - int eventType = [self eventTypeFromName:eventName]; FIRDatabaseHandle handle = [_query observeEventType:eventType withBlock:withBlock @@ -139,7 +137,6 @@ - (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot [dict setValue:snapshot.key forKey:@"key"]; NSDictionary *val = snapshot.value; [dict setObject:val forKey:@"value"]; - // Snapshot ordering NSMutableArray *childKeys = [NSMutableArray array]; if (snapshot.childrenCount > 0) { @@ -152,13 +149,11 @@ - (NSDictionary *) snapshotToDict:(FIRDataSnapshot *) snapshot [childKeys addObject:child.key]; } } - [dict setObject:childKeys forKey:@"childKeys"]; [dict setValue:@(snapshot.hasChildren) forKey:@"hasChildren"]; [dict setValue:@(snapshot.exists) forKey:@"exists"]; [dict setValue:@(snapshot.childrenCount) forKey:@"childrenCount"]; [dict setValue:snapshot.priority forKey:@"priority"]; - return dict; } @@ -171,7 +166,6 @@ - (NSDictionary *) getAndSendDatabaseError:(NSError *) error @"msg": [error debugDescription] }; [self sendJSEvent:DATABASE_ERROR_EVENT title:DATABASE_ERROR_EVENT props: evt]; - return evt; } @@ -471,7 +465,6 @@ - (id) init { FirestackDBReference *ref = [self getDBHandle:path modifiers:modifiers modifiersString:modifiersString]; [ref addEventHandler:eventName]; - callback(@[[NSNull null], @{ @"status": @"success", @"handle": path @@ -495,7 +488,6 @@ - (id) init { NSString *key = [self getDBListenerKey:path withModifiers:modifiersString]; FirestackDBReference *ref = [_dbReferences objectForKey:key]; - if (ref != nil) { if (eventName == nil || [eventName isEqualToString:@""]) { [ref cleanup]; @@ -507,7 +499,6 @@ - (id) init } } } - callback(@[[NSNull null], @{ @"result": @"success", @"handle": path, @@ -559,6 +550,16 @@ - (id) init }]; } +RCT_EXPORT_METHOD(goOffline) +{ + [FIRDatabase database].goOffline; +} + +RCT_EXPORT_METHOD(goOnline) +{ + [FIRDatabase database].goOnline; +} + - (FIRDatabaseReference *) getPathRef:(NSString *) path { return [[[FIRDatabase database] reference] child:path]; @@ -612,4 +613,5 @@ - (NSString *) getDBListenerKey:(NSString *) path return @[DATABASE_DATA_EVENT, DATABASE_ERROR_EVENT]; } + @end diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index bd5b63c..6be7b5a 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -203,6 +203,14 @@ export default class Database extends Base { return path.join('-'); } + goOnline() { + FirestackDatabase.goOnline(); + } + + goOffline() { + FirestackDatabase.goOffline(); + } + get namespace(): string { return 'firestack:database'; } diff --git a/package.json b/package.json index 005fd6e..0c2c0a1 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,98 @@ { - "name": "react-native-firestack", - "version": "2.3.3", - "author": "Ari Lerner (https://fullstackreact.com)", - "description": "A firebase v3 adapter", - "main": "index", - "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start", - "build": "./node_modules/.bin/babel --source-maps=true --out-dir=dist .", - "dev": "npm run compile -- --watch", - "lint": "eslint ./src", - "publish_pages": "gh-pages -d public/", - "test": "./node_modules/.bin/mocha", - "watchcpx": "node ./bin/watchCopy" + "_args": [ + [ + { + "raw": "https://github.com/Salakar/react-native-firestack", + "scope": null, + "escapedName": null, + "name": null, + "rawSpec": "https://github.com/Salakar/react-native-firestack", + "spec": "git+https://github.com/Salakar/react-native-firestack.git", + "type": "hosted", + "hosted": { + "type": "github", + "ssh": "git@github.com:Salakar/react-native-firestack.git", + "sshUrl": "git+ssh://git@github.com/Salakar/react-native-firestack.git", + "httpsUrl": "git+https://github.com/Salakar/react-native-firestack.git", + "gitUrl": "git://github.com/Salakar/react-native-firestack.git", + "shortcut": "github:Salakar/react-native-firestack", + "directUrl": "https://raw.githubusercontent.com/Salakar/react-native-firestack/master/package.json" + } + }, + "/Users/floriannorbertbepunkt/Dropbox/Dropbox Arbeit/Mister Bishop Studios/Roomary/roomary" + ] + ], + "_from": "git+https://github.com/Salakar/react-native-firestack.git", + "_id": "react-native-firestack@2.3.3", + "_inCache": true, + "_location": "/react-native-firestack", + "_phantomChildren": {}, + "_requested": { + "raw": "https://github.com/Salakar/react-native-firestack", + "scope": null, + "escapedName": null, + "name": null, + "rawSpec": "https://github.com/Salakar/react-native-firestack", + "spec": "git+https://github.com/Salakar/react-native-firestack.git", + "type": "hosted", + "hosted": { + "type": "github", + "ssh": "git@github.com:Salakar/react-native-firestack.git", + "sshUrl": "git+ssh://git@github.com/Salakar/react-native-firestack.git", + "httpsUrl": "git+https://github.com/Salakar/react-native-firestack.git", + "gitUrl": "git://github.com/Salakar/react-native-firestack.git", + "shortcut": "github:Salakar/react-native-firestack", + "directUrl": "https://raw.githubusercontent.com/Salakar/react-native-firestack/master/package.json" + } }, - "repository": { - "type": "git", - "url": "https://github.com/fullstackreact/react-native-firestack.git" + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "git+https://github.com/Salakar/react-native-firestack.git#50b709f91cf4e9fd141ef94964405f88d4d2779d", + "_shasum": "05959e918292eeb9d6a3c39d9afb493ad11d5348", + "_shrinkwrap": null, + "_spec": "https://github.com/Salakar/react-native-firestack", + "_where": "/Users/floriannorbertbepunkt/Dropbox/Dropbox Arbeit/Mister Bishop Studios/Roomary/roomary", + "author": { + "name": "Ari Lerner", + "email": "ari@fullstack.io", + "url": "https://fullstackreact.com" + }, + "bugs": { + "url": "https://github.com/fullstackreact/react-native-firestack/issues" + }, + "dependencies": { + "bows": "^1.6.0", + "es6-symbol": "^3.1.0" + }, + "description": "A firebase v3 adapter", + "devDependencies": { + "babel-eslint": "^7.0.0", + "babel-jest": "^14.1.0", + "babel-preset-react-native": "^1.9.0", + "cpx": "^1.5.0", + "debug": "^2.2.0", + "enzyme": "^2.4.1", + "eslint": "^3.8.1", + "eslint-config-airbnb": "^12.0.0", + "eslint-plugin-flowtype": "^2.20.0", + "eslint-plugin-import": "^2.0.1", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.4.1", + "flow-bin": "^0.35.0", + "jest": "^14.1.0", + "jest-react-native": "^14.1.3", + "mocha": "^3.0.2", + "react": "^15.3.0", + "react-dom": "^15.3.0", + "react-native-mock": "^0.2.6", + "react-test-renderer": "^15.3.0", + "should": "^11.1.0", + "sinon": "^2.0.0-pre.2" }, + "gitHead": "50b709f91cf4e9fd141ef94964405f88d4d2779d", + "homepage": "https://github.com/fullstackreact/react-native-firestack#readme", "jest": { "preset": "jest-react-native", "setupFiles": [], @@ -27,7 +103,6 @@ "./node_modules/react-addons-test-utils" ] }, - "license": "ISC", "keywords": [ "react", "react-native", @@ -35,10 +110,20 @@ "firestack", "firebase" ], + "license": "ISC", + "main": "index", + "name": "react-native-firestack", + "optionalDependencies": {}, "peerDependencies": { "react": "*", "react-native": "*" }, + "readme": "# Firestack\n\nFirestack makes using the latest [Firebase](http://firebase.com) with React Native straight-forward.\n\n```\nnpm i react-native-firestack --save\n```\n\n[![Gitter](https://badges.gitter.im/fullstackreact/react-native-firestack.svg)](https://gitter.im/fullstackreact/react-native-firestack?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\n[![npm version](https://img.shields.io/npm/v/react-native-firestack.svg)](https://www.npmjs.com/package/react-native-firestack)\n[![License](https://img.shields.io/npm/l/react-native-firestack.svg)](/LICENSE)\n\nFirestack is a _light-weight_ layer sitting on-top of the native Firebase libraries for both iOS and Android which mirrors the React Native JS api as closely as possible.\n\nFeaturing; authentication, storage, real-time database, presence, analytics, cloud messaging, remote configuration, redux support and more!\n\n## Firestack vs Firebase JS lib\n\nAlthough the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it is mainly designed for the web. \n\nThe native SDK's are much better for performance compared to the web SDK. The web SDK will run on the same thread as your apps ([JS thread](https://facebook.github.io/react-native/docs/performance.html#javascript-frame-rate)) therefore limiting your JS framerate, potentially affecting things touch events and transitions/animations.\n\nThe native SDK's also contains functionality that the web SDK's do not, for example [Analytics](/docs/api/analytics.md) and [Remote Config](/docs/api/remote-config.md).\n\n## Example app\n\nWe have a working application example available in at [fullstackreact/FirestackApp](https://github.com/fullstackreact/FirestackApp). Check it out for more details about how to use Firestack.\n\n## Documentation\n\n* Installation\n * [iOS](docs/installation.ios.md)\n * [Android](docs/installation.android.md)\n* [Firebase Setup](docs/firebase-setup.md)\n* API\n * [Authentication](docs/api/authentication.md)\n * [Analytics](docs/api/analytics.md)\n * [Storage](docs/api/storage.md)\n * [Realtime Database](docs/api/database.md)\n * [Presence](docs/api/presence.md)\n * [ServerValue](docs/api/server-value.md)\n * [Cloud Messaging](docs/api/cloud-messaging.md)\n * [Remote Config](docs/api/remote-config.md)\n * [Events](docs/api/events.md)\n* [Redux](docs/redux.md)\n\n## Contributing\n\nFor a detailed discussion of how Firestack works as well as how to contribute, check out our [contribution guide](https://github.com/fullstackreact/react-native-firestack/blob/master/Contributing.md).\n", + "readmeFilename": "README.md", + "repository": { + "type": "git", + "url": "git+https://github.com/fullstackreact/react-native-firestack.git" + }, "rnpm": { "commands": { "prelink": "node_modules/react-native-firestack/bin/prepare.sh", @@ -51,32 +136,14 @@ "packageInstance": "new FirestackPackage()" } }, - "devDependencies": { - "babel-eslint": "^7.0.0", - "babel-jest": "^14.1.0", - "babel-preset-react-native": "^1.9.0", - "cpx": "^1.5.0", - "debug": "^2.2.0", - "enzyme": "^2.4.1", - "eslint": "^3.8.1", - "eslint-config-airbnb": "^12.0.0", - "eslint-plugin-flowtype": "^2.20.0", - "eslint-plugin-import": "^2.0.1", - "eslint-plugin-jsx-a11y": "^2.2.3", - "eslint-plugin-react": "^6.4.1", - "flow-bin": "^0.35.0", - "jest": "^14.1.0", - "jest-react-native": "^14.1.3", - "mocha": "^3.0.2", - "react": "^15.3.0", - "react-dom": "^15.3.0", - "react-native-mock": "^0.2.6", - "react-test-renderer": "^15.3.0", - "should": "^11.1.0", - "sinon": "^2.0.0-pre.2" + "scripts": { + "build": "babel --source-maps=true --out-dir=dist .", + "dev": "npm run compile -- --watch", + "lint": "eslint ./src", + "publish_pages": "gh-pages -d public/", + "start": "node node_modules/react-native/local-cli/cli.js start", + "test": "mocha", + "watchcpx": "node ./bin/watchCopy" }, - "dependencies": { - "bows": "^1.6.0", - "es6-symbol": "^3.1.0" - } + "version": "2.3.3" } From 7630c919f28d1f7d5316ecaf4bfc39c89fd2b4e8 Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Mon, 12 Dec 2016 17:55:10 +0000 Subject: [PATCH 172/275] added isObject util --- lib/utils/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/utils/index.js b/lib/utils/index.js index a26aed6..e6610dc 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -12,6 +12,14 @@ const _handler = (resolve, reject, err, resp) => { }); }; +/** + * Simple is object check. + * @param item + * @returns {boolean} + */ +export function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item) && item !== null); +} /** * Makes an objects keys it's values From 384fa1695f9e087fc5bca2c546ae3af147f92df0 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:42:18 +0000 Subject: [PATCH 173/275] remove old methods / misc cleanup --- lib/modules/messaging.js | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/lib/modules/messaging.js b/lib/modules/messaging.js index ec43536..eb5da3b 100644 --- a/lib/modules/messaging.js +++ b/lib/modules/messaging.js @@ -11,6 +11,7 @@ const FirestackMessagingEvt = new NativeEventEmitter(FirestackMessaging); export default class Messaging extends Base { constructor(firestack, options = {}) { super(firestack, options); + this.namespace = 'firestack:messaging'; } /* @@ -33,11 +34,6 @@ export default class Messaging extends Base { return this.offMessage(...args); } - - get namespace() { - return 'firestack:cloudMessaging' - } - getToken() { this.log.info('getToken for cloudMessaging'); return promisify('getToken', FirestackMessaging)(); @@ -72,13 +68,13 @@ export default class Messaging extends Base { } subscribeToTopic(topic) { - this.log.info('subscribeToTopic ' + topic); + this.log.info(`subscribeToTopic ${topic}`); const finalTopic = `/topics/${topic}`; return promisify('subscribeToTopic', FirestackMessaging)(finalTopic); } unsubscribeFromTopic(topic) { - this.log.info('unsubscribeFromTopic ' + topic); + this.log.info(`unsubscribeFromTopic ${topic}`); const finalTopic = `/topics/${topic}`; return promisify('unsubscribeFromTopic', FirestackMessaging)(finalTopic); } @@ -96,27 +92,6 @@ export default class Messaging extends Base { return promisify(() => sub, FirestackMessaging)(sub); } - // old API - /** - * @deprecated - * @param args - * @returns {*} - */ - listenForReceiveNotification(...args) { - console.warn('Firestack: listenForReceiveNotification is now deprecated, please use onMessage'); - return this.onMessage(...args); - } - - /** - * @deprecated - * @param args - * @returns {*} - */ - unlistenForReceiveNotification(...args) { - console.warn('Firestack: unlistenForReceiveNotification is now deprecated, please use offMessage'); - return this.offMessage(...args); - } - listenForReceiveUpstreamSend(callback) { this.log.info('Setting up send callback'); const sub = this._on('FirestackUpstreamSend', callback, FirestackMessagingEvt); From 227798deb4bcb7873b7e948946c2813e24333fd9 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:44:55 +0000 Subject: [PATCH 174/275] cleanup logger --- lib/utils/log.js | 56 +++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/utils/log.js b/lib/utils/log.js index 0ce363c..448b326 100644 --- a/lib/utils/log.js +++ b/lib/utils/log.js @@ -1,44 +1,42 @@ -// document hack -import root from './window-or-global'; -let bows; -(function (base) { - window = base || window - if (!window.localStorage) window.localStorage = {}; -})(root); -const levels = [ - 'warn', 'info', 'error', 'debug' -]; +import { windowOrGlobal } from './'; + +((base) => { + window = base || window; + if (!window.localStorage) window.localStorage = {}; +})(windowOrGlobal); -class Log { +export default class Log { constructor(namespace) { this._namespace = namespace || 'firestack'; + require('bows').config({ padLength: 20 }); this.loggers = {}; - // Add the logging levels for each level - levels - .forEach(level => this[level] = (...args) => this._log(level)(...args)); } - static enable(booleanOrStringDebug) { - window.localStorage.debug = - typeof booleanOrStringDebug === 'string' ? - (booleanOrStringDebug === '*' ? true : booleanOrStringDebug) : - (booleanOrStringDebug instanceof RegExp ? booleanOrStringDebug.toString() : booleanOrStringDebug); + get warn() { + return this._createOrGetLogger('warn'); + } + + get info() { + return this._createOrGetLogger('info'); + } + get error() { + return this._createOrGetLogger('error'); + } + + get debug() { + return this._createOrGetLogger('debug'); + } + + static enable(booleanOrStringDebug) { + window.localStorage.debug = booleanOrStringDebug; window.localStorage.debugColors = !!window.localStorage.debug; } - _log(level) { - if (!this.loggers[level]) { - (function () { - const bows = require('bows'); - bows.config({ padLength: 20 }); - this.loggers[level] = bows(this._namespace, `[${level}]`); - }.bind(this))(); - } + _createOrGetLogger(level) { + if (!this.loggers[level]) this.loggers[level] = require('bows')(this._namespace, `[${level}]`); return this.loggers[level]; } } - -export default Log; From f6ce4b8d1774c7446ad1bccbc3c14cb6aa284173 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:45:43 +0000 Subject: [PATCH 175/275] moved windowOr global inside utils/index --- lib/utils/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/utils/index.js b/lib/utils/index.js index a26aed6..fc9e394 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -13,6 +13,9 @@ const _handler = (resolve, reject, err, resp) => { }; +// noinspection Eslint +export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this; + /** * Makes an objects keys it's values * @param object From 38dc52904edc0c792b6b5159c7e61b35d12cc3e4 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:49:06 +0000 Subject: [PATCH 176/275] misc cleanup in base --- lib/modules/base.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/modules/base.js b/lib/modules/base.js index 7d3b14d..0ce3719 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -11,7 +11,13 @@ const FirestackModuleEvt = new NativeEventEmitter(FirestackModule); const logs = {}; +// single event emitter for all classes extending base +// TODO +// const EE = new EventEmitter(); + type FirestackOptions = {}; + +// TODO cleanup export class Base extends EventEmitter { constructor(firestack: Object, options: FirestackOptions = {}) { super(); @@ -22,12 +28,10 @@ export class Base extends EventEmitter { this.options = Object.assign({}, firestack.options, options); } + // Logger get log(): Log { - if (!logs[this.namespace]) { - const debug = this.firestack._debug; - logs[this.namespace] = new Log(this.namespace, debug); - } + if (!logs[this.namespace]) logs[this.namespace] = new Log(this.namespace, this.firestack._debug); return logs[this.namespace]; } @@ -40,9 +44,9 @@ export class Base extends EventEmitter { // TODO unused - do we need this anymore? _addToFirestackInstance(...methods: Array) { - methods.forEach(name => { + methods.forEach((name) => { this.firestack[name] = this[name].bind(this); - }) + }); } /** @@ -75,7 +79,7 @@ export class Base extends EventEmitter { const sub = nativeModule.addListener(name, cb); this.eventHandlers[name] = sub; resolve(sub); - }) + }); } _off(name) { @@ -84,7 +88,7 @@ export class Base extends EventEmitter { const subscription = this.eventHandlers[name]; subscription.remove(); // Remove subscription delete this.eventHandlers[name]; - resolve(subscription) + resolve(subscription); } }); } @@ -94,7 +98,7 @@ export class ReferenceBase extends Base { constructor(firestack: Object, path: Array | string) { super(firestack); - this.path = Array.isArray(path) ? path : (typeof path == 'string' ? [path] : []); + this.path = Array.isArray(path) ? path : (typeof path === 'string' ? [path] : []); // sanitize path, just in case this.path = this.path.filter(str => str !== ''); @@ -106,10 +110,10 @@ export class ReferenceBase extends Base { } pathToString(): string { - let path = this.path; + const path = this.path; let pathStr = (path.length > 0 ? path.join('/') : '/'); if (pathStr[0] != '/') { - pathStr = `/${pathStr}` + pathStr = `/${pathStr}`; } return pathStr; } From 0ae9d2fc1ab83bb96890503b3cc5649386d72766 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:50:33 +0000 Subject: [PATCH 177/275] update deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 005fd6e..11c72bf 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint ./src", "publish_pages": "gh-pages -d public/", "test": "./node_modules/.bin/mocha", - "watchcpx": "node ./bin/watchCopy" + "watchcpx": "echo 'See https://github.com/wix/wml for watching changes. \r\n'" }, "repository": { "type": "git", From ee7a63c93ac743b715c3a6fdbe0baada82c85e98 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 10:52:50 +0000 Subject: [PATCH 178/275] moved windowOr global inside utils/index --- .watchmanconfig | 11 +++++++++ bin/watchCopy.js | 45 ----------------------------------- lib/utils/window-or-global.js | 5 ---- 3 files changed, 11 insertions(+), 50 deletions(-) create mode 100644 .watchmanconfig delete mode 100644 bin/watchCopy.js delete mode 100644 lib/utils/window-or-global.js diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..b347186 --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,11 @@ +{ +"ignore_dirs": [ + ".git", + "node_modules", + "android/build", + "android/.idea", + "android/.gradle", + "android/gradle", + ".idea" + ] +} diff --git a/bin/watchCopy.js b/bin/watchCopy.js deleted file mode 100644 index b7e9a58..0000000 --- a/bin/watchCopy.js +++ /dev/null @@ -1,45 +0,0 @@ -const { watch, copy } = require('cpx'); -const { resolve } = require('path'); -const packageJson = require('./../package.json'); - -const readline = require('readline'); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -const PROJECT_DIR = resolve(__dirname, './../'); -let TARGET_DIR = process.env.TARGET_DIR; - -if (!TARGET_DIR) { - console.error('Missing TARGET_DIR process env, aborting!'); - console.error('EXAMPLE USAGE: TARGET_DIR=/Users/YOU/Documents/SomeReactApp npm run watchcpx'); - process.exit(1); -} - -if (!TARGET_DIR.includes('node_modules')) { - TARGET_DIR = `${TARGET_DIR}/node_modules/${packageJson.name}`; -} - -rl.question(`Watch for changes in '${PROJECT_DIR}' and copy to '${TARGET_DIR}'? (y/n): `, (answer) => { - if (answer.toLowerCase() === 'y') { - // flat copy node_modules as we're not watching it - console.log('Copying node_modules directory...'); - copy(PROJECT_DIR + '/node_modules/**/*.*', TARGET_DIR + '/node_modules', { clean: true }, () => { - console.log('Copy complete.'); - console.log('Watching for changes in project directory... (excludes node_modules)'); - const watcher = watch(PROJECT_DIR + '/{ios,android,lib}/**/*.*', TARGET_DIR, { verbose: true }); - watcher.on('copy', (e) => { - // if (!e.srcPath.startsWith('node_modules')) { - console.log(`Copied ${e.srcPath} to ${e.dstPath}`); - // } - }); - }); - } else { - console.log('Aborting watch.'); - process.exit(); - } - rl.close(); -}); - diff --git a/lib/utils/window-or-global.js b/lib/utils/window-or-global.js deleted file mode 100644 index f6273f0..0000000 --- a/lib/utils/window-or-global.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict' -// https://github.com/purposeindustries/window-or-global -module.exports = (typeof self === 'object' && self.self === self && self) || - (typeof global === 'object' && global.global === global && global) || - this; From 9435b1b59087343e33cedf796abb6ac176c043ec Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 11:00:25 +0000 Subject: [PATCH 179/275] misc storage --- lib/modules/{storage.js => storage/index.js} | 65 +++----------------- lib/modules/storage/reference.js | 52 ++++++++++++++++ 2 files changed, 62 insertions(+), 55 deletions(-) rename lib/modules/{storage.js => storage/index.js} (59%) create mode 100644 lib/modules/storage/reference.js diff --git a/lib/modules/storage.js b/lib/modules/storage/index.js similarity index 59% rename from lib/modules/storage.js rename to lib/modules/storage/index.js index 5817759..d8e1bbd 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage/index.js @@ -1,62 +1,19 @@ /* @flow */ import { NativeModules, NativeEventEmitter } from 'react-native'; -import { promisify, noop } from '../utils'; -import { Base, ReferenceBase } from './base'; +import { Base } from './../base'; +import StorageRef from './reference'; +import { promisify, noop } from './../../utils'; const FirestackStorage = NativeModules.FirestackStorage; const FirestackStorageEvt = new NativeEventEmitter(FirestackStorage); -class StorageRef extends ReferenceBase { - constructor(storage, path) { - super(storage.firestack, path); - - this.storage = storage; - } - - downloadUrl(): Promise { - const path = this.pathToString(); - this.log.debug('downloadUrl(', path, ')'); - return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path) - .catch(err => { - this.log.error('Error downloading URL for ', path, '. Error: ', err); - throw err; - }); - } - - /** - * Downloads a reference to the device - * @param {String} downloadPath Where to store the file - * @param listener - * @return {Promise} - */ - download(downloadPath: string, listener: Function = noop): Promise { - const path = this.pathToString(); - this.log.debug('download(', path, ') -> ', downloadPath); - const listeners = [ - this.storage._addListener('download_progress', listener), - this.storage._addListener('download_paused', listener), - this.storage._addListener('download_resumed', listener), - ]; - - return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) - .then((res) => { - this.log.debug('res --->', res); - listeners.forEach(listener => listener.remove()); - return res; - }) - .catch(err => { - this.log.error('Error downloading ', path, ' to ', downloadPath, '. Error: ', err); - throw err; - }); - } -} - type StorageOptionsType = { storageBucket?: ?string, }; + export default class Storage extends Base { - constructor(firestack: Object, options:StorageOptionsType={}) { + constructor(firestack: Object, options: StorageOptionsType = {}) { super(firestack, options); if (this.options.storageBucket) { @@ -69,14 +26,13 @@ export default class Storage extends Base { ref(...path: Array): StorageRef { const key = this._pathKey(...path); if (!this.refs[key]) { - const ref = new StorageRef(this, path); - this.refs[key] = ref; + this.refs[key] = new StorageRef(this, path); } return this.refs[key]; } /** - * Upload a filepath + * Upload a file path * @param {string} name The destination for the file * @param {string} filePath The local path of the file * @param {object} metadata An object containing metadata @@ -97,7 +53,7 @@ export default class Storage extends Base { listeners.forEach(listener => listener.remove()); return res; }) - .catch(err => { + .catch((err) => { this.log.error('Error uploading file ', name, ' to ', _filePath, '. Error: ', err); throw err; }); @@ -108,8 +64,7 @@ export default class Storage extends Base { } _addListener(evt: string, cb: (evt: Object) => Object): {remove: () => void} { - let listener = FirestackStorageEvt.addListener(evt, cb); - return listener; + return FirestackStorageEvt.addListener(evt, cb); } setStorageUrl(url: string): void { @@ -134,7 +89,7 @@ export default class Storage extends Base { }; get namespace(): string { - return 'firestack:storage' + return 'firestack:storage'; } } diff --git a/lib/modules/storage/reference.js b/lib/modules/storage/reference.js new file mode 100644 index 0000000..005d58d --- /dev/null +++ b/lib/modules/storage/reference.js @@ -0,0 +1,52 @@ +/* @flow */ +import { NativeModules } from 'react-native'; + +import { promisify, noop } from '../../utils'; +import { ReferenceBase } from './../base'; +import Storage from './'; + +const FirestackStorage = NativeModules.FirestackStorage; + +export default class StorageReference extends ReferenceBase { + constructor(storage: Storage, path: Array) { + super(storage.firestack, path); + this.storage = storage; + } + + downloadUrl(): Promise { + const path = this.pathToString(); + this.log.debug('downloadUrl(', path, ')'); + return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path) + .catch((err) => { + this.log.error('Error downloading URL for ', path, '. Error: ', err); + throw err; + }); + } + + /** + * Downloads a reference to the device + * @param {String} downloadPath Where to store the file + * @param listener + * @return {Promise} + */ + download(downloadPath: string, listener: Function = noop): Promise { + const path = this.pathToString(); + this.log.debug('download(', path, ') -> ', downloadPath); + const listeners = [ + this.storage._addListener('download_progress', listener), + this.storage._addListener('download_paused', listener), + this.storage._addListener('download_resumed', listener), + ]; + + return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) + .then((res) => { + this.log.debug('res --->', res); + listeners.forEach(l => l.remove()); + return res; + }) + .catch((err) => { + this.log.error('Error downloading ', path, ' to ', downloadPath, '. Error: ', err); + throw err; + }); + } +} From 932036d80591ebcf409490c71561a8adbbc4043f Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 11:56:40 +0000 Subject: [PATCH 180/275] Merge branch 'master' of https://github.com/Salakar/react-native-firestack # Conflicts: # lib/modules/storage/index.js # package.json --- lib/modules/presence.js | 3 +-- lib/modules/storage/reference.js | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/modules/presence.js b/lib/modules/presence.js index 7952acd..99f2ccb 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -1,14 +1,13 @@ import invariant from 'invariant'; -import { promisify } from '../utils'; import { Base, ReferenceBase } from './base'; class PresenceRef extends ReferenceBase { constructor(presence, ref, pathParts) { super(presence.firestack); + this.ref = ref; this.presence = presence; const db = this.firestack.database(); - this.ref = ref; this.lastOnlineRef = this.ref.child('lastOnline'); this._connectedRef = db.ref('.info/connected'); diff --git a/lib/modules/storage/reference.js b/lib/modules/storage/reference.js index 005d58d..e70a93d 100644 --- a/lib/modules/storage/reference.js +++ b/lib/modules/storage/reference.js @@ -7,16 +7,17 @@ import Storage from './'; const FirestackStorage = NativeModules.FirestackStorage; -export default class StorageReference extends ReferenceBase { +export default class StorageRef extends ReferenceBase { constructor(storage: Storage, path: Array) { super(storage.firestack, path); + this.storage = storage; } downloadUrl(): Promise { const path = this.pathToString(); this.log.debug('downloadUrl(', path, ')'); - return promisify('downloadUrl', FirestackStorage)(this.storage.storageUrl, path) + return promisify('downloadUrl', FirestackStorage)(path) .catch((err) => { this.log.error('Error downloading URL for ', path, '. Error: ', err); throw err; @@ -38,7 +39,7 @@ export default class StorageReference extends ReferenceBase { this.storage._addListener('download_resumed', listener), ]; - return promisify('downloadFile', FirestackStorage)(this.storage.storageUrl, path, downloadPath) + return promisify('downloadFile', FirestackStorage)(path, downloadPath) .then((res) => { this.log.debug('res --->', res); listeners.forEach(l => l.remove()); From c16482a421ddbc4f8a189434d8667d348234b223 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 12:34:39 +0000 Subject: [PATCH 181/275] cleanup presence - added todo's that need fixing --- lib/modules/presence.js | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/modules/presence.js b/lib/modules/presence.js index 99f2ccb..64f68a1 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -1,34 +1,33 @@ -import invariant from 'invariant'; import { Base, ReferenceBase } from './base'; class PresenceRef extends ReferenceBase { constructor(presence, ref, pathParts) { super(presence.firestack); - this.ref = ref; + this._onConnect = []; this.presence = presence; - const db = this.firestack.database(); - this.lastOnlineRef = this.ref.child('lastOnline'); - - this._connectedRef = db.ref('.info/connected'); this._pathParts = pathParts; - - this._onConnect = []; + this.lastOnlineRef = this.ref.child('lastOnline'); + this._connectedRef = this.firestack.database().ref('.info/connected'); } setOnline() { - this.ref.setAt({ online: true }); + this.ref.set({ online: true }); + + // todo cleanup - creating a ref every time? this._connectedRef.on('value', (snapshot) => { const val = snapshot.val(); if (val) { // add self to connection list // this.ref.push() - this.ref.setAt({ + this.ref.set({ online: true, }) .then(() => { this._disconnect(); + // todo switch to event emitter + // todo this will leak this._onConnect.forEach((fn) => { if (fn && typeof fn === 'function') { fn.bind(this)(this.ref); @@ -42,8 +41,7 @@ class PresenceRef extends ReferenceBase { setOffline() { if (this.ref) { - this.ref.setAt({ online: false }) - .then(() => this.ref.off('value')); + this.ref.set({ online: false }).then(() => this.ref.off('value')); this.presence.off(this._pathParts); } return this; @@ -51,11 +49,9 @@ class PresenceRef extends ReferenceBase { _disconnect() { if (this.ref) { - this.ref.onDisconnect() - .setValue({ online: false }); - // set last online time - this.lastOnlineRef.onDisconnect() - .setValue(this.firestack.ServerValue.TIMESTAMP); + this.ref.onDisconnect().setValue({ online: false }); + // todo ServerValue is a promise? so this should be broken..? + this.lastOnlineRef.onDisconnect().setValue(this.firestack.ServerValue.TIMESTAMP); } } @@ -63,6 +59,8 @@ class PresenceRef extends ReferenceBase { return this._pathParts.join('/'); } + // todo switch to event emitter + // todo this will leak onConnect(cb) { this._onConnect.push(cb); return this; @@ -79,15 +77,13 @@ export default class Presence extends Base { } on(key) { - invariant(key, 'You must supply a key for presence'); + if (!key || !key.length) throw new Error('You must supply a key for presence'); const path = this.path.concat(key); const pathKey = this._presenceKey(path); if (!this.instances[pathKey]) { const _ref = this.firestack.database().ref(pathKey); this.log.debug('Created new presence object for ', pathKey); - const inst = new PresenceRef(this, _ref, path); - - this.instances[pathKey] = inst; + this.instances[pathKey] = new PresenceRef(this, _ref, path); } return this.instances[pathKey]; From 540ef6555f83616be96653bc9ad92a065ceeb629 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 12:37:38 +0000 Subject: [PATCH 182/275] cleanup presence - added todo's that need fixing --- lib/modules/presence.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/modules/presence.js b/lib/modules/presence.js index 64f68a1..e44b6b6 100644 --- a/lib/modules/presence.js +++ b/lib/modules/presence.js @@ -71,7 +71,6 @@ class PresenceRef extends ReferenceBase { export default class Presence extends Base { constructor(firestack, options = {}) { super(firestack, options); - this.instances = {}; this.path = ['/presence/connections']; } From ff8fe80675e255141a6f760b3f1bfc0332dc7c47 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 12:39:39 +0000 Subject: [PATCH 183/275] added isFunction util --- lib/utils/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/utils/index.js b/lib/utils/index.js index dc8676c..96a3990 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -21,6 +21,15 @@ export function isObject(item) { return (item && typeof item === 'object' && !Array.isArray(item) && item !== null); } +/** + * Simple is function check + * @param item + * @returns {*|boolean} + */ +export function isFunction(item) { + return (item && typeof item === 'function'); +} + // noinspection Eslint export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this; From e9f648d5d944352370054438088f1f76959c2753 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 12:40:31 +0000 Subject: [PATCH 184/275] added tryJSONStringify / tryJSONParse utils --- lib/utils/index.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/utils/index.js b/lib/utils/index.js index 96a3990..376602a 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -30,6 +30,33 @@ export function isFunction(item) { return (item && typeof item === 'function'); } +/** + * + * @param string + * @returns {*} + */ +export function tryJSONParse(string) { + try { + return JSON.parse(string); + } catch (jsonError) { + return string; + } +} + +/** + * + * @param data + * @returns {*} + */ +export function tryJSONStringify(data) { + try { + return JSON.stringify(data); + } catch (jsonError) { + return undefined; + } +} + + // noinspection Eslint export const windowOrGlobal = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || this; From c32972a020e970204154d4bd7500c35d1d9db202 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 14:13:35 +0000 Subject: [PATCH 185/275] added tryJSONStringify / tryJSONParse utils --- lib/utils/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index 376602a..cfcaa38 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -52,7 +52,7 @@ export function tryJSONStringify(data) { try { return JSON.stringify(data); } catch (jsonError) { - return undefined; + return null; } } From 4960ecd808412c0e5097982cb70c1c6645071765 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 14:14:23 +0000 Subject: [PATCH 186/275] firestack set/push now correctly support any type of value --- .../io/fullstack/firestack/database/FirestackDatabase.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java index 4a79fa5..c7e1a76 100644 --- a/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -78,6 +78,7 @@ public void set( DatabaseReference ref = mFirebaseDatabase.getReference(path); Map m = Utils.recursivelyDeconstructReadableMap(props); + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { @@ -85,7 +86,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { } }; - ref.setValue(m, listener); + ref.setValue(m.get("value"), listener); } @ReactMethod @@ -155,7 +156,7 @@ public void onComplete(DatabaseError error, DatabaseReference ref) { } }; - newRef.setValue(m, listener); + newRef.setValue(m.get("value"), listener); } else { Log.d(TAG, "No value passed to push: " + newPath); WritableMap res = Arguments.createMap(); From 36bc3912e469acaff0d3a0af11dc862267ad360f Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 14:17:20 +0000 Subject: [PATCH 187/275] - push is now sync with no value OR async with a value - added key property to refs - added parent property to refs - added root property to refs - added toString method to refs --- lib/modules/database/reference.js | 179 ++++++++++++++++-------------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index 63db82d..c47e565 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -2,11 +2,12 @@ * @flow */ import { NativeModules } from 'react-native'; -import { promisify } from './../../utils'; -import { ReferenceBase } from './../base'; -import Snapshot from './snapshot.js'; -import Disconnect from './disconnect.js'; + import Query from './query.js'; +import Snapshot from './snapshot'; +import Disconnect from './disconnect'; +import { ReferenceBase } from './../base'; +import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, generatePushID } from './../../utils'; const FirestackDatabase = NativeModules.FirestackDatabase; @@ -26,29 +27,12 @@ export default class Reference extends ReferenceBase { super(db.firestack, path); this.db = db; - this.query = new Query(this, path, existingModifiers); - this.uid = uid++; // uuid.v4(); + this.uid = uid += 1; this.listeners = {}; - - // Aliases - this.get = this.getAt; - this.set = this.setAt; - this.update = this.updateAt; - this.remove = this.removeAt; - + this.query = new Query(this, path, existingModifiers); this.log.debug('Created new Reference', this.dbPath(), this.uid); } - // Parent roots - parent() { - const parentPaths = this.path.slice(0, -1); - return new Reference(this.db, parentPaths); - } - - root() { - return new Reference(this.db, []); - } - child(...paths: Array) { return new Reference(this.db, this.path.concat(paths)); } @@ -59,38 +43,53 @@ export default class Reference extends ReferenceBase { } // Get the value of a ref either with a key - getAt() { + // todo - where is this on the FB JS api - seems just like another random function + get() { const path = this.dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); } - setAt(val: any) { + set(value: any) { const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value); + const _value = this._serializeAnyType(value); + return promisify('set', FirestackDatabase)(path, _value); } - updateAt(val: any) { + update(val: Object) { const path = this.dbPath(); - const value = this._serializeValue(val); + const value = this._serializeObject(val); return promisify('update', FirestackDatabase)(path, value); } - // TODO - what is key even for here? - removeAt(key: string) { - const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path); + remove() { + return promisify('remove', FirestackDatabase)(this.dbPath()); } - push(val: any = {}) { + /** + * + * @param value + * @param onComplete + * @returns {*} + */ + push(value: any, onComplete: Function) { + if (value === null || value === undefined) { + // todo add server timestamp to push id call. + const _paths = this.path.concat([generatePushID()]); + return new Reference(this.db, _paths); + } + const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('push', FirestackDatabase)(path, value) + const _value = this._serializeAnyType(value); + return promisify('push', FirestackDatabase)(path, _value) .then(({ ref }) => { - const separator = '/'; - return new Reference(this.db, ref.split(separator)); + const newRef = new Reference(this.db, ref.split('/')); + if (isFunction(onComplete)) return onComplete(null, newRef); + return newRef; + }).catch((e) => { + if (isFunction(onComplete)) return onComplete(e, null); + return e; }); } @@ -100,7 +99,7 @@ export default class Reference extends ReferenceBase { const modifiersString = this.query.getModifiersString(); this.log.debug('adding reference.on', path, modifiersString, evt); return this.db.storeRef(this.uid, this).then(() => { - return this.db.on(this.uid, path, modifiersString, modifiers, evt, cb).then(subscriptions => { + return this.db.on(this.uid, path, modifiersString, modifiers, evt, cb).then((subscriptions) => { this.listeners[evt] = subscriptions; }); }); @@ -111,12 +110,11 @@ export default class Reference extends ReferenceBase { const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); return this.db.storeRef(this.uid, this).then(() => { + // todo use event emitter - not callbacks return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) .then(({ snapshot }) => new Snapshot(this, snapshot)) - .then(snapshot => { - if (cb && typeof cb === 'function') { - cb(snapshot); - } + .then((snapshot) => { + if (isFunction(cb)) cb(snapshot); return snapshot; }); }); @@ -128,7 +126,8 @@ export default class Reference extends ReferenceBase { const modifiersString = this.query.getModifiersString(); this.log.debug('ref.off(): ', path, modifiers, evt); return this.db.unstoreRef(this.uid).then(() => { - return this.db.off(this.uid, path, modifiersString, modifiers, evt, origCB).then(subscriptions => { + return this.db.off(this.uid, path, modifiersString, modifiers, evt, origCB).then(() => { + // todo urm - whats this? // delete this.listeners[eventName]; // this.listeners[evt] = subscriptions; }); @@ -136,41 +135,41 @@ export default class Reference extends ReferenceBase { } cleanup() { - let promises = Object.keys(this.listeners) - .map(key => this.off(key)); - return Promise.all(promises); - } - - // Sanitize value - // As Firebase cannot store date objects. - _serializeValue(obj: Object = {}) { - if (!obj) { - return obj; - } - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.toISOString(); - } + return Promise.all(Object.keys(this.listeners).map(key => this.off(key))); + } + + /** + * + * @param obj + * @returns {Object} + * @private + */ + _serializeObject(obj: Object) { + if (!isObject(obj)) return obj; + + // json stringify then parse it calls toString on Objects / Classes + // that support it i.e new Date() becomes a ISO string. + return tryJSONParse(tryJSONStringify(obj)); + } + + /** + * + * @param value + * @returns {*} + * @private + */ + _serializeAnyType(value: any) { + if (isObject(value)) { return { - ...sum, - [key]: val, + type: 'object', + value: this._serializeObject(value), }; - }, {}); - } + } - // TODO this function isn't used anywhere - why is it here? - _deserializeValue(obj: Object = {}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.getTime(); - } - return { - ...sum, - [key]: val, - }; - }, {}); + return { + type: typeof value, + value, + }; } // Modifiers @@ -242,24 +241,36 @@ export default class Reference extends ReferenceBase { } // attributes - get fullPath(): string { + toString(): string { return this.dbPath(); } - get name(): string { - return this.path.splice(-1); - } + dbPath(paths?: Array): string { + const path = paths || this.path; + const pathStr = (path.length > 0 ? path.join('/') : '/'); - dbPath(): string { - let path = this.path; - let pathStr = (path.length > 0 ? path.join('/') : '/'); if (pathStr[0] !== '/') { - pathStr = `/${pathStr}`; + return `/${pathStr}`; } + return pathStr; } get namespace(): string { return 'firestack:dbRef'; } + + get key(): string|null { + if (!this.path.length) return null; + return this.path.slice(this.path.length - 1, this.path.length)[0]; + } + + get parent(): Reference|null { + if (!this.path.length || this.path.length === 1) return null; + return new Reference(this.db, this.path.slice(0, -1)); + } + + get root(): Reference { + return new Reference(this.db, []); + } } From 6b57acaba52398a9634d6554d37981bdb31ddd48 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 13 Dec 2016 14:41:49 +0000 Subject: [PATCH 188/275] misc --- lib/utils/log.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/utils/log.js b/lib/utils/log.js index 448b326..3584ce1 100644 --- a/lib/utils/log.js +++ b/lib/utils/log.js @@ -1,5 +1,3 @@ - - import { windowOrGlobal } from './'; ((base) => { From da8b175ef5f3244ac7d81ca71db4108e4792d113 Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Thu, 15 Dec 2016 16:54:40 +0000 Subject: [PATCH 189/275] Update iOS to support push/update changes --- ios/Firestack/FirestackDatabase.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Firestack/FirestackDatabase.m b/ios/Firestack/FirestackDatabase.m index 88356fc..bdb10fa 100644 --- a/ios/Firestack/FirestackDatabase.m +++ b/ios/Firestack/FirestackDatabase.m @@ -391,11 +391,11 @@ - (id) init } RCT_EXPORT_METHOD(set:(NSString *) path - value:(NSDictionary *)value + data:(NSDictionary *)data callback:(RCTResponseSenderBlock) callback) { FIRDatabaseReference *ref = [self getPathRef:path]; - [ref setValue:value withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { + [ref setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { [self handleCallback:@"set" callback:callback databaseError:error]; }]; } @@ -420,7 +420,7 @@ - (id) init } RCT_EXPORT_METHOD(push:(NSString *) path - props:(NSDictionary *) props + data:(NSDictionary *) data callback:(RCTResponseSenderBlock) callback) { FIRDatabaseReference *ref = [self getPathRef:path]; @@ -429,8 +429,8 @@ - (id) init NSURL *url = [NSURL URLWithString:newRef.URL]; NSString *newPath = [url path]; - if ([props count] > 0) { - [newRef setValue:props withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { + if ([data count] > 0) { + [newRef setValue:[data valueForKey:@"value"] withCompletionBlock:^(NSError * _Nullable error, FIRDatabaseReference * _Nonnull ref) { if (error != nil) { // Error handling NSDictionary *evt = @{ From 56335c519fa881aa1674f10e3c20f5a3d354e81c Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Mon, 19 Dec 2016 17:59:17 +0000 Subject: [PATCH 190/275] Fix JS reference memory leaks --- lib/modules/database/index.js | 214 +++++++++++------------------- lib/modules/database/reference.js | 61 +++------ 2 files changed, 91 insertions(+), 184 deletions(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index 6be7b5a..7faee09 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -22,30 +22,22 @@ export default class Database extends Base { this.log.debug('Created new Database instance', this.options); this.persistenceEnabled = false; - this.successListener = null; - this.errorListener = null; - this.refs = {}; - this.dbSubscriptions = {}; // { path: { modifier: { eventType: [Subscriptions] } } } + this.successListener = FirestackDatabaseEvt + .addListener( + 'database_event', + event => this.handleDatabaseEvent(event)); + this.errorListener = FirestackDatabaseEvt + .addListener( + 'database_error', + err => this.handleDatabaseError(err)); + + this.dbSubscriptions = {}; } ref(...path: Array) { return new Reference(this, path); } - storeRef(key: string, instance: Reference): Promise { - if (!this.refs[key]) { - this.refs[key] = instance; - } - return Promise.resolve(this.refs[key]); - } - - unstoreRef(key: string): Promise { - if (this.refs[key]) { - delete this.refs[key]; - } - return Promise.resolve(); - } - setPersistence(enable: boolean = true) { let promise; if (this.persistenceEnabled !== enable) { @@ -59,148 +51,94 @@ export default class Database extends Base { return promise; } - handleDatabaseEvent(evt: Object) { - const body = evt.body || {}; - const path = body.path; - const modifiersString = body.modifiersString || ''; - const modifier = modifiersString; - const eventName = body.eventName; - this.log.debug('handleDatabaseEvent: ', path, modifiersString, eventName, body.snapshot && body.snapshot.key); - - // subscriptionsMap === { path: { modifier: { eventType: [Subscriptions] } } } - const modifierMap = this.dbSubscriptions[path]; - if (modifierMap) { - const eventTypeMap = modifierMap[modifier]; - if (eventTypeMap) { - const callbacks = eventTypeMap[eventName] || []; - this.log.debug(' -- about to fire its ' + callbacks.length + ' callbacks'); - callbacks.forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new Snapshot(this, body.snapshot); - cb(snap, body); - } - }); - } + handleDatabaseEvent(event: Object) { + const body = event.body || {}; + const { path, modifiersString, eventName, snapshot } = body; + const dbHandle = this._dbHandle(path, modifiersString); + this.log.debug('handleDatabaseEvent: ', dbHandle, eventName, snapshot && snapshot.key); + + if (this.dbSubscriptions[dbHandle] && this.dbSubscriptions[dbHandle][eventName]) { + this.dbSubscriptions[dbHandle][eventName].forEach(cb => { + if (cb && typeof(cb) === 'function') { + const snap = new Snapshot(this, snapshot); + cb(snap, body); + } + }) + } else { + FirestackDatabase.off(path, modifiersString, eventName, () => { + this.log.debug('handleDatabaseEvent: No JS listener registered, removed native listener', dbHandle, eventName); + }); } } - handleDatabaseError(evt: Object) { - this.log.debug('handleDatabaseError ->', evt); + handleDatabaseError(err: Object) { + this.log.debug('handleDatabaseError ->', err); } - on(referenceKey: string, path: string, modifiersString: string, modifiers: Array, evt: string, cb: () => void) { - this.log.debug('adding on listener', referenceKey, path, modifiers, evt); - const key = this._pathKey(path); - - if (!this.dbSubscriptions[key]) { - this.dbSubscriptions[key] = {}; - } - - if (!this.dbSubscriptions[key][modifiersString]) { - this.dbSubscriptions[key][modifiersString] = {}; - } - - if (!this.dbSubscriptions[key][modifiersString][evt]) { - this.dbSubscriptions[key][modifiersString][evt] = []; - } - - this.dbSubscriptions[key][modifiersString][evt].push(cb); - - if (!this.successListener) { - this.successListener = FirestackDatabaseEvt - .addListener( - 'database_event', - this.handleDatabaseEvent.bind(this)); - } - - if (!this.errorListener) { - this.errorListener = FirestackDatabaseEvt - .addListener( - 'database_error', - this.handleDatabaseError.bind(this)); + on(path: string, modifiersString: string, modifiers: Array, eventName: string, cb: () => void) { + const dbHandle = this._dbHandle(path, modifiersString); + this.log.debug('adding on listener', dbHandle); + + if (this.dbSubscriptions[dbHandle]) { + if (this.dbSubscriptions[dbHandle][eventName]) { + this.dbSubscriptions[dbHandle][eventName].push(cb); + } else { + this.dbSubscriptions[dbHandle][eventName] = [cb]; + } + } else { + this.dbSubscriptions[dbHandle] = { + [eventName]: [cb] + } } - return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, evt).then(() => { - return [this.successListener, this.errorListener]; - }); + return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, eventName); } - off(referenceKey: string, path: string, modifiersString: string, modifiers: Array, eventName: string, origCB?: () => void) { - const pathKey = this._pathKey(path); - this.log.debug('off() : ', referenceKey, pathKey, modifiersString, eventName); - // Remove subscription - if (this.dbSubscriptions[pathKey]) { + off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) { + const dbHandle = this._dbHandle(path, modifiersString); + this.log.debug('off() : ', dbHandle, eventName); - if (!eventName || eventName === '') { - // remove all listeners for this pathKey - this.dbSubscriptions[pathKey] = {}; - } + if (!this.dbSubscriptions[dbHandle] + || (eventName && !this.dbSubscriptions[dbHandle][eventName])) { + this.log.warn('off() called, but not currently listening at that location (bad path)', dbHandle, eventName); + return Promise.resolve(); + } - // TODO clean me - no need for this many conditionals - if (this.dbSubscriptions[pathKey][modifiersString]) { - if (this.dbSubscriptions[pathKey][modifiersString][eventName]) { - if (origCB) { - // remove only the given callback - this.dbSubscriptions[pathKey][modifiersString][eventName].splice(this.dbSubscriptions[pathKey][modifiersString][eventName].indexOf(origCB), 1); - } else { - // remove all callbacks for this path:modifier:eventType - delete this.dbSubscriptions[pathKey][modifiersString][eventName]; - } - } else { - this.log.warn('off() called, but not currently listening at that location (bad eventName)', pathKey, modifiersString, eventName); - } + if (eventName && origCB) { + const i = this.dbSubscriptions[dbHandle][eventName].indexOf(origCB); + if (i === -1) { + this.log.warn('off() called, but the callback specifed is not listening at that location (bad path)', dbHandle, eventName); + return Promise.resolve(); } else { - this.log.warn('off() called, but not currently listening at that location (bad modifier)', pathKey, modifiersString, eventName); - } - - if (Object.keys(this.dbSubscriptions[pathKey]).length <= 0) { - // there are no more subscriptions so we can unwatch - delete this.dbSubscriptions[pathKey]; - } - if (Object.keys(this.dbSubscriptions).length === 0) { - if (this.successListener) { - this.successListener.remove(); - this.successListener = null; - } - if (this.errorListener) { - this.errorListener.remove(); - this.errorListener = null; + this.dbSubscriptions[dbHandle][eventName] = this.dbSubscriptions[dbHandle][eventName].splice(i, 1); + if (this.dbSubscriptions[dbHandle][eventName].length > 0) { + return Promise.resolve(); } } + } else if (eventName) { + this.dbSubscriptions[dbHandle][eventName] = []; } else { - this.log.warn('off() called, but not currently listening at that location (bad path)', pathKey, modifiersString, eventName); - } - - const subscriptions = [this.successListener, this.errorListener]; - const modifierMap = this.dbSubscriptions[path]; - - if (modifierMap && modifierMap[modifiersString] && modifierMap[modifiersString][eventName] && modifierMap[modifiersString][eventName].length > 0) { - return Promise.resolve(subscriptions); + this.dbSubscriptions[dbHandle] = {} } - - return promisify('off', FirestackDatabase)(path, modifiersString, eventName).then(() => { - // subscriptions.forEach(sub => sub.remove()); - // delete this.listeners[eventName]; - return subscriptions; - }); + return promisify('off', FirestackDatabase)(path, modifiersString, eventName); } cleanup() { - let promises = Object.keys(this.refs) - .map(key => this.refs[key]) - .map(ref => ref.cleanup()); + let promises = []; + Object.keys(this.dbSubscriptions).forEach(dbHandle => { + Object.keys(this.dbSubscriptions[dbHandle]).forEach(eventName => { + let separator = dbHandle.indexOf('|'); + let path = dbHandle.substring(0, separator); + let modifiersString = dbHandle.substring(separator + 1); + + promises.push(this.off(path, modifiersString, eventName)) + }) + }) return Promise.all(promises); } - release(...path: Array) { - const key = this._pathKey(...path); - if (this.refs[key]) { - delete this.refs[key]; - } - } - - _pathKey(...path: Array): string { - return path.join('-'); + _dbHandle(path: string = '', modifiersString: string = '') { + return path + '|' + modifiersString; } goOnline() { diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index c47e565..4770fb1 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -12,7 +12,6 @@ import { promisify, isFunction, isObject, tryJSONParse, tryJSONStringify, genera const FirestackDatabase = NativeModules.FirestackDatabase; // https://firebase.google.com/docs/reference/js/firebase.database.Reference -let uid = 0; /** * @class Reference @@ -21,16 +20,13 @@ export default class Reference extends ReferenceBase { db: FirestackDatabase; query: Query; - uid: number; constructor(db: FirestackDatabase, path: Array, existingModifiers?: Array) { super(db.firestack, path); this.db = db; - this.uid = uid += 1; - this.listeners = {}; this.query = new Query(this, path, existingModifiers); - this.log.debug('Created new Reference', this.dbPath(), this.uid); + this.log.debug('Created new Reference', this.db._dbHandle(path, existingModifiers)); } child(...paths: Array) { @@ -42,15 +38,6 @@ export default class Reference extends ReferenceBase { return promisify('keepSynced', FirestackDatabase)(path, bool); } - // Get the value of a ref either with a key - // todo - where is this on the FB JS api - seems just like another random function - get() { - const path = this.dbPath(); - const modifiers = this.query.getModifiers(); - const modifiersString = this.query.getModifiersString(); - return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, 'value'); - } - set(value: any) { const path = this.dbPath(); const _value = this._serializeAnyType(value); @@ -93,49 +80,31 @@ export default class Reference extends ReferenceBase { }); } - on(evt?: string, cb: () => any) { + on(eventName: string, cb: () => any) { const path = this.dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); - this.log.debug('adding reference.on', path, modifiersString, evt); - return this.db.storeRef(this.uid, this).then(() => { - return this.db.on(this.uid, path, modifiersString, modifiers, evt, cb).then((subscriptions) => { - this.listeners[evt] = subscriptions; - }); - }); + this.log.debug('adding reference.on', path, modifiersString, eventName); + return this.db.on(path, modifiersString, modifiers, eventName, cb); } - once(evt?: string = 'once', cb: (snapshot: Object) => void) { + once(eventName: string = 'once', cb: (snapshot: Object) => void) { const path = this.dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); - return this.db.storeRef(this.uid, this).then(() => { - // todo use event emitter - not callbacks - return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, evt) - .then(({ snapshot }) => new Snapshot(this, snapshot)) - .then((snapshot) => { - if (isFunction(cb)) cb(snapshot); - return snapshot; - }); - }); - } - - off(evt: string = '', origCB?: () => any) { - const path = this.dbPath(); - const modifiers = this.query.getModifiers(); - const modifiersString = this.query.getModifiersString(); - this.log.debug('ref.off(): ', path, modifiers, evt); - return this.db.unstoreRef(this.uid).then(() => { - return this.db.off(this.uid, path, modifiersString, modifiers, evt, origCB).then(() => { - // todo urm - whats this? - // delete this.listeners[eventName]; - // this.listeners[evt] = subscriptions; + return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, eventName) + .then(({ snapshot }) => new Snapshot(this, snapshot)) + .then((snapshot) => { + if (isFunction(cb)) cb(snapshot); + return snapshot; }); - }); } - cleanup() { - return Promise.all(Object.keys(this.listeners).map(key => this.off(key))); + off(eventName?: string = '', origCB?: () => any) { + const path = this.dbPath(); + const modifiersString = this.query.getModifiersString(); + this.log.debug('ref.off(): ', path, modifiersString, eventName); + return this.db.off(path, modifiersString, eventName, origCB); } /** From f5218e804438bc1eb060b2ef4370e219087c2400 Mon Sep 17 00:00:00 2001 From: salakar Date: Mon, 19 Dec 2016 20:03:53 +0000 Subject: [PATCH 191/275] cleanup / lint issues --- lib/modules/database/index.js | 221 ++++++++++++++++------------ lib/modules/database/reference.js | 230 ++++++++++++++++++++++-------- 2 files changed, 300 insertions(+), 151 deletions(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index 7faee09..9df1989 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -2,16 +2,14 @@ * @flow * Database representation wrapper */ -'use strict'; import { NativeModules, NativeEventEmitter } from 'react-native'; -const FirestackDatabase = NativeModules.FirestackDatabase; -const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); - -import { promisify } from './../../utils'; - import { Base } from './../base'; -import Reference from './reference.js'; import Snapshot from './snapshot.js'; +import Reference from './reference.js'; +import { promisify, isFunction } from './../../utils'; + +const FirestackDatabase = NativeModules.FirestackDatabase; +const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); /** * @class Database @@ -19,126 +17,124 @@ import Snapshot from './snapshot.js'; export default class Database extends Base { constructor(firestack: Object, options: Object = {}) { super(firestack, options); - this.log.debug('Created new Database instance', this.options); - + this.subscriptions = {}; this.persistenceEnabled = false; - this.successListener = FirestackDatabaseEvt - .addListener( - 'database_event', - event => this.handleDatabaseEvent(event)); - this.errorListener = FirestackDatabaseEvt - .addListener( - 'database_error', - err => this.handleDatabaseError(err)); - - this.dbSubscriptions = {}; + this.namespace = 'firestack:database'; + + this.successListener = FirestackDatabaseEvt.addListener( + 'database_event', + event => this._handleDatabaseEvent(event) + ); + + this.errorListener = FirestackDatabaseEvt.addListener( + 'database_error', + err => this._handleDatabaseError(err) + ); + + this.log.debug('Created new Database instance', this.options); } + /** + * Returns a new firestack reference instance + * @param path + * @returns {Reference} + */ ref(...path: Array) { return new Reference(this, path); } + /** + * Enabled / disable database persistence + * @param enable + * @returns {*} + */ setPersistence(enable: boolean = true) { - let promise; if (this.persistenceEnabled !== enable) { this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); - promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); this.persistenceEnabled = enable; - } else { - promise = this.whenReady(Promise.resolve({ status: 'Already enabled' })) - } - - return promise; - } - - handleDatabaseEvent(event: Object) { - const body = event.body || {}; - const { path, modifiersString, eventName, snapshot } = body; - const dbHandle = this._dbHandle(path, modifiersString); - this.log.debug('handleDatabaseEvent: ', dbHandle, eventName, snapshot && snapshot.key); - - if (this.dbSubscriptions[dbHandle] && this.dbSubscriptions[dbHandle][eventName]) { - this.dbSubscriptions[dbHandle][eventName].forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new Snapshot(this, snapshot); - cb(snap, body); - } - }) - } else { - FirestackDatabase.off(path, modifiersString, eventName, () => { - this.log.debug('handleDatabaseEvent: No JS listener registered, removed native listener', dbHandle, eventName); - }); + return this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); } - } - handleDatabaseError(err: Object) { - this.log.debug('handleDatabaseError ->', err); + return this.whenReady(Promise.resolve({ status: 'Already enabled' })); } + /** + * + * @param path + * @param modifiersString + * @param modifiers + * @param eventName + * @param cb + * @returns {*} + */ on(path: string, modifiersString: string, modifiers: Array, eventName: string, cb: () => void) { - const dbHandle = this._dbHandle(path, modifiersString); - this.log.debug('adding on listener', dbHandle); - - if (this.dbSubscriptions[dbHandle]) { - if (this.dbSubscriptions[dbHandle][eventName]) { - this.dbSubscriptions[dbHandle][eventName].push(cb); - } else { - this.dbSubscriptions[dbHandle][eventName] = [cb]; - } + const handle = this._handle(path, modifiersString); + this.log.debug('adding on listener', handle); + + if (this.subscriptions[handle]) { + if (this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName].push(cb); + else this.subscriptions[handle][eventName] = [cb]; } else { - this.dbSubscriptions[dbHandle] = { - [eventName]: [cb] - } + this.subscriptions[handle] = { [eventName]: [cb] }; } return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, eventName); } + /** + * + * @param path + * @param modifiersString + * @param eventName + * @param origCB + * @returns {*} + */ off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) { - const dbHandle = this._dbHandle(path, modifiersString); - this.log.debug('off() : ', dbHandle, eventName); + const handle = this._handle(path, modifiersString); + this.log.debug('off() : ', handle, eventName); - if (!this.dbSubscriptions[dbHandle] - || (eventName && !this.dbSubscriptions[dbHandle][eventName])) { - this.log.warn('off() called, but not currently listening at that location (bad path)', dbHandle, eventName); + if (!this.subscriptions[handle] || (eventName && !this.subscriptions[handle][eventName])) { + this.log.warn('off() called, but not currently listening at that location (bad path)', handle, eventName); return Promise.resolve(); } if (eventName && origCB) { - const i = this.dbSubscriptions[dbHandle][eventName].indexOf(origCB); + const i = this.subscriptions[handle][eventName].indexOf(origCB); if (i === -1) { - this.log.warn('off() called, but the callback specifed is not listening at that location (bad path)', dbHandle, eventName); + this.log.warn('off() called, but the callback specifed is not listening at that location (bad path)', handle, eventName); + return Promise.resolve(); + } + + this.subscriptions[handle][eventName] = this.subscriptions[handle][eventName].splice(i, 1); + + if (this.subscriptions[handle][eventName].length > 0) { return Promise.resolve(); - } else { - this.dbSubscriptions[dbHandle][eventName] = this.dbSubscriptions[dbHandle][eventName].splice(i, 1); - if (this.dbSubscriptions[dbHandle][eventName].length > 0) { - return Promise.resolve(); - } } } else if (eventName) { - this.dbSubscriptions[dbHandle][eventName] = []; + this.subscriptions[handle][eventName] = []; } else { - this.dbSubscriptions[dbHandle] = {} + this.subscriptions[handle] = {}; } + return promisify('off', FirestackDatabase)(path, modifiersString, eventName); } + /** + * Removes all event handlers and their native subscriptions + * @returns {Promise.<*>} + */ cleanup() { - let promises = []; - Object.keys(this.dbSubscriptions).forEach(dbHandle => { - Object.keys(this.dbSubscriptions[dbHandle]).forEach(eventName => { - let separator = dbHandle.indexOf('|'); - let path = dbHandle.substring(0, separator); - let modifiersString = dbHandle.substring(separator + 1); - - promises.push(this.off(path, modifiersString, eventName)) - }) - }) - return Promise.all(promises); - } + const promises = []; + Object.keys(this.subscriptions).forEach((handle) => { + Object.keys(this.subscriptions[handle]).forEach((eventName) => { + const separator = handle.indexOf('|'); + const path = handle.substring(0, separator); + const modifiersString = handle.substring(separator + 1); + promises.push(this.off(path, modifiersString, eventName)); + }); + }); - _dbHandle(path: string = '', modifiersString: string = '') { - return path + '|' + modifiersString; + return Promise.all(promises); } goOnline() { @@ -149,7 +145,52 @@ export default class Database extends Base { FirestackDatabase.goOffline(); } - get namespace(): string { - return 'firestack:database'; + /** + * INTERNALS + */ + + + /** + * + * @param path + * @param modifiersString + * @returns {string} + * @private + */ + _dbHandle(path: string = '', modifiersString: string = '') { + return `${path}|${modifiersString}`; + } + + + /** + * + * @param event + * @private + */ + _handleDatabaseEvent(event: Object) { + const body = event.body || {}; + const { path, modifiersString, eventName, snapshot } = body; + const handle = this._handle(path, modifiersString); + + this.log.debug('_handleDatabaseEvent: ', handle, eventName, snapshot && snapshot.key); + + if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) { + this.subscriptions[handle][eventName].forEach((cb) => { + if (isFunction(cb)) cb(new Snapshot(this, snapshot), body); + }); + } else { + FirestackDatabase.off(path, modifiersString, eventName, () => { + this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', handle, eventName); + }); + } + } + + /** + * + * @param err + * @private + */ + _handleDatabaseError(err: Object) { + this.log.debug('_handleDatabaseError ->', err); } } diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index 4770fb1..b7b4166 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -23,35 +23,50 @@ export default class Reference extends ReferenceBase { constructor(db: FirestackDatabase, path: Array, existingModifiers?: Array) { super(db.firestack, path); - this.db = db; + this.namespace = 'firestack:db:ref'; this.query = new Query(this, path, existingModifiers); this.log.debug('Created new Reference', this.db._dbHandle(path, existingModifiers)); } - child(...paths: Array) { - return new Reference(this.db, this.path.concat(paths)); - } - + /** + * + * @param bool + * @returns {*} + */ keepSynced(bool: boolean) { - const path = this.dbPath(); + const path = this._dbPath(); return promisify('keepSynced', FirestackDatabase)(path, bool); } + /** + * + * @param value + * @returns {*} + */ set(value: any) { - const path = this.dbPath(); + const path = this._dbPath(); const _value = this._serializeAnyType(value); return promisify('set', FirestackDatabase)(path, _value); } + /** + * + * @param val + * @returns {*} + */ update(val: Object) { - const path = this.dbPath(); + const path = this._dbPath(); const value = this._serializeObject(val); return promisify('update', FirestackDatabase)(path, value); } + /** + * + * @returns {*} + */ remove() { - return promisify('remove', FirestackDatabase)(this.dbPath()); + return promisify('remove', FirestackDatabase)(this._dbPath()); } /** @@ -67,7 +82,7 @@ export default class Reference extends ReferenceBase { return new Reference(this.db, _paths); } - const path = this.dbPath(); + const path = this._dbPath(); const _value = this._serializeAnyType(value); return promisify('push', FirestackDatabase)(path, _value) .then(({ ref }) => { @@ -81,7 +96,7 @@ export default class Reference extends ReferenceBase { } on(eventName: string, cb: () => any) { - const path = this.dbPath(); + const path = this._dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); this.log.debug('adding reference.on', path, modifiersString, eventName); @@ -89,7 +104,7 @@ export default class Reference extends ReferenceBase { } once(eventName: string = 'once', cb: (snapshot: Object) => void) { - const path = this.dbPath(); + const path = this._dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); return promisify('onOnce', FirestackDatabase)(path, modifiersString, modifiers, eventName) @@ -101,145 +116,238 @@ export default class Reference extends ReferenceBase { } off(eventName?: string = '', origCB?: () => any) { - const path = this.dbPath(); + const path = this._dbPath(); const modifiersString = this.query.getModifiersString(); this.log.debug('ref.off(): ', path, modifiersString, eventName); return this.db.off(path, modifiersString, eventName, origCB); } /** - * - * @param obj - * @returns {Object} - * @private + * MODIFIERS */ - _serializeObject(obj: Object) { - if (!isObject(obj)) return obj; - - // json stringify then parse it calls toString on Objects / Classes - // that support it i.e new Date() becomes a ISO string. - return tryJSONParse(tryJSONStringify(obj)); - } /** * - * @param value - * @returns {*} - * @private + * @returns {Reference} */ - _serializeAnyType(value: any) { - if (isObject(value)) { - return { - type: 'object', - value: this._serializeObject(value), - }; - } - - return { - type: typeof value, - value, - }; - } - - // Modifiers orderByKey(): Reference { return this.orderBy('orderByKey'); } + /** + * + * @returns {Reference} + */ orderByPriority(): Reference { return this.orderBy('orderByPriority'); } + /** + * + * @returns {Reference} + */ orderByValue(): Reference { return this.orderBy('orderByValue'); } + /** + * + * @param key + * @returns {Reference} + */ orderByChild(key: string): Reference { return this.orderBy('orderByChild', key); } + /** + * + * @param name + * @param key + * @returns {Reference} + */ orderBy(name: string, key?: string): Reference { const newRef = new Reference(this.db, this.path, this.query.getModifiers()); newRef.query.setOrderBy(name, key); return newRef; } - // Limits + /** + * LIMITS + */ + + /** + * + * @param limit + * @returns {Reference} + */ limitToLast(limit: number): Reference { return this.limit('limitToLast', limit); } + /** + * + * @param limit + * @returns {Reference} + */ limitToFirst(limit: number): Reference { return this.limit('limitToFirst', limit); } + /** + * + * @param name + * @param limit + * @returns {Reference} + */ limit(name: string, limit: number): Reference { const newRef = new Reference(this.db, this.path, this.query.getModifiers()); newRef.query.setLimit(name, limit); return newRef; } - // Filters + /** + * FILTERS + */ + + /** + * + * @param value + * @param key + * @returns {Reference} + */ equalTo(value: any, key?: string): Reference { return this.filter('equalTo', value, key); } + /** + * + * @param value + * @param key + * @returns {Reference} + */ endAt(value: any, key?: string): Reference { return this.filter('endAt', value, key); } + /** + * + * @param value + * @param key + * @returns {Reference} + */ startAt(value: any, key?: string): Reference { return this.filter('startAt', value, key); } + /** + * + * @param name + * @param value + * @param key + * @returns {Reference} + */ filter(name: string, value: any, key?: string): Reference { const newRef = new Reference(this.db, this.path, this.query.getModifiers()); newRef.query.setFilter(name, value, key); return newRef; } + // TODO why is this presence here on DB ref? its unrelated? presence(path: string) { const presence = this.firestack.presence; const ref = path ? this.child(path) : this; - return presence.ref(ref, this.dbPath()); + return presence.ref(ref, this._dbPath()); } - // onDisconnect onDisconnect() { return new Disconnect(this); } - // attributes - toString(): string { - return this.dbPath(); + child(...paths: Array) { + return new Reference(this.db, this.path.concat(paths)); } - dbPath(paths?: Array): string { - const path = paths || this.path; - const pathStr = (path.length > 0 ? path.join('/') : '/'); - - if (pathStr[0] !== '/') { - return `/${pathStr}`; - } - - return pathStr; + toString(): string { + return this._dbPath(); } - get namespace(): string { - return 'firestack:dbRef'; - } + /** + * GETTERS + */ + /** + * Returns the current key of this ref - i.e. /foo/bar returns 'bar' + * @returns {*} + */ get key(): string|null { if (!this.path.length) return null; return this.path.slice(this.path.length - 1, this.path.length)[0]; } + /** + * Returns the parent ref of the current ref i.e. a ref of /foo/bar would return a new ref to '/foo' + * @returns {*} + */ get parent(): Reference|null { if (!this.path.length || this.path.length === 1) return null; return new Reference(this.db, this.path.slice(0, -1)); } + + /** + * Returns a ref to the root of db - '/' + * @returns {Reference} + */ get root(): Reference { return new Reference(this.db, []); } + + /** + * INTERNALS + */ + + _dbPath(paths?: Array): string { + const path = paths || this.path; + const pathStr = (path.length > 0 ? path.join('/') : '/'); + + if (pathStr[0] !== '/') { + return `/${pathStr}`; + } + + return pathStr; + } + + /** + * + * @param obj + * @returns {Object} + * @private + */ + _serializeObject(obj: Object) { + if (!isObject(obj)) return obj; + + // json stringify then parse it calls toString on Objects / Classes + // that support it i.e new Date() becomes a ISO string. + return tryJSONParse(tryJSONStringify(obj)); + } + + /** + * + * @param value + * @returns {*} + * @private + */ + _serializeAnyType(value: any) { + if (isObject(value)) { + return { + type: 'object', + value: this._serializeObject(value), + }; + } + + return { + type: typeof value, + value, + }; + } } From e51fd5ecf3f1bdaac591e4f98940473a242485fc Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Tue, 20 Dec 2016 09:36:12 +0000 Subject: [PATCH 192/275] Update index.js derp fix --- lib/modules/database/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index 9df1989..e6060a6 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -157,7 +157,7 @@ export default class Database extends Base { * @returns {string} * @private */ - _dbHandle(path: string = '', modifiersString: string = '') { + _handle(path: string = '', modifiersString: string = '') { return `${path}|${modifiersString}`; } From fa96855948768f4450110741e8a0341856fd7c5e Mon Sep 17 00:00:00 2001 From: Michael Diarmid Date: Tue, 20 Dec 2016 09:53:32 +0000 Subject: [PATCH 193/275] Update reference.js fix another derp --- lib/modules/database/reference.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index b7b4166..7a0c6a7 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -26,7 +26,7 @@ export default class Reference extends ReferenceBase { this.db = db; this.namespace = 'firestack:db:ref'; this.query = new Query(this, path, existingModifiers); - this.log.debug('Created new Reference', this.db._dbHandle(path, existingModifiers)); + this.log.debug('Created new Reference', this.db._handle(path, existingModifiers)); } /** From a1451c4bf7fe2e5f2421664a32bf2eaedfe27aae Mon Sep 17 00:00:00 2001 From: Chris Bianca Date: Tue, 20 Dec 2016 09:59:59 +0000 Subject: [PATCH 194/275] Throw error if callback isn't a function rather than check each time an event is received --- lib/modules/database/index.js | 2 +- lib/modules/database/reference.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index e6060a6..bc61666 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -176,7 +176,7 @@ export default class Database extends Base { if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) { this.subscriptions[handle][eventName].forEach((cb) => { - if (isFunction(cb)) cb(new Snapshot(this, snapshot), body); + cb(new Snapshot(this, snapshot), body); }); } else { FirestackDatabase.off(path, modifiersString, eventName, () => { diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index 7a0c6a7..ec0f2ea 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -96,6 +96,7 @@ export default class Reference extends ReferenceBase { } on(eventName: string, cb: () => any) { + if (!isFunction(cb)) throw new Error('The specified callback must be a function'); const path = this._dbPath(); const modifiers = this.query.getModifiers(); const modifiersString = this.query.getModifiersString(); From 35c3e277eb4b46a09a05896cbe1a7c3350828859 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 11:42:06 +0000 Subject: [PATCH 195/275] misc snapshot linting --- lib/modules/database/snapshot.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/modules/database/snapshot.js b/lib/modules/database/snapshot.js index c1f2d8c..d887daa 100644 --- a/lib/modules/database/snapshot.js +++ b/lib/modules/database/snapshot.js @@ -4,12 +4,12 @@ import Reference from './reference.js'; export default class Snapshot { - static key:String; - static value:Object; - static exists:boolean; - static hasChildren:boolean; - static childrenCount:Number; - static childKeys:String[]; + static key: String; + static value: Object; + static exists: boolean; + static hasChildren: boolean; + static childrenCount: Number; + static childKeys: String[]; ref: Object; key: string; @@ -21,8 +21,8 @@ export default class Snapshot { childKeys: Array; constructor(ref: Reference, snapshot: Object) { - this.ref = ref; - this.key = snapshot.key; + this.ref = ref; + this.key = snapshot.key; this.value = snapshot.value; this.exists = snapshot.exists || true; this.priority = snapshot.priority; @@ -41,7 +41,7 @@ export default class Snapshot { } map(fn: (key: string) => mixed) { - let arr = []; + const arr = []; this.forEach(item => arr.push(fn(item))); return arr; } From 2aac6bc2cd4b9f109aec91c097f4a7a70d09c15c Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 11:42:37 +0000 Subject: [PATCH 196/275] remove namespace from base, conflicts on class extension --- lib/modules/base.js | 4 ---- lib/modules/database/index.js | 19 +++++++------------ 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/modules/base.js b/lib/modules/base.js index 0ce3719..1704fe4 100644 --- a/lib/modules/base.js +++ b/lib/modules/base.js @@ -62,10 +62,6 @@ export class Base extends EventEmitter { }); } - get namespace(): string { - return 'firestack:base'; - } - // Event handlers // proxy to firestack instance _on(name, cb, nativeModule) { diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index bc61666..bd2e711 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -71,12 +71,9 @@ export default class Database extends Base { const handle = this._handle(path, modifiersString); this.log.debug('adding on listener', handle); - if (this.subscriptions[handle]) { - if (this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName].push(cb); - else this.subscriptions[handle][eventName] = [cb]; - } else { - this.subscriptions[handle] = { [eventName]: [cb] }; - } + if (!this.subscriptions[handle]) this.subscriptions[handle] = {}; + if (!this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName] = []; + this.subscriptions[handle][eventName].push(cb); return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, eventName); } @@ -100,16 +97,14 @@ export default class Database extends Base { if (eventName && origCB) { const i = this.subscriptions[handle][eventName].indexOf(origCB); + if (i === -1) { - this.log.warn('off() called, but the callback specifed is not listening at that location (bad path)', handle, eventName); + this.log.warn('off() called, but the callback specified is not listening at that location (bad path)', handle, eventName); return Promise.resolve(); } - this.subscriptions[handle][eventName] = this.subscriptions[handle][eventName].splice(i, 1); - - if (this.subscriptions[handle][eventName].length > 0) { - return Promise.resolve(); - } + this.subscriptions[handle][eventName].splice(i, 1); + if (this.subscriptions[handle][eventName].length > 0) return Promise.resolve(); } else if (eventName) { this.subscriptions[handle][eventName] = []; } else { From 74b233aba5105f74166ac7cd444177eb01b05d78 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 12:48:55 +0000 Subject: [PATCH 197/275] fixed incorrect ref.child() api, was expecting an array, api should expect a path string that is then split by '/' --- lib/modules/database/reference.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/database/reference.js b/lib/modules/database/reference.js index ec0f2ea..3a87804 100644 --- a/lib/modules/database/reference.js +++ b/lib/modules/database/reference.js @@ -264,8 +264,8 @@ export default class Reference extends ReferenceBase { return new Disconnect(this); } - child(...paths: Array) { - return new Reference(this.db, this.path.concat(paths)); + child(path: string) { + return new Reference(this.db, this.path.concat(path.split('/'))); } toString(): string { From 12b1f4fad93f27dbeea3dbc0d50bc55434d64e90 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 12:49:21 +0000 Subject: [PATCH 198/275] added deepGet util from my `deeps` lib. --- lib/utils/index.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/utils/index.js b/lib/utils/index.js index cfcaa38..d1e9ae4 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1,6 +1,6 @@ // modeled after base64 web-safe chars, but ordered by ASCII const PUSH_CHARS = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz'; - +const hasOwnProperty = Object.hasOwnProperty; const DEFAULT_CHUNK_SIZE = 50; // internal promise handler @@ -12,6 +12,30 @@ const _handler = (resolve, reject, err, resp) => { }); }; +/** + * Deep get a value from an object. + * @website https://github.com/Salakar/deeps + * @param object + * @param path + * @param joiner + * @returns {*} + */ +export function deepGet(object, path, joiner = '/') { + const keys = path.split(joiner); + + let i = 0; + let tmp = object; + const len = keys.length; + + while (i < len) { + const key = keys[i += 1]; + if (!tmp || !hasOwnProperty.call(tmp, key)) return null; + tmp = tmp[key]; + } + + return tmp; +} + /** * Simple is object check. * @param item From 57df0780d75497ac02720451247cd22ae1cfdda2 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 12:58:44 +0000 Subject: [PATCH 199/275] added missing DataSnapshot methods - child, exists, getPriority, hasChild, hasChildren, numChildren --- lib/modules/database/snapshot.js | 51 ++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/modules/database/snapshot.js b/lib/modules/database/snapshot.js index d887daa..0b89e1b 100644 --- a/lib/modules/database/snapshot.js +++ b/lib/modules/database/snapshot.js @@ -2,13 +2,13 @@ * @flow */ import Reference from './reference.js'; +import { isObject, deepGet } from './../../utils'; export default class Snapshot { static key: String; static value: Object; static exists: boolean; static hasChildren: boolean; - static childrenCount: Number; static childKeys: String[]; ref: Object; @@ -16,8 +16,6 @@ export default class Snapshot { value: any; exists: boolean; priority: any; - hasChildren: boolean; - childrenCount: number; childKeys: Array; constructor(ref: Reference, snapshot: Object) { @@ -25,24 +23,59 @@ export default class Snapshot { this.key = snapshot.key; this.value = snapshot.value; this.exists = snapshot.exists || true; - this.priority = snapshot.priority; - this.hasChildren = snapshot.hasChildren || false; - this.childrenCount = snapshot.childrenCount || 0; + this.priority = snapshot.priority === undefined ? null : snapshot.priority; this.childKeys = snapshot.childKeys || []; } + /* + * DEFAULT API METHODS + */ val() { return this.value; } + child(path: string) { + const value = deepGet(this.value, path); + const childRef = this.ref.child(path); + return new Snapshot(childRef, { + value, + key: childRef.key, + exists: value !== null, + childKeys: isObject(value) ? Object.keys(value) : [], + }); + } + + exists() { + return this.value !== null; + } + forEach(fn: (key: any) => any) { - (this.childKeys || []) - .forEach(key => fn(this.value[key])); + (this.childKeys || []).forEach((key, i) => fn(this.value[key], i)); + } + + getPriority() { + return this.priority; + } + + hasChild(key: string) { + return this.childKeys.includes(key); + } + + hasChildren() { + return this.numChildren() > 0; + } + + numChildren() { + if (!isObject(this.value)) return 0; + return Object.keys(this.value).length; } + /* + * EXTRA API METHODS + */ map(fn: (key: string) => mixed) { const arr = []; - this.forEach(item => arr.push(fn(item))); + this.forEach((item, i) => arr.push(fn(item, i))); return arr; } From 7b9c2d407b05cf47c4a0411a78ad38e9061cc7e3 Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 13:05:53 +0000 Subject: [PATCH 200/275] added deepExists util --- lib/utils/index.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/utils/index.js b/lib/utils/index.js index d1e9ae4..54bd80d 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -36,6 +36,30 @@ export function deepGet(object, path, joiner = '/') { return tmp; } +/** + * Deep check if a key exists. + * @website https://github.com/Salakar/deeps + * @param object + * @param path + * @param joiner + * @returns {*} + */ +export function deepExists(object, path, joiner = '/') { + const keys = path.split(joiner); + + let i = 0; + let tmp = object; + const len = keys.length; + + while (i < len) { + const key = keys[i += 1]; + if (!tmp || !hasOwnProperty.call(tmp, key)) return false; + tmp = tmp[key]; + } + + return tmp !== undefined; +} + /** * Simple is object check. * @param item From 5d81ea0b5356d1fc6c151dbbee2d1703bea0c2be Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 13:07:00 +0000 Subject: [PATCH 201/275] `snapshot.hasChild(path)` now supports deep checks i.e `snapshot.hasChild('foo/bar/baz')` --- lib/modules/database/snapshot.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/modules/database/snapshot.js b/lib/modules/database/snapshot.js index 0b89e1b..f963bf9 100644 --- a/lib/modules/database/snapshot.js +++ b/lib/modules/database/snapshot.js @@ -2,7 +2,7 @@ * @flow */ import Reference from './reference.js'; -import { isObject, deepGet } from './../../utils'; +import { isObject, deepGet, deepExists } from './../../utils'; export default class Snapshot { static key: String; @@ -26,6 +26,7 @@ export default class Snapshot { this.priority = snapshot.priority === undefined ? null : snapshot.priority; this.childKeys = snapshot.childKeys || []; } + /* * DEFAULT API METHODS */ @@ -50,15 +51,15 @@ export default class Snapshot { } forEach(fn: (key: any) => any) { - (this.childKeys || []).forEach((key, i) => fn(this.value[key], i)); + return this.childKeys.forEach((key, i) => fn(this.value[key], i)); } getPriority() { return this.priority; } - hasChild(key: string) { - return this.childKeys.includes(key); + hasChild(path: string) { + return deepExists(this.value, path); } hasChildren() { From 569be66973902589a51438df96bce8cca93832be Mon Sep 17 00:00:00 2001 From: salakar Date: Tue, 20 Dec 2016 13:23:54 +0000 Subject: [PATCH 202/275] Fixes #185 - snapshot incorrect receiving db instance instead of the ref --- lib/modules/database/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/database/index.js b/lib/modules/database/index.js index bd2e711..fab272e 100644 --- a/lib/modules/database/index.js +++ b/lib/modules/database/index.js @@ -171,7 +171,7 @@ export default class Database extends Base { if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) { this.subscriptions[handle][eventName].forEach((cb) => { - cb(new Snapshot(this, snapshot), body); + cb(new Snapshot(new Reference(this, path.split('/'), modifiersString.split('|')), snapshot), body); }); } else { FirestackDatabase.off(path, modifiersString, eventName, () => { From 4ab951b0306a1b251d90c034607f7913f2b280d6 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Tue, 20 Dec 2016 14:02:52 +0000 Subject: [PATCH 203/275] Update database.md --- docs/api/database.md | 181 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/docs/api/database.md b/docs/api/database.md index 8b13789..54594f1 100644 --- a/docs/api/database.md +++ b/docs/api/database.md @@ -1 +1,182 @@ +# Realtime Database + +Firestack mimics the [Web Firebase SDK Realtime Database](https://firebase.google.com/docs/database/web/read-and-write), whilst +providing support for devices in low/no data connection state. + +All Realtime Database operations are accessed via `database()`. + +Basic read example: +```javascript +firestack.database() + .ref('posts') + .on('value', (snapshot) => { + const value = snapshot.val(); + }); +``` + +Basic write example: +```javascript +firestack.database() + .ref('posts/1234') + .set({ + title: 'My awesome post', + content: 'Some awesome content', + }); +``` + +## Unmounted components + +Listening to database updates on unmounted components will trigger a warning: + +> Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component. + +It is important to always unsubscribe the reference from receiving new updates once the component is no longer in use. +This can be achived easily using [Reacts Component Lifecycle](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle) events: + +Always ensure the handler function provided is of the same reference so Firestack can unsubscribe the ref listener. + +```javascript +class MyComponent extends Component { + constructor() { + super(); + this.ref = null; + } + + // On mount, subscribe to ref updates + componentDidMount() { + this.ref = firestack.database().ref('posts/1234'); + this.ref.on('value', this.handlePostUpdate); + } + + // On unmount, ensure we no longer listen for updates + componentWillUnmount() { + if (this.ref) { + this.ref.off('value', this.handlePostUpdate); + } + } + + // Bind the method only once to keep the same reference + handlePostUpdate = (snapshot) => { + console.log('Post Content', snapshot.val()); + } + + render() { + return null; + } +} + +``` + +## Usage in offline environments + +### Reading data + +Firstack allows the database instance to [persist on disk](https://firebase.google.com/docs/database/android/offline-capabilities) if enabled. +To enable database persistence, call the following method before calls are made: + +```javascript +firestack.database().setPersistence(true); +``` + +Any subsequent calls to Firebase stores the data for the ref on disk. + +### Writing data + +Out of the box, Firebase has great support for writing operations in offline environments. Calling a write command whilst offline +will always trigger any subscribed refs with new data. Once the device reconnects to Firebase, it will be synced with the server. + +The following todo code snippet will work in both online and offline environments: + +```javascript +// Assume the todos are stored as an object value on Firebase as: +// { name: string, complete: boolean } + +class ToDos extends Component { + constructor() { + super(); + this.ref = null; + this.listView = new ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2, + }); + + this.state = { + todos: this.listView.cloneWithRows({}), + }; + + // Keep a local reference of the TODO items + this.todos = {}; + } + + // Load the Todos on mount + componentDidMount() { + this.ref = firestack.database().ref('users/1234/todos'); + this.ref.on('value', this.handleToDoUpdate); + } + + // Unsubscribe from the todos on unmount + componentWillUnmount() { + if (this.ref) { + this.ref.off('value', this.handleToDoUpdate); + } + } + + // Handle ToDo updates + handleToDoUpdate = (snapshot) => { + this.todos = snapshot.val() || {}; + + this.setState({ + todos: this.listView.cloneWithRows(this.todos), + }); + } + + // Add a new ToDo onto Firebase + // If offline, this will still trigger an update to handleToDoUpdate + addToDo() { + firestack.database() + .ref('users/1234/todos') + .set({ + ...this.todos, { + name: 'Yet another todo...', + complete: false, + }, + }); + } + + // Render a ToDo row + renderToDo(todo) { + // Dont render the todo if its complete + if (todo.complete) { + return null; + } + + return ( + + {todo.name} + + ); + } + + // Render the list of ToDos with a Button + render() { + return ( + + this.renderToDo(...args)} + /> + +