diff --git a/index.html b/index.html
index d3ce1ab..bbf2d59 100644
--- a/index.html
+++ b/index.html
@@ -354,7 +354,8 @@
var CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
var CONFIG_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 7
- var TOKEN_STORAGE_KEY = 'liman_user_token'
+ var TOKEN_STORAGE_KEY = 'customer_session_token'
+ var LEGACY_TOKEN_SUFFIX = '_app_token'
var VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
var VERSION_STORAGE_KEY = 'version'
var CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
@@ -575,14 +576,37 @@
})
}
+ function collectAuthTokensForPreserve() {
+ var preserved = {}
+
+ var session = localStorage.getItem(TOKEN_STORAGE_KEY)
+ if (session) {
+ preserved[TOKEN_STORAGE_KEY] = session
+ }
+
+ for (var i = 0; i < localStorage.length; i++) {
+ var key = localStorage.key(i)
+ if (!key) continue
+ if (key.length >= LEGACY_TOKEN_SUFFIX.length &&
+ key.slice(-LEGACY_TOKEN_SUFFIX.length) === LEGACY_TOKEN_SUFFIX) {
+ var value = localStorage.getItem(key)
+ if (value) {
+ preserved[key] = value
+ }
+ }
+ }
+
+ return preserved
+ }
+
function clearLocalStorageExceptToken() {
- var token = localStorage.getItem(TOKEN_STORAGE_KEY)
+ var preserved = collectAuthTokensForPreserve()
localStorage.clear()
- if (token) {
- localStorage.setItem(TOKEN_STORAGE_KEY, token)
- }
+ Object.keys(preserved).forEach(function (key) {
+ localStorage.setItem(key, preserved[key])
+ })
}
async function clearServiceWorkersAndCaches() {
diff --git a/src/app/bootstrap.ts b/src/app/bootstrap.ts
index ce88a69..3a9c1ea 100644
--- a/src/app/bootstrap.ts
+++ b/src/app/bootstrap.ts
@@ -9,6 +9,7 @@ import { applyProjectTheme } from '@/utils/config'
import { useSettingsStore } from '@/store/settingsStore'
import { useUserStore } from '@/store/userStore'
import { getToken } from '@/utils'
+import '@/utils/useDateTime'
import { initPriceRealtimeChannel } from '@/utils/priceSocket'
type BootWindow = Window & {
diff --git a/src/config/config-cache.ts b/src/config/config-cache.ts
index fa61bfb..ec631b5 100644
--- a/src/config/config-cache.ts
+++ b/src/config/config-cache.ts
@@ -1,7 +1,7 @@
import type { AppConfig } from '@/config/types'
import { resetVersionCheckCache } from '@/config/load-version'
import { clearCachedPwaManifest } from '@/modules/pwa/manifest-cache'
-import { getTokenName } from '@/themeConfig'
+import { collectAuthTokensForPreserve } from '@/utils/authToken'
const CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
@@ -201,13 +201,12 @@ export async function clearBrowserRuntimeCaches() {
}
function clearLocalStorageExceptToken() {
- const tokenKey = getTokenName()
- const token = localStorage.getItem(tokenKey)
+ const preserved = collectAuthTokensForPreserve()
localStorage.clear()
- if (token) {
- localStorage.setItem(tokenKey, token)
+ for (const [key, value] of Object.entries(preserved)) {
+ localStorage.setItem(key, value)
}
}
@@ -242,13 +241,13 @@ export async function refreshConfigCacheForVersion(
startVersionUpdateLock(normalizedVersion)
+ const freshConfig = await fetchConfig()
+
clearCachedAppConfig()
clearAppStorageExceptToken()
clearAllBootstrapFetchCaches()
await clearBrowserRuntimeCaches()
- const freshConfig = await fetchConfig()
-
writeCachedAppConfig(freshConfig)
finishVersionUpdateBeforeReload(normalizedVersion)
@@ -261,13 +260,13 @@ export async function resetAppAndRefreshConfig(
) {
const normalizedVersion = getNormalizedVersion(version)
+ const freshConfig = await fetchConfig()
+
clearCachedAppConfig()
clearAppStorageExceptToken()
clearAllBootstrapFetchCaches()
await clearBrowserRuntimeCaches()
- const freshConfig = await fetchConfig()
-
writeCachedAppConfig(freshConfig)
if (normalizedVersion) {
diff --git a/src/modules/version/VersionUpdateModal.vue b/src/modules/version/VersionUpdateModal.vue
index 24f56ef..93b20b8 100644
--- a/src/modules/version/VersionUpdateModal.vue
+++ b/src/modules/version/VersionUpdateModal.vue
@@ -73,6 +73,8 @@ async function checkRemoteVersion() {
}
async function startUpdate(version: string) {
+ const configBackup = window.__APP_CONFIG__
+
try {
isUpdating.value = true
hasError.value = false
@@ -95,6 +97,11 @@ async function startUpdate(version: string) {
} catch (error) {
console.error('Version update failed:', error)
+ if (configBackup) {
+ window.__APP_CONFIG__ = configBackup
+ setAppConfig(configBackup)
+ }
+
cancelVersionUpdateLock()
isUpdating.value = false
diff --git a/src/themeConfig.ts b/src/themeConfig.ts
index 65721cf..9ca0c7f 100644
--- a/src/themeConfig.ts
+++ b/src/themeConfig.ts
@@ -31,9 +31,14 @@ export function getAvatarUploadURL(): string {
return base ? `${base}/customer/avatar` : '/customer/avatar'
}
+/** کلید ثابت — وقتی config پاک میشود (بهروزرسانی نسخه) توکن گم نشود */
+export const SESSION_AUTH_KEY = 'customer_session_token'
+
+export const LEGACY_TOKEN_SUFFIX = '_app_token'
+
export function getTokenName(): string {
- const name = getConfigApp('name') || readCachedTenantField('name') || 'app'
- return `${name}_app_token`
+ const name = readCachedTenantField('name') || getConfigApp('name') || 'app'
+ return `${name}${LEGACY_TOKEN_SUFFIX}`
}
/** @deprecated از getTokenName() استفاده کن */
diff --git a/src/utils/authToken.ts b/src/utils/authToken.ts
new file mode 100644
index 0000000..a853617
--- /dev/null
+++ b/src/utils/authToken.ts
@@ -0,0 +1,72 @@
+import {
+ getTokenName,
+ LEGACY_TOKEN_SUFFIX,
+ SESSION_AUTH_KEY,
+} from '@/themeConfig'
+
+function legacyTokenKeys(): string[] {
+ const keys: string[] = []
+
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i)
+ if (key?.endsWith(LEGACY_TOKEN_SUFFIX)) {
+ keys.push(key)
+ }
+ }
+
+ return keys
+}
+
+/** خواندن توکن — اول session ثابت، بعد کلید tenant، بعد هر *_app_token */
+export function findStoredAuthToken(): string | null {
+ const session = localStorage.getItem(SESSION_AUTH_KEY)
+ if (session) {
+ return session
+ }
+
+ const named = localStorage.getItem(getTokenName())
+ if (named) {
+ localStorage.setItem(SESSION_AUTH_KEY, named)
+ return named
+ }
+
+ for (const key of legacyTokenKeys()) {
+ const value = localStorage.getItem(key)
+ if (value) {
+ localStorage.setItem(SESSION_AUTH_KEY, value)
+ return value
+ }
+ }
+
+ return null
+}
+
+export function persistAuthToken(token: string | undefined | null) {
+ if (!token) {
+ return
+ }
+
+ localStorage.setItem(SESSION_AUTH_KEY, token)
+ localStorage.setItem(getTokenName(), token)
+}
+
+export function clearStoredAuthTokens() {
+ localStorage.removeItem(SESSION_AUTH_KEY)
+
+ for (const key of legacyTokenKeys()) {
+ localStorage.removeItem(key)
+ }
+}
+
+export function collectAuthTokensForPreserve(): Record {
+ const preserved: Record = {}
+
+ for (const key of [SESSION_AUTH_KEY, getTokenName(), ...legacyTokenKeys()]) {
+ const value = localStorage.getItem(key)
+ if (value) {
+ preserved[key] = value
+ }
+ }
+
+ return preserved
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 78ddc34..407be0b 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,4 +1,8 @@
-import { getTokenName } from '@/themeConfig'
+import {
+ clearStoredAuthTokens,
+ findStoredAuthToken,
+ persistAuthToken,
+} from '@/utils/authToken'
import { ref } from 'vue'
import {useConfigStore} from "@/store/configStore";
@@ -11,7 +15,10 @@ const nameProject= getConfigApp('name')
function setToken(token: string | undefined) {
- return localStorage.setItem(getTokenName(), token)
+ if (!token) {
+ return
+ }
+ return persistAuthToken(token)
}
function setFingerPrint(val: string | undefined) {
@@ -19,7 +26,7 @@ function setFingerPrint(val: string | undefined) {
}
function getToken() {
- return localStorage.getItem(getTokenName())
+ return findStoredAuthToken()
}
function getIsActiveFingerprint() {
@@ -56,7 +63,7 @@ export async function authenticateWithFingerprint() {
const credentialId = btoa(String.fromCharCode(...new Uint8Array(credential.rawId)))
// بازیابی توکن از localStorage
- const token = localStorage.getItem(`${nameProject}_app_token`)
+ const token = findStoredAuthToken()
if (!token) {
console.warn('توکن پیدا نشد، عملیات متوقف شد.')
@@ -78,7 +85,7 @@ export async function authenticateWithFingerprint() {
function removeToken() {
- return localStorage.removeItem(getTokenName())
+ return clearStoredAuthTokens()
}
function removeFingerPrint() {
diff --git a/src/utils/useAxios.ts b/src/utils/useAxios.ts
index e3bb0bc..0e1d66e 100644
--- a/src/utils/useAxios.ts
+++ b/src/utils/useAxios.ts
@@ -3,6 +3,7 @@ import { baseURL as fallbackBaseURL } from '@/themeConfig'
import type {AxiosRequestConfig, AxiosResponse} from 'axios'
import axios from 'axios'
import {getToken, removeToken} from "@/utils";
+import { findStoredAuthToken } from '@/utils/authToken';
import router from "@/router";
import {get} from 'lodash'
import {message} from "ant-design-vue";
@@ -25,6 +26,58 @@ const _axios = axios.create({
headers,
})
+let sessionVerifyTimer: ReturnType | null = null
+let sessionVerifyInFlight = false
+
+function requestPath(config: { url?: string; baseURL?: string } | undefined): string {
+ const raw = String(config?.url ?? '')
+ if (raw.startsWith('http')) {
+ try {
+ return new URL(raw).pathname
+ } catch {
+ return raw
+ }
+ }
+ return raw
+}
+
+async function logoutToLogin() {
+ removeToken()
+ if (router.currentRoute.value.name !== 'login') {
+ await router.push({ name: 'login' })
+ }
+}
+
+function scheduleSessionVerify() {
+ if (sessionVerifyTimer) {
+ return
+ }
+
+ sessionVerifyTimer = setTimeout(async () => {
+ sessionVerifyTimer = null
+ if (sessionVerifyInFlight) {
+ return
+ }
+
+ const token = findStoredAuthToken()
+ if (!token) {
+ return
+ }
+
+ sessionVerifyInFlight = true
+ try {
+ await _axios.get('/customer/me', {
+ headers: { Authorization: `Bearer ${token}` },
+ meta: { errorMessage: 'none' },
+ })
+ } catch {
+ await logoutToLogin()
+ } finally {
+ sessionVerifyInFlight = false
+ }
+ }, 2000)
+}
+
_axios.interceptors.request.use(
(config) => {
const runtimeBaseURL = getConfigApp('node_server')
@@ -54,24 +107,24 @@ _axios.interceptors.response.use(
return response.data
},
async function (error) {
- console.log('this is axios error', error)
- if (error.response.data && error.response.data.message) {
- checkStatus(error.response.data.message, error.response.status, error.config)
+ const response = error.response
+ const config = error.config
+
+ if (response?.data?.message && response.status !== 401) {
+ checkStatus(response.data.message, response.status, config)
}
- //console.log('this is error on interceptors.response', error.config.meta)
- /* if (error.response && (error.response.status == 401 || error.response.status == 400)) {
- //debugger
- removeToken()
- router.push({ name: 'login' })
- }*/
- if (error.response && error.response.status == 401) {
- ///removeToken()
- console.log('error')
- removeToken()
- // router.push({ name: 'login' })
- await router.push({name: 'login'})
- } else if (error.response && error.response.status == 403) {
+ if (response?.status === 401) {
+ const hadAuth = Boolean(config?.headers?.Authorization)
+ if (hadAuth) {
+ const path = requestPath(config)
+ if (path.includes('/customer/me')) {
+ await logoutToLogin()
+ } else {
+ scheduleSessionVerify()
+ }
+ }
+ } else if (response?.status === 403) {
message.error('شما دسترسی لازم برای انجام این عملیات را ندارید')
} else if (error.response && error.response.status == 422) {
// message.error(error.response.data?.message)
diff --git a/src/views/base/home/components/AppHeader.vue b/src/views/base/home/components/AppHeader.vue
index b3713bf..2edf998 100644
--- a/src/views/base/home/components/AppHeader.vue
+++ b/src/views/base/home/components/AppHeader.vue
@@ -2,7 +2,7 @@