bartonhammond / snowflake

:snowflake: A React-Native Android iOS Starter App/ BoilerPlate / Example with Redux, RN Router, & Jest with the Snowflake Hapi Server running locally or on RedHat OpenShift for the backend, or a Parse Server running locally or remotely on Heroku
http://bartonhammond.github.io/snowflake/snowflake.js.html
MIT License
4.59k stars 613 forks source link

Firebase integration #30

Closed Froelund closed 8 years ago

Froelund commented 8 years ago

Hi!

I've started writing integration to Firebase. I have a few issues.

One is that Firebase doesn't have the possibility to use username as user identifier, it only relies on email. Do you @bartonhammond think it would be a better idea to fork the project in a firebase version, or write the integration alongside the Parse integration?

You are managing the session token in authActions. Wouldn't it be better to manage that in the Parse.js API-class? And have the Parse class handle the different response codes. Also instead of creating a new instance of Parse class every time one of the actions are fired, authActions could have an instance of the api class.

bartonhammond commented 8 years ago

Hi @Froelund - thanks for taking an interest into snowflake. You've asked numerous questions so let me respond.

0) Snowflake fork or ? - I haven't used Firebase so I don't know what the impact would be to snowflake. If one could write a Firebase class similar to Parse.js, then the snowflake code could be refactored to use one of the other solution. I would recommend abstracting a "BackEnd" class that Firebase and Parse both extend/implement so that the Actions don't know which implementation is used. The "BackEnd" abstraction would have the "signup, login, reset, etc" interfaces but the "Parse.js" or "Firebase.js" would have the concrete implementation. The choice of "BackEnd" would be in the config.js file. The snowflake code would just interact with the "BackEnd" class. If this abstrract "BackEnd" is the approach, the tests should all still pass as we would mock "BackEnd" rather then the "Parse.js" or "Firebase.js". So if possible, I'd prefer to see Firebase w/in Snowflake and the user can decide to choose one or the other.

1) Session token management- it's actually managed by AppAuthToken.js - I wanted to have all state change managed by Actions so I could save and restore the state. If this "BackEnd" class described above provides the "sessionToken" as AuthActions does, then the management of the token would still be abstracted.

I chose not to have Parse.js manage the token as that's a "bleeding concern" - the responsibility of Parse.js is to provide an interface to Parse.com. How the "token" is managed is not part of that responsibility. The responsibility of Parse.js is to provide the response that contains the token and to make calls to Parse.com and utilize that token no matter how it was stored or provided. If next week I want to change the storage mechanism, I update AppAuthToken only.

2) username as identifier - see authActions.signup. If email identifier were required by snowflake, while using Parse, I would just set the username to email. I would then remove the "username" field from the signup form. So with the "config.js" object setting the default "BackEnd" to Parse or Firebase, the Login class could be updated to display the "username" field or not. That toggling of the username field being required or not, could be something orthogonal to Firebase support. Parse.com can be setup to allow "username" access or not.

3) Parse.js manage the response codes? Again, to me, that would be a 'bleeding of concerns' - why should the responses be interpreted? It would mean that the Parse.js would have to know exactly what call was made and under what context. By keeping the Parse.js class as nothing more then a "conduit", or thin veneer, the business logic of handling the response is performed by the action. Parse.js is responsible for correctly interfacing w/ Parse.com but not responsible for the interpretation of the results. Now once a "BackEnd" class abstraction is made, it could be that there's some implementation in it or in Parse.js & Firebase.js that knows how to return a "standard" response. Since the actions know how to handle the responses, to support both "BackEnds", the responses coming from either have to be standardized. Again, not having experience w/ Firebase, I don't know how difficult that would be but I assume it's do-able.

3) New class every time actions fired? My intentions of having all my "framework"y type objects be classes, and to create them when needed, is to avoid any "state" issues. Since I'm working with Redux and Immutable objects, I don't want to have any state in other objects. I don't believe one can claim Immutability by convention - as soon as a class has state that can be modified, you've lost immutability claims. So classes like AppAuthToken and Parse.js get created, used and destroyed. In classic OO, objects have state. But in this new era of Redux, all the state is in Redux, which is a far superior solution imo. So my classes are stateless.

I hope this helps. I look forward to further discussions as you proceed.

bartonhammond commented 8 years ago

btw, @Froelund I've been looking at a local server / cloud solution for my next investment w/ snowflake, see #28. I'm very impressed with Hapi.js and want to move in that direction as an alternative "BackEnd" Up until this conversation with you about Firebase, I was thinking of a new project too! But now I've convinced myself that I need to have this "BackEnd" abstraction.

I'm currently wrapping my brain around Hapi.js and a OpenShift node.js cloud offering - it looks really affordable (free) for small projects. So I'll make a separate project or sub-project for this "OpenShift Hapi server" solution, and snowflake will be extended to optionally use this Hapi server as one of the alternatives ie, "Parse.com", "Hapi" or "Firebase".

Why do I want this "server" solution? Cause I need to be able to write "server side code". Parse.com has that ability but it's a very difficult environment to work in - they have their own private implementations of Promises and Http and no debugging ability other then console.log. You're not able to work locally with a debugger before deployment. I did a project w/ Parse.com cloud code and it was a horrible experience.

With a local nodejs server that can run exactly with the same code on the cloud, snowflake would provide a developer the ability to write server logic.

Looks like Firebase is looking a "triggers" so there may be a solution in the future with Firebase server logic. http://stackoverflow.com/questions/31340542/how-to-write-custom-code-logic-when-using-firebase.

At this time I'm focusing on http://hapijs.com/. Once I can implement a Hapi solution, it would be easy for someone to use Express solution, for example. My point is that having a "BackEnd" abstraction class supports these types of implementations.

Depending on how much time you're able to look at this, maybe I'll abstract this "BackEnd" class for Hapi and Parse and then you'll have a model for how to proceed w/ Firebase. Let's keep in communication.

-barton

Froelund commented 8 years ago

Thank you for your response.

0) Fork or ? We agree. I've made the integration with firebase now, just to play around with snowflake. I essentially just replaced Parse with Firebase. When you have the backend abstraction ready I will be happy to supply the Firebase implementation!

1) Session token management: My point is that the concept of tokens is very specific to the Parse integration, and shouldn't be get/put/deleted in the authActions.

2) Username as identifier: For these two integrations it would be adequate to switch on whatever Backend is supplied in the config. However if we imagine 5 maybe 10 different integrations it would be a lot of switching. This of course could be solved in various ways by having the supplied backend indicating it's own fields/identifiers. I just might become to generic?

3) Parse.js manage the response codes? For me this is a question about abstraction. If a backend implementation is supplied it should be the one managing the http-specific response codes, and deliver a Promise either resolved or rejected. From what I can see the only thing you are using the response codes to is determining what to do with the tokens(Reference to my previous point about tokens) and then which event to dispatch. If tokens were moved away from the authActions, the authActions would only be concerned about dispatching the events.

4) New class every time actions fired? Okay. I don't know Redux( It's on my list :) ) so I don't really get the point on this one. It might just be me needing to learn this new era :) BTW: Are they in fact Stateless when they are given a constructor argument? Couldn't you just make them static methods instead of a new instance each time?

5) Own server logic: I've been struggling with the very same issues. The Cloud Code is very tedious to work with. In my Firebase project I just wrote a server that's interacting with the Firebase API just as the connected clients. Same concept as this Pluggable Search Engine https://github.com/firebase/flashlight

bartonhammond commented 8 years ago

I should get to the abstraction within the newt couple of days. As I work through the implementation I may in fact change these very things you're mentioning. Thanks for the response and suggestions. I'll get back when I have something concrete.

Froelund commented 8 years ago

Sounds good :+1:

abeauvois commented 8 years ago

@Froelund I would be interested in your Firebase version of this great SnowFlake project from @bartonhammond. Any chance to have a look at it and eventually contribute to it?

bartonhammond commented 8 years ago

@abeauvois I had to do some refactoring to prepare for different server solutions.

I'm wrapping up a Snowflake free OpenShift scaling Nodejs server with Mongo & Redis now. Everything is working but I need to finish my performance tests and put together videos and docs.

I expect to release that Monday or so unless I run into issues.

After I release the Snowflake server, @Froelund should be able to address the Firebase option.

@Froelund: there is a "feature/openshift" branch that you can look at now to see how I approached the 'pluggable' client/server code, namely, src/lib/BackendFactory and Backend.js as a "base class" - just extend Backend and implement the Firebase logic. Then update the BackendFactory to produce the Firebase client server code.

There's also option in package.json to switch server implementation.

You should look at how I do documentation and document your code. I'd also like to have videos showing it working. If you're not able to do these things, that's fine - I'll do them. I just want to be consistent with the "quality" of docs and such. ;)

The "feature/openshift" branch is pretty stable but until I finish my performance tests it may change though I don't expect it to. But don't branch off that - wait until I merge my PR into the master. But, in preparation, you could look at that code.

Thanks for your interest in Snowflake @abeauvois !

bartonhammond commented 8 years ago

In case you're not watching the project, I released the OpenShift Hapi Server yesterday along with the modifications in Snowflake to toggle backends.

You should be good to go now

Froelund commented 8 years ago

Great. I'll take a look :+1:

Froelund commented 8 years ago

Hey @bartonhammond I have finished signup feature with firebase. However, two issues still to be resolved:

tarkanlar commented 8 years ago

Hey @froelund, is it possible to share the code? I am trying to write the same feature

Froelund commented 8 years ago

@tarkanlar Absolutely. I pushed the code to the fork I did: https://github.com/Froelund/snowflake

bartonhammond commented 8 years ago

@Froelund

1) For login to Firebase:

What I think we add a LOGIN_STATE_LOGIN_FIREBASE and when src/reducers/auth/authActions loginState() is called it simply does this:

export function loginState() {
if (CONFIG.backend.firebase) {
  return {
    type: LOGIN_STATE_LOGIN_FIREBASE
  };
} else {
return {
    type: LOGIN_STATE_LOGIN
  };
}

Then the authReducer just handles this new state just like the others:

 case LOGIN_STATE_LOGIN:
case LOGIN_STATE_LOGIN_FIREBASE: //////
  case LOGIN_STATE_REGISTER:
  case LOGIN_STATE_FORGOT_PASSWORD:
    return formValidation(
      state.setIn(['form', 'state'], action.type)
        .setIn(['form','error'],null)
    );

Then in src/containers/Login.js we just handle this new state:

...
  case(LOGIN_STATE_LOGIN_FIREBASE):
      loginButtonText = 'Log in';
      leftMessage = register;
      rightMessage = forgotPassword;
      onButtonPress = () => {
        this.props.actions.loginWithEmail(this.props.auth.form.fields.email, this.props.auth.form.fields.password); //<Notice new signature 'loginWithEmail'
      };
      passwordDisplay = itemCheckBox;
      break;

Then in src/components/LoginForm.js we have

      /**
       * ### Login for Firebase
       * The login form has only 2 fields
       */
    case(LOGIN_STATE_LOGIN_FIREBASE):
      loginForm = t.struct({
        email: t.String,
        password: t.String
      });
      options.fields['email'] = emal;
      options.fields['password'] = password;
        break;

I don't thinksrc/component/authAction.js should change much, just add a new method that supports firebase login, something like loginWithEmail.

2) Now for Profile we'll add a new action: something likeGET_PROFILE_SUCCESS_FIREBASE or another action set for describing the state of the Profile UI similiar to how Login is doing it now (LOGIN_STATE_LOGIN/REGISTER/LOGOUT...)

so in src/reducers/profile/profileActions.js we can check the config

export function getProfileSuccess(json) {
  if (CONFIG.backend.firebase) {
  return {
    type: GET_PROFILE_SUCCESS_FIREBASE,
    payload: json
  };
} else {
return {
    type: GET_PROFILE_SUCCESS,
    payload: json
  };
}
}

And then in src/reducers/profile/profileReducer.js we can check for that state, like in "authReducer.js" we'll set the state of the form

 case GET_PROFILE_SUCCESS:
 case GET_PROFILE_SUCCESS_FIREBASE:
    let nextProfileState =  state.setIn(['form', 'state'], action.type) //<<<< Controls which fields display
                    state.setIn(['form', 'isFetching'], false)

So now in src/containers/Profile.js we can add a switch statement for the UI just like in src/containers/Login.js - and within the case statements you can call a new updateProfileRequiringPassword method.

You may find it easier to refactor src/containers/Profile.js to be more like src/containers/Login.js and either reuse src/components/LoginForm.js or introduce a src/components/ProfileForm.js. In either case, Profile.js will have to handle two different states of the UI (Parse vs Firebase) which is very similar to how Login.js works (register, login, etc)

Rather then include the password structure / definition from LoginForm.js, we should extract the field definitions of username, password, email, passwordAgain out of src/components/LoginForm.js and also src/container/Profile.js.

Be sure to update the tests and such to cover the new methods and actions.

Does this make sense to you? It might be a little more work then we anticipated. I just don't want a lot of "if / then /else" statements all over the place - but rather a state like Login does.

What are you thoughts @Froelund?

yonahforst commented 8 years ago

@Froelund have you thought about how to take advantage of Firebase's Event callbacks while keeping with the BackendFactory abstraction?

@bartonhammond maybe you can pitch in here. What makes Firebase really cool is the ability to subscribe to queries on the server and get notified when their results change.

So, looking at @Froelund's implementation of getProfile():

  async getProfile() {
    return new Promise((resolve, reject) => {
      if(this._profile != null){
        this.firebaseRef.root().child('profiles/profile').child(this._profile.objectId).once('value', function (profileDataSnapshot) {
          var profile = Object.assign(this._profile, profileDataSnapshot.val());
          resolve(profile);
        }.bind(this), function (err) {
          reject(err);
        });
      }else{
        reject('Not logged in');
      }
    });
  }

if we changed ....once('value', ... to ...on('value',... the given function would be called any/every time the profile object is changed on the server.

I think this is awesome, especially for mobile apps. But I'm guessing BackendFactory and redux-thunk actions weren't indented to be used with repeating responses.

I'm curious if you guys see these kinds of localState<->serverState sync features fitting into the current Snowflake setup.

bartonhammond commented 8 years ago

@joshblour @Froelund Well - I saw a lot of this "reactive" type programming w/ Meteor where the data model on the client would just update automatically. Snowflake doesn't address this nor does Parse.com.

For example, when you validate your email, Parse doesn't have any "after" change hook on the User record that gives you the ability to message the app of the change.

What Snowflake does is just show the current value of "emailVerified". I know that's old school but it is easy to grasp and the main objective for Snowflake was demonstrating React Native & Redux - not necessarily reactive programming.

With the type of app that Snowflake is right now, it's not a huge issue - I figure if you verified your email, and then get into the app and it doesn't indicate you verified, well, you would understand that you haven't refreshed.

Also, if you used 2 devices and on one you updated your password, username and email and then later got on another device that was still logged in, you wouldn't see that updated username & email until you somehow refreshed. But since "you" did it, "you" would know the app just hasn't refreshed the data.

I have to think about this more.

I see there are numerous libraries like http://www.ractivejs.org/ and http://reactivex.io/ and some projects like https://github.com/escherpad/luna.

What are your thoughts?

yonahforst commented 8 years ago

@bartonhammond yeah, that makes total sense. It's definitely not necessary for Snowflake, as a demonstration.

I'm using Snowflake as a base for a project where this kind of sync would really useful and was wondering about how it fits in, if it all. I found this https://github.com/erikras/react-redux-universal-hot-example/issues/252#issuecomment-143929077 which seems promising but I still have to wrap my head around it.

bartonhammond commented 8 years ago

@joshblour Well this has peaked my interest - I'm looking in to a general solution, if I can.

bartonhammond commented 8 years ago

So I found Falcor from Netflix. I am going to look at what impact that would be on Snowflake.

Maybe you could look at https://auth0.com/docs/quickstart/backend/firebase/falcor?

bartonhammond commented 8 years ago

The idea with Falcor is we would have 1 client json model to work with and just swap out the server without affecting the client. There is a Redux -Falcor project. If we go this route I will probably drop Parse or have it on a separate branch.

yonahforst commented 8 years ago

I've never heard of Falcor but from what I'm reading, what I understand is that it's a client library + server 'router' that lets you plug in your datasource-of-choice. But wouldn't this force users to deploy their own server, even if they want to use cloud database like parse or firebase?

bartonhammond commented 8 years ago

@joshblour - yeah - that's the understanding I have too at this point. I'm still learning about it myself. What I was hoping for was a client that didn't know or care what the server implementation was.

Right now, Snowflake has to be configured to work w/ one server of the other. If it's possible to swap out backends w/o modifying the client, and we understand how to support Firebase with Falcor, I think it's a step in the right direction.

I'm not sure I want to continue supporting Parse. Worse case is though, we could create a branch w/ the current implementation while the master moves on to Falcor.

Hopefully w/in a few days I'll have a better understanding and what the impact would be.

bartonhammond commented 8 years ago

@joshblour Forget about Falcor - it won't work with RN at this time. And besides, it doesn't have capabilities like Firebase with real time notification. But it is interesting technology.

yonahforst commented 8 years ago

I've also moved away from Firebase. The data structure and querying abilities didn't seem like a good fit for my app. I'm still interested in finding a reactive backend solution. I've been looking at rethinkDB, which seems promising, but is missing built-in authentication

bartonhammond commented 8 years ago

@Froelund Your Firebase integration might have some stronger legs: Parse.com is shutting down: #51

bartonhammond commented 8 years ago

@Froelund - Not sure if you saw this: https://www.firebase.com/blog/2016-01-20-tutorial-firebase-react-native.html

abachuk commented 8 years ago

+1 for Firebase

ericpeters0n commented 8 years ago

Yeah for backend abstraction!

yonahforst commented 8 years ago

So for anyone still watching this thread, I'm moving back to parse (my own hosted parse-server) from firebase because they just added realtime capabilities! yay! https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery

Also, they seem to be working on a new react-native + redux library: https://github.com/ParsePlatform/ParseReact/commit/16c836269a55a7a630ff17d264b77169a6f87472

naoisegolden commented 8 years ago

+1 to firebase for people that don't want a DIY approach to the persistence/backend layer

bartonhammond commented 8 years ago

I have no personal interest in pursuing Firebase at this time. If someone wants to add that option and they follow the current pattern of accessing the backend, they can make a PR.

But you might want to wait a bit as i am currently working at upgrading to RN 29. Once that is complete I plan on building a Serverless Framework with GraphQL. On Jul 20, 2016 4:33 AM, "Naoise Golden Santos" notifications@github.com wrote:

+1 to firebase for people that don't want a DIY approach to the persistence/backend layer

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/bartonhammond/snowflake/issues/30#issuecomment-233900258, or mute the thread https://github.com/notifications/unsubscribe-auth/ABORPMfUG6r9JOWoEldCDiZ4QsefOaZ7ks5qXet3gaJpZM4G746s .

bartonhammond commented 8 years ago

I personally won't be doing Firebase. I do plan on doing GraphQL w/ Serverless Framework...