nuxt-community / firebase-module

🔥 Easily integrate Firebase into your Nuxt project. 🔥
https://firebase.nuxtjs.org
MIT License
641 stars 99 forks source link

Support for v9 beta #531

Closed simeon9696 closed 2 years ago

simeon9696 commented 3 years ago

Following the keynote yesterday they released the v9 Javascript SDK. This has support for treeshaking, firestore-lite and other benefits.

I'm not sure if you'd want to support something that's still in beta but I just thought I'd put it on your radar

dosstx commented 3 years ago

When I talked to @lupas on discord a while back, he alluded to the possibility of updating the nuxt module for this. Lets hope he does!

lupas commented 3 years ago

Hey guys

Been playing around with v9 Beta lately and I completely love it, but it will require quite the refactoring of the module. Many things of this module might even not make sense anymore... so I would probably have to build it up from scratch.

Definitely don't have time at the moment, and since it's still in Beta I will probably tackle this later this year.

If anyone wants to start a branch and start working on it, feel free, would appreciate it!

We might be able to simplify many things with v9.

lupas commented 3 years ago

Wrote a short Medium article on how to setup Firebase v9 Beta in Nuxt.js (without this module). Module integration coming shortly before it is out of Beta, might start working on it soon.

dosstx commented 2 years ago

@lupas Love your medium article, but what benefits/advantages would an upgraded Nuxt/Firebase module have over the regular firebase plugin you made in the medium article? You stated that many of the features in your previous Firebase module may no longer be needed. Thanks for any insight!

lupas commented 2 years ago

@dosstx Answered you on medium. Would be interested in everyone's thoughts on whether a new version of the module even makes sense - or if some micro-modules/plugins/helpers that solve some of the remaining difficulties would be the better approach.

dosstx commented 2 years ago

I've so far implemented the new v9 SDK into my app. So far so good, however, not sure how to "hook up" the onAuthStateChanged action? Can you or someone provide an example? Are we putting this back in nuxt.config.js or??

lupas commented 2 years ago

@dosstx For SSR auth or regular client side auth?

For regular auth: Haven't tried it myself yet but you should be able to just create a plugins/myAuthInit.client.js file as a plugin, with the following content:

import { onAuthStateChanged } from "firebase/auth";

export default ({ store }) => {
  onAuthStateChanged(auth, (user) => {
    if (user) {
      store.commit("SET_USER", {
        email: user.email,
      });
    } else {
      store.commit("UNSET_USER");
    }
  });
};

Load the plugin on client side only and it should be all fine.

dosstx commented 2 years ago

Sorry to bother again about this....but @lupas ...with your above onAuthStateChanged() code in the plugins , how would I unsubscribe from the auth observer ? My use case is that I have a sign up button like so:

async signUp() {
      const auth = getAuth()
      const batch = writeBatch(db)

      try {
        const UserCredential = await createUserWithEmailAndPassword(
          auth,
          this.formValues.email,
          this.formValues.password
        )

        const userDocRef = doc(db, 'users', UserCredential.user.uid)
        batch.set(userDocRef, {
          uid: UserCredential.user.uid,
          displayName: this.formValues.displayName,
          photoURL: `https://gravatar.com/avatar/${md5(
            this.formValues.email
          )}?d=identicon`
        })
        const usernameDocRef = doc(db, 'usernames', this.formValues.displayName)
        batch.set(usernameDocRef, { uid: UserCredential.user.uid })

        // Commit batch
        await batch.commit()
        console.log('batch commited, user is:', UserCredential.user.uid)
        await this.verifyEmail()

I need to only let a user sign in after their email is verified. So, in your plugin code, I have:

export default ({ store }) => {
  const auth = getAuth()
  onAuthStateChanged(auth, (user) => {
    if (user) {
      if (!user.emailVerified) {
        // User has not verified the email yet
        store.dispatch('logOutUser')
      }

Problem is that I can't use the sendEmailVerification(auth.currentUser, actionCodeSettings) method because the auth observer sees that the user is not verified, hence the log out.

Hope my question makes sense.

lupas commented 2 years ago

@dosstx Out of the top of my head, you could do something like this:

import { onAuthStateChanged } from "firebase/auth";

export default ({ store }) => {
  const unsubscribeAuthListener = onAuthStateChanged(auth, (user) => {
    if (user) {
      store.commit("SET_USER", {
        email: user.email,
      });
    } else {
      store.commit("UNSET_USER");
    }
  });
 store.commit("SET_AUTH_LISTENER", unsubscribeAuthListener); // <--- do this, and add the mutation also
}

And then somewhere in your code, unsubscribe like so:

// assuming you named it `unsubscribeAuthListener` in your state
store.state.unsubscribeAuthListener()
dosstx commented 2 years ago

Thanks! I think your solution is more simple. I had an overengineered version of this where I could just inject the plugin into my components and be able to use this.

import { doc, getDoc } from 'firebase/firestore'
import { db } from '~/plugins/firebase'

export default ({ store }, inject) => {
  const auth = getAuth()
  inject(
    'authObserver',
    onAuthStateChanged(auth, async (user) => {
      console.log('state:', user)
      if (user) {
        if (!user.emailVerified) {
          // Force logout user and reset store
          store.dispatch('logOutUser')
        } else {
          // Otherwise User object (not doc) has verified email .. lets move on to next steps
          const { uid } = user
          const userDocRef = doc(db, 'users', uid)
          const userDocSnap = await getDoc(userDocRef)
          if (!userDocSnap.data().emailVerified) {
            await store.dispatch('updateUserProfile', user)
            store.dispatch('onAuthStateChangedAction', user)
          } else {
            store.dispatch('onAuthStateChangedAction', user)
          }
        }
      } else {
        store.dispatch('logOutUser')
      }
    })
  )
}

and my store stuff:

  async onAuthStateChangedAction({ commit, dispatch }, authUser) {
    const { displayName, email, emailVerified, photoURL, providerData, uid } =
      authUser
    commit('SET_AUTH_USER', {
      displayName,
      email,
      emailVerified,
      photoURL,
      providerData,
      uid
    })
    await dispatch('getUserProfile', authUser)
  },
  async getUserProfile({ commit }, authUser) {
    try {
      const docRef = doc(db, 'users', authUser.uid)
      const profile = await getDoc(docRef)
      commit('SET_USER_PROFILE', profile.data())
      console.log('fetched profile and set')
    } catch (error) {
      console.error('Error fetching user profile', error)
    }
  },
  async updateUserProfile({ commit }, user) {
    try {
      const docRef = doc(db, 'users', user.uid)
      await updateDoc(docRef, {
        updatedAt: serverTimestamp(),
        emailVerified: user.emailVerified
      })
      const profile = await getDoc(docRef)
      commit('SET_USER_PROFILE', profile.data())
      console.log('User profile updated and set')
    } catch (error) {
      console.error('Error updating user profile', error)
    }
  },
  async logOutUser({ commit }) {
    const auth = getAuth()
    await signOut(auth)
    console.log('user logged out')
    commit('RESET_STORE')
  },

Then, in my sign up page I unsubscribe to listener like so:

data() {
return {
unsubscribe: null
}
}
  mounted() {
    this.unsubscribe = this.$authObserver
  },
    async signUp() {
      this.unsubscribe() < --- detach listener here and then continue with sign up process. Then, during user login, the user will get observed again automatically
...snip...
}

The use case for me is that the data model requires two root collections users and usernames. They use a simple reverse mapping (point to each other) that enables uniqueness validation so my users can pick unique usernames. Email validation is required and a user doc is created prior to that. Hence, the observer needs to watch that, but if I don't detach it during the sign up process, it causes issues with the createUserWithEmailAndPassword() and the sendEmailVerification().

It works...but I am still in testing stage. Thanks!

1shaked commented 2 years ago

Is it still relevant to open a branch and start working on it?

lupas commented 2 years ago

See https://github.com/nuxt-community/firebase-module/issues/573