[Vuejs]-Why is firebase authentication not persisting on refresh in Ionic with Vue and Pinia?

0👍

I ended up doing a refactor of the pinia store and that solved the issue. I believe the issue may have been caused by how the auth listener called initializeProfileListener. I didn’t have code in the auth listener to check if the profile listener was already initialized, so everytime the authstate changed or it would initialize a new profile listener without unsubbing the old one. I’m not absolutely certain that is what was causing the issue though.

Below is the new code that functions properly.
pinia store:

import { defineStore } from "pinia";
import { User } from "firebase/auth";
import {
  Profile,
  getProfile,
  profileListener,
} from "@/firebase/helpers/firestore/profileManager";
import {
  fbCreateAccount,
  fbSignIn,
  fbAuthStateListener,
  fbSignOut,
} from "@/firebase/helpers/firestore/authHelper";
import {Unsubscribe} from "@firebase/firestore";
import errorHandler from "@/helpers/errorHandler";

/**@see {@link Profile} */
export enum UserType {
  DNE,
  uploader,
  checker,
  host,
}

interface State {
  user: User | null;
  profile: Profile | null;
  error: null | any;
  unsub: Unsubscribe | null;
}

export const useUserStore = defineStore("user", {
  state: (): State => {
    return {
      user: null,
      profile: null,
      error: null,
      unsub: null,
    };
  },
  getters: {
    isLoggedIn: (state) => state.user !== null,
    //DEV: do we need this to be a getter?
    userError: (state) => {
      if (state.error) {
        switch (state.error.code) {
          case "auth/user-not-found":
            return "Email or Password incorrect!";
          case "auth/wrong-password":
            return "Email or Password incorrect!";
          default:
            return state.error;
        }
      }
      return null;
    },
    /**
     * @see Profile
     */
    getType: (state): UserType => {
      if (state.user === null) return UserType.DNE;
      if (!state.profile) return UserType.DNE;
      if (state.profile.locations.length > 0) return UserType.host;
      if (state.profile.queues.length > 0) return UserType.checker;
      return UserType.uploader;
    },
  },
  actions: {
    initializeAuthListener() {
      return new Promise((resolve) => {
        fbAuthStateListener(async (user: any) => {
          if (user) {
            this.user = user;
            const profile = await getProfile(user.uid);
            if (profile) {
              this.profile = profile;
              //TODO: initialize profile listener
              if(this.unsub === null) {
                this.initializeProfileListener();
              }
            }
          }
          resolve(true);
        });
      });
    },
    /**
     *
     * @param email email for login
     * @param password password for login
     */
    async signInEmailPassword(email: string, password: string) {
      try {
        const userCredential = await fbSignIn(email, password);
        this.user = userCredential.user ? userCredential.user : null;
        this.error = null;
        return true;
      } catch (error: any) {
        console.log(typeof error.code);
        console.log(error.code);
        this.user = null;
        this.error = error;
        return false;
      }
    },
    async logoutUser() {
      try {
        await fbSignOut();
        this.user = null;
        this.profile = null;
        this.error = null;
        if (this.unsub) this.unsub();
        return true;
      } catch (error: any) {
        this.error = error;
        return false;
      }
    },
    async createEmailPasswordAccount(
      email: string,
      password: string,
      userName: string,
      refSource: string
    ) {
      try {
        const { user, profile } = await fbCreateAccount(
          email,
          password,
          userName,
          refSource
        );
        //set local store
        this.user = user ? user : null;
        this.profile = profile ? profile : null;
        this.error = null;
        //TODO: send email verification
        return true;
      } catch (error: any) {
        this.user = null;
        this.error = error;
        return false;
      }
    },
    initializeProfileListener() {
      try {
        if (this.user) {
          const unsub = profileListener(
            this.user?.uid,
            async (profile: any) => {
              if (profile) {
                this.profile = profile;
              }
            }
          );
          this.unsub = unsub;
        }
      } catch (error) {
        errorHandler(error as Error);
      }
    },
  },
});

authHelper.ts

import { auth } from "@/firebase/firebase";
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged,
  updateProfile as updateAuthProfile,
} from "@firebase/auth";
import { Profile, setProfile, getProfile } from "./profileManager";

/**
 * @param email
 * @param password
 * @param userName
 * @param refSource @see profileManager
 * @returns
 */
export const fbCreateAccount = async (
  email: string,
  password: string,
  userName: string,
  refSource: string
) => {
  //DEBUG: creating a user works but throws an error.
  const userCredential = await createUserWithEmailAndPassword(
    auth,
    email,
    password
  );
  if (userCredential) {
    //add username to fireauth profile
    await updateAuthProfile(userCredential.user, { displayName: userName });
    //create user profile data in firestore
    let profile: Profile | undefined = new Profile(
      userCredential.user.uid,
      refSource
    );
    await setProfile(profile);
    profile = await getProfile(userCredential.user.uid);
    //TODO: errorHandling for setProfile and getProfile
    return {
      user: userCredential.user,
      profile: profile,
    };
  } else {
    return {
      user: null,
      profile: null,
    };
  }
};

/**
 *
 * @param email
 * @param password
 * @returns UserCredential {@link https://firebase.google.com/docs/reference/js/auth.usercredential.md?authuser=0#usercredential_interface}
 */
export const fbSignIn = async (email: string, password: string) => {
  const userCredential = signInWithEmailAndPassword(auth, email, password);
  //TODO: add call to add to profile signins array
  return userCredential;
};

export const fbSignOut = async () => {
  await signOut(auth);
  return true;
};

/**
 * @see {@link https://firebase.google.com/docs/reference/js/auth.md?authuser=0&hl=en#onauthstatechanged}
 * @param callback contains either user or null
 */
export const fbAuthStateListener = (callback: any) => {
  onAuthStateChanged(auth, (user) => {
    if (user) {
      //user is signed in
      callback(user);
    } else {
      //user is signed out
      callback(null);
    }
  });
};

Leave a comment