前端热更新

This commit is contained in:
biss committed 2026-08-21 22:02:33 +08:00
1 parent af88732a07
commit 5b95fe8f40
3 files changed
+19 -16

No files matched your search

+11 -4
View File
@@ -8,6 +8,13 @@ import {
refreshIfNeeded, refreshIfNeeded,
} from '../auth/session' } from '../auth/session'
declare module 'axios' {
export interface AxiosRequestConfig {
// Public kiosk pages must remain usable when this browser has an expired login session.
skipAuthSession?: boolean
}
}
const http = axios.create({ const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api', baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api',
timeout: 15000, timeout: 15000,
@@ -20,12 +27,12 @@ http.interceptors.request.use(async (config) => {
config.url?.endsWith('/auth/logout') || config.url?.endsWith('/auth/logout') ||
config.url?.endsWith('/auth/sso/exchange') || config.url?.endsWith('/auth/sso/exchange') ||
config.url?.endsWith('/auth/sso/bind') config.url?.endsWith('/auth/sso/bind')
if (!isAuthenticationRequest) { if (!isAuthenticationRequest && !config.skipAuthSession) {
const activeToken = await refreshIfNeeded(true) const activeToken = await refreshIfNeeded(true)
if (activeToken) markActivity() if (activeToken) markActivity()
} }
const token = localStorage.getItem(authStorageKeys.token) const token = localStorage.getItem(authStorageKeys.token)
if (token) config.headers.Authorization = `Bearer ${token}` if (token && !config.skipAuthSession) config.headers.Authorization = `Bearer ${token}`
return config return config
}) })
@@ -40,7 +47,7 @@ http.interceptors.response.use(
error.config?.url?.endsWith('/auth/sso/bind') error.config?.url?.endsWith('/auth/sso/bind')
const retryableConfig = error.config as const retryableConfig = error.config as
(typeof error.config & { _jiaowuRetried?: boolean }) | undefined (typeof error.config & { _jiaowuRetried?: boolean }) | undefined
if (error.response?.status === 401 && !isAuthenticationRequest && if (error.response?.status === 401 && !isAuthenticationRequest && !error.config?.skipAuthSession &&
!retryableConfig?._jiaowuRetried) { !retryableConfig?._jiaowuRetried) {
const token = await refreshAuthSession() const token = await refreshAuthSession()
if (token && retryableConfig) { if (token && retryableConfig) {
@@ -49,7 +56,7 @@ http.interceptors.response.use(
return http.request(retryableConfig) return http.request(retryableConfig)
} }
} }
if (error.response?.status === 401 && !isAuthenticationRequest) { if (error.response?.status === 401 && !isAuthenticationRequest && !error.config?.skipAuthSession) {
clearAuthSession() clearAuthSession()
goLogin(location.pathname + location.search + location.hash) goLogin(location.pathname + location.search + location.hash)
} }
+7 -11
View File
@@ -18,10 +18,8 @@ interface AppUpdateCheckResponse {
const updateChannel = const updateChannel =
import.meta.env.VITE_APP_UPDATE_CHANNEL?.trim() || 'production' import.meta.env.VITE_APP_UPDATE_CHANNEL?.trim() || 'production'
const foregroundCheckInterval = 5 * 60 * 1000
let checkInProgress: Promise<void> | undefined let checkInProgress: Promise<void> | undefined
let lastCheckedAt = 0
function absoluteApiUrl(relativePath: string) { function absoluteApiUrl(relativePath: string) {
const configuredBase = String(http.defaults.baseURL ?? '/api') const configuredBase = String(http.defaults.baseURL ?? '/api')
@@ -36,9 +34,7 @@ function absoluteApiUrl(relativePath: string) {
async function checkForAppUpdate() { async function checkForAppUpdate() {
if (checkInProgress) return checkInProgress if (checkInProgress) return checkInProgress
if (!navigator.onLine || Date.now() - lastCheckedAt < foregroundCheckInterval) { if (!navigator.onLine) return
return
}
checkInProgress = (async () => { checkInProgress = (async () => {
try { try {
@@ -64,6 +60,9 @@ async function checkForAppUpdate() {
nativeVersion: current.native, nativeVersion: current.native,
currentVersion, currentVersion,
}, },
// Update discovery is deliberately public. Do not delay startup for a
// token refresh or send a stale Authorization header to this endpoint.
skipAuthSession: true,
timeout: 15000, timeout: 15000,
}, },
) )
@@ -96,7 +95,6 @@ async function checkForAppUpdate() {
// OTA failure must never stop users from entering the bundled application. // OTA failure must never stop users from entering the bundled application.
console.warn('[app-update] update check failed', error) console.warn('[app-update] update check failed', error)
} finally { } finally {
lastCheckedAt = Date.now()
checkInProgress = undefined checkInProgress = undefined
} }
})() })()
@@ -116,10 +114,8 @@ export async function initializeAppUpdates() {
console.warn('[app-update] bundle readiness confirmation failed', error) console.warn('[app-update] bundle readiness confirmation failed', error)
} }
// Every cold start checks the latest public release immediately. A bundle
// downloaded here is activated when the app is next launched.
void checkForAppUpdate() void checkForAppUpdate()
window.addEventListener('visibilitychange', () => { window.addEventListener('online', () => void checkForAppUpdate())
if (document.visibilityState === 'visible') {
void checkForAppUpdate()
}
})
} }
+1 -1
View File
@@ -23,7 +23,7 @@ async function load() {
if (loading) return if (loading) return
loading = true loading = true
try { try {
data.value = (await http.get(`/venue-displays/${type.value}s/${id.value}`)).data data.value = (await http.get(`/venue-displays/${type.value}s/${id.value}`, { skipAuthSession: true })).data
await nextTick() await nextTick()
fitPage() fitPage()
} finally { } finally {