diff --git a/apps/backend/src/services/session/session-cookie-map.js b/apps/backend/src/services/session/session-cookie-map.ts similarity index 63% rename from apps/backend/src/services/session/session-cookie-map.js rename to apps/backend/src/services/session/session-cookie-map.ts index b4957c31..f39da4c8 100644 --- a/apps/backend/src/services/session/session-cookie-map.js +++ b/apps/backend/src/services/session/session-cookie-map.ts @@ -5,8 +5,19 @@ const TENCENT_COOKIE_URLS = [ 'https://milo.qq.com', ] -export function normalizeTencentCookieMap(cookies = []) { - const cookieMap = {} +type BrowserCookie = { + name?: string + value?: string +} + +type BrowserContextLike = { + cookies: (urls?: string[]) => Promise +} + +export type TencentCookieMap = Record + +export function normalizeTencentCookieMap(cookies: BrowserCookie[] = []): TencentCookieMap { + const cookieMap: TencentCookieMap = {} for (const cookie of cookies) { if (!cookie?.name) { @@ -32,7 +43,7 @@ export function normalizeTencentCookieMap(cookies = []) { return cookieMap } -export async function loadCookieMapFromContext(browserContext) { +export async function loadCookieMapFromContext(browserContext: BrowserContextLike): Promise { const cookies = await browserContext.cookies(TENCENT_COOKIE_URLS) return normalizeTencentCookieMap(cookies) } diff --git a/apps/backend/src/services/session/session-login-shared.js b/apps/backend/src/services/session/session-login-shared.ts similarity index 58% rename from apps/backend/src/services/session/session-login-shared.js rename to apps/backend/src/services/session/session-login-shared.ts index 2c36bf0a..d59ca960 100644 --- a/apps/backend/src/services/session/session-login-shared.js +++ b/apps/backend/src/services/session/session-login-shared.ts @@ -1,11 +1,33 @@ -// @ts-nocheck - import { runtimeConfig } from '../../config/runtime.js' import { logDebug } from '../../utils/logger.js' const SESSION_DEBUG_ENABLED = Boolean(runtimeConfig.session.debug) -export function logBrowserSessionDebug(scope, detail) { +type FrameLike = unknown + +type PageWithFrames = { + frames: () => FrameLike[] + waitForTimeout: (milliseconds: number) => Promise +} + +type QrDownloadResponse = { + ok: () => boolean + status: () => number + body: () => Promise +} + +type PageWithRequestContext = { + context: () => { + request: { + get: (url: string, options?: { + failOnStatusCode?: boolean + headers?: Record + }) => Promise + } + } +} + +export function logBrowserSessionDebug(scope: string, detail?: unknown): void { if (!SESSION_DEBUG_ENABLED) { return } @@ -13,13 +35,17 @@ export function logBrowserSessionDebug(scope, detail) { logDebug('[browser/session]', scope, detail) } -export async function waitForFrame(page, predicate, timeoutMs = 15_000) { +export async function waitForFrame( + page: PageWithFrames, + predicate: (frame: TFrame) => boolean, + timeoutMs = 15_000, +): Promise { const startedAt = Date.now() while (Date.now() - startedAt < timeoutMs) { - const matched = page.frames().find((frame) => { + const matched = page.frames().find((frame): frame is TFrame => { try { - return predicate(frame) + return predicate(frame as TFrame) } catch { return false } @@ -35,7 +61,13 @@ export async function waitForFrame(page, predicate, timeoutMs = 15_000) { throw new Error('未找到登录二维码 frame') } -export async function downloadRemoteQrImage(page, qrUrl, qrImagePath, referer = '', loginType = 'unknown') { +export async function downloadRemoteQrImage( + page: PageWithRequestContext, + qrUrl: string, + qrImagePath: string, + referer = '', + loginType = 'unknown', +): Promise { try { const response = await page.context().request.get(qrUrl, { failOnStatusCode: false, @@ -68,7 +100,7 @@ export async function downloadRemoteQrImage(page, qrUrl, qrImagePath, referer = } } -export function isRetryableQrCaptureError(error) { +export function isRetryableQrCaptureError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error || '') return ( diff --git a/apps/backend/src/services/session/session-review.js b/apps/backend/src/services/session/session-review.ts similarity index 62% rename from apps/backend/src/services/session/session-review.js rename to apps/backend/src/services/session/session-review.ts index 5c6cd30a..bcef4bcf 100644 --- a/apps/backend/src/services/session/session-review.js +++ b/apps/backend/src/services/session/session-review.ts @@ -2,7 +2,30 @@ import path from 'node:path' import { logBrowserSessionDebug } from './session-login-shared.js' -export function resolveReviewSignature(session, activityInfo) { +type BrowserPageLike = { + screenshot: (options: { path: string; fullPage?: boolean }) => Promise +} + +type ReviewSession = { + sessionId: string + loginType: string + sessionDir: string + page: BrowserPageLike + lastReview?: { + screenshotPath?: string + signature?: string + [key: string]: unknown + } | null +} + +type ActivityInfoForReview = { + role?: { + roleId?: string + roleName?: string + } +} + +export function resolveReviewSignature(session: Pick, activityInfo: ActivityInfoForReview) { const roleId = String(activityInfo?.role?.roleId || '').trim() const roleName = String(activityInfo?.role?.roleName || '').trim() @@ -13,7 +36,7 @@ export function resolveReviewSignature(session, activityInfo) { } } -export async function ensureReviewScreenshot(session, activityInfo) { +export async function ensureReviewScreenshot(session: ReviewSession, activityInfo: ActivityInfoForReview): Promise { const { roleId, roleName, signature } = resolveReviewSignature(session, activityInfo) if (session.lastReview?.screenshotPath && session.lastReview?.signature === signature) { diff --git a/apps/backend/src/services/session/session-service-config.js b/apps/backend/src/services/session/session-service-config.ts similarity index 98% rename from apps/backend/src/services/session/session-service-config.js rename to apps/backend/src/services/session/session-service-config.ts index 8e31f658..80c0e88e 100644 --- a/apps/backend/src/services/session/session-service-config.js +++ b/apps/backend/src/services/session/session-service-config.ts @@ -1,5 +1,3 @@ -// @ts-check - import path from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md index 0972ccc5..9c385db8 100644 --- a/docs/backend-typescript-migration-plan.md +++ b/docs/backend-typescript-migration-plan.md @@ -436,6 +436,17 @@ - `npm run typecheck` - `npm run build` - `npm test` 共 139 个用例通过 +86. 腾讯浏览器会话 cookie / review / 登录共享 / 服务配置模块迁移到 `.ts`: + - `src/services/session/session-cookie-map.ts` + - `src/services/session/session-review.ts` + - `src/services/session/session-login-shared.ts` + - `src/services/session/session-service-config.ts` +87. cookie map、review 签名与截图、二维码下载、frame 等待、浏览器会话配置常量已显式类型化 +88. Docker 内验证通过: + - `src/services/session/session.test.js` 共 14 个用例通过 + - `npm run typecheck` + - `npm run build` + - `npm test` 共 139 个用例通过 ## 下一步建议