diff --git a/README.md b/README.md index 0ebacc6..79bbdfe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ +---- +### This library is no longer maintained, use at your own risk or please [seek alternatives on npm](https://www.npmjs.com/search?q=react+native+firebase). +---- + ## Firestack -Firestack makes using the latest [Firebase](http://firebase.com) straight-forward. +Firestack makes using the latest [Firebase](https://firebase.google.com/) 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) @@ -16,7 +20,11 @@ For a detailed discussion of how Firestack works as well as how to contribute, c * Covers lots of awesome features of Firebase: * authentication * username and password - * social auth (implemented, but need to add providers) + * social auth (implemented in conjunction with [react-native-oauth](https://github.com/fullstackreact/react-native-oauth)) + * Facebook + * Github + * Google + * Twitter * storage handling * upload files * download urls @@ -25,6 +33,7 @@ For a detailed discussion of how Firestack works as well as how to contribute, c * presence out-of-the-box * analytics * Remote configuration + * FCM (in-progress) * Redux support built-in (but not required) * Android and iOS support * Community supported and professionally backed @@ -59,7 +68,7 @@ To use Firestack, we'll need to have a development environment that includes the ### 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`. +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**. @@ -117,6 +126,12 @@ source 'https://github.com/CocoaPods/Specs.git' ].each do |lib| pod lib end + +target '[project name]' do +end + +# target '[project test name]' do +# 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. @@ -193,7 +208,7 @@ 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. +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: @@ -201,7 +216,7 @@ After creating a Firebase project, click on the [Add Firebase to your iOS app](h 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`. +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 @@ -280,12 +295,14 @@ All methods return a promise. 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.listenForAuth(function(evt) { +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 @@ -306,7 +323,7 @@ firestack.listenForAuth(function(evt) { 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.unlistenForAuth() +firestack.auth.unlistenForAuth() ``` #### createUserWithEmail() @@ -314,7 +331,7 @@ firestack.unlistenForAuth() We can create a user by calling the `createUserWithEmail()` function. The `createUserWithEmail()` accepts two parameters, an email and a password. ```javascript -firestack.createUserWithEmail('ari@fullstack.io', '123456') +firestack.auth.createUserWithEmail('ari@fullstack.io', '123456') .then((user) => { console.log('user created', user) }) @@ -342,7 +359,7 @@ firestack.auth.signInWithEmail('ari@fullstack.io', '123456') To sign a user using a self-signed custom token, use the `signInWithCustomToken()` function. It accepts one parameter, the custom token: ```javascript -firestack.signInWithCustomToken(TOKEN) +firestack.auth.signInWithCustomToken(TOKEN) .then((user) => { console.log('User successfully logged in', user) }) @@ -362,7 +379,7 @@ We can use an external authentication provider, such as twitter/facebook for aut [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). +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'); @@ -370,12 +387,12 @@ var {FBLogin, FBLoginManager} = require('react-native-facebook-login'); var Login = React.createClass({ render: function() { return ( - { console.log(user) }) @@ -397,7 +414,7 @@ When the auth token has expired, we can ask firebase to reauthenticate with the 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.updateUserEmail('ari+rocks@fullstack.io') +firestack.auth.updateUserEmail('ari+rocks@fullstack.io') .then((res) => console.log('Updated user email')) .catch(err => console.error('There was an error updating user email')) ``` @@ -407,7 +424,7 @@ firestack.updateUserEmail('ari+rocks@fullstack.io') 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.updateUserPassword('somethingReallyS3cr3t733t') +firestack.auth.updateUserPassword('somethingReallyS3cr3t733t') .then(res => console.log('Updated user password')) .catch(err => console.error('There was an error updating your password')) ``` @@ -417,7 +434,7 @@ firestack.updateUserPassword('somethingReallyS3cr3t733t') 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.sendPasswordResetWithEmail('ari+rocks@fullstack.io') +firestack.auth.sendPasswordResetWithEmail('ari+rocks@fullstack.io') .then(res => console.log('Check your inbox for further instructions')) .catch(err => console.error('There was an error :(')) ``` @@ -431,7 +448,7 @@ 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.updateUserProfile({ +firestack.auth.updateUserProfile({ displayName: 'Ari Lerner' }) .then(res => console.log('Your profile has been updated')) @@ -443,7 +460,7 @@ firestack.updateUserProfile({ 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.deleteUser() +firestack.auth.deleteUser() .then(res => console.log('Sad to see you go')) .catch(err => console.error('There was an error - Now you are trapped!')) ``` @@ -453,7 +470,7 @@ firestack.deleteUser() If you want user's token, use `getToken()` method. ```javascript -firestack.getToken() +firestack.auth.getToken() .then(res => console.log(res.token)) .catch(err => console.error('error')) ``` @@ -463,7 +480,7 @@ firestack.getToken() To sign the current user out, use the `signOut()` method. It accepts no parameters ```javascript -firestack.signOut() +firestack.auth.signOut() .then(res => console.log('You have been signed out')) .catch(err => console.error('Uh oh... something weird happened')) ``` @@ -473,7 +490,7 @@ firestack.signOut() 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.getCurrentUser() +firestack.auth.getCurrentUser() .then(user => console.log('The currently logged in user', user)) .catch(err => console.error('An error occurred')) ``` @@ -634,12 +651,52 @@ 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. +> 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: + +```js +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: + +```js +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. @@ -676,7 +733,7 @@ For handling offline operations, you can enable persistence by using the `setPer 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. +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 @@ -756,7 +813,7 @@ Monitor token generation firestack.cloudMessaging.listenForTokenRefresh(function (token) { console.log('refresh device token', token); }); - + // remove listener firestack.cloudMessaging.unlistenForTokenRefresh(); ``` @@ -829,14 +886,14 @@ After the `npm` version is installed, you can either clone the repo directly int git clone https://github.com/fullstackreact/react-native-firestack.git ./node_modules/react-native-firestack ``` -Alternatively, you can clone the repo somewhere else and `rsync` the directory over to the `node_modules/` directory. +Alternatively, you can clone the repo somewhere else and `rsync` the directory over to the `node_modules/` directory. > This is the method I use as it allows me to separate the codebases: ```bash git clone https://github.com/fullstackreact/react-native-firestack.git \ ~/Development/react-native/mine/react-native-firestack/ - + ## And rsync rsync -avhW --delete \ --exclude='node_modules' \ @@ -863,3 +920,15 @@ The following is left to be done: - [ ] Add JS api - [ ] Move to use swift (cleaner syntax) - [ ] TODO: Finish Facebook integration + +# Fullstack React Book + + +Fullstack React Book + + +This repo was written and is maintained by the [Fullstack React](https://fullstackreact.com) team. + +If you're looking to learn React, there's no faster way than by spending a few hours with the [Fullstack React book](https://fullstackreact.com). + +
diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml index f4f39e8..76a4349 100644 --- a/android/.idea/gradle.xml +++ b/android/.idea/gradle.xml @@ -5,6 +5,7 @@ diff --git a/android/build.gradle b/android/build.gradle index 4009637..a0fd688 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -20,13 +20,13 @@ android { dependencies { compile 'com.facebook.react:react-native:0.20.+' - compile 'com.google.android.gms:play-services-base:9.6.1' + compile 'com.google.android.gms:play-services-base:+' - compile 'com.google.firebase:firebase-core:9.6.0' - compile 'com.google.firebase:firebase-auth:9.6.0' - compile 'com.google.firebase:firebase-analytics:9.6.0' - compile 'com.google.firebase:firebase-database:9.6.0' - compile 'com.google.firebase:firebase-storage:9.6.0' - compile 'com.google.firebase:firebase-messaging:9.6.0' + compile 'com.google.firebase:firebase-core: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 index d1be2e6..c4ee65d 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAnalytics.java @@ -102,16 +102,16 @@ private Bundle makeEventBundle(final String name, final Map map) bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, val); } if (map.containsKey("quantity")) { - long val = (long) map.get("quantity"); - bundle.putLong(FirebaseAnalytics.Param.QUANTITY, val); + double val = (double) map.get("quantity"); + bundle.putDouble(FirebaseAnalytics.Param.QUANTITY, val); } if (map.containsKey("price")) { - long val = (long) map.get("price"); - bundle.putLong(FirebaseAnalytics.Param.PRICE, val); + double val = (double) map.get("price"); + bundle.putDouble(FirebaseAnalytics.Param.PRICE, val); } if (map.containsKey("value")) { - long val = (long) map.get("value"); - bundle.putLong(FirebaseAnalytics.Param.VALUE, val); + double val = (double) map.get("value"); + bundle.putDouble(FirebaseAnalytics.Param.VALUE, val); } if (map.containsKey("currency")) { String val = (String) map.get("currency"); @@ -171,7 +171,7 @@ private Bundle makeEventBundle(final String name, final Map map) } if (map.containsKey("shipping")) { double val = (double) map.get("shipping"); - bundle.putDouble(FirebaseAnalytics.Param.NUMBER_OF_PASSENGERS, val); + bundle.putDouble(FirebaseAnalytics.Param.SHIPPING, val); } if (map.containsKey("group_id")) { String val = (String) map.get("group_id"); @@ -213,14 +213,14 @@ private Bundle makeEventBundle(final String name, final Map map) 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.getBinder(entry.getKey()) == null) { + if (bundle.getBundle(entry.getKey()) == null) { bundle.putString(entry.getKey(), entry.getValue().toString()); } } return bundle; } -} \ No newline at end of file +} diff --git a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java index 29a4efb..676b27f 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackAuth.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackAuth.java @@ -1,3 +1,4 @@ + package io.fullstack.firestack; import android.content.Context; @@ -32,6 +33,8 @@ import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.GetTokenResult; import com.google.firebase.auth.GoogleAuthProvider; +import com.google.firebase.auth.FirebaseAuthException; +import com.google.firebase.auth.TwitterAuthProvider; class FirestackAuthModule extends ReactContextBaseJavaModule { private final int NO_CURRENT_USER = 100; @@ -68,7 +71,7 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { WritableMap msgMap = Arguments.createMap(); msgMap.putString("eventName", "listenForAuth"); - if (user != null) { + if (firebaseAuth.getCurrentUser() != null) { WritableMap userMap = getUserMap(); msgMap.putBoolean("authenticated", true); @@ -99,7 +102,7 @@ public void unlistenForAuth(final Callback callback) { } @ReactMethod - public void createUserWithEmail(final String email, final String password, final Callback onComplete) { + public void createUserWithEmail(final String email, final String password, final Callback callback) { mAuth = FirebaseAuth.getInstance(); mAuth.createUserWithEmailAndPassword(email, password) @@ -107,13 +110,18 @@ public void createUserWithEmail(final String email, final String password, final @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - user = task.getResult().getUser(); - userCallback(user, onComplete); - }else{ - userErrorCallback(task, onComplete); + 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 @@ -125,13 +133,19 @@ public void signInWithEmail(final String email, final String password, final Cal @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { - user = task.getResult().getUser(); - userCallback(user, callback); + FirestackAuthModule.this.user = task.getResult().getUser(); + userCallback(FirestackAuthModule.this.user, callback); } else { - userErrorCallback(task, callback); + // 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 @@ -140,6 +154,8 @@ public void signInWithProvider(final String provider, final String authToken, fi this.facebookLogin(authToken,callback); } else if (provider.equals("google")) { this.googleLogin(authToken,callback); + } else if (provider.equals("twitter")) { + this.twitterLogin(authToken,authSecret,callback); } else // TODO FirestackUtils.todoNote(TAG, "signInWithProvider", callback); @@ -156,14 +172,18 @@ public void onComplete(@NonNull Task task) { Log.d(TAG, "signInAnonymously:onComplete:" + task.isSuccessful()); if (task.isSuccessful()) { - user = task.getResult().getUser(); - anonymousUserCallback(user, callback); - }else{ - userErrorCallback(task, callback); + 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 @@ -175,22 +195,60 @@ public void signInWithCustomToken(final String customToken, final Callback callb @Override public void onComplete(@NonNull Task task) { Log.d(TAG, "signInWithCustomToken:onComplete:" + task.isSuccessful()); - if (task.isSuccessful()) { - user = task.getResult().getUser(); - userCallback(user, callback); - } else { - userErrorCallback(task, callback); - } + 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) { - // TODO: - FirestackUtils.todoNote(TAG, "reauthenticateWithCredentialForProvider", callback); - // AuthCredential credential; - // Log.d(TAG, "reauthenticateWithCredentialForProvider called with: " + provider); + AuthCredential credential; + + if (provider.equals("facebook")) { + credential = FacebookAuthProvider.getCredential(authToken); + } else if (provider.equals("google")) { + credential = GoogleAuthProvider.getCredential(authToken, null); + } else if (provider.equals("twitter")) { + credential = TwitterAuthProvider.getCredential(authToken, authSecret); + } 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 @@ -207,10 +265,15 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // 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); @@ -234,10 +297,15 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // 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); @@ -258,11 +326,16 @@ public void onComplete(@NonNull Task task) { WritableMap resp = Arguments.createMap(); resp.putString("status", "complete"); callback.invoke(null, resp); - }else{ + } else { callback.invoke(task.getException().toString()); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -281,10 +354,15 @@ public void onComplete(@NonNull Task task) { resp.putString("msg", "User account deleted"); callback.invoke(null, resp); } else { - userErrorCallback(task, callback); + // 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); @@ -314,7 +392,12 @@ public void onComplete(@NonNull Task task) { callback.invoke(err); } } - }); + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception ex) { + userExceptionCallback(ex, callback); + } + }); } @ReactMethod @@ -347,16 +430,21 @@ public void onComplete(@NonNull Task task) { FirebaseUser u = FirebaseAuth.getInstance().getCurrentUser(); userCallback(u, callback); } else { - userErrorCallback(task, callback); + // 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(); - user = null; + this.user = null; WritableMap resp = Arguments.createMap(); resp.putString("status", "complete"); @@ -368,11 +456,11 @@ public void signOut(final Callback callback) { public void getCurrentUser(final Callback callback) { mAuth = FirebaseAuth.getInstance(); - user = mAuth.getCurrentUser(); - if(user == null){ + this.user = mAuth.getCurrentUser(); + if(this.user == null){ noUserCallback(callback); }else{ - userCallback(user, callback); + userCallback(this.user, callback); } } @@ -386,14 +474,19 @@ public void googleLogin(String IdToken, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - user = task.getResult().getUser(); - userCallback(user, callback); - }else{ - userErrorCallback(task, callback); - } + 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 @@ -405,79 +498,79 @@ public void facebookLogin(String Token, final Callback callback) { .addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - if (task.isSuccessful()) { - user = task.getResult().getUser(); - userCallback(user, callback); - }else{ - userErrorCallback(task, callback); - } + 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 onComplete) { - WritableMap userMap = getUserMap(); - - if (passedUser == null) { - mAuth = FirebaseAuth.getInstance(); - final FirebaseUser user = mAuth.getCurrentUser(); - } else { - final FirebaseUser user = passedUser; - } - - user.getToken(true).addOnCompleteListener(new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = Arguments.createMap(); - - if (user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", false); - } - - msgMap.putMap("user", userMap); + @ReactMethod + public void twitterLogin(String Token, String Secret, final Callback callback) { + mAuth = FirebaseAuth.getInstance(); - onComplete.invoke(null, msgMap); - } - }); + AuthCredential credential = TwitterAuthProvider.getCredential(Token, Secret); + 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); + } + }); } - // TODO: Reduce to one method - public void anonymousUserCallback(FirebaseUser passedUser, final Callback onComplete) { - WritableMap userMap = getUserMap(); + // Internal helpers + public void userCallback(FirebaseUser passedUser, final Callback callback) { if (passedUser == null) { mAuth = FirebaseAuth.getInstance(); - final FirebaseUser user = mAuth.getCurrentUser(); + this.user = mAuth.getCurrentUser(); } else { - final FirebaseUser user = passedUser; + this.user = passedUser; } - user.getToken(true).addOnCompleteListener(new OnCompleteListener() { + this.user.getToken(false).addOnCompleteListener(new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { - WritableMap msgMap = Arguments.createMap(); - WritableMap userMap = Arguments.createMap(); - - if (user != null) { - final String token = task.getResult().getToken(); - - userMap.putString("token", token); - userMap.putBoolean("anonymous", true); + WritableMap msgMap = Arguments.createMap(); + WritableMap userMap = getUserMap(); + boolean authenticated = false; + if (FirestackAuthModule.this.user != null) { + final String token = task.getResult().getToken(); + userMap.putString("token", token); + authenticated = true; } msgMap.putMap("user", userMap); - - onComplete.invoke(null, msgMap); + msgMap.putBoolean("authenticated", authenticated); + 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(); @@ -489,12 +582,23 @@ public void noUserCallback(final Callback callback) { } public void userErrorCallback(Task task, final Callback onFail) { - WritableMap error = Arguments.createMap(); - error.putInt("errorCode", task.getException().hashCode()); - error.putString("errorMessage", task.getException().getMessage()); - error.putString("allErrorMessage", task.getException().toString()); + userExceptionCallback(task.getException(), onFail); + } - onFail.invoke(error); + 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() { @@ -512,9 +616,10 @@ private WritableMap getUserMap() { userMap.putString("email", email); userMap.putString("uid", uid); userMap.putString("providerId", provider); - + userMap.putBoolean("emailVerified", user.isEmailVerified()); + userMap.putBoolean("anonymous", user.isAnonymous()); if (name != null) { - userMap.putString("name", name); + userMap.putString("displayName", name); } if (photoUrl != null) { diff --git a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java index 78f45c7..37aeca3 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackDatabase.java @@ -3,16 +3,11 @@ import android.content.Context; import android.util.Log; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.ListIterator; -import java.util.ArrayList; 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; @@ -24,11 +19,6 @@ import com.facebook.react.bridge.ReadableArray; 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.database.FirebaseDatabase; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.ChildEventListener; @@ -62,33 +52,35 @@ public void setModifiers(final ReadableArray modifiers) { public void addChildEventListener(final String name, final ReadableArray modifiers) { final FirestackDBReference self = this; - mEventListener = new ChildEventListener() { - @Override - public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent(name, mPath, dataSnapshot); - } + 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(name, mPath, dataSnapshot); - } + @Override + public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { + self.handleDatabaseEvent("child_changed", mPath, dataSnapshot); + } - @Override - public void onChildRemoved(DataSnapshot dataSnapshot) { - self.handleDatabaseEvent(name, mPath, dataSnapshot); - } + @Override + public void onChildRemoved(DataSnapshot dataSnapshot) { + self.handleDatabaseEvent("child_removed", mPath, dataSnapshot); + } - @Override - public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { - self.handleDatabaseEvent(name, 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); + } + }; + } - @Override - public void onCancelled(DatabaseError error) { - self.handleDatabaseError(name, mPath, error); - } - }; - Query ref = this.getDatabaseQueryAtPathAndModifiers(modifiers); ref.addChildEventListener(mEventListener); this.setListeningTo(mPath, name); @@ -144,6 +136,9 @@ public Boolean isListeningTo(final String path, final String 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); @@ -177,17 +172,24 @@ public void removeChildEventListener() { } public void removeValueEventListener() { + DatabaseReference ref = this.getDatabaseRef(); if (mValueListener != null) { - DatabaseReference ref = this.getDatabaseRef(); 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(); + WritableMap evt = Arguments.createMap(); evt.putString("eventName", name); evt.putString("path", path); evt.putMap("body", data); @@ -302,8 +304,12 @@ public String getName() { public void enablePersistence( final Boolean enable, final Callback callback) { - FirebaseDatabase.getInstance() + try { + FirebaseDatabase.getInstance() .setPersistenceEnabled(enable); + } catch (Throwable t) { + Log.e(TAG, "FirebaseDatabase setPersistenceEnabled exception", t); + } WritableMap res = Arguments.createMap(); res.putString("status", "success"); @@ -430,7 +436,7 @@ public void on(final String path, final ReadableArray modifiers, final String name, final Callback callback) { - FirestackDBReference ref = this.getDBHandle(path, name); + FirestackDBReference ref = this.getDBHandle(path); WritableMap resp = Arguments.createMap(); @@ -440,7 +446,7 @@ public void on(final String path, ref.addChildEventListener(name, modifiers); } - this.saveDBHandle(path, name, ref); + this.saveDBHandle(path, ref); resp.putString("result", "success"); Log.d(TAG, "Added listener " + name + " for " + ref); @@ -454,23 +460,20 @@ public void onOnce(final String path, final String name, final Callback callback) { Log.d(TAG, "Setting one-time listener on event: " + name + " for path " + path); - FirestackDBReference ref = this.getDBHandle(path, "once"); + 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, final String name, final Callback callback) { - // TODO - FirestackDBReference ref = this.getDBHandle(path, name); - - if (name.equals("value")) { - ref.removeValueEventListener(); - } else { - ref.removeChildEventListener(); - } - - this.removeDBHandle(path, name); - Log.d(TAG, "Removed listener " + name); + 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"); @@ -570,29 +573,24 @@ private void handleCallback( } } - private FirestackDBReference getDBHandle(final String path, final String eventName) { - String key = this.keyPath(path, eventName); - if (!mDBListeners.containsKey(key)) { + private FirestackDBReference getDBHandle(final String path) { + if (!mDBListeners.containsKey(path)) { ReactContext ctx = getReactApplicationContext(); - mDBListeners.put(key, new FirestackDBReference(ctx, path)); + mDBListeners.put(path, new FirestackDBReference(ctx, path)); } - return mDBListeners.get(key); + return mDBListeners.get(path); } - private void saveDBHandle(final String path, - final String eventName, - final FirestackDBReference dbRef) { - String key = this.keyPath(path, eventName); - this.removeDBHandle(key, eventName); - mDBListeners.put(key, dbRef); + private void saveDBHandle(final String path, final FirestackDBReference dbRef) { + mDBListeners.put(path, dbRef); } - private void removeDBHandle(final String path, final String eventName) { - String key = this.keyPath(path, eventName); - if (mDBListeners.containsKey(key)) { - FirestackDBReference r = mDBListeners.get(key); + private void removeDBHandle(final String path) { + if (mDBListeners.containsKey(path)) { + FirestackDBReference r = mDBListeners.get(path); r.cleanup(); + mDBListeners.remove(path); } } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackModule.java b/android/src/main/java/io/fullstack/firestack/FirestackModule.java index ac2418f..2ec7972 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackModule.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackModule.java @@ -193,14 +193,14 @@ public void serverValue(@Nullable final Callback onComplete) { @Override public void onHostResume() { WritableMap params = Arguments.createMap(); - params.putBoolean("isForground", true); + params.putBoolean("isForeground", true); FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); } @Override public void onHostPause() { WritableMap params = Arguments.createMap(); - params.putBoolean("isForground", false); + params.putBoolean("isForeground", false); FirestackUtils.sendEvent(mReactContext, "FirestackAppState", params); } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java index ea5927d..b84edb0 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackPackage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackPackage.java @@ -29,8 +29,8 @@ public List createNativeModules(ReactApplicationContext reactConte 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 FirestackStorage(reactContext)); + // modules.add(new FirestackCloudMessaging(reactContext)); return modules; } diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index 667958e..691aa48 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -43,7 +43,7 @@ import com.google.firebase.storage.StorageMetadata; import com.google.firebase.storage.StorageReference; -class FirestackStorageModule extends ReactContextBaseJavaModule { +class FirestackStorage extends ReactContextBaseJavaModule { private static final String TAG = "FirestackStorage"; private static final String DocumentDirectoryPath = "DOCUMENT_DIRECTORY_PATH"; @@ -53,6 +53,7 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { 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 BundlePath = "MAIN_BUNDLE_PATH"; private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; @@ -62,8 +63,10 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private ReactContext mReactContext; private FirebaseApp app; - public FirestackStorageModule(ReactApplicationContext reactContext) { + public FirestackStorage(ReactApplicationContext reactContext) { super(reactContext); + + Log.d(TAG, "Attaching FirestackStorage"); this.context = reactContext; mReactContext = reactContext; @@ -96,8 +99,9 @@ public void onSuccess(Uri uri) { res.putString("bucket", storageRef.getBucket()); res.putString("fullPath", uri.toString()); res.putString("path", uri.getPath()); + res.putString("url", uri.toString()); - storageRef.getMetadata() + fileRef.getMetadata() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(final StorageMetadata storageMetadata) { @@ -112,7 +116,7 @@ public void onSuccess(final StorageMetadata storageMetadata) { metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); metadata.putString("md5hash", storageMetadata.getMd5Hash()); metadata.putString("encoding", storageMetadata.getContentEncoding()); - metadata.putString("downloadUrl", storageMetadata.getDownloadUrl().toString()); + res.putString("url", storageMetadata.getDownloadUrl().toString()); res.putMap("metadata", metadata); callback.invoke(null, res); @@ -145,7 +149,7 @@ public void onFailure(@NonNull Exception exception) { 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 storageRef = urlStr!=null ? storage.getReferenceFromUrl(urlStr) : storage.getReference(); StorageReference fileRef = storageRef.child(name); Log.i(TAG, "From file: " + filepath + " to " + urlStr + " with name " + name); @@ -278,6 +282,7 @@ public Map getConstants() { constants.put(CachesDirectoryPath, this.getReactApplicationContext().getCacheDir().getAbsolutePath()); constants.put(FileTypeRegular, 0); constants.put(FileTypeDirectory, 1); + constants.put(BundlePath, null); File externalStorageDirectory = Environment.getExternalStorageDirectory(); if (externalStorageDirectory != null) { diff --git a/ios/Firestack.xcodeproj/project.pbxproj b/ios/Firestack.xcodeproj/project.pbxproj index ba05903..05df7d2 100644 --- a/ios/Firestack.xcodeproj/project.pbxproj +++ b/ios/Firestack.xcodeproj/project.pbxproj @@ -203,6 +203,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; }; name = Debug; }; @@ -237,6 +238,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + USER_HEADER_SEARCH_PATHS = ""; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/ios/Firestack/Firestack.h b/ios/Firestack/Firestack.h index ffb5cc3..e148a80 100644 --- a/ios/Firestack/Firestack.h +++ b/ios/Firestack/Firestack.h @@ -16,10 +16,9 @@ } // + (void) registerForNotification:(NSString *) typeStr andToken:(NSData *)deviceToken; -+ (void) setup:(UIApplication *) application ++ (void) setup:(UIApplication *) application withLaunchOptions: (NSDictionary *) launchOptions; -+ (void) reloadFirestack; + (id) sharedInstance; - (void) debugLog:(NSString *)title @@ -35,4 +34,4 @@ withLaunchOptions: (NSDictionary *) launchOptions; @end -#endif \ No newline at end of file +#endif diff --git a/ios/Firestack/Firestack.m b/ios/Firestack/Firestack.m index 284f38b..717311d 100644 --- a/ios/Firestack/Firestack.m +++ b/ios/Firestack/Firestack.m @@ -24,14 +24,14 @@ - (void)dealloc } // TODO: Implement -+ (void) setup:(UIApplication *) application ++ (void) setup:(UIApplication *) application withLaunchOptions: (NSDictionary *) launchOptions { NSLog(@"Called setup for firestack with application"); - + dispatch_once(&onceToken, ^{ [application registerForRemoteNotifications]; - + [[NSNotificationCenter defaultCenter] postNotificationName:kFirestackInitialized object:nil]; @@ -42,7 +42,7 @@ - (id) init { self = [super init]; if (self != nil) { - NSLog(@"Setting up Firestace instance"); + NSLog(@"Setting up Firestack instance"); [Firestack initializeFirestack:self]; } return self; @@ -52,12 +52,7 @@ + (void) initializeFirestack:(Firestack *) instance { dispatch_once(&onceToken, ^{ _sharedInstance = instance; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reloadFirestack) - name:RCTReloadNotification - object:nil]; - + [[NSNotificationCenter defaultCenter] postNotificationName:kFirestackInitialized object:nil]; @@ -70,15 +65,6 @@ + (instancetype) sharedInstance return _sharedInstance; } -+ (void) reloadFirestack -{ - // Reloading firestack - onceToken = 0; // not sure if this is a good idea or a bad idea... - [[Firestack sharedInstance] debugLog:@"Firestack" - msg:@"Reloading firestack"]; - _sharedInstance = nil; -} - - (FIRApp *) firebaseApp { return [FIRApp defaultApp]; @@ -101,7 +87,7 @@ - (FIRApp *) firebaseApp // Are we debugging, yo? self.debug = [opts valueForKey:@"debug"] != nil ? YES : NO; NSLog(@"options passed into configureWithOptions: %@", [opts valueForKey:@"debug"]); - + NSDictionary *keyMapping = @{ @"GOOGLE_APP_ID": @[ @"appId", @@ -158,18 +144,18 @@ - (FIRApp *) firebaseApp ] }; NSArray *optionKeys = [keyMapping allKeys]; - + NSMutableDictionary *props; - + NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"]; - + if ([[NSFileManager defaultManager] fileExistsAtPath:plistPath]) { // If the Firebase plist is included props = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath]; } else { props = [[NSMutableDictionary alloc] initWithCapacity:[optionKeys count]]; } - + // Bundle ID either from options OR from the main bundle NSString *bundleID; if ([opts valueForKey:@"bundleID"]) { @@ -178,7 +164,7 @@ - (FIRApp *) firebaseApp bundleID = [[NSBundle mainBundle] bundleIdentifier]; } [props setValue:bundleID forKey:@"BUNDLE_ID"]; - + // Prefer the user configuration options over the default options for (int i=0; i < [optionKeys count]; i++) { // Traditional for loop here @@ -189,9 +175,9 @@ - (FIRApp *) firebaseApp NSString *value = [opts valueForKey:key]; [props setValue:value forKey:key]; } - + NSArray *possibleNames = [keyMapping objectForKey:key]; - + for (NSString *name in possibleNames) { if ([opts valueForKey:name] != nil) { // The user passed this option in @@ -205,7 +191,7 @@ - (FIRApp *) firebaseApp NSLog(@"An error occurred: %@", err); } } - + @try { if (self.debug) { NSLog(@"props ->: %@", props); @@ -220,7 +206,7 @@ - (FIRApp *) firebaseApp NSLog(@"STORAGE_BUCKET: %@", [props valueForKey:@"STORAGE_BUCKET"]); NSLog(@"DEEP_LINK_SCHEME: %@", [props valueForKey:@"DEEP_LINK_SCHEME"]); } - + FIROptions *finalOptions = [[FIROptions alloc] initWithGoogleAppID:[props valueForKey:@"GOOGLE_APP_ID"] bundleID:[props valueForKey:@"BUNDLE_ID"] @@ -232,16 +218,16 @@ - (FIRApp *) firebaseApp databaseURL:[props valueForKey:@"DATABASE_URL"] storageBucket:[props valueForKey:@"STORAGE_BUCKET"] deepLinkURLScheme:[props valueForKey:@"DEEP_LINK_SCHEME"]]; - + // Save configuration option // NSDictionary *cfg = [self getConfig]; // [cfg setValuesForKeysWithDictionary:props]; - + // if (!self.configured) { - + if ([FIRApp defaultApp] == NULL) { [FIRApp configureWithOptions:finalOptions]; - } + } [Firestack initializeFirestack:self]; callback(@[[NSNull null], props]); } @@ -382,8 +368,8 @@ - (void) debugLog:(NSString *)title // Not sure how to get away from this... yet - (NSArray *)supportedEvents { return @[ - INITIALIZED_EVENT, - DEBUG_EVENT, + INITIALIZED_EVENT, + DEBUG_EVENT, AUTH_CHANGED_EVENT]; } diff --git a/ios/Firestack/FirestackAuth.m b/ios/Firestack/FirestackAuth.m index 0eba61c..6d45075 100644 --- a/ios/Firestack/FirestackAuth.m +++ b/ios/Firestack/FirestackAuth.m @@ -17,7 +17,7 @@ @implementation FirestackAuth RCT_EXPORT_MODULE(FirestackAuth); RCT_EXPORT_METHOD(signInAnonymously: - (RCTResponseSenderBlock) callBack) + (RCTResponseSenderBlock) callback) { @try { [[FIRAuth auth] signInAnonymouslyWithCompletion @@ -25,29 +25,33 @@ @implementation FirestackAuth if (!user) { NSDictionary *evt = @{ @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, - @"msg": [error localizedDescription] + @"errorMessage": [error localizedDescription] }; - [self sendJSEvent:AUTH_CHANGED_EVENT + [self sendJSEvent:AUTH_CHANGED_EVENT props: evt]; - callBack(@[evt]); + callback(@[evt]); } else { - NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callBack(@[[NSNull null], userProps]); + NSDictionary *userProps = [self userPropsFromFIRUser:user]; + NSDictionary *responseProps = @{ + @"authenticated": @((BOOL) true), + @"user": userProps + }; + callback(@[[NSNull null], responseProps]); } }]; } @catch(NSException *ex) { NSDictionary *eventError = @{ @"eventName": AUTH_ANONYMOUS_ERROR_EVENT, - @"msg": ex.reason + @"errorMessage": ex.reason }; - + [self sendJSEvent:AUTH_ERROR_EVENT props:eventError]; NSLog(@"An exception occurred: %@", ex); - callBack(@[eventError]); + callback(@[eventError]); } } @@ -61,7 +65,11 @@ @implementation FirestackAuth if (user != nil) { NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + NSDictionary *responseProps = @{ + @"authenticated": @((BOOL) true), + @"user": userProps + }; + callback(@[[NSNull null], responseProps]); } else { NSDictionary *err = [FirestackErrors handleFirebaseError:AUTH_ERROR_EVENT @@ -94,9 +102,15 @@ @implementation FirestackAuth if (user != nil) { // User is signed in. NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + NSDictionary *responseProps = @{ + @"authenticated": @((BOOL) true), + @"user": userProps + }; + callback(@[[NSNull null], responseProps]); } else { NSLog(@"An error occurred: %@", [error localizedDescription]); + NSLog(@"[Error signInWithProvider]: %@", [error userInfo]); + NSLog(@"%@", [NSThread callStackSymbols]); // No user is signed in. NSDictionary *err = @{ @"error": @"No user signed in", @@ -144,14 +158,15 @@ @implementation FirestackAuth sendJSEvent:AUTH_CHANGED_EVENT props: @{ @"eventName": @"userTokenError", - @"msg": [error localizedFailureReason] + @"authenticated": @((BOOL)false), + @"errorMessage": [error localizedFailureReason] }]; } else { [self sendJSEvent:AUTH_CHANGED_EVENT props: @{ @"eventName": @"user", - @"authenticated": @(true), + @"authenticated": @((BOOL)true), @"user": userProps }]; } @@ -164,7 +179,7 @@ @implementation FirestackAuth [self sendJSEvent:AUTH_CHANGED_EVENT props:@{ @"eventName": @"no_user", - @"authenticated": @(false), + @"authenticated": @((BOOL)false), @"error": err }]; } @@ -186,10 +201,15 @@ @implementation FirestackAuth if (user != nil) { NSDictionary *userProps = [self userPropsFromFIRUser:user]; - callback(@[[NSNull null], userProps]); + NSDictionary *responseProps = @{ + @"authenticated": @((BOOL) true), + @"user": userProps + }; + callback(@[[NSNull null], responseProps]); } else { // No user is signed in. NSDictionary *err = @{ + @"authenticated": @((BOOL) false), @"user": @"No user logged in" }; callback(@[err]); @@ -228,10 +248,11 @@ @implementation FirestackAuth completion:^(FIRUser *user, NSError *error) { if (user != nil) { NSDictionary *userProps = [self userPropsFromFIRUser:user]; - - callback(@[[NSNull null], @{ - @"user": userProps - }]); + NSDictionary *responseProps = @{ + @"authenticated": @((BOOL) true), + @"user": userProps + }; + callback(@[[NSNull null], responseProps]); } else { NSDictionary *err = [FirestackErrors handleFirebaseError:@"signinError" @@ -478,6 +499,8 @@ - (FIRAuthCredential *)getCredentialForProvider:(NSString *)provider } else if ([provider compare:@"google" options:NSCaseInsensitiveSearch] == NSOrderedSame) { credential = [FIRGoogleAuthProvider credentialWithIDToken:authToken accessToken:authTokenSecret]; + } else if ([provider compare:@"github" options:NSCaseInsensitiveSearch] == NSOrderedSame) { + credential = [FIRGitHubAuthProvider credentialWithToken:authToken]; } else { NSLog(@"Provider not yet handled: %@", provider); } diff --git a/ios/Firestack/FirestackCloudMessaging.m b/ios/Firestack/FirestackCloudMessaging.m index 9cfa6b3..ca0f1ca 100644 --- a/ios/Firestack/FirestackCloudMessaging.m +++ b/ios/Firestack/FirestackCloudMessaging.m @@ -13,28 +13,7 @@ #endif #import "FirestackCloudMessaging.h" #import "FirestackEvents.h" -#import "RCTConvert.h" - -// https://github.com/facebook/react-native/blob/master/Libraries/PushNotificationIOS/RCTPushNotificationManager.m -@implementation RCTConvert (UILocalNotification) - -+ (UILocalNotification *)UILocalNotification:(id)json -{ - NSDictionary *details = [self NSDictionary:json]; - UILocalNotification *notification = [UILocalNotification new]; - notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date]; - notification.alertBody = [RCTConvert NSString:details[@"alertBody"]]; - notification.alertAction = [RCTConvert NSString:details[@"alertAction"]]; - notification.soundName = [RCTConvert NSString:details[@"soundName"]] ?: UILocalNotificationDefaultSoundName; - notification.userInfo = [RCTConvert NSDictionary:details[@"userInfo"]]; - notification.category = [RCTConvert NSString:details[@"category"]]; - if (details[@"applicationIconBadgeNumber"]) { - notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"applicationIconBadgeNumber"]]; - } - return notification; -} - -@end +// #import "RCTConvert.h" @implementation FirestackCloudMessaging @@ -89,34 +68,39 @@ + (void) setup:(UIApplication *) application selector:@selector(handleTokenRefresh) name:kFIRInstanceIDTokenRefreshNotification object: nil]; +} +#pragma mark Request permissions +- (void) requestPermissions:(NSDictionary *)requestedPermissions + callback:(RCTResponseSenderBlock) callback +{ if (SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(@"9.0")) { - UIUserNotificationType allNotificationTypes = - (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); - UIUserNotificationSettings *settings = - [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; -} else { - // iOS 10 or later - #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - UNAuthorizationOptions authOptions = - UNAuthorizationOptionAlert - | UNAuthorizationOptionSound - | UNAuthorizationOptionBadge; - [[UNUserNotificationCenter currentNotificationCenter] - requestAuthorizationWithOptions:authOptions - completionHandler:^(BOOL granted, NSError * _Nullable error) { - } - ]; - - // For iOS 10 display notification (sent via APNS) - [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; - // For iOS 10 data message (sent via FCM) - [[FIRMessaging messaging] setRemoteMessageDelegate:self]; - #endif -} + UIUserNotificationType allNotificationTypes = + (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); + UIUserNotificationSettings *settings = + [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + } else { + // iOS 10 or later + #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + UNAuthorizationOptions authOptions = + UNAuthorizationOptionAlert + | UNAuthorizationOptionSound + | UNAuthorizationOptionBadge; + [[UNUserNotificationCenter currentNotificationCenter] + requestAuthorizationWithOptions:authOptions + completionHandler:^(BOOL granted, NSError * _Nullable error) { + } + ]; + + // For iOS 10 display notification (sent via APNS) + [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; + // For iOS 10 data message (sent via FCM) + [[FIRMessaging messaging] setRemoteMessageDelegate:self]; + #endif + } -[[UIApplication sharedApplication] registerForRemoteNotifications]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; } #pragma mark callbacks diff --git a/ios/Firestack/FirestackStorage.m b/ios/Firestack/FirestackStorage.m index d88a19a..6d289d5 100644 --- a/ios/Firestack/FirestackStorage.m +++ b/ios/Firestack/FirestackStorage.m @@ -25,7 +25,12 @@ - (dispatch_queue_t)methodQueue path:(NSString *) path callback:(RCTResponseSenderBlock) callback) { - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; + FIRStorageReference *storageRef; + if (storageUrl == nil ) { + storageRef = [[FIRStorage storage] reference]; + } else { + storageRef = [[FIRStorage storage] referenceForURL:storageUrl]; + } FIRStorageReference *fileRef = [storageRef child:path]; [fileRef downloadURLWithCompletion:^(NSURL * _Nullable URL, NSError * _Nullable error) { if (error != nil) { @@ -52,14 +57,13 @@ - (dispatch_queue_t)methodQueue metadata:(NSDictionary *)metadata callback:(RCTResponseSenderBlock) callback) { + FIRStorageReference *storageRef; if (urlStr == nil) { - NSError *err = [[NSError alloc] init]; - [err setValue:@"Storage configuration error" forKey:@"name"]; - [err setValue:@"Call setStorageUrl() first" forKey:@"description"]; - return callback(@[err]); + storageRef = [[FIRStorage storage] reference]; + } else { + storageRef = [[FIRStorage storage] referenceForURL:urlStr]; } - FIRStorageReference *storageRef = [[FIRStorage storage] referenceForURL:urlStr]; FIRStorageReference *uploadRef = [storageRef child:name]; FIRStorageMetadata *firmetadata = [[FIRStorageMetadata alloc] initWithDictionary:metadata]; @@ -131,6 +135,7 @@ - (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask @"fullPath": ref.fullPath, @"bucket": ref.bucket, @"name": ref.name, + @"downloadUrl": snapshot.metadata.downloadURLs[0].absoluteString, @"metadata": [snapshot.metadata dictionaryRepresentation] }; @@ -157,6 +162,7 @@ - (void) addUploadObservers:(FIRStorageUploadTask *) uploadTask case FIRStorageErrorCodeUnknown: // Unknown error occurred, inspect the server response [errProps setValue:@"Unknown error" forKey:@"description"]; + NSLog(@"Unknown error: %@", snapshot.error); break; } diff --git a/lib/modules/authentication.js b/lib/modules/authentication.js index 12fdcd4..c06edfc 100644 --- a/lib/modules/authentication.js +++ b/lib/modules/authentication.js @@ -9,18 +9,6 @@ import { Base } from './base' export class Authentication extends Base { constructor(firestack, options={}) { super(firestack, options); - - this._addToFirestackInstance( - 'listenForAuth', 'unlistenForAuth', - 'createUserWithEmail', 'signInWithEmail', - 'signInAnonymously', - 'signInWithProvider', 'signInWithCustomToken', - 'reauthenticateWithCredentialForProvider', - 'updateUserEmail', 'updatePassword', - 'updateUserProfile', 'sendPasswordResetWithEmail', - 'deleteUser', 'getToken', - 'signOut', 'getCurrentUser' - ) } // Auth @@ -169,4 +157,4 @@ export class Authentication extends Base { } } -export default Authentication \ No newline at end of file +export default Authentication diff --git a/lib/modules/cloudmessaging.js b/lib/modules/cloudmessaging.js index a03a2ed..73b390b 100644 --- a/lib/modules/cloudmessaging.js +++ b/lib/modules/cloudmessaging.js @@ -1,12 +1,20 @@ -import {NativeModules, NativeEventEmitter} from 'react-native'; +import {Platform, NativeModules, NativeEventEmitter} from 'react-native'; const FirestackCloudMessaging = NativeModules.FirestackCloudMessaging; const FirestackCloudMessagingEvt = new NativeEventEmitter(FirestackCloudMessaging); import promisify from '../utils/promisify' import { Base, ReferenceBase } from './base' + +const defaultPermissions = { + 'badge': 1, + 'sound': 2, + 'alert': 3 +} export class CloudMessaging extends Base { constructor(firestack, options = {}) { super(firestack, options); + + this.requestedPermissions = Object.assign({}, defaultPermissions, options.permissions); } get namespace() { return 'firestack:cloudMessaging' @@ -16,6 +24,20 @@ export class CloudMessaging extends Base { return promisify('getToken', FirestackCloudMessaging)(); } + // Request FCM permissions + requestPermissions(requestedPermissions = {}) { + if (Platform.OS === 'ios') { + const mergedRequestedPermissions = Object.assign({}, + this.requestedPermissions, + requestedPermissions); + return promisify('requestPermissions', FirestackCloudMessaging)(mergedRequestedPermissions) + .then(perms => { + + return perms; + }); + } + } + sendMessage(details:Object = {}, type:string='local') { const methodName = `send${type == 'local' ? 'Local' : 'Remote'}` this.log.info('sendMessage', methodName, details); diff --git a/lib/modules/database.js b/lib/modules/database.js index f7f91fe..cbf3bc4 100644 --- a/lib/modules/database.js +++ b/lib/modules/database.js @@ -35,7 +35,7 @@ class DataSnapshot { forEach(fn) { (this.childKeys || []) - .forEach(key => fn(this.value[key])) + .forEach(key => fn({key: key, value: this.value[key]})) } map(fn) { @@ -96,7 +96,7 @@ class DatabaseQuery { } setFilter(name, ...args) { - this.filters[name] = args; + this.filters[name] = args.filter(n => n != undefined); return this.ref; } @@ -111,9 +111,10 @@ class DatabaseQuery { } Object.keys(this.filters) .forEach(key => { - const filter = this.filters[key]; + let filter = this.filters[key]; if (filter) { - const filterArgs = [key, filter].join(argsSeparator) + const cleanFilters = filter.filter((f) => typeof f !== "undefined"); + const filterArgs = ([key].concat(cleanFilters)).join(argsSeparator); modifiers.push(filterArgs); } }) @@ -232,7 +233,7 @@ class DatabaseRef extends ReferenceBase { const path = this.dbPath(); return this.db.off(path, evt, origCB) .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt].length > 0) { + if (dbSubscriptions[path] && dbSubscriptions[path][evt] && dbSubscriptions[path][evt].length > 0) { return subscriptions; } @@ -514,4 +515,4 @@ export class Database extends Base { } } -export default Database \ No newline at end of file +export default Database diff --git a/lib/modules/storage.js b/lib/modules/storage.js index 6e5f46a..3434415 100644 --- a/lib/modules/storage.js +++ b/lib/modules/storage.js @@ -6,6 +6,8 @@ const FirestackStorageEvt = new NativeEventEmitter(FirestackStorage); import promisify from '../utils/promisify' import { Base, ReferenceBase } from './base' +console.log('FirestackStorage ---->', FirestackStorage); + class StorageRef extends ReferenceBase { constructor(storage, path) { super(storage.firestack, path); diff --git a/lib/utils/window-or-global.js b/lib/utils/window-or-global.js index 3228c06..c19a003 100644 --- a/lib/utils/window-or-global.js +++ b/lib/utils/window-or-global.js @@ -2,4 +2,4 @@ // https://github.com/purposeindustries/window-or-global module.exports = (typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global) || - this \ No newline at end of file + this diff --git a/package.json b/package.json index e611094..a84919f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-firestack", - "version": "2.3.3", + "version": "2.3.8", "author": "Ari Lerner (https://fullstackreact.com)", "description": "A firebase v3 adapter", "main": "index", @@ -67,6 +67,7 @@ }, "dependencies": { "bows": "^1.6.0", - "es6-symbol": "^3.1.0" + "es6-symbol": "^3.1.0", + "invariant": "^2.2.2" } }