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 个用例通过
## 下一步建议