后端迁移会话页面模块
This commit is contained in:
+73
-21
@@ -1,4 +1,4 @@
|
|||||||
// @ts-nocheck
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
@@ -7,16 +7,68 @@ import { isRetryableQrCaptureError, logBrowserSessionDebug } from './session-log
|
|||||||
import { captureQqQrImage, ensureQqLoginReady, extractQqQrState } from './session-qq.js'
|
import { captureQqQrImage, ensureQqLoginReady, extractQqQrState } from './session-qq.js'
|
||||||
import { captureWxQrImage, ensureWxLoginReady, extractWxQrState } from './session-wx.js'
|
import { captureWxQrImage, ensureWxLoginReady, extractWxQrState } from './session-wx.js'
|
||||||
|
|
||||||
|
type LoginType = 'qq' | 'wx' | string
|
||||||
|
|
||||||
|
type LocatorLike = {
|
||||||
|
count: () => Promise<number>
|
||||||
|
first: () => LocatorLike
|
||||||
|
click: (options?: { timeout?: number }) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
type BrowserPageLike = {
|
||||||
|
locator: (selector: string) => LocatorLike
|
||||||
|
waitForTimeout: (milliseconds: number) => Promise<void>
|
||||||
|
evaluate: <T = any>(callback: any) => Promise<T>
|
||||||
|
reload: (options?: { waitUntil?: string }) => Promise<unknown>
|
||||||
|
goto: (url: string, options?: { waitUntil?: string }) => Promise<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string>
|
||||||
|
now?: () => string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CaptureQrImageWithRetryOptions = {
|
||||||
|
captureQqQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise<unknown>
|
||||||
|
captureWxQrImage?: (page: BrowserPageLike, qrImagePath: string) => Promise<unknown>
|
||||||
|
ensureLoginTab?: typeof ensureLoginTab
|
||||||
|
isRetryableQrCaptureError?: (error: unknown) => boolean
|
||||||
|
logBrowserSessionDebug?: (scope: string, detail?: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnsureLoginTabOptions = {
|
||||||
|
ensureQqLoginReady?: (page: BrowserPageLike) => Promise<unknown>
|
||||||
|
ensureWxLoginReady?: (page: BrowserPageLike) => Promise<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtractQrStateOptions = {
|
||||||
|
extractQqQrState?: (page: BrowserPageLike) => Promise<unknown>
|
||||||
|
extractWxQrState?: (page: BrowserPageLike) => Promise<unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export async function captureSessionQr(
|
export async function captureSessionQr(
|
||||||
session,
|
session: SessionForQrCapture,
|
||||||
{
|
{
|
||||||
ensureLoginReady = true,
|
ensureLoginReady = true,
|
||||||
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
||||||
captureQrImageWithRetry: captureQrImageWithRetryImpl = captureQrImageWithRetry,
|
captureQrImageWithRetry: captureQrImageWithRetryImpl = captureQrImageWithRetry,
|
||||||
readFile = fs.readFile,
|
readFile = fs.readFile,
|
||||||
now = () => new Date().toISOString(),
|
now = () => new Date().toISOString(),
|
||||||
} = {},
|
}: CaptureSessionQrOptions = {},
|
||||||
) {
|
): Promise<void> {
|
||||||
if (ensureLoginReady) {
|
if (ensureLoginReady) {
|
||||||
await ensureLoginTabImpl(session.page, session.loginType)
|
await ensureLoginTabImpl(session.page, session.loginType)
|
||||||
}
|
}
|
||||||
@@ -33,19 +85,19 @@ export async function captureSessionQr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function captureQrImageWithRetry(
|
export async function captureQrImageWithRetry(
|
||||||
page,
|
page: BrowserPageLike,
|
||||||
loginType,
|
loginType: LoginType,
|
||||||
qrImagePath,
|
qrImagePath: string,
|
||||||
{
|
{
|
||||||
captureQqQrImage: captureQqQrImageImpl = captureQqQrImage,
|
captureQqQrImage: captureQqQrImageImpl = captureQqQrImage,
|
||||||
captureWxQrImage: captureWxQrImageImpl = captureWxQrImage,
|
captureWxQrImage: captureWxQrImageImpl = captureWxQrImage,
|
||||||
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
ensureLoginTab: ensureLoginTabImpl = ensureLoginTab,
|
||||||
isRetryableQrCaptureError: isRetryableQrCaptureErrorImpl = isRetryableQrCaptureError,
|
isRetryableQrCaptureError: isRetryableQrCaptureErrorImpl = isRetryableQrCaptureError,
|
||||||
logBrowserSessionDebug: logBrowserSessionDebugImpl = logBrowserSessionDebug,
|
logBrowserSessionDebug: logBrowserSessionDebugImpl = logBrowserSessionDebug,
|
||||||
} = {},
|
}: CaptureQrImageWithRetryOptions = {},
|
||||||
) {
|
): Promise<void> {
|
||||||
const maxAttempts = loginType === 'qq' ? 4 : 3
|
const maxAttempts = loginType === 'qq' ? 4 : 3
|
||||||
let lastError = null
|
let lastError: unknown = null
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
@@ -94,13 +146,13 @@ export async function captureQrImageWithRetry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureLoginTab(
|
export async function ensureLoginTab(
|
||||||
page,
|
page: BrowserPageLike,
|
||||||
loginType,
|
loginType: LoginType,
|
||||||
{
|
{
|
||||||
ensureQqLoginReady: ensureQqLoginReadyImpl = ensureQqLoginReady,
|
ensureQqLoginReady: ensureQqLoginReadyImpl = ensureQqLoginReady,
|
||||||
ensureWxLoginReady: ensureWxLoginReadyImpl = ensureWxLoginReady,
|
ensureWxLoginReady: ensureWxLoginReadyImpl = ensureWxLoginReady,
|
||||||
} = {},
|
}: EnsureLoginTabOptions = {},
|
||||||
) {
|
): Promise<void> {
|
||||||
const tabSelector = loginType === 'wx' ? '.wx-tab' : '.qc-tab'
|
const tabSelector = loginType === 'wx' ? '.wx-tab' : '.qc-tab'
|
||||||
const loginButton = page.locator('#unlogin')
|
const loginButton = page.locator('#unlogin')
|
||||||
let interacted = false
|
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(() => {
|
return page.evaluate(() => {
|
||||||
const isVisible = (element) => {
|
const isVisible = (element: Element | null) => {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return false
|
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<void> {
|
||||||
try {
|
try {
|
||||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||||
} catch {
|
} catch {
|
||||||
@@ -173,13 +225,13 @@ export async function reloadActivityPageForPresentation(page, activityUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function extractQrState(
|
export async function extractQrState(
|
||||||
page,
|
page: BrowserPageLike,
|
||||||
loginType,
|
loginType: LoginType,
|
||||||
{
|
{
|
||||||
extractQqQrState: extractQqQrStateImpl = extractQqQrState,
|
extractQqQrState: extractQqQrStateImpl = extractQqQrState,
|
||||||
extractWxQrState: extractWxQrStateImpl = extractWxQrState,
|
extractWxQrState: extractWxQrStateImpl = extractWxQrState,
|
||||||
} = {},
|
}: ExtractQrStateOptions = {},
|
||||||
) {
|
): Promise<unknown> {
|
||||||
if (loginType === 'qq') {
|
if (loginType === 'qq') {
|
||||||
return extractQqQrStateImpl(page)
|
return extractQqQrStateImpl(page)
|
||||||
}
|
}
|
||||||
@@ -464,6 +464,14 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 139 个用例通过
|
- `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 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user