Files
banu-front/src/services/api/http.js
T
madresebanoo 4402fb6a3d fix: branding, 401 logout, and blocked-user message on register page
- 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>
2026-09-02 15:54:55 +04:30

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 }