I have a login page hooked up to firebase and I'm trying to use pinia to update the state to login the user after registering. I'm having a problem that whenever it tries to access the state I get "Uncaught (in promise) TypeError: this.userStore is undefined"
Pinia Store:
import { defineStore } from 'pinia'
export default defineStore('user', {
state: () => ({
userLoggedIn: false,
}),
})
RegisterForm:
<script>
import { auth, usersCollection } from "#/includes/firebase";
import { mapWritableState } from 'pinia';
import useUserStore from "#/stores/user";
export default {
name: "registerForm",
computed: {
...mapWritableState(useUserStore, ['userLoggedIn'])
},
mounted() {
console.log(this.userStore.userLoggedIn);
},
console logged the login state for testing, but shows undefined. Not sure what the issue is. I have another store setup the same way and it works just fine.
same for my methods to actually trigger the state from false to true. Having the console log "this.userLoggedIn" works, but does not work in the methods.
methods: {
async register(values) {
this.reg_show_alert = true;
this.reg_in_submission = true;
this.reg_alert_variant = "bg-blue-500";
this.reg_alert_msg = "Please wait! You account is bring created.";
let userCred = null;
try {
userCred = await auth.createUserWithEmailAndPassword(
values.email,
values.password
);
} catch (error) {
this.reg_in_submission = false;
this.reg_alert_variant = "bg-red-500";
this.reg_alert_msg =
"An unexpected error occured. Please try again later.";
return;
}
try {
await usersCollection.add({
name: values.name,
email: values.email,
age: values.age,
country: values.country,
});
} catch (error) {
this.reg_in_submission = false;
this.reg_alert_variant = "bg-red-500";
this.reg_alert_msg =
"An unexpected error occured. Please try again later.";
return;
}
this.userStore.userLoggedIn = true;
this.reg_alert_variant = "bg-green-500";
this.reg_alert_msg = "You account has been created";
console.log(userCred);
},
},
In your store file
export const useUserStore = defineStore("user", {
state: () => ({ userLoggedIn: false })
});
In your, register form
import { mapWritableState } from "pinia";
import { useUserStore } from "#/store";
computed: {
...mapWritableState(useUserStore, [
"userLoggedIn",
]),
},
mounted() {
console.log(this.userLoggedIn);
//You can directly modify your state value.
this.userLoggedIn = true;
console.log(this.userLoggedIn);
},
Related
I'm trying to add MFA inside my web app and the multiFactor property is missing.
Check the code:
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.2/firebase-app.js";
import { getAuth, RecaptchaVerifier, PhoneAuthProvider, signInWithEmailAndPassword }
from "https://www.gstatic.com/firebasejs/9.6.2/firebase-auth.js";
const firebaseConfig = {
...
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
auth.onAuthStateChanged((user) => {
const userEl = document.getElementById('user');
if (user) {
userEl.innerHTML = `${user.email} logged in. ${JSON.stringify(
user.multiFactor.enrolledFactors
)}`;
} else {
userEl.innerHTML = 'signed out';
}
});
window.recaptchaVerifier = new RecaptchaVerifier('recaptcha-container', {
'size': 'invisible',
'callback': (response) => {
console.log('captcha solved!');
}
}, auth);
const enrollBtn = document.getElementById('enroll-button');
enrollBtn.onclick = () => {
signInWithEmailAndPassword(auth, 'blabla#gmail.com', 'foobar').then(() => {
const user = auth.currentUser;
if (!user) {
return alert('User not logged!');
}
const phoneNumber = document.getElementById('enroll-phone').value;
console.log(user);
user.multiFactor.getSession().then((session) => {
const phoneOpts = {
phoneNumber,
session,
};
const phoneAuthProvider = new PhoneAuthProvider();
phoneAuthProvider.verifyPhoneNumber(
phoneOpts,
window.recaptchaVerifier
).then((verificationId) => {
window.verificationId = verificationId;
alert('sms text sent!');
});
});
});
};
In the code above the user.multiFactor is undefined. The signIn is returning the user normally, but without this property.
error on console:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'getSession')
The Firebase project have MFA enabled:
enter image description here
**************** UPDATE *******************
Apparently change the code to this worked:
const mfaUser = multiFactor(user);
mfaUser.getSession().then((session) => {
But now I'm getting this error when I call verifyPhoneNumber:
VM21778 index.html:315 TypeError: Cannot read properties of undefined (reading 'tenantId')
at _addTidIfNecessary (firebase-auth.js:1934:14)
at startEnrollPhoneMfa (firebase-auth.js:6778:125)
at _verifyPhoneNumber (firebase-auth.js:8500:40)
However I'm not using Multi-Tenancy option, this is disabled in my project.
Changed to:
const mfaUser = multiFactor(user);
mfaUser.getSession().then((session) => {
and:
const phoneAuthProvider = new PhoneAuthProvider(auth);
I don't know if Firebase Auth docs is deprecated or I'm doing something different. XD
I'm building a simple test for a project of mine after watching a bunch of vue.js lessons.
My test is simple, fetch some json from an api i created with express.js and output it on the screen, I wanted do it the proper way so i build all the little components that make up my test homepage and commited the fetch through dispatching an action, this is the structure:
my submissions/action.js
export default {
async loadSubs(context) {
const res = await fetch(`http://localhost:3001/api`, {
mode: "no-cors",
});
console.log(res);
const resData = await res.json();
console.log(resData.name);
if (!res.ok) {
console.log(resData);
const error = new Error(resData.message || "failed to fetch");
throw error;
}
const subsList = [];
for (const key in resData) {
const sub = {
name: resData[key].name,
};
subsList.push(sub);
}
context.commit("setSubs", subsList);
},
};
my submission/mutation.js:
export default {
setSubs(state, payload) {
state.submissions = payload;
},
};
they get imported in submissions/index.js :
import mutations from "./mutations.js";
import actions from "./actions.js";
import getters from "./getters.js";
export default {
namespaced: true,
state() {
return {
submissions: [
{
name: "",
},
],
};
},
mutations,
actions,
getters,
};
and submission/index.js gets imported in store/index.js
import { createStore } from "vuex";
import SubmissionsModule from "./modules/Submissions/index.js";
const store = createStore({
modules: {
submissions: SubmissionsModule,
},
});
export default store;
My vue components are the following(i'm leaving out the css)
BaseCard.vue
<template>
<div class="card">
<slot></slot>
</div>
</template>
my Submission/SingleSubmission.vue
<template>
<div class="subList">
<base-card>
<h2>{{ name }}</h2>
</base-card>
</div>
</template>
<script>
export default {
props: ["name"],
};
</script>
and this is my views/Home.vue:
<template>
<div>
<single-submission></single-submission>
</div>
</template>
<script>
import SingleSubmission from "../components/SingleSubmission.vue";
export default {
components: {
SingleSubmission,
},
computed: {},
created() {
this.loadSubmission();
},
methods: {
async loadSubmission() {
//this.isLoading = true;
try {
await this.$store.dispatch("submissions/loadSubs");
} catch (error) {
this.error = error.message || "something went wrong";
}
// this.isLoading = false;
},
},
};
</script>
The api is just sending back a line of json, just to test if i can render something.
const express = require("express");
const app = express();
const apiPrefix = "/api";
//Define the root endpoint
app.get(apiPrefix, async (req, res) => {
console.log(req.headers);
res.json({
name: "test",
});
});
app.listen(3001, () => {
console.log("listening on port 3001");
});
RESULTS OF WHAT I'VE DONE:
this is the result of res object when i console log it:
and this is the network tab on the browser i use:(fun fact:on Brave and Chrome the response tab is empty, while on firefox, i can see the json intended to see, but only in the developer tools reponse tab )
In the end the response status is 200 but i get nothing from the fetch expept res but console.log(resData.name) don't even gets executed and nothing is printed on the screen.
I really don't know what to do because it's seems such a stupid thing and I can't get around it.
I'm trying to put the firebase-authentication code in a different .js file and simply import it to my app.js file, but in doing so I'm running into an error I can't solve.
Here's how the auth.js file looks like:
export class AuthFirebase
{
constructor(
userLoggedIn = false,
authToken = null,
user = null,
) {
// This mostly gets called on subsequent page loads to determine
// what the current status of the user is with "user" being an object
// return by Firebase with credentials and other info inside of it
firebase.auth().onAuthStateChanged(user => {
this.userLoggedIn = user ? true : false;
this.user = user;
});
}
/* this.auth = AuthService;
this.authenticated = this.auth.isAuthenticated();
this.auth.authNotifier.on('authChange', authState => {
this.authenticated = authState.authenticated;
});*/
login(type) {
let provider;
// Determine which provider to use depending on provided type
// which is passed through from app.html
if (type === 'google') {
provider = new firebase.auth.GoogleAuthProvider();
} else if (type === 'facebook') {
provider = new firebase.auth.FacebookAuthProvider();
} else if (type === 'twitter') {
provider = new firebase.auth.TwitterAuthProvider();
}
// Call the Firebase signin method for our provider
// then take the successful or failed result and deal with
// it accordingly.
firebase.auth().signInWithPopup(provider).then((result: any) => {
// The token for this session
this.authToken = result.credential.accessToken;
// The user object containing information about the current user
this.user = result.user;
// Set a class variable to true to state we are logged in
this.userLoggedIn = true;
this.router.navigateToRoute('contacts');
}).catch(error => {
console.log('Erro no signIn');
let errorCode = error.code;
let errorMessage = error.message;
let email = error.email;
let credential = error.credential;
});
}
logout() {
// Self-explanatory signout code
firebase.auth().signOut().then(() => {
this.userLoggedIn = false;
this.router.navigateToRoute('inicio');
}).catch(error => {
throw new Error(error);
});
}
}
And here's how I'm importing it in the app.js file:
import { inject } from 'aurelia-framework';
import { Redirect } from 'aurelia-router';
import {AuthFirebase} from './common/auth';
function findDefaultRoute(router) {
return router.navigation[0].relativeHref;
}
#inject(AuthFirebase)
export class App {
constructor(authfirebase) {
this.authfirebase = authfirebase;
}
login() {
this.authfirebase.login(type);
}
logout() {
this.authfirebase.logout();
}
//...
I figured logout() just needs to be called when needed but I need to add something to login(), because when I invoke it I get this error:
Uncaught ReferenceError: type is not defined
at App.login (app.js:25)
at CallScope.evaluate (vendor-bundle.js:23250)
at Listener.callSource (vendor-bundle.js:26902)
at Listener.handleEvent (vendor-bundle.js:26911)
at HTMLDocument.handleDelegatedEvent (vendor-bundle.js:24981)
If anyone could help me out I'd appreciate it, thanks.
UPDATE
auth.js code
import { inject } from 'aurelia-framework';
import { ContactGateway } from '../contacts/services/gateway';
import { Router } from 'aurelia-router';
#inject(Router, ContactGateway)
export class AuthFirebase
{
constructor(userLoggedIn = false, authToken = null, user = null, router, contactGateway) {
this.router = router;
this.contactGateway = contactGateway;
// This mostly gets called on subsequent page loads to determine
// what the current status of the user is with "user" being an object
// return by Firebase with credentials and other info inside of it
firebase.auth().onAuthStateChanged(user => {
this.userLoggedIn = user ? true : false;
this.user = user;
});
}
/* this.auth = AuthService;
this.authenticated = this.auth.isAuthenticated();
this.auth.authNotifier.on('authChange', authState => {
this.authenticated = authState.authenticated;
});*/
login(type) {
let provider;
// Determine which provider to use depending on provided type
// which is passed through from app.html
if (type === 'google') {
provider = new firebase.auth.GoogleAuthProvider();
} else if (type === 'facebook') {
provider = new firebase.auth.FacebookAuthProvider();
} else if (type === 'twitter') {
provider = new firebase.auth.TwitterAuthProvider();
}
// Call the Firebase signin method for our provider
// then take the successful or failed result and deal with
// it accordingly.
firebase.auth().signInWithPopup(provider).then((result: any) => {
// The token for this session
this.authToken = result.credential.accessToken;
// The user object containing information about the current user
this.user = result.user;
// Set a class variable to true to state we are logged in
this.userLoggedIn = true;
console.log('antes do navigate');
this.router.navigateToRoute('contacts');
console.log('depois do navigate');
}).catch(error => {
let errorCode = error.code;
let errorMessage = error.message;
let email = error.email;
let credential = error.credential;
console.log('Erro no signIn - ' + errorMessage);
});
}
logout() {
// Self-explanatory signout code
firebase.auth().signOut().then(() => {
this.userLoggedIn = false;
this.router.navigateToRoute('inicio');
}).catch(error => {
throw new Error(error);
});
}
}
Excuse my ignorance, I am fairly new to the reactive concepts.
My issue is with not knowing how to deal loading a Ionic 2 loader or an Ionic 2 alert based on the stores current state.
I have been able to achieve the loader behaviour I need by subscribing to the store slice it is reacting to. Although when it comes to an alert (thrown on a catched error), it never fires in the subscription block.
Any help pointing out a better direction, or what I have missed would be greatly appreciated.
This code is from the signin modals view.
signin(user) {
this.submitAttempt = true;
if (this.signinForm.valid) {
let loader = this.loadingCtrl.create({
content: "Signing In..."
});
let auth;
let signinSub = this.store.select(s => auth = s.auth).subscribe(() => {
if (auth.state) {
loader.dismiss();
} else if (auth.error) {
let alert = this.alertCtrl.create({
title: "Error",
subTitle: auth.error,
buttons: ['OK']
});
loader.dismiss();
alert.present();
}
});
loader.present();
this.store.dispatch(UserActions.UserActions.signinUser(user));
}
}
Effect
#Effect() signinUser$ = this.actions$
.ofType(UserActions.ActionTypes.SIGNIN_USER)
.map(toPayload)
.switchMap(user => {
return Observable.fromPromise(this.userService.signinUser(user))
.map(result => {
return ({ type: "GET_USER", payload: user});
})
.catch(err => {
return Observable.of({ type: "SIGNIN_USER_FAILED", payload: err });
});
});
Service
signinUser(user): Promise<any> {
return <Promise<any>>firebase.auth()
.signInWithEmailAndPassword(user.email, user.password);
}
Reducer
export const UserReducer: ActionReducer<Auth> = (state: Auth = initialState, action: Action) => {
switch(action.type) {
case UserActions.ActionTypes.SIGNIN_USER:
return state;
case UserActions.ActionTypes.SIGNIN_USER_FAILED:
return Object.assign(state, { apiState: "Failed", error: action.payload.message });
case UserActions.ActionTypes.STARTED_SIGNIN:
return Object.assign(state, { requested: true });
case UserActions.ActionTypes.GET_USER:
return Object.assign(state, { apiState: "Success", error: ""});
case UserActions.ActionTypes.GET_USER_SUCCESS:
return Object.assign({ user: action.payload.val() }, state, { state: true });
default:
return state;
};
}
store
export interface Auth {
state: boolean,
requested: boolean,
apiState: string,
error: {},
user?: {}
}
export interface AppState {
auth: Auth;
}
I just have a loadingState in my store and then I load and unload the spinner/loading UI based on that state.
I have a complete project here showing how I manage the state and the UI
https://github.com/aaronksaunders/ngrx-simple-auth
/**
* Keeping Track of the AuthenticationState
*/
export interface AuthenticationState {
inProgress: boolean; // are we taking some network action
isLoggedIn: boolean; // is the user logged in or not
tokenCheckComplete: boolean; // have we checked for a persisted user token
user: Object; // current user | null
error?: Object; // if an error occurred | null
}
and then in the different states, AuthActions.LOGIN
case AuthActions.LOGIN: {
return Object.assign({}, state, {inProgress: true, isLoggedIn: false, error: null})
}
and then, AuthActions.LOGIN_SUCCESS
case AuthActions.LOGIN_SUCCESS: {
return Object.assign({}, state, {inProgress: false, user: action.payload, isLoggedIn: true})
}
here is how we handle it in the LoginPage
var dispose = this.store.select('authReducer').subscribe(
(currentState: AuthenticationState) => {
console.log("auth store changed - ", currentState);
if (currentState.user) {
dispose.unsubscribe();
this.nav.setRoot(HomePage, {});
}
// this is where the magic happens...
this.handleProgressDialog(currentState);
this.error = currentState.error
},
error => {
console.log(error)
}
);
}
how we handle loading
/**
*
* #param _currentState
*/
handleProgressDialog(_currentState) {
if (_currentState.inProgress && this.loading === null) {
this.loading = this.loadingCtrl.create({
content: "Logging In User..."
});
this.loading.present()
}
if (!_currentState.inProgress && this.loading !== null) {
this.loading && this.loading.dismiss();
this.loading = null;
}
}
I use Ionic 2 with ngrx too and so far as I know, LoadingController and AlertController don't provide any observable or promise. So I think the best you can do is what you're doing now by subscribing its state and do some condition based on its state.
OR you can get rid LoadingController replace it with ion-spinner:
<ion-spinner [disabled]="isLoading$ | async"></ion-spinner>
And replace AlertController with some label :
<span>{{errorMessage$ | async}}</span>
Below is my code, I want login() and authenticated() functions to wait for getProfile() function to finish its execution. I tried several ways like promise etc. but I couldn't implement it. Please suggest me the solution.
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { myConfig } from './auth.config';
// Avoid name not found warnings
declare var Auth0Lock: any;
#Injectable()
export class Auth {
// Configure Auth0
lock = new Auth0Lock(myConfig.clientID, myConfig.domain, {
additionalSignUpFields: [{
name: "address", // required
placeholder: "enter your address", // required
icon: "https://example.com/address_icon.png", // optional
validator: function(value) { // optional
// only accept addresses with more than 10 chars
return value.length > 10;
}
}]
});
//Store profile object in auth class
userProfile: any;
constructor() {
this.getProfile(); //I want here this function to finish its work
}
getProfile() {
// Set userProfile attribute if already saved profile
this.userProfile = JSON.parse(localStorage.getItem('profile'));
// Add callback for lock `authenticated` event
this.lock.on("authenticated", (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
// Fetch profile information
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
// Handle error
alert(error);
return;
}
profile.user_metadata = profile.user_metadata || {};
localStorage.setItem('profile', JSON.stringify(profile));
this.userProfile = profile;
});
});
};
public login() {
this.lock.show();
this.getProfile(); //I want here this function to finish its work
};
public authenticated() {
this.getProfile(); //I want here this function to finish its work
return tokenNotExpired();
};
public logout() {
// Remove token and profile from localStorage
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
this.userProfile = undefined;
};
}
Like you saw in the comments, you have to use Promise or Observable to achieve this, since your behaviour is pretty simple, you should use Promise because Observable will have a lot of features you don't need in this case.
Here is the Promise version of your service:
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { myConfig } from './auth.config';
// Avoid name not found warnings
declare var Auth0Lock: any;
#Injectable()
export class Auth {
// Configure Auth0
lock = new Auth0Lock(myConfig.clientID, myConfig.domain, {
additionalSignUpFields: [{
name: "address", // required
placeholder: "enter your address", // required
icon: "https://example.com/address_icon.png", // optional
validator: function(value) { // optional
// only accept addresses with more than 10 chars
return value.length > 10;
}
}]
});
//Store profile object in auth class
userProfile: any;
constructor() {
this.getProfile(); //I want here this function to finish its work
}
getProfile():Promise<void> {
return new Promise<void>(resolve => {
// Set userProfile attribute if already saved profile
this.userProfile = JSON.parse(localStorage.getItem('profile'));
// Add callback for lock `authenticated` event
this.lock.on("authenticated", (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
// Fetch profile information
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
// Handle error
alert(error);
return;
}
profile.user_metadata = profile.user_metadata || {};
localStorage.setItem('profile', JSON.stringify(profile));
this.userProfile = profile;
resolve()
});
});
})
};
public login(): Promise<void>{
this.lock.show();
return this.getProfile(); //I want here this function to finish its work
};
public authenticated():void{
this.getProfile().then( () => {
return tokenNotExpired();
});
};
public logout():void {
// Remove token and profile from localStorage
localStorage.removeItem('id_token');
localStorage.removeItem('profile');
this.userProfile = undefined;
};
}
More on Promise here
I would recommend that you set up getProfile to return an observable. Then your other functions can subscribe to that function and do their actions in the subscribe function. The Angular 2 HTTP tutorial gives an example of this