import { computed, ref, type Ref } from 'vue' import { fetchTencentBrowserSession, fetchTencentBrowserSessionSummary, } from '@/services/tencent/session' import type { TencentBrowserSessionData, TencentBrowserSessionStatus, TencentBrowserSessionSummaryData, } from '@/types/tencent/session' import { resolveTencentActionMessage } from './session-errors' const POLL_INTERVAL_MS = 2_500 const POLL_FAILURE_LIMIT = 3 export const ACTIVE_TENCENT_SESSION_STATUSES = new Set([ 'waiting_scan', 'scanned', 'logged_in', 'ready_to_redeem', 'redeeming', ]) export function useTencentBrowserSessionPolling(options: { session: Ref sessionLoading: Ref notifyActionError: (error: unknown, fallbackMessage: string) => void }) { const { session, sessionLoading, notifyActionError } = options const pollFailureCount = ref(0) const pollWarningMessage = ref('') let pollTimer: ReturnType | null = null let pollToken = 0 const sessionNotice = computed(() => pollWarningMessage.value || session.value?.notice || '') async function refreshSession({ silent = false } = {}) { if (!session.value?.sessionId) { return false } if (!silent) { sessionLoading.value = true resetPollingWarning() } try { const currentSession = session.value const response = await fetchTencentBrowserSessionSummary(currentSession.sessionId) if (response.code !== 0) { throw new Error(response.msg || '获取浏览器会话状态失败') } const nextSession = mergeSessionData(currentSession, response.data) session.value = nextSession if (shouldRefreshQrImage(currentSession, response.data)) { const fullResponse = await fetchTencentBrowserSession(currentSession.sessionId) if (fullResponse.code !== 0) { throw new Error(fullResponse.msg || '获取浏览器会话二维码失败') } session.value = mergeSessionData(nextSession, fullResponse.data) } if (silent) { resetPollingWarning() } return true } catch (error) { if (silent) { handleSilentPollingError(error) } else { notifyActionError(error, '获取浏览器会话状态失败') } return false } finally { if (!silent) { sessionLoading.value = false } } } function startPolling() { const token = ++pollToken const loop = async () => { if (token !== pollToken || !session.value?.sessionId) { return } await refreshSession({ silent: true }) if (token !== pollToken || !isActiveTencentSessionStatus(session.value?.status)) { return } pollTimer = setTimeout(() => { void loop() }, POLL_INTERVAL_MS) } pollTimer = setTimeout(() => { void loop() }, POLL_INTERVAL_MS) } function resetPolling() { pollToken += 1 if (pollTimer) { clearTimeout(pollTimer) pollTimer = null } } function resetPollingWarning() { pollFailureCount.value = 0 pollWarningMessage.value = '' } function restartPollingIfActive(nextSession = session.value) { if (isActiveTencentSessionStatus(nextSession?.status)) { startPolling() } } function handleSilentPollingError(error: unknown) { console.error(error) pollFailureCount.value += 1 if (pollFailureCount.value < POLL_FAILURE_LIMIT) { return } pollWarningMessage.value = `${resolveTencentActionMessage(error, '会话状态刷新失败')},已暂停自动轮询,请手动刷新或重新生成二维码。` resetPolling() } return { refreshSession, resetPolling, resetPollingWarning, restartPollingIfActive, sessionNotice, startPolling, } } export function isActiveTencentSessionStatus( status: TencentBrowserSessionStatus | null | undefined, ) { return Boolean(status && ACTIVE_TENCENT_SESSION_STATUSES.has(status)) } function mergeSessionData( current: TencentBrowserSessionData | null, next: TencentBrowserSessionSummaryData | TencentBrowserSessionData, ) { if (!current) { return { ...next, qrImageBase64: next.qrImageBase64 || '', } } return { ...current, ...next, qrImageBase64: typeof next.qrImageBase64 === 'string' ? next.qrImageBase64 : current.qrImageBase64, activityInfo: next.activityInfo ?? current.activityInfo ?? null, redeem: next.redeem ?? current.redeem ?? null, artifacts: next.artifacts || current.artifacts, } } function shouldRefreshQrImage( current: TencentBrowserSessionData, next: TencentBrowserSessionSummaryData, ) { if (!next.artifacts?.hasQrImage) { return false } if (!current.qrImageBase64) { return true } return Boolean(next.qrUpdatedAt && next.qrUpdatedAt !== current.qrUpdatedAt) }