Remove the Vue splash overlay and unused duplicate public logos from dist output. Co-authored-by: Cursor <cursoragent@cursor.com>
323 lines
8.6 KiB
TypeScript
323 lines
8.6 KiB
TypeScript
import type { AppConfig } from '@/config/types'
|
|
import { resetVersionCheckCache } from '@/config/load-version'
|
|
import { clearCachedPwaManifest } from '@/modules/pwa/manifest-cache'
|
|
import { tokenName } from '@/themeConfig'
|
|
|
|
const CONFIG_COOKIE_NAME = 'APP_CONFIG_CACHE'
|
|
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL'
|
|
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
|
const CONFIG_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 7
|
|
const MAX_COOKIE_VALUE_LENGTH = 3500
|
|
|
|
const VERSION_STORAGE_KEY = 'version'
|
|
const VERSION_UPDATE_LOCK_KEY = 'version_update_lock'
|
|
const VERSION_LAST_RELOAD_KEY = 'version_last_reload'
|
|
|
|
type CachedConfigPayload = {
|
|
cached_at: number
|
|
config: AppConfig
|
|
}
|
|
|
|
type VersionUpdateLock = {
|
|
version: string
|
|
started_at: number
|
|
}
|
|
|
|
function getCookie(name: string) {
|
|
const cookie = document.cookie.split('; ').find((row) => row.startsWith(`${name}=`))
|
|
|
|
if (!cookie) return null
|
|
|
|
return cookie.substring(name.length + 1)
|
|
}
|
|
|
|
function setCookie(name: string, value: string, maxAgeSeconds: number) {
|
|
const secure = window.location.protocol === 'https:' ? '; Secure' : ''
|
|
|
|
document.cookie = [
|
|
`${name}=${value}`,
|
|
'Path=/',
|
|
`Max-Age=${maxAgeSeconds}`,
|
|
'SameSite=Lax',
|
|
secure
|
|
].join('; ')
|
|
}
|
|
|
|
export function readCachedAppConfig(): AppConfig | null {
|
|
try {
|
|
const rawCookie = getCookie(CONFIG_COOKIE_NAME)
|
|
|
|
if (!rawCookie) return null
|
|
|
|
const decoded = decodeURIComponent(rawCookie)
|
|
const payload = JSON.parse(decoded) as CachedConfigPayload
|
|
|
|
if (!payload?.config) return null
|
|
|
|
return payload.config
|
|
} catch (error) {
|
|
console.warn('Failed to read cached app config:', error)
|
|
clearCachedAppConfig()
|
|
return null
|
|
}
|
|
}
|
|
|
|
function persistBootLogo(config: AppConfig) {
|
|
try {
|
|
const nested = (config as {user?: Record<string, unknown>})?.user
|
|
const root = (config || {}) as Record<string, unknown>
|
|
const avatar = String(nested?.avatar || root.avatar || '').trim()
|
|
const version = String(localStorage.getItem(VERSION_STORAGE_KEY) || '').trim()
|
|
const cachedUrl = localStorage.getItem('BOOT_LOGO_URL') || ''
|
|
const cachedVer = localStorage.getItem('BOOT_LOGO_VERSION') || ''
|
|
const cacheFresh = !!(cachedUrl && version && cachedVer && version === cachedVer)
|
|
if (!cacheFresh && avatar) localStorage.setItem('BOOT_LOGO_URL', avatar)
|
|
if (version) localStorage.setItem('BOOT_LOGO_VERSION', version)
|
|
const apply = (window as Window & {__APPLY_BOOT_BRANDING__?: (c: unknown) => void})
|
|
.__APPLY_BOOT_BRANDING__
|
|
if (typeof apply === 'function') apply(config)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
export function writeCachedAppConfig(config: AppConfig) {
|
|
try {
|
|
persistBootLogo(config)
|
|
|
|
const payload: CachedConfigPayload = {
|
|
cached_at: Date.now(),
|
|
config
|
|
}
|
|
|
|
const encoded = encodeURIComponent(JSON.stringify(payload))
|
|
|
|
if (encoded.length > MAX_COOKIE_VALUE_LENGTH) {
|
|
console.warn(`App config is too large for cookie cache. Size: ${encoded.length}`)
|
|
return
|
|
}
|
|
|
|
setCookie(CONFIG_COOKIE_NAME, encoded, CONFIG_COOKIE_MAX_AGE_SECONDS)
|
|
} catch (error) {
|
|
console.warn('Failed to write app config cache:', error)
|
|
}
|
|
}
|
|
|
|
export function clearCachedAppConfig() {
|
|
document.cookie = `${CONFIG_COOKIE_NAME}=; Path=/; Max-Age=0; SameSite=Lax`
|
|
clearCachedPwaManifest()
|
|
}
|
|
|
|
export function getStoredAppVersion() {
|
|
return localStorage.getItem(VERSION_STORAGE_KEY)
|
|
}
|
|
|
|
export function getDisplayAppVersion(fallbackVersion: string) {
|
|
return getStoredAppVersion() || fallbackVersion
|
|
}
|
|
|
|
export function writeStoredAppVersion(version: string) {
|
|
localStorage.setItem(VERSION_STORAGE_KEY, String(version).trim())
|
|
}
|
|
|
|
export function getNormalizedVersion(version: unknown) {
|
|
if (version === null || version === undefined) return ''
|
|
return String(version).trim()
|
|
}
|
|
|
|
export function shouldUpdateForVersion(serverVersion: unknown) {
|
|
const normalizedServerVersion = getNormalizedVersion(serverVersion)
|
|
|
|
if (!normalizedServerVersion) return false
|
|
|
|
const storedVersion = getStoredAppVersion()
|
|
|
|
if (!storedVersion) {
|
|
writeStoredAppVersion(normalizedServerVersion)
|
|
return false
|
|
}
|
|
|
|
return storedVersion !== normalizedServerVersion
|
|
}
|
|
|
|
export function canStartVersionUpdate(serverVersion: string) {
|
|
const version = getNormalizedVersion(serverVersion)
|
|
|
|
if (!version) return false
|
|
|
|
const lastReloadVersion = sessionStorage.getItem(VERSION_LAST_RELOAD_KEY)
|
|
|
|
if (lastReloadVersion === version) {
|
|
return false
|
|
}
|
|
|
|
const rawLock = sessionStorage.getItem(VERSION_UPDATE_LOCK_KEY)
|
|
|
|
if (rawLock) {
|
|
try {
|
|
const lock = JSON.parse(rawLock) as VersionUpdateLock
|
|
const isSameVersion = lock.version === version
|
|
const isFreshLock = Date.now() - lock.started_at < 60_000
|
|
|
|
if (isSameVersion && isFreshLock) {
|
|
return false
|
|
}
|
|
} catch {
|
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
export function startVersionUpdateLock(serverVersion: string) {
|
|
const version = getNormalizedVersion(serverVersion)
|
|
|
|
const lock: VersionUpdateLock = {
|
|
version,
|
|
started_at: Date.now()
|
|
}
|
|
|
|
sessionStorage.setItem(VERSION_UPDATE_LOCK_KEY, JSON.stringify(lock))
|
|
}
|
|
|
|
export function finishVersionUpdateBeforeReload(serverVersion: string) {
|
|
const version = getNormalizedVersion(serverVersion)
|
|
|
|
writeStoredAppVersion(version)
|
|
|
|
sessionStorage.setItem(VERSION_LAST_RELOAD_KEY, version)
|
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY)
|
|
}
|
|
|
|
export function cancelVersionUpdateLock() {
|
|
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY)
|
|
}
|
|
|
|
function clearReadableCookiesExceptConfigAlreadyHandled() {
|
|
document.cookie.split(';').forEach((cookie) => {
|
|
const name = cookie.split('=')[0]?.trim()
|
|
|
|
if (!name) return
|
|
|
|
document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax`
|
|
})
|
|
}
|
|
|
|
export async function clearBrowserRuntimeCaches() {
|
|
try {
|
|
if ('serviceWorker' in navigator) {
|
|
const registrations = await navigator.serviceWorker.getRegistrations()
|
|
|
|
await Promise.all(registrations.map((registration) => registration.unregister()))
|
|
}
|
|
} catch (error) {
|
|
console.warn('Could not unregister service workers:', error)
|
|
}
|
|
|
|
try {
|
|
if (!('caches' in window)) return
|
|
|
|
const cacheKeys = await caches.keys()
|
|
await Promise.all(cacheKeys.map((key) => caches.delete(key)))
|
|
} catch (error) {
|
|
console.warn('Could not clear browser caches:', error)
|
|
}
|
|
}
|
|
|
|
function clearLocalStorageExceptToken() {
|
|
const token = localStorage.getItem(tokenName)
|
|
let bootLogo = ''
|
|
let bootLogoVer = ''
|
|
try {
|
|
bootLogo = localStorage.getItem('BOOT_LOGO_URL') || ''
|
|
bootLogoVer = localStorage.getItem('BOOT_LOGO_VERSION') || ''
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
localStorage.clear()
|
|
|
|
if (token) {
|
|
localStorage.setItem(tokenName, token)
|
|
}
|
|
if (bootLogo) localStorage.setItem('BOOT_LOGO_URL', bootLogo)
|
|
if (bootLogoVer) localStorage.setItem('BOOT_LOGO_VERSION', bootLogoVer)
|
|
}
|
|
|
|
export function clearAppStorageExceptToken() {
|
|
clearReadableCookiesExceptConfigAlreadyHandled()
|
|
clearLocalStorageExceptToken()
|
|
sessionStorage.clear()
|
|
}
|
|
|
|
export function clearAllBootstrapFetchCaches() {
|
|
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY)
|
|
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
|
clearCachedPwaManifest()
|
|
resetVersionCheckCache()
|
|
window.__APP_CONFIG__ = undefined
|
|
}
|
|
|
|
export function hardReloadApp() {
|
|
const refreshUrl = `${window.location.origin}${window.location.pathname}?__app_refresh=${Date.now()}`
|
|
window.location.href = refreshUrl
|
|
}
|
|
|
|
export async function refreshConfigCacheForVersion(
|
|
newVersion: string,
|
|
fetchConfig: () => Promise<AppConfig>
|
|
) {
|
|
const normalizedVersion = getNormalizedVersion(newVersion)
|
|
|
|
if (!normalizedVersion) {
|
|
throw new Error('Version is empty')
|
|
}
|
|
|
|
startVersionUpdateLock(normalizedVersion)
|
|
|
|
clearCachedAppConfig()
|
|
clearAppStorageExceptToken()
|
|
clearAllBootstrapFetchCaches()
|
|
await clearBrowserRuntimeCaches()
|
|
|
|
const freshConfig = await fetchConfig()
|
|
|
|
writeCachedAppConfig(freshConfig)
|
|
finishVersionUpdateBeforeReload(normalizedVersion)
|
|
try {
|
|
localStorage.setItem('BOOT_LOGO_VERSION', normalizedVersion)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
return freshConfig
|
|
}
|
|
|
|
export async function resetAppAndRefreshConfig(
|
|
fetchConfig: () => Promise<AppConfig>,
|
|
version?: string | null
|
|
) {
|
|
const normalizedVersion = getNormalizedVersion(version)
|
|
|
|
clearCachedAppConfig()
|
|
clearAppStorageExceptToken()
|
|
clearAllBootstrapFetchCaches()
|
|
await clearBrowserRuntimeCaches()
|
|
|
|
const freshConfig = await fetchConfig()
|
|
|
|
writeCachedAppConfig(freshConfig)
|
|
|
|
if (normalizedVersion) {
|
|
writeStoredAppVersion(normalizedVersion)
|
|
try {
|
|
localStorage.setItem('BOOT_LOGO_VERSION', normalizedVersion)
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
return freshConfig
|
|
}
|