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);
}
});
};
Source:stackexchange.com