拆分腾讯会话活动页采集与展示同步逻辑
This commit is contained in:
@@ -0,0 +1,231 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { extractHostState, reloadActivityPageForPresentation } from './session-page.js'
|
||||||
|
import { shouldRefreshLoggedInPresentation } from './session-state.js'
|
||||||
|
|
||||||
|
export function createEmptyActivityInfo() {
|
||||||
|
return {
|
||||||
|
nickname: '',
|
||||||
|
role: {
|
||||||
|
ready: false,
|
||||||
|
roleName: '',
|
||||||
|
roleId: '',
|
||||||
|
area: '',
|
||||||
|
partition: '',
|
||||||
|
platId: '',
|
||||||
|
md5str: '',
|
||||||
|
checkparam: '',
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
cdkeyInputId: '',
|
||||||
|
cdkeyValue: '',
|
||||||
|
verifyInputId: '',
|
||||||
|
verifyValue: '',
|
||||||
|
verifyImgId: '',
|
||||||
|
submitId: '',
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
visible: false,
|
||||||
|
src: '',
|
||||||
|
naturalWidth: 0,
|
||||||
|
naturalHeight: 0,
|
||||||
|
},
|
||||||
|
popup: {
|
||||||
|
visible: false,
|
||||||
|
text: '',
|
||||||
|
detail: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureLoggedInPresentation(
|
||||||
|
session,
|
||||||
|
{
|
||||||
|
hostState = null,
|
||||||
|
credentialReady = false,
|
||||||
|
activityUrl = '',
|
||||||
|
extractHostState: extractHostStateImpl = extractHostState,
|
||||||
|
reloadActivityPageForPresentation: reloadActivityPageForPresentationImpl = reloadActivityPageForPresentation,
|
||||||
|
shouldRefreshLoggedInPresentation: shouldRefreshLoggedInPresentationImpl = shouldRefreshLoggedInPresentation,
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
let nextHostState = hostState || (await extractHostStateImpl(session.page))
|
||||||
|
|
||||||
|
if (!credentialReady || !shouldRefreshLoggedInPresentationImpl(nextHostState)) {
|
||||||
|
return {
|
||||||
|
reloaded: false,
|
||||||
|
hostState: nextHostState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.presentationSyncAttempts >= 2) {
|
||||||
|
return {
|
||||||
|
reloaded: false,
|
||||||
|
hostState: nextHostState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.presentationSyncAttempts += 1
|
||||||
|
await reloadActivityPageForPresentationImpl(session.page, activityUrl)
|
||||||
|
nextHostState = await extractHostStateImpl(session.page)
|
||||||
|
|
||||||
|
return {
|
||||||
|
reloaded: true,
|
||||||
|
hostState: nextHostState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureActivityInfoReady(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
triggerRender = false,
|
||||||
|
timeoutMs = 8_000,
|
||||||
|
extractActivityInfo: extractActivityInfoImpl = extractActivityInfo,
|
||||||
|
triggerActivityRoleRender: triggerActivityRoleRenderImpl = triggerActivityRoleRender,
|
||||||
|
now = () => Date.now(),
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
if (triggerRender) {
|
||||||
|
await triggerActivityRoleRenderImpl(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startedAt = now()
|
||||||
|
let lastInfo = await extractActivityInfoImpl(page)
|
||||||
|
|
||||||
|
while (now() - startedAt < timeoutMs) {
|
||||||
|
if (lastInfo?.role?.ready) {
|
||||||
|
return lastInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
lastInfo = await extractActivityInfoImpl(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function triggerActivityRoleRender(page) {
|
||||||
|
try {
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const maybeRender = window.renderCdkey
|
||||||
|
|
||||||
|
if (typeof maybeRender === 'function') {
|
||||||
|
maybeRender()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// ignore render trigger failures; polling will still inspect the page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractActivityInfo(page) {
|
||||||
|
try {
|
||||||
|
return await page.evaluate(() => {
|
||||||
|
const asString = (value) => (typeof value === 'string' ? value.trim() : '')
|
||||||
|
const isVisible = (element) => {
|
||||||
|
if (!element) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(element)
|
||||||
|
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const pick = (selector) => document.querySelector(selector)
|
||||||
|
const pickBest = (selector, predicate = null) => {
|
||||||
|
const matches = [...document.querySelectorAll(selector)]
|
||||||
|
const preferred = matches.find((element) => {
|
||||||
|
if (predicate && !predicate(element)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVisible(element)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (preferred) {
|
||||||
|
return preferred
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predicate) {
|
||||||
|
return matches.find((element) => predicate(element)) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches[0] || null
|
||||||
|
}
|
||||||
|
const textOf = (selector) => asString(pick(selector)?.textContent || '')
|
||||||
|
const inputOf = (selector) => {
|
||||||
|
const element = pickBest(
|
||||||
|
selector,
|
||||||
|
(node) => node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return asString(element.value || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleData = window.roleData && typeof window.roleData === 'object' ? window.roleData : {}
|
||||||
|
const roleSelector = pick('[id^="milo_role_selector_"]')
|
||||||
|
const roleNameText =
|
||||||
|
textOf('#role_name') ||
|
||||||
|
asString(roleSelector?.selectedOptions?.[0]?.textContent || '')
|
||||||
|
const verifyImg = pickBest('[id^="milo_verifyImg_"]', (node) => node instanceof HTMLImageElement)
|
||||||
|
const popup = pick('#pop2')
|
||||||
|
const cdkeyInput = pickBest(
|
||||||
|
'[id^="milo_cdkeyInfo_"]',
|
||||||
|
(node) =>
|
||||||
|
(node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
|
||||||
|
!node.disabled &&
|
||||||
|
node.type !== 'hidden',
|
||||||
|
)
|
||||||
|
const verifyInput = pickBest(
|
||||||
|
'[id^="milo_verifyInput_"]',
|
||||||
|
(node) =>
|
||||||
|
(node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
|
||||||
|
!node.disabled &&
|
||||||
|
node.type !== 'hidden',
|
||||||
|
)
|
||||||
|
const submitButton = pickBest(
|
||||||
|
'#milo_cdkey_submit, [id^="milo_cdkey_submit"]',
|
||||||
|
(node) => node instanceof HTMLElement && !node.hasAttribute('disabled'),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
nickname: textOf('#login_nickname_span'),
|
||||||
|
role: {
|
||||||
|
ready: Boolean(window.isRole || roleNameText),
|
||||||
|
roleName: roleNameText,
|
||||||
|
roleId: asString(roleData.sRoleId),
|
||||||
|
area: asString(roleData.sArea || '36'),
|
||||||
|
partition: asString(roleData.sPartition),
|
||||||
|
platId: asString(roleData.sPlatId),
|
||||||
|
md5str: asString(roleData.sMd5str || roleData.md5str || roleData.sMdStr),
|
||||||
|
checkparam: asString(roleData.sCheckparam),
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
cdkeyInputId: cdkeyInput?.id || '',
|
||||||
|
cdkeyValue: inputOf('[id^="milo_cdkeyInfo_"]'),
|
||||||
|
verifyInputId: verifyInput?.id || '',
|
||||||
|
verifyValue: inputOf('[id^="milo_verifyInput_"]'),
|
||||||
|
verifyImgId: verifyImg?.id || '',
|
||||||
|
submitId: submitButton?.id || '',
|
||||||
|
},
|
||||||
|
verify: {
|
||||||
|
visible: isVisible(verifyImg),
|
||||||
|
src: asString(verifyImg?.getAttribute('src')),
|
||||||
|
naturalWidth: Number(verifyImg?.naturalWidth || 0),
|
||||||
|
naturalHeight: Number(verifyImg?.naturalHeight || 0),
|
||||||
|
},
|
||||||
|
popup: {
|
||||||
|
visible: isVisible(popup),
|
||||||
|
text: textOf('#PopText'),
|
||||||
|
detail: textOf('#PopText2'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return createEmptyActivityInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,11 @@ import { logBrowserSessionDebug } from './session-login-shared.js'
|
|||||||
import {
|
import {
|
||||||
loadCookieMapFromContext,
|
loadCookieMapFromContext,
|
||||||
} from './session-cookie-map.js'
|
} from './session-cookie-map.js'
|
||||||
|
import {
|
||||||
|
ensureActivityInfoReady,
|
||||||
|
ensureLoggedInPresentation,
|
||||||
|
extractActivityInfo,
|
||||||
|
} from './session-activity.js'
|
||||||
import {
|
import {
|
||||||
buildSessionPayload as buildSessionPayloadBase,
|
buildSessionPayload as buildSessionPayloadBase,
|
||||||
} from './session-payload.js'
|
} from './session-payload.js'
|
||||||
@@ -54,7 +59,6 @@ import {
|
|||||||
resolveSessionStatus,
|
resolveSessionStatus,
|
||||||
resolveTencentSessionExpiresAt,
|
resolveTencentSessionExpiresAt,
|
||||||
SESSION_TTL_MS,
|
SESSION_TTL_MS,
|
||||||
shouldRefreshLoggedInPresentation,
|
|
||||||
} from './session-state.js'
|
} from './session-state.js'
|
||||||
|
|
||||||
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||||
@@ -235,7 +239,11 @@ export async function refreshTencentBrowserSession(sessionId, { includeQrImage =
|
|||||||
const credentialReady = hasRedeemCredential(cookieMap)
|
const credentialReady = hasRedeemCredential(cookieMap)
|
||||||
|
|
||||||
if (credentialReady) {
|
if (credentialReady) {
|
||||||
const presentation = await ensureLoggedInPresentation(session, { hostState, credentialReady })
|
const presentation = await ensureLoggedInPresentation(session, {
|
||||||
|
hostState,
|
||||||
|
credentialReady,
|
||||||
|
activityUrl: ACTIVITY_URL,
|
||||||
|
})
|
||||||
hostState = presentation.hostState
|
hostState = presentation.hostState
|
||||||
} else {
|
} else {
|
||||||
session.presentationSyncAttempts = 0
|
session.presentationSyncAttempts = 0
|
||||||
@@ -488,213 +496,6 @@ function resetTencentSessionAutoClose(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureLoggedInPresentation(
|
|
||||||
session,
|
|
||||||
{ hostState = null, credentialReady = false } = {},
|
|
||||||
) {
|
|
||||||
let nextHostState = hostState || (await extractHostState(session.page))
|
|
||||||
|
|
||||||
if (!credentialReady || !shouldRefreshLoggedInPresentation(nextHostState)) {
|
|
||||||
return {
|
|
||||||
reloaded: false,
|
|
||||||
hostState: nextHostState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.presentationSyncAttempts >= 2) {
|
|
||||||
return {
|
|
||||||
reloaded: false,
|
|
||||||
hostState: nextHostState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session.presentationSyncAttempts += 1
|
|
||||||
await reloadActivityPageForPresentation(session.page, ACTIVITY_URL)
|
|
||||||
nextHostState = await extractHostState(session.page)
|
|
||||||
|
|
||||||
return {
|
|
||||||
reloaded: true,
|
|
||||||
hostState: nextHostState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ensureActivityInfoReady(page, { triggerRender = false, timeoutMs = 8_000 } = {}) {
|
|
||||||
if (triggerRender) {
|
|
||||||
await triggerActivityRoleRender(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
const startedAt = Date.now()
|
|
||||||
let lastInfo = await extractActivityInfo(page)
|
|
||||||
|
|
||||||
while (Date.now() - startedAt < timeoutMs) {
|
|
||||||
if (lastInfo?.role?.ready) {
|
|
||||||
return lastInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
lastInfo = await extractActivityInfo(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerActivityRoleRender(page) {
|
|
||||||
try {
|
|
||||||
await page.evaluate(() => {
|
|
||||||
const maybeRender = window.renderCdkey
|
|
||||||
|
|
||||||
if (typeof maybeRender === 'function') {
|
|
||||||
maybeRender()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
// ignore render trigger failures; polling will still inspect the page
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function extractActivityInfo(page) {
|
|
||||||
try {
|
|
||||||
return await page.evaluate(() => {
|
|
||||||
const asString = (value) => (typeof value === 'string' ? value.trim() : '')
|
|
||||||
const isVisible = (element) => {
|
|
||||||
if (!element) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = window.getComputedStyle(element)
|
|
||||||
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
||||||
}
|
|
||||||
|
|
||||||
const pick = (selector) => document.querySelector(selector)
|
|
||||||
const pickBest = (selector, predicate = null) => {
|
|
||||||
const matches = [...document.querySelectorAll(selector)]
|
|
||||||
const preferred = matches.find((element) => {
|
|
||||||
if (predicate && !predicate(element)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return isVisible(element)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (preferred) {
|
|
||||||
return preferred
|
|
||||||
}
|
|
||||||
|
|
||||||
if (predicate) {
|
|
||||||
return matches.find((element) => predicate(element)) || null
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches[0] || null
|
|
||||||
}
|
|
||||||
const textOf = (selector) => asString(pick(selector)?.textContent || '')
|
|
||||||
const inputOf = (selector) => {
|
|
||||||
const element = pickBest(
|
|
||||||
selector,
|
|
||||||
(node) => node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement)) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return asString(element.value || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleData = window.roleData && typeof window.roleData === 'object' ? window.roleData : {}
|
|
||||||
const roleSelector = pick('[id^="milo_role_selector_"]')
|
|
||||||
const roleNameText =
|
|
||||||
textOf('#role_name') ||
|
|
||||||
asString(roleSelector?.selectedOptions?.[0]?.textContent || '')
|
|
||||||
const verifyImg = pickBest('[id^="milo_verifyImg_"]', (node) => node instanceof HTMLImageElement)
|
|
||||||
const popup = pick('#pop2')
|
|
||||||
const cdkeyInput = pickBest(
|
|
||||||
'[id^="milo_cdkeyInfo_"]',
|
|
||||||
(node) =>
|
|
||||||
(node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
|
|
||||||
!node.disabled &&
|
|
||||||
node.type !== 'hidden',
|
|
||||||
)
|
|
||||||
const verifyInput = pickBest(
|
|
||||||
'[id^="milo_verifyInput_"]',
|
|
||||||
(node) =>
|
|
||||||
(node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
|
|
||||||
!node.disabled &&
|
|
||||||
node.type !== 'hidden',
|
|
||||||
)
|
|
||||||
const submitButton = pickBest(
|
|
||||||
'#milo_cdkey_submit, [id^="milo_cdkey_submit"]',
|
|
||||||
(node) => node instanceof HTMLElement && !node.hasAttribute('disabled'),
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
nickname: textOf('#login_nickname_span'),
|
|
||||||
role: {
|
|
||||||
ready: Boolean(window.isRole || roleNameText),
|
|
||||||
roleName: roleNameText,
|
|
||||||
roleId: asString(roleData.sRoleId),
|
|
||||||
area: asString(roleData.sArea || '36'),
|
|
||||||
partition: asString(roleData.sPartition),
|
|
||||||
platId: asString(roleData.sPlatId),
|
|
||||||
md5str: asString(roleData.sMd5str || roleData.md5str || roleData.sMdStr),
|
|
||||||
checkparam: asString(roleData.sCheckparam),
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
cdkeyInputId: cdkeyInput?.id || '',
|
|
||||||
cdkeyValue: inputOf('[id^="milo_cdkeyInfo_"]'),
|
|
||||||
verifyInputId: verifyInput?.id || '',
|
|
||||||
verifyValue: inputOf('[id^="milo_verifyInput_"]'),
|
|
||||||
verifyImgId: verifyImg?.id || '',
|
|
||||||
submitId: submitButton?.id || '',
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
visible: isVisible(verifyImg),
|
|
||||||
src: asString(verifyImg?.getAttribute('src')),
|
|
||||||
naturalWidth: Number(verifyImg?.naturalWidth || 0),
|
|
||||||
naturalHeight: Number(verifyImg?.naturalHeight || 0),
|
|
||||||
},
|
|
||||||
popup: {
|
|
||||||
visible: isVisible(popup),
|
|
||||||
text: textOf('#PopText'),
|
|
||||||
detail: textOf('#PopText2'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
nickname: '',
|
|
||||||
role: {
|
|
||||||
ready: false,
|
|
||||||
roleName: '',
|
|
||||||
roleId: '',
|
|
||||||
area: '',
|
|
||||||
partition: '',
|
|
||||||
platId: '',
|
|
||||||
md5str: '',
|
|
||||||
checkparam: '',
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
cdkeyInputId: '',
|
|
||||||
cdkeyValue: '',
|
|
||||||
verifyInputId: '',
|
|
||||||
verifyValue: '',
|
|
||||||
verifyImgId: '',
|
|
||||||
submitId: '',
|
|
||||||
},
|
|
||||||
verify: {
|
|
||||||
visible: false,
|
|
||||||
src: '',
|
|
||||||
naturalWidth: 0,
|
|
||||||
naturalHeight: 0,
|
|
||||||
},
|
|
||||||
popup: {
|
|
||||||
visible: false,
|
|
||||||
text: '',
|
|
||||||
detail: '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function cleanupExpiredTencentBrowserSessions() {
|
async function cleanupExpiredTencentBrowserSessions() {
|
||||||
const expired = [...sessions.values()].filter((session) => session.expiresAt <= Date.now())
|
const expired = [...sessions.values()].filter((session) => session.expiresAt <= Date.now())
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ import {
|
|||||||
parseViewportSpec,
|
parseViewportSpec,
|
||||||
} from './session-browser-config.js'
|
} from './session-browser-config.js'
|
||||||
import { normalizeTencentCookieMap } from './session-cookie-map.js'
|
import { normalizeTencentCookieMap } from './session-cookie-map.js'
|
||||||
|
import {
|
||||||
|
createEmptyActivityInfo,
|
||||||
|
ensureActivityInfoReady,
|
||||||
|
ensureLoggedInPresentation,
|
||||||
|
extractActivityInfo,
|
||||||
|
} from './session-activity.js'
|
||||||
import {
|
import {
|
||||||
captureQrImageWithRetry,
|
captureQrImageWithRetry,
|
||||||
ensureLoginTab,
|
ensureLoginTab,
|
||||||
} from './session-page.js'
|
} from './session-page.js'
|
||||||
import {
|
import { buildArtifactsPayload, buildRedeemPayload } from './session-payload.js'
|
||||||
buildArtifactsPayload,
|
|
||||||
buildRedeemPayload,
|
|
||||||
} from './session-payload.js'
|
|
||||||
import { ensureReviewScreenshot } from './session-review.js'
|
import { ensureReviewScreenshot } from './session-review.js'
|
||||||
import { resolveInitialQrSessionState } from './session-state.js'
|
import { resolveInitialQrSessionState } from './session-state.js'
|
||||||
import {
|
import {
|
||||||
@@ -293,3 +296,81 @@ test('captureQrImageWithRetry retries once after retryable error', async () => {
|
|||||||
{ type: 'capture', attempt: 2 },
|
{ type: 'capture', attempt: 2 },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ensureLoggedInPresentation reloads page when logged-in host state is stale', async () => {
|
||||||
|
const session = {
|
||||||
|
page: { marker: 'page' },
|
||||||
|
presentationSyncAttempts: 0,
|
||||||
|
}
|
||||||
|
const calls = []
|
||||||
|
let hostCall = 0
|
||||||
|
|
||||||
|
const result = await ensureLoggedInPresentation(session, {
|
||||||
|
credentialReady: true,
|
||||||
|
activityUrl: 'https://example.com/activity',
|
||||||
|
async extractHostState() {
|
||||||
|
hostCall += 1
|
||||||
|
return hostCall === 1
|
||||||
|
? { unloginVisible: true, loginedVisible: false, loginText: '' }
|
||||||
|
: { unloginVisible: false, loginedVisible: true, loginText: '已登录' }
|
||||||
|
},
|
||||||
|
async reloadActivityPageForPresentation(_page, activityUrl) {
|
||||||
|
calls.push(activityUrl)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(session.presentationSyncAttempts, 1)
|
||||||
|
assert.deepEqual(calls, ['https://example.com/activity'])
|
||||||
|
assert.deepEqual(result, {
|
||||||
|
reloaded: true,
|
||||||
|
hostState: { unloginVisible: false, loginedVisible: true, loginText: '已登录' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ensureActivityInfoReady triggers render and polls until role becomes ready', async () => {
|
||||||
|
const calls = []
|
||||||
|
const page = {
|
||||||
|
async waitForTimeout(ms) {
|
||||||
|
calls.push({ type: 'wait', ms })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const snapshots = [
|
||||||
|
{ role: { ready: false, roleName: '' } },
|
||||||
|
{ role: { ready: true, roleName: '测试角色' } },
|
||||||
|
]
|
||||||
|
let nowValue = 0
|
||||||
|
|
||||||
|
const result = await ensureActivityInfoReady(page, {
|
||||||
|
triggerRender: true,
|
||||||
|
timeoutMs: 1000,
|
||||||
|
async triggerActivityRoleRender() {
|
||||||
|
calls.push({ type: 'trigger' })
|
||||||
|
},
|
||||||
|
async extractActivityInfo() {
|
||||||
|
calls.push({ type: 'extract' })
|
||||||
|
return snapshots.shift()
|
||||||
|
},
|
||||||
|
now() {
|
||||||
|
nowValue += 300
|
||||||
|
return nowValue
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
{ type: 'trigger' },
|
||||||
|
{ type: 'extract' },
|
||||||
|
{ type: 'wait', ms: 400 },
|
||||||
|
{ type: 'extract' },
|
||||||
|
])
|
||||||
|
assert.deepEqual(result, { role: { ready: true, roleName: '测试角色' } })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('extractActivityInfo falls back to empty structure when page evaluation fails', async () => {
|
||||||
|
const page = {
|
||||||
|
async evaluate() {
|
||||||
|
throw new Error('page closed')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.deepEqual(await extractActivityInfo(page), createEmptyActivityInfo())
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user