Files
ghatreh_user/src/config/config-cache.ts
T
mohmdsdgandCursor bddd591d63 feat(boot): use boot-loader as splash with cached tenant logo
Remove the Vue splash overlay and unused duplicate public logos from dist output.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 20:24:44 +03:30

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
}