后端迁移会话状态载荷模块
This commit is contained in:
+41
-7
@@ -1,8 +1,39 @@
|
|||||||
import process from 'node:process'
|
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 browserConfig = options.browserConfig || {}
|
||||||
const chromePath = options.chromePath || ''
|
const chromePath = options.chromePath || ''
|
||||||
const nodeEnv = options.nodeEnv ?? process.env.NODE_ENV
|
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 = {
|
const options = {
|
||||||
headless: launchOptions.headless,
|
headless: launchOptions.headless,
|
||||||
devtools: launchOptions.devtools,
|
devtools: launchOptions.devtools,
|
||||||
@@ -54,7 +88,7 @@ export function buildChromiumLaunchOptions(launchOptions, { chromePath = '' } =
|
|||||||
return options
|
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)
|
const fromVncResolution = parseViewportSpec(rawValue)
|
||||||
if (fromVncResolution) {
|
if (fromVncResolution) {
|
||||||
return fromVncResolution
|
return fromVncResolution
|
||||||
@@ -63,7 +97,7 @@ export function resolveDefaultViewport(rawValue = process.env.TENCENT_BROWSER_VN
|
|||||||
return { width: 1600, height: 900 }
|
return { width: 1600, height: 900 }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseViewportSpec(rawValue) {
|
export function parseViewportSpec(rawValue: unknown): ViewportSize | null {
|
||||||
const text = String(rawValue || '').trim()
|
const text = String(rawValue || '').trim()
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@@ -85,7 +119,7 @@ export function parseViewportSpec(rawValue) {
|
|||||||
return { width, height }
|
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()
|
const normalized = String(value || fallback).trim().toLowerCase()
|
||||||
if (['qq', 'qc'].includes(normalized)) {
|
if (['qq', 'qc'].includes(normalized)) {
|
||||||
return 'qq'
|
return 'qq'
|
||||||
@@ -96,6 +130,6 @@ export function normalizeLoginType(value, fallback = DEFAULT_LOGIN_TYPE) {
|
|||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLoginTypeLabel(loginType) {
|
export function getLoginTypeLabel(loginType: LoginType | string): string {
|
||||||
return loginType === 'wx' ? '微信' : 'QQ'
|
return loginType === 'wx' ? '微信' : 'QQ'
|
||||||
}
|
}
|
||||||
+71
-7
@@ -1,14 +1,14 @@
|
|||||||
export function buildSessionPayload(
|
export function buildSessionPayload(
|
||||||
session,
|
session: TencentBrowserSessionPayloadSource,
|
||||||
{
|
{
|
||||||
activityUrl = '',
|
activityUrl = '',
|
||||||
includeQrImage = true,
|
includeQrImage = true,
|
||||||
sessionDebugEnabled = false,
|
sessionDebugEnabled = false,
|
||||||
browserDebug = null,
|
browserDebug = null,
|
||||||
} = {},
|
}: BuildSessionPayloadOptions = {},
|
||||||
) {
|
): any {
|
||||||
const activityInfo = session.lastState?.activityInfo || null
|
const activityInfo = session.lastState?.activityInfo || null
|
||||||
const payload = {
|
const payload: Record<string, any> = {
|
||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
loginType: session.loginType,
|
loginType: session.loginType,
|
||||||
activityUrl,
|
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) {
|
if (!review) {
|
||||||
return null
|
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) {
|
if (!redeem) {
|
||||||
return null
|
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 = {
|
const payload = {
|
||||||
hasQrImage: Boolean(session.qrImageBase64),
|
hasQrImage: Boolean(session.qrImageBase64),
|
||||||
hasReviewScreenshot: Boolean(session.lastReview?.screenshotPath),
|
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
|
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(
|
export function resolveInitialQrSessionState(
|
||||||
loginType,
|
loginType: LoginType | string,
|
||||||
{
|
{
|
||||||
updatedAt = new Date().toISOString(),
|
updatedAt = new Date().toISOString(),
|
||||||
nowMs = Date.now(),
|
nowMs = Date.now(),
|
||||||
sessionTtlMs = SESSION_TTL_MS,
|
sessionTtlMs = SESSION_TTL_MS,
|
||||||
loginTypeLabel = getLoginTypeLabel(loginType),
|
loginTypeLabel = getLoginTypeLabel(loginType),
|
||||||
} = {},
|
}: InitialQrSessionStateOptions = {},
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
status: 'waiting_scan',
|
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)
|
const autoCloseAt = Number(session?.autoCloseAt || 0)
|
||||||
if (autoCloseAt > 0) {
|
if (autoCloseAt > 0) {
|
||||||
return autoCloseAt
|
return autoCloseAt
|
||||||
@@ -28,13 +74,13 @@ export function resolveTencentSessionExpiresAt(session, nowMs = Date.now(), sess
|
|||||||
return nowMs + sessionTtlMs
|
return nowMs + sessionTtlMs
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldRefreshLoggedInPresentation(hostState) {
|
export function shouldRefreshLoggedInPresentation(hostState: HostState): boolean {
|
||||||
const loginText = String(hostState?.loginText || '').trim()
|
const loginText = String(hostState?.loginText || '').trim()
|
||||||
|
|
||||||
return Boolean(hostState?.unloginVisible || !hostState?.loginedVisible || !loginText)
|
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 (hostState.loginedVisible || credentialReady) {
|
||||||
if (credentialReady && activityInfo?.role?.ready) {
|
if (credentialReady && activityInfo?.role?.ready) {
|
||||||
return 'ready_to_redeem'
|
return 'ready_to_redeem'
|
||||||
@@ -60,7 +106,7 @@ export function resolveSessionNotice({
|
|||||||
credentialReady,
|
credentialReady,
|
||||||
activityInfo,
|
activityInfo,
|
||||||
loginType,
|
loginType,
|
||||||
}) {
|
}: SessionNoticeInput): string {
|
||||||
const loginTypeLabel = getLoginTypeLabel(loginType)
|
const loginTypeLabel = getLoginTypeLabel(loginType)
|
||||||
|
|
||||||
if (credentialReady) {
|
if (credentialReady) {
|
||||||
@@ -86,6 +132,6 @@ export function resolveSessionNotice({
|
|||||||
return `请使用${loginTypeLabel}扫码登录`
|
return `请使用${loginTypeLabel}扫码登录`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasRedeemCredential(cookieMap) {
|
export function hasRedeemCredential(cookieMap: CookieMap): boolean {
|
||||||
return Boolean(cookieMap.openid && cookieMap.access_token && cookieMap.appid)
|
return Boolean(cookieMap.openid && cookieMap.access_token && cookieMap.appid)
|
||||||
}
|
}
|
||||||
@@ -426,6 +426,16 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 139 个用例通过
|
- `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