This commit is contained in:
949036910@qq.com
2026-06-03 18:49:37 +08:00
parent 9d9a6f62c9
commit c81c4e31e0
5 changed files with 944 additions and 179 deletions

View File

@@ -2,10 +2,14 @@
/**
* 发布用 Puppeteer 浏览器(按平台/账号隔离 userDataDir
*
* WindowsChrome 必须以 detached 子进程启动,否则 Node 被 Tauri 结束后会连带关掉浏览器。
*/
const fs = require("fs");
const path = require("path");
const os = require("os");
const http = require("http");
const { spawn } = require("child_process");
const {
COMMON_BROWSER_ARGS,
@@ -55,33 +59,115 @@ function userDataDir(platform, accountId) {
return getRuntimeDataPath("browser-data", "publish", sub);
}
function debugPortForKey(key) {
let h = 5381;
for (let i = 0; i < key.length; i++) {
h = ((h << 5) + h + key.charCodeAt(i)) >>> 0;
}
return 9300 + (h % 700);
}
function delay(ms) {
return new Promise((r) => setTimeout(r, ms));
}
function fetchDebugVersion(port) {
return new Promise((resolve, reject) => {
const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {
let body = "";
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(e);
}
});
});
req.on("error", reject);
req.setTimeout(2500, () => {
req.destroy();
reject(new Error("timeout"));
});
});
}
async function isDebugPortOpen(port) {
try {
await fetchDebugVersion(port);
return true;
} catch {
return false;
}
}
async function waitForDebugPort(port, timeoutMs = 25000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (await isDebugPortOpen(port)) return;
await delay(400);
}
throw new Error(`Chrome 调试端口 ${port} 未就绪,请检查是否已安装 Chrome/Edge`);
}
/**
* 独立进程启动 Chrome与 Node 生命周期解耦)
*/
async function spawnDetachedChrome(executablePath, dir, port) {
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=*",
...COMMON_BROWSER_ARGS.filter((a) => !a.startsWith("--user-data-dir")),
"about:blank",
];
const child = spawn(executablePath, args, {
detached: true,
stdio: "ignore",
windowsHide: false,
});
child.unref();
await waitForDebugPort(port);
}
async function connectPublishBrowser(puppeteer, port) {
return puppeteer.connect({
browserURL: `http://127.0.0.1:${port}`,
defaultViewport: { width: 1280, height: 900 },
});
}
async function getPublishBrowser(platform, accountId) {
const key = contextKey(platform, accountId);
const existing = browsers.get(key);
if (existing && existing.isConnected()) {
return existing;
}
const puppeteer = loadPuppeteer();
const dir = userDataDir(platform, accountId);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const port = debugPortForKey(key);
const executablePath = getChromiumExecutablePath();
if (!executablePath) {
throw new Error(
"未找到 Chrome/Edge请安装浏览器或设置 AICLIENT_CHROMIUM_PATH",
);
}
const browser = await puppeteer.launch({
executablePath,
headless: false,
userDataDir: dir,
args: COMMON_BROWSER_ARGS,
ignoreDefaultArgs: BROWSER_IGNORE_DEFAULT_ARGS,
defaultViewport: { width: 1280, height: 900 },
});
let browser;
if (await isDebugPortOpen(port)) {
browser = await connectPublishBrowser(puppeteer, port);
} else {
await spawnDetachedChrome(executablePath, dir, port);
browser = await connectPublishBrowser(puppeteer, port);
}
browser.on("disconnected", () => browsers.delete(key));
browsers.set(key, browser);
return browser;
@@ -112,7 +198,8 @@ async function newPublishPage(platform, accountId, cookies) {
let page =
pages.find((p) => {
try {
return p.url() === "about:blank";
const u = p.url();
return !u || u === "about:blank";
} catch {
return false;
}
@@ -137,16 +224,37 @@ async function newPublishPage(platform, accountId, cookies) {
async function closePublishBrowser(platform, accountId) {
const key = contextKey(platform, accountId);
const browser = browsers.get(key);
if (!browser) return;
const port = debugPortForKey(key);
let browser = browsers.get(key);
browsers.delete(key);
try {
const puppeteer = loadPuppeteer();
if (!browser || !browser.isConnected()) {
if (!(await isDebugPortOpen(port))) return;
browser = await connectPublishBrowser(puppeteer, port);
}
await browser.close();
} catch {
/* ignore */
}
}
/**
* 断开 Puppeteer 连接Chrome 以 detached 进程继续运行
*/
async function detachPublishBrowser(platform, accountId) {
const key = contextKey(platform, accountId);
const browser = browsers.get(key);
browsers.delete(key);
try {
if (browser && browser.isConnected()) {
await browser.disconnect();
}
} catch (e) {
console.warn("detachPublishBrowser:", e.message);
}
}
const LOGIN_URLS = {
douyin: "https://creator.douyin.com/",
kuaishou: "https://cp.kuaishou.com/",
@@ -162,4 +270,7 @@ module.exports = {
LOGIN_URLS,
getChromiumExecutablePath,
closePublishBrowser,
detachPublishBrowser,
debugPortForKey,
contextKey,
};