后端迁移会话状态载荷模块
This commit is contained in:
+41
-7
@@ -1,8 +1,39 @@
|
||||
import process from 'node:process'
|
||||
|
||||
export const DEFAULT_LOGIN_TYPE = 'qq'
|
||||
export type LoginType = 'qq' | 'wx'
|
||||
|
||||
export function resolveBrowserLaunchOptions(options = {}) {
|
||||
export const DEFAULT_LOGIN_TYPE: LoginType = 'qq'
|
||||
|
||||
type BrowserConfigInput = {
|
||||
headless?: boolean
|
||||
devtools?: boolean
|
||||
keepAlive?: boolean
|
||||
prewarm?: boolean
|
||||
slowMoMs?: number
|
||||
}
|
||||
|
||||
type ResolveBrowserLaunchOptionsInput = {
|
||||
browserConfig?: BrowserConfigInput
|
||||
chromePath?: string
|
||||
nodeEnv?: string
|
||||
ci?: string
|
||||
}
|
||||
|
||||
export type BrowserLaunchOptions = {
|
||||
headless: boolean
|
||||
devtools: boolean
|
||||
usesBundledChromium: boolean
|
||||
keepAlive: boolean
|
||||
prewarm: boolean
|
||||
slowMoMs: number
|
||||
}
|
||||
|
||||
export type ViewportSize = {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export function resolveBrowserLaunchOptions(options: ResolveBrowserLaunchOptionsInput = {}): BrowserLaunchOptions {
|
||||
const browserConfig = options.browserConfig || {}
|
||||
const chromePath = options.chromePath || ''
|
||||
const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV
|
||||
@@ -37,7 +68,10 @@ export function resolveBrowserLaunchOptions(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildChromiumLaunchOptions(launchOptions, { chromePath = '' } = {}) {
|
||||
export function buildChromiumLaunchOptions(
|
||||
launchOptions: BrowserLaunchOptions,
|
||||
{ chromePath = '' }: { chromePath?: string } = {},
|
||||
) {
|
||||
const options = {
|
||||
headless: launchOptions.headless,
|
||||
devtools: launchOptions.devtools,
|
||||
@@ -54,7 +88,7 @@ export function buildChromiumLaunchOptions(launchOptions, { chromePath = '' } =
|
||||
return options
|
||||
}
|
||||
|
||||
export function resolveDefaultViewport(rawValue = process.env.TENCENT_BROWSER_VNC_RESOLUTION) {
|
||||
export function resolveDefaultViewport(rawValue = process.env.TENCENT_BROWSER_VNC_RESOLUTION): ViewportSize {
|
||||
const fromVncResolution = parseViewportSpec(rawValue)
|
||||
if (fromVncResolution) {
|
||||
return fromVncResolution
|
||||
@@ -63,7 +97,7 @@ export function resolveDefaultViewport(rawValue = process.env.TENCENT_BROWSER_VN
|
||||
return { width: 1600, height: 900 }
|
||||
}
|
||||
|
||||
export function parseViewportSpec(rawValue) {
|
||||
export function parseViewportSpec(rawValue: unknown): ViewportSize | null {
|
||||
const text = String(rawValue || '').trim()
|
||||
|
||||
if (!text) {
|
||||
@@ -85,7 +119,7 @@ export function parseViewportSpec(rawValue) {
|
||||
return { width, height }
|
||||
}
|
||||
|
||||
export function normalizeLoginType(value, fallback = DEFAULT_LOGIN_TYPE) {
|
||||
export function normalizeLoginType(value: unknown, fallback: LoginType = DEFAULT_LOGIN_TYPE): LoginType {
|
||||
const normalized = String(value || fallback).trim().toLowerCase()
|
||||
if (['qq', 'qc'].includes(normalized)) {
|
||||
return 'qq'
|
||||
@@ -96,6 +130,6 @@ export function normalizeLoginType(value, fallback = DEFAULT_LOGIN_TYPE) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
export function getLoginTypeLabel(loginType) {
|
||||
export function getLoginTypeLabel(loginType: LoginType | string): string {
|
||||
return loginType === 'wx' ? '微信' : 'QQ'
|
||||
}
|
||||
+71
-7
@@ -1,14 +1,14 @@
|
||||
export function buildSessionPayload(
|
||||
session,
|
||||
session: TencentBrowserSessionPayloadSource,
|
||||
{
|
||||
activityUrl = '',
|
||||
includeQrImage = true,
|
||||
sessionDebugEnabled = false,
|
||||
browserDebug = null,
|
||||
} = {},
|
||||
) {
|
||||
}: BuildSessionPayloadOptions = {},
|
||||
): any {
|
||||
const activityInfo = session.lastState?.activityInfo || null
|
||||
const payload = {
|
||||
const payload: Record<string, any> = {
|
||||
sessionId: session.sessionId,
|
||||
loginType: session.loginType,
|
||||
activityUrl,
|
||||
@@ -45,7 +45,65 @@ export function buildSessionPayload(
|
||||
}
|
||||
}
|
||||
|
||||
export function buildReviewPayload(review, { includeInternalPaths = false } = {}) {
|
||||
type BuildSessionPayloadOptions = {
|
||||
activityUrl?: string
|
||||
includeQrImage?: boolean
|
||||
sessionDebugEnabled?: boolean
|
||||
browserDebug?: unknown
|
||||
}
|
||||
|
||||
type InternalPathOptions = {
|
||||
includeInternalPaths?: boolean
|
||||
}
|
||||
|
||||
type TencentBrowserSessionPayloadSource = {
|
||||
sessionId: string
|
||||
loginType?: string
|
||||
status?: string
|
||||
notice?: string
|
||||
createdAt?: string
|
||||
updatedAt?: string
|
||||
expiresAt: string | number | Date
|
||||
qrUpdatedAt?: string
|
||||
qrImageBase64?: string
|
||||
qrImagePath?: string
|
||||
sessionDir?: string
|
||||
lastState?: {
|
||||
credentialReady?: boolean
|
||||
cookieKeys?: string[]
|
||||
activityInfo?: any
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
lastReview?: ReviewPayloadSource | null
|
||||
lastRedeem?: RedeemPayloadSource | null
|
||||
}
|
||||
|
||||
type ReviewPayloadSource = {
|
||||
capturedAt?: string
|
||||
roleId?: string
|
||||
roleName?: string
|
||||
screenshotPath?: string
|
||||
signature?: string
|
||||
}
|
||||
|
||||
type RedeemAttemptPayloadSource = {
|
||||
attempt?: number
|
||||
redeem?: unknown
|
||||
}
|
||||
|
||||
type RedeemPayloadSource = {
|
||||
code?: string
|
||||
area?: string
|
||||
proofMode?: string
|
||||
attempts?: RedeemAttemptPayloadSource[]
|
||||
final?: RedeemAttemptPayloadSource | null
|
||||
finishedAt?: string
|
||||
screenshotPath?: string
|
||||
htmlPath?: string
|
||||
resultPath?: string
|
||||
}
|
||||
|
||||
export function buildReviewPayload(review: ReviewPayloadSource | null | undefined, { includeInternalPaths = false }: InternalPathOptions = {}) {
|
||||
if (!review) {
|
||||
return null
|
||||
}
|
||||
@@ -67,7 +125,7 @@ export function buildReviewPayload(review, { includeInternalPaths = false } = {}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildRedeemPayload(redeem, { includeInternalPaths = false } = {}) {
|
||||
export function buildRedeemPayload(redeem: RedeemPayloadSource | null | undefined, { includeInternalPaths = false }: InternalPathOptions = {}) {
|
||||
if (!redeem) {
|
||||
return null
|
||||
}
|
||||
@@ -103,7 +161,13 @@ export function buildRedeemPayload(redeem, { includeInternalPaths = false } = {}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildArtifactsPayload(session, { includeInternalPaths = false } = {}) {
|
||||
export function buildArtifactsPayload(
|
||||
session: Pick<
|
||||
TencentBrowserSessionPayloadSource,
|
||||
'sessionDir' | 'qrImagePath' | 'qrImageBase64' | 'lastReview' | 'lastRedeem'
|
||||
>,
|
||||
{ includeInternalPaths = false }: InternalPathOptions = {},
|
||||
) {
|
||||
const payload = {
|
||||
hasQrImage: Boolean(session.qrImageBase64),
|
||||
hasReviewScreenshot: Boolean(session.lastReview?.screenshotPath),
|
||||
+54
-8
@@ -1,15 +1,57 @@
|
||||
import { getLoginTypeLabel } from './session-browser-config.js'
|
||||
import { getLoginTypeLabel, type LoginType } from './session-browser-config.js'
|
||||
|
||||
export const SESSION_TTL_MS = 30 * 60 * 1000
|
||||
|
||||
type InitialQrSessionStateOptions = {
|
||||
updatedAt?: string
|
||||
nowMs?: number
|
||||
sessionTtlMs?: number
|
||||
loginTypeLabel?: string
|
||||
}
|
||||
|
||||
type HostState = {
|
||||
loginText?: string
|
||||
unloginVisible?: boolean
|
||||
loginedVisible?: boolean
|
||||
}
|
||||
|
||||
type QrState = {
|
||||
expired?: boolean
|
||||
scanned?: boolean
|
||||
}
|
||||
|
||||
type ActivityInfo = {
|
||||
role?: {
|
||||
ready?: boolean
|
||||
roleName?: string
|
||||
}
|
||||
}
|
||||
|
||||
type SessionStatusInput = {
|
||||
hostState: HostState
|
||||
qrState: QrState
|
||||
credentialReady?: boolean
|
||||
activityInfo?: ActivityInfo | null
|
||||
}
|
||||
|
||||
type SessionNoticeInput = SessionStatusInput & {
|
||||
loginType: LoginType | string
|
||||
}
|
||||
|
||||
type CookieMap = {
|
||||
openid?: string
|
||||
access_token?: string
|
||||
appid?: string
|
||||
}
|
||||
|
||||
export function resolveInitialQrSessionState(
|
||||
loginType,
|
||||
loginType: LoginType | string,
|
||||
{
|
||||
updatedAt = new Date().toISOString(),
|
||||
nowMs = Date.now(),
|
||||
sessionTtlMs = SESSION_TTL_MS,
|
||||
loginTypeLabel = getLoginTypeLabel(loginType),
|
||||
} = {},
|
||||
}: InitialQrSessionStateOptions = {},
|
||||
) {
|
||||
return {
|
||||
status: 'waiting_scan',
|
||||
@@ -19,7 +61,11 @@ export function resolveInitialQrSessionState(
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveTencentSessionExpiresAt(session, nowMs = Date.now(), sessionTtlMs = SESSION_TTL_MS) {
|
||||
export function resolveTencentSessionExpiresAt(
|
||||
session: { autoCloseAt?: number | string } | null | undefined,
|
||||
nowMs = Date.now(),
|
||||
sessionTtlMs = SESSION_TTL_MS,
|
||||
): number {
|
||||
const autoCloseAt = Number(session?.autoCloseAt || 0)
|
||||
if (autoCloseAt > 0) {
|
||||
return autoCloseAt
|
||||
@@ -28,13 +74,13 @@ export function resolveTencentSessionExpiresAt(session, nowMs = Date.now(), sess
|
||||
return nowMs + sessionTtlMs
|
||||
}
|
||||
|
||||
export function shouldRefreshLoggedInPresentation(hostState) {
|
||||
export function shouldRefreshLoggedInPresentation(hostState: HostState): boolean {
|
||||
const loginText = String(hostState?.loginText || '').trim()
|
||||
|
||||
return Boolean(hostState?.unloginVisible || !hostState?.loginedVisible || !loginText)
|
||||
}
|
||||
|
||||
export function resolveSessionStatus({ hostState, qrState, credentialReady, activityInfo }) {
|
||||
export function resolveSessionStatus({ hostState, qrState, credentialReady, activityInfo }: SessionStatusInput): string {
|
||||
if (hostState.loginedVisible || credentialReady) {
|
||||
if (credentialReady && activityInfo?.role?.ready) {
|
||||
return 'ready_to_redeem'
|
||||
@@ -60,7 +106,7 @@ export function resolveSessionNotice({
|
||||
credentialReady,
|
||||
activityInfo,
|
||||
loginType,
|
||||
}) {
|
||||
}: SessionNoticeInput): string {
|
||||
const loginTypeLabel = getLoginTypeLabel(loginType)
|
||||
|
||||
if (credentialReady) {
|
||||
@@ -86,6 +132,6 @@ export function resolveSessionNotice({
|
||||
return `请使用${loginTypeLabel}扫码登录`
|
||||
}
|
||||
|
||||
export function hasRedeemCredential(cookieMap) {
|
||||
export function hasRedeemCredential(cookieMap: CookieMap): boolean {
|
||||
return Boolean(cookieMap.openid && cookieMap.access_token && cookieMap.appid)
|
||||
}
|
||||
@@ -426,6 +426,16 @@
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
- `npm test` 共 139 个用例通过
|
||||
83. 腾讯浏览器会话配置 / 状态 / payload 纯逻辑模块迁移到 `.ts`:
|
||||
- `src/services/session/session-browser-config.ts`
|
||||
- `src/services/session/session-state.ts`
|
||||
- `src/services/session/session-payload.ts`
|
||||
84. 登录类型、浏览器启动参数、默认视口、会话状态 / notice、二维码 TTL、redeem/review/artifact payload 已显式类型化;`buildSessionPayload` 对外暂保留动态返回以兼容仍未迁移的 JS 路由与 session 编排
|
||||
85. Docker 内验证通过:
|
||||
- `src/services/session/session.test.js` 共 14 个用例通过
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
- `npm test` 共 139 个用例通过
|
||||
|
||||
## 下一步建议
|
||||
|
||||
|
||||
Reference in New Issue
Block a user