2025-10-22 19:47:56 +08:00

72 lines
1.9 KiB
TypeScript

import { chromium, type BrowserContext } from 'playwright'
// A simple singleton manager for a persistent Chromium context with ref-counting.
// Prevents concurrent tasks from closing the shared window prematurely.
let contextPromise: Promise<BrowserContext> | null = null
let context: BrowserContext | null = null
let refCount = 0
let idleCloseTimer: NodeJS.Timeout | null = null
const USER_DATA_DIR = 'chrome-profile/douyin'
const DEFAULT_OPTIONS = { headless: true } as const
async function launchContext(): Promise<BrowserContext> {
const ctx = await chromium.launchPersistentContext(USER_DATA_DIR, DEFAULT_OPTIONS)
// When the context is closed externally, reset manager state
ctx.on('close', () => {
context = null
contextPromise = null
refCount = 0
if (idleCloseTimer) {
clearTimeout(idleCloseTimer)
idleCloseTimer = null
}
})
return ctx
}
export async function acquireBrowserContext(): Promise<BrowserContext> {
// Cancel any pending idle close if a new consumer arrives
if (idleCloseTimer) {
clearTimeout(idleCloseTimer)
idleCloseTimer = null
}
if (context) {
refCount += 1
return context
}
if (!contextPromise) {
contextPromise = launchContext()
}
context = await contextPromise
refCount += 1
return context
}
export async function releaseBrowserContext(options?: { idleMillis?: number }): Promise<void> {
const idleMillis = options?.idleMillis ?? 15_000
refCount = Math.max(0, refCount - 1)
if (refCount > 0 || !context) return
// Delay the close to allow bursty workloads to reuse the context
if (idleCloseTimer) {
clearTimeout(idleCloseTimer)
idleCloseTimer = null
}
idleCloseTimer = setTimeout(async () => {
try {
if (context && refCount === 0) {
await context.close()
}
} finally {
context = null
contextPromise = null
idleCloseTimer = null
}
}, idleMillis)
}