AutoTeam-F / web /src /composables /useAppState.js
ZRainbow's picture
feat: AutoTeam 大版本升级:安全轮换、并行、IPv6、指纹加固与前端重构
1f4cfcb
Raw
History Blame Contribute Delete
7.73 kB
import { computed, onUnmounted, ref, watch } from 'vue'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { api } from '../api.js'
import { useRotateStream } from './useRotateStream.js'
export const APP_QUERY_KEYS = {
status: ['status'],
tasks: ['tasks'],
adminStatus: ['admin-status'],
codexStatus: ['main-codex-status'],
manualAccountStatus: ['manual-account-status'],
registerFailures: ['register-failures'],
}
export const REFRESH_POLICY = {
activeMs: 3000,
idleMs: 120000,
burstMs: 90000,
staleMs: 5000,
}
function dataRef(query, fallback = null) {
return computed(() => query.data.value ?? fallback)
}
function activeTask(tasks) {
return (tasks || []).find(task => task.status === 'running' || task.status === 'pending') || null
}
export function useAppState(authenticated) {
const queryClient = useQueryClient()
const enabled = computed(() => !!authenticated.value)
const burstUntil = ref(0)
let activeTimer = null
let burstTimer = null
const statusQuery = useQuery({
queryKey: APP_QUERY_KEYS.status,
queryFn: () => api.getStatus(),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const tasksQuery = useQuery({
queryKey: APP_QUERY_KEYS.tasks,
queryFn: () => api.getTasks(),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const adminStatusQuery = useQuery({
queryKey: APP_QUERY_KEYS.adminStatus,
queryFn: () => api.getAdminStatus(),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const codexStatusQuery = useQuery({
queryKey: APP_QUERY_KEYS.codexStatus,
queryFn: () => api.getMainCodexStatus(),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const manualAccountStatusQuery = useQuery({
queryKey: APP_QUERY_KEYS.manualAccountStatus,
queryFn: () => api.getManualAccountStatus(),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const registerFailuresQuery = useQuery({
queryKey: APP_QUERY_KEYS.registerFailures,
queryFn: () => api.getRegisterFailures(50),
enabled,
staleTime: REFRESH_POLICY.staleMs,
refetchInterval: computed(() => enabled.value ? REFRESH_POLICY.idleMs : false),
refetchOnWindowFocus: true,
keepPreviousData: true,
})
const status = dataRef(statusQuery)
const tasks = dataRef(tasksQuery, [])
const adminStatus = dataRef(adminStatusQuery)
const codexStatus = dataRef(codexStatusQuery)
const manualAccountStatus = dataRef(manualAccountStatusQuery)
const registerFailures = dataRef(registerFailuresQuery)
const runningTask = computed(() => activeTask(tasks.value))
const busyTask = computed(() => {
if (adminStatus.value?.login_in_progress) return { command: 'admin-login' }
if (codexStatus.value?.in_progress) return { command: 'main-codex-sync' }
return runningTask.value
})
const loading = computed(() => (
statusQuery.isFetching.value
|| tasksQuery.isFetching.value
|| adminStatusQuery.isFetching.value
|| codexStatusQuery.isFetching.value
|| manualAccountStatusQuery.isFetching.value
|| registerFailuresQuery.isFetching.value
))
const unauthorized = computed(() => [
statusQuery.error.value,
tasksQuery.error.value,
adminStatusQuery.error.value,
codexStatusQuery.error.value,
manualAccountStatusQuery.error.value,
registerFailuresQuery.error.value,
].some(error => error?.status === 401))
const registerFailuresUnavailable = computed(() => {
const error = registerFailuresQuery.error.value
if (!error) return ''
if (error.status === 404 || error.status === 405) {
return '当前后端未提供注册失败明细 JSON 接口,前端已降级为空列表。'
}
return `获取注册失败明细失败: ${error.message || '未知错误'}`
})
const registerFailuresLoading = computed(() => registerFailuresQuery.isFetching.value)
const rotateStream = useRotateStream({ immediate: false })
const offTransition = rotateStream.onTransition(() => {
refetchCore()
})
function invalidateCore() {
for (const key of [
APP_QUERY_KEYS.status,
APP_QUERY_KEYS.tasks,
APP_QUERY_KEYS.adminStatus,
APP_QUERY_KEYS.codexStatus,
APP_QUERY_KEYS.manualAccountStatus,
APP_QUERY_KEYS.registerFailures,
]) {
queryClient.invalidateQueries({ queryKey: key })
}
}
function clearCoreCache() {
for (const key of [
APP_QUERY_KEYS.status,
APP_QUERY_KEYS.tasks,
APP_QUERY_KEYS.adminStatus,
APP_QUERY_KEYS.codexStatus,
APP_QUERY_KEYS.manualAccountStatus,
APP_QUERY_KEYS.registerFailures,
]) {
queryClient.removeQueries({ queryKey: key })
}
}
async function refetchCore() {
if (!enabled.value) return
invalidateCore()
await Promise.allSettled([
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.status, type: 'active' }),
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.tasks, type: 'active' }),
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.adminStatus, type: 'active' }),
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.codexStatus, type: 'active' }),
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.manualAccountStatus, type: 'active' }),
queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.registerFailures, type: 'active' }),
])
}
function stopActiveRefresh() {
if (activeTimer) {
clearInterval(activeTimer)
activeTimer = null
}
}
function startActiveRefresh(durationMs = 0) {
if (!enabled.value) return
if (!activeTimer) {
activeTimer = setInterval(refetchCore, REFRESH_POLICY.activeMs)
}
if (durationMs > 0) {
burstUntil.value = Date.now() + durationMs
clearTimeout(burstTimer)
burstTimer = setTimeout(() => {
burstUntil.value = 0
if (!busyTask.value) stopActiveRefresh()
}, durationMs)
}
}
function notifyActionStarted() {
startActiveRefresh(REFRESH_POLICY.burstMs)
refetchCore()
}
async function refresh() {
await refetchCore()
}
watch(enabled, (ok) => {
if (ok) {
rotateStream.connect()
refetchCore()
return
}
stopActiveRefresh()
rotateStream.disconnect()
clearCoreCache()
}, { immediate: true })
watch(unauthorized, (hit) => {
if (hit) {
authenticated.value = false
}
})
watch(busyTask, (task) => {
if (task) {
startActiveRefresh()
return
}
refetchCore()
if (!burstUntil.value || Date.now() >= burstUntil.value) stopActiveRefresh()
})
onUnmounted(() => {
stopActiveRefresh()
clearTimeout(burstTimer)
offTransition && offTransition()
rotateStream.disconnect()
})
return {
status,
tasks,
adminStatus,
codexStatus,
manualAccountStatus,
registerFailures,
registerFailuresLoading,
registerFailuresUnavailable,
runningTask,
busyTask,
loading,
rotateStream,
refresh,
notifyActionStarted,
}
}