openyida 2026.4.2-beta.4 → 2026.4.2-beta.6
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/lib/auth/login-cdp.js +315 -0
- package/lib/auth/login.js +11 -4
- package/lib/core/doctor.js +24 -2
- package/package.json +1 -1
- package/yida-skills/SKILL.md +1 -1
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login-cdp.js - 优先 CDP 协议登录,降级 Playwright 扫码登录
|
|
3
|
+
*
|
|
4
|
+
* 登录策略(按优先级):
|
|
5
|
+
* 1. CDP 模式:启动本地已安装的 Chrome,通过 CDP 协议连接,无需下载 Chromium
|
|
6
|
+
* 2. Playwright 模式:CDP 不可用时,降级为 playwright 内置 Chromium 扫码登录
|
|
7
|
+
*
|
|
8
|
+
* 导出函数:
|
|
9
|
+
* loginWithCdpOrPlaywright() - 执行完整登录流程(CDP 优先)
|
|
10
|
+
* findLocalChrome() - 查找本地 Chrome 可执行文件路径
|
|
11
|
+
* launchChromeWithCdp() - 启动 Chrome 并开启 CDP 调试端口
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const { execSync, spawn } = require('child_process');
|
|
20
|
+
const { extractInfoFromCookies } = require('../core/utils');
|
|
21
|
+
const { saveCookieCache, interactiveLogin } = require('./login');
|
|
22
|
+
const { t } = require('../core/i18n');
|
|
23
|
+
|
|
24
|
+
const DEFAULT_LOGIN_URL = 'https://www.aliwork.com/workPlatform';
|
|
25
|
+
const CDP_PORT = 19222;
|
|
26
|
+
const CDP_CONNECT_TIMEOUT_MS = 8000;
|
|
27
|
+
const LOGIN_POLL_INTERVAL_MS = 2000;
|
|
28
|
+
const LOGIN_TIMEOUT_MS = 600_000; // 10 分钟
|
|
29
|
+
|
|
30
|
+
// ── 本地 Chrome 路径查找 ──────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 按平台查找本地已安装的 Chrome / Chromium 可执行文件。
|
|
34
|
+
* @returns {string|null} 可执行文件路径,找不到返回 null
|
|
35
|
+
*/
|
|
36
|
+
function findLocalChrome() {
|
|
37
|
+
const platform = os.platform();
|
|
38
|
+
|
|
39
|
+
const candidates = {
|
|
40
|
+
darwin: [
|
|
41
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
42
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
43
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
44
|
+
path.join(os.homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
|
|
45
|
+
],
|
|
46
|
+
win32: [
|
|
47
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
48
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
49
|
+
path.join(os.homedir(), 'AppData\\Local\\Google\\Chrome\\Application\\chrome.exe'),
|
|
50
|
+
],
|
|
51
|
+
linux: [
|
|
52
|
+
'/usr/bin/google-chrome',
|
|
53
|
+
'/usr/bin/google-chrome-stable',
|
|
54
|
+
'/usr/bin/chromium-browser',
|
|
55
|
+
'/usr/bin/chromium',
|
|
56
|
+
'/snap/bin/chromium',
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const platformCandidates = candidates[platform] || candidates.linux;
|
|
61
|
+
for (const chromePath of platformCandidates) {
|
|
62
|
+
if (fs.existsSync(chromePath)) {
|
|
63
|
+
return chromePath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 尝试从 PATH 中查找
|
|
68
|
+
try {
|
|
69
|
+
const whichResult = execSync('which google-chrome || which chromium-browser || which chromium', {
|
|
70
|
+
encoding: 'utf-8',
|
|
71
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
|
+
}).trim().split('\n')[0];
|
|
73
|
+
if (whichResult && fs.existsSync(whichResult)) {
|
|
74
|
+
return whichResult;
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// 找不到,忽略
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── CDP 连接工具 ──────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 等待 CDP 调试端口就绪(轮询 /json/version)。
|
|
87
|
+
* @param {number} port
|
|
88
|
+
* @param {number} timeoutMs
|
|
89
|
+
* @returns {Promise<boolean>}
|
|
90
|
+
*/
|
|
91
|
+
function waitForCdpPort(port, timeoutMs) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const deadline = Date.now() + timeoutMs;
|
|
94
|
+
const http = require('http');
|
|
95
|
+
|
|
96
|
+
function poll() {
|
|
97
|
+
if (Date.now() > deadline) {
|
|
98
|
+
resolve(false);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const req = http.get(`http://127.0.0.1:${port}/json/version`, (res) => {
|
|
102
|
+
resolve(res.statusCode === 200);
|
|
103
|
+
});
|
|
104
|
+
req.on('error', () => {
|
|
105
|
+
setTimeout(poll, 300);
|
|
106
|
+
});
|
|
107
|
+
req.setTimeout(500, () => {
|
|
108
|
+
req.destroy();
|
|
109
|
+
setTimeout(poll, 300);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
poll();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 启动本地 Chrome 并开启 CDP 调试端口。
|
|
119
|
+
* @param {string} chromePath - Chrome 可执行文件路径
|
|
120
|
+
* @param {string} loginUrl - 登录页 URL
|
|
121
|
+
* @returns {{ process: ChildProcess, userDataDir: string } | null}
|
|
122
|
+
*/
|
|
123
|
+
function launchChromeWithCdp(chromePath, loginUrl) {
|
|
124
|
+
const userDataDir = path.join(os.tmpdir(), `yidacli-chrome-${Date.now()}`);
|
|
125
|
+
fs.mkdirSync(userDataDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
const args = [
|
|
128
|
+
`--remote-debugging-port=${CDP_PORT}`,
|
|
129
|
+
`--user-data-dir=${userDataDir}`,
|
|
130
|
+
'--no-first-run',
|
|
131
|
+
'--no-default-browser-check',
|
|
132
|
+
'--disable-default-apps',
|
|
133
|
+
loginUrl,
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const chromeProcess = spawn(chromePath, args, {
|
|
138
|
+
detached: false,
|
|
139
|
+
stdio: 'ignore',
|
|
140
|
+
});
|
|
141
|
+
return { process: chromeProcess, userDataDir };
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Cookie 轮询等待 ───────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 通过 playwright CDP 连接,轮询等待 tianshu_csrf_token Cookie 出现。
|
|
151
|
+
* @param {object} context - playwright BrowserContext
|
|
152
|
+
* @returns {Promise<{ cookies: Array, baseUrl: string } | null>}
|
|
153
|
+
*/
|
|
154
|
+
async function pollForLoginCookies(context) {
|
|
155
|
+
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
156
|
+
|
|
157
|
+
while (Date.now() < deadline) {
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, LOGIN_POLL_INTERVAL_MS));
|
|
159
|
+
|
|
160
|
+
const cookies = await context.cookies();
|
|
161
|
+
const csrfCookie = cookies.find((cookie) => cookie.name === 'tianshu_csrf_token' && cookie.value);
|
|
162
|
+
|
|
163
|
+
if (!csrfCookie) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 提取 baseUrl(优先用 yida_user_cookie 的 domain)
|
|
168
|
+
let baseUrl = 'https://www.aliwork.com';
|
|
169
|
+
const yidaCookie = cookies.find((cookie) => cookie.name === 'yida_user_cookie');
|
|
170
|
+
if (yidaCookie && yidaCookie.domain && yidaCookie.domain.includes('aliwork.com')) {
|
|
171
|
+
baseUrl = 'https://' + yidaCookie.domain.replace(/^\./, '');
|
|
172
|
+
} else if (csrfCookie.domain && csrfCookie.domain !== '.aliwork.com') {
|
|
173
|
+
baseUrl = 'https://' + csrfCookie.domain.replace(/^\./, '');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { cookies, baseUrl };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── CDP 登录主流程 ────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 尝试通过 CDP 协议连接本地 Chrome 完成登录。
|
|
186
|
+
* @param {string} loginUrl
|
|
187
|
+
* @returns {Promise<object|null>} loginResult 或 null(失败时)
|
|
188
|
+
*/
|
|
189
|
+
async function loginViaCdp(loginUrl) {
|
|
190
|
+
const chromePath = findLocalChrome();
|
|
191
|
+
if (!chromePath) {
|
|
192
|
+
console.error(' ℹ️ 未找到本地 Chrome,将使用 Playwright 内置 Chromium');
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.error(` 🌐 检测到本地 Chrome:${chromePath}`);
|
|
197
|
+
console.error(` 🔗 正在通过 CDP 协议连接(端口 ${CDP_PORT})...`);
|
|
198
|
+
|
|
199
|
+
// 检查端口是否已被占用(Chrome 已在运行)
|
|
200
|
+
const portAlreadyOpen = await waitForCdpPort(CDP_PORT, 500);
|
|
201
|
+
let chromeProcess = null;
|
|
202
|
+
let userDataDir = null;
|
|
203
|
+
|
|
204
|
+
if (!portAlreadyOpen) {
|
|
205
|
+
const launched = launchChromeWithCdp(chromePath, loginUrl);
|
|
206
|
+
if (!launched) {
|
|
207
|
+
console.error(' ⚠️ Chrome 启动失败,降级为 Playwright 模式');
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
chromeProcess = launched.process;
|
|
211
|
+
userDataDir = launched.userDataDir;
|
|
212
|
+
|
|
213
|
+
const ready = await waitForCdpPort(CDP_PORT, CDP_CONNECT_TIMEOUT_MS);
|
|
214
|
+
if (!ready) {
|
|
215
|
+
console.error(' ⚠️ CDP 端口未就绪,降级为 Playwright 模式');
|
|
216
|
+
if (chromeProcess) { chromeProcess.kill(); }
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
console.error(' ℹ️ 检测到 Chrome 已在运行,直接连接');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let playwright;
|
|
224
|
+
try {
|
|
225
|
+
playwright = require('playwright');
|
|
226
|
+
} catch {
|
|
227
|
+
console.error(' ⚠️ playwright 未安装,降级为扫码模式');
|
|
228
|
+
if (chromeProcess) { chromeProcess.kill(); }
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let browser = null;
|
|
233
|
+
try {
|
|
234
|
+
browser = await playwright.chromium.connectOverCDP(`http://127.0.0.1:${CDP_PORT}`);
|
|
235
|
+
const contexts = browser.contexts();
|
|
236
|
+
const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
|
|
237
|
+
|
|
238
|
+
// 导航到登录页(如果当前页面不是登录页)
|
|
239
|
+
const pages = context.pages();
|
|
240
|
+
const loginPage = pages.length > 0 ? pages[0] : await context.newPage();
|
|
241
|
+
const currentUrl = loginPage.url();
|
|
242
|
+
if (!currentUrl.includes('aliwork.com')) {
|
|
243
|
+
await loginPage.goto(loginUrl, { timeout: 30000 });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.error(' ⏳ 请在浏览器中完成宜搭登录(最多等待 10 分钟)...');
|
|
247
|
+
|
|
248
|
+
const result = await pollForLoginCookies(context);
|
|
249
|
+
if (!result) {
|
|
250
|
+
console.error(' ⏰ 登录超时(10 分钟),请重试');
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.error(' ✅ CDP 登录成功!');
|
|
255
|
+
return result;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(` ⚠️ CDP 连接失败(${error.message}),降级为 Playwright 模式`);
|
|
258
|
+
return null;
|
|
259
|
+
} finally {
|
|
260
|
+
if (browser) {
|
|
261
|
+
try { await browser.disconnect(); } catch { /* ignore */ }
|
|
262
|
+
}
|
|
263
|
+
if (chromeProcess) {
|
|
264
|
+
try { chromeProcess.kill(); } catch { /* ignore */ }
|
|
265
|
+
}
|
|
266
|
+
if (userDataDir) {
|
|
267
|
+
try { fs.rmSync(userDataDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── 对外入口 ──────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 登录主入口:优先 CDP 连接本地 Chrome,失败时降级为 Playwright 扫码登录。
|
|
276
|
+
*
|
|
277
|
+
* @param {object} [options]
|
|
278
|
+
* @param {string} [options.loginUrl] - 登录页 URL,默认读取 config.json
|
|
279
|
+
* @returns {object} loginResult { csrf_token, corp_id, user_id, base_url, cookies }
|
|
280
|
+
*/
|
|
281
|
+
async function loginWithCdpOrPlaywright(options = {}) {
|
|
282
|
+
const loginUrl = options.loginUrl || DEFAULT_LOGIN_URL;
|
|
283
|
+
|
|
284
|
+
console.error('\n🔐 正在打开浏览器,请扫码登录...');
|
|
285
|
+
|
|
286
|
+
// 策略 1:CDP 连接本地 Chrome
|
|
287
|
+
const cdpResult = await loginViaCdp(loginUrl);
|
|
288
|
+
if (cdpResult) {
|
|
289
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cdpResult.cookies);
|
|
290
|
+
if (!csrfToken) {
|
|
291
|
+
console.error(' ⚠️ CDP 登录后未找到 csrf_token,降级为 Playwright 模式');
|
|
292
|
+
} else {
|
|
293
|
+
saveCookieCache(cdpResult.cookies, cdpResult.baseUrl);
|
|
294
|
+
console.error(` 🔑 csrf_token: ${csrfToken.slice(0, 16)}...`);
|
|
295
|
+
if (corpId) { console.error(` 🏢 组织 ID: ${corpId}`); }
|
|
296
|
+
return {
|
|
297
|
+
csrf_token: csrfToken,
|
|
298
|
+
corp_id: corpId,
|
|
299
|
+
user_id: userId,
|
|
300
|
+
base_url: cdpResult.baseUrl,
|
|
301
|
+
cookies: cdpResult.cookies,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 策略 2:降级为 Playwright 扫码登录
|
|
307
|
+
console.error(' 📱 使用 Playwright 内置 Chromium 登录...');
|
|
308
|
+
return interactiveLogin();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
loginWithCdpOrPlaywright,
|
|
313
|
+
findLocalChrome,
|
|
314
|
+
launchChromeWithCdp,
|
|
315
|
+
};
|
package/lib/auth/login.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* login.js -
|
|
2
|
+
* login.js - 宜搭登录态管理
|
|
3
|
+
*
|
|
4
|
+
* 登录策略(按优先级):
|
|
5
|
+
* 1. 本地 Cookie 缓存(最快)
|
|
6
|
+
* 2. CDP 协议连接本地 Chrome(无需下载 Chromium)
|
|
7
|
+
* 3. Playwright 内置 Chromium 扫码登录(兜底)
|
|
3
8
|
*
|
|
4
9
|
* 导出函数:
|
|
5
|
-
* ensureLogin() -
|
|
10
|
+
* ensureLogin() - 确保有效登录态(优先缓存,否则触发浏览器登录)
|
|
6
11
|
* checkLoginOnly() - 仅检查登录态,不触发登录
|
|
7
12
|
* refreshCsrfFromCache() - 从缓存 Cookie 重新提取 csrf_token
|
|
8
|
-
* interactiveLogin() -
|
|
13
|
+
* interactiveLogin() - 打开浏览器扫码登录(Playwright 模式,作为兜底)
|
|
9
14
|
* saveCookieCache() - 保存 Cookie 到本地缓存(供 qr-login.js 使用)
|
|
10
15
|
* logout() - 退出登录,清空 Cookie 缓存
|
|
11
16
|
*/
|
|
@@ -151,7 +156,9 @@ function ensureLogin() {
|
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
|
|
159
|
+
// 优先 CDP 连接本地 Chrome,失败时降级为 Playwright 扫码登录
|
|
160
|
+
const { loginWithCdpOrPlaywright } = require('./login-cdp');
|
|
161
|
+
return loginWithCdpOrPlaywright({ loginUrl: loadConfig().loginUrl });
|
|
155
162
|
}
|
|
156
163
|
|
|
157
164
|
// ── Playwright 扫码登录 ───────────────────────────────
|
package/lib/core/doctor.js
CHANGED
|
@@ -229,8 +229,8 @@ class EnvironmentChecker {
|
|
|
229
229
|
passed: false,
|
|
230
230
|
severity: Severity.ERROR,
|
|
231
231
|
message: `Chromium 未安装(期望路径:${chromiumPath || '未知'})`,
|
|
232
|
-
fixType: FixType.
|
|
233
|
-
|
|
232
|
+
fixType: FixType.AUTO,
|
|
233
|
+
fixAction: 'install-chromium',
|
|
234
234
|
};
|
|
235
235
|
} catch {
|
|
236
236
|
// playwright API 不可用,降级为仅检查包安装
|
|
@@ -742,6 +742,28 @@ class FixEngine {
|
|
|
742
742
|
};
|
|
743
743
|
}
|
|
744
744
|
|
|
745
|
+
case 'install-chromium': {
|
|
746
|
+
try {
|
|
747
|
+
const { execSync } = require('child_process');
|
|
748
|
+
console.log('\n 🌐 正在安装 Chromium 浏览器(请稍候)...');
|
|
749
|
+
execSync('npx playwright install chromium', {
|
|
750
|
+
stdio: 'inherit',
|
|
751
|
+
timeout: 300_000,
|
|
752
|
+
});
|
|
753
|
+
return {
|
|
754
|
+
id: issue.id,
|
|
755
|
+
fixed: true,
|
|
756
|
+
message: 'Chromium 安装完成',
|
|
757
|
+
};
|
|
758
|
+
} catch (error) {
|
|
759
|
+
return {
|
|
760
|
+
id: issue.id,
|
|
761
|
+
fixed: false,
|
|
762
|
+
message: `Chromium 安装失败,请手动运行:npx playwright install chromium(${error.message})`,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
745
767
|
case 'init-project': {
|
|
746
768
|
try {
|
|
747
769
|
const copy = require('./copy');
|
package/package.json
CHANGED
package/yida-skills/SKILL.md
CHANGED