openyida 2026.7.19 → 2026.7.21
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/README.md +14 -0
- package/bin/yida.js +107 -18
- package/lib/app/create-app.js +12 -1
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form.js +154 -67
- package/lib/app/create-page.js +45 -9
- package/lib/app/get-schema.js +65 -14
- package/lib/app/import-app.js +92 -41
- package/lib/app/publish.js +289 -556
- package/lib/app/schema-field-resolution.js +370 -0
- package/lib/app/services/app-reader.js +85 -0
- package/lib/app/services/app-service.js +235 -0
- package/lib/app/services/canvas-page-compiler.js +91 -0
- package/lib/app/services/canvas-page-schema-builder.js +149 -0
- package/lib/app/services/field-bindings.js +68 -0
- package/lib/app/services/form-compiler.js +1922 -0
- package/lib/app/services/form-mode-service.js +182 -0
- package/lib/app/services/form-schema-patcher.js +333 -0
- package/lib/app/services/form-schema-reader.js +59 -0
- package/lib/app/services/form-service.js +379 -0
- package/lib/app/services/form-validation.js +562 -0
- package/lib/app/services/native-page-compiler.js +92 -0
- package/lib/app/services/native-page-schema-builder.js +428 -0
- package/lib/app/services/page-resource-service.js +185 -0
- package/lib/app/update-app.js +18 -6
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/bridge/bridge.js +37 -5
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-manifest.js +40 -0
- package/lib/core/doctor.js +15 -4
- package/lib/core/env-cmd.js +1 -1
- package/lib/core/env.js +7 -6
- package/lib/core/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +8 -46
- package/lib/core/locales/zh.js +8 -46
- package/lib/core/sample.js +1 -1
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +104 -3
- package/lib/formula/evaluate.js +8 -66
- package/lib/formula/field-refs.js +83 -0
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- package/lib/process/services/process-compiler.js +2056 -0
- package/lib/process/services/process-reader.js +232 -0
- package/lib/process/services/process-resource-service.js +571 -0
- package/lib/process/services/process-service.js +122 -0
- package/lib/process/services/process-stage-checkpoint.js +246 -0
- package/lib/report/append.js +7 -26
- package/lib/report/http.js +31 -5
- package/lib/report/index.js +10 -2
- package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
- package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
- package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
- package/lib/schema/adapters/app-adapter.js +210 -0
- package/lib/schema/adapters/form-adapter.js +1362 -0
- package/lib/schema/adapters/keys.js +19 -0
- package/lib/schema/adapters/page-adapter.js +566 -0
- package/lib/schema/adapters/process-adapter.js +519 -0
- package/lib/schema/applier.js +1878 -0
- package/lib/schema/apply-store.js +1083 -0
- package/lib/schema/command.js +645 -0
- package/lib/schema/dependency-graph.js +83 -0
- package/lib/schema/errors.js +481 -0
- package/lib/schema/hash.js +9 -0
- package/lib/schema/manifest-limits.js +177 -0
- package/lib/schema/manifest-loader.js +309 -0
- package/lib/schema/manifest-schema-v1.json +193 -0
- package/lib/schema/normalize-manifest.js +281 -0
- package/lib/schema/page-canvas-foundation.js +447 -0
- package/lib/schema/page-data-source-builder.js +561 -0
- package/lib/schema/page-foundation.js +556 -0
- package/lib/schema/page-source-loader.js +314 -0
- package/lib/schema/planner.js +691 -0
- package/lib/schema/remote-dispatch-boundary.js +39 -0
- package/lib/schema/remote-missing.js +21 -0
- package/lib/schema/remote-reader.js +189 -0
- package/lib/schema/resource-registry.js +157 -0
- package/lib/schema/server-revision.js +71 -0
- package/lib/schema/sort.js +28 -0
- package/lib/schema/state-store.js +589 -0
- package/package.json +2 -2
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-package-size.js +1 -1
- package/yida-skills/SKILL.md +114 -29
- package/yida-skills/references/schema-as-code-phase1.md +99 -0
- package/yida-skills/references/setup-and-env.md +59 -59
- package/yida-skills/references/task-retrospective.md +6 -6
- package/yida-skills/skills/yida-app/SKILL.md +60 -14
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
- package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
- package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
- package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
- package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
- package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +7 -1
- package/yida-skills/skills/yida-login/SKILL.md +50 -99
- package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
- package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
- package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
- package/yida-skills/skills/yida-report/SKILL.md +7 -1
- package/yida-skills/skills-index.json +14 -46
- package/yida-skills/skills/large-file-write/SKILL.md +0 -93
- package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
- package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
- package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
- package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
- package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
package/lib/core/utils.js
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 导出函数:
|
|
5
5
|
* findProjectRoot() - 查找项目根目录(兼容悟空环境)
|
|
6
|
-
*
|
|
6
|
+
* extractInfoFromCookies() - 从 Cookie 列表中提取 csrf_token / corp_id / user_id
|
|
7
|
+
* loadCookieData() - 已废弃:默认登录态不再读取 Cookie 缓存
|
|
8
|
+
* getAuthStatus() - 读取当前鉴权模式的安全状态摘要
|
|
7
9
|
* triggerLogin() - 触发登录
|
|
8
|
-
*
|
|
10
|
+
* refreshCsrfToken() - 刷新 csrf_token
|
|
11
|
+
* resolveBaseUrl() - 从 cookieData 中解析 base_url
|
|
9
12
|
* isLoginExpired() - 检测响应体是否表示登录过期
|
|
13
|
+
* isCsrfTokenExpired() - 检测响应体是否表示 csrf_token 过期
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
'use strict';
|
|
@@ -150,9 +154,6 @@ function detectActiveTool() {
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
function hasDesktopEnvironment(env = process.env, platform = process.platform) {
|
|
153
|
-
if (env.OPENYIDA_FORCE_TERMINAL_QR === '1') {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
157
|
if (env.OPENYIDA_ASSUME_DESKTOP === '1') {
|
|
157
158
|
return true;
|
|
158
159
|
}
|
|
@@ -256,15 +257,16 @@ function getNodeExecutable() {
|
|
|
256
257
|
return 'node';
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
function isInjectedAuthMode() {
|
|
260
|
-
|
|
260
|
+
function isInjectedAuthMode(env = process.env) {
|
|
261
|
+
const authEnabled = String(env.YIDA_AUTH_ENABLED || '').trim().toLowerCase();
|
|
262
|
+
return ['1', 'true', 'yes', 'on'].includes(authEnabled);
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
function isEnvAuthMode() {
|
|
264
|
-
return
|
|
265
|
+
function isEnvAuthMode(env = process.env) {
|
|
266
|
+
return isInjectedAuthMode(env);
|
|
265
267
|
}
|
|
266
268
|
|
|
267
|
-
function isTokenAuthMode() {
|
|
269
|
+
function isTokenAuthMode(_env = process.env) {
|
|
268
270
|
return true;
|
|
269
271
|
}
|
|
270
272
|
|
|
@@ -329,9 +331,6 @@ function resolveTokenBaseUrl(root, fallbackBaseUrl, session) {
|
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
function loadTokenAuthData(projectRoot, defaultBaseUrl) {
|
|
332
|
-
if (!isTokenAuthMode()) {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
334
|
try {
|
|
336
335
|
const { loadTokenSession } = require('../auth/token-store');
|
|
337
336
|
const session = loadTokenSession({ projectRoot });
|
|
@@ -342,7 +341,7 @@ function loadTokenAuthData(projectRoot, defaultBaseUrl) {
|
|
|
342
341
|
corp_id: session.corp_id,
|
|
343
342
|
user_id: session.user_id,
|
|
344
343
|
base_url: resolveTokenBaseUrl(projectRoot, defaultBaseUrl, session),
|
|
345
|
-
auth_source: 'token',
|
|
344
|
+
auth_source: session.auth_source === 'env' ? 'env' : 'token',
|
|
346
345
|
auth_mode: 'token',
|
|
347
346
|
};
|
|
348
347
|
} catch {
|
|
@@ -364,10 +363,115 @@ async function resolveBearerAuthHeaders(options = {}) {
|
|
|
364
363
|
};
|
|
365
364
|
}
|
|
366
365
|
|
|
366
|
+
// ── Cookie 解析 ───────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
const CSRF_COOKIE_NAMES = new Set([
|
|
369
|
+
'tianshu_csrf_token',
|
|
370
|
+
'china_csrf_token',
|
|
371
|
+
'csrf_token',
|
|
372
|
+
'_csrf_token',
|
|
373
|
+
]);
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 从 Cookie 列表中提取 csrf_token、corp_id、user_id。
|
|
377
|
+
*
|
|
378
|
+
* 国内宜搭(aliwork.com):corpId/userId 合并写在 `tianshu_corp_user` 里,
|
|
379
|
+
* 形如 `${corpId}_${userId}`,按最后一个下划线切分。
|
|
380
|
+
*
|
|
381
|
+
* 海外 YiDA(yidaapps.com):不写 `tianshu_corp_user`,而是单独写 `corp_id` cookie
|
|
382
|
+
* 存放 corpId 明文;userId 加密在 `pub_uid` 里客户端无法解密,留 null 接受。
|
|
383
|
+
*
|
|
384
|
+
* @param {Array} cookies
|
|
385
|
+
* @returns {{ csrfToken: string|null, corpId: string|null, userId: string|null }}
|
|
386
|
+
*/
|
|
387
|
+
function extractInfoFromCookies(cookies) {
|
|
388
|
+
let csrfToken = null;
|
|
389
|
+
let corpId = null;
|
|
390
|
+
let userId = null;
|
|
391
|
+
const cookieList = Array.isArray(cookies) ? cookies : [];
|
|
392
|
+
|
|
393
|
+
for (const cookie of cookieList) {
|
|
394
|
+
if (cookie && CSRF_COOKIE_NAMES.has(cookie.name) && cookie.value && !csrfToken) {
|
|
395
|
+
csrfToken = cookie.value;
|
|
396
|
+
} else if (cookie && cookie.name === 'tianshu_corp_user') {
|
|
397
|
+
const lastUnderscore = cookie.value.lastIndexOf('_');
|
|
398
|
+
if (lastUnderscore > 0) {
|
|
399
|
+
corpId = cookie.value.slice(0, lastUnderscore);
|
|
400
|
+
userId = cookie.value.slice(lastUnderscore + 1);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (!corpId) {
|
|
406
|
+
const corpCookie = cookieList.find((c) => c && ['corp_id', 'corpId'].includes(c.name) && c.value);
|
|
407
|
+
if (corpCookie) {
|
|
408
|
+
corpId = corpCookie.value;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!userId) {
|
|
413
|
+
const userCookie = cookieList.find((c) => c && ['user_id', 'userId', 'staffId'].includes(c.name) && c.value);
|
|
414
|
+
if (userCookie) {
|
|
415
|
+
userId = userCookie.value;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return { csrfToken, corpId, userId };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function parseCookieHeader(rawCookieHeader, options = {}) {
|
|
423
|
+
const raw = String(rawCookieHeader || '').trim();
|
|
424
|
+
if (!raw) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
let domain = '';
|
|
428
|
+
if (options.baseUrl) {
|
|
429
|
+
try {
|
|
430
|
+
domain = new URL(options.baseUrl).hostname;
|
|
431
|
+
} catch {
|
|
432
|
+
domain = '';
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return raw
|
|
436
|
+
.split(';')
|
|
437
|
+
.map((part) => {
|
|
438
|
+
const trimmed = part.trim();
|
|
439
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
440
|
+
if (equalsIndex <= 0) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const name = trimmed.slice(0, equalsIndex).trim();
|
|
444
|
+
const value = trimmed.slice(equalsIndex + 1).trim();
|
|
445
|
+
if (!name) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const cookie = { name, value };
|
|
449
|
+
if (domain) {
|
|
450
|
+
cookie.domain = domain;
|
|
451
|
+
}
|
|
452
|
+
return cookie;
|
|
453
|
+
})
|
|
454
|
+
.filter(Boolean);
|
|
455
|
+
}
|
|
456
|
+
|
|
367
457
|
// ── 登录态缓存读取 ────────────────────────────────────
|
|
368
458
|
|
|
369
459
|
/**
|
|
370
|
-
*
|
|
460
|
+
* 已废弃:默认登录态不再读取 Cookie。
|
|
461
|
+
* 保留函数名用于旧测试/显式 mock 兼容;实际登录态只从 token session 读取。
|
|
462
|
+
* @param {string} [projectRoot]
|
|
463
|
+
* @param {string} [defaultBaseUrl]
|
|
464
|
+
* @returns {object|null}
|
|
465
|
+
*/
|
|
466
|
+
function loadCookieData(projectRoot, defaultBaseUrl) {
|
|
467
|
+
void projectRoot;
|
|
468
|
+
void defaultBaseUrl;
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 读取当前默认登录态。
|
|
474
|
+
* 默认使用 OAuth token session;YIDA_AUTH_ENABLED=true 时仅使用宿主注入 token。
|
|
371
475
|
* @param {string} [projectRoot]
|
|
372
476
|
* @param {string} [defaultBaseUrl]
|
|
373
477
|
* @returns {object|null}
|
|
@@ -378,15 +482,41 @@ function loadAuthData(projectRoot, defaultBaseUrl) {
|
|
|
378
482
|
return loadTokenAuthData(root, fallbackBaseUrl);
|
|
379
483
|
}
|
|
380
484
|
|
|
485
|
+
function getAuthStatus(options = {}) {
|
|
486
|
+
const { tokenStatus } = require('../auth/token-auth');
|
|
487
|
+
return tokenStatus(options);
|
|
488
|
+
}
|
|
489
|
+
|
|
381
490
|
// ── 登录触发 ──────────────────────────────────────────
|
|
382
491
|
|
|
383
492
|
/**
|
|
384
|
-
*
|
|
493
|
+
* 校验默认登录态;host mode 下不会触发 OAuth,本地模式提示用户执行 openyida login。
|
|
385
494
|
* @param {object} [options]
|
|
386
495
|
* @param {boolean} [options.force=false] - 是否跳过本地缓存,强制重新登录
|
|
387
496
|
* @returns {object} loginResult
|
|
388
497
|
*/
|
|
389
498
|
function triggerLogin(options = {}) {
|
|
499
|
+
if (isEnvAuthMode()) {
|
|
500
|
+
const status = getAuthStatus({ projectRoot: options.projectRoot || findProjectRoot() });
|
|
501
|
+
if (status && status.ok) {
|
|
502
|
+
return loadTokenAuthData(options.projectRoot || findProjectRoot(), status.base_url || 'https://www.aliwork.com');
|
|
503
|
+
}
|
|
504
|
+
const reason = status && status.failure_reason
|
|
505
|
+
? status.failure_reason
|
|
506
|
+
: 'env_token_missing';
|
|
507
|
+
const { CliError } = require('./cli-error');
|
|
508
|
+
throw new CliError(
|
|
509
|
+
`not_logged_in: host-injected token is unavailable (${reason}). Ask the host to inject an OpenYida token.`,
|
|
510
|
+
{
|
|
511
|
+
code: 'INJECTED_AUTH_REQUIRED',
|
|
512
|
+
details: {
|
|
513
|
+
authMode: 'token',
|
|
514
|
+
failure_reason: reason,
|
|
515
|
+
},
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
390
520
|
const tokenAuthData = loadTokenAuthData(options.projectRoot || findProjectRoot(), 'https://www.aliwork.com');
|
|
391
521
|
if (tokenAuthData) {
|
|
392
522
|
return tokenAuthData;
|
|
@@ -403,6 +533,15 @@ function triggerLogin(options = {}) {
|
|
|
403
533
|
);
|
|
404
534
|
}
|
|
405
535
|
|
|
536
|
+
/**
|
|
537
|
+
* 已废弃:默认 token 登录态不再刷新 CSRF。
|
|
538
|
+
* @returns {object} loginResult
|
|
539
|
+
*/
|
|
540
|
+
function refreshCsrfToken() {
|
|
541
|
+
warn(t('login.csrf_refresh'));
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
|
|
406
545
|
// ── 响应检测 ──────────────────────────────────────────
|
|
407
546
|
|
|
408
547
|
/**
|
|
@@ -412,7 +551,7 @@ function triggerLogin(options = {}) {
|
|
|
412
551
|
*/
|
|
413
552
|
function isLoginExpired(responseJson) {
|
|
414
553
|
if (!responseJson) {return false;}
|
|
415
|
-
const content = responseJson && typeof responseJson.content === 'object' && !Array.isArray(responseJson.content)
|
|
554
|
+
const content = responseJson && responseJson.content && typeof responseJson.content === 'object' && !Array.isArray(responseJson.content)
|
|
416
555
|
? responseJson.content
|
|
417
556
|
: {};
|
|
418
557
|
const status = String(responseJson.status || content.status || '').toLowerCase();
|
|
@@ -446,6 +585,19 @@ function isLoginExpired(responseJson) {
|
|
|
446
585
|
);
|
|
447
586
|
}
|
|
448
587
|
|
|
588
|
+
/**
|
|
589
|
+
* 检测响应体是否表示 csrf_token 过期。
|
|
590
|
+
* @param {object} responseJson
|
|
591
|
+
* @returns {boolean}
|
|
592
|
+
*/
|
|
593
|
+
function isCsrfTokenExpired(responseJson) {
|
|
594
|
+
return (
|
|
595
|
+
responseJson &&
|
|
596
|
+
responseJson.success === false &&
|
|
597
|
+
responseJson.errorCode === 'TIANSHU_000030'
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
449
601
|
function isHttpRedirectStatus(statusCode) {
|
|
450
602
|
return [301, 302, 303, 307, 308].includes(Number(statusCode));
|
|
451
603
|
}
|
|
@@ -457,23 +609,23 @@ function isHttpAuthStatus(statusCode) {
|
|
|
457
609
|
// ── base_url 解析 ─────────────────────────────────────
|
|
458
610
|
|
|
459
611
|
/**
|
|
460
|
-
* 从
|
|
612
|
+
* 从 cookieData 中解析 base_url,支持多环境配置优先级。
|
|
461
613
|
*
|
|
462
614
|
* 优先级(高 → 低):
|
|
463
615
|
* 1. OPENYIDA_ENDPOINT 环境变量
|
|
464
|
-
* 2.
|
|
616
|
+
* 2. cookieData.base_url(登录后实际跳转域名)
|
|
465
617
|
* 3. 当前激活的私有化环境配置
|
|
466
618
|
* 4. 当前激活的环境配置(公有云默认)
|
|
467
619
|
* 5. defaultBaseUrl 参数 / 公有云兜底
|
|
468
620
|
*
|
|
469
|
-
* @param {object}
|
|
621
|
+
* @param {object} cookieData
|
|
470
622
|
* @param {string} [defaultBaseUrl]
|
|
471
623
|
* @returns {string}
|
|
472
624
|
*/
|
|
473
|
-
function resolveBaseUrl(
|
|
625
|
+
function resolveBaseUrl(cookieData, defaultBaseUrl) {
|
|
474
626
|
const { resolveEndpoint } = require('./env-manager');
|
|
475
|
-
const resolved = resolveEndpoint(
|
|
476
|
-
if (defaultBaseUrl && resolved === 'https://www.aliwork.com' && (!
|
|
627
|
+
const resolved = resolveEndpoint(cookieData, undefined);
|
|
628
|
+
if (defaultBaseUrl && resolved === 'https://www.aliwork.com' && (!cookieData || !cookieData.base_url)) {
|
|
477
629
|
return defaultBaseUrl.replace(/\/+$/, '');
|
|
478
630
|
}
|
|
479
631
|
return resolved;
|
|
@@ -491,19 +643,79 @@ function collectResponseText(res, onEnd) {
|
|
|
491
643
|
});
|
|
492
644
|
}
|
|
493
645
|
|
|
646
|
+
function splitRequestAuthArgs(cookiesOrOptions, maybeOptions) {
|
|
647
|
+
if (Array.isArray(cookiesOrOptions)) {
|
|
648
|
+
return {
|
|
649
|
+
cookies: cookiesOrOptions,
|
|
650
|
+
options: maybeOptions || {},
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
cookies: [],
|
|
655
|
+
options: cookiesOrOptions || {},
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function resolveCookieAuthCookies(cookies) {
|
|
660
|
+
if (Array.isArray(cookies) && cookies.length > 0) {
|
|
661
|
+
return cookies;
|
|
662
|
+
}
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function buildCookieAuthHeaders(baseUrl, cookies, optionsOverride = {}) {
|
|
667
|
+
const parsedUrl = new URL(baseUrl);
|
|
668
|
+
const requestHost = parsedUrl.hostname;
|
|
669
|
+
const cookieList = resolveCookieAuthCookies(cookies);
|
|
670
|
+
const filteredCookies = cookieList.filter(c => {
|
|
671
|
+
const cookieDomain = (c.domain || '').replace(/^\./, '');
|
|
672
|
+
return !cookieDomain || requestHost === cookieDomain || requestHost.endsWith('.' + cookieDomain);
|
|
673
|
+
});
|
|
674
|
+
const effectiveCookies = filteredCookies.length > 0 ? filteredCookies : cookieList;
|
|
675
|
+
const cookieHeader = effectiveCookies.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
676
|
+
const { csrfToken } = extractInfoFromCookies(effectiveCookies);
|
|
677
|
+
const headers = {};
|
|
678
|
+
if (cookieHeader) {
|
|
679
|
+
headers.Cookie = cookieHeader;
|
|
680
|
+
}
|
|
681
|
+
if (optionsOverride.csrfToken || csrfToken) {
|
|
682
|
+
headers.global_csrf_token = optionsOverride.csrfToken || csrfToken;
|
|
683
|
+
}
|
|
684
|
+
return headers;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async function resolveRequestAuthHeaders(baseUrl, cookies, optionsOverride = {}) {
|
|
688
|
+
if (Array.isArray(cookies) && cookies.length > 0) {
|
|
689
|
+
return buildCookieAuthHeaders(baseUrl, cookies, optionsOverride);
|
|
690
|
+
}
|
|
691
|
+
const auth = await resolveBearerAuthHeaders(optionsOverride);
|
|
692
|
+
return auth.headers;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function createNonJsonResponseResult(statusCode, data) {
|
|
696
|
+
const responseText = data === null || data === undefined ? '' : String(data);
|
|
697
|
+
return {
|
|
698
|
+
success: false,
|
|
699
|
+
errorMsg: `HTTP ${statusCode}: ` + t('common.response_not_json'),
|
|
700
|
+
__httpStatus: statusCode,
|
|
701
|
+
__nonJsonResponse: true,
|
|
702
|
+
__emptyBody: responseText.trim().length === 0,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
494
706
|
/**
|
|
495
707
|
* 发送 HTTP POST 请求(application/x-www-form-urlencoded)
|
|
496
708
|
* @param {string} baseUrl
|
|
497
709
|
* @param {string} requestPath
|
|
498
710
|
* @param {string} postData - querystring 格式
|
|
499
|
-
* @param {
|
|
711
|
+
* @param {Array} cookies
|
|
500
712
|
* @returns {Promise<object>}
|
|
501
713
|
*/
|
|
502
|
-
async function httpPost(baseUrl, requestPath, postData,
|
|
714
|
+
async function httpPost(baseUrl, requestPath, postData, cookiesOrOptions, maybeOptions) {
|
|
503
715
|
const https = require('https');
|
|
504
716
|
const http = require('http');
|
|
505
|
-
|
|
506
|
-
const
|
|
717
|
+
const { cookies, options: optionsOverride } = splitRequestAuthArgs(cookiesOrOptions, maybeOptions);
|
|
718
|
+
const authHeaders = await resolveRequestAuthHeaders(baseUrl, cookies, optionsOverride);
|
|
507
719
|
const bodyData = postData === null || postData === undefined ? '' : String(postData);
|
|
508
720
|
|
|
509
721
|
return new Promise((resolve, reject) => {
|
|
@@ -523,9 +735,9 @@ async function httpPost(baseUrl, requestPath, postData, optionsOverride = {}) {
|
|
|
523
735
|
Origin: baseUrl,
|
|
524
736
|
Referer: optionsOverride.referer || baseUrl + '/',
|
|
525
737
|
'x-requested-with': 'XMLHttpRequest',
|
|
526
|
-
...
|
|
738
|
+
...authHeaders,
|
|
527
739
|
},
|
|
528
|
-
timeout: 30000,
|
|
740
|
+
timeout: optionsOverride.timeout || 30000,
|
|
529
741
|
};
|
|
530
742
|
|
|
531
743
|
const req = requestModule.request(options, (res) => {
|
|
@@ -547,10 +759,16 @@ async function httpPost(baseUrl, requestPath, postData, optionsOverride = {}) {
|
|
|
547
759
|
resolve({ __needLogin: true });
|
|
548
760
|
return;
|
|
549
761
|
}
|
|
762
|
+
if (isCsrfTokenExpired(parsed)) {
|
|
763
|
+
resolve({ __csrfExpired: true });
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
550
766
|
resolve(parsed);
|
|
551
767
|
} catch {
|
|
552
|
-
|
|
553
|
-
|
|
768
|
+
if (!optionsOverride.silentStatus) {
|
|
769
|
+
warn(t('common.http_response', data.substring(0, 500)));
|
|
770
|
+
}
|
|
771
|
+
resolve(createNonJsonResponseResult(res.statusCode, data));
|
|
554
772
|
}
|
|
555
773
|
});
|
|
556
774
|
});
|
|
@@ -568,11 +786,11 @@ async function httpPost(baseUrl, requestPath, postData, optionsOverride = {}) {
|
|
|
568
786
|
});
|
|
569
787
|
}
|
|
570
788
|
|
|
571
|
-
async function httpPostJson(baseUrl, requestPath, payload,
|
|
789
|
+
async function httpPostJson(baseUrl, requestPath, payload, cookiesOrOptions, maybeOptions) {
|
|
572
790
|
const https = require('https');
|
|
573
791
|
const http = require('http');
|
|
574
|
-
|
|
575
|
-
const
|
|
792
|
+
const { cookies, options: optionsOverride } = splitRequestAuthArgs(cookiesOrOptions, maybeOptions);
|
|
793
|
+
const authHeaders = await resolveRequestAuthHeaders(baseUrl, cookies, optionsOverride);
|
|
576
794
|
|
|
577
795
|
return new Promise((resolve, reject) => {
|
|
578
796
|
const parsedUrl = new URL(baseUrl);
|
|
@@ -592,7 +810,7 @@ async function httpPostJson(baseUrl, requestPath, payload, optionsOverride = {})
|
|
|
592
810
|
Origin: baseUrl,
|
|
593
811
|
Referer: optionsOverride.referer || baseUrl + '/',
|
|
594
812
|
'x-requested-with': 'XMLHttpRequest',
|
|
595
|
-
...
|
|
813
|
+
...authHeaders,
|
|
596
814
|
},
|
|
597
815
|
timeout: optionsOverride.timeout || 30000,
|
|
598
816
|
};
|
|
@@ -616,10 +834,16 @@ async function httpPostJson(baseUrl, requestPath, payload, optionsOverride = {})
|
|
|
616
834
|
resolve({ __needLogin: true });
|
|
617
835
|
return;
|
|
618
836
|
}
|
|
837
|
+
if (isCsrfTokenExpired(parsed)) {
|
|
838
|
+
resolve({ __csrfExpired: true });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
619
841
|
resolve(parsed);
|
|
620
842
|
} catch {
|
|
621
|
-
|
|
622
|
-
|
|
843
|
+
if (!optionsOverride.silentStatus) {
|
|
844
|
+
warn(t('common.http_response', data.substring(0, 500)));
|
|
845
|
+
}
|
|
846
|
+
resolve(createNonJsonResponseResult(res.statusCode, data));
|
|
623
847
|
}
|
|
624
848
|
});
|
|
625
849
|
});
|
|
@@ -641,15 +865,15 @@ async function httpPostJson(baseUrl, requestPath, payload, optionsOverride = {})
|
|
|
641
865
|
* @param {string} baseUrl
|
|
642
866
|
* @param {string} requestPath
|
|
643
867
|
* @param {object} queryParams
|
|
644
|
-
* @param {
|
|
868
|
+
* @param {Array} cookies
|
|
645
869
|
* @returns {Promise<object>}
|
|
646
870
|
*/
|
|
647
|
-
async function httpGet(baseUrl, requestPath, queryParams,
|
|
871
|
+
async function httpGet(baseUrl, requestPath, queryParams, cookiesOrOptions, maybeOptions) {
|
|
648
872
|
const https = require('https');
|
|
649
873
|
const http = require('http');
|
|
650
874
|
const querystring = require('querystring');
|
|
651
|
-
|
|
652
|
-
const
|
|
875
|
+
const { cookies, options: optionsOverride } = splitRequestAuthArgs(cookiesOrOptions, maybeOptions);
|
|
876
|
+
const authHeaders = await resolveRequestAuthHeaders(baseUrl, cookies, optionsOverride);
|
|
653
877
|
|
|
654
878
|
return new Promise((resolve, reject) => {
|
|
655
879
|
const parsedUrl = new URL(baseUrl);
|
|
@@ -667,9 +891,9 @@ async function httpGet(baseUrl, requestPath, queryParams, optionsOverride = {})
|
|
|
667
891
|
Origin: baseUrl,
|
|
668
892
|
Referer: baseUrl + '/',
|
|
669
893
|
'x-requested-with': 'XMLHttpRequest',
|
|
670
|
-
...
|
|
894
|
+
...authHeaders,
|
|
671
895
|
},
|
|
672
|
-
timeout: 30000,
|
|
896
|
+
timeout: optionsOverride.timeout || 30000,
|
|
673
897
|
};
|
|
674
898
|
|
|
675
899
|
const req = requestModule.request(options, (res) => {
|
|
@@ -691,10 +915,16 @@ async function httpGet(baseUrl, requestPath, queryParams, optionsOverride = {})
|
|
|
691
915
|
resolve({ __needLogin: true });
|
|
692
916
|
return;
|
|
693
917
|
}
|
|
918
|
+
if (isCsrfTokenExpired(parsed)) {
|
|
919
|
+
resolve({ __csrfExpired: true });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
694
922
|
resolve(parsed);
|
|
695
923
|
} catch {
|
|
696
|
-
|
|
697
|
-
|
|
924
|
+
if (!optionsOverride.silentStatus) {
|
|
925
|
+
warn(t('common.http_response', data.substring(0, 500)));
|
|
926
|
+
}
|
|
927
|
+
resolve(createNonJsonResponseResult(res.statusCode, data));
|
|
698
928
|
}
|
|
699
929
|
});
|
|
700
930
|
});
|
|
@@ -714,31 +944,16 @@ async function httpGet(baseUrl, requestPath, queryParams, optionsOverride = {})
|
|
|
714
944
|
/**
|
|
715
945
|
* 带自动重登录的请求封装。
|
|
716
946
|
* @param {Function} requestFn - 接受 authRef 返回 Promise 的工厂函数
|
|
717
|
-
* @param {object} authRef - { baseUrl,
|
|
947
|
+
* @param {object} authRef - { csrfToken, cookies, baseUrl, cookieData }
|
|
718
948
|
* @returns {Promise<object>}
|
|
719
949
|
*/
|
|
720
|
-
function tokenAuthRequiredResult() {
|
|
721
|
-
return {
|
|
722
|
-
success: false,
|
|
723
|
-
__needLogin: true,
|
|
724
|
-
errorCode: 'TOKEN_AUTH_REQUIRED',
|
|
725
|
-
errorMsg: 'not_logged_in: token auth is unavailable. Run openyida login first.',
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function tokenRefreshFailedResult(error) {
|
|
730
|
-
return {
|
|
731
|
-
success: false,
|
|
732
|
-
errorCode: 'TOKEN_REFRESH_FAILED',
|
|
733
|
-
errorMsg: error && error.message
|
|
734
|
-
? error.message
|
|
735
|
-
: 'token_refresh_failed: failed to refresh access token. Please retry later.',
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
950
|
async function requestWithAutoLogin(requestFn, authRef) {
|
|
740
951
|
const result = await requestFn(authRef);
|
|
741
952
|
|
|
953
|
+
if (isExplicitCookieAuthRef(authRef)) {
|
|
954
|
+
return result;
|
|
955
|
+
}
|
|
956
|
+
|
|
742
957
|
if (result && result.__needLogin) {
|
|
743
958
|
const { tokenRefresh, isRefreshAuthRequired } = require('../auth/token-auth');
|
|
744
959
|
try {
|
|
@@ -755,8 +970,13 @@ async function requestWithAutoLogin(requestFn, authRef) {
|
|
|
755
970
|
}
|
|
756
971
|
return tokenRefreshFailedResult(new Error('token_refresh_failed'));
|
|
757
972
|
}
|
|
758
|
-
const refreshedTokenData =
|
|
973
|
+
const refreshedTokenData = refreshResult.access_token
|
|
974
|
+
? normalizeRefreshedTokenAuthData(refreshResult, currentBaseUrl)
|
|
975
|
+
: loadTokenAuthData(authRef && authRef.projectRoot, currentBaseUrl);
|
|
759
976
|
if (refreshedTokenData) {
|
|
977
|
+
if (!authRef) {
|
|
978
|
+
authRef = {};
|
|
979
|
+
}
|
|
760
980
|
authRef.authData = refreshedTokenData;
|
|
761
981
|
authRef.baseUrl = resolveBaseUrl(refreshedTokenData);
|
|
762
982
|
authRef.authMode = refreshedTokenData.auth_mode || refreshedTokenData.authMode || '';
|
|
@@ -777,14 +997,62 @@ async function requestWithAutoLogin(requestFn, authRef) {
|
|
|
777
997
|
return result;
|
|
778
998
|
}
|
|
779
999
|
|
|
1000
|
+
function isExplicitCookieAuthRef(authRef) {
|
|
1001
|
+
if (!authRef || !Array.isArray(authRef.cookies) || authRef.cookies.length === 0) {
|
|
1002
|
+
return false;
|
|
1003
|
+
}
|
|
1004
|
+
const authData = authRef.authData || authRef.cookieData || {};
|
|
1005
|
+
const authMode = authRef.authMode || authRef.auth_mode || authData.auth_mode || authData.authMode;
|
|
1006
|
+
const authSource = authRef.authSource || authRef.auth_source || authData.auth_source || authData.authSource;
|
|
1007
|
+
return authMode !== 'token' && authSource !== 'token';
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function normalizeRefreshedTokenAuthData(refreshResult, fallbackBaseUrl) {
|
|
1011
|
+
if (!refreshResult || !refreshResult.access_token) {
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
return {
|
|
1015
|
+
corp_id: refreshResult.corp_id,
|
|
1016
|
+
user_id: refreshResult.user_id,
|
|
1017
|
+
base_url: resolveBaseUrl(refreshResult, fallbackBaseUrl),
|
|
1018
|
+
auth_source: refreshResult.auth_source === 'env' ? 'env' : 'token',
|
|
1019
|
+
auth_mode: 'token',
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function tokenAuthRequiredResult() {
|
|
1024
|
+
return {
|
|
1025
|
+
success: false,
|
|
1026
|
+
__needLogin: true,
|
|
1027
|
+
errorCode: 'TOKEN_AUTH_REQUIRED',
|
|
1028
|
+
errorMsg: 'not_logged_in: token auth is unavailable. Run openyida login first.',
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function tokenRefreshFailedResult(error) {
|
|
1033
|
+
return {
|
|
1034
|
+
success: false,
|
|
1035
|
+
errorCode: 'TOKEN_REFRESH_FAILED',
|
|
1036
|
+
errorMsg: error && error.message
|
|
1037
|
+
? error.message
|
|
1038
|
+
: 'token_refresh_failed: failed to refresh access token. Please retry later.',
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
780
1042
|
module.exports = {
|
|
781
1043
|
detectActiveTool,
|
|
782
1044
|
hasDesktopEnvironment,
|
|
783
1045
|
findProjectRoot,
|
|
1046
|
+
extractInfoFromCookies,
|
|
1047
|
+
parseCookieHeader,
|
|
1048
|
+
getAuthStatus,
|
|
784
1049
|
loadAuthData,
|
|
1050
|
+
loadCookieData,
|
|
785
1051
|
triggerLogin,
|
|
1052
|
+
refreshCsrfToken,
|
|
786
1053
|
resolveBaseUrl,
|
|
787
1054
|
isLoginExpired,
|
|
1055
|
+
isCsrfTokenExpired,
|
|
788
1056
|
httpPost,
|
|
789
1057
|
httpPostJson,
|
|
790
1058
|
httpGet,
|