From c7726dde321c2efa00970936443dfe9bf616523a Mon Sep 17 00:00:00 2001 From: yml Date: Thu, 21 May 2026 15:30:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E8=BF=81=E7=A7=BB=E4=BC=9A?= =?UTF-8?q?=E8=AF=9D=E9=A1=B5=E9=9D=A2=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{session-page.js => session-page.ts} | 94 ++++++++++++++----- docs/backend-typescript-migration-plan.md | 8 ++ 2 files changed, 81 insertions(+), 21 deletions(-) rename apps/backend/src/services/session/{session-page.js => session-page.ts} (65%) diff --git a/apps/backend/src/services/session/session-page.js b/apps/backend/src/services/session/session-page.ts similarity index 65% rename from apps/backend/src/services/session/session-page.js rename to apps/backend/src/services/session/session-page.ts index 03bc3093..4abcd16b 100644 --- a/apps/backend/src/services/session/session-page.js +++ b/apps/backend/src/services/session/session-page.ts @@ -1,4 +1,4 @@ -// @ts-nocheck +/// import fs from 'node:fs/promises' import path from 'node:path' @@ -7,16 +7,68 @@ import { isRetryableQrCaptureError, logBrowserSessionDebug } from './session-log import { captureQqQrImage, ensureQqLoginReady, extractQqQrState } from './session-qq.js' import { captureWxQrImage, ensureWxLoginReady, extractWxQrState } from './session-wx.js' +type LoginType = 'qq' | 'wx' | string + +type LocatorLike = { + count: () => Promise + first: () => LocatorLike + click: (options?: { timeout?: number }) => Promise +} + +type BrowserPageLike = { + locator: (selector: string) => LocatorLike + waitForTimeout: (milliseconds: number) => Promise + evaluate: (callback: any) => Promise + reload: (options?: { waitUntil?: string }) => Promise + goto: (url: string, options?: { waitUntil?: string }) => Promise +} + +type SessionForQrCapture = { + page: BrowserPageLike + loginType: LoginType + sessionDir: string + qrImagePath?: string + qrImageBase64?: string + qrUpdatedAt?: string + updatedAt?: string +} + +type CaptureSessionQrOptions = { + ensureLoginReady?: boolean + ensureLoginTab?: typeof ensureLoginTab + captureQrImageWithRetry?: typeof captureQrImageWithRetry + readFile?: (path: string, encoding: BufferEncoding) => Promise + now?: () => string +} + +type CaptureQrImageWithRetryOptions = { + captureQqQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise + captureWxQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise + ensureLoginTab?: typeof ensureLoginTab + isRetryableQrCaptureError?: (error: unknown) => boolean + logBrowserSessionDebug?: (scope: string, detail?: unknown) => void +} + +type EnsureLoginTabOptions = { + ensureQqLoginReady?: (page: BrowserPageLike) => Promise + ensureWxLoginReady?: (page: BrowserPageLike) => Promise +} + +type ExtractQrStateOptions = { + extractQqQrState?: (page: BrowserPageLike) => Promise + extractWxQrState?: (page: BrowserPageLike) => Promise +} + export async function captureSessionQr( - session, + session: SessionForQrCapture, { ensureLoginReady = true, ensureLoginTab: ensureLoginTabImpl = ensureLoginTab, captureQrImageWithRetry: captureQrImageWithRetryImpl = captureQrImageWithRetry, readFile = fs.readFile, now = () => new Date().toISOString(), - } = {}, -) { + }: CaptureSessionQrOptions = {}, +): Promise { if (ensureLoginReady) { await ensureLoginTabImpl(session.page, session.loginType) } @@ -33,19 +85,19 @@ export async function captureSessionQr( } export async function captureQrImageWithRetry( - page, - loginType, - qrImagePath, + page: BrowserPageLike, + loginType: LoginType, + qrImagePath: string, { captureQqQrImage: captureQqQrImageImpl = captureQqQrImage, captureWxQrImage: captureWxQrImageImpl = captureWxQrImage, ensureLoginTab: ensureLoginTabImpl = ensureLoginTab, isRetryableQrCaptureError: isRetryableQrCaptureErrorImpl = isRetryableQrCaptureError, logBrowserSessionDebug: logBrowserSessionDebugImpl = logBrowserSessionDebug, - } = {}, -) { + }: CaptureQrImageWithRetryOptions = {}, +): Promise { const maxAttempts = loginType === 'qq' ? 4 : 3 - let lastError = null + let lastError: unknown = null for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { try { @@ -94,13 +146,13 @@ export async function captureQrImageWithRetry( } export async function ensureLoginTab( - page, - loginType, + page: BrowserPageLike, + loginType: LoginType, { ensureQqLoginReady: ensureQqLoginReadyImpl = ensureQqLoginReady, ensureWxLoginReady: ensureWxLoginReadyImpl = ensureWxLoginReady, - } = {}, -) { + }: EnsureLoginTabOptions = {}, +): Promise { const tabSelector = loginType === 'wx' ? '.wx-tab' : '.qc-tab' const loginButton = page.locator('#unlogin') let interacted = false @@ -139,9 +191,9 @@ export async function ensureLoginTab( } } -export async function extractHostState(page) { +export async function extractHostState(page: BrowserPageLike) { return page.evaluate(() => { - const isVisible = (element) => { + const isVisible = (element: Element | null) => { if (!element) { return false } @@ -162,7 +214,7 @@ export async function extractHostState(page) { }) } -export async function reloadActivityPageForPresentation(page, activityUrl) { +export async function reloadActivityPageForPresentation(page: BrowserPageLike, activityUrl: string): Promise { try { await page.reload({ waitUntil: 'domcontentloaded' }) } catch { @@ -173,13 +225,13 @@ export async function reloadActivityPageForPresentation(page, activityUrl) { } export async function extractQrState( - page, - loginType, + page: BrowserPageLike, + loginType: LoginType, { extractQqQrState: extractQqQrStateImpl = extractQqQrState, extractWxQrState: extractWxQrStateImpl = extractWxQrState, - } = {}, -) { + }: ExtractQrStateOptions = {}, +): Promise { if (loginType === 'qq') { return extractQqQrStateImpl(page) } diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 102dbc9e..c87690ea 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -464,6 +464,14 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 139 个用例通过 +95. 腾讯浏览器会话页面模块迁移到 `.ts`: + - `src/services/session/session-page.ts` +96. 二维码截图重试、登录 tab 切换、host 状态提取、页面刷新、QQ/WX 二维码状态分发已显式类型化 +97. Docker 内验证通过: + - `src/services/session/session.test.js` 共 14 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 139 个用例通过 ## 下一步建议