openyida 2026.10.27-beta.1 → 2026.10.27
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 +5 -24
- package/bin/yida.js +15 -20
- package/lib/app/form-message.js +352 -0
- package/lib/app/services/canvas-page-schema-builder.js +43 -1
- package/lib/auth/token-auth.js +1 -12
- package/lib/auth/token-store.js +0 -1
- package/lib/core/agent-capabilities.js +5 -60
- package/lib/core/command-manifest.js +73 -10
- package/lib/core/locales/en.js +30 -19
- package/lib/core/locales/zh.js +29 -18
- package/lib/core/task-center.js +32 -4
- package/lib/navigation/page-navigation.js +62 -0
- package/lib/permission/get-permission.js +11 -38
- package/lib/permission/permit-package-service.js +80 -20
- package/lib/permission/save-permission.js +35 -73
- package/lib/samples/openyida-scaffold/canvas-navigation.jsx +6 -2
- package/lib/solution-template/solution-template.js +666 -0
- package/lib/task-center/contract.js +26 -1
- package/lib/task-center/loader.js +5 -0
- package/lib/task-center/navigation.js +41 -0
- package/lib/task-center/scaffold.js +9 -0
- package/package.json +5 -18
- package/yida-skills/SKILL.md +7 -13
- package/yida-skills/references/setup-and-env.md +1 -10
- package/yida-skills/references/yida-api.md +3 -1
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +4 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +2 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +2 -0
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +2 -2
- package/yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md +29 -1
- package/yida-skills/skills/yida-create-app/SKILL.md +18 -0
- package/yida-skills/skills/yida-create-page/SKILL.md +19 -0
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/references/application-style-library.md +2 -0
- package/yida-skills/skills/yida-design/references/application-theme-consistency.md +4 -0
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +27 -44
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +3 -1
- package/yida-skills/skills/yida-form-message/SKILL.md +53 -0
- package/yida-skills/skills/yida-form-message/references/guide.md +111 -0
- package/yida-skills/skills/yida-form-permission/SKILL.md +1 -1
- package/yida-skills/skills/yida-login/SKILL.md +1 -24
- package/yida-skills/skills/yida-solution-template/SKILL.md +98 -0
- package/yida-skills/skills/yida-task-center/SKILL.md +3 -1
- package/yida-skills/skills-index.json +26 -0
- package/bin/yida-pre-beta.js +0 -11
- package/lib/auth/qwenwork-client.js +0 -190
- package/lib/auth/qwenwork-cloud.js +0 -93
- package/lib/auth/qwenwork-desktop.js +0 -154
- package/lib/auth/qwenwork-login.js +0 -108
- package/lib/auth/qwenwork.js +0 -37
- package/lib/core/qwenwork-login-guidance.js +0 -35
- package/scripts/install-qwenwork-pre-beta.js +0 -40
- package/yida-skills/references/qwenwork-cloud-login.md +0 -21
- package/yida-skills/references/qwenwork-desktop-login.md +0 -12
package/README.md
CHANGED
|
@@ -143,27 +143,6 @@ openyida login --check-only --json
|
|
|
143
143
|
openyida auth status
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
In QwenWork web, `openyida login --qwenwork --json` (or ordinary `login`) sends the sandbox Agent Token and gateway origin directly to the selected YiDA authentication endpoint. YiDA validates the identity through the trusted gateway’s fixed `/api/agent-gw/yida/user_info` endpoint, then issues its own short-lived session. This login flow no longer calls the legacy exchange or `/user/info` endpoints. Login preparation stays silent; failures stop without browser OAuth. Desktop and other hosts retain their existing authentication paths.
|
|
147
|
-
|
|
148
|
-
For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
openyida auth qwenwork user-info --json
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
This command exchanges the injected `QWENWORK_AGENT_TOKEN` (legacy: `MULE_AGENT_TOKEN` / `AGENT_TOKEN`) at `QWENWORK_GATEWAY_URL` (legacy: `MULE_GATEWAY_URL`), then queries `/user/info` on the same origin. It reports identity and scope metadata without printing or persisting either credential. It does not create an OpenYida login session, and QwenWork organization IDs are not YiDA `corpId` values. Existing OAuth/profile behavior is unchanged.
|
|
155
|
-
|
|
156
|
-
Programmatic callers can use `exchangeQwenWorkAgentToken`, `getQwenWorkUserInfo` and `getQwenWorkIdentity` from `lib/auth/qwenwork-client.js`. The exchange function returns an in-memory `access_token` credential for the identity query or a future server-side login exchange; callers must not log or persist it.
|
|
157
|
-
|
|
158
|
-
```js
|
|
159
|
-
const { exchangeQwenWorkAgentToken, getQwenWorkUserInfo } = require('openyida/lib/auth/qwenwork-client');
|
|
160
|
-
async function readCloudUser() {
|
|
161
|
-
const credential = await exchangeQwenWorkAgentToken();
|
|
162
|
-
// The result contains QwenWork identifiers, not YiDA IDs.
|
|
163
|
-
return getQwenWorkUserInfo(credential);
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
146
|
To reuse an existing shared login profile for the current project, switch first instead of starting a new OAuth login:
|
|
168
147
|
|
|
169
148
|
```bash
|
|
@@ -478,9 +457,9 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
478
457
|
|
|
479
458
|
| Command | Description |
|
|
480
459
|
|---------|-------------|
|
|
481
|
-
| `openyida login [
|
|
460
|
+
| `openyida login [target-url] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps\|--alibaba] [--client-id <clientId>] [--endpoint <url>] [--no-browser]` | Login with OAuth token mode |
|
|
482
461
|
| `openyida logout` | Logout / unbind current project auth |
|
|
483
|
-
| `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch
|
|
462
|
+
| `openyida auth <status\|login\|refresh\|logout\|profiles\|profile switch>` | Token login state and profile management |
|
|
484
463
|
| `openyida org <list\|switch> [--json] [--corp-id <corpId>]` | Organization management (list / switch existing profiles first) |
|
|
485
464
|
| `openyida env [--json\|setup\|list\|show\|switch\|add\|remove] [options]` | Detect AI tool environment & token login state |
|
|
486
465
|
|
|
@@ -494,6 +473,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
494
473
|
| `openyida app-entry get <appType> [--json]` | Read frontend and management runtime entries |
|
|
495
474
|
| `openyida app-entry set <appType> [--frontend <url>\|--frontend-page <formUuid>] [--management <url>\|--management-page <formUuid>] [--clear-frontend] [--clear-management] [--json]` | Register application entries with partial update and readback |
|
|
496
475
|
| `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
|
|
476
|
+
| `openyida solution-template <search\|detail\|prepare\|ai-build\|enable\|status> [templateId\|requestId] [options] [--json]` | Solution templates for independent local agents: discovery, cloud AI handoff and direct enable |
|
|
497
477
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
|
|
498
478
|
| `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
|
|
499
479
|
| `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
|
|
@@ -537,6 +517,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
537
517
|
| `openyida print-template <list\|inspect\|fields\|build\|create\|copy\|save\|restore\|settings\|status\|order\|render\|files> <appType> <formUuid> [templateId] [--config <file>] [--scene <order\|quotation\|delivery\|receipt\|approval\|codes>] [--bindings <file>] [--name <title>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--status START\|STOP] [--instance <id>] [--page <n>] [--size <n>] [--allow-permission-change] [--json] \| print-template <list\|render> --detail-url <url> [--template-id <id>] [--json]` | Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs |
|
|
538
518
|
| `openyida form-view <list\|inspect\|save\|create\|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]` | Native form views: list, inspect and configure columns, widths, frozen columns and filters |
|
|
539
519
|
| `openyida custom-button <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native custom buttons: inspect and configure form edits, automation flows and page links |
|
|
520
|
+
| `openyida form-message <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [messageId] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native form notifications: inspect triggers, recipients and templates, then safely maintain bindings |
|
|
540
521
|
| `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
|
|
541
522
|
| `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check resources, frontend menu hiding and application theme delivery |
|
|
542
523
|
| `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
|
|
@@ -554,7 +535,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
554
535
|
| Command | Description |
|
|
555
536
|
|---------|-------------|
|
|
556
537
|
| `openyida data <query\|get\|create\|update\|transfer> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
557
|
-
| `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
|
|
538
|
+
| `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|link\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
|
|
558
539
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
559
540
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
560
541
|
| `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
|
package/bin/yida.js
CHANGED
|
@@ -588,29 +588,17 @@ async function main() {
|
|
|
588
588
|
}
|
|
589
589
|
return;
|
|
590
590
|
}
|
|
591
|
+
// Template-only preflight; managed runs retain their existing guard contract.
|
|
592
|
+
if (command === 'solution-template' && !hasHelpFlag(rawArgs) && !readManagedContext()) {
|
|
593
|
+
require('../lib/solution-template/solution-template').assertIndependentLocalAgent();
|
|
594
|
+
}
|
|
591
595
|
commandReceipt = beginCommandReceipt({ command, args: rawArgs });
|
|
592
596
|
process.once('exit', code => {
|
|
593
597
|
try {commandReceipt.finish(code);} catch {process.exitCode = 1;}
|
|
594
598
|
});
|
|
595
599
|
assertManagedInvocation(command, rawArgs);
|
|
596
600
|
applyQuietFlag();
|
|
597
|
-
// 一次性票据只在当前进程消费,不能先自更新并重启命令。
|
|
598
|
-
switch (command) {
|
|
599
|
-
case 'login-qwenwork':
|
|
600
|
-
await require('../lib/auth/qwenwork-desktop').run(args);
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
601
|
applyGlobalEnvironmentFlags();
|
|
604
|
-
// 网页态由 CLI 消费宿主凭据;桌面态继续消费宿主准备的登录态。
|
|
605
|
-
const webLogin = (command === 'login' || (command === 'auth' && args[0] === 'login')) &&
|
|
606
|
-
require('../lib/core/qwenwork-login-guidance').isQwenWorkWebRuntime(require('../lib/core/utils').detectRuntimeCapabilities());
|
|
607
|
-
if ((command === 'login' && args.includes('--qwenwork')) || webLogin) {
|
|
608
|
-
const loginArgs = applyLoginEnvironmentFlags(command === 'auth' ? args.slice(1) : args, { inferTargetUrl: true });
|
|
609
|
-
if (hasHelpFlag(loginArgs)) { printLoginHelp(); return; }
|
|
610
|
-
assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
|
|
611
|
-
await require('../lib/auth/qwenwork-login').run(loginArgs, buildTokenLoginOptions(loginArgs));
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
602
|
|
|
615
603
|
const { maybeAutoUpdate } = require('../lib/core/update');
|
|
616
604
|
const updateResult = await maybeAutoUpdate({
|
|
@@ -730,10 +718,6 @@ async function main() {
|
|
|
730
718
|
|
|
731
719
|
case 'auth': {
|
|
732
720
|
const subCommand = args[0];
|
|
733
|
-
if (subCommand === 'qwenwork') {
|
|
734
|
-
await require('../lib/auth/qwenwork').run(args.slice(1));
|
|
735
|
-
break;
|
|
736
|
-
}
|
|
737
721
|
const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
|
|
738
722
|
const { getAuthStatus } = require('../lib/core/utils');
|
|
739
723
|
const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
|
|
@@ -810,6 +794,12 @@ async function main() {
|
|
|
810
794
|
break;
|
|
811
795
|
}
|
|
812
796
|
|
|
797
|
+
case 'solution-template': {
|
|
798
|
+
const { run } = require('../lib/solution-template/solution-template');
|
|
799
|
+
await run(args);
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
|
|
813
803
|
case 'corp-efficiency': {
|
|
814
804
|
const { run } = require('../lib/corp-efficiency/corp-efficiency');
|
|
815
805
|
await run(args);
|
|
@@ -857,6 +847,11 @@ async function main() {
|
|
|
857
847
|
await run(args);
|
|
858
848
|
break;
|
|
859
849
|
}
|
|
850
|
+
case 'form-message': {
|
|
851
|
+
const { run } = require('../lib/app/form-message');
|
|
852
|
+
await run(args);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
860
855
|
case 'print-template': {
|
|
861
856
|
const { run } = require('../lib/print-template/print-template');
|
|
862
857
|
await run(args);
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { isDeepStrictEqual } = require('util');
|
|
7
|
+
const { CliError } = require('../core/cli-error');
|
|
8
|
+
const { t } = require('../core/i18n');
|
|
9
|
+
const { resolveProjectRoot } = require('../core/utils');
|
|
10
|
+
const { createYidaClient } = require('../core/yida-client');
|
|
11
|
+
|
|
12
|
+
const ACTIONS = ['list', 'inspect', 'options', 'create', 'update', 'delete'];
|
|
13
|
+
const RECEIVER_TYPES = new Set(['employee', 'role', 'formField', 'variable']);
|
|
14
|
+
const FORM_TRIGGERS = new Set(['FORM_DATA_CREATE', 'FORM_DATA_EDIT', 'FORM_DATA_DELETE', 'FORM_COMMENT']);
|
|
15
|
+
const SYSTEM_VARIABLES = new Set(['originator']);
|
|
16
|
+
const MESSAGE_FIELD_COMPONENTS = new Set([
|
|
17
|
+
'TextareaField', 'EmployeeField', 'SelectField', 'MultiSelectField', 'DateField', 'TextField',
|
|
18
|
+
'RadioField', 'NumberField', 'CheckboxField', 'RateField', 'CascadeDateField', 'DepartmentSelectField',
|
|
19
|
+
]);
|
|
20
|
+
const RECIPIENT_FIELD_COMPONENTS = new Set(['EmployeeField']);
|
|
21
|
+
const ID = /^[A-Za-z0-9_-]+$/;
|
|
22
|
+
const TEMPLATES = Object.freeze({
|
|
23
|
+
workNotice: { uuid: 'system_default_work_notice', type: 'workNotice', content: true, target: 'person' },
|
|
24
|
+
detailImage: { uuid: '8fbf8a81-c775-4570-b367-86d4d719a85f', type: 'card', fields: true, target: 'person' },
|
|
25
|
+
imageCard: { uuid: '39caf717-df43-46e1-98f9-564fdd1dc789', type: 'card', content: true, target: 'person' },
|
|
26
|
+
detailText: { uuid: '97bd36d0-64d8-4c50-96e8-f02312655700', type: 'card', fields: true, target: 'person' },
|
|
27
|
+
commentCard: { uuid: 'c298cd88-0de6-44c3-9023-c93e0d7db9ac', type: 'card', content: true, target: 'person' },
|
|
28
|
+
pinnedImage: { uuid: 'cdcdf076-3625-4da7-9a61-571591594209', type: 'ceilingCard', content: true, target: 'group' },
|
|
29
|
+
pinnedText: { uuid: '68240de5-8d9e-416a-b5b2-c71dca0ab703', type: 'ceilingCard', content: true, target: 'group' },
|
|
30
|
+
});
|
|
31
|
+
const TEMPLATE_BY_UUID = new Map(Object.entries(TEMPLATES).map(([key, item]) => [item.uuid, { key, ...item }]));
|
|
32
|
+
|
|
33
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
const clone = value => JSON.parse(JSON.stringify(value));
|
|
35
|
+
|
|
36
|
+
/** Build a stable fingerprint for concurrency checks and destructive actions. */
|
|
37
|
+
function hash(value) {
|
|
38
|
+
const stable = item => Array.isArray(item) ? item.map(stable) : object(item)
|
|
39
|
+
? Object.fromEntries(Object.keys(item).sort().map(key => [key, stable(item[key])])) : item;
|
|
40
|
+
return crypto.createHash('sha256').update(JSON.stringify(stable(value))).digest('hex');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fail(code, key, detail = '', details) {
|
|
44
|
+
throw new CliError(t(`form_message.${key}`, detail), { code: `FORM_MESSAGE_${code}`, details });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function safe(value) {
|
|
48
|
+
if (!value || typeof value !== 'object') {return;}
|
|
49
|
+
for (const key of Object.keys(value)) {
|
|
50
|
+
if (['__proto__', 'prototype', 'constructor'].includes(key)) {fail('INVALID_CONFIG', 'invalid_config', key);}
|
|
51
|
+
safe(value[key]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseJson(value, label) {
|
|
56
|
+
try { return typeof value === 'string' ? JSON.parse(value) : value; }
|
|
57
|
+
catch (_) { return fail('INVALID_CONFIG', 'invalid_config', label); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function unwrap(response, endpoint) {
|
|
61
|
+
if (!response || response.success !== true || response.__needLogin || response.__csrfExpired || response.content === false) {
|
|
62
|
+
fail('API_ERROR', 'api_error', response?.errorMsg || response?.message || endpoint, {
|
|
63
|
+
endpoint, platformErrorCode: response?.errorCode || null, needLogin: !!response?.__needLogin,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return response.content;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Normalize a native binding while retaining fields required for lossless backup and update. */
|
|
70
|
+
function normalize(raw) {
|
|
71
|
+
if (!object(raw) || raw.id === undefined || raw.id === null || !String(raw.id).trim()) {
|
|
72
|
+
fail('INVALID_RESPONSE', 'invalid_response', 'id');
|
|
73
|
+
}
|
|
74
|
+
const cardConfig = raw.cardConfig ? parseJson(raw.cardConfig, 'cardConfig') : {};
|
|
75
|
+
if (!object(cardConfig)) {fail('INVALID_RESPONSE', 'invalid_response', 'cardConfig');}
|
|
76
|
+
const recipients = [];
|
|
77
|
+
const add = (source, type) => String(source || '').split(',').map(value => value.trim()).filter(Boolean)
|
|
78
|
+
.forEach(value => recipients.push({ type, value }));
|
|
79
|
+
add(raw.persons, 'employee'); add(raw.roleId, 'role'); add(raw.formField, 'formField'); add(raw.variable, 'variable');
|
|
80
|
+
const nativeGroups = Array.isArray(cardConfig.groups) ? clone(cardConfig.groups) : [];
|
|
81
|
+
const template = TEMPLATE_BY_UUID.get(cardConfig.templateUuid) || {};
|
|
82
|
+
const buttons = Array.isArray(cardConfig.buttons) ? clone(cardConfig.buttons) : [];
|
|
83
|
+
return {
|
|
84
|
+
id: String(raw.id),
|
|
85
|
+
formUuid: raw.formUuid || null,
|
|
86
|
+
trigger: raw.phase || null,
|
|
87
|
+
messageType: raw.messageType || 'NORMAL',
|
|
88
|
+
recipients,
|
|
89
|
+
groups: nativeGroups,
|
|
90
|
+
template: {
|
|
91
|
+
key: template.key || null,
|
|
92
|
+
uuid: cardConfig.templateUuid || raw.messageUuid || null,
|
|
93
|
+
type: cardConfig.templateType || template.type || null,
|
|
94
|
+
title: cardConfig.cardTitle || '',
|
|
95
|
+
content: cardConfig.mainContent || '',
|
|
96
|
+
fields: Array.isArray(cardConfig.datas) ? clone(cardConfig.datas) : [],
|
|
97
|
+
image: Array.isArray(cardConfig.messageImage) ? clone(cardConfig.messageImage) : [],
|
|
98
|
+
contentType: cardConfig.messageContentType || (raw.messageUuid ? 'TEMPLATE' : 'CUSTOM'),
|
|
99
|
+
platformTemplateUuid: raw.messageUuid && !String(raw.messageUuid).includes('#') ? raw.messageUuid : null,
|
|
100
|
+
button: buttons[0] || null,
|
|
101
|
+
},
|
|
102
|
+
advanced: {
|
|
103
|
+
openMore: !!cardConfig.openMore,
|
|
104
|
+
activityIds: String(raw.activityId || '').split(',').filter(Boolean),
|
|
105
|
+
condition: raw.conditionJson ? parseJson(raw.conditionJson, 'conditionJson') : null,
|
|
106
|
+
processId: raw.processId || null,
|
|
107
|
+
outcome: String(raw.outResult || '').split(',').filter(Boolean),
|
|
108
|
+
currentGroup: raw.curDingGroup === 'y',
|
|
109
|
+
sendWorkNotice: raw.isSendMessage === 'y',
|
|
110
|
+
},
|
|
111
|
+
native: clone(raw),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseArgs(args) {
|
|
116
|
+
const positional = [];
|
|
117
|
+
const flags = {};
|
|
118
|
+
for (let i = 0; i < args.length; i++) {
|
|
119
|
+
const arg = args[i];
|
|
120
|
+
if (Object.hasOwn(flags, arg)) {fail('USAGE', 'usage');}
|
|
121
|
+
if (['--json', '--yes', '--help', '-h'].includes(arg)) { flags[arg] = true; continue; }
|
|
122
|
+
if (['--config', '--backup', '--expected-hash'].includes(arg)) {
|
|
123
|
+
if (!args[i + 1] || args[i + 1].startsWith('-')) {fail('USAGE', 'usage');}
|
|
124
|
+
flags[arg] = args[++i]; continue;
|
|
125
|
+
}
|
|
126
|
+
if (arg.startsWith('-')) {fail('USAGE', 'usage');}
|
|
127
|
+
positional.push(arg);
|
|
128
|
+
}
|
|
129
|
+
if (flags['--help'] || flags['-h']) {return { help: true };}
|
|
130
|
+
const [action, appType, formUuid, messageId] = positional;
|
|
131
|
+
const targeted = ['inspect', 'update', 'delete'].includes(action);
|
|
132
|
+
if (!ACTIONS.includes(action) || positional.length !== (targeted ? 4 : 3)
|
|
133
|
+
|| !ID.test(appType || '') || !ID.test(formUuid || '') || (targeted && !ID.test(messageId || ''))
|
|
134
|
+
|| (['create', 'update'].includes(action) && !flags['--config'])
|
|
135
|
+
|| (action === 'delete' && (!flags['--yes'] || !flags['--expected-hash']))) {fail('USAGE', 'usage');}
|
|
136
|
+
const allowed = { list: [], inspect: [], options: [], create: ['--config', '--backup'],
|
|
137
|
+
update: ['--config', '--backup', '--expected-hash'], delete: ['--yes', '--backup', '--expected-hash'] };
|
|
138
|
+
if (Object.keys(flags).some(key => key !== '--json' && !allowed[action].includes(key))) {fail('USAGE', 'usage');}
|
|
139
|
+
return { action, appType, formUuid, messageId, flags };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function fieldId(field) { return field.fieldId || field.id || field.componentId; }
|
|
143
|
+
function fieldType(field) { return field.componentName || field.fieldType || field.type; }
|
|
144
|
+
|
|
145
|
+
function normalizeTemplate(value) {
|
|
146
|
+
if (!object(value)) {fail('INVALID_CONFIG', 'invalid_config', 'template');}
|
|
147
|
+
const registry = value.key ? TEMPLATES[value.key] : TEMPLATE_BY_UUID.get(value.uuid);
|
|
148
|
+
if (!registry || (value.uuid && value.uuid !== registry.uuid) || (value.type && value.type !== registry.type)) {
|
|
149
|
+
fail('INVALID_CONFIG', 'invalid_config', 'template.key / template.uuid / template.type');
|
|
150
|
+
}
|
|
151
|
+
const result = { ...value, key: value.key || TEMPLATE_BY_UUID.get(registry.uuid).key,
|
|
152
|
+
uuid: registry.uuid, type: registry.type };
|
|
153
|
+
result.title = String(result.title || '').trim();
|
|
154
|
+
result.content = String(result.content || '').trim();
|
|
155
|
+
result.fields = result.fields || [];
|
|
156
|
+
result.image = result.image || [];
|
|
157
|
+
result.contentType = 'CUSTOM';
|
|
158
|
+
result.platformTemplateUuid = null;
|
|
159
|
+
result.button = result.button || null;
|
|
160
|
+
if (!result.title || result.title.replace(/\$!\{\w+\}/g, '').length > 50) {fail('INVALID_CONFIG', 'invalid_config', 'template.title');}
|
|
161
|
+
if (registry.content && !result.content) {fail('INVALID_CONFIG', 'invalid_config', 'template.content');}
|
|
162
|
+
if (registry.fields && (!Array.isArray(result.fields) || !result.fields.length)) {fail('INVALID_CONFIG', 'invalid_config', 'template.fields');}
|
|
163
|
+
if (!Array.isArray(result.fields) || !Array.isArray(result.image)) {fail('INVALID_CONFIG', 'invalid_config', 'template.fields / template.image');}
|
|
164
|
+
if (result.button !== undefined && result.button !== null) {
|
|
165
|
+
if (!object(result.button) || typeof result.button.name !== 'string'
|
|
166
|
+
|| !['formInstDetail', 'comment', 'custom'].includes(result.button.type)) {fail('INVALID_CONFIG', 'invalid_config', 'template.button');}
|
|
167
|
+
if (result.button.type === 'custom') {
|
|
168
|
+
let url; try { url = new URL(result.button.value); } catch (_) { fail('INVALID_CONFIG', 'invalid_config', 'template.button.value'); }
|
|
169
|
+
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password) {fail('INVALID_CONFIG', 'invalid_config', 'template.button.value');}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { result, registry };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Validate references against the target form metadata before a mutation. */
|
|
176
|
+
function validateConfig(value, metadata) {
|
|
177
|
+
if (!object(value) || !FORM_TRIGGERS.has(value.trigger)) {
|
|
178
|
+
fail('UNSUPPORTED_TRIGGER', 'unsupported_trigger', value?.trigger || '');
|
|
179
|
+
}
|
|
180
|
+
const formType = metadata.form.formType;
|
|
181
|
+
if (formType === 'process') {fail('UNSUPPORTED_FORM', 'unsupported_form', formType);}
|
|
182
|
+
const { result: template, registry } = normalizeTemplate(value.template);
|
|
183
|
+
const recipients = value.recipients || [];
|
|
184
|
+
const groups = value.groups || [];
|
|
185
|
+
if (!Array.isArray(recipients) || !Array.isArray(groups)) {fail('INVALID_CONFIG', 'invalid_config', 'recipients / groups');}
|
|
186
|
+
if (registry.target === 'person' && !recipients.length) {fail('INVALID_CONFIG', 'invalid_config', 'recipients');}
|
|
187
|
+
if (registry.target === 'group' && !groups.length) {fail('INVALID_CONFIG', 'invalid_config', 'groups');}
|
|
188
|
+
if (recipients.length > 100) {fail('INVALID_CONFIG', 'invalid_config', 'recipients (max 100)');}
|
|
189
|
+
const seen = new Set();
|
|
190
|
+
for (const receiver of recipients) {
|
|
191
|
+
if (!object(receiver) || !RECEIVER_TYPES.has(receiver.type) || !ID.test(String(receiver.value || ''))
|
|
192
|
+
|| seen.has(`${receiver.type}:${receiver.value}`)) {fail('INVALID_CONFIG', 'invalid_config', 'recipients');}
|
|
193
|
+
seen.add(`${receiver.type}:${receiver.value}`);
|
|
194
|
+
if (receiver.type === 'variable' && !SYSTEM_VARIABLES.has(receiver.value)) {fail('INVALID_CONFIG', 'invalid_config', `recipient variable: ${receiver.value}`);}
|
|
195
|
+
if (receiver.type === 'formField' && !metadata.recipientFields.some(field => fieldId(field) === receiver.value)) {
|
|
196
|
+
fail('INVALID_CONFIG', 'invalid_config', `recipient field: ${receiver.value}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const group of groups) {
|
|
200
|
+
if (!object(group) || !ID.test(String(group.cid || group.id || ''))) {fail('INVALID_CONFIG', 'invalid_config', 'groups');}
|
|
201
|
+
}
|
|
202
|
+
for (const item of template.fields) {
|
|
203
|
+
if (!object(item) || !['variable', 'formField'].includes(item.type) || typeof item.title !== 'string' || !item.title.trim()
|
|
204
|
+
|| !ID.test(String(item.value || ''))) {fail('INVALID_CONFIG', 'invalid_config', 'template.fields');}
|
|
205
|
+
if (item.type === 'variable' && !metadata.systemFields.some(field => field.value === item.value)) {
|
|
206
|
+
fail('INVALID_CONFIG', 'invalid_config', `template variable: ${item.value}`);
|
|
207
|
+
}
|
|
208
|
+
if (item.type === 'formField' && !metadata.messageFields.some(field => fieldId(field) === item.value)) {
|
|
209
|
+
fail('INVALID_CONFIG', 'invalid_config', `template field: ${item.value}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { trigger: value.trigger, recipients: clone(recipients), groups: clone(groups), template };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function patchConfig(base, patch) {
|
|
216
|
+
if (!object(patch) || !Object.keys(patch).length || Object.keys(patch).some(key => !['trigger', 'recipients', 'groups', 'template'].includes(key))) {
|
|
217
|
+
fail('INVALID_CONFIG', 'invalid_config', 'trigger, recipients, groups, template');
|
|
218
|
+
}
|
|
219
|
+
safe(patch);
|
|
220
|
+
const next = { ...base, ...patch };
|
|
221
|
+
if (patch.template) {next.template = { ...base.template, ...patch.template };}
|
|
222
|
+
return next;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function editable(message) {
|
|
226
|
+
return { trigger: message.trigger, recipients: clone(message.recipients), groups: clone(message.groups), template: clone(message.template) };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function serialize(config, formUuid, messageId, native = {}) {
|
|
230
|
+
const byType = type => config.recipients.filter(item => item.type === type).map(item => item.value).join(',');
|
|
231
|
+
const registered = config.template.key ? TEMPLATES[config.template.key] : TEMPLATE_BY_UUID.get(config.template.uuid);
|
|
232
|
+
const nativeCardConfig = native.cardConfig ? parseJson(native.cardConfig, 'cardConfig') : {};
|
|
233
|
+
const cardConfig = {
|
|
234
|
+
...nativeCardConfig,
|
|
235
|
+
openMore: false,
|
|
236
|
+
groups: config.groups,
|
|
237
|
+
messageImage: config.template.image,
|
|
238
|
+
cardTitle: config.template.title,
|
|
239
|
+
mainContent: config.template.content,
|
|
240
|
+
datas: config.template.fields,
|
|
241
|
+
templateUuid: config.template.uuid || registered.uuid,
|
|
242
|
+
templateType: config.template.type || registered.type,
|
|
243
|
+
messageContentType: 'CUSTOM',
|
|
244
|
+
buttons: config.template.button ? [config.template.button] : [],
|
|
245
|
+
};
|
|
246
|
+
return {
|
|
247
|
+
...clone(native),
|
|
248
|
+
service: native.service || '', formUuid, phase: config.trigger, outResult: native.outResult || '',
|
|
249
|
+
formField: byType('formField'), studentField: native.studentField || '', roleId: byType('role'),
|
|
250
|
+
persons: byType('employee'), varField: native.varField || '', messageUuid: '', id: messageId || '',
|
|
251
|
+
variable: byType('variable'), email: native.email || '', formFieldEmail: native.formFieldEmail || '',
|
|
252
|
+
activityId: native.activityId || '', messageType: native.messageType || 'NORMAL',
|
|
253
|
+
curDingGroup: native.curDingGroup || 'n', isSendMessage: native.isSendMessage || 'n',
|
|
254
|
+
processId: native.processId || '', conditionJson: native.conditionJson || '', cardConfig: JSON.stringify(cardConfig),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function execute(parsed, client = createYidaClient()) {
|
|
259
|
+
const { action, appType, formUuid, messageId, flags } = parsed;
|
|
260
|
+
const prefix = `/${appType}/query/`;
|
|
261
|
+
const read = async (endpoint, params = {}) => unwrap(await client.get(prefix + endpoint, { formUuid, ...params }), endpoint);
|
|
262
|
+
const list = async () => {
|
|
263
|
+
const rows = await read('formMsgTpl/listFormMessageBind.json');
|
|
264
|
+
if (!Array.isArray(rows)) {fail('INVALID_RESPONSE', 'invalid_response', 'messages');}
|
|
265
|
+
const messages = rows.map(normalize);
|
|
266
|
+
if (messages.some(item => item.formUuid && item.formUuid !== formUuid)
|
|
267
|
+
|| new Set(messages.map(item => item.id)).size !== messages.length) {fail('INVALID_RESPONSE', 'invalid_response', 'messages');}
|
|
268
|
+
return messages;
|
|
269
|
+
};
|
|
270
|
+
const metadata = async () => {
|
|
271
|
+
const info = await read('formdesign/getFormAndAppInfo.json', { appType });
|
|
272
|
+
const form = info?.formDatas?.values?.find(item => item.formUuid === formUuid);
|
|
273
|
+
if (!form || !Array.isArray(form.fields)) {fail('INVALID_RESPONSE', 'invalid_response', 'form metadata');}
|
|
274
|
+
const fields = form.fields;
|
|
275
|
+
return {
|
|
276
|
+
form: { formUuid, formType: form.formType, title: form.title, processCode: form.processCode || null },
|
|
277
|
+
triggers: [...FORM_TRIGGERS],
|
|
278
|
+
readOnlyTriggers: ['RECUR_JOB_FIELD', 'INST_START', 'INST_COMPLETE', 'INST_NODE', 'TASK_GENERATE', 'TASK_COMPLETED'],
|
|
279
|
+
templates: Object.entries(TEMPLATES).map(([key, item]) => ({ key, ...item })),
|
|
280
|
+
systemFields: [
|
|
281
|
+
{ value: 'formInstId', componentName: 'TextField' }, { value: 'originatorName', componentName: 'TextField' },
|
|
282
|
+
{ value: 'originator', componentName: 'TextField' }, { value: 'createTime', componentName: 'DateField' },
|
|
283
|
+
{ value: 'modifiedTime', componentName: 'DateField' }, { value: 'serialNo', componentName: 'TextField' },
|
|
284
|
+
{ value: 'formTitle', componentName: 'TextField' }, { value: 'title', componentName: 'TextField' },
|
|
285
|
+
],
|
|
286
|
+
messageFields: fields.filter(field => MESSAGE_FIELD_COMPONENTS.has(fieldType(field))),
|
|
287
|
+
recipientFields: fields.filter(field => RECIPIENT_FIELD_COMPONENTS.has(fieldType(field))),
|
|
288
|
+
recipientVariables: [...SYSTEM_VARIABLES],
|
|
289
|
+
supportsToggle: false,
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
// Link to the form's native notification settings in the authenticated environment.
|
|
293
|
+
const configUrl = new URL(`/${appType}/admin/${formUuid}/settings/messageSetting`, client.getAuthRef().baseUrl).href;
|
|
294
|
+
const output = { success: true, action, appType, formUuid, configUrl };
|
|
295
|
+
if (action === 'options') {return { ...output, options: await metadata() };}
|
|
296
|
+
const beforeList = await list();
|
|
297
|
+
const before = messageId ? beforeList.find(item => item.id === messageId) : null;
|
|
298
|
+
if (messageId && !before) {fail('NOT_FOUND', 'not_found', messageId);}
|
|
299
|
+
if (action === 'list') {return { ...output, messages: beforeList, totalCount: beforeList.length, supportsToggle: false };}
|
|
300
|
+
if (action === 'inspect') {return { ...output, messageId, message: before, configHash: hash(editable(before)), supportsToggle: false };}
|
|
301
|
+
if (flags['--expected-hash'] && flags['--expected-hash'] !== hash(editable(before))) {fail('CONFLICT', 'conflict');}
|
|
302
|
+
|
|
303
|
+
let expected;
|
|
304
|
+
if (action !== 'delete') {
|
|
305
|
+
let patch;
|
|
306
|
+
try { patch = parseJson(fs.readFileSync(path.resolve(flags['--config']), 'utf8'), 'config'); }
|
|
307
|
+
catch (error) { if (error instanceof CliError) {throw error;} fail('INVALID_CONFIG', 'invalid_config', 'config file'); }
|
|
308
|
+
const defaults = { trigger: 'FORM_DATA_CREATE', recipients: [{ type: 'variable', value: 'originator' }], groups: [],
|
|
309
|
+
template: { key: 'workNotice', title: '', content: '', fields: [], image: [], button: null } };
|
|
310
|
+
expected = validateConfig(patchConfig(before ? editable(before) : defaults, patch), await metadata());
|
|
311
|
+
if (before && isDeepStrictEqual(editable(before), expected)) {
|
|
312
|
+
return { ...output, messageId, changed: false, message: before, configHash: hash(expected), readbackVerified: true };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const snapshot = beforeList.map(item => [item.id, hash(item.native)]);
|
|
316
|
+
if (!isDeepStrictEqual(snapshot, (await list()).map(item => [item.id, hash(item.native)]))) {fail('CONFLICT', 'conflict');}
|
|
317
|
+
const backupPath = flags['--backup'] ? path.resolve(flags['--backup']) : path.join(resolveProjectRoot().projectRoot,
|
|
318
|
+
'.cache', 'openyida', 'form-message', `${Date.now()}-${crypto.randomUUID()}.json`);
|
|
319
|
+
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
320
|
+
fs.writeFileSync(backupPath, JSON.stringify({ appType, formUuid, action, messages: beforeList }, null, 2), { flag: 'wx', mode: 0o600 });
|
|
321
|
+
const details = { target: { appType, formUuid, ...(messageId ? { messageId } : {}) }, backupPath,
|
|
322
|
+
retrySafe: false, sideEffectState: 'unknown', readbackAllowed: true };
|
|
323
|
+
const endpoint = action === 'delete' ? 'formMsgTpl/removeMessageBind.json' : 'formMsgTpl/saveOrUpdateMessageBind.json';
|
|
324
|
+
const params = action === 'delete' ? { id: messageId } : serialize(expected, formUuid, messageId, before?.native);
|
|
325
|
+
try { unwrap(await client.postFormOnce(prefix + endpoint, params), endpoint); }
|
|
326
|
+
catch (error) { throw new CliError(error.message, { code: error.code || 'FORM_MESSAGE_WRITE_FAILED', details: { ...error.details, ...details } }); }
|
|
327
|
+
try {
|
|
328
|
+
const after = await list();
|
|
329
|
+
if (action === 'delete') {
|
|
330
|
+
if (after.some(item => item.id === messageId)) {fail('READBACK_MISMATCH', 'readback_mismatch');}
|
|
331
|
+
return { ...output, messageId, changed: true, backupPath, readbackVerified: true };
|
|
332
|
+
}
|
|
333
|
+
const added = after.filter(item => !beforeList.some(old => old.id === item.id));
|
|
334
|
+
const saved = action === 'create' ? (added.length === 1 ? added[0] : null) : after.find(item => item.id === messageId);
|
|
335
|
+
if (!saved || !isDeepStrictEqual(editable(saved), expected)) {fail('READBACK_MISMATCH', 'readback_mismatch');}
|
|
336
|
+
return { ...output, messageId: saved.id, changed: true, backupPath, readbackVerified: true,
|
|
337
|
+
message: saved, configHash: hash(expected) };
|
|
338
|
+
} catch (error) {
|
|
339
|
+
throw new CliError(error.message, { code: error.code || 'FORM_MESSAGE_READBACK_FAILED',
|
|
340
|
+
details: { ...details, mutationAccepted: true, sideEffectState: 'accepted_unverified' } });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function run(args, options = {}) {
|
|
345
|
+
const parsed = parseArgs(args);
|
|
346
|
+
if (parsed.help) { process.stdout.write(t('form_message.usage') + '\n'); return; }
|
|
347
|
+
const result = await execute(parsed, options.client);
|
|
348
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
module.exports = { ACTIONS, TEMPLATES, run, execute, parseArgs, normalize, validateConfig, patchConfig, serialize, hash, editable };
|
|
@@ -10,7 +10,16 @@ const {
|
|
|
10
10
|
findEmojiInValue,
|
|
11
11
|
} = require('../../core/no-emoji-detector');
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const { createPageNavigation } = require('../../navigation/page-navigation');
|
|
14
|
+
const { createTaskCenterContract } = require('../../task-center/contract');
|
|
15
|
+
const { createTaskCenterLoader } = require('../../task-center/loader');
|
|
16
|
+
const { createTaskCenterNavigation } = require('../../task-center/navigation');
|
|
17
|
+
|
|
18
|
+
const CANVAS_YIDA_API_BRIDGE_SOURCE = `${createPageNavigation.toString()}
|
|
19
|
+
${createTaskCenterNavigation.toString()}
|
|
20
|
+
${createTaskCenterContract.toString()}
|
|
21
|
+
${createTaskCenterLoader.toString()}
|
|
22
|
+
var OPENYIDA_YIDA_FORM_API_METHODS = [
|
|
14
23
|
'saveFormData',
|
|
15
24
|
'updateFormData',
|
|
16
25
|
'searchFormDataIds',
|
|
@@ -239,8 +248,15 @@ function openyidaCreateRouterBridge(utils) {
|
|
|
239
248
|
|
|
240
249
|
function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
241
250
|
var utils = context && context.utils;
|
|
251
|
+
var navigation = createPageNavigation(window);
|
|
252
|
+
var taskNavigation = createTaskCenterNavigation();
|
|
253
|
+
var taskCounts = createTaskCenterLoader(createTaskCenterContract(), function() { return yidaBridge; });
|
|
242
254
|
var methodNames = openyidaCollectUtilsMethodNames(utils);
|
|
243
255
|
function invoke(methodName) {
|
|
256
|
+
if (methodName === 'openPage') { return navigation.open.apply(null, Array.prototype.slice.call(arguments, 1)); }
|
|
257
|
+
if (['taskCenterCount', 'taskCenterLink', 'openTaskCenter'].indexOf(methodName) !== -1) {
|
|
258
|
+
return bridge[methodName].apply(null, Array.prototype.slice.call(arguments, 1));
|
|
259
|
+
}
|
|
244
260
|
if (!utils || typeof utils[methodName] !== 'function') {
|
|
245
261
|
throw new Error('this.utils.' + methodName + ' is not available');
|
|
246
262
|
}
|
|
@@ -251,6 +267,8 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
251
267
|
apiGroups: {
|
|
252
268
|
utility: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
253
269
|
router: ['push'],
|
|
270
|
+
navigation: ['openPage', 'taskCenterLink', 'openTaskCenter'],
|
|
271
|
+
tasks: ['taskCenterCount'],
|
|
254
272
|
yida: OPENYIDA_KNOWN_YIDA_API_METHODS.slice()
|
|
255
273
|
},
|
|
256
274
|
knownMethods: OPENYIDA_UTIL_API_METHODS.slice(),
|
|
@@ -259,9 +277,33 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
|
|
|
259
277
|
return !utils || typeof utils[methodName] !== 'function';
|
|
260
278
|
}),
|
|
261
279
|
invoke: invoke,
|
|
280
|
+
openPage: navigation.open,
|
|
281
|
+
taskCenterLink: function(options) {
|
|
282
|
+
return taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
|
|
283
|
+
},
|
|
284
|
+
taskCenterCount: async function(options) {
|
|
285
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)
|
|
286
|
+
|| Object.keys(options).some(function(key) { return ['scope', 'appType', 'kind', 'filters'].indexOf(key) === -1; })) {
|
|
287
|
+
var error = new Error('TASK_FILTER_INVALID: options');
|
|
288
|
+
error.code = 'TASK_FILTER_INVALID';
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
var target = taskNavigation.target({ scope: options.scope, appType: options.appType, kind: options.kind, baseUrl: window.location.origin });
|
|
292
|
+
var filters = createTaskCenterContract().normalizeFilters(target.kind, options.filters === undefined ? {} : options.filters, 'app-query');
|
|
293
|
+
return taskCounts.count(target.kind, Object.assign({}, filters, { transport: 'app-query' },
|
|
294
|
+
target.scope === 'corp' ? { scope: 'corp' } : { appType: target.appType }));
|
|
295
|
+
},
|
|
296
|
+
openTaskCenter: function(options) {
|
|
297
|
+
var target = taskNavigation.target(Object.assign({ baseUrl: window.location.origin }, options));
|
|
298
|
+
return navigation.open(target.pageUrl, { mode: target.mode });
|
|
299
|
+
},
|
|
262
300
|
router: openyidaCreateRouterBridge(utils),
|
|
263
301
|
yida: yidaBridge
|
|
264
302
|
};
|
|
303
|
+
['openPage', 'taskCenterLink', 'openTaskCenter', 'taskCenterCount'].forEach(function(methodName) {
|
|
304
|
+
if (bridge.availableMethods.indexOf(methodName) === -1) { bridge.availableMethods.push(methodName); }
|
|
305
|
+
});
|
|
306
|
+
bridge.missingKnownMethods = bridge.missingKnownMethods.filter(function(name) { return name !== 'openPage'; });
|
|
265
307
|
methodNames.forEach(function(methodName) {
|
|
266
308
|
if (!bridge[methodName]) {
|
|
267
309
|
bridge[methodName] = function() {
|
package/lib/auth/token-auth.js
CHANGED
|
@@ -175,7 +175,6 @@ function normalizeTokenResponse(response, baseUrl, clientId) {
|
|
|
175
175
|
);
|
|
176
176
|
return {
|
|
177
177
|
auth_mode: 'token',
|
|
178
|
-
credential_source: data.credential_source,
|
|
179
178
|
status: data.status || 'ok',
|
|
180
179
|
can_auto_use: true,
|
|
181
180
|
message: data.message || data.errorMsg || response.errorMsg,
|
|
@@ -487,9 +486,7 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
487
486
|
// Token exchange follows the active environment endpoint, just like the
|
|
488
487
|
// legacy login bootstrap. The response may return a different corp base_url
|
|
489
488
|
// for business requests and generated application links.
|
|
490
|
-
|
|
491
|
-
const desktopSession = session.credential_source === 'qwenwork_desktop';
|
|
492
|
-
const authBaseUrlValue = desktopSession ? session.base_url : resolveTokenBaseUrl({
|
|
489
|
+
const authBaseUrlValue = resolveTokenBaseUrl({
|
|
493
490
|
projectRoot: options.projectRoot,
|
|
494
491
|
endpoint: options.endpoint,
|
|
495
492
|
});
|
|
@@ -502,13 +499,6 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
502
499
|
requireOkResponse(response);
|
|
503
500
|
const data = getTokenPayload(response);
|
|
504
501
|
const responseBusinessBaseUrl = data.base_url || data.baseUrl;
|
|
505
|
-
if (desktopSession && responseBusinessBaseUrl !== undefined) {
|
|
506
|
-
try {
|
|
507
|
-
const value = new URL(responseBusinessBaseUrl);
|
|
508
|
-
if (typeof responseBusinessBaseUrl !== 'string' || responseBusinessBaseUrl.length > 2048 || /\s/.test(responseBusinessBaseUrl) ||
|
|
509
|
-
value.protocol !== 'https:' || value.username || value.password || value.pathname !== '/' || value.search || value.hash) { throw new Error(); }
|
|
510
|
-
} catch { throw createTokenAuthRequiredError('refresh_origin_mismatch'); }
|
|
511
|
-
}
|
|
512
502
|
const businessBaseUrl = session.base_url || authBaseUrlValue;
|
|
513
503
|
const normalized = normalizeTokenResponse({
|
|
514
504
|
...data,
|
|
@@ -517,7 +507,6 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
517
507
|
user_id: data.user_id || data.userId || session.user_id,
|
|
518
508
|
user_name: data.user_name || data.userName || session.user_name,
|
|
519
509
|
refresh_token: data.refresh_token || data.refreshToken || session.refresh_token,
|
|
520
|
-
credential_source: data.credential_source || session.credential_source,
|
|
521
510
|
}, businessBaseUrl, clientId);
|
|
522
511
|
if (!normalized.access_token) {
|
|
523
512
|
return {
|
package/lib/auth/token-store.js
CHANGED
|
@@ -276,7 +276,6 @@ function normalizeTokenSession(session = {}) {
|
|
|
276
276
|
);
|
|
277
277
|
const normalized = {
|
|
278
278
|
auth_mode: 'token',
|
|
279
|
-
credential_source: session.credential_source,
|
|
280
279
|
token_type: session.token_type || session.tokenType || 'Bearer',
|
|
281
280
|
access_token: session.access_token || session.accessToken,
|
|
282
281
|
refresh_token: session.refresh_token || session.refreshToken,
|