199 lines
4.8 KiB
TypeScript
199 lines
4.8 KiB
TypeScript
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<TencentBrowserSessionData | null>
|
|
sessionLoading: Ref<boolean>
|
|
notifyActionError: (error: unknown, fallbackMessage: string) => void
|
|
}) {
|
|
const { session, sessionLoading, notifyActionError } = options
|
|
const pollFailureCount = ref(0)
|
|
const pollWarningMessage = ref('')
|
|
|
|
let pollTimer: ReturnType<typeof setTimeout> | 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)
|
|
}
|