I'm trying to fix a behavior in my VueJS SPA wherein a limbo state arises. The app doesn't know the JWT has already expired and therefore presents itself as if the user is still logged in. This can happen after hibernation, for example.
These users can keep on making any request to the API, but end up with a 401
response (and correctly so).
I'd like to have a global handler for 401
responses. (This would be: "clear everything user-related from vuex and present the page as if the user was a guest, with login form popup, etc.") Otherwise, I would have to write a 401 handler for EVERY request.
I can add response interceptors to axios, and they work fine. These interceptors don't have access to Vuex (or Vue), though.
Whenever I try to import Vuex or Vue into my Axios, I get circular dependencies (of course) and everything breaks.
If I just throw/return the error, I still have to handle it separately on every request. How can I dispatch methods on this.$store
from within an axios interceptor?
The Axios file contains an export default class API
that is added to Vue globally in main.js
:
import api from 'Api/api'
// ...
Vue.prototype.$http = api
I had thought there has to be a way to access Vue
from $http
, since it's a global instance method. But I appear to be mistaken?
main.js
// ...
import api from 'Api/api'
// ...
Vue.prototype.$http = api
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App },
vuetify: new Vuetify(opts),
});
api.js
import Client from './ApiClient'
const apiClient = new Client({ basePath: process.env.VUE_APP_API_URL })
const api = {
get(url) {
return apiClient._get(`${basePath}/${url}`)
},
post(url, data) {
return apiClient._post(`${basePath}/${url}`, data)
},
// ...
}
export default api
ApiClient.js
const axios = require('axios')
const errorHandler = (error) => {
if (error.response.status === 401) {
store.dispatch('user/logout') // here is the problem
}
return Promise.reject({ ...error })
}
export default class API {
constructor(options) {
this.options = Object.assign({ basePath: '' }, options)
this.axios = axios.create({ timeout: 60000 })
this.axios.interceptors.response.use(
response => response,
error => errorHandler(error)
)
}
// ...
}
Importing store in ApiClient.js
results in a dependency cycle: I assume because I'm importing Vue in it?
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import PersistedState from 'vuex-persistedstate'
import CreateMutationsSharer from 'vuex-shared-mutations';
import SecureLS from 'secure-ls';
// import modules
Vue.use(Vuex);
const ls = new SecureLS({ encodingType: 'aes' });
export default new Vuex.Store({
// options
})
main.js:
import store from './store';
const Instance = new Vue({
store,
...
})
export const { $store } = Instance;
Now you can import { $store } from '@/main.js'
anywhere you want. And it's going to be the same instance you have mounted in your app, not a new Vuex.Store({})
(which is what ./store
exports, each time you import it somewhere else).
You can export the same way anything else you might want to use in services, tests, helpers, etc... I.e:
export const { $store, $http, $bus, $t } = Instance;
Base on these thread I was able to manage a solution for my needs:
main.js
import api, {apiConfig} from 'Api/api'
apiConfig({ store: $store });
ApiClient.js
let configs = {
store: undefined,
};
const apiConfig = ({ store }) => {
configs = { ...configs, store };
};
export default api;
export { apiConfig };
This way the api.js file will require a configuration that can later be expanded.
conf
import Axios from 'axios'
import IdentityProxy from './IdentityProxy.js'
import UsuariosProxi from './UsuariosProxi'
import ZonasProxi from './ZonasProxi'
//axios
Axios.defaults.headers.common.Accept='application/json'
//Axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
Axios.interceptors.request.use(
config => {
let token = localStorage.getItem('access_token');
if(token){
config.headers= {
'x-access-token': `${token}`
}
}
return config;
},
error => Promise.reject(error)
);
Axios.interceptors.response.use(
response => response,
error => {
if (error.response.status===403||error.response.status===401) {
localStorage.removeItem('access_token');
window.location.reload(true);
}
return Promise.reject(error);
}
);
let url=null
if(localStorage.getItem("config")!==null){
let config = JSON.parse(localStorage.getItem("config"))
url = config
}
console.log(url)
export default{
identityProxy: new IdentityProxy(Axios, url),
_usuarioProxi: new UsuariosProxi(Axios, url),
_zonasProxi: new ZonasProxi(Axios, url),
}
//
export default class IdentityProxy{
constructor(axios,url){
this.axios = axios;
this.url =url;
}
register(params){
return this.axios.post(this.url+'/identity/register',params)
}
login(params){
return this.axios.post(this.url+'/auth/signin',params)
}
}
//
export default class UsuariosProxi{
constructor(axios,url){
this.axios = axios;
this.url =url;
}
/* getAll(){
return this.axios.get(this.url+'/users')
} */
getAll(page, take) {
return this.axios.get(this.url + `/users?page=${page}&take=${take}`);
}
create(params) {
return this.axios.post(this.url + '/auth/signup', params);
}
get(id) {
return this.axios.get(this.url + `/users/${id}`);
}
update(id, params) {
return this.axios.put(this.url + `/users/${id}`, params);
}
remove(id) {
return this.axios.delete(this.url + `/users/${id}`);
}
//-----APARTE SOLO TRAE LISTA DE ROLES--------
getRoles() {
return this.axios.get(this.url + '/users/newrol');
}
}
//st
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const state = {
user:null
}
export default new Vuex.Store({
state
});
What about direct import your store to ApiClient.js? Something like
const axios = require('axios')
import store from 'path/to/store'
const errorHandler = (error) => {
if (error.response.status === 401) {
store.dispatch('user/logout') // now store should be accessible
}
return Promise.reject({ ...error })
}
export default class API {
constructor(options) {
this.options = Object.assign({ basePath: '' }, options)
this.axios = axios.create({ timeout: 60000 })
this.axios.interceptors.response.use(
response => response,
error => errorHandler(error)
)
}
// ...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With