diff --git a/apps/backend/src/services/session/session-wx.js b/apps/backend/src/services/session/session-wx.ts
similarity index 78%
rename from apps/backend/src/services/session/session-wx.js
rename to apps/backend/src/services/session/session-wx.ts
index 09a38583..e66fcab7 100644
--- a/apps/backend/src/services/session/session-wx.js
+++ b/apps/backend/src/services/session/session-wx.ts
@@ -1,12 +1,64 @@
-// @ts-nocheck
+///
import fs from 'node:fs/promises'
import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js'
-export async function ensureWxLoginReady(page) {
+type LocatorLike = {
+ first: () => LocatorLike
+ waitFor: (options?: { state?: string; timeout?: number }) => Promise
+ screenshot: (options: { path: string }) => Promise
+ isVisible: () => Promise
+ click: (options?: { timeout?: number }) => Promise
+ evaluate: (callback: (element: Element) => T | Promise) => Promise
+}
+
+type FrameLike = {
+ url: () => string
+ locator: (selector: string) => LocatorLike
+ waitForTimeout: (milliseconds: number) => Promise
+ evaluate: (callback: any) => Promise
+}
+
+type PageLike = {
+ frames: () => FrameLike[]
+ waitForTimeout: (milliseconds: number) => Promise
+ context: () => {
+ request: {
+ get: (url: string, options?: any) => Promise<{
+ ok: () => boolean
+ status: () => number
+ body: () => Promise
+ }>
+ }
+ }
+}
+
+type WxLoginViewState = {
+ qrVisible: boolean
+ quickLoginVisible: boolean
+ switchToNormalVisible: boolean
+ qrCandidates: Array<{
+ index: number
+ visible: boolean
+ src: string
+ naturalWidth: number
+ }>
+ bodyText: string
+}
+
+type WxQrState = {
+ visible: boolean
+ qrVisible?: boolean
+ scanned: boolean
+ expired: boolean
+ message: string
+ frameUrl?: string
+}
+
+export async function ensureWxLoginReady(page: PageLike): Promise {
try {
- const frame = await waitForFrame(
+ const frame = await waitForFrame(
page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
10_000,
@@ -52,9 +104,9 @@ export async function ensureWxLoginReady(page) {
}
}
-export async function captureWxQrImage(page, qrImagePath) {
+export async function captureWxQrImage(page: PageLike, qrImagePath: string): Promise {
const qrLocator = await findWxQrLocator(page)
- const frame = await waitForFrame(
+ const frame = await waitForFrame(
page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
10_000,
@@ -70,11 +122,11 @@ export async function captureWxQrImage(page, qrImagePath) {
const downloaded = await downloadRemoteQrImage(page, qrUrl, qrImagePath, frame.url(), 'wx')
logBrowserSessionDebug('wx.capture.downloadResult', {
qrUrl,
- downloaded: Boolean(downloaded?.length),
+ downloaded: Buffer.isBuffer(downloaded) && downloaded.length > 0,
qrImagePath,
})
- if (downloaded?.length) {
+ if (Buffer.isBuffer(downloaded) && downloaded.length > 0) {
await fs.writeFile(qrImagePath, downloaded)
return
}
@@ -86,10 +138,10 @@ export async function captureWxQrImage(page, qrImagePath) {
await qrLocator.screenshot({ path: qrImagePath })
}
-export async function findWxQrLocator(page) {
+export async function findWxQrLocator(page: PageLike): Promise {
await ensureWxLoginReady(page)
- const frame = await waitForFrame(
+ const frame = await waitForFrame(
page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
)
@@ -109,7 +161,7 @@ export async function findWxQrLocator(page) {
return locator
}
-export async function extractWxQrState(page) {
+export async function extractWxQrState(page: Pick): Promise {
const frame = page.frames().find((item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'))
if (!frame) {
@@ -146,10 +198,10 @@ export async function extractWxQrState(page) {
}
}
-async function inspectWxLoginView(frame) {
+async function inspectWxLoginView(frame: FrameLike): Promise {
return frame
.evaluate(() => {
- const isVisible = (element) => {
+ const isVisible = (element: Element | null) => {
if (!(element instanceof HTMLElement)) {
return false
}
@@ -203,7 +255,7 @@ async function inspectWxLoginView(frame) {
}))
}
-async function switchWxQuickLoginToQr(frame) {
+async function switchWxQuickLoginToQr(frame: FrameLike): Promise {
const switcherCandidates = [
frame.locator('.js_switchToNormal:visible').first(),
frame.locator('.js_switchToNormal').first(),
@@ -279,10 +331,10 @@ async function switchWxQuickLoginToQr(frame) {
.catch(() => false)
}
-async function resolveWxQrImageUrl(frame) {
+async function resolveWxQrImageUrl(frame: FrameLike): Promise {
return frame
.evaluate(() => {
- const isVisible = (element) => {
+ const isVisible = (element: Element | null) => {
if (!(element instanceof HTMLElement)) {
return false
}
diff --git a/docs/backend-typescript-migration-plan.md b/docs/backend-typescript-migration-plan.md
index 1e7f0401..0caa7fe7 100644
--- a/docs/backend-typescript-migration-plan.md
+++ b/docs/backend-typescript-migration-plan.md
@@ -480,6 +480,14 @@
- `npm run typecheck`
- `npm run build`
- `npm test` 共 139 个用例通过
+101. 腾讯浏览器 WX 登录模块迁移到 `.ts`:
+ - `src/services/session/session-wx.ts`
+102. WX 登录 iframe、快捷登录切换、二维码候选状态、远程下载 fallback、二维码 locator 与扫码状态已显式类型化
+103. Docker 内验证通过:
+ - `src/services/session/session.test.js` 共 14 个用例通过
+ - `npm run typecheck`
+ - `npm run build`
+ - `npm test` 共 139 个用例通过
## 下一步建议