@taole/dd-playwright 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/dd-playwright",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Standalone stdio MCP & CLI: Playwright browser automation + isolated whistle proxy + diandian account storageState injection",
5
5
  "type": "module",
6
6
  "license": "ISC",
@@ -1,4 +1,5 @@
1
1
  import { chromium } from "playwright";
2
+ import { ensureChromiumInstalled } from "../browser-check.mjs";
2
3
 
3
4
  export const DIANDIAN_HOME_URL = "https://y.tuwan.com";
4
5
  export const DIANDIAN_USER_INFO_URL =
@@ -43,10 +44,9 @@ export class DiandianAuthManager {
43
44
 
44
45
  this.startupPromise = (async () => {
45
46
  await this.close();
46
-
47
+ await ensureChromiumInstalled();
47
48
  const instance = await this.chromium.launch({ headless });
48
49
  const ctx = await instance.newContext({
49
- viewport: { width: 1100, height: 850 },
50
50
  userAgent:
51
51
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
52
52
  });
@@ -0,0 +1,103 @@
1
+ import { chromium } from "playwright";
2
+ import { existsSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
4
+
5
+ let cachedChromiumMeta = null;
6
+
7
+ /**
8
+ * 获取当前 Chromium 内核安装状态与元数据
9
+ * @returns {Promise<{ installed: boolean, executablePath: string, version: string|null, error: string|null }>}
10
+ */
11
+ export async function getChromiumMetadata() {
12
+ let executablePath = "";
13
+ try {
14
+ executablePath = chromium.executablePath();
15
+ } catch (err) {
16
+ return {
17
+ installed: false,
18
+ executablePath: "",
19
+ version: null,
20
+ error: err.message,
21
+ };
22
+ }
23
+
24
+ const installed = Boolean(executablePath && existsSync(executablePath));
25
+ if (!installed) {
26
+ return {
27
+ installed: false,
28
+ executablePath: executablePath || "",
29
+ version: null,
30
+ error: "未在系统默认目录中发现对应版本的 Chromium 二进制文件",
31
+ };
32
+ }
33
+
34
+ if (cachedChromiumMeta && cachedChromiumMeta.installed) {
35
+ return cachedChromiumMeta;
36
+ }
37
+
38
+ let version = null;
39
+ try {
40
+ const browser = await chromium.launch({ headless: true });
41
+ version = browser.version();
42
+ await browser.close();
43
+ } catch (err) {
44
+ // 无法正常启动时可能是缺少依赖库或路径损坏
45
+ return {
46
+ installed: true,
47
+ executablePath,
48
+ version: null,
49
+ error: `启动检查异常: ${err.message}`,
50
+ };
51
+ }
52
+
53
+ cachedChromiumMeta = {
54
+ installed: true,
55
+ executablePath,
56
+ version,
57
+ error: null,
58
+ };
59
+ return cachedChromiumMeta;
60
+ }
61
+
62
+ /**
63
+ * 确保 Chromium 内核已就绪,若缺失则自动执行下载补齐
64
+ * @param {object} [options]
65
+ * @param {boolean} [options.silent] 是否静默输出
66
+ * @returns {Promise<boolean>} 是否成功就绪
67
+ */
68
+ export async function ensureChromiumInstalled({ silent = false } = {}) {
69
+ const meta = await getChromiumMetadata();
70
+ if (meta.installed && meta.version) {
71
+ return true;
72
+ }
73
+
74
+ if (!silent) {
75
+ console.log(
76
+ "\n[dd-playwright] 检测到当前系统尚未安装 Chromium 浏览器内核,正在为您自动下载 (~150MB),请稍候...",
77
+ );
78
+ }
79
+
80
+ try {
81
+ // 调用 playwright 内置命令安装 chromium
82
+ execSync("npx playwright install chromium", {
83
+ stdio: silent ? "ignore" : "inherit",
84
+ windowsHide: true,
85
+ });
86
+ cachedChromiumMeta = null; // 清理缓存
87
+ const verify = await getChromiumMetadata();
88
+ if (!silent) {
89
+ if (verify.installed) {
90
+ console.log(`[dd-playwright] Chromium 内核自动安装成功!版本: ${verify.version}\n`);
91
+ } else {
92
+ console.warn(`[dd-playwright] Chromium 安装流程已结束,但未能在路径检测到执行文件: ${verify.executablePath}\n`);
93
+ }
94
+ }
95
+ return verify.installed;
96
+ } catch (err) {
97
+ if (!silent) {
98
+ console.error(`[dd-playwright] 自动安装 Chromium 失败: ${err.message}`);
99
+ console.error(`可手动在终端执行: npx playwright install chromium\n`);
100
+ }
101
+ return false;
102
+ }
103
+ }
@@ -5,6 +5,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
5
5
  import { DatabaseSync } from "node:sqlite";
6
6
  import { join } from "node:path";
7
7
  import { chromium } from "playwright";
8
+ import { ensureChromiumInstalled } from "./browser-check.mjs";
8
9
  import { WhistleProxyManager } from "./whistle-proxy.mjs";
9
10
 
10
11
  /**
@@ -164,6 +165,7 @@ export class PlaywrightBrowserManager {
164
165
  }
165
166
 
166
167
  if (!this.browser) {
168
+ await ensureChromiumInstalled();
167
169
  const launchArgs = this.whistle.started ? this.whistle.getBrowserArgs() : [];
168
170
  this.browser = await chromium.launch({
169
171
  headless: this.headless,
@@ -1,7 +1,8 @@
1
1
  import { createServer } from "node:http";
2
+ import { readFileSync } from "node:fs";
2
3
  import { fileURLToPath } from "node:url";
3
- import { AccountStore } from "../account-store.mjs";
4
4
  import { DiandianAuthManager } from "../auth/diandian-auth.mjs";
5
+ import { getChromiumMetadata } from "../browser-check.mjs";
5
6
 
6
7
  function sendJson(res, statusCode, payload) {
7
8
  res.writeHead(statusCode, {
@@ -300,7 +301,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
300
301
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/><path d="M15 9h6"/></svg>
301
302
  dd-playwright 账号管理控制台
302
303
  </h1>
303
- <span style="font-size: 13px; color: var(--muted);">v0.1.1</span>
304
+ <span style="font-size: 13px; color: var(--muted);">v{{VERSION}}</span>
304
305
  </header>
305
306
 
306
307
  <div class="nav-tabs">
@@ -407,11 +408,25 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
407
408
  <span id="headlessKnob" style="position: absolute; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; transition: .2s; border-radius: 50%;"></span>
408
409
  </label>
409
410
  </div>
410
-
411
411
  <div style="margin-top: 16px; font-size: 12px; color: var(--muted); line-height: 1.6; padding: 12px; background: #090d16; border-radius: 6px; border: 1px solid var(--border);">
412
412
  📁 <b>数据存储说明:</b><br>
413
413
  本服务所有数据与账号库已默认固化在用户主目录下的 <code>~/.dd-browser-mcp/runtime.db</code>,无需配置任何复杂环境变量,开箱即用。
414
414
  </div>
415
+
416
+ <!-- Chromium 浏览器内核状态展示卡片 -->
417
+ <div style="margin-top: 20px; padding: 16px; background: #0f172a; border-radius: 8px; border: 1px solid var(--border);">
418
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
419
+ <div style="font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px;">
420
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/><line x1="21.17" y1="8" x2="12" y2="8"/><line x1="3.95" y1="6.06" x2="8.54" y2="14"/><line x1="10.88" y1="21.94" x2="15.46" y2="14"/></svg>
421
+ Chromium 浏览器内核
422
+ </div>
423
+ <span id="chromiumStatusBadge" style="font-size: 11px; padding: 2px 8px; border-radius: 4px; background: #334155; color: var(--muted);">检测中...</span>
424
+ </div>
425
+ <div id="chromiumDetails" style="font-size: 12px; color: var(--muted); line-height: 1.8;">
426
+ <div>版本号:<span id="chromiumVersion" style="color: #cbd5e1; font-family: monospace;">加载中...</span></div>
427
+ <div>执行路径:<span id="chromiumPath" style="color: #cbd5e1; font-family: monospace; word-break: break-all;">加载中...</span></div>
428
+ </div>
429
+ </div>
415
430
  </div>
416
431
  </div>
417
432
 
@@ -668,9 +683,35 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
668
683
  }
669
684
  }
670
685
 
671
- // 初次加载账号与配置
686
+ async function loadChromiumMeta() {
687
+ try {
688
+ const res = await fetch('/api/chromium');
689
+ const data = await res.json();
690
+ const badge = document.getElementById('chromiumStatusBadge');
691
+ const versionEl = document.getElementById('chromiumVersion');
692
+ const pathEl = document.getElementById('chromiumPath');
693
+ if (!badge || !versionEl || !pathEl) return;
694
+
695
+ if (data.ok && data.meta && data.meta.installed) {
696
+ badge.innerText = '已安装就绪';
697
+ badge.style.background = 'rgba(16, 185, 129, 0.15)';
698
+ badge.style.color = 'var(--success)';
699
+ versionEl.innerText = data.meta.version || '已安装 (Playwright 托管)';
700
+ pathEl.innerText = data.meta.executablePath || '-';
701
+ } else {
702
+ badge.innerText = '未就绪 (首次调用将自动安装)';
703
+ badge.style.background = 'rgba(239, 68, 68, 0.15)';
704
+ badge.style.color = 'var(--danger)';
705
+ versionEl.innerText = '未检测到内核';
706
+ pathEl.innerText = data.meta?.executablePath || '-';
707
+ }
708
+ } catch {}
709
+ }
710
+
711
+ // 初次加载账号、配置与内核状态
672
712
  loadAccounts();
673
713
  loadSettings();
714
+ loadChromiumMeta();
674
715
  </script>
675
716
  </body>
676
717
  </html>`;
@@ -734,11 +775,18 @@ export function createWebServer({ accountStore, authManager }) {
734
775
  <div id="code-mcp-local" class="code-block">${localConfigStr}</div>
735
776
  `;
736
777
 
778
+ const pkgPath = fileURLToPath(new URL("../../package.json", import.meta.url));
779
+ let pkgVersion = "0.1.0";
780
+ try {
781
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
782
+ pkgVersion = pkg.version || "0.1.0";
783
+ } catch {}
784
+
737
785
  const renderedHtml = DASHBOARD_HTML
786
+ .replace("{{VERSION}}", pkgVersion)
738
787
  .replace("{{BANNER_STYLE}}", bannerStyle)
739
788
  .replace("{{BANNER_CONTENT}}", bannerContent)
740
789
  .replace("{{CONFIG_SECTIONS}}", configSections);
741
-
742
790
  sendHtml(res, renderedHtml);
743
791
  return;
744
792
  }
@@ -784,6 +832,17 @@ export function createWebServer({ accountStore, authManager }) {
784
832
  }
785
833
  return;
786
834
  }
835
+ // 2.3 获取 Chromium 内核元数据
836
+ if (pathname === "/api/chromium" && req.method === "GET") {
837
+ try {
838
+ const meta = await getChromiumMetadata();
839
+ sendJson(res, 200, { ok: true, meta });
840
+ } catch (err) {
841
+ sendJson(res, 500, { ok: false, error: err.message });
842
+ }
843
+ return;
844
+ }
845
+
787
846
 
788
847
 
789
848
  // 3. 切换激活账号