From 4d004c3515464bcf25137b146d93a4670d51902d Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 27 Jun 2026 13:49:00 +0330 Subject: [PATCH] fix some bugs --- index.html | 34 +++++- src/app/bootstrap.ts | 1 + src/config/config-cache.ts | 17 ++- src/modules/version/VersionUpdateModal.vue | 7 ++ src/themeConfig.ts | 9 +- src/utils/authToken.ts | 72 ++++++++++++ src/utils/index.ts | 17 ++- src/utils/useAxios.ts | 85 +++++++++++--- src/views/base/home/components/AppHeader.vue | 109 +++++++++++++++--- src/views/base/home/components/PriceStats.vue | 2 +- src/views/base/home/index_old3.vue | 2 +- src/views/base/home/mobile/MobileHomeView.vue | 15 ++- vite.config.ts | 18 ++- 13 files changed, 331 insertions(+), 57 deletions(-) create mode 100644 src/utils/authToken.ts 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 @@
@@ -40,47 +40,126 @@ + + diff --git a/vite.config.ts b/vite.config.ts index ace2e6a..b81ca52 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ enabled: true }, workbox: { - cleanupOutdatedCaches: false, + cleanupOutdatedCaches: true, clientsClaim: true, skipWaiting: true }, @@ -93,6 +93,22 @@ export default defineConfig({ server: { port: 3100, }, + build: { + rollupOptions: { + output: { + manualChunks(id) { + if ( + id.includes('node_modules/moment-jalaali') + || id.includes('node_modules/jalaali-js') + || id.includes('node_modules/moment/') + || id.includes('/src/utils/useDateTime') + ) { + return 'datetime' + } + }, + }, + }, + }, // base: `/single`, // base: `/customer`, css: {