diff --git a/apps/backend/src/services/session/session-activity.js b/apps/backend/src/services/session/session-activity.ts similarity index 68% rename from apps/backend/src/services/session/session-activity.js rename to apps/backend/src/services/session/session-activity.ts index 5572f6fa..5010cf5a 100644 --- a/apps/backend/src/services/session/session-activity.js +++ b/apps/backend/src/services/session/session-activity.ts @@ -1,9 +1,83 @@ -// @ts-nocheck +/// import { extractHostState, reloadActivityPageForPresentation } from './session-page.js' import { shouldRefreshLoggedInPresentation } from './session-state.js' -export function createEmptyActivityInfo() { +type BrowserPageLike = { + evaluate: (callback: any) => Promise + waitForTimeout: (milliseconds: number) => Promise +} + +type HostState = { + loginText?: string + unloginVisible?: boolean + loginedVisible?: boolean +} + +export type ActivityInfo = { + nickname: string + role: { + ready: boolean + roleName: string + roleId: string + area: string + partition: string + platId: string + md5str: string + checkparam: string + } + form: { + cdkeyInputId: string + cdkeyValue: string + verifyInputId: string + verifyValue: string + verifyImgId: string + submitId: string + } + verify: { + visible: boolean + src: string + naturalWidth: number + naturalHeight: number + } + popup: { + visible: boolean + text: string + detail: string + } +} + +type SessionForPresentation = { + page: BrowserPageLike + presentationSyncAttempts: number +} + +type EnsureLoggedInPresentationOptions = { + hostState?: HostState | null + credentialReady?: boolean + activityUrl?: string + extractHostState?: (page: BrowserPageLike) => Promise + reloadActivityPageForPresentation?: (page: BrowserPageLike, activityUrl: string) => Promise + shouldRefreshLoggedInPresentation?: (hostState: HostState) => boolean +} + +type EnsureActivityInfoReadyOptions = { + triggerRender?: boolean + timeoutMs?: number + extractActivityInfo?: (page: BrowserPageLike) => Promise + triggerActivityRoleRender?: (page: BrowserPageLike) => Promise + now?: () => number +} + +declare global { + interface Window { + renderCdkey?: () => void + roleData?: Record + isRole?: unknown + } +} + +export function createEmptyActivityInfo(): ActivityInfo { return { nickname: '', role: { @@ -39,7 +113,7 @@ export function createEmptyActivityInfo() { } export async function ensureLoggedInPresentation( - session, + session: SessionForPresentation, { hostState = null, credentialReady = false, @@ -47,8 +121,8 @@ export async function ensureLoggedInPresentation( extractHostState: extractHostStateImpl = extractHostState, reloadActivityPageForPresentation: reloadActivityPageForPresentationImpl = reloadActivityPageForPresentation, shouldRefreshLoggedInPresentation: shouldRefreshLoggedInPresentationImpl = shouldRefreshLoggedInPresentation, - } = {}, -) { + }: EnsureLoggedInPresentationOptions = {}, +): Promise<{ reloaded: boolean; hostState: HostState }> { let nextHostState = hostState || (await extractHostStateImpl(session.page)) if (!credentialReady || !shouldRefreshLoggedInPresentationImpl(nextHostState)) { @@ -76,15 +150,15 @@ export async function ensureLoggedInPresentation( } export async function ensureActivityInfoReady( - page, + page: BrowserPageLike, { triggerRender = false, timeoutMs = 8_000, extractActivityInfo: extractActivityInfoImpl = extractActivityInfo, triggerActivityRoleRender: triggerActivityRoleRenderImpl = triggerActivityRoleRender, now = () => Date.now(), - } = {}, -) { + }: EnsureActivityInfoReadyOptions = {}, +): Promise { if (triggerRender) { await triggerActivityRoleRenderImpl(page) } @@ -104,7 +178,7 @@ export async function ensureActivityInfoReady( return lastInfo } -export async function triggerActivityRoleRender(page) { +export async function triggerActivityRoleRender(page: BrowserPageLike): Promise { try { await page.evaluate(() => { const maybeRender = window.renderCdkey @@ -118,11 +192,11 @@ export async function triggerActivityRoleRender(page) { } } -export async function extractActivityInfo(page) { +export async function extractActivityInfo(page: BrowserPageLike): Promise { try { return await page.evaluate(() => { - const asString = (value) => (typeof value === 'string' ? value.trim() : '') - const isVisible = (element) => { + const asString = (value: unknown) => (typeof value === 'string' ? value.trim() : '') + const isVisible = (element: Element | null) => { if (!element) { return false } @@ -131,8 +205,11 @@ export async function extractActivityInfo(page) { return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' } - const pick = (selector) => document.querySelector(selector) - const pickBest = (selector, predicate = null) => { + const pick = (selector: string) => document.querySelector(selector) + const pickBest = ( + selector: string, + predicate: ((element: Element) => boolean) | null = null, + ): Element | null => { const matches = [...document.querySelectorAll(selector)] const preferred = matches.find((element) => { if (predicate && !predicate(element)) { @@ -170,7 +247,7 @@ export async function extractActivityInfo(page) { const roleSelector = pick('[id^="milo_role_selector_"]') const roleNameText = textOf('#role_name') || - asString(roleSelector?.selectedOptions?.[0]?.textContent || '') + asString(roleSelector instanceof HTMLSelectElement ? roleSelector.selectedOptions?.[0]?.textContent || '' : '') const verifyImg = pickBest('[id^="milo_verifyImg_"]', (node) => node instanceof HTMLImageElement) const popup = pick('#pop2') const cdkeyInput = pickBest( @@ -215,8 +292,8 @@ export async function extractActivityInfo(page) { verify: { visible: isVisible(verifyImg), src: asString(verifyImg?.getAttribute('src')), - naturalWidth: Number(verifyImg?.naturalWidth || 0), - naturalHeight: Number(verifyImg?.naturalHeight || 0), + naturalWidth: verifyImg instanceof HTMLImageElement ? Number(verifyImg.naturalWidth || 0) : 0, + naturalHeight: verifyImg instanceof HTMLImageElement ? Number(verifyImg.naturalHeight || 0) : 0, }, popup: { visible: isVisible(popup), diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 0caa7fe7..08930aea 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -488,6 +488,14 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 139 个用例通过 +104. 腾讯浏览器会话 activity 信息模块迁移到 `.ts`: + - `src/services/session/session-activity.ts` +105. 活动角色 / 表单 / 验证码 / 弹窗信息结构、登录态 presentation 同步、角色渲染触发与轮询已显式类型化 +106. Docker 内验证通过: + - `src/services/session/session.test.js` 共 14 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 139 个用例通过 ## 下一步建议