Laravel + Vuejs have trouble with async problem - laravel

I have a async problem with my Vuejs/Laravel App.
If a user is connected i have in vuex : my state user and a state token. I set this token in the localStorage and with that i can relogin him if my user F5 (refresh page).
There is my store (namespaced:auth) :
export default {
namespaced: true,
state: {
token: null,
user: null
},
getters: {
authenticated(state){
return state.token && state.user;
},
user(state){
return state.user;
}
},
mutations: {
SET_TOKEN(state, token){
state.token = token;
},
SET_USER(state, data){
state.user = data;
},
},
actions: {
async login({ dispatch }, credentials){
let response = await axios.post(`/api/auth/connexion`, credentials);
dispatch('attempt', response.data.token);
}
,
async attempt({ commit, state }, token) {
if(token){
commit('SET_TOKEN', token);
}
if (!state.token){
return
}
try{
let response = await axios.get(`/api/auth/me`);
commit('SET_USER', response.data);
console.log('Done')
}
catch(err){
commit('SET_TOKEN', null);
commit('SET_USER', null);
}
}
}
}
My app.js, i do that for relog my current user :
store.dispatch('auth/attempt', localStorage.getItem('token'));
And for finish on route in router :
{
name: 'Login',
path: '/',
component: Login,
beforeEnter: (to, from, next) => {
console.log('Getter in router : ' + store.getters['auth/authenticated']);
if (!store.getters['auth/authenticated']){
next()
}else {
return next({
name: 'Home'
})
}
}
}
Where is the problem, if i call in a Home.vue my getter "authenticated" is work fine (true if user connect and false if not).
But in my router is take all time default value set in store (null). I know why is because if i reload my page for 0.5s the store state is null and after that the localStorage is learn and my user and token is created.
What i want (i think) is the router wait my actions attempt and after that he can do check.
I have follow a tutoriel https://www.youtube.com/watch?v=1YGWP-mj6nQ&list=PLfdtiltiRHWF1jqLcNO_2jWJXj9RuSDvY&index=6
And i really don't know what i need to do for my router work fine =(
Thanks a lot if someone can explain me that !
Update :
If i do that is work :
{
name: 'Login',
path: '/',
component: Login,
beforeEnter:async (to, from, next) => {
await store.dispatch('auth/attempt', localStorage.getItem('token'));
if (!store.getters['auth/authenticated']){
next()
}else {
return next({
name: 'Home'
})
}
}
But i want to understand what i really need to do because that is not a clear way is really trash.
Update2:
Maybe i found the best solution if someone have other way i take it.
{
name: 'Home',
path: '/accueil',
component: Home,
beforeEnter: (to, from, next) => {
store.dispatch('auth/attempt', localStorage.getItem('token')).then((response) => {
if (!store.getters['auth/authenticated']){
return next({
name: 'Login'
})
}else {
next();
}
});
}
}

I answer more clearly how easily wait the store !
Before in my app.js i have this :
store.dispatch('auth/attempt', localStorage.getItem('token'));
Is set for normaly every reload do that but if i put a console.log in, i see the router is execute and after the action 'attempt' is execute.
So delete this line in app.js and you can add a .then() after !
router.beforeEach((to, from, next) => {
store.dispatch('auth/attempt', localStorage.getItem('token')).then(() =>{
if (store.getters['auth/authenticated']){
next();
}else {
//something other
}
})
})
With that i have the same line code but i can say wait action "attempt" to my router.
I'm not sur is the best way obviously. But is work and i think is not a bad way !

Related

NestJs Timeout issue with HttpService

I am facing a timeout issue with nestJs Httpservice.
The error number is -60 and error code is 'ETIMEDOUT'.
I am basically trying to call one api after the previous one is successfully.
Here is the first api
getUaaToken(): Observable<any> {
//uaaUrlForClient is defined
return this.httpService
.post(
uaaUrlForClient,
{ withCredentials: true },
{
auth: {
username: this.configService.get('AUTH_USERNAME'),
password: this.configService.get('AUTH_PASSWORD'),
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log(axiosResponse);
return this.getJwtToken(axiosResponse.data.access_token).subscribe();
}),
catchError((err) => {
throw new UnauthorizedException('failed to login to uaa');
}),
);
}
Here is the second api
getJwtToken(uaaToken: string): Observable<any> {
console.log('inside jwt method', uaaToken);
const jwtSignInUrl = `${awsBaseUrl}/api/v1/auth`;
return this.httpService
.post(
jwtSignInUrl,
{ token: uaaToken },
{
headers: {
'Access-Control-Allow-Origin': '*',
'Content-type': 'Application/json',
},
},
)
.pipe(
map((axiosResponse: AxiosResponse) => {
console.log('SUCUSUCSCUSS', axiosResponse);
return axiosResponse.data;
}),
catchError((err) => {
console.log('ERRRORRRORROR', err);
// return err;
throw new UnauthorizedException('failed to login for');
}),
);
}
Both files are in the same service file. Strangely, when i call the second api through the controller like below. It works fine
#Post('/signin')
#Grafana('Get JWT', '[POST] /v1/api/auth')
signin(#Body() tokenBody: { token: string }) {
return this.authService.getJwtToken(tokenBody.token);
}
When the two api's are called, however, the first one works, the second one that is chained is giving me the timeout issue.
Any ideas?
Two things that made it work: changed the http proxy settings and used switchMap.

Cannot access state by getters vuex vuejs

I am new to vuex I want to access user object that is inside in state by the getters getUser() method, I tried to call this
console.log(this.$store.getters.getUser); in mounted method but no results.
I tried to console log inside mutations and it return the user object.
I also followed documentations and googled the solutions but nothing happen
What else am I missing?
store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
user: {},
},
getters: {
getUser: state => {
return state.user
}
},
mutations: {
auth_user(state,data) {
state.user = data
}
},
actions: {
fetchAuthUser(context){
axios.get('/user-data').then(response => {
context.commit("auth_user",response.data)
}).catch({});
}
},
})
export default store
Index.js
mounted(){
// this.auth_user;
this.getCategories();
this.$store.dispatch('fetchAuthUser', this.auth_user);
console.log(this.$store.getters.getUser);
},
Your action is async and should return a promise.
fetchAuthUser(context){
return axios.get('/user-data').then(response => {
context.commit("auth_user",response.data)
}).catch({});
}
In your mounted you can use then to handle data.
mounted(){
// this.auth_user;
this.getCategories();
this.$store.dispatch('fetchAuthUser', this.auth_user).then(() => {
console.log(this.$store.getters.getUser);
});
}

GraphQL mutation "Cannot set headers after they are sent to the client"

I'm implementing graphql login mutation to authenticate user login credential. Mutation verifies the password with bcrypt then sends a cookie to the client, which will render user profile based on whether the cookie is a buyer or owner user).
GraphQL Login Mutation Code:
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: {
loginUser: {
type: UserType,
args: {
email: { type: GraphQLString },
password: { type: GraphQLString }
},
resolve: function (parent, args, { req, res }) {
User.findOne({ email: args.email }, (err, user) => {
if (user) {
bcrypt.compare(args.password, user.password).then(isMatch => {
if (isMatch) {
if (!user.owner) {
res.cookie('cookie', "buyer", { maxAge: 900000, httpOnly: false, path: '/' });
} else {
res.cookie('cookie', "owner", { maxAge: 900000, httpOnly: false, path: '/' });
}
return res.status(200).json('Successful login');
} else {
console.log('Incorrect password');
}
});
}
});
}
}
}
});
Server.js:
app.use("/graphql",
(req, res) => {
return graphqlHTTP({
schema,
graphiql: true,
context: { req, res },
})(req, res);
});
Error message:
(node:10630) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[0] at ServerResponse.setHeader (_http_outgoing.js:470:11)
[0] at ServerResponse.header (/Users/xxx/xxx/server/node_modules/express/lib/response.js:771:10)
[0] at ServerResponse.append (/Users/xxx/xxx/server/node_modules/express/lib/response.js:732:15)
[0] at ServerResponse.res.cookie (/Users/xxx/xxx/server/node_modules/express/lib/response.js:857:8)
[0] at bcrypt.compare.then.isMatch (/Users/xxx/xxx/server/schema/schema.js:89:41)
I've done some research on this error, but can't seem to find a relevant answer. The issue seems to lie within response body being executing more than once, thus "cannot set headers after they are sent to the client". Since I'm sending both res.cookie() and res.status(200), how could I fix this problem?
express-graphql already sets the status and sends a response for you -- there's no need to call either res.status or res.json inside your resolver.
GraphQL always returns a status of 200, unless the requested query was invalid, in which case it returns a status of 400. If errors occur while executing the request, they will be included the response (in an errors array separate from the returned data) but the status will still be 200. This is all by design -- see additional discussion here.
Instead of calling res.json, your resolver should return a value of the appropriate type (in this particular case UserType), or a Promise that will resolve to this value.
Additionally, you shouldn't utilize callbacks inside resolvers since they are not compatible with Promises. If the bcrypt library you're using supports using Promises, use the appropriate API. If it doesn't, switch to a library that does (like bcryptjs) or wrap your callback inside a Promise. Ditto for whatever ORM you're using.
In the end, your resolver should look something like this:
resolve: function (parent, args, { req, res }) {
const user = await User.findOne({ email: args.email })
if (user) {
const isMatch = await bcrypt.compare(args.password, user.password)
if (isMatch) {
const cookieValue = user.owner ? 'owner' : 'buyer'
res.cookie('cookie', cookieValue, { maxAge: 900000, httpOnly: false, path: '/' })
return user
}
}
// If you want an error returned in the response, just throw it
throw new Error('Invalid credentials')
}

Return axios Promise through Vuex

all!
I need to get axios Promise reject in my vue component using vuex.
I have serviceApi.js file:
export default {
postAddService(service) {
return axios.post('api/services', service);
}
}
my action in vuex:
actions: {
addService(state, service) {
state.commit('setServiceLoadStatus', 1);
ServicesAPI.postAddService(service)
.then( ({data}) => {
state.commit('setServiceLoadStatus', 2);
})
.catch(({response}) => {
state.commit('setServiceLoadStatus', 2);
console.log(response.data.message);
return Promise.reject(response); // <= can't catch this one
});
}
}
and in my vue component:
methods: {
addService() {
this.$store.dispatch('addService', this.service)
.then(() => {
this.forceLeave = true;
this.$router.push({name: 'services'});
this.$store.dispatch('snackbar/fire', {
text: 'New Service has been added',
color: 'success'
}).then()
})
.catch((err) => { // <== This never hapens
this.$store.dispatch('snackbar/fire', {
text: err.response.data.message || err.response.data.error,
color: 'error'
}).then();
});
}
When i use axios directly in my component all work well. I get both success and error messages.
But when i work using vuex i can't get error message in component, hoever in vuex action console.log prints what i need.
I'm always getting only successfull messages, even when bad things hapen on beckend.
How can i handle this situation using vuex ?
Wellcome to stackoverflow. One should not want to expect anything back from an action. After calling an action. Any response should be set/saved in the state via mutations. So rather have an error property on your state. Something like this should work
actions: {
async addService(state, service) {
try {
state.commit('setServiceLoadStatus', 1);
const result = await ServicesAPI.postAddService(service);
state.commit('setServiceLoadStatus', 2);
} catch (error) {
state.commit("error", "Could not add service");
state.commit('setServiceLoadStatus', 2);
console.log(response.data.message);
}
}
}
And in your component you can just have an alert that listens on your state.error
Edit: Only time you are going expect something back from an action is if you want to call other actions synchronously using async /await. In that case the result would be a Promise.

How to make the delay when a user check on Vue.js?

I have SPA application on Vue.js + Laravel. Authorization logic, completely delegated to Laravel app. However, i need check auth status, when routing has changed. I create small class, which responsible for it.
export default {
user: {
authenticated : false
},
check: function(context) {
context.$http.get('/api/v1/user').then((response) => {
if (response.body.user != null) {
this.user.authenticated = true
}
}, (response) =>{
console.log(response)
});
}
Within the component has a method that is called when a change url.
beforeRouteEnter (to, from, next) {
next(vm =>{
Auth.check(vm);
if (!Auth.user.authenticated) {
next({path:'/login'});
}
})
}
Function next() given Vue app instance, then check user object. If user false, next() call again for redirect to login page. All it works, but only when the page is already loaded. If i'll reload /account page, there is a redirect to /login page, because request to Api not completed yet, but code will continue execute. Any idea?
Quite simple to do, you need to make your code work asynchronously and hold routing before request is completed.
export default {
user: {
authenticated : false
},
check: function(context) {
return context.$http.get('/api/v1/user').then((response) => {
if (response.body.user != null) {
this.user.authenticated = true
}
}, (response) => {
console.log(response)
});
}
}
then
beforeRouteEnter (to, from, next) {
next(vm => {
Auth.check(vm).then(() => {
if (!Auth.user.authenticated) {
next({path:'/login'});
} else {
next()
}
}
})
}
Other pro tips
Display some loading indicator when loading so your application doesn't seem to freeze (you can use global router hooks for that)
If you are using vue-resource, consider using interceptors (perhaps in addition to the routing checks)
Consider using router.beforeEach so that you don't have to copy-paste beforeRouteEnter to every component
Done. Need to return promise like that
check: function(context) {
return context.$http.get('/api/v1/user').then((response) => {
if (response.body.user != null) {
this.user.authenticated = true
}
}, (response) =>{
console.log(response)
});
}
and then
beforeRouteEnter (to, from, next) {
Auth.check().then(()=>{
if(!Auth.user.authenticated)
next({path:'/login'})
else
next();
})
}

Resources