后端迁移会话支撑模块
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { logDebug } from '../../utils/logger.js'
|
||||
|
||||
const SESSION_DEBUG_ENABLED = Boolean(runtimeConfig.session.debug)
|
||||
|
||||
type FrameLike = unknown
|
||||
|
||||
type PageWithFrames = {
|
||||
frames: () => FrameLike[]
|
||||
waitForTimeout: (milliseconds: number) => Promise<void>
|
||||
}
|
||||
|
||||
type QrDownloadResponse = {
|
||||
ok: () => boolean
|
||||
status: () => number
|
||||
body: () => Promise<Buffer>
|
||||
}
|
||||
|
||||
type PageWithRequestContext = {
|
||||
context: () => {
|
||||
request: {
|
||||
get: (url: string, options?: {
|
||||
failOnStatusCode?: boolean
|
||||
headers?: Record<string, string>
|
||||
}) => Promise<QrDownloadResponse>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function logBrowserSessionDebug(scope: string, detail?: unknown): void {
|
||||
if (!SESSION_DEBUG_ENABLED) {
|
||||
return
|
||||
}
|
||||
|
||||
logDebug('[browser/session]', scope, detail)
|
||||
}
|
||||
|
||||
export async function waitForFrame<TFrame extends FrameLike>(
|
||||
page: PageWithFrames,
|
||||
predicate: (frame: TFrame) => boolean,
|
||||
timeoutMs = 15_000,
|
||||
): Promise<TFrame> {
|
||||
const startedAt = Date.now()
|
||||
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
const matched = page.frames().find((frame): frame is TFrame => {
|
||||
try {
|
||||
return predicate(frame as TFrame)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (matched) {
|
||||
return matched
|
||||
}
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
}
|
||||
|
||||
throw new Error('未找到登录二维码 frame')
|
||||
}
|
||||
|
||||
export async function downloadRemoteQrImage(
|
||||
page: PageWithRequestContext,
|
||||
qrUrl: string,
|
||||
qrImagePath: string,
|
||||
referer = '',
|
||||
loginType = 'unknown',
|
||||
): Promise<Buffer | false | null> {
|
||||
try {
|
||||
const response = await page.context().request.get(qrUrl, {
|
||||
failOnStatusCode: false,
|
||||
headers: {
|
||||
referer,
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok()) {
|
||||
logBrowserSessionDebug(`${loginType}.capture.downloadNotOk`, {
|
||||
qrUrl,
|
||||
status: response.status(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
const body = await response.body()
|
||||
|
||||
if (!body?.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return body
|
||||
} catch (error) {
|
||||
logBrowserSessionDebug(`${loginType}.capture.downloadError`, {
|
||||
qrUrl,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function isRetryableQrCaptureError(error: unknown): boolean {
|
||||
const message = error instanceof Error ? error.message : String(error || '')
|
||||
|
||||
return (
|
||||
message.includes('Frame was detached') ||
|
||||
message.includes('Execution context was destroyed') ||
|
||||
message.includes('Target page, context or browser has been closed') ||
|
||||
message.includes('waiting for') ||
|
||||
message.includes('Timeout')
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user