后端迁移会话微信登录模块

This commit is contained in:
yml
2026-05-21 15:34:56 +08:00
parent 07a58343c9
commit 150de31c74
2 changed files with 75 additions and 15 deletions
@@ -1,12 +1,64 @@
// @ts-nocheck /// <reference lib="dom" />
import fs from 'node:fs/promises' import fs from 'node:fs/promises'
import { downloadRemoteQrImage, logBrowserSessionDebug, waitForFrame } from './session-login-shared.js' 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<void>
screenshot: (options: { path: string }) => Promise<void>
isVisible: () => Promise<boolean>
click: (options?: { timeout?: number }) => Promise<void>
evaluate: <T = unknown>(callback: (element: Element) => T | Promise<T>) => Promise<T>
}
type FrameLike = {
url: () => string
locator: (selector: string) => LocatorLike
waitForTimeout: (milliseconds: number) => Promise<void>
evaluate: <T = any>(callback: any) => Promise<T>
}
type PageLike = {
frames: () => FrameLike[]
waitForTimeout: (milliseconds: number) => Promise<void>
context: () => {
request: {
get: (url: string, options?: any) => Promise<{
ok: () => boolean
status: () => number
body: () => Promise<Buffer>
}>
}
}
}
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<void> {
try { try {
const frame = await waitForFrame( const frame = await waitForFrame<FrameLike>(
page, page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'), (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
10_000, 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<void> {
const qrLocator = await findWxQrLocator(page) const qrLocator = await findWxQrLocator(page)
const frame = await waitForFrame( const frame = await waitForFrame<FrameLike>(
page, page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'), (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
10_000, 10_000,
@@ -70,11 +122,11 @@ export async function captureWxQrImage(page, qrImagePath) {
const downloaded = await downloadRemoteQrImage(page, qrUrl, qrImagePath, frame.url(), 'wx') const downloaded = await downloadRemoteQrImage(page, qrUrl, qrImagePath, frame.url(), 'wx')
logBrowserSessionDebug('wx.capture.downloadResult', { logBrowserSessionDebug('wx.capture.downloadResult', {
qrUrl, qrUrl,
downloaded: Boolean(downloaded?.length), downloaded: Buffer.isBuffer(downloaded) && downloaded.length > 0,
qrImagePath, qrImagePath,
}) })
if (downloaded?.length) { if (Buffer.isBuffer(downloaded) && downloaded.length > 0) {
await fs.writeFile(qrImagePath, downloaded) await fs.writeFile(qrImagePath, downloaded)
return return
} }
@@ -86,10 +138,10 @@ export async function captureWxQrImage(page, qrImagePath) {
await qrLocator.screenshot({ path: qrImagePath }) await qrLocator.screenshot({ path: qrImagePath })
} }
export async function findWxQrLocator(page) { export async function findWxQrLocator(page: PageLike): Promise<LocatorLike> {
await ensureWxLoginReady(page) await ensureWxLoginReady(page)
const frame = await waitForFrame( const frame = await waitForFrame<FrameLike>(
page, page,
(item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'), (item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'),
) )
@@ -109,7 +161,7 @@ export async function findWxQrLocator(page) {
return locator return locator
} }
export async function extractWxQrState(page) { export async function extractWxQrState(page: Pick<PageLike, 'frames'>): Promise<WxQrState> {
const frame = page.frames().find((item) => item.url().includes('open.weixin.qq.com/connect/qrconnect')) const frame = page.frames().find((item) => item.url().includes('open.weixin.qq.com/connect/qrconnect'))
if (!frame) { if (!frame) {
@@ -146,10 +198,10 @@ export async function extractWxQrState(page) {
} }
} }
async function inspectWxLoginView(frame) { async function inspectWxLoginView(frame: FrameLike): Promise<WxLoginViewState> {
return frame return frame
.evaluate(() => { .evaluate(() => {
const isVisible = (element) => { const isVisible = (element: Element | null) => {
if (!(element instanceof HTMLElement)) { if (!(element instanceof HTMLElement)) {
return false return false
} }
@@ -203,7 +255,7 @@ async function inspectWxLoginView(frame) {
})) }))
} }
async function switchWxQuickLoginToQr(frame) { async function switchWxQuickLoginToQr(frame: FrameLike): Promise<boolean> {
const switcherCandidates = [ const switcherCandidates = [
frame.locator('.js_switchToNormal:visible').first(), frame.locator('.js_switchToNormal:visible').first(),
frame.locator('.js_switchToNormal').first(), frame.locator('.js_switchToNormal').first(),
@@ -279,10 +331,10 @@ async function switchWxQuickLoginToQr(frame) {
.catch(() => false) .catch(() => false)
} }
async function resolveWxQrImageUrl(frame) { async function resolveWxQrImageUrl(frame: FrameLike): Promise<string> {
return frame return frame
.evaluate(() => { .evaluate(() => {
const isVisible = (element) => { const isVisible = (element: Element | null) => {
if (!(element instanceof HTMLElement)) { if (!(element instanceof HTMLElement)) {
return false return false
} }
@@ -480,6 +480,14 @@
- `npm run typecheck` - `npm run typecheck`
- `npm run build` - `npm run build`
- `npm test` 共 139 个用例通过 - `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 个用例通过
## 下一步建议 ## 下一步建议