Files
order_site/apps/backend/src/services/session/session-login-shared.ts
T
2026-05-21 15:24:03 +08:00

114 lines
2.6 KiB
TypeScript

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')
)
}