This commit is contained in:
949036910@qq.com
2026-06-03 21:43:46 +08:00
parent bbdee469fc
commit 7d65e3e602
4 changed files with 178 additions and 38 deletions

View File

@@ -34,13 +34,20 @@ function getChromiumExecutablePath() {
const candidates = [];
if (process.platform === "win32") {
const pf = process.env.ProgramFiles || "C:\\Program Files";
const local = process.env.LOCALAPPDATA || "";
const administrator_appdata="C:\\Users\\Administrator\\AppData\\Local";
const pfx86 =
process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
const local =
process.env.LOCALAPPDATA ||
path.join(os.homedir(), "AppData", "Local");
const administrator_appdata="C:\\Users\\Administrator\\AppData\\Local";
candidates.push(
path.join(pf, "Google", "Chrome", "Application", "chrome.exe"),
path.join(pfx86, "Google", "Chrome", "Application", "chrome.exe"),
path.join(local, "Google", "Chrome", "Application", "chrome.exe"),
path.join(pf, "Microsoft", "Edge", "Application", "msedge.exe"),
// path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
path.join(pfx86, "Microsoft", "Edge", "Application", "msedge.exe"),
path.join(local, "Microsoft", "Edge", "Application", "msedge.exe"),
path.join(administrator_appdata, "Google", "Chrome", "bin","chrome.exe"),
);
}
return candidates.find((p) => p && fs.existsSync(p));
@@ -107,6 +114,19 @@ async function isDebugPortOpen(port) {
}
}
/** 关闭占用调试端口的 Chrome避免复用无窗口/僵尸实例导致扫码登录不弹窗 */
async function forceCloseChromeOnPort(port) {
if (!(await isDebugPortOpen(port))) return;
try {
const puppeteer = loadPuppeteer();
const browser = await connectPublishBrowser(puppeteer, port);
await browser.close();
} catch {
/* ignore */
}
await delay(1500);
}
async function waitForDebugPort(port, timeoutMs = 25000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
@@ -118,17 +138,25 @@ async function waitForDebugPort(port, timeoutMs = 25000) {
/**
* 独立进程启动 Chrome与 Node 生命周期解耦)
* @param {string} [startUrl]
*/
async function spawnDetachedChrome(executablePath, dir, port) {
if (await isDebugPortOpen(port)) return;
async function spawnDetachedChrome(executablePath, dir, port, startUrl, options = {}) {
const force = options.force === true;
if (force) {
await forceCloseChromeOnPort(port);
} else if (await isDebugPortOpen(port)) {
return;
}
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const args = [
`--remote-debugging-port=${port}`,
`--user-data-dir=${dir}`,
"--remote-allow-origins=*",
"--start-maximized",
"--new-window",
...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")),
"about:blank",
startUrl && String(startUrl).trim() ? String(startUrl).trim() : "about:blank",
];
const child = spawn(executablePath, args, {
@@ -164,15 +192,24 @@ function cleanupProfileLocks(dir) {
}
/**
* 扫码登录专用:可见 Chrome对齐 Electron launchPersistentContext headless:false
* 扫码登录puppeteer.launch 打开可见窗口Node 在登录等待期间保持存活
* @param {string} [_startUrl] 由 newLoginPage 负责导航
*/
async function getLoginBrowser(platform, accountId) {
async function getLoginBrowser(platform, accountId, _startUrl) {
const key = `login_${contextKey(platform, accountId)}`;
const existing = loginBrowsers.get(key);
if (existing && existing.isConnected()) {
return existing;
}
const publishKey = contextKey(platform, accountId);
const publishPort = debugPortForKey(publishKey);
const loginPort = debugPortForKey(key);
await closeLoginBrowser(platform, accountId);
await closePublishBrowser(platform, accountId);
await forceCloseChromeOnPort(loginPort);
await forceCloseChromeOnPort(publishPort);
const puppeteer = loadPuppeteer();
const dir = userDataDir(platform, accountId);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -181,20 +218,22 @@ async function getLoginBrowser(platform, accountId) {
const executablePath = getChromiumExecutablePath();
if (!executablePath) {
throw new Error(
"未找到 Chrome/Edge请安装浏览器或设置 AICLIENT_CHROMIUM_PATH",
"未找到 Chrome/Edge请安装 Google Chrome 或 Microsoft Edge或设置 AICLIENT_CHROMIUM_PATH",
);
}
globalThis.__native?.emitProgress?.("正在打开 Chrome 登录窗口…");
const browser = await puppeteer.launch({
executablePath,
headless: false,
userDataDir: dir,
args: [...COMMON_BROWSER_ARGS, "--start-maximized"],
args: [
"--start-maximized",
...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")),
],
ignoreDefaultArgs: BROWSER_IGNORE_DEFAULT_ARGS,
defaultViewport: { width: 1280, height: 900 },
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
});
browser.on("disconnected", () => loginBrowsers.delete(key));
@@ -251,13 +290,56 @@ async function closeBlankPages(browser, keepPage) {
}
}
async function newLoginPage(platform, accountId, cookies = []) {
const browser = await getLoginBrowser(platform, accountId);
const page = await browser.newPage();
async function bringPageToFront(page) {
try {
await page.bringToFront();
} catch {
/* ignore */
}
try {
const client = await page.createCDPSession();
await client.send("Page.bringToFront");
await client.send("Browser.setWindowBounds", {
windowId: (await client.send("Browser.getWindowForTarget")).windowId,
bounds: { windowState: "normal" },
});
} catch {
/* ignore */
}
}
/**
* @param {string} [startUrl] 登录页 URLspawn 时直接打开
*/
async function newLoginPage(platform, accountId, cookies = [], startUrl) {
const browser = await getLoginBrowser(platform, accountId, startUrl);
const pages = await browser.pages();
let page =
pages.find((p) => {
try {
const u = p.url();
return u && u !== "about:blank" && u !== "chrome://newtab/";
} catch {
return false;
}
}) || pages[0] || null;
if (!page) {
page = await browser.newPage();
if (startUrl) {
await page.goto(startUrl, {
waitUntil: "domcontentloaded",
timeout: 60000,
});
}
}
await page.setUserAgent(DEFAULT_USER_AGENT);
await page.evaluateOnNewDocument(STEALTH_INIT_SCRIPT);
page.setDefaultNavigationTimeout(60000);
page.setDefaultTimeout(60000);
if (Array.isArray(cookies) && cookies.length) {
try {
await page.setCookie(...cookies);
@@ -265,21 +347,26 @@ async function newLoginPage(platform, accountId, cookies = []) {
console.warn("setCookie 失败:", e.message);
}
}
try {
await page.bringToFront();
} catch {
/* ignore */
}
await bringPageToFront(page);
return page;
}
async function closeLoginBrowser(platform, accountId) {
const key = `login_${contextKey(platform, accountId)}`;
const browser = loginBrowsers.get(key);
if (!browser) return;
const port = debugPortForKey(key);
let browser = loginBrowsers.get(key);
loginBrowsers.delete(key);
try {
await browser.close();
if (browser && browser.isConnected()) {
await browser.close();
return;
}
const puppeteer = loadPuppeteer();
if (await isDebugPortOpen(port)) {
browser = await connectPublishBrowser(puppeteer, port);
await browser.close();
}
} catch {
/* ignore */
}