├── .babelrc ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .watchmanconfig ├── Contributing.md ├── Firestack.podspec ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── fullstack │ └── firestack │ ├── FirestackInstanceIdService.java │ ├── FirestackMessagingService.java │ ├── FirestackModule.java │ ├── FirestackPackage.java │ ├── Utils.java │ ├── analytics │ └── FirestackAnalytics.java │ ├── auth │ └── FirestackAuth.java │ ├── database │ ├── FirestackDatabase.java │ └── FirestackDatabaseReference.java │ ├── messaging │ └── FirestackMessaging.java │ └── storage │ └── FirestackStorage.java ├── bin ├── cocoapods.sh └── prepare.sh ├── docs ├── api │ ├── analytics.md │ ├── authentication.md │ ├── cloud-messaging.md │ ├── database.md │ ├── events.md │ ├── presence.md │ ├── remote-config.md │ ├── server-value.md │ └── storage.md ├── firebase-setup.md ├── installation.android.md ├── installation.ios.md └── redux.md ├── firestack.android.js ├── firestack.ios.js ├── index.js ├── ios ├── Firestack.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcuserdata │ │ ├── Cheol.xcuserdatad │ │ └── xcschemes │ │ │ ├── Firestack.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── auser.xcuserdatad │ │ └── xcschemes │ │ ├── Firestack.xcscheme │ │ └── xcschememanagement.plist ├── Firestack │ ├── Firestack.h │ ├── Firestack.m │ ├── FirestackAnalytics.h │ ├── FirestackAnalytics.m │ ├── FirestackAuth.h │ ├── FirestackAuth.m │ ├── FirestackCloudMessaging.h │ ├── FirestackCloudMessaging.m │ ├── FirestackDatabase.h │ ├── FirestackDatabase.m │ ├── FirestackErrors.h │ ├── FirestackErrors.m │ ├── FirestackEvents.h │ ├── FirestackStorage.h │ └── FirestackStorage.m ├── Podfile ├── Podfile.template └── buildScript.sh ├── lib ├── constants.js ├── firestack.js ├── firestackModule.js ├── flow.js ├── modules │ ├── analytics.js │ ├── auth │ │ ├── Email.js │ │ └── index.js │ ├── base.js │ ├── database │ │ ├── disconnect.js │ │ ├── index.js │ │ ├── query.js │ │ ├── reference.js │ │ └── snapshot.js │ ├── messaging.js │ ├── presence.js │ ├── remoteConfig.js │ ├── storage │ │ ├── index.js │ │ └── reference.js │ └── user.js └── utils │ ├── __tests__ │ ├── log-test.js │ ├── promisify-test.js │ └── singleton-test.js │ ├── eventEmitter.js │ ├── index.js │ ├── log.js │ └── singleton.js ├── package.json └── test ├── firestack.test.js ├── mocha.opts └── test-helper.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "ecmaFeatures": { 5 | "jsx": true 6 | }, 7 | "plugins": [ 8 | "flowtype" 9 | ], 10 | "env": { 11 | "es6": true, 12 | "jasmine": true 13 | }, 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "experimentalObjectRestSpread": true 17 | } 18 | }, 19 | "rules": { 20 | "class-methods-use-this": 0, 21 | "no-underscore-dangle": 0, 22 | "no-use-before-define": 0, 23 | "arrow-body-style": 0, 24 | "import/prefer-default-export": 0, 25 | "radix": 0, 26 | "new-cap": 0, 27 | "max-len": 0, 28 | "no-continue": 0, 29 | "no-console": 0, 30 | "global-require": 0, 31 | "import/extensions": 0, 32 | "import/no-unresolved": 0, 33 | "import/no-extraneous-dependencies": 0, 34 | "react/jsx-filename-extension": 0 35 | }, 36 | "globals": { 37 | "__DEV__": true, 38 | "window": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | 4 | # Some modules have their own node_modules with overlap 5 | .*/node_modules/node-haste/.* 6 | 7 | 8 | # React Native problems 9 | .*/node_modules/react-native/Libraries/Animated/src/AnimatedInterpolation.js 10 | .*/node_modules/react-native/Libraries/Animated/src/Interpolation.js 11 | .*/node_modules/react-native/Libraries/BugReporting/dumpReactTree.js 12 | .*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationHeader.js 13 | .*/node_modules/react-native/Libraries/CustomComponents/NavigationExperimental/NavigationPagerStyleInterpolater.js 14 | .*/node_modules/react-native/Libraries/Experimental/WindowedListView.js 15 | .*/node_modules/react-native/Libraries/Image/Image.io.js 16 | .*/node_modules/react-native/Libraries/NavigationExperimental/NavigationExperimental.js 17 | .*/node_modules/react-native/Libraries/NavigationExperimental/NavigationHeaderStyleInterpolator.js 18 | .*/node_modules/react-native/Libraries/Network/FormData.js 19 | .*/node_modules/react-native/Libraries/ReactIOS/YellowBox.js 20 | 21 | 22 | 23 | # Ignore react and fbjs where there are overlaps, but don't ignore 24 | # anything that react-native relies on 25 | .*/node_modules/fbjs/lib/Map.js 26 | .*/node_modules/fbjs/lib/ErrorUtils.js 27 | 28 | # Flow has a built-in definition for the 'react' module which we prefer to use 29 | # over the currently-untyped source 30 | .*/node_modules/react/react.js 31 | .*/node_modules/react/lib/React.js 32 | .*/node_modules/react/lib/ReactDOM.js 33 | 34 | .*/__mocks__/.* 35 | .*/__tests__/.* 36 | 37 | .*/commoner/test/source/widget/share.js 38 | 39 | # Ignore commoner tests 40 | .*/node_modules/commoner/test/.* 41 | 42 | # See https://github.com/facebook/flow/issues/442 43 | .*/react-tools/node_modules/commoner/lib/reader.js 44 | 45 | # Ignore jest 46 | .*/node_modules/jest-cli/.* 47 | 48 | # Ignore Website 49 | .*/website/.* 50 | 51 | # Ignore generators 52 | .*/local-cli/generator.* 53 | 54 | # Ignore BUCK generated folders 55 | .*\.buckd/ 56 | 57 | .*/node_modules/is-my-json-valid/test/.*\.json 58 | .*/node_modules/iconv-lite/encodings/tables/.*\.json 59 | .*/node_modules/y18n/test/.*\.json 60 | .*/node_modules/spdx-license-ids/spdx-license-ids.json 61 | .*/node_modules/spdx-exceptions/index.json 62 | .*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json 63 | .*/node_modules/resolve/lib/core.json 64 | .*/node_modules/jsonparse/samplejson/.*\.json 65 | .*/node_modules/json5/test/.*\.json 66 | .*/node_modules/ua-parser-js/test/.*\.json 67 | .*/node_modules/builtin-modules/builtin-modules.json 68 | .*/node_modules/binary-extensions/binary-extensions.json 69 | .*/node_modules/url-regex/tlds.json 70 | .*/node_modules/joi/.*\.json 71 | .*/node_modules/isemail/.*\.json 72 | .*/node_modules/tr46/.*\.json 73 | .*/node_modules/protobufjs/src/bower.json 74 | .*/node_modules/grpc/node_modules/protobufjs/src/bower.json 75 | 76 | [include] 77 | node_modules/fbjs/lib 78 | 79 | [libs] 80 | lib/flow.js 81 | node_modules/react-native/Libraries/react-native/react-native-interface.js 82 | node_modules/react-native/flow 83 | node_modules/fbjs/flow/lib 84 | 85 | [options] 86 | module.system=haste 87 | 88 | experimental.strict_type_args=true 89 | unsafe.enable_getters_and_setters=true 90 | 91 | esproposal.class_static_fields=enable 92 | esproposal.class_instance_fields=enable 93 | 94 | munge_underscores=true 95 | 96 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 97 | 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' 98 | 99 | suppress_type=$FlowIssue 100 | suppress_type=$FlowFixMe 101 | suppress_type=$FixMe 102 | 103 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 104 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 105 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | *.DS_Store 4 | 5 | # Xcode 6 | *.pbxuser 7 | *.mode1v3 8 | *.mode2v3 9 | *.perspectivev3 10 | *.xcuserstate 11 | project.xcworkspace/ 12 | xcuserdata/ 13 | 14 | # Android 15 | 16 | # Built application files 17 | android/*/build/ 18 | 19 | # Crashlytics configuations 20 | android/com_crashlytics_export_strings.xml 21 | 22 | # Local configuration file (sdk path, etc) 23 | android/local.properties 24 | 25 | # Gradle generated files 26 | android/.gradle/ 27 | 28 | # Signing files 29 | android/.signing/ 30 | 31 | # User-specific configurations 32 | android/.idea/gradle.xml 33 | android/.idea/libraries/ 34 | android/.idea/workspace.xml 35 | android/.idea/tasks.xml 36 | android/.idea/.name 37 | android/.idea/compiler.xml 38 | android/.idea/copyright/profiles_settings.xml 39 | android/.idea/encodings.xml 40 | android/.idea/misc.xml 41 | android/.idea/modules.xml 42 | android/.idea/scopes/scope_settings.xml 43 | android/.idea/vcs.xml 44 | android/*.iml 45 | 46 | # OS-specific files 47 | .DS_Store 48 | .DS_Store? 49 | ._* 50 | .Spotlight-V100 51 | .Trashes 52 | ehthumbs.db 53 | Thumbs.dbandroid/gradle 54 | android/gradlew 55 | android/build 56 | android/gradlew.bat 57 | android/gradle/ 58 | .idea 59 | .idea 60 | coverage 61 | yarn.lock 62 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.iml 4 | .*.haste_cache.* 5 | .DS_Store 6 | .idea 7 | .babelrc 8 | .eslintrc 9 | npm-debug.log 10 | src/ 11 | examples/ 12 | public/ 13 | scripts/ 14 | test/ -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": [ 3 | ".git", 4 | "node_modules", 5 | "android/build", 6 | "android/.idea", 7 | "android/.gradle", 8 | "android/gradle", 9 | ".idea" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing guide 2 | 3 | This is an in-progress guide to help guide you in understanding how Firestack works with the goal to help on-board your contributions. If you have any questions, comments, or concerns, feel free to leave it here or join the [gitter channel at https://gitter.im/fullstackreact/react-native-firestack](https://gitter.im/fullstackreact/react-native-firestack). 4 | 5 | ## Contribution methods 6 | 7 | Contributing is easy. Make a fork of the project on [github](https://github.com/fullstackreact/react-native-firestack). Clone this repo on your machine and work on the edits there. 8 | 9 | ```shell 10 | git clone https://github.com/[your_name]/react-native-firestack.git 11 | cd react-native-firestack 12 | npm install 13 | ``` 14 | 15 | We have an [Example app - FirestackApp](https://github.com/fullstackreact/FirestackApp) which we use to demonstrate and test features (until we can get a proper testing environment). Currently, our workflow looks like this: 16 | 17 | 1. Write JS/native feature 18 | 2. `rsync` the local library to your `node_modules` directory (react-native does not play well with symlinks). 19 | For instance, running the following in the firestackApp root directory. Make sure you replace the `~/Development/react-native/mine/react-native-firestack` with the path of your cloned repo on your drive: 20 | 21 | ```javascript 22 | rsync -avhW --delete \ 23 | --exclude='node_modules' \ 24 | --exclude='.git' \ 25 | --exclude='coverage' \ 26 | ~/Development/react-native/mine/react-native-firestack/ \ 27 | ./node_modules/react-native-firestack/ 28 | ``` 29 | 30 | 3. Test in-app 31 | 4. Update README.md with bugfix/feature 32 | 5. Create a pull request (PR) 33 | 34 | ## High level 35 | 36 | ## How it works technically 37 | 38 | Firestack is broken up by functional modules which control/interact with the different features of Firebase. I.e. there is a database module, which maps to the Real-Time Database feature in Firebase, Analytics maps to the Firebase analytics stack. 39 | 40 | When the user creates a new instance of Firestack, they are creating an instance of the JS class defined in `lib/firestack.js`. 41 | 42 | ```javascript 43 | // This creates a JS instance of the 44 | // Firestack class 45 | const firestack = new Firestack({}); 46 | ``` 47 | 48 | Each of the modules in Firestack can be accessed through this instance. For instance, when we want to access the real-time database through the `firestack` instance, the JS API exposes a `database` accessor. 49 | 50 | For instance, when interacting with the database from the instance above, we would call `.database` to get access to a singleton instance of the JS `Database` class defined in `lib/modules/database.js`. 51 | 52 | ### Database walk-through 53 | 54 | ```javascript 55 | const db = firestack.database; 56 | ``` 57 | 58 | The `lib/modules/database.js` file exports two classes, one called `Database` and the other called `DatabaseRef`. Essentially, the `Database` class is a wrapper class that provides a handful of methods to forward off to a `DatabaseRef` instance. 59 | 60 | The `DatabaseRef` class defines the actual interaction with the native Firebase SDK. Let's look at the `getAt` method as an example of how the JS side interacts with the native-side and back. 61 | 62 | When the user accessess a Firebase ref, the `Database` instance creates a new instance of the `DatabaseRef` JS class. 63 | 64 | ```javascript 65 | const ref = db.ref('/events'); 66 | ``` 67 | 68 | The `DatabaseRef` class is the wrapper that maps to Firebase database points. For efficiency, the `paths` are stored as an array so we can walk up and down the firebase database using the `parent()` and `child()` methods on a database ref. 69 | 70 | Calling `getAt()` on the `ref` (an instance of the `DatabaseRef` class) will make a call to the **native** SDK using a method called `promisify()` 71 | 72 | ```javascript 73 | class DatabaseRef { 74 | // ... 75 | getAt(key) { 76 | let path = this.path; 77 | if (key && typeof(key) == 'string') { 78 | path = `${path}${separator}${key}` 79 | } 80 | return promisify('onOnce', FirestackDatabase)(path); 81 | } 82 | } 83 | ``` 84 | 85 | Ignoring the first few lines (which are helpers to add to the `path`, which we'll look at shortly), the `promisify()` function (defined in `lib/promisify.js`) takes two arguments: 86 | 87 | 1. The 'string' name of the native function to call 88 | 2. The native module we want to call it on 89 | 90 | The `promisify()` function returns a function that returns a `Promise` object in JS. This returned function calls the native function with a React-Native callback. When the React Native function calls the callback function, the Promise is resolved. 91 | 92 | Getting back to the Database example, the `getAt()` function (which has an alias of `get`) calls the `onOnce` function on the `FirestackDatabase` native module. Each platform has their own native module version for each feature area of Firebase. 93 | 94 | Every function on the `DatabaseRef` class is called with the `path` from Firebase as well as it's other options. 95 | 96 | Let's look at the `onOnce` function of the iOS version of `FirestackDatabase` implemented in `ios/Firestack/FirestackDatabase.m`: 97 | 98 | ``` 99 | // This might differ from the current code, but 100 | // is implemented this way at the time of the writing 101 | // of this document 102 | RCT_EXPORT_METHOD(onOnce:(NSString *) path 103 | name:(NSString *) name 104 | callback:(RCTResponseSenderBlock) callback) 105 | { 106 | int eventType = [self eventTypeFromName:name]; 107 | 108 | FIRDatabaseReference *ref = [self getRefAtPath:path]; 109 | [ref observeSingleEventOfType:eventType 110 | withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { 111 | callback(@[[NSNull null], [self snapshotToDict:snapshot]]); 112 | } 113 | withCancelBlock:^(NSError * _Nonnull error) { 114 | NSLog(@"Error onDBEventOnce: %@", [error debugDescription]); 115 | callback(@[@{ 116 | @"error": @"onceError", 117 | @"msg": [error debugDescription] 118 | }]); 119 | }]; 120 | } 121 | ``` 122 | 123 | Every native function (in either iOS or Android) is expected to accept a single callback as the final argument. The `onOnce` function accepts the path (as the first argument) and the name of the event we're interested in (such as `value`) and uses the Native SDK to set up the appropriate functionality. When the function has been called and completed, the callback is called with an error on failure and with success on success. 124 | 125 | > An error response is considered one which the first argument is non-null. Therefore, to send a successful response, the first value when calling the callback should be null to indicate success. 126 | 127 | ## Adding functionality 128 | 129 | // TODO -------------------------------------------------------------------------------- /Firestack.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read('package.json')) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "Firestack" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.description = <<-DESC 10 | Wanna integrate firebase into your app using React Native? 11 | DESC 12 | s.homepage = "http://fullstackreact.com" 13 | s.license = package['license'] 14 | s.author = "Ari Lerner" 15 | s.source = { :git => "https://github.com/fullstackreact/react-native-firestack.git", :tag => "v#{s.version}" } 16 | s.social_media_url = 'http://twitter.com/fullstackio' 17 | s.platform = :ios, "8.0" 18 | s.header_dir = 'ios/Firestack' 19 | s.preserve_paths = 'README.md', 'package.json', '*.js' 20 | s.source_files = 'ios/Firestack/*.{h,m}' 21 | s.dependency 'React' 22 | s.dependency 'Firebase/Auth' 23 | s.dependency 'Firebase/Core' 24 | s.dependency 'Firebase/Database' 25 | s.dependency 'Firebase/Messaging' 26 | s.dependency 'Firebase/RemoteConfig' 27 | s.dependency 'Firebase/Storage' 28 | end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Ari Lerner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## We've built our own RNFirebase: https://github.com/invertase/react-native-firebase 2 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // START - required to allow working on this project inside Android Studio 2 | // YES, jcenter is required twice - it somehow tricks studio into compiling deps below 3 | // doesn't break anything anywhere else and projects using this lib work as normal 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.1.3' 10 | } 11 | } 12 | // END 13 | 14 | apply plugin: 'com.android.library' 15 | 16 | android { 17 | compileSdkVersion 23 18 | buildToolsVersion "23.0.1" 19 | 20 | defaultConfig { 21 | minSdkVersion 16 22 | targetSdkVersion 23 23 | versionCode 1 24 | versionName "1.0" 25 | multiDexEnabled true 26 | } 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | } 31 | } 32 | } 33 | 34 | // START - required to allow working on this project inside Android Studio 35 | // YES, jcenter is required twice - it somehow tricks studio into compiling deps below 36 | // doesn't break anything anywhere else and projects using this lib work as normal 37 | // you'll now have code completion/validation and all the other AS goodies. 38 | allprojects { 39 | repositories { 40 | jcenter() 41 | } 42 | } 43 | // END 44 | 45 | dependencies { 46 | compile 'com.facebook.react:react-native:0.20.+' 47 | compile 'com.google.android.gms:play-services-base:9.8.0' 48 | compile 'com.google.firebase:firebase-core:9.8.0' 49 | compile 'com.google.firebase:firebase-config:9.8.0' 50 | compile 'com.google.firebase:firebase-auth:9.8.0' 51 | compile 'com.google.firebase:firebase-analytics:9.8.0' 52 | compile 'com.google.firebase:firebase-database:9.8.0' 53 | compile 'com.google.firebase:firebase-storage:9.8.0' 54 | compile 'com.google.firebase:firebase-messaging:9.8.0' 55 | } 56 | 57 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackInstanceIdService.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.util.Log; 4 | import android.os.Bundle; 5 | import android.content.Intent; 6 | 7 | import com.google.firebase.iid.FirebaseInstanceId; 8 | import com.google.firebase.iid.FirebaseInstanceIdService; 9 | 10 | import io.fullstack.firestack.messaging.FirestackMessaging; 11 | 12 | public class FirestackInstanceIdService extends FirebaseInstanceIdService { 13 | 14 | private static final String TAG = "FSInstanceIdService"; 15 | 16 | /** 17 | * 18 | */ 19 | @Override 20 | public void onTokenRefresh() { 21 | String refreshedToken = FirebaseInstanceId.getInstance().getToken(); 22 | Log.d(TAG, "Refreshed token: " + refreshedToken); 23 | Intent i = new Intent(FirestackMessaging.INTENT_NAME_TOKEN); 24 | Bundle bundle = new Bundle(); 25 | bundle.putString("token", refreshedToken); 26 | i.putExtras(bundle); 27 | sendBroadcast(i); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackMessagingService.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Intent; 4 | import android.util.Log; 5 | 6 | import com.google.firebase.messaging.FirebaseMessagingService; 7 | import com.google.firebase.messaging.RemoteMessage; 8 | import com.google.firebase.messaging.SendException; 9 | 10 | import io.fullstack.firestack.messaging.FirestackMessaging; 11 | 12 | public class FirestackMessagingService extends FirebaseMessagingService { 13 | 14 | private static final String TAG = "FSMessagingService"; 15 | 16 | @Override 17 | public void onMessageReceived(RemoteMessage remoteMessage) { 18 | Log.d(TAG, "Remote message received"); 19 | // debug 20 | Log.d(TAG, "From: " + remoteMessage.getFrom()); 21 | 22 | if (remoteMessage.getData().size() > 0) { 23 | Log.d(TAG, "Message data payload: " + remoteMessage.getData()); 24 | } 25 | 26 | if (remoteMessage.getNotification() != null) { 27 | Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody()); 28 | } 29 | 30 | Intent i = new Intent(FirestackMessaging.INTENT_NAME_NOTIFICATION); 31 | i.putExtra("data", remoteMessage); 32 | sendOrderedBroadcast(i, null); 33 | 34 | } 35 | 36 | @Override 37 | public void onMessageSent(String msgId) { 38 | // Called when an upstream message has been successfully sent to the GCM connection server. 39 | Log.d(TAG, "upstream message has been successfully sent"); 40 | Intent i = new Intent(FirestackMessaging.INTENT_NAME_SEND); 41 | i.putExtra("msgId", msgId); 42 | sendOrderedBroadcast(i, null); 43 | } 44 | 45 | @Override 46 | public void onSendError(String msgId, Exception exception) { 47 | // Called when there was an error sending an upstream message. 48 | Log.d(TAG, "error sending an upstream message"); 49 | Intent i = new Intent(FirestackMessaging.INTENT_NAME_SEND); 50 | i.putExtra("msgId", msgId); 51 | i.putExtra("hasError", true); 52 | SendException sendException = (SendException) exception; 53 | i.putExtra("errorCode", sendException.getErrorCode()); 54 | switch(sendException.getErrorCode()){ 55 | case SendException.ERROR_INVALID_PARAMETERS: 56 | i.putExtra("errorMessage", "Message was sent with invalid parameters."); 57 | break; 58 | case SendException.ERROR_SIZE: 59 | i.putExtra("errorMessage", "Message exceeded the maximum payload size."); 60 | break; 61 | case SendException.ERROR_TOO_MANY_MESSAGES: 62 | i.putExtra("errorMessage", "App has too many pending messages so this one was dropped."); 63 | break; 64 | case SendException.ERROR_TTL_EXCEEDED: 65 | i.putExtra("errorMessage", "Message time to live (TTL) was exceeded before the message could be sent."); 66 | break; 67 | case SendException.ERROR_UNKNOWN: 68 | default: 69 | i.putExtra("errorMessage", "Unknown error."); 70 | break; 71 | } 72 | sendOrderedBroadcast(i, null); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackModule.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import java.util.Map; 4 | import java.util.HashMap; 5 | 6 | import android.util.Log; 7 | import android.content.Context; 8 | import android.support.annotation.Nullable; 9 | 10 | import com.facebook.react.bridge.Callback; 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.ReadableMap; 13 | import com.facebook.react.bridge.WritableMap; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.LifecycleEventListener; 16 | import com.facebook.react.bridge.ReactApplicationContext; 17 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 18 | 19 | import com.google.android.gms.common.ConnectionResult; 20 | import com.google.android.gms.common.GoogleApiAvailability; 21 | 22 | import com.google.firebase.FirebaseApp; 23 | import com.google.firebase.FirebaseOptions; 24 | import com.google.firebase.database.ServerValue; 25 | 26 | interface KeySetterFn { 27 | String setKeyOrDefault(String a, String b); 28 | } 29 | 30 | @SuppressWarnings("WeakerAccess") 31 | public class FirestackModule extends ReactContextBaseJavaModule implements LifecycleEventListener { 32 | private static final String TAG = "Firestack"; 33 | private FirebaseApp app; 34 | 35 | public FirestackModule(ReactApplicationContext reactContext) { 36 | super(reactContext); 37 | } 38 | 39 | @Override 40 | public String getName() { 41 | return TAG; 42 | } 43 | 44 | private WritableMap getPlayServicesStatus() { 45 | GoogleApiAvailability gapi = GoogleApiAvailability.getInstance(); 46 | final int status = gapi.isGooglePlayServicesAvailable(getReactApplicationContext()); 47 | WritableMap result = Arguments.createMap(); 48 | result.putInt("status", status); 49 | if (status == ConnectionResult.SUCCESS) { 50 | result.putBoolean("isAvailable", true); 51 | } else { 52 | result.putBoolean("isAvailable", false); 53 | result.putBoolean("isUserResolvableError", gapi.isUserResolvableError(status)); 54 | result.putString("error", gapi.getErrorString(status)); 55 | } 56 | return result; 57 | } 58 | 59 | @ReactMethod 60 | public void configureWithOptions(final ReadableMap params, @Nullable final Callback onComplete) { 61 | Log.i(TAG, "configureWithOptions"); 62 | 63 | FirebaseOptions.Builder builder = new FirebaseOptions.Builder(); 64 | FirebaseOptions defaultOptions = FirebaseOptions.fromResource(getReactApplicationContext().getBaseContext()); 65 | 66 | if (defaultOptions == null) { 67 | defaultOptions = new FirebaseOptions.Builder().build(); 68 | } 69 | 70 | KeySetterFn fn = new KeySetterFn() { 71 | public String setKeyOrDefault( 72 | final String key, 73 | final String defaultValue) { 74 | if (params.hasKey(key)) { 75 | // User-set key 76 | final String val = params.getString(key); 77 | Log.d(TAG, "Setting " + key + " from params to: " + val); 78 | return val; 79 | } else if (defaultValue != null && !defaultValue.equals("")) { 80 | Log.d(TAG, "Setting " + key + " from params to: " + defaultValue); 81 | return defaultValue; 82 | } else { 83 | return null; 84 | } 85 | } 86 | }; 87 | 88 | String val = fn.setKeyOrDefault("applicationId", defaultOptions.getApplicationId()); 89 | if (val != null) builder.setApplicationId(val); 90 | 91 | val = fn.setKeyOrDefault("apiKey", defaultOptions.getApiKey()); 92 | if (val != null) builder.setApiKey(val); 93 | 94 | val = fn.setKeyOrDefault("gcmSenderID", defaultOptions.getGcmSenderId()); 95 | if (val != null) builder.setGcmSenderId(val); 96 | 97 | val = fn.setKeyOrDefault("storageBucket", defaultOptions.getStorageBucket()); 98 | if (val != null) builder.setStorageBucket(val); 99 | 100 | val = fn.setKeyOrDefault("databaseURL", defaultOptions.getDatabaseUrl()); 101 | if (val != null) builder.setDatabaseUrl(val); 102 | 103 | val = fn.setKeyOrDefault("databaseUrl", defaultOptions.getDatabaseUrl()); 104 | if (val != null) builder.setDatabaseUrl(val); 105 | 106 | val = fn.setKeyOrDefault("clientId", defaultOptions.getApplicationId()); 107 | if (val != null) builder.setApplicationId(val); 108 | 109 | 110 | // if (params.hasKey("applicationId")) { 111 | // final String applicationId = params.getString("applicationId"); 112 | // Log.d(TAG, "Setting applicationId from params " + applicationId); 113 | // builder.setApplicationId(applicationId); 114 | // } 115 | // if (params.hasKey("apiKey")) { 116 | // final String apiKey = params.getString("apiKey"); 117 | // Log.d(TAG, "Setting API key from params " + apiKey); 118 | // builder.setApiKey(apiKey); 119 | // } 120 | // if (params.hasKey("APIKey")) { 121 | // final String apiKey = params.getString("APIKey"); 122 | // Log.d(TAG, "Setting API key from params " + apiKey); 123 | // builder.setApiKey(apiKey); 124 | // } 125 | // if (params.hasKey("gcmSenderID")) { 126 | // final String gcmSenderID = params.getString("gcmSenderID"); 127 | // Log.d(TAG, "Setting gcmSenderID from params " + gcmSenderID ); 128 | // builder.setGcmSenderId(gcmSenderID); 129 | // } 130 | // if (params.hasKey("storageBucket")) { 131 | // final String storageBucket = params.getString("storageBucket"); 132 | // Log.d(TAG, "Setting storageBucket from params " + storageBucket); 133 | // builder.setStorageBucket(storageBucket); 134 | // } 135 | // if (params.hasKey("databaseURL")) { 136 | // final String databaseURL = params.getString("databaseURL"); 137 | // Log.d(TAG, "Setting databaseURL from params " + databaseURL); 138 | // builder.setDatabaseUrl(databaseURL); 139 | // } 140 | // if (params.hasKey("clientID")) { 141 | // final String clientID = params.getString("clientID"); 142 | // Log.d(TAG, "Setting clientID from params " + clientID); 143 | // builder.setApplicationId(clientID); 144 | // } 145 | 146 | try { 147 | Log.i(TAG, "Configuring app"); 148 | if (app == null) { 149 | app = FirebaseApp.initializeApp(getReactApplicationContext().getBaseContext(), builder.build()); 150 | } 151 | Log.i(TAG, "Configured"); 152 | 153 | WritableMap resp = Arguments.createMap(); 154 | resp.putString("msg", "success"); 155 | onComplete.invoke(null, resp); 156 | } catch (Exception ex) { 157 | Log.e(TAG, "ERROR configureWithOptions"); 158 | Log.e(TAG, ex.getMessage()); 159 | 160 | WritableMap resp = Arguments.createMap(); 161 | resp.putString("msg", ex.getMessage()); 162 | 163 | onComplete.invoke(resp); 164 | } 165 | } 166 | 167 | @ReactMethod 168 | public void serverValue(@Nullable final Callback onComplete) { 169 | WritableMap timestampMap = Arguments.createMap(); 170 | for (Map.Entry entry : ServerValue.TIMESTAMP.entrySet()) { 171 | timestampMap.putString(entry.getKey(), entry.getValue()); 172 | } 173 | 174 | WritableMap map = Arguments.createMap(); 175 | map.putMap("TIMESTAMP", timestampMap); 176 | if (onComplete != null) onComplete.invoke(null, map); 177 | } 178 | 179 | // Internal helpers 180 | @Override 181 | public void onHostResume() { 182 | WritableMap params = Arguments.createMap(); 183 | params.putBoolean("isForground", true); 184 | Utils.sendEvent(getReactApplicationContext(), "FirestackAppState", params); 185 | } 186 | 187 | @Override 188 | public void onHostPause() { 189 | WritableMap params = Arguments.createMap(); 190 | params.putBoolean("isForground", false); 191 | Utils.sendEvent(getReactApplicationContext(), "FirestackAppState", params); 192 | } 193 | 194 | @Override 195 | public void onHostDestroy() { 196 | 197 | } 198 | 199 | @Override 200 | public Map getConstants() { 201 | final Map constants = new HashMap<>(); 202 | constants.put("googleApiAvailability", getPlayServicesStatus()); 203 | 204 | // TODO remove once this has been moved on ios 205 | constants.put("serverValueTimestamp", ServerValue.TIMESTAMP); 206 | return constants; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/FirestackPackage.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.JavaScriptModule; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.UIManagerModule; 10 | import com.facebook.react.uimanager.ViewManager; 11 | 12 | import java.util.List; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | 16 | import io.fullstack.firestack.auth.FirestackAuth; 17 | import io.fullstack.firestack.storage.FirestackStorage; 18 | import io.fullstack.firestack.database.FirestackDatabase; 19 | import io.fullstack.firestack.analytics.FirestackAnalytics; 20 | import io.fullstack.firestack.messaging.FirestackMessaging; 21 | 22 | @SuppressWarnings("unused") 23 | public class FirestackPackage implements ReactPackage { 24 | private Context mContext; 25 | 26 | public FirestackPackage() { 27 | } 28 | /** 29 | * @param reactContext react application context that can be used to create modules 30 | * @return list of native modules to register with the newly created catalyst instance 31 | */ 32 | @Override 33 | public List createNativeModules(ReactApplicationContext reactContext) { 34 | List modules = new ArrayList<>(); 35 | modules.add(new FirestackModule(reactContext)); 36 | modules.add(new FirestackAuth(reactContext)); 37 | modules.add(new FirestackDatabase(reactContext)); 38 | modules.add(new FirestackAnalytics(reactContext)); 39 | modules.add(new FirestackStorage(reactContext)); 40 | modules.add(new FirestackMessaging(reactContext)); 41 | return modules; 42 | } 43 | 44 | /** 45 | * @return list of JS modules to register with the newly created catalyst instance. 46 | *

47 | * IMPORTANT: Note that only modules that needs to be accessible from the native code should be 48 | * listed here. Also listing a native module here doesn't imply that the JS implementation of it 49 | * will be automatically included in the JS bundle. 50 | */ 51 | @Override 52 | public List> createJSModules() { 53 | return Collections.emptyList(); 54 | } 55 | 56 | /** 57 | * @param reactContext 58 | * @return a list of view managers that should be registered with {@link UIManagerModule} 59 | */ 60 | @Override 61 | public List createViewManagers(ReactApplicationContext reactContext) { 62 | return Collections.emptyList(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/Utils.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack; 2 | 3 | import android.util.Log; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import com.facebook.react.bridge.Arguments; 11 | import com.facebook.react.bridge.Callback; 12 | import com.facebook.react.bridge.WritableMap; 13 | import com.facebook.react.bridge.ReadableMap; 14 | import com.facebook.react.bridge.ReactContext; 15 | import com.facebook.react.bridge.WritableArray; 16 | import com.facebook.react.bridge.WritableNativeArray; 17 | import com.facebook.react.modules.core.DeviceEventManagerModule; 18 | 19 | import com.facebook.react.bridge.ReadableType; 20 | import com.facebook.react.bridge.ReadableArray; 21 | import com.google.firebase.database.DataSnapshot; 22 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 23 | 24 | @SuppressWarnings("WeakerAccess") 25 | public class Utils { 26 | private static final String TAG = "Utils"; 27 | 28 | // TODO NOTE 29 | public static void todoNote(final String tag, final String name, final Callback callback) { 30 | Log.e(tag, "The method " + name + " has not yet been implemented."); 31 | Log.e(tag, "Feel free to contribute to finish the method in the source."); 32 | 33 | WritableMap errorMap = Arguments.createMap(); 34 | errorMap.putString("error", "unimplemented"); 35 | callback.invoke(null, errorMap); 36 | } 37 | 38 | /** 39 | * send a JS event 40 | **/ 41 | public static void sendEvent(final ReactContext context, final String eventName, final WritableMap params) { 42 | if (context != null) { 43 | context 44 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 45 | .emit(eventName, params); 46 | } else { 47 | Log.d(TAG, "Missing context - cannot send event!"); 48 | } 49 | } 50 | 51 | // snapshot 52 | public static WritableMap dataSnapshotToMap( 53 | String name, 54 | String path, 55 | String modifiersString, 56 | DataSnapshot dataSnapshot 57 | ) { 58 | WritableMap data = Arguments.createMap(); 59 | 60 | data.putString("key", dataSnapshot.getKey()); 61 | data.putBoolean("exists", dataSnapshot.exists()); 62 | data.putBoolean("hasChildren", dataSnapshot.hasChildren()); 63 | 64 | data.putDouble("childrenCount", dataSnapshot.getChildrenCount()); 65 | if (!dataSnapshot.hasChildren()) { 66 | Object value = dataSnapshot.getValue(); 67 | String type = value != null ? value.getClass().getName() : ""; 68 | switch (type) { 69 | case "java.lang.Boolean": 70 | data.putBoolean("value", (Boolean) value); 71 | break; 72 | case "java.lang.Long": 73 | Long longVal = (Long) value; 74 | data.putDouble("value", (double) longVal); 75 | break; 76 | case "java.lang.Double": 77 | data.putDouble("value", (Double) value); 78 | break; 79 | case "java.lang.String": 80 | data.putString("value", (String) value); 81 | break; 82 | default: 83 | data.putString("value", null); 84 | } 85 | } else { 86 | Object value = Utils.castSnapshotValue(dataSnapshot); 87 | if (value instanceof WritableNativeArray) { 88 | data.putArray("value", (WritableArray) value); 89 | } else { 90 | data.putMap("value", (WritableMap) value); 91 | } 92 | } 93 | 94 | // Child keys 95 | WritableArray childKeys = Utils.getChildKeys(dataSnapshot); 96 | data.putArray("childKeys", childKeys); 97 | 98 | Object priority = dataSnapshot.getPriority(); 99 | if (priority == null) { 100 | data.putString("priority", null); 101 | } else { 102 | data.putString("priority", priority.toString()); 103 | } 104 | 105 | WritableMap eventMap = Arguments.createMap(); 106 | eventMap.putString("eventName", name); 107 | eventMap.putMap("snapshot", data); 108 | eventMap.putString("path", path); 109 | eventMap.putString("modifiersString", modifiersString); 110 | return eventMap; 111 | } 112 | 113 | public static Any castSnapshotValue(DataSnapshot snapshot) { 114 | if (snapshot.hasChildren()) { 115 | if (isArray(snapshot)) { 116 | return (Any) buildArray(snapshot); 117 | } else { 118 | return (Any) buildMap(snapshot); 119 | } 120 | } else { 121 | if (snapshot.getValue() != null) { 122 | String type = snapshot.getValue().getClass().getName(); 123 | switch (type) { 124 | case "java.lang.Boolean": 125 | return (Any) (snapshot.getValue()); 126 | case "java.lang.Long": 127 | return (Any) (snapshot.getValue()); 128 | case "java.lang.Double": 129 | return (Any) (snapshot.getValue()); 130 | case "java.lang.String": 131 | return (Any) (snapshot.getValue()); 132 | default: 133 | Log.w(TAG, "Invalid type: " + type); 134 | return (Any) null; 135 | } 136 | } 137 | return (Any) null; 138 | } 139 | } 140 | 141 | private static boolean isArray(DataSnapshot snapshot) { 142 | long expectedKey = 0; 143 | for (DataSnapshot child : snapshot.getChildren()) { 144 | try { 145 | long key = Long.parseLong(child.getKey()); 146 | if (key == expectedKey) { 147 | expectedKey++; 148 | } else { 149 | return false; 150 | } 151 | } catch (NumberFormatException ex) { 152 | return false; 153 | } 154 | } 155 | return true; 156 | } 157 | 158 | private static WritableArray buildArray(DataSnapshot snapshot) { 159 | WritableArray array = Arguments.createArray(); 160 | for (DataSnapshot child : snapshot.getChildren()) { 161 | Any castedChild = castSnapshotValue(child); 162 | switch (castedChild.getClass().getName()) { 163 | case "java.lang.Boolean": 164 | array.pushBoolean((Boolean) castedChild); 165 | break; 166 | case "java.lang.Long": 167 | Long longVal = (Long) castedChild; 168 | array.pushDouble((double) longVal); 169 | break; 170 | case "java.lang.Double": 171 | array.pushDouble((Double) castedChild); 172 | break; 173 | case "java.lang.String": 174 | array.pushString((String) castedChild); 175 | break; 176 | case "com.facebook.react.bridge.WritableNativeMap": 177 | array.pushMap((WritableMap) castedChild); 178 | break; 179 | case "com.facebook.react.bridge.WritableNativeArray": 180 | array.pushArray((WritableArray) castedChild); 181 | break; 182 | default: 183 | Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); 184 | break; 185 | } 186 | } 187 | return array; 188 | } 189 | 190 | private static WritableMap buildMap(DataSnapshot snapshot) { 191 | WritableMap map = Arguments.createMap(); 192 | for (DataSnapshot child : snapshot.getChildren()) { 193 | Any castedChild = castSnapshotValue(child); 194 | 195 | switch (castedChild.getClass().getName()) { 196 | case "java.lang.Boolean": 197 | map.putBoolean(child.getKey(), (Boolean) castedChild); 198 | break; 199 | case "java.lang.Long": 200 | Long longVal = (Long) castedChild; 201 | map.putDouble(child.getKey(), (double) longVal); 202 | break; 203 | case "java.lang.Double": 204 | map.putDouble(child.getKey(), (Double) castedChild); 205 | break; 206 | case "java.lang.String": 207 | map.putString(child.getKey(), (String) castedChild); 208 | break; 209 | case "com.facebook.react.bridge.WritableNativeMap": 210 | map.putMap(child.getKey(), (WritableMap) castedChild); 211 | break; 212 | case "com.facebook.react.bridge.WritableNativeArray": 213 | map.putArray(child.getKey(), (WritableArray) castedChild); 214 | break; 215 | default: 216 | Log.w(TAG, "Invalid type: " + castedChild.getClass().getName()); 217 | break; 218 | } 219 | } 220 | return map; 221 | } 222 | 223 | public static WritableArray getChildKeys(DataSnapshot snapshot) { 224 | WritableArray childKeys = Arguments.createArray(); 225 | 226 | if (snapshot.hasChildren()) { 227 | for (DataSnapshot child : snapshot.getChildren()) { 228 | childKeys.pushString(child.getKey()); 229 | } 230 | } 231 | 232 | return childKeys; 233 | } 234 | 235 | public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { 236 | Map deconstructedMap = new HashMap<>(); 237 | if (readableMap == null) { 238 | return deconstructedMap; 239 | } 240 | 241 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 242 | while (iterator.hasNextKey()) { 243 | String key = iterator.nextKey(); 244 | ReadableType type = readableMap.getType(key); 245 | switch (type) { 246 | case Null: 247 | deconstructedMap.put(key, null); 248 | break; 249 | case Boolean: 250 | deconstructedMap.put(key, readableMap.getBoolean(key)); 251 | break; 252 | case Number: 253 | deconstructedMap.put(key, readableMap.getDouble(key)); 254 | break; 255 | case String: 256 | deconstructedMap.put(key, readableMap.getString(key)); 257 | break; 258 | case Map: 259 | deconstructedMap.put(key, Utils.recursivelyDeconstructReadableMap(readableMap.getMap(key))); 260 | break; 261 | case Array: 262 | deconstructedMap.put(key, Utils.recursivelyDeconstructReadableArray(readableMap.getArray(key))); 263 | break; 264 | default: 265 | throw new IllegalArgumentException("Could not convert object with key: " + key + "."); 266 | } 267 | 268 | } 269 | return deconstructedMap; 270 | } 271 | 272 | public static List recursivelyDeconstructReadableArray(ReadableArray readableArray) { 273 | List deconstructedList = new ArrayList<>(readableArray.size()); 274 | for (int i = 0; i < readableArray.size(); i++) { 275 | ReadableType indexType = readableArray.getType(i); 276 | switch (indexType) { 277 | case Null: 278 | deconstructedList.add(i, null); 279 | break; 280 | case Boolean: 281 | deconstructedList.add(i, readableArray.getBoolean(i)); 282 | break; 283 | case Number: 284 | deconstructedList.add(i, readableArray.getDouble(i)); 285 | break; 286 | case String: 287 | deconstructedList.add(i, readableArray.getString(i)); 288 | break; 289 | case Map: 290 | deconstructedList.add(i, Utils.recursivelyDeconstructReadableMap(readableArray.getMap(i))); 291 | break; 292 | case Array: 293 | deconstructedList.add(i, Utils.recursivelyDeconstructReadableArray(readableArray.getArray(i))); 294 | break; 295 | default: 296 | throw new IllegalArgumentException("Could not convert object at index " + i + "."); 297 | } 298 | } 299 | return deconstructedList; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/analytics/FirestackAnalytics.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack.analytics; 2 | 3 | import android.util.Log; 4 | import android.app.Activity; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.ReactMethod; 9 | import com.facebook.react.bridge.ReadableMap; 10 | import com.google.firebase.analytics.FirebaseAnalytics; 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 13 | 14 | 15 | public class FirestackAnalytics extends ReactContextBaseJavaModule { 16 | 17 | private static final String TAG = "FirestackAnalytics"; 18 | 19 | public FirestackAnalytics(ReactApplicationContext reactContext) { 20 | super(reactContext); 21 | Log.d(TAG, "New instance"); 22 | } 23 | 24 | /** 25 | * 26 | * @return 27 | */ 28 | @Override 29 | public String getName() { 30 | return TAG; 31 | } 32 | 33 | @ReactMethod 34 | public void logEvent(final String name, @Nullable final ReadableMap params) { 35 | FirebaseAnalytics.getInstance(getReactApplicationContext()).logEvent(name, Arguments.toBundle(params)); 36 | } 37 | 38 | /** 39 | * 40 | * @param enabled 41 | */ 42 | @ReactMethod 43 | public void setAnalyticsCollectionEnabled(final Boolean enabled) { 44 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setAnalyticsCollectionEnabled(enabled); 45 | } 46 | 47 | /** 48 | * 49 | * @param screenName 50 | * @param screenClassOverride 51 | */ 52 | @ReactMethod 53 | public void setCurrentScreen(final String screenName, final String screenClassOverride) { 54 | final Activity activity = getCurrentActivity(); 55 | if (activity != null) { 56 | // needs to be run on main thread 57 | Log.d(TAG, "setCurrentScreen " + screenName + " - " + screenClassOverride); 58 | activity.runOnUiThread(new Runnable() { 59 | @Override 60 | public void run() { 61 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setCurrentScreen(activity, screenName, screenClassOverride); 62 | } 63 | }); 64 | } 65 | } 66 | 67 | /** 68 | * 69 | * @param milliseconds 70 | */ 71 | @ReactMethod 72 | public void setMinimumSessionDuration(final double milliseconds) { 73 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setMinimumSessionDuration((long) milliseconds); 74 | } 75 | 76 | /** 77 | * 78 | * @param milliseconds 79 | */ 80 | @ReactMethod 81 | public void setSessionTimeoutDuration(final double milliseconds) { 82 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setSessionTimeoutDuration((long) milliseconds); 83 | } 84 | 85 | /** 86 | * 87 | * @param id 88 | */ 89 | @ReactMethod 90 | public void setUserId(final String id) { 91 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserId(id); 92 | } 93 | 94 | /** 95 | * 96 | * @param name 97 | * @param value 98 | */ 99 | @ReactMethod 100 | public void setUserProperty(final String name, final String value) { 101 | FirebaseAnalytics.getInstance(getReactApplicationContext()).setUserProperty(name, value); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /android/src/main/java/io/fullstack/firestack/messaging/FirestackMessaging.java: -------------------------------------------------------------------------------- 1 | package io.fullstack.firestack.messaging; 2 | 3 | import java.util.Map; 4 | 5 | import android.content.Context; 6 | import android.content.IntentFilter; 7 | import android.content.Intent; 8 | import android.content.BroadcastReceiver; 9 | import android.util.Log; 10 | 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.Callback; 13 | import com.facebook.react.bridge.ReactApplicationContext; 14 | import com.facebook.react.bridge.ReactContext; 15 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 16 | import com.facebook.react.bridge.ReactMethod; 17 | import com.facebook.react.bridge.ReadableMap; 18 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 19 | import com.facebook.react.bridge.ReadableType; 20 | import com.facebook.react.bridge.WritableMap; 21 | 22 | import com.google.firebase.iid.FirebaseInstanceId; 23 | import com.google.firebase.messaging.FirebaseMessaging; 24 | import com.google.firebase.messaging.RemoteMessage; 25 | 26 | import io.fullstack.firestack.Utils; 27 | 28 | public class FirestackMessaging extends ReactContextBaseJavaModule { 29 | 30 | private static final String TAG = "FirestackMessaging"; 31 | private static final String EVENT_NAME_TOKEN = "FirestackRefreshToken"; 32 | private static final String EVENT_NAME_NOTIFICATION = "FirestackReceiveNotification"; 33 | private static final String EVENT_NAME_SEND = "FirestackUpstreamSend"; 34 | 35 | public static final String INTENT_NAME_TOKEN = "io.fullstack.firestack.refreshToken"; 36 | public static final String INTENT_NAME_NOTIFICATION = "io.fullstack.firestack.ReceiveNotification"; 37 | public static final String INTENT_NAME_SEND = "io.fullstack.firestack.Upstream"; 38 | 39 | private IntentFilter mRefreshTokenIntentFilter; 40 | private IntentFilter mReceiveNotificationIntentFilter; 41 | private IntentFilter mReceiveSendIntentFilter; 42 | private BroadcastReceiver mBroadcastReceiver; 43 | 44 | public FirestackMessaging(ReactApplicationContext reactContext) { 45 | super(reactContext); 46 | mRefreshTokenIntentFilter = new IntentFilter(INTENT_NAME_TOKEN); 47 | mReceiveNotificationIntentFilter = new IntentFilter(INTENT_NAME_NOTIFICATION); 48 | mReceiveSendIntentFilter = new IntentFilter(INTENT_NAME_SEND); 49 | initRefreshTokenHandler(); 50 | initMessageHandler(); 51 | initSendHandler(); 52 | Log.d(TAG, "New instance"); 53 | } 54 | 55 | @Override 56 | public String getName() { 57 | return TAG; 58 | } 59 | 60 | private void initMessageHandler() { 61 | Log.d(TAG, "Firestack initMessageHandler called"); 62 | 63 | if (mBroadcastReceiver == null) { 64 | mBroadcastReceiver = new BroadcastReceiver() { 65 | @Override 66 | public void onReceive(Context context, Intent intent) { 67 | RemoteMessage remoteMessage = intent.getParcelableExtra("data"); 68 | Log.d(TAG, "Firebase onReceive: " + remoteMessage); 69 | WritableMap params = Arguments.createMap(); 70 | 71 | params.putNull("data"); 72 | params.putNull("notification"); 73 | params.putString("id", remoteMessage.getMessageId()); 74 | params.putString("messageId", remoteMessage.getMessageId()); 75 | 76 | 77 | if (remoteMessage.getData().size() != 0) { 78 | WritableMap dataMap = Arguments.createMap(); 79 | Map data = remoteMessage.getData(); 80 | 81 | for (String key : data.keySet()) { 82 | dataMap.putString(key, data.get(key)); 83 | } 84 | 85 | params.putMap("data", dataMap); 86 | } 87 | 88 | 89 | if (remoteMessage.getNotification() != null) { 90 | WritableMap notificationMap = Arguments.createMap(); 91 | RemoteMessage.Notification notification = remoteMessage.getNotification(); 92 | notificationMap.putString("title", notification.getTitle()); 93 | notificationMap.putString("body", notification.getBody()); 94 | notificationMap.putString("icon", notification.getIcon()); 95 | notificationMap.putString("sound", notification.getSound()); 96 | notificationMap.putString("tag", notification.getTag()); 97 | params.putMap("notification", notificationMap); 98 | } 99 | 100 | ReactContext ctx = getReactApplicationContext(); 101 | Utils.sendEvent(ctx, EVENT_NAME_NOTIFICATION, params); 102 | } 103 | }; 104 | 105 | } 106 | getReactApplicationContext().registerReceiver(mBroadcastReceiver, mReceiveNotificationIntentFilter); 107 | } 108 | 109 | /** 110 | * 111 | */ 112 | private void initRefreshTokenHandler() { 113 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 114 | @Override 115 | public void onReceive(Context context, Intent intent) { 116 | WritableMap params = Arguments.createMap(); 117 | params.putString("token", intent.getStringExtra("token")); 118 | ReactContext ctx = getReactApplicationContext(); 119 | Log.d(TAG, "initRefreshTokenHandler received event " + EVENT_NAME_TOKEN); 120 | Utils.sendEvent(ctx, EVENT_NAME_TOKEN, params); 121 | } 122 | 123 | ; 124 | }, mRefreshTokenIntentFilter); 125 | } 126 | 127 | @ReactMethod 128 | public void subscribeToTopic(String topic, final Callback callback) { 129 | try { 130 | FirebaseMessaging.getInstance().subscribeToTopic(topic); 131 | callback.invoke(null, topic); 132 | } catch (Exception e) { 133 | e.printStackTrace(); 134 | Log.d(TAG, "Firebase token: " + e); 135 | WritableMap error = Arguments.createMap(); 136 | error.putString("message", e.getMessage()); 137 | callback.invoke(error); 138 | 139 | } 140 | } 141 | 142 | @ReactMethod 143 | public void getToken(final Callback callback) { 144 | 145 | try { 146 | String token = FirebaseInstanceId.getInstance().getToken(); 147 | Log.d(TAG, "Firebase token: " + token); 148 | callback.invoke(null, token); 149 | } catch (Exception e) { 150 | WritableMap error = Arguments.createMap(); 151 | error.putString("message", e.getMessage()); 152 | callback.invoke(error); 153 | } 154 | } 155 | 156 | @ReactMethod 157 | public void unsubscribeFromTopic(String topic, final Callback callback) { 158 | try { 159 | FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); 160 | callback.invoke(null, topic); 161 | } catch (Exception e) { 162 | WritableMap error = Arguments.createMap(); 163 | error.putString("message", e.getMessage()); 164 | callback.invoke(error); 165 | } 166 | } 167 | 168 | // String senderId, String messageId, String messageType, 169 | @ReactMethod 170 | public void send(ReadableMap params, final Callback callback) { 171 | ReadableMap data = params.getMap("data"); 172 | FirebaseMessaging fm = FirebaseMessaging.getInstance(); 173 | RemoteMessage.Builder remoteMessage = new RemoteMessage.Builder(params.getString("sender")); 174 | 175 | remoteMessage.setMessageId(params.getString("id")); 176 | remoteMessage.setMessageType(params.getString("type")); 177 | 178 | if (params.hasKey("ttl")) { 179 | remoteMessage.setTtl(params.getInt("ttl")); 180 | } 181 | 182 | if (params.hasKey("collapseKey")) { 183 | remoteMessage.setCollapseKey(params.getString("collapseKey")); 184 | } 185 | 186 | ReadableMapKeySetIterator iterator = data.keySetIterator(); 187 | 188 | while (iterator.hasNextKey()) { 189 | String key = iterator.nextKey(); 190 | ReadableType type = data.getType(key); 191 | if (type == ReadableType.String) { 192 | remoteMessage.addData(key, data.getString(key)); 193 | } 194 | } 195 | 196 | try { 197 | fm.send(remoteMessage.build()); 198 | WritableMap res = Arguments.createMap(); 199 | res.putString("status", "success"); 200 | Log.d(TAG, "send: Message sent"); 201 | callback.invoke(null, res); 202 | } catch (Exception e) { 203 | Log.e(TAG, "send: error sending message", e); 204 | WritableMap error = Arguments.createMap(); 205 | error.putString("code", e.toString()); 206 | error.putString("message", e.toString()); 207 | callback.invoke(error); 208 | } 209 | } 210 | 211 | private void initSendHandler() { 212 | getReactApplicationContext().registerReceiver(new BroadcastReceiver() { 213 | @Override 214 | public void onReceive(Context context, Intent intent) { 215 | WritableMap params = Arguments.createMap(); 216 | if (intent.getBooleanExtra("hasError", false)) { 217 | WritableMap error = Arguments.createMap(); 218 | error.putInt("code", intent.getIntExtra("errCode", 0)); 219 | error.putString("message", intent.getStringExtra("errorMessage")); 220 | params.putMap("err", error); 221 | } else { 222 | params.putNull("err"); 223 | } 224 | ReactContext ctx = getReactApplicationContext(); 225 | Utils.sendEvent(ctx, EVENT_NAME_SEND, params); 226 | } 227 | }, mReceiveSendIntentFilter); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /bin/cocoapods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/cocoapods.sh 4 | 5 | ios_dir=`pwd`/ios 6 | if [ -d ios_dir ] 7 | then 8 | exit 0 9 | fi 10 | 11 | podfile="$ios_dir/Podfile" 12 | template=`pwd`/node_modules/react-native-firestack/ios/Podfile.template 13 | 14 | echo "Checking Podfile in iOS project ($podfile)" 15 | 16 | if [ -f $podfile ] 17 | then 18 | echo "" 19 | echo "Found an existing Podfile, Do you want to override it? [N/y]" 20 | read generate_env_file 21 | 22 | if [ "$generate_env_file" != "y" ] 23 | then 24 | 25 | # pod outdated | grep "The following pod updates are available:" 26 | # status=$? 27 | # if [ $status -eq 0 ]; then 28 | # echo "From what I can tell, there look to be updates available..." 29 | # echo "Do you want to update your cocoapods? [N/y]" 30 | # read update_pods 31 | # if [ "$update_pods" != "y" ] 32 | # then 33 | # pod update --project-directory=ios 34 | # exit 0 35 | # else 36 | # exit 0 37 | # fi 38 | # fi 39 | 40 | echo "Add the following pods": 41 | echo "" 42 | echo "" 43 | cat $template 44 | echo "" 45 | echo "" 46 | echo "and run 'pod install' to install Firestack for iOS" 47 | exit 0 48 | fi 49 | 50 | rm -f $podfile 51 | rm -f "$podfile.lock" 52 | fi 53 | 54 | echo "Adding Podfile to iOS project" 55 | 56 | cd ios 57 | pod init >/dev/null 2>&1 58 | cat $template >> $podfile 59 | cd .. 60 | 61 | echo "Installing Pods" 62 | 63 | pod install --project-directory=ios -------------------------------------------------------------------------------- /bin/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## https://github.com/auth0/react-native-lock/blob/master/bin/prepare.sh 4 | 5 | echo "Preparing to link react-native-firestack for iOS" 6 | 7 | echo "Checking CocoaPods..." 8 | has_cocoapods=`which pod >/dev/null 2>&1` 9 | if [ -z "$has_cocoapods" ] 10 | then 11 | echo "CocoaPods already installed" 12 | else 13 | echo "Installing CocoaPods..." 14 | gem install cocoapods 15 | fi -------------------------------------------------------------------------------- /docs/api/analytics.md: -------------------------------------------------------------------------------- 1 | # Analytics 2 | 3 | Integrating Firebase analytics is super simple using Firestack. A number of methods are provided to help tailor analytics specifically for your 4 | own app. The Firebase SDK includes a number of pre-set events which are automatically handled, and cannot be used with custom events: 5 | 6 | ``` 7 | 'app_clear_data', 8 | 'app_uninstall', 9 | 'app_update', 10 | 'error', 11 | 'first_open', 12 | 'in_app_purchase', 13 | 'notification_dismiss', 14 | 'notification_foreground', 15 | 'notification_open', 16 | 'notification_receive', 17 | 'os_update', 18 | 'session_start', 19 | 'user_engagement', 20 | ``` 21 | 22 | #### `logEvent(event: string, params?: Object): void` 23 | 24 | Log a custom event with optional params. 25 | 26 | ```javascript 27 | firestack.analytics().logEvent('clicked_advert', { id: 1337 }); 28 | ``` 29 | 30 | #### `setAnalyticsCollectionEnabled(enabled: boolean): void` 31 | 32 | Sets whether analytics collection is enabled for this app on this device. 33 | 34 | ```javascript 35 | firestack.analytics().setAnalyticsCollectionEnabled(false); 36 | ``` 37 | 38 | #### `setCurrentScreen(screenName: string, screenClassOverride?: string): void` 39 | 40 | Sets the current screen name, which specifies the current visual context in your app. 41 | 42 | > 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. 43 | 44 | ```javascript 45 | firestack.analytics().setCurrentScreen('user_profile'); 46 | ``` 47 | 48 | #### `setMinimumSessionDuration(miliseconds: number): void` 49 | 50 | Sets the minimum engagement time required before starting a session. The default value is 10000 (10 seconds). 51 | 52 | ```javascript 53 | firestack.analytics().setMinimumSessionDuration(15000); 54 | ``` 55 | 56 | #### `setSessionTimeoutDuration(miliseconds: number): void` 57 | 58 | Sets the duration of inactivity that terminates the current session. The default value is 1800000 (30 minutes). 59 | 60 | ```javascript 61 | firestack.analytics().setSessionTimeoutDuration(900000); 62 | ``` 63 | 64 | #### `setUserId(id: string): void` 65 | 66 | Gives a user a uniqiue identificaition. 67 | 68 | ```javascript 69 | const id = firestack.auth().currentUser.uid; 70 | 71 | firestack.analytics().setUserId(id); 72 | ``` 73 | 74 | #### `setUserProperty(name: string, value: string): void` 75 | 76 | Sets a key/value pair of data on the current user. 77 | 78 | ```javascript 79 | firestack.analytics().setUserProperty('nickname', 'foobar'); 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/api/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | 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). 4 | 5 | > Authentication requires Google Play services to be installed on Android. 6 | 7 | ## Auth 8 | 9 | ### Properties 10 | 11 | ##### `authenticated: boolean` - Returns the current Firebase authentication state. 12 | ##### `currentUser: User | null` - Returns the currently signed-in user (or null). See the [User](/docs/api/authentication.md#user) class documentation for further usage. 13 | 14 | ### Methods 15 | 16 | #### [`onAuthStateChanged(event: Function): Function`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged) 17 | 18 | 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. 19 | 20 | ```javascript 21 | class Example extends React.Component { 22 | 23 | constructor() { 24 | super(); 25 | this.unsubscribe = null; 26 | } 27 | 28 | componentDidMount() { 29 | this.unsubscribe = firestack.auth().onAuthStateChanged((user) => { 30 | if (user) { 31 | // User is signed in. 32 | } 33 | }); 34 | } 35 | 36 | componentWillUnmount() { 37 | if (this.unsubscribe) { 38 | this.unsubscribe(); 39 | } 40 | } 41 | 42 | } 43 | ``` 44 | 45 | #### [`createUserWithEmailAndPassword(email: string, password: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#createUserWithEmailAndPassword) 46 | 47 | We can create a user by calling the `createUserWithEmailAndPassword()` function. 48 | The method accepts two parameters, an email and a password. 49 | 50 | ```javascript 51 | firestack.auth().createUserWithEmailAndPassword('foo@bar.com', '123456') 52 | .then((user) => { 53 | console.log('user created', user) 54 | }) 55 | .catch((err) => { 56 | console.error('An error occurred', err); 57 | }); 58 | ``` 59 | 60 | #### [`signInWithEmailAndPassword(email: string, password: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithEmailAndPassword) 61 | 62 | To sign a user in with their email and password, use the `signInWithEmailAndPassword()` function. 63 | It accepts two parameters, the user's email and password: 64 | 65 | ```javascript 66 | firestack.auth().signInWithEmailAndPassword('foo@bar.com', '123456') 67 | .then((user) => { 68 | console.log('User successfully logged in', user) 69 | }) 70 | .catch((err) => { 71 | console.error('User signin error', err); 72 | }); 73 | ``` 74 | 75 | #### [`signInAnonymously(): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInAnonymously) 76 | 77 | Sign an anonymous user. If the user has already signed in, that user will be returned. 78 | 79 | ```javascript 80 | firestack.auth().signInAnonymously() 81 | .then((user) => { 82 | console.log('Anonymous user successfully logged in', user) 83 | }) 84 | .catch((err) => { 85 | console.error('Anonymous user signin error', err); 86 | }); 87 | ``` 88 | 89 | #### [`signInWithCredential(credential: Object): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithCredential) 90 | 91 | Sign in the user with a 3rd party credential provider. `credential` requires the following properties: 92 | 93 | ```javascript 94 | { 95 | provider: string, 96 | token: string, 97 | secret: string 98 | } 99 | ``` 100 | 101 | ```javascript 102 | const credential = { 103 | provider: 'facebook.com', 104 | token: '12345', 105 | secret: '6789', 106 | }; 107 | 108 | firestack.auth().signInWithCredential(credential) 109 | .then((user) => { 110 | console.log('User successfully signed in', user) 111 | }) 112 | .catch((err) => { 113 | console.error('User signin error', err); 114 | }); 115 | ``` 116 | 117 | #### [`signInWithCustomToken(token: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signInWithCustomToken) 118 | 119 | Sign a user in with a self-signed [JWT](https://jwt.io) token. 120 | 121 | To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: 122 | 123 | ```javascript 124 | firestack.auth().signInWithCustomToken('12345') 125 | .then((user) => { 126 | console.log('User successfully logged in', user) 127 | }) 128 | .catch((err) => { 129 | console.error('User signin error', err); 130 | }); 131 | ``` 132 | 133 | #### [`sendPasswordResetEmail(email: string): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#sendPasswordResetEmail) 134 | 135 | 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. 136 | 137 | ```javascript 138 | firestack.auth().sendPasswordResetEmail('foo@bar.com') 139 | .then(() => { 140 | console.log('Password reset email sent'); 141 | }) 142 | .catch((error) => { 143 | console.error('Unable send password reset email', error); 144 | }); 145 | ``` 146 | 147 | #### [`signOut(): Promise`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#confirmPasswordReset) 148 | 149 | Completes the password reset process, given a confirmation code and new password. 150 | 151 | ```javascript 152 | firestack.auth().signOut() 153 | .then(() => { 154 | console.log('User signed out successfully'); 155 | }) 156 | .catch(); 157 | ``` 158 | 159 | ## User 160 | 161 | User class returned from `firestack.auth().currentUser`. 162 | 163 | ### Properties 164 | 165 | ##### `displayName: string | null` - The user's display name (if available). 166 | ##### `email: string | null` - The user's email address (if available). 167 | ##### `emailVerified: boolean` - True if the user's email address has been verified. 168 | ##### `isAnonymous: boolean` 169 | ##### `photoURL: string | null` - The URL of the user's profile picture (if available). 170 | ##### `providerData: Object | null` - Additional provider-specific information about the user. 171 | ##### `providerId: string | null` - The authentication provider ID for the current user. For example, 'facebook.com', or 'google.com'. 172 | ##### `uid: string` - The user's unique ID. 173 | 174 | ### Methods 175 | 176 | #### [`delete(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#delete) 177 | 178 | Delete the current user. 179 | 180 | ```javascript 181 | firestack.auth().currentUser 182 | .delete() 183 | .then() 184 | .catch(); 185 | ``` 186 | 187 | #### [`getToken(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#getToken) 188 | 189 | Returns the users authentication token. 190 | 191 | ```javascript 192 | firestack.auth().currentUser 193 | .getToken() 194 | .then((token) => {}) 195 | .catch(); 196 | ``` 197 | 198 | 199 | #### [`reauthenticate(credential: Object): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reauthenticate) 200 | 201 | Reauthenticate the current user with credentials: 202 | 203 | ```javascript 204 | { 205 | provider: string, 206 | token: string, 207 | secret: string 208 | } 209 | ``` 210 | 211 | ```javascript 212 | const credentials = { 213 | provider: 'facebook.com', 214 | token: '12345', 215 | secret: '6789', 216 | }; 217 | 218 | firestack.auth().currentUser 219 | .reauthenticate(credentials) 220 | .then() 221 | .catch(); 222 | ``` 223 | 224 | #### [`reload(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#reload) 225 | 226 | Refreshes the current user. 227 | 228 | ```javascript 229 | firestack.auth().currentUser 230 | .reload() 231 | .then((user) => {}) 232 | .catch(); 233 | ``` 234 | 235 | #### [`sendEmailVerification(): Promise`](https://firebase.google.com/docs/reference/js/firebase.User#sendEmailVerification) 236 | 237 | Sends a verification email to a user. This will Promise reject is the user is anonymous. 238 | 239 | ```javascript 240 | firestack.auth().currentUser 241 | .sendEmailVerification() 242 | .then() 243 | .catch(); 244 | ``` 245 | 246 | #### [updateEmail(email: string)](https://firebase.google.com/docs/reference/js/firebase.User#updateEmail) 247 | 248 | 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. 249 | 250 | ```javascript 251 | firestack.auth().updateUserEmail('foo@bar.com') 252 | .then() 253 | .catch(); 254 | ``` 255 | 256 | #### [updatePassword(password: string)](https://firebase.google.com/docs/reference/js/firebase.User#updatePassword) 257 | 258 | 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. 259 | 260 | ```javascript 261 | firestack.auth().updatePassword('foobar1234') 262 | .then() 263 | .catch(); 264 | ``` 265 | 266 | #### [updateProfile(profile: Object)](https://firebase.google.com/docs/reference/js/firebase.User#updateProfile) 267 | 268 | Updates a user's profile data. Profile data should be an object of fields to update: 269 | 270 | ```javascript 271 | { 272 | displayName: string, 273 | photoURL: string, 274 | } 275 | ``` 276 | 277 | ```javascript 278 | firestack.auth() 279 | .updateProfile({ 280 | displayName: 'Ari Lerner' 281 | }) 282 | .then() 283 | .catch(); 284 | ``` 285 | -------------------------------------------------------------------------------- /docs/api/cloud-messaging.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/api/database.md: -------------------------------------------------------------------------------- 1 | 2 | # Realtime Database 3 | 4 | Firestack mimics the [Web Firebase SDK Realtime Database](https://firebase.google.com/docs/database/web/read-and-write), whilst 5 | providing support for devices in low/no data connection state. 6 | 7 | All Realtime Database operations are accessed via `database()`. 8 | 9 | Basic read example: 10 | ```javascript 11 | firestack.database() 12 | .ref('posts') 13 | .on('value', (snapshot) => { 14 | const value = snapshot.val(); 15 | }); 16 | ``` 17 | 18 | Basic write example: 19 | ```javascript 20 | firestack.database() 21 | .ref('posts/1234') 22 | .set({ 23 | title: 'My awesome post', 24 | content: 'Some awesome content', 25 | }); 26 | ``` 27 | 28 | ## Unmounted components 29 | 30 | Listening to database updates on unmounted components will trigger a warning: 31 | 32 | > 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. 33 | 34 | It is important to always unsubscribe the reference from receiving new updates once the component is no longer in use. 35 | This can be achived easily using [Reacts Component Lifecycle](https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle) events: 36 | 37 | Always ensure the handler function provided is of the same reference so Firestack can unsubscribe the ref listener. 38 | 39 | ```javascript 40 | class MyComponent extends Component { 41 | constructor() { 42 | super(); 43 | this.ref = null; 44 | } 45 | 46 | // On mount, subscribe to ref updates 47 | componentDidMount() { 48 | this.ref = firestack.database().ref('posts/1234'); 49 | this.ref.on('value', this.handlePostUpdate); 50 | } 51 | 52 | // On unmount, ensure we no longer listen for updates 53 | componentWillUnmount() { 54 | if (this.ref) { 55 | this.ref.off('value', this.handlePostUpdate); 56 | } 57 | } 58 | 59 | // Bind the method only once to keep the same reference 60 | handlePostUpdate = (snapshot) => { 61 | console.log('Post Content', snapshot.val()); 62 | } 63 | 64 | render() { 65 | return null; 66 | } 67 | } 68 | 69 | ``` 70 | 71 | ## Usage in offline environments 72 | 73 | ### Reading data 74 | 75 | Firstack allows the database instance to [persist on disk](https://firebase.google.com/docs/database/android/offline-capabilities) if enabled. 76 | To enable database persistence, pass the configuration option `persistence` before calls are made: 77 | 78 | ```javascript 79 | const configurationOptions = { 80 | persistence: true 81 | }; 82 | const firestack = new Firestack(configurationOptions); 83 | ``` 84 | 85 | Any subsequent calls to Firebase stores the data for the ref on disk. 86 | 87 | ### Writing data 88 | 89 | Out of the box, Firebase has great support for writing operations in offline environments. Calling a write command whilst offline 90 | will always trigger any subscribed refs with new data. Once the device reconnects to Firebase, it will be synced with the server. 91 | 92 | The following todo code snippet will work in both online and offline environments: 93 | 94 | ```javascript 95 | // Assume the todos are stored as an object value on Firebase as: 96 | // { name: string, complete: boolean } 97 | 98 | class ToDos extends Component { 99 | constructor() { 100 | super(); 101 | this.ref = null; 102 | this.listView = new ListView.DataSource({ 103 | rowHasChanged: (r1, r2) => r1 !== r2, 104 | }); 105 | 106 | this.state = { 107 | todos: this.listView.cloneWithRows({}), 108 | }; 109 | 110 | // Keep a local reference of the TODO items 111 | this.todos = {}; 112 | } 113 | 114 | // Load the Todos on mount 115 | componentDidMount() { 116 | this.ref = firestack.database().ref('users/1234/todos'); 117 | this.ref.on('value', this.handleToDoUpdate); 118 | } 119 | 120 | // Unsubscribe from the todos on unmount 121 | componentWillUnmount() { 122 | if (this.ref) { 123 | this.ref.off('value', this.handleToDoUpdate); 124 | } 125 | } 126 | 127 | // Handle ToDo updates 128 | handleToDoUpdate = (snapshot) => { 129 | this.todos = snapshot.val() || {}; 130 | 131 | this.setState({ 132 | todos: this.listView.cloneWithRows(this.todos), 133 | }); 134 | } 135 | 136 | // Add a new ToDo onto Firebase 137 | // If offline, this will still trigger an update to handleToDoUpdate 138 | addToDo() { 139 | firestack.database() 140 | .ref('users/1234/todos') 141 | .set({ 142 | ...this.todos, { 143 | name: 'Yet another todo...', 144 | complete: false, 145 | }, 146 | }); 147 | } 148 | 149 | // Render a ToDo row 150 | renderToDo(todo) { 151 | // Dont render the todo if its complete 152 | if (todo.complete) { 153 | return null; 154 | } 155 | 156 | return ( 157 | 158 | {todo.name} 159 | 160 | ); 161 | } 162 | 163 | // Render the list of ToDos with a Button 164 | render() { 165 | return ( 166 | 167 | this.renderToDo(...args)} 170 | /> 171 | 172 |