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/.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/.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/.flowconfig b/.flowconfig new file mode 100644 index 0000000..f43b490 --- /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] +lib/flow.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 diff --git a/.gitignore b/.gitignore index 1347dc0..b5f5b47 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 @@ -51,8 +52,10 @@ android/*.iml ehthumbs.db Thumbs.dbandroid/gradle android/gradlew +android/build android/gradlew.bat android/gradle/ .idea .idea coverage +yarn.lock 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/Firestack.podspec b/Firestack.podspec index ac45018..b569fe1 100644 --- a/Firestack.podspec +++ b/Firestack.podspec @@ -1,61 +1,28 @@ require 'json' -package = JSON.parse(File.read('package.json')) -version = package["version"] -repo = package['repository'] -author = package['author'] -all_pods = [ - 'FirebaseAnalytics', 'FirebaseAuth', 'FirebaseRemoteConfig', - 'FirebaseDatabase', 'FirebaseStorage', 'FirebaseInstanceID', - 'GoogleInterchangeUtilities', 'GoogleIPhoneUtilities', - 'GoogleNetworkingUtilities', 'GoogleParsingUtilities', - 'GoogleSymbolUtilities' -] +package = JSON.parse(File.read('package.json')) Pod::Spec.new do |s| - - s.name = "Firestack" - s.version = version - s.summary = "Firestack makes working with Firebase v3 easy" - - s.description = <<-DESC - Wanna integrate firebase into your app using React Native? - DESC - - s.homepage = "http://fullstackreact.com" - - s.license = { :type => "MIT", :file => "LICENSE" } - s.author = { "Ari Lerner" => author } - s.social_media_url = 'http://twitter.com/fullstackio' - - # When using multiple platforms - s.ios.deployment_target = "8.0" - # s.osx.deployment_target = "10.7" - # s.watchos.deployment_target = "2.0" - # s.tvos.deployment_target = "9.0" - - s.source = { :git => repo['url'], :tag => "v#{version}" } - s.public_header_files = "ios/Firestack/*.h" - - s.source_files = 'ios/Firestack/*.{h,m}' - s.preserve_paths = 'README.md', 'package.json', '*.js' - - s.ios.frameworks = [ - 'CFNetwork', 'Security', 'SystemConfiguration' - ] - s.ios.libraries = ['icucore', 'c++', 'sqlite3', 'z'] - - s.xcconfig = { - 'HEADER_SEARCH_PATHS' => [ - "$(inherited)", - "${SRCROOT}/../../React/**", - "${SRCROOT}/../../node_modules/react-native/**" - ].join(' '), - 'FRAMEWORK_SEARCH_PATHS' => [ - "$(inherited)", - "${PODS_ROOT}/Firebase/**", - "${PODS_ROOT}/FirebaseStorage/**", - ].join(' '), - 'OTHER_LDFLAGS' => '$(inherited) -ObjC' - } -end \ No newline at end of file + s.name = "Firestack" + s.version = package["version"] + s.summary = package["description"] + s.description = <<-DESC + Wanna integrate firebase into your app using React Native? + DESC + s.homepage = "http://fullstackreact.com" + s.license = package['license'] + s.author = "Ari Lerner" + s.source = { :git => "https://github.com/fullstackreact/react-native-firestack.git", :tag => "v#{s.version}" } + s.social_media_url = 'http://twitter.com/fullstackio' + s.platform = :ios, "8.0" + s.header_dir = 'ios/Firestack' + s.preserve_paths = 'README.md', 'package.json', '*.js' + s.source_files = 'ios/Firestack/*.{h,m}' + s.dependency 'React' + s.dependency 'Firebase/Auth' + s.dependency 'Firebase/Core' + s.dependency 'Firebase/Database' + s.dependency 'Firebase/Messaging' + s.dependency 'Firebase/RemoteConfig' + s.dependency 'Firebase/Storage' +end diff --git a/README.md b/README.md index 84c3333..6ed926f 100644 --- a/README.md +++ b/README.md @@ -1,903 +1,67 @@ -## Firestack +# 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. - -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 - -* 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 - -## 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!')) +npm i react-native-firestack --save ``` -#### 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. +[![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) -## Running with the `master` branch +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. -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. +Featuring; authentication, storage, real-time database, presence, analytics, cloud messaging, remote configuration, redux support and more! -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: +## Firestack vs Firebase JS lib -```bash -npm install --save react-native-firestack -``` +Although the [Firebase](https://www.npmjs.com/package/firebase) JavaScript library will work with React Native, it is mainly designed for the web. -After the `npm` version is installed, you can either clone the repo directly into our `node_modules/` directory: +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. -```bash -git clone https://github.com/fullstackreact/react-native-firestack.git ./node_modules/react-native-firestack -``` +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). -Alternatively, you can clone the repo somewhere else and `rsync` the directory over to the `node_modules/` directory. +## Example app -> This is the method I use as it allows me to separate the codebases: +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. -```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/ -``` +## Documentation + +* Installation + * [iOS](docs/installation.ios.md) + * [Android](docs/installation.android.md) +* [Firebase Setup](docs/firebase-setup.md) +* API + * [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) + * [Remote Config](docs/api/remote-config.md) + * [Events](docs/api/events.md) +* [Redux](docs/redux.md) + +## Feature overview +Feature | Firestack | Firebase +------------ | ------------- | ------------- +Real time database | -- | YES +Authentication | -- | YES +Cloud messaging | -- | YES +Storage | -- | YES +Crash reporting | -- | YES +Notifications | -- | YES +Remote config | -- | YES +Dynamic links | -- | YES +AdMob | -- | YES +Analytics | -- | YES +App indexing | -- | YES +Hosting | -- | YES +AdWords | -- | YES +Invites | -- | YES ## 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 +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). diff --git a/android/build.gradle b/android/build.gradle index a06d876..f0c0545 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,15 +31,26 @@ 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.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' - compile 'com.google.firebase:firebase-database:9.8.0' - compile 'com.google.firebase:firebase-storage:9.8.0' - compile 'com.google.firebase:firebase-messaging:9.8.0' + compile 'com.google.android.gms:play-services-base:+' + compile 'com.google.firebase:firebase-core:10.0.1' + compile 'com.google.firebase:firebase-config:10.0.1' + compile 'com.google.firebase:firebase-auth:10.0.1' + compile 'com.google.firebase:firebase-analytics:10.0.1' + compile 'com.google.firebase:firebase-database:10.0.1' + compile 'com.google.firebase:firebase-storage:10.0.1' + compile 'com.google.firebase:firebase-messaging:10.0.1' } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java deleted file mode 100644 index ec67022..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ /dev/null @@ -1,226 +0,0 @@ -package io.fullstack.firestack; - -import android.content.Context; -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 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.ReadableMap; -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.analytics.FirebaseAnalytics; -import com.google.firebase.analytics.FirebaseAnalytics.Event.*; -import com.google.firebase.analytics.FirebaseAnalytics.Param; - -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); - } - - @Override - public String getName() { - return TAG; - } - - @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); - final Bundle bundle = makeEventBundle(name, m); - Log.d(TAG, "Logging event " + eventName); - 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; - } - - private Bundle makeEventBundle(final String name, final Map map) { - Bundle bundle = new Bundle(); - // Available from the Analytics event - if (map.containsKey("id")) { - String id = (String) map.get("id"); - bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id); - } - if (map.containsKey("name")) { - String val = (String) map.get("name"); - bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); - } - if (map.containsKey("category")) { - String val = (String) map.get("category"); - bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); - } - if (map.containsKey("quantity")) { - long val = (long) map.get("quantity"); - bundle.putLong(FirebaseAnalytics.Param.QUANTITY, val); - } - if (map.containsKey("price")) { - long val = (long) map.get("price"); - bundle.putLong(FirebaseAnalytics.Param.PRICE, val); - } - if (map.containsKey("value")) { - long val = (long) map.get("value"); - bundle.putLong(FirebaseAnalytics.Param.VALUE, val); - } - if (map.containsKey("currency")) { - String val = (String) map.get("currency"); - bundle.putString(FirebaseAnalytics.Param.CURRENCY, val); - } - if (map.containsKey("origin")) { - String val = (String) map.get("origin"); - bundle.putString(FirebaseAnalytics.Param.ORIGIN, val); - } - if (map.containsKey("item_location_id")) { - String val = (String) map.get("item_location_id"); - bundle.putString(FirebaseAnalytics.Param.ITEM_LOCATION_ID, val); - } - if (map.containsKey("location")) { - String val = (String) map.get("location"); - bundle.putString(FirebaseAnalytics.Param.LOCATION, val); - } - if (map.containsKey("destination")) { - String val = (String) map.get("destination"); - bundle.putString(FirebaseAnalytics.Param.DESTINATION, val); - } - if (map.containsKey("start_date")) { - String val = (String) map.get("start_date"); - bundle.putString(FirebaseAnalytics.Param.START_DATE, val); - } - if (map.containsKey("end_date")) { - String val = (String) map.get("end_date"); - bundle.putString(FirebaseAnalytics.Param.END_DATE, val); - } - if (map.containsKey("transaction_id")) { - String val = (String) map.get("transaction_id"); - bundle.putString(FirebaseAnalytics.Param.TRANSACTION_ID, val); - } - if (map.containsKey("number_of_nights")) { - long val = (long) map.get("number_of_nights"); - bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_NIGHTS, val); - } - if (map.containsKey("number_of_rooms")) { - long val = (long) map.get("number_of_rooms"); - bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_ROOMS, val); - } - if (map.containsKey("number_of_passengers")) { - long val = (long) map.get("number_of_passengers"); - bundle.putLong(FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS, val); - } - if (map.containsKey("travel_class")) { - String val = (String) map.get("travel_class"); - bundle.putString(FirebaseAnalytics.Param.TRAVEL_CLASS, val); - } - if (map.containsKey("coupon")) { - String val = (String) map.get("coupon"); - bundle.putString(FirebaseAnalytics.Param.COUPON, val); - } - if (map.containsKey("tax")) { - long val = (long) map.get("tax"); - bundle.putLong(FirebaseAnalytics.Param.TAX, val); - } - if (map.containsKey("shipping")) { - double val = (double) map.get("shipping"); - bundle.putDouble(FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS, val); - } - if (map.containsKey("group_id")) { - String val = (String) map.get("group_id"); - bundle.putString(FirebaseAnalytics.Param.GROUP_ID, val); - } - if (map.containsKey("level")) { - long val = (long) map.get("level"); - bundle.putLong(FirebaseAnalytics.Param.LEVEL, val); - } - if (map.containsKey("character")) { - String val = (String) map.get("character"); - bundle.putString(FirebaseAnalytics.Param.CHARACTER, val); - } - if (map.containsKey("score")) { - long val = (long) map.get("score"); - bundle.putLong(FirebaseAnalytics.Param.SCORE, val); - } - if (map.containsKey("search_term")) { - String val = (String) map.get("search_term"); - bundle.putString(FirebaseAnalytics.Param.SEARCH_TERM, val); - } - if (map.containsKey("content_type")) { - String val = (String) map.get("content_type"); - bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, val); - } - if (map.containsKey("sign_up_method")) { - String val = (String) map.get("sign_up_method"); - bundle.putString(FirebaseAnalytics.Param.SIGN_UP_METHOD, val); - } - if (map.containsKey("virtual_currency_name")) { - String val = (String) map.get("virtual_currency_name"); - bundle.putString(FirebaseAnalytics.Param.VIRTUAL_CURRENCY_NAME, val); - } - if (map.containsKey("achievement_id")) { - String val = (String) map.get("achievement_id"); - bundle.putString(FirebaseAnalytics.Param.ACHIEVEMENT_ID, val); - } - if (map.containsKey("flight_number")) { - String val = (String) map.get("flight_number"); - bundle.putString(FirebaseAnalytics.Param.FLIGHT_NUMBER, val); - } - - Iterator> entries = map.entrySet().iterator(); - while (entries.hasNext()) { - Map.Entry entry = entries.next(); - if (bundle.getBundle(entry.getKey()) == null) { - bundle.putString(entry.getKey(), entry.getValue().toString()); - } - } - return bundle; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java deleted file mode 100644 index 36dd584..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ /dev/null @@ -1,644 +0,0 @@ - -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; - -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.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; -import com.google.firebase.auth.UserProfileChangeRequest; -import com.google.firebase.auth.FacebookAuthProvider; -import com.google.firebase.auth.FirebaseAuth; -import com.google.firebase.auth.FirebaseUser; -import com.google.firebase.auth.GetTokenResult; -import com.google.firebase.auth.GoogleAuthProvider; -import com.google.firebase.auth.FirebaseAuthException; - -class FirestackAuthModule extends ReactContextBaseJavaModule { - private final int NO_CURRENT_USER = 100; - private final int ERROR_FETCHING_TOKEN = 101; - - 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; - - public FirestackAuthModule(ReactApplicationContext reactContext) { - super(reactContext); - this.context = reactContext; - mReactContext = reactContext; - - Log.d(TAG, "New FirestackAuth instance"); - } - - @Override - public String getName() { - return TAG; - } - - @ReactMethod - public void listenForAuth() { - mAuthListener = new FirebaseAuth.AuthStateListener() { - - @Override - public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { - WritableMap msgMap = Arguments.createMap(); - msgMap.putString("eventName", "listenForAuth"); - - if (firebaseAuth.getCurrentUser() != null) { - WritableMap userMap = getUserMap(); - - msgMap.putBoolean("authenticated", true); - msgMap.putMap("user", userMap); - - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } else { - msgMap.putBoolean("authenticated", false); - FirestackUtils.sendEvent(mReactContext, "listenForAuth", msgMap); - } - } - }; - - mAuth = FirebaseAuth.getInstance(); - mAuth.addAuthStateListener(mAuthListener); - } - - @ReactMethod - public void unlistenForAuth(final Callback callback) { - if (mAuthListener != null) { - mAuth.removeAuthStateListener(mAuthListener); - - WritableMap resp = Arguments.createMap(); - resp.putString("status", "complete"); - - 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) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - // userErrorCallback(task, 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) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - // userErrorCallback(task, callback); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - public void onFailure(@NonNull Exception ex) { - Log.e(TAG, "An exception occurred: " + ex.getMessage()); - 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()); - - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - anonymousUserCallback(FirestackAuthModule.this.user, callback); - } else { - // userErrorCallback(task, 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()); - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - } else { - // userErrorCallback(task, 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) { - AuthCredential credential; - - if (provider.equals("facebook")) { - credential = FacebookAuthProvider.getCredential(authToken); - } else if (provider.equals("google")) { - credential = GoogleAuthProvider.getCredential(authToken, null); - } else { - // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); - // AuthCredential credential; - // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); - return; - } - - FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); - if (user != null) { - user.reauthenticate(credential) - .addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - Log.d(TAG, "User re-authenticated with " + provider); - 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); - } - } - - @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) { - if (task.isSuccessful()) { - Log.d(TAG, "User email address updated"); - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - // userErrorCallback(task, 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) { - if (task.isSuccessful()) { - Log.d(TAG, "User password updated"); - - FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); - userCallback(u, callback); - } else { - // userErrorCallback(task, 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 sendPasswordResetWithEmail(final String email, final Callback callback) { - mAuth = FirebaseAuth.getInstance(); - - mAuth.sendPasswordResetEmail(email) - .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()); - } - } - }).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 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); - } - } - }).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 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); - } - } - }).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); - } - } - }).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{ - userCallback(this.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() { - @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); - } - } - }).addOnFailureListener(new OnFailureListener() { - @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) { - if (task.isSuccessful()) { - FirestackAuthModule.this.user = task.getResult().getUser(); - userCallback(FirestackAuthModule.this.user, callback); - }else{ - // userErrorCallback(task, 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; - } - - 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); - msgMap.putBoolean("authenticated", true); - 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(); - - 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); - } - }); - } - - - 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) { - userExceptionCallback(task.getException(), onFail); - } - - public void userExceptionCallback(Exception exp, final Callback onFail) { - WritableMap error = Arguments.createMap(); - error.putString("errorMessage", exp.getMessage()); - error.putString("allErrorMessage", exp.toString()); - - try { - throw exp; - } catch (FirebaseAuthException ex) { - error.putString("errorCode", ex.getErrorCode()); - } catch (Exception ex) { - Log.e(TAG, ex.getMessage()); - } - - 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); - userMap.putBoolean("emailVerified", user.isEmailVerified()); - - if (name != null) { - userMap.putString("displayName", name); - } - - if (photoUrl != null) { - userMap.putString("photoUrl", photoUrl.toString()); - } - } else { - userMap.putString("msg", "no user"); - } - - return userMap; - } -} -n userMap; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java b/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java deleted file mode 100644 index ef17a88..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackCloudMessaging.java +++ /dev/null @@ -1,207 +0,0 @@ -package io.fullstack.firestack; - -import java.util.Map; - -import android.content.Context; -import android.content.IntentFilter; -import android.content.Intent; -import android.content.BroadcastReceiver; -import android.util.Log; - -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Callback; -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; -import com.facebook.react.bridge.ReadableType; -import com.facebook.react.bridge.WritableMap; - -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.RemoteMessage; - -/** - * Created by nori on 2016/09/12. - */ -public class FirestackCloudMessaging extends ReactContextBaseJavaModule { - - private static final String TAG = "FirestackCloudMessaging"; - private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; - private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; - private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; - - public static final String INTENT_NAME_TOKEN = "io.fullstack.firestack.refreshToken"; - 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 FirestackCloudMessaging(ReactApplicationContext reactContext) { - super(reactContext); - mReactContext = reactContext; - mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); - mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); - mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); - initRefreshTokenHandler(); - initMessageHandler(); - initSendHandler(); - Log.d(TAG, "New instance"); - } - - @Override - public String getName() { - return TAG; - } - - @ReactMethod - public void getToken(final Callback callback) { - - try { - String token = FirebaseInstanceId.getInstance().getToken(); - Log.d(TAG, "Firebase token: " + token); - callback.invoke(null, token); - } catch (Exception e) { - WritableMap error = Arguments.createMap(); - error.putString("message", e.getMessage()); - callback.invoke(error); - } - } - - /** - * - */ - private void initRefreshTokenHandler() { - getReactApplicationContext().registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - WritableMap params = Arguments.createMap(); - 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); - } - - ; - }, mRefreshTokenIntentFilter); - } - - @ReactMethod - public void subscribeToTopic(String topic, final Callback callback) { - try { - FirebaseMessaging.getInstance().subscribeToTopic(topic); - callback.invoke(null,topic); - } catch (Exception e) { - e.printStackTrace(); - Log.d(TAG, "Firebase token: " + e); - WritableMap error = Arguments.createMap(); - error.putString("message", e.getMessage()); - callback.invoke(error); - - } - } - - @ReactMethod - public void unsubscribeFromTopic(String topic, final Callback callback) { - try { - FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); - callback.invoke(null,topic); - } catch (Exception e) { - WritableMap error = Arguments.createMap(); - error.putString("message", e.getMessage()); - callback.invoke(error); - } - } - - private void initMessageHandler() { - Log.d(TAG, "Firestack initMessageHandler called"); - getReactApplicationContext().registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - RemoteMessage remoteMessage = intent.getParcelableExtra("data"); - Log.d(TAG, "Firebase onReceive: " + remoteMessage); - WritableMap params = Arguments.createMap(); - if (remoteMessage.getData().size() != 0) { - WritableMap dataMap = Arguments.createMap(); - Map data = remoteMessage.getData(); - //Set keysIterator = data.keySet(); - for (String key : data.keySet()) { - dataMap.putString(key, data.get(key)); - } - params.putMap("data", dataMap); - } else { - params.putNull("data"); - } - if (remoteMessage.getNotification() != null) { - WritableMap notificationMap = Arguments.createMap(); - RemoteMessage.Notification notification = remoteMessage.getNotification(); - notificationMap.putString("title", notification.getTitle()); - notificationMap.putString("body", notification.getBody()); - notificationMap.putString("icon", notification.getIcon()); - notificationMap.putString("sound", notification.getSound()); - notificationMap.putString("tag", notification.getTag()); - params.putMap("notification", notificationMap); - } else { - params.putNull("notification"); - } - ReactContext ctx = getReactApplicationContext(); - FirestackUtils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); - } - }, mReceiveNotificationIntentFilter); - } - - @ReactMethod - public void send(String senderId, String messageId, String messageType, ReadableMap params, final Callback callback) { - FirebaseMessaging fm = FirebaseMessaging.getInstance(); - RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(senderId); - remoteMessage.setMessageId(messageId); - remoteMessage.setMessageType(messageType); - ReadableMapKeySetIterator iterator = params.keySetIterator(); - while (iterator.hasNextKey()) { - String key = iterator.nextKey(); - ReadableType type = params.getType(key); - if (type == ReadableType.String) { - remoteMessage.addData(key, params.getString(key)); - Log.d(TAG, "Firebase send: " + key); - Log.d(TAG, "Firebase send: " + params.getString(key)); - } - } - try { - fm.send(remoteMessage.build()); - WritableMap res = Arguments.createMap(); - res.putString("status", "success"); - callback.invoke(null, res); - } catch(Exception e) { - Log.e(TAG, "Error sending message", e); - WritableMap error = Arguments.createMap(); - error.putString("code", e.toString()); - error.putString("message", e.toString()); - callback.invoke(error); - } - } - - private void initSendHandler() { - getReactApplicationContext().registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - WritableMap params = Arguments.createMap(); - if (intent.getBooleanExtra("hasError", false)) { - WritableMap error = Arguments.createMap(); - error.putInt("code", intent.getIntExtra("errCode", 0)); - error.putString("message", intent.getStringExtra("errorMessage")); - params.putMap("err", error); - } else { - params.putNull("err"); - } - ReactContext ctx = getReactApplicationContext(); - FirestackUtils.sendEvent(ctx, EVENT_NAME_SEND, params); - } - }, mReceiveSendIntentFilter); - } -} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java deleted file mode 100644 index 37aeca3..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ /dev/null @@ -1,719 +0,0 @@ -package io.fullstack.firestack; - -import android.content.Context; -import android.util.Log; -import java.util.HashMap; -import java.util.List; -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.WritableMap; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReactContext; - -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"; - - private String mPath; - private ReadableArray mModifiers; - private HashMap mListeners = new HashMap(); - private FirestackDatabaseModule mDatabase; - private ChildEventListener mEventListener; - private ValueEventListener mValueListener; - private ValueEventListener mOnceValueListener; - private ReactContext mReactContext; - - public FirestackDBReference(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 modifiers) { - final FirestackDBReference self = this; - - if (mEventListener == null) { - mEventListener = new ChildEventListener() { - @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_added", mPath, dataSnapshot); - } - - @Override - public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_changed", mPath, dataSnapshot); - } - - @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("child_removed", mPath, dataSnapshot); - } - - @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent("child_moved", mPath, dataSnapshot); - } - - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError(name, mPath, error); - } - }; - } - - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); - ref.addChildEventListener(mEventListener); - this.setListeningTo(mPath, name); - } - - public void addValueEventListener(final String name, final ReadableArray modifiers) { - final FirestackDBReference self = this; - - mValueListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent("value", mPath, dataSnapshot); - } - - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError("value", mPath, error); - } - }; - - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); - ref.addValueEventListener(mValueListener); - this.setListeningTo(mPath, "value"); - } - - public void addOnceValueEventListener(final ReadableArray modifiers, - final Callback callback) { - final FirestackDBReference self = this; - - mOnceValueListener = new ValueEventListener() { - @Override - public void onDataChange(DataSnapshot dataSnapshot) { - WritableMap data = FirestackUtils.dataSnapshotToMap("value", mPath, 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(modifiers); - ref.addListenerForSingleValueEvent(mOnceValueListener); - } - - public Boolean isListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, 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 notListeningTo(final String path, final String evtName) { - String key = this.pathListeningKey(path, evtName); - mListeners.remove(key); - } - - private String pathListeningKey(final String path, final String eventName) { - return "listener/" + path + "/" + eventName; - } - - public void cleanup() { - Log.d(TAG, "cleaning up database reference " + this); - this.removeChildEventListener(); - this.removeValueEventListener(); - } - - 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"); - mEventListener = null; - } - } - - public void removeValueEventListener() { - 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 DataSnapshot dataSnapshot) { - if (!FirestackDBReference.this.isListeningTo(path, name)) { - return; - } - WritableMap data = FirestackUtils.dataSnapshotToMap(name, path, dataSnapshot); - WritableMap evt = Arguments.createMap(); - evt.putString("eventName", name); - evt.putString("path", path); - evt.putMap("body", data); - - FirestackUtils.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); - - FirestackUtils.sendEvent(mReactContext, "database_error", evt); - } - - public DatabaseReference getDatabaseRef() { - return FirebaseDatabase.getInstance().getReference(mPath); - } - - private Query getDatabaseQueryAtPathAndModifiers(final ReadableArray modifiers) { - DatabaseReference ref = this.getDatabaseRef(); - - 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; - } - -} - -class FirestackDatabaseModule extends ReactContextBaseJavaModule { - - private static final String TAG = "FirestackDatabase"; - - private Context context; - private ReactContext mReactContext; - private HashMap mDBListeners = new HashMap(); - - public FirestackDatabaseModule(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) { - 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) { - DatabaseReference ref = this.getDatabaseReferenceAtPath(path); - - final FirestackDatabaseModule self = this; - Map m = FirestackUtils.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 FirestackDatabaseModule self = this; - Map m = FirestackUtils.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 FirestackDatabaseModule 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 FirestackDatabaseModule self = this; - Map m = FirestackUtils.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 ReadableArray modifiers, - final String name, - final Callback callback) { - FirestackDBReference ref = this.getDBHandle(path); - - WritableMap resp = Arguments.createMap(); - - if (name.equals("value")) { - ref.addValueEventListener(name, modifiers); - } else { - ref.addChildEventListener(name, modifiers); - } - - this.saveDBHandle(path, ref); - resp.putString("result", "success"); - Log.d(TAG, "Added listener " + name + " for " + ref); - - resp.putString("handle", path); - callback.invoke(null, resp); - } - - @ReactMethod - public void onOnce(final String path, - final ReadableArray modifiers, - 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); - } - - /** - * 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, @Deprecated final String name, final Callback callback) { - this.removeDBHandle(path); - Log.d(TAG, "Removed listener " + path); - 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 = FirestackUtils.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); - // FirestackUtils.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); - // FirestackUtils.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 FirestackDBReference getDBHandle(final String path) { - if (!mDBListeners.containsKey(path)) { - ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(path, new FirestackDBReference(ctx, path)); - } - - return mDBListeners.get(path); - } - - private void saveDBHandle(final String path, final FirestackDBReference dbRef) { - mDBListeners.put(path, dbRef); - } - - private void removeDBHandle(final String path) { - if (mDBListeners.containsKey(path)) { - FirestackDBReference r = mDBListeners.get(path); - r.cleanup(); - mDBListeners.remove(path); - } - } - - 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); - 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 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/FirestackInstanceIdService.java b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java index 8922ef4..f27f8c9 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java @@ -1,15 +1,14 @@ 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; +import io.fullstack.firestack.messaging.FirestackMessaging; + public class FirestackInstanceIdService extends FirebaseInstanceIdService { private static final String TAG = "FSInstanceIdService"; @@ -21,10 +20,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(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/FirestackMessagingService.java b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java index 485e762..9c32564 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java @@ -7,6 +7,8 @@ import com.google.firebase.messaging.RemoteMessage; import com.google.firebase.messaging.SendException; +import io.fullstack.firestack.messaging.FirestackMessaging; + public class FirestackMessagingService extends FirebaseMessagingService { private static final String TAG = "FSMessagingService"; @@ -16,16 +18,16 @@ public void onMessageReceived(RemoteMessage remoteMessage) { Log.d(TAG, "Remote message received"); // debug Log.d(TAG, "From: " + remoteMessage.getFrom()); + if (remoteMessage.getData().size() > 0) { Log.d(TAG, "Message data payload: " + remoteMessage.getData()); } + if (remoteMessage.getNotification() != null) { Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); } - if (remoteMessage.getNotification() != null) { - } - Intent i = new Intent(FirestackCloudMessaging.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(FirestackCloudMessaging.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(FirestackCloudMessaging.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/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java index ac2418f..d537411 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -1,25 +1,24 @@ package io.fullstack.firestack; -import android.content.Context; -import android.util.Log; import java.util.Map; -import android.support.annotation.NonNull; +import java.util.HashMap; + +import android.util.Log; +import android.content.Context; import android.support.annotation.Nullable; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.LifecycleEventListener; 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.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.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.database.ServerValue; @@ -28,18 +27,13 @@ interface KeySetterFn { String setKeyOrDefault(String a, String b); } -class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { +@SuppressWarnings("WeakerAccess") +public class FirestackModule 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 FirestackModule(ReactApplicationContext reactContext) { super(reactContext); - this.context = context; - mReactContext = reactContext; - - Log.d(TAG, "New instance"); } @Override @@ -47,12 +41,27 @@ public String getName() { return TAG; } + 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) { Log.i(TAG, "configureWithOptions"); FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); - FirebaseOptions defaultOptions = FirebaseOptions.fromResource(this.context); + FirebaseOptions defaultOptions = FirebaseOptions.fromResource(getReactApplicationContext().getBaseContext()); if (defaultOptions == null) { defaultOptions = new FirebaseOptions.Builder().build(); @@ -60,8 +69,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); @@ -76,47 +85,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")) { @@ -156,24 +144,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(getReactApplicationContext().getBaseContext(), 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); } } @@ -186,26 +173,36 @@ 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); + Utils.sendEvent(getReactApplicationContext(), "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); + Utils.sendEvent(getReactApplicationContext(), "FirestackAppState", params); + } - @Override - public void onHostDestroy() { + @Override + public void onHostDestroy() { - } -} \ No newline at end of file + } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("googleApiAvailability", getPlayServicesStatus()); + + // TODO remove once this has been moved on ios + constants.put("serverValueTimestamp", ServerValue.TIMESTAMP); + return constants; + } +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java index ea5927d..9a22981 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -6,12 +6,20 @@ 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 java.util.List; +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; @@ -24,13 +32,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 FirestackModule(reactContext)); + 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; } @@ -54,4 +61,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 deleted file mode 100644 index 25ca218..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ /dev/null @@ -1,298 +0,0 @@ -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; -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.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; - -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.StorageMetadata; -import com.google.firebase.storage.StorageReference; - -class FirestackStorageModule extends ReactContextBaseJavaModule { - - private static final String TAG = "FirestackStorage"; - private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; - private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH"; - private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH"; - private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH"; - private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH"; - private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH"; - private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH"; - - 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"); - } - - @Override - public String getName() { - return TAG; - } - - @ReactMethod - 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()); - - storageRef.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()); - - WritableMap err = Arguments.createMap(); - err.putString("status", "error"); - err.putString("description", exception.getLocalizedMessage()); - - 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); - -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); - - 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); - // 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() { - @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)); - } - } - - @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(); - - callback.invoke(null, path); - } catch (Exception ex) { - ex.printStackTrace(); - callback.invoke(makeErrorPayload(1, ex)); - } - } - - private WritableMap getDownloadData(final UploadTask.TaskSnapshot taskSnapshot) { - Uri downloadUrl = taskSnapshot.getDownloadUrl(); - StorageMetadata d = taskSnapshot.getMetadata(); - - WritableMap resp = Arguments.createMap(); - resp.putString("downloadUrl", downloadUrl.toString()); - resp.putString("fullPath", d.getPath()); - resp.putString("bucket", d.getBucket()); - resp.putString("name", d.getName()); - - WritableMap metadataObj = Arguments.createMap(); - metadataObj.putString("cacheControl", d.getCacheControl()); - metadataObj.putString("contentDisposition", d.getContentDisposition()); - metadataObj.putString("contentType", d.getContentType()); - resp.putMap("metadata", metadataObj); - - return resp; - } - - private WritableMap makeErrorPayload(double code, Exception ex) { - WritableMap error = Arguments.createMap(); - error.putDouble("code", code); - error.putString("message", ex.getMessage()); - return error; - } - - // Comes almost directory from react-native-fs - @Override - public Map getConstants() { - final Map constants = new HashMap<>(); - - constants.put(DocumentDirectory, 0); - constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath()); - constants.put(TemporaryDirectoryPath, null); - constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); - constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath()); - constants.put(FileTypeRegular, 0); - constants.put(FileTypeDirectory, 1); - - File externalStorageDirectory = Environment.getExternalStorageDirectory(); - if (externalStorageDirectory != null) { - constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath()); - } else { - constants.put(ExternalStorageDirectoryPath, null); - } - - File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null); - if (externalDirectory != null) { - constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath()); - } else { - constants.put(ExternalDirectoryPath, null); - } - - return constants; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java b/android/src/main/java/io/fullstack/firestack/FirestackUtils.java deleted file mode 100644 index 32c871a..0000000 --- a/android/src/main/java/io/fullstack/firestack/FirestackUtils.java +++ /dev/null @@ -1,228 +0,0 @@ -package io.fullstack.firestack; - -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -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.ReadableArray; -import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableType; -import com.google.firebase.database.DataSnapshot; - -public class FirestackUtils { - private static final String TAG = "FirestackUtils"; - - // 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."); - - WritableMap errorMap = Arguments.createMap(); - errorMap.putString("error", "unimplemented"); - callback.invoke(null, errorMap); - } - - /** - * send a JS event - **/ - public static void sendEvent(final ReactContext context, - final String eventName, - final WritableMap params) { - if (context.hasActiveCatalystInstance()) { - context - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); - } else { - Log.d(TAG, "Waiting for CatalystInstance before sending event"); - } - } - - // 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); - } - - // 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()); - } - - WritableMap eventMap = Arguments.createMap(); - eventMap.putString("eventName", name); - eventMap.putMap("snapshot", data); - eventMap.putString("path", path); - return eventMap; - } - - 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; - } - } - return (Any) data; - } else { - if (snapshot.getValue() != null) { - String type = snapshot.getValue().getClass().getName(); - switch (type) { - case "java.lang.Boolean": - return (Any)(snapshot.getValue()); - case "java.lang.Long": - return (Any)(snapshot.getValue()); - case "java.lang.Double": - return (Any)(snapshot.getValue()); - case "java.lang.String": - return (Any)(snapshot.getValue()); - default: - Log.w(TAG, "Invalid type: "+type); - return (Any) null; - } - } - return (Any) null; - } - } - - public static WritableArray getChildKeys(DataSnapshot snapshot) { - WritableArray childKeys = Arguments.createArray(); - - if (snapshot.hasChildren()) { - for (DataSnapshot child : snapshot.getChildren()) { - childKeys.pushString(child.getKey()); - } - } - - return childKeys; - } - - 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 + "."); - } - - } - 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 + "."); - } - } - return deconstructedList; - } -} diff --git a/android/src/main/java/io/fullstack/firestack/Utils.java b/android/src/main/java/io/fullstack/firestack/Utils.java new file mode 100644 index 0000000..a763204 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/Utils.java @@ -0,0 +1,301 @@ +package io.fullstack.firestack; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableNativeArray; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +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 Utils { + private static final String TAG = "Utils"; + + // 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."); + + WritableMap errorMap = Arguments.createMap(); + errorMap.putString("error", "unimplemented"); + callback.invoke(null, errorMap); + } + + /** + * send a JS event + **/ + public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) { + if (context != null) { + context + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } else { + Log.d(TAG, "Missing context - cannot send event!"); + } + } + + // snapshot + 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 { + Object value = Utils.castSnapshotValue(dataSnapshot); + if (value instanceof WritableNativeArray) { + data.putArray("value", (WritableArray) value); + } else { + data.putMap("value", (WritableMap) value); + } + } + + // Child keys + WritableArray childKeys = Utils.getChildKeys(dataSnapshot); + data.putArray("childKeys", childKeys); + + 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); + eventMap.putString("modifiersString", modifiersString); + return eventMap; + } + + public static Any castSnapshotValue(DataSnapshot snapshot) { + if (snapshot.hasChildren()) { + if (isArray(snapshot)) { + return (Any) buildArray(snapshot); + } else { + return (Any) buildMap(snapshot); + } + } else { + if (snapshot.getValue() != null) { + String type = snapshot.getValue().getClass().getName(); + switch (type) { + case "java.lang.Boolean": + return (Any) (snapshot.getValue()); + case "java.lang.Long": + return (Any) (snapshot.getValue()); + case "java.lang.Double": + return (Any) (snapshot.getValue()); + case "java.lang.String": + return (Any) (snapshot.getValue()); + default: + Log.w(TAG, "Invalid type: " + type); + return (Any) null; + } + } + return (Any) null; + } + } + + 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(); + + if (snapshot.hasChildren()) { + for (DataSnapshot child : snapshot.getChildren()) { + childKeys.pushString(child.getKey()); + } + } + + return childKeys; + } + + public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { + 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); + 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, Utils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); + break; + case Array: + deconstructedMap.put(key, Utils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); + break; + default: + throw new IllegalArgumentException("Could not convert object with key: " + key + "."); + } + + } + 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, Utils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); + break; + case Array: + deconstructedList.add(i, Utils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); + break; + default: + throw new IllegalArgumentException("Could not convert object at index " + i + "."); + } + } + return deconstructedList; + } +} diff --git a/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java new file mode 100644 index 0000000..14e6ac2 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java @@ -0,0 +1,103 @@ +package io.fullstack.firestack.analytics; + +import android.util.Log; +import android.app.Activity; +import android.support.annotation.Nullable; + +import com.facebook.react.bridge.Arguments; +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; + + +public class FirestackAnalytics extends ReactContextBaseJavaModule { + + private static final String TAG = "FirestackAnalytics"; + + public FirestackAnalytics(ReactApplicationContext reactContext) { + super(reactContext); + Log.d(TAG, "New instance"); + } + + /** + * + * @return + */ + @Override + public String getName() { + return TAG; + } + + @ReactMethod + public void logEvent(final String name, @Nullable final ReadableMap params) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).logEvent(name, Arguments.toBundle(params)); + } + + /** + * + * @param enabled + */ + @ReactMethod + public void setAnalyticsCollectionEnabled(final Boolean enabled) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setAnalyticsCollectionEnabled(enabled); + } + + /** + * + * @param screenName + * @param screenClassOverride + */ + @ReactMethod + public void setCurrentScreen(final String screenName, final String screenClassOverride) { + final Activity activity = getCurrentActivity(); + if (activity != null) { + // needs to be run on main thread + Log.d(TAG, "setCurrentScreen " + screenName + " - " + screenClassOverride); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setCurrentScreen(activity, screenName, screenClassOverride); + } + }); + } + } + + /** + * + * @param milliseconds + */ + @ReactMethod + public void setMinimumSessionDuration(final double milliseconds) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setMinimumSessionDuration((long) milliseconds); + } + + /** + * + * @param milliseconds + */ + @ReactMethod + public void setSessionTimeoutDuration(final double milliseconds) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setSessionTimeoutDuration((long) milliseconds); + } + + /** + * + * @param id + */ + @ReactMethod + public void setUserId(final String id) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserId(id); + } + + /** + * + * @param name + * @param value + */ + @ReactMethod + public void setUserProperty(final String name, final String value) { + FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserProperty(name, value); + } +} diff --git a/android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java new file mode 100644 index 0000000..3f194ba --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/auth/FirestackAuth.java @@ -0,0 +1,619 @@ + +package io.fullstack.firestack.auth; + +import android.util.Log; + +import java.util.Map; + +import android.net.Uri; +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.WritableMap; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReactContext; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; + +import com.google.firebase.auth.AuthCredential; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.UserProfileChangeRequest; +import com.google.firebase.auth.FacebookAuthProvider; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseAuthException; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.auth.GetTokenResult; +import com.google.firebase.auth.GoogleAuthProvider; +import com.google.firebase.auth.EmailAuthProvider; + + +import io.fullstack.firestack.Utils; + + +@SuppressWarnings("ThrowableResultOfMethodCallIgnored") +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; + + private static final String TAG = "FirestackAuth"; + + // private Context context; + private ReactContext mReactContext; + private FirebaseAuth mAuth; + private FirebaseAuth.AuthStateListener mAuthListener; + + public FirestackAuth(ReactApplicationContext reactContext) { + super(reactContext); + mReactContext = reactContext; + mAuth = FirebaseAuth.getInstance(); + + Log.d(TAG, "New FirestackAuth instance"); + } + + @Override + public String getName() { + return TAG; + } + + /** + * Returns a no user error. + * + * @param callback JS callback + */ + private 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, null); + } + } + + @ReactMethod + public void listenForAuth() { + 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 (user != null) { + // TODO move to helper + WritableMap userMap = getUserMap(user); + msgMap.putBoolean("authenticated", true); + msgMap.putMap("user", userMap); + + Utils.sendEvent(mReactContext, "listenForAuth", msgMap); + } else { + msgMap.putBoolean("authenticated", false); + Utils.sendEvent(mReactContext, "listenForAuth", msgMap); + } + } + }; + mAuth.addAuthStateListener(mAuthListener); + } + } + + @ReactMethod + public void unlistenForAuth(final Callback callback) { + if (mAuthListener != null) { + mAuth.removeAuthStateListener(mAuthListener); + + // TODO move to helper + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + + callback.invoke(null, resp); + } + } + + @ReactMethod + public void createUserWithEmail(final String email, final String password, final Callback callback) { + mAuth.createUserWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), 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.signInWithEmailAndPassword(email, password) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (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 + Utils.todoNote(TAG, "signInWithProvider", callback); + } + + @ReactMethod + public void linkPassword(final String email, final String password, final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + if (user != null) { + AuthCredential credential = EmailAuthProvider.getCredential(email, password); + user + .linkWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "user linked with password credential"); + userCallback(mAuth.getCurrentUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + @ReactMethod + public void link(final String provider, final String authToken, final String authSecret, final Callback callback) { + if (provider.equals("password")) { + linkPassword(authToken, authSecret, callback); + } else + // TODO other providers + Utils.todoNote(TAG, "linkWithProvider", callback); + } + + @ReactMethod + public void signInAnonymously(final Callback callback) { + Log.d(TAG, "signInAnonymously:called:"); + mAuth.signInAnonymously() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); + + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } + + @ReactMethod + public void signInWithCustomToken(final String customToken, final Callback callback) { + mAuth.signInWithCustomToken(customToken) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), 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: + Utils.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 = mAuth.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"); + userCallback(mAuth.getCurrentUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + @ReactMethod + public void updateUserPassword(final String newPassword, final Callback callback) { + FirebaseUser user = mAuth.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"); + userCallback(mAuth.getCurrentUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + @ReactMethod + public void sendPasswordResetWithEmail(final String email, final Callback callback) { + 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); + } + } + }); + } + + @ReactMethod + public void deleteUser(final Callback callback) { + FirebaseUser user = mAuth.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); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + + @ReactMethod + public void sendEmailVerification(final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + 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); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + + @ReactMethod + public void getToken(final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + 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); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + @ReactMethod + public void updateUserProfile(ReadableMap props, final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + if (user != null) { + UserProfileChangeRequest.Builder profileBuilder = new UserProfileChangeRequest.Builder(); + + Map m = Utils.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) { + try { + if (task.isSuccessful()) { + Log.d(TAG, "User profile updated"); + userCallback(mAuth.getCurrentUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } else { + callbackNoUser(callback, true); + } + } + + @ReactMethod + public void signOut(final Callback callback) { + mAuth.signOut(); + + WritableMap resp = Arguments.createMap(); + resp.putString("status", "complete"); + resp.putString("msg", "User signed out"); + callback.invoke(null, resp); + } + + @ReactMethod + public void reloadUser(final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + if (user == null) { + callbackNoUser(callback, false); + } else { + user.reload() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + Log.d(TAG, "user reloaded"); + userCallback(mAuth.getCurrentUser(), callback); + } else { + userErrorCallback(task, callback); + } + } + }); + } + } + + @ReactMethod + public void getCurrentUser(final Callback callback) { + FirebaseUser user = mAuth.getCurrentUser(); + + if (user == null) { + callbackNoUser(callback, false); + } else { + Log.d("USRC", user.getUid()); + userCallback(user, callback); + } + } + + // TODO: Check these things + @ReactMethod + public void googleLogin(String IdToken, final Callback callback) { + AuthCredential credential = GoogleAuthProvider.getCredential(IdToken, null); + mAuth.signInWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } + + @ReactMethod + public void facebookLogin(String Token, final Callback callback) { + AuthCredential credential = FacebookAuthProvider.getCredential(Token); + mAuth.signInWithCredential(credential) + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + try { + if (task.isSuccessful()) { + userCallback(task.getResult().getUser(), callback); + } else { + userErrorCallback(task, callback); + } + } catch (Exception ex) { + userExceptionCallback(ex, callback); + } + } + }); + } + + // Internal helpers + 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(user); + userMap.putString("token", task.getResult().getToken()); + callback.invoke(null, userMap); + } 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.putString("code", ((FirebaseAuthException) task.getException()).getErrorCode()); + error.putString("message", task.getException().getMessage()); + onFail.invoke(error); + } + + private void userExceptionCallback(Exception ex, final Callback onFail) { + WritableMap error = Arguments.createMap(); + error.putInt("code", ex.hashCode()); + error.putString("message", ex.getMessage()); + onFail.invoke(error); + } + + private WritableMap getUserMap(FirebaseUser user) { + WritableMap userMap = Arguments.createMap(); + 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); + + 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/database/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java new file mode 100644 index 0000000..971d2da --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabase.java @@ -0,0 +1,343 @@ +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 com.google.firebase.database.ServerValue; + + +import io.fullstack.firestack.Utils; + +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 + public String getName() { + return TAG; + } + + // Persistence + @ReactMethod + public void enablePersistence( + final Boolean enable, + final Callback callback) { + try { + mFirebaseDatabase.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 = mFirebaseDatabase.getReference(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 = 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.setValue(m.get("value"), listener); + } + + @ReactMethod + public void update(final String path, + final ReadableMap props, + final Callback callback) { + 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.updateChildren(m, listener); + } + + @ReactMethod + public void remove(final String path, + final Callback callback) { + DatabaseReference ref = mFirebaseDatabase.getReference(path); + DatabaseReference.CompletionListener listener = new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("remove", callback, error); + } + }; + + 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 = mFirebaseDatabase.getReference(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 + 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.get("value"), listener); + } else { + Log.d(TAG, "No value passed to push: " + newPath); + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + res.putString("ref", newPath); + callback.invoke(null, res); + } + } + + @ReactMethod + public void on(final String path, + final String modifiersString, + final ReadableArray modifiersArray, + final String eventName, + final Callback callback) { + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString); + + if (eventName.equals("value")) { + ref.addValueEventListener(); + } else { + ref.addChildEventListener(eventName); + } + + WritableMap resp = Arguments.createMap(); + resp.putString("status", "success"); + resp.putString("handle", path); + callback.invoke(null, resp); + } + + @ReactMethod + public void onOnce(final String path, + final String modifiersString, + final ReadableArray modifiersArray, + final String eventName, + final Callback callback) { + FirestackDatabaseReference ref = this.getDBHandle(path, modifiersArray, modifiersString); + ref.addOnceValueEventListener(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, + final String eventName, + final Callback callback) { + + String key = this.getDBListenerKey(path, modifiersString); + FirestackDatabaseReference r = mDBListeners.get(key); + + if (r != null) { + if (eventName == null || "".equals(eventName)) { + r.cleanup(); + mDBListeners.remove(key); + } else { + r.removeEventListener(eventName); + if (!r.hasListeners()) { + mDBListeners.remove(key); + } ; + } + } + + Log.d(TAG, "Removed listener " + path + "/" + modifiersString); + WritableMap resp = Arguments.createMap(); + resp.putString("handle", path); + resp.putString("status", "success"); + resp.putString("modifiersString", modifiersString); + //TODO: Remaining listeners + callback.invoke(null, resp); + } + + // On Disconnect + @ReactMethod + public void onDisconnectSetObject(final String path, final ReadableMap props, final Callback callback) { + 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 error, DatabaseReference ref) { + handleCallback("onDisconnectSetObject", callback, error); + } + }); + } + + @ReactMethod + public void onDisconnectSetString(final String path, final String value, final Callback callback) { + DatabaseReference ref = mFirebaseDatabase.getReference(path); + + OnDisconnect od = ref.onDisconnect(); + od.setValue(value, new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectSetString", callback, error); + } + }); + } + + @ReactMethod + public void onDisconnectRemove(final String path, final Callback callback) { + DatabaseReference ref = mFirebaseDatabase.getReference(path); + + OnDisconnect od = ref.onDisconnect(); + od.removeValue(new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectRemove", callback, error); + } + }); + } + @ReactMethod + public void onDisconnectCancel(final String path, final Callback callback) { + DatabaseReference ref = mFirebaseDatabase.getReference(path); + + OnDisconnect od = ref.onDisconnect(); + od.cancel(new DatabaseReference.CompletionListener() { + @Override + public void onComplete(DatabaseError error, DatabaseReference ref) { + handleCallback("onDisconnectCancel", callback, error); + } + }); + } + + @ReactMethod + public void goOnline() { + mFirebaseDatabase.goOnline(); + } + + @ReactMethod + public void goOffline() { + mFirebaseDatabase.goOffline(); + } + + private void handleCallback( + final String methodName, + final Callback callback, + final DatabaseError databaseError) { + 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 ReadableArray modifiersArray, + final String modifiersString) { + String key = this.getDBListenerKey(path, modifiersString); + FirestackDatabaseReference r = mDBListeners.get(key); + + if (r == null) { + ReactContext ctx = getReactApplicationContext(); + r = new FirestackDatabaseReference(ctx, mFirebaseDatabase, path, modifiersArray, modifiersString); + mDBListeners.put(key, r); + } + + return r; + } + + private String getDBListenerKey(String path, String modifiersString) { + return path + " | " + modifiersString; + } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("serverValueTimestamp", ServerValue.TIMESTAMP); + return constants; + } +} 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..47e6f11 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/database/FirestackDatabaseReference.java @@ -0,0 +1,296 @@ +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; +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 = "FirestackDBReference"; + + private Query mQuery; + private String mPath; + private String mModifiersString; + private ChildEventListener mEventListener; + private ValueEventListener mValueListener; + private ReactContext mReactContext; + private Set childEventListeners = new HashSet<>(); + + 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 addChildEventListener(final String eventName) { + if (mEventListener == null) { + mEventListener = new ChildEventListener() { + @Override + public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { + handleDatabaseEvent("child_added", dataSnapshot); + } + + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { + handleDatabaseEvent("child_changed", dataSnapshot); + } + + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + handleDatabaseEvent("child_removed", dataSnapshot); + } + + @Override + public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { + handleDatabaseEvent("child_moved", dataSnapshot); + } + + @Override + public void onCancelled(DatabaseError error) { + handleDatabaseError(error); + } + }; + 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); + } + //Keep track of the events that the JS is interested in knowing about + childEventListeners.add(eventName); + } + + public void addValueEventListener() { + if (mValueListener == null) { + mValueListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + handleDatabaseEvent("value", dataSnapshot); + } + + @Override + public void onCancelled(DatabaseError error) { + handleDatabaseError(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 Callback callback) { + final ValueEventListener onceValueEventListener = new ValueEventListener() { + @Override + public void onDataChange(DataSnapshot dataSnapshot) { + WritableMap data = Utils.dataSnapshotToMap("value", mPath, mModifiersString, 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); + } + }; + mQuery.addListenerForSingleValueEvent(onceValueEventListener); + Log.d(TAG, "Added OnceValueEventListener for path: " + mPath + " with modifiers " + mModifiersString); + } + + public void removeEventListener(String eventName) { + if ("value".equals(eventName)) { + this.removeValueEventListener(); + } else { + childEventListeners.remove(eventName); + if (childEventListeners.isEmpty()) { + this.removeChildEventListener(); + } + } + } + + public boolean hasListeners() { + return mEventListener != null || mValueListener != null; + } + + public void cleanup() { + Log.d(TAG, "cleaning up database reference " + this); + childEventListeners.clear(); + this.removeChildEventListener(); + this.removeValueEventListener(); + } + + private void removeChildEventListener() { + if (mEventListener != null) { + mQuery.removeEventListener(mEventListener); + mEventListener = null; + } + } + + private void removeValueEventListener() { + if (mValueListener != null) { + mQuery.removeEventListener(mValueListener); + mValueListener = null; + } + } + + 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.putMap("body", data); + + Utils.sendEvent(mReactContext, "database_event", evt); + } + + private void handleDatabaseError(final DatabaseError error) { + WritableMap err = Arguments.createMap(); + err.putString("eventName", "database_error"); + err.putString("path", mPath); + err.putString("modifiersString", mModifiersString); + err.putInt("errorCode", error.getCode()); + err.putString("errorDetails", error.getDetails()); + err.putString("msg", error.getMessage()); + + WritableMap evt = Arguments.createMap(); + evt.putString("eventName", "database_error"); + evt.putMap("body", err); + + Utils.sendEvent(mReactContext, "database_error", evt); + } + + 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(); + + while(it.hasNext()) { + String str = (String) it.next(); + + String[] strArr = str.split(":"); + String methStr = strArr[0]; + + if (methStr.equalsIgnoreCase("orderByKey")) { + query = query.orderByKey(); + } else if (methStr.equalsIgnoreCase("orderByValue")) { + query = query.orderByValue(); + } else if (methStr.equalsIgnoreCase("orderByPriority")) { + query = query.orderByPriority(); + } else if (methStr.contains("orderByChild")) { + String key = strArr[1]; + Log.d(TAG, "orderByChild: " + key); + query = query.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 type = strArr[2]; + if ("number".equals(type)) { + double doubleValue = Double.parseDouble(value); + 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(booleanValue); + } + } else { + if (strArr.length > 3) { + query = query.equalTo(value, strArr[3]); + } else { + query = query.equalTo(value); + } + } + } else if (methStr.contains("endAt")) { + String value = strArr[1]; + String type = strArr[2]; + if ("number".equals(type)) { + double doubleValue = Double.parseDouble(value); + 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(booleanValue); + } + } else { + if (strArr.length > 3) { + query = query.endAt(value, strArr[3]); + } else { + query = query.endAt(value); + } + } + } else if (methStr.contains("startAt")) { + String value = strArr[1]; + String type = strArr[2]; + if ("number".equals(type)) { + double doubleValue = Double.parseDouble(value); + 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(booleanValue); + } + } else { + if (strArr.length > 3) { + query = query.startAt(value, strArr[3]); + } else { + query = query.startAt(value); + } + } + } + } + + return query; + } +} diff --git a/android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java b/android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java new file mode 100644 index 0000000..4114e76 --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java @@ -0,0 +1,229 @@ +package io.fullstack.firestack.messaging; + +import java.util.Map; + +import android.content.Context; +import android.content.IntentFilter; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.WritableMap; + +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.RemoteMessage; + +import io.fullstack.firestack.Utils; + +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"; + + public static final String INTENT_NAME_TOKEN = "io.fullstack.firestack.refreshToken"; + public static final String INTENT_NAME_NOTIFICATION = "io.fullstack.firestack.ReceiveNotification"; + public static final String INTENT_NAME_SEND = "io.fullstack.firestack.Upstream"; + + private IntentFilter mRefreshTokenIntentFilter; + private IntentFilter mReceiveNotificationIntentFilter; + private IntentFilter mReceiveSendIntentFilter; + private BroadcastReceiver mBroadcastReceiver; + + public FirestackMessaging(ReactApplicationContext reactContext) { + super(reactContext); + mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); + mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); + mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); + initRefreshTokenHandler(); + initMessageHandler(); + initSendHandler(); + Log.d(TAG, "New instance"); + } + + @Override + public String getName() { + return TAG; + } + + private void initMessageHandler() { + Log.d(TAG, "Firestack initMessageHandler called"); + + if (mBroadcastReceiver == null) { + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + RemoteMessage remoteMessage = intent.getParcelableExtra("data"); + Log.d(TAG, "Firebase onReceive: " + remoteMessage); + WritableMap params = Arguments.createMap(); + + params.putNull("data"); + params.putNull("notification"); + params.putString("id", remoteMessage.getMessageId()); + params.putString("messageId", remoteMessage.getMessageId()); + + + if (remoteMessage.getData().size() != 0) { + WritableMap dataMap = Arguments.createMap(); + Map data = remoteMessage.getData(); + + for (String key : data.keySet()) { + dataMap.putString(key, data.get(key)); + } + + params.putMap("data", dataMap); + } + + + if (remoteMessage.getNotification() != null) { + WritableMap notificationMap = Arguments.createMap(); + RemoteMessage.Notification notification = remoteMessage.getNotification(); + notificationMap.putString("title", notification.getTitle()); + notificationMap.putString("body", notification.getBody()); + notificationMap.putString("icon", notification.getIcon()); + notificationMap.putString("sound", notification.getSound()); + notificationMap.putString("tag", notification.getTag()); + params.putMap("notification", notificationMap); + } + + ReactContext ctx = getReactApplicationContext(); + Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); + } + }; + + } + getReactApplicationContext().registerReceiver(mBroadcastReceiver, mReceiveNotificationIntentFilter); + } + + /** + * + */ + private void initRefreshTokenHandler() { + getReactApplicationContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + WritableMap params = Arguments.createMap(); + params.putString("token", intent.getStringExtra("token")); + ReactContext ctx = getReactApplicationContext(); + Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN); + Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params); + } + + ; + }, mRefreshTokenIntentFilter); + } + + @ReactMethod + public void subscribeToTopic(String topic, final Callback callback) { + try { + FirebaseMessaging.getInstance().subscribeToTopic(topic); + callback.invoke(null, topic); + } catch (Exception e) { + e.printStackTrace(); + Log.d(TAG, "Firebase token: " + e); + WritableMap error = Arguments.createMap(); + error.putString("message", e.getMessage()); + callback.invoke(error); + + } + } + + @ReactMethod + public void getToken(final Callback callback) { + + try { + String token = FirebaseInstanceId.getInstance().getToken(); + Log.d(TAG, "Firebase token: " + token); + callback.invoke(null, token); + } catch (Exception e) { + WritableMap error = Arguments.createMap(); + error.putString("message", e.getMessage()); + callback.invoke(error); + } + } + + @ReactMethod + public void unsubscribeFromTopic(String topic, final Callback callback) { + try { + FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); + callback.invoke(null, topic); + } catch (Exception e) { + WritableMap error = Arguments.createMap(); + error.putString("message", e.getMessage()); + callback.invoke(error); + } + } + + // String senderId, String messageId, String messageType, + @ReactMethod + public void send(ReadableMap params, final Callback callback) { + ReadableMap data = params.getMap("data"); + FirebaseMessaging fm = FirebaseMessaging.getInstance(); + RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(params.getString("sender")); + + remoteMessage.setMessageId(params.getString("id")); + remoteMessage.setMessageType(params.getString("type")); + + if (params.hasKey("ttl")) { + remoteMessage.setTtl(params.getInt("ttl")); + } + + if (params.hasKey("collapseKey")) { + remoteMessage.setCollapseKey(params.getString("collapseKey")); + } + + ReadableMapKeySetIterator iterator = data.keySetIterator(); + + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + ReadableType type = data.getType(key); + if (type == ReadableType.String) { + remoteMessage.addData(key, data.getString(key)); + } + } + + try { + fm.send(remoteMessage.build()); + WritableMap res = Arguments.createMap(); + res.putString("status", "success"); + Log.d(TAG, "send: Message sent"); + callback.invoke(null, res); + } catch (Exception e) { + Log.e(TAG, "send: error sending message", e); + WritableMap error = Arguments.createMap(); + error.putString("code", e.toString()); + error.putString("message", e.toString()); + callback.invoke(error); + } + } + + private void initSendHandler() { + getReactApplicationContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + WritableMap params = Arguments.createMap(); + if (intent.getBooleanExtra("hasError", false)) { + WritableMap error = Arguments.createMap(); + error.putInt("code", intent.getIntExtra("errCode", 0)); + error.putString("message", intent.getStringExtra("errorMessage")); + params.putMap("err", error); + } else { + params.putNull("err"); + } + ReactContext ctx = getReactApplicationContext(); + Utils.sendEvent(ctx, EVENT_NAME_SEND, params); + } + }, mReceiveSendIntentFilter); + } +} diff --git a/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java new file mode 100644 index 0000000..cae695f --- /dev/null +++ b/android/src/main/java/io/fullstack/firestack/storage/FirestackStorage.java @@ -0,0 +1,484 @@ +package io.fullstack.firestack.storage; + +import android.util.Log; +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; + +import android.net.Uri; +import android.database.Cursor; +import android.provider.MediaStore; +import android.support.annotation.NonNull; + +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.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.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.storage.StorageException; +import com.google.firebase.storage.StorageTask; +import com.google.firebase.storage.StreamDownloadTask; +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; + +import io.fullstack.firestack.Utils; + + +@SuppressWarnings("WeakerAccess") +public class FirestackStorage extends ReactContextBaseJavaModule { + + private static final String TAG = "FirestackStorage"; + private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; + private static final String ExternalDirectoryPath = "EXTERNAL_DIRECTORY_PATH"; + private static final String ExternalStorageDirectoryPath = "EXTERNAL_STORAGE_DIRECTORY_PATH"; + private static final String PicturesDirectoryPath = "PICTURES_DIRECTORY_PATH"; + private static final String TemporaryDirectoryPath = "TEMPORARY_DIRECTORY_PATH"; + private static final String CachesDirectoryPath = "CACHES_DIRECTORY_PATH"; + private static final String DocumentDirectory = "DOCUMENT_DIRECTORY_PATH"; + + private static final String FileTypeRegular = "FILETYPE_REGULAR"; + private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; + + private static final String STORAGE_EVENT = "storage_event"; + private static final String STORAGE_ERROR = "storage_error"; + private static final String STORAGE_STATE_CHANGED = "state_changed"; + private static final String STORAGE_UPLOAD_SUCCESS = "upload_success"; + private static final String STORAGE_UPLOAD_FAILURE = "upload_failure"; + 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); + + Log.d(TAG, "New instance"); + } + + @Override + public String getName() { + return TAG; + } + + + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + + @ReactMethod + public void delete(final String path, + final Callback callback) { + StorageReference reference = this.getReference(path); + reference.delete().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + WritableMap data = Arguments.createMap(); + data.putString("success", "success"); + data.putString("path", path); + callback.invoke(null, data); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception exception) { + callback.invoke(makeErrorPayload(1, exception)); + } + }); + } + + @ReactMethod + public void getDownloadURL(final String path, + final Callback callback) { + Log.d(TAG, "Download url for remote path: " + path); + final StorageReference reference = this.getReference(path); + + Task downloadTask = reference.getDownloadUrl(); + downloadTask + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Uri uri) { + callback.invoke(null, uri.toString()); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + callback.invoke(makeErrorPayload(1, exception)); + } + }); + } + + @ReactMethod + public void getMetadata(final String path, + final Callback callback) { + StorageReference reference = this.getReference(path); + reference.getMetadata().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StorageMetadata storageMetadata) { + WritableMap data = getMetadataAsMap(storageMetadata); + callback.invoke(null, data); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception exception) { + callback.invoke(makeErrorPayload(1, exception)); + } + }); + } + + @ReactMethod + public void updateMetadata(final String path, + final ReadableMap metadata, + final Callback callback) { + StorageReference reference = this.getReference(path); + StorageMetadata md = buildMetadataFromMap(metadata); + reference.updateMetadata(md).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StorageMetadata storageMetadata) { + WritableMap data = getMetadataAsMap(storageMetadata); + callback.invoke(null, data); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(Exception exception) { + callback.invoke(makeErrorPayload(1, exception)); + } + }); + } + + @ReactMethod + public void downloadFile(final String path, + final String localPath, + final Callback callback) { + 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; + } + Log.d(TAG, "downloadFile from remote path: " + path); + + StorageReference reference = this.getReference(path); + + reference.getStream(new StreamDownloadTask.StreamProcessor() { + @Override + public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException { + int indexOfLastSlash = localPath.lastIndexOf("/"); + String pathMinusFileName = indexOfLastSlash>0 ? localPath.substring(0, indexOfLastSlash) + "/" : "/"; + String filename = indexOfLastSlash>0 ? localPath.substring(indexOfLastSlash+1) : localPath; + File fileWithJustPath = new File(pathMinusFileName); + fileWithJustPath.mkdirs(); + 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) { + Log.d(TAG, "Got download progress " + taskSnapshot); + WritableMap event = getDownloadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_STATE_CHANGED, path, event); + } + }).addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Download is paused " + taskSnapshot); + WritableMap event = getDownloadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_STATE_CHANGED, path, event); + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Successfully downloaded file " + taskSnapshot); + WritableMap resp = getDownloadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_DOWNLOAD_SUCCESS, path, resp); + //TODO: A little hacky, but otherwise throws a not consumed exception + resp = getDownloadTaskAsMap(taskSnapshot); + callback.invoke(null, resp); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + Log.e(TAG, "Failed to download file " + exception.getMessage()); + //TODO: JS Error event + callback.invoke(makeErrorPayload(1, exception)); + } + }); + } + + @ReactMethod + public void putFile(final String path, final String localPath, final ReadableMap metadata, final Callback callback) { + StorageReference reference = this.getReference(path); + + Log.i(TAG, "Upload file: " + localPath + " to " + path); + + try { + Uri file; + if (localPath.startsWith("content://")) { + String realPath = getRealPathFromURI(localPath); + file = Uri.fromFile(new File(realPath)); + } else { + file = Uri.fromFile(new File(localPath)); + } + + StorageMetadata md = buildMetadataFromMap(metadata); + UploadTask uploadTask = reference.putFile(file, 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()); + //TODO: JS Error event + callback.invoke(makeErrorPayload(1, exception)); + } + }) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Successfully uploaded file " + taskSnapshot); + WritableMap resp = getUploadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_UPLOAD_SUCCESS, path, resp); + //TODO: A little hacky, but otherwise throws a not consumed exception + resp = getUploadTaskAsMap(taskSnapshot); + callback.invoke(null, resp); + } + }) + .addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Got upload progress " + taskSnapshot); + WritableMap event = getUploadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_STATE_CHANGED, path, event); + } + }) + .addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { + Log.d(TAG, "Upload is paused " + taskSnapshot); + WritableMap event = getUploadTaskAsMap(taskSnapshot); + handleStorageEvent(STORAGE_STATE_CHANGED, path, event); + } + }); + } catch (Exception ex) { + final int errorCode = 2; + callback.invoke(makeErrorPayload(errorCode, ex)); + } + } + + //Firebase.Storage methods + @ReactMethod + public void setMaxDownloadRetryTime(final double milliseconds) { + FirebaseStorage.getInstance().setMaxDownloadRetryTimeMillis((long)milliseconds); + } + + @ReactMethod + public void setMaxOperationRetryTime(final double milliseconds) { + FirebaseStorage.getInstance().setMaxOperationRetryTimeMillis((long)milliseconds); + } + + @ReactMethod + public void setMaxUploadRetryTime(final double milliseconds) { + FirebaseStorage.getInstance().setMaxUploadRetryTimeMillis((long)milliseconds); + } + + private StorageReference getReference(String path) { + if (path.startsWith("url::")) { + String url = path.substring(5); + return FirebaseStorage.getInstance().getReferenceFromUrl(url); + } else { + return FirebaseStorage.getInstance().getReference(path); + } + } + + private StorageMetadata buildMetadataFromMap(ReadableMap metadata) { + StorageMetadata.Builder metadataBuilder = new StorageMetadata.Builder(); + Map m = Utils.recursivelyDeconstructReadableMap(metadata); + + for (Map.Entry entry : m.entrySet()) { + metadataBuilder.setCustomMetadata(entry.getKey(), entry.getValue().toString()); + } + + return metadataBuilder.build(); + } + + private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) { + WritableMap metadata = Arguments.createMap(); + metadata.putString("bucket", storageMetadata.getBucket()); + metadata.putString("generation", storageMetadata.getGeneration()); + metadata.putString("metageneration", storageMetadata.getMetadataGeneration()); + metadata.putString("fullPath", storageMetadata.getPath()); + metadata.putString("name", storageMetadata.getName()); + metadata.putDouble("size", storageMetadata.getSizeBytes()); + metadata.putDouble("timeCreated", storageMetadata.getCreationTimeMillis()); + metadata.putDouble("updated", storageMetadata.getUpdatedTimeMillis()); + metadata.putString("md5hash", storageMetadata.getMd5Hash()); + metadata.putString("cacheControl", storageMetadata.getCacheControl()); + metadata.putString("contentDisposition", storageMetadata.getContentDisposition()); + metadata.putString("contentEncoding", storageMetadata.getContentEncoding()); + metadata.putString("contentLanguage", storageMetadata.getContentLanguage()); + metadata.putString("contentType", storageMetadata.getContentType()); + + WritableArray downloadURLs = Arguments.createArray(); + for (Uri uri : storageMetadata.getDownloadUrls()) { + downloadURLs.pushString(uri.getPath()); + } + metadata.putArray("downloadURLs", downloadURLs); + + WritableMap customMetadata = Arguments.createMap(); + for (String key : storageMetadata.getCustomMetadataKeys()) { + customMetadata.putString(key, storageMetadata.getCustomMetadata(key)); + } + metadata.putMap("customMetadata", customMetadata); + + return metadata; + } + + 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 getDownloadTaskAsMap(final StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap resp = Arguments.createMap(); + resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred()); + resp.putString("ref", taskSnapshot.getStorage().getPath()); + resp.putString("state", this.getTaskStatus(taskSnapshot.getTask())); + resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount()); + + return resp; + } + + private WritableMap getUploadTaskAsMap(final UploadTask.TaskSnapshot taskSnapshot) { + StorageMetadata d = taskSnapshot.getMetadata(); + + WritableMap resp = Arguments.createMap(); + resp.putDouble("bytesTransferred", taskSnapshot.getBytesTransferred()); + resp.putString("downloadUrl", taskSnapshot.getDownloadUrl() != null ? taskSnapshot.getDownloadUrl().toString() : null); + resp.putString("ref", taskSnapshot.getStorage().getPath()); + resp.putString("state", this.getTaskStatus(taskSnapshot.getTask())); + resp.putDouble("totalBytes", taskSnapshot.getTotalByteCount()); + + if (taskSnapshot.getMetadata() != null) { + WritableMap metadata = getMetadataAsMap(taskSnapshot.getMetadata()); + resp.putMap("metadata", metadata); + } + + return resp; + } + + private String getTaskStatus(StorageTask task) { + if (task.isInProgress()) { + return "RUNNING"; + } else if (task.isPaused()) { + return "PAUSED"; + } else if (task.isSuccessful() || task.isComplete()) { + return "SUCCESS"; + } else if (task.isCanceled()) { + return "CANCELLED"; + } else if (task.getException() != null) { + return "ERROR"; + } else { + return "UNKNOWN"; + } + } + + private void handleStorageEvent(final String name, final String path, WritableMap body) { + WritableMap evt = Arguments.createMap(); + evt.putString("eventName", name); + evt.putString("path", path); + evt.putMap("body", body); + + Utils.sendEvent(this.getReactApplicationContext(), STORAGE_EVENT, evt); + } + + private void handleStorageError(final String path, final StorageException error) { + WritableMap body = Arguments.createMap(); + body.putString("path", path); + body.putString("message", error.getMessage()); + + WritableMap evt = Arguments.createMap(); + evt.putString("eventName", STORAGE_ERROR); + evt.putMap("body", body); + + Utils.sendEvent(this.getReactApplicationContext(), STORAGE_ERROR, evt); + } + + private WritableMap makeErrorPayload(double code, Exception ex) { + WritableMap error = Arguments.createMap(); + error.putDouble("code", code); + error.putString("message", ex.getMessage()); + return error; + } + + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + + constants.put(DocumentDirectory, 0); + constants.put(DocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath()); + constants.put(TemporaryDirectoryPath, null); + constants.put(PicturesDirectoryPath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath()); + constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath()); + constants.put(FileTypeRegular, 0); + constants.put(FileTypeDirectory, 1); + + File externalStorageDirectory = Environment.getExternalStorageDirectory(); + if (externalStorageDirectory != null) { + constants.put(ExternalStorageDirectoryPath, externalStorageDirectory.getAbsolutePath()); + } else { + constants.put(ExternalStorageDirectoryPath, null); + } + + File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null); + if (externalDirectory != null) { + constants.put(ExternalDirectoryPath, externalDirectory.getAbsolutePath()); + } else { + constants.put(ExternalDirectoryPath, null); + } + + return constants; + } +} diff --git a/docs/api/analytics.md b/docs/api/analytics.md new file mode 100644 index 0000000..daefe5c --- /dev/null +++ b/docs/api/analytics.md @@ -0,0 +1,80 @@ +# 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): void` + +Log a custom event with optional params. + +```javascript +firestack.analytics().logEvent('clicked_advert', { id: 1337 }); +``` + +#### `setAnalyticsCollectionEnabled(enabled: boolean): void` + +Sets whether analytics collection is enabled for this app on this device. + +```javascript +firestack.analytics().setAnalyticsCollectionEnabled(false); +``` + +#### `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'); +``` + +#### `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); +``` + +#### `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); +``` + +#### `setUserId(id: string): void` + +Gives a user a uniqiue identificaition. + +```javascript +const id = firestack.auth().currentUser.uid; + +firestack.analytics().setUserId(id); +``` + +#### `setUserProperty(name: string, value: string): void` + +Sets a key/value pair of data on the current user. + +```javascript +firestack.analytics().setUserProperty('nickname', 'foobar'); +``` diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 0000000..84cb5bb --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1,284 @@ +# 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). + +> Authentication requires Google Play services to be installed on Android. + +## Auth + +### 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.md#user) class documentation for further usage. + +### 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. + +```javascript +class Example extends React.Component { + + constructor() { + super(); + this.unsubscribe = null; + } + + componentDidMount() { + this.unsubscribe = firestack.auth().onAuthStateChanged((user) => { + if (user) { + // User is signed in. + } + }); + } + + componentWillUnmount() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } + +} +``` + +#### [`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. + +```javascript +firestack.auth().createUserWithEmailAndPassword('foo@bar.com', '123456') + .then((user) => { + console.log('user created', user) + }) + .catch((err) => { + console.error('An error occurred', err); + }); +``` + +#### [`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: + +```javascript +firestack.auth().signInWithEmailAndPassword('foo@bar.com', '123456') + .then((user) => { + console.log('User successfully logged in', user) + }) + .catch((err) => { + console.error('User signin error', err); + }); +``` + +#### [`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. + +```javascript +firestack.auth().signInAnonymously() + .then((user) => { + console.log('Anonymous user successfully logged in', user) + }) + .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 +} +``` + +```javascript +const credential = { + provider: 'facebook', + token: '12345', + secret: '6789', +}; + +firestack.auth().signInWithCredential(credential) + .then((user) => { + console.log('User successfully signed in', user) + }) + .catch((err) => { + console.error('User signin error', err); + }); +``` + +#### [`signInWithCustomToken(token: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#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('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. Unlike the web SDK, the email will contain a password reset link rather than a code. + +```javascript +firestack.auth().sendPasswordResetEmail('foo@bar.com') + .then(() => { + console.log('Password reset email sent'); + }) + .catch((error) => { + console.error('Unable send password reset email', 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. + +```javascript +firestack.auth().signOut() + .then(() => { + console.log('User signed out successfully'); + }) + .catch(); +``` + +## User + +User class returned from `firestack.auth().currentUser`. + +### Properties + +##### `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().currentUser + .delete() + .then() + .catch(); +``` + +#### [`getToken(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#getToken) + +Returns the users authentication token. + +```javascript +firestack.auth().currentUser + .getToken() + .then((token) => {}) + .catch(); +``` + + +#### [`reauthenticate(credential: Object): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reauthenticate) + +Reauthenticate the current user with credentials: + +```javascript +{ + provider: string, + token: string, + secret: string +} +``` + +```javascript +const credentials = { + provider: 'facebook.com', + token: '12345', + secret: '6789', +}; + +firestack.auth().currentUser + .reauthenticate(credentials) + .then() + .catch(); +``` + +#### [`reload(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reload) + +Refreshes the current user. + +```javascript +firestack.auth().currentUser + .reload() + .then((user) => {}) + .catch(); +``` + +#### [`sendEmailVerification(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#sendEmailVerification) + +Sends a verification email to a user. This will Promise reject is the user is anonymous. + +```javascript +firestack.auth().currentUser + .sendEmailVerification() + .then() + .catch(); +``` + +#### [updateEmail(email: string)](https://firebase.google.com/docs/reference/js/firebase.User#updateEmail) + +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. + +```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().updatePassword('foobar1234') + .then() + .catch(); +``` + +#### [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: + +```javascript +{ + displayName: string, + photoURL: string, +} +``` + +```javascript +firestack.auth() + .updateProfile({ + displayName: 'Ari Lerner' + }) + .then() + .catch(); +``` 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 @@ + diff --git a/docs/api/database.md b/docs/api/database.md new file mode 100644 index 0000000..8597984 --- /dev/null +++ b/docs/api/database.md @@ -0,0 +1,220 @@ + +# 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(); + }); +``` + +Read for export: +```javascript +firestack.database() + .ref('posts') + .on('value', (snapshot) => { + const value = snapshot.exportVal(); + }); +``` +This includes hidden properties like `.priority` + +Basic write example: +```javascript +firestack.database() + .ref('posts/1234') + .set({ + title: 'My awesome post', + content: 'Some awesome content', + }); +``` + +Test value exists at location: +```javascript +firestack.database() + .ref('posts/1234') + .on('value', (snapshot) => { + const exists = snapshot.exists(); + }); +``` + +Basic write with priority example: +```javascript +firestack.database() + .ref('posts/1235') + .setWithPriority({ + title: 'Another Awesome Post', + content: 'Some awesome content', + }, 10); +``` +Useful for `orderByPriority` queries. + + +Transaction Support: +```javascript +firestack.database() + .ref('posts/1234/title') + .transaction((title) => 'My Awesome Post'); +``` + +## 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)} + /> + +