修复冻结用户即时下线
This commit is contained in:
@@ -5,10 +5,12 @@ import { computed } from 'vue'
|
||||
import { RouterView, useRoute } from 'vue-router'
|
||||
|
||||
import InAppBrowserPrompt from './components/InAppBrowserPrompt.vue'
|
||||
import { useSessionMonitor } from './features/auth/composables/useSessionMonitor'
|
||||
import AdminLayout from './layouts/AdminLayout.vue'
|
||||
import AppLayout from './layouts/AppLayout.vue'
|
||||
|
||||
const route = useRoute()
|
||||
useSessionMonitor()
|
||||
const layout = computed(() => {
|
||||
if (route.path === '/m' || route.path.startsWith('/m/')) {
|
||||
return 'blank'
|
||||
|
||||
@@ -80,8 +80,8 @@ export async function resetPassword(phone: string, code: string, newPassword: st
|
||||
await apiClient.post('/auth/password/reset', { phone, code, new_password: newPassword })
|
||||
}
|
||||
|
||||
export async function fetchMe() {
|
||||
const { data } = await apiClient.get<ApiResponse<AuthUser>>('/me')
|
||||
export async function fetchMe(options?: { silent?: boolean }) {
|
||||
const { data } = await apiClient.get<ApiResponse<AuthUser>>('/me', options)
|
||||
return data.data
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { onBeforeUnmount, onMounted, watch } from 'vue'
|
||||
|
||||
import { fetchMe } from '@/features/auth/api/auth'
|
||||
import { getAccessToken, type AuthScope } from '@/shared/utils/authStorage'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
|
||||
const scope: AuthScope = 'user'
|
||||
const reconnectDelay = 3000
|
||||
const verificationInterval = 5000
|
||||
|
||||
// 保持一个轻量级会话通道,使后台冻结能在没有页面请求时立即让用户退出。
|
||||
export function useSessionMonitor() {
|
||||
const session = useSessionStore()
|
||||
let source: EventSource | null = null
|
||||
let reconnectTimer: number | null = null
|
||||
let verificationTimer: number | null = null
|
||||
let checking = false
|
||||
let stopped = false
|
||||
|
||||
function closeSource() {
|
||||
source?.close()
|
||||
source = null
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (stopped || reconnectTimer !== null || !session.isLoggedIn) return
|
||||
reconnectTimer = window.setTimeout(() => {
|
||||
reconnectTimer = null
|
||||
connect()
|
||||
}, reconnectDelay)
|
||||
}
|
||||
|
||||
async function verifySession() {
|
||||
if (stopped || checking || !session.isLoggedIn) return
|
||||
checking = true
|
||||
try {
|
||||
await fetchMe({ silent: true })
|
||||
} catch {
|
||||
// 认证失败由 axios 拦截器清理令牌并跳转;网络错误等待下一次校验。
|
||||
} finally {
|
||||
checking = false
|
||||
}
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (stopped || source || !session.isLoggedIn) return
|
||||
const token = getAccessToken(scope)
|
||||
if (!token) return
|
||||
|
||||
source = new EventSource(`/api/auth/events?token=${encodeURIComponent(token)}`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
source.addEventListener('connected', () => undefined)
|
||||
source.onerror = () => {
|
||||
closeSource()
|
||||
void verifySession()
|
||||
scheduleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
closeSource()
|
||||
if (reconnectTimer !== null) {
|
||||
window.clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
if (verificationTimer !== null) {
|
||||
window.clearInterval(verificationTimer)
|
||||
verificationTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
stop()
|
||||
if (!session.isLoggedIn) return
|
||||
connect()
|
||||
void verifySession()
|
||||
verificationTimer = window.setInterval(verifySession, verificationInterval)
|
||||
}
|
||||
|
||||
function handleAuthStorageChanged(event: Event) {
|
||||
const detail = (event as CustomEvent<{ scope?: AuthScope }>).detail
|
||||
if (detail?.scope !== scope || stopped) return
|
||||
if (session.isLoggedIn) {
|
||||
start()
|
||||
} else {
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (document.visibilityState === 'visible') void verifySession()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
stopped = false
|
||||
start()
|
||||
window.addEventListener('auth-storage-changed', handleAuthStorageChanged)
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopped = true
|
||||
stop()
|
||||
window.removeEventListener('auth-storage-changed', handleAuthStorageChanged)
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => session.isLoggedIn,
|
||||
loggedIn => {
|
||||
if (stopped) return
|
||||
if (loggedIn) start()
|
||||
else stop()
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user