openyida 2026.10.23-beta.6 → 2026.10.25
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 -22
- package/bin/yida.js +15 -18
- 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 -8
- package/lib/auth/token-store.js +0 -1
- package/lib/core/agent-capabilities.js +5 -38
- 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/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 -13
- package/yida-skills/SKILL.md +7 -8
- package/yida-skills/references/setup-and-env.md +0 -4
- 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-page/SKILL.md +1 -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-login/SKILL.md +0 -18
- 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-desktop.js +0 -153
- package/lib/auth/qwenwork-login.js +0 -87
- package/lib/auth/qwenwork.js +0 -37
- package/lib/core/qwenwork-login-guidance.js +0 -32
- package/scripts/install-qwenwork-pre-beta.js +0 -40
- package/yida-skills/references/qwenwork-desktop-login.md +0 -12
package/README.md
CHANGED
|
@@ -143,25 +143,6 @@ openyida login --check-only --json
|
|
|
143
143
|
openyida auth status
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
For QwenWork cloud identity diagnostics, run inside the actual cloud execution environment:
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
openyida auth qwenwork user-info --json
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
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.
|
|
153
|
-
|
|
154
|
-
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.
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
const { exchangeQwenWorkAgentToken, getQwenWorkUserInfo } = require('openyida/lib/auth/qwenwork-client');
|
|
158
|
-
async function readCloudUser() {
|
|
159
|
-
const credential = await exchangeQwenWorkAgentToken();
|
|
160
|
-
// The result contains QwenWork identifiers, not YiDA IDs.
|
|
161
|
-
return getQwenWorkUserInfo(credential);
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
146
|
To reuse an existing shared login profile for the current project, switch first instead of starting a new OAuth login:
|
|
166
147
|
|
|
167
148
|
```bash
|
|
@@ -476,9 +457,9 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
476
457
|
|
|
477
458
|
| Command | Description |
|
|
478
459
|
|---------|-------------|
|
|
479
|
-
| `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 |
|
|
480
461
|
| `openyida logout` | Logout / unbind current project auth |
|
|
481
|
-
| `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 |
|
|
482
463
|
| `openyida org <list\|switch> [--json] [--corp-id <corpId>]` | Organization management (list / switch existing profiles first) |
|
|
483
464
|
| `openyida env [--json\|setup\|list\|show\|switch\|add\|remove] [options]` | Detect AI tool environment & token login state |
|
|
484
465
|
|
|
@@ -492,6 +473,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
492
473
|
| `openyida app-entry get <appType> [--json]` | Read frontend and management runtime entries |
|
|
493
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 |
|
|
494
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 |
|
|
495
477
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
|
|
496
478
|
| `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
|
|
497
479
|
| `openyida design-plan catalog [--json]` | List available planning themes and page patterns |
|
|
@@ -535,6 +517,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
535
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 |
|
|
536
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 |
|
|
537
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 |
|
|
538
521
|
| `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
|
|
539
522
|
| `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check resources, frontend menu hiding and application theme delivery |
|
|
540
523
|
| `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
|
|
@@ -552,7 +535,7 @@ When preparing a PR that needs a personal Fork, or when you ask how to support t
|
|
|
552
535
|
| Command | Description |
|
|
553
536
|
|---------|-------------|
|
|
554
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) |
|
|
555
|
-
| `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.) |
|
|
556
539
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
557
540
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
558
541
|
| `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
|
package/bin/yida.js
CHANGED
|
@@ -588,27 +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
|
-
// 桌面准备完成后继续执行公开命令,由真实登录态决定结果。
|
|
605
|
-
if (command === 'login' && args.includes('--qwenwork')) {
|
|
606
|
-
const loginArgs = applyLoginEnvironmentFlags(args, { inferTargetUrl: true });
|
|
607
|
-
if (hasHelpFlag(loginArgs)) { printLoginHelp(); return; }
|
|
608
|
-
assertNoUnsupportedLegacyLoginFlags(rawArgs, loginArgs);
|
|
609
|
-
await require('../lib/auth/qwenwork-login').run(loginArgs, buildTokenLoginOptions(loginArgs));
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
602
|
|
|
613
603
|
const { maybeAutoUpdate } = require('../lib/core/update');
|
|
614
604
|
const updateResult = await maybeAutoUpdate({
|
|
@@ -728,10 +718,6 @@ async function main() {
|
|
|
728
718
|
|
|
729
719
|
case 'auth': {
|
|
730
720
|
const subCommand = args[0];
|
|
731
|
-
if (subCommand === 'qwenwork') {
|
|
732
|
-
await require('../lib/auth/qwenwork').run(args.slice(1));
|
|
733
|
-
break;
|
|
734
|
-
}
|
|
735
721
|
const authArgs = applyLoginEnvironmentFlags(args.slice(1), { inferTargetUrl: true });
|
|
736
722
|
const { getAuthStatus } = require('../lib/core/utils');
|
|
737
723
|
const { tokenLogin, tokenLogout, tokenRefresh } = require('../lib/auth/token-auth');
|
|
@@ -808,6 +794,12 @@ async function main() {
|
|
|
808
794
|
break;
|
|
809
795
|
}
|
|
810
796
|
|
|
797
|
+
case 'solution-template': {
|
|
798
|
+
const { run } = require('../lib/solution-template/solution-template');
|
|
799
|
+
await run(args);
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
|
|
811
803
|
case 'corp-efficiency': {
|
|
812
804
|
const { run } = require('../lib/corp-efficiency/corp-efficiency');
|
|
813
805
|
await run(args);
|
|
@@ -855,6 +847,11 @@ async function main() {
|
|
|
855
847
|
await run(args);
|
|
856
848
|
break;
|
|
857
849
|
}
|
|
850
|
+
case 'form-message': {
|
|
851
|
+
const { run } = require('../lib/app/form-message');
|
|
852
|
+
await run(args);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
858
855
|
case 'print-template': {
|
|
859
856
|
const { run } = require('../lib/print-template/print-template');
|
|
860
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,9 +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 && normalizeBaseUrl(responseBusinessBaseUrl, null) !== session.base_url) {
|
|
506
|
-
throw createTokenAuthRequiredError('refresh_origin_mismatch');
|
|
507
|
-
}
|
|
508
502
|
const businessBaseUrl = session.base_url || authBaseUrlValue;
|
|
509
503
|
const normalized = normalizeTokenResponse({
|
|
510
504
|
...data,
|
|
@@ -513,7 +507,6 @@ async function tokenRefreshUnlocked(options, session) {
|
|
|
513
507
|
user_id: data.user_id || data.userId || session.user_id,
|
|
514
508
|
user_name: data.user_name || data.userName || session.user_name,
|
|
515
509
|
refresh_token: data.refresh_token || data.refreshToken || session.refresh_token,
|
|
516
|
-
credential_source: data.credential_source || session.credential_source,
|
|
517
510
|
}, businessBaseUrl, clientId);
|
|
518
511
|
if (!normalized.access_token) {
|
|
519
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,
|