4402fb6a3d
- Copyright.vue / AuthCopyright.vue: change link from mugweb.ir to madresebanoo.ir - SelectField.vue: add min-width:0 to prevent flex overflow in narrow layouts - useRedirectAfterLogin: check roles array (multi-role support) instead of single role - StudentRegisterPage: show block reason when user's status is blocked - http.js: only clear session on 401 if a token already existed (login form 401 should not trigger logout); clear via authStore so WordPress cookie is also cleaned up Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
112 lines
3.3 KiB
JavaScript
112 lines
3.3 KiB
JavaScript
import axios from 'axios'
|
|
import { toast } from 'vue3-toastify'
|
|
import { tokenService } from '@/services/api/token-service'
|
|
import { camelize, snakize } from '@/utils/convert-to-camel-snake'
|
|
|
|
const defaultHeaders = {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
}
|
|
|
|
const addAuthTokenInterceptor = (instance) => {
|
|
instance.interceptors.request.use(
|
|
(config) => {
|
|
const token = tokenService.getToken()
|
|
const headers = {
|
|
...config.headers,
|
|
Authorization: token ? `Bearer ${token}` : '',
|
|
}
|
|
if (config.data instanceof FormData) {
|
|
delete headers['Content-Type']
|
|
}
|
|
return { ...config, headers }
|
|
},
|
|
null,
|
|
{ synchronous: true }
|
|
)
|
|
}
|
|
|
|
const addSnakizeInterceptor = (instance) => {
|
|
instance.interceptors.request.use(
|
|
(config) => {
|
|
if (config.data instanceof FormData || config.noSnakize) {
|
|
return config
|
|
}
|
|
return {
|
|
...config,
|
|
params: config.params ? snakize(config.params) : {},
|
|
data: config.data ? snakize(config.data) : undefined,
|
|
}
|
|
},
|
|
(e) => Promise.reject(e)
|
|
)
|
|
}
|
|
|
|
const addResponseUnwrapInterceptor = (instance) => {
|
|
instance.interceptors.response.use(
|
|
(res) => {
|
|
if (typeof res?.data === 'number' || typeof res?.data === 'boolean') {
|
|
return { data: res.data }
|
|
}
|
|
return res?.data || res
|
|
},
|
|
(e) => Promise.reject(e)
|
|
)
|
|
}
|
|
|
|
const addCamelizeInterceptor = (instance) => {
|
|
instance.interceptors.response.use(
|
|
(res) => camelize(res),
|
|
(e) => Promise.reject(e)
|
|
)
|
|
}
|
|
|
|
const addUnauthorizeInterceptor = (instance) => {
|
|
instance.interceptors.response.use(
|
|
(res) => res,
|
|
async (e) => {
|
|
// Login/OTP-verify endpoints also answer 401 for a plain wrong
|
|
// password or code — that's a credential failure the login form
|
|
// should handle itself, not a session expiring. Only treat this as
|
|
// "the session died" when we actually had a token going in.
|
|
if (e?.response?.status === 401 && tokenService.hasToken()) {
|
|
// Route through the auth store, not tokenService directly, so the
|
|
// reactive user state and the WordPress-facing mb_auth_status cookie
|
|
// both get cleared along with the token — an expired/invalid session
|
|
// must not leave WordPress showing a stale "logged in" state.
|
|
try {
|
|
const { useAuthStore } = await import('@/store/auth')
|
|
useAuthStore().clearUser()
|
|
} catch {
|
|
tokenService.clearAll()
|
|
}
|
|
toast('نشست شما تایید نشد، لطفا مجدد وارد شوید')
|
|
try {
|
|
const { default: router } = await import('@/router')
|
|
router.push('/login').catch(() => {})
|
|
} catch {}
|
|
}
|
|
throw e
|
|
}
|
|
)
|
|
}
|
|
|
|
const createInstance = (baseUrl, { headers = {}, ...configs } = {}) => {
|
|
const instance = axios.create({
|
|
timeout: 15_000,
|
|
baseURL: baseUrl,
|
|
headers: { ...defaultHeaders, ...headers },
|
|
...configs,
|
|
})
|
|
addAuthTokenInterceptor(instance)
|
|
addResponseUnwrapInterceptor(instance)
|
|
addCamelizeInterceptor(instance)
|
|
addSnakizeInterceptor(instance)
|
|
addUnauthorizeInterceptor(instance)
|
|
return instance
|
|
}
|
|
|
|
export const http = createInstance(import.meta.env.VITE_API_BASE_URL || '')
|
|
|
|
export { createInstance }
|