@xmanrui/dsh-im 4.9.0 → 4.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +70 -18
- package/lib/index.js +235 -235
- package/package.json +10 -6
- package/plugin-src/client/channels/dingtalk/api.js +23 -6
- package/plugin-src/client/channels/dingtalk/index.js +33 -6
- package/plugin-src/client/channels/dingtalk/styles.js +2 -0
- package/plugin-src/client/i18n.js +4 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +24 -0
- package/plugin-src/host/modern-harness-api.mjs +17 -8
- package/src/channels/dingtalk/connection-error.mjs +299 -0
- package/src/channels/dingtalk/dingtalk-controller.mjs +79 -34
- package/src/channels/dingtalk/dingtalk-runtime.mjs +9 -2
- package/src/channels/shared/i18n-en/dingtalk.mjs +23 -0
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
} from '../shared/connection-test.mjs';
|
|
13
13
|
import { t } from '../shared/i18n.mjs';
|
|
14
14
|
import { publicMessageFailure } from '../shared/message-failure.mjs';
|
|
15
|
+
import {
|
|
16
|
+
describeDingtalkConnectionFailure,
|
|
17
|
+
dingtalkPublicConnectionError,
|
|
18
|
+
dingtalkRuntimeStartError,
|
|
19
|
+
} from './connection-error.mjs';
|
|
15
20
|
|
|
16
21
|
const ACTIVE_ATTEMPT_STATES = new Set(['starting', 'pending', 'connecting']);
|
|
17
22
|
const TERMINAL_ATTEMPT_STATES = new Set(['connected', 'expired', 'failed', 'cancelled']);
|
|
@@ -216,12 +221,14 @@ export class DingtalkController {
|
|
|
216
221
|
try {
|
|
217
222
|
await this.#startRuntime(latest, clientSecret);
|
|
218
223
|
this.#errors.delete(latest.botId);
|
|
219
|
-
} catch {
|
|
220
|
-
this.#
|
|
221
|
-
latest
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.#rememberConnectionFailure({
|
|
226
|
+
config: latest,
|
|
227
|
+
clientSecret,
|
|
228
|
+
error,
|
|
229
|
+
fallbackMessage: '钉钉连接未就绪,请稍后重试。',
|
|
230
|
+
context: `bot ${latest.botId} failed to initialize`,
|
|
231
|
+
});
|
|
225
232
|
}
|
|
226
233
|
this.#touch();
|
|
227
234
|
});
|
|
@@ -318,12 +325,14 @@ export class DingtalkController {
|
|
|
318
325
|
try {
|
|
319
326
|
await this.#startRuntime(config, normalizedSecret);
|
|
320
327
|
this.#errors.delete(identity.botId);
|
|
321
|
-
} catch {
|
|
322
|
-
this.#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.#rememberConnectionFailure({
|
|
330
|
+
config,
|
|
331
|
+
clientSecret: normalizedSecret,
|
|
332
|
+
error,
|
|
333
|
+
fallbackMessage: '钉钉已接入,但消息连接暂未就绪,请稍后重试。',
|
|
334
|
+
context: `credential-bound bot ${identity.botId} is not ready`,
|
|
335
|
+
});
|
|
327
336
|
}
|
|
328
337
|
this.#touch();
|
|
329
338
|
});
|
|
@@ -380,11 +389,14 @@ export class DingtalkController {
|
|
|
380
389
|
await this.#startRuntime(config, clientSecret);
|
|
381
390
|
this.#errors.delete(botId);
|
|
382
391
|
} catch (error) {
|
|
383
|
-
this.#
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
392
|
+
const publicError = this.#rememberConnectionFailure({
|
|
393
|
+
config,
|
|
394
|
+
clientSecret,
|
|
395
|
+
error,
|
|
396
|
+
fallbackMessage: '钉钉连接仍未就绪,请稍后重试。',
|
|
397
|
+
context: `bot ${botId} failed to reconnect`,
|
|
398
|
+
});
|
|
399
|
+
throw dingtalkPublicConnectionError(publicError, error);
|
|
388
400
|
} finally {
|
|
389
401
|
this.#touch();
|
|
390
402
|
}
|
|
@@ -675,11 +687,13 @@ export class DingtalkController {
|
|
|
675
687
|
await rollback();
|
|
676
688
|
throw abortError();
|
|
677
689
|
}
|
|
678
|
-
this.#
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
690
|
+
this.#rememberConnectionFailure({
|
|
691
|
+
config,
|
|
692
|
+
clientSecret,
|
|
693
|
+
error,
|
|
694
|
+
fallbackMessage: '钉钉已接入,但消息连接暂未就绪,请稍后重试。',
|
|
695
|
+
context: `authorized bot ${identity.botId} is not ready`,
|
|
696
|
+
});
|
|
683
697
|
}
|
|
684
698
|
return { botId: identity.botId, alreadyConnected: Boolean(previousConfig) };
|
|
685
699
|
});
|
|
@@ -696,11 +710,14 @@ export class DingtalkController {
|
|
|
696
710
|
} catch (error) {
|
|
697
711
|
await this.#configStore.save(previousConfig).catch(() => undefined);
|
|
698
712
|
await this.#startRuntime(previousConfig, clientSecret).catch(() => undefined);
|
|
699
|
-
this.#
|
|
700
|
-
previousConfig
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
713
|
+
const publicError = this.#rememberConnectionFailure({
|
|
714
|
+
config: previousConfig,
|
|
715
|
+
clientSecret,
|
|
716
|
+
error,
|
|
717
|
+
fallbackMessage: '钉钉连接未就绪,请稍后重试。',
|
|
718
|
+
context: `bot ${previousConfig.botId} failed to apply updated settings`,
|
|
719
|
+
});
|
|
720
|
+
throw dingtalkPublicConnectionError(publicError, error);
|
|
704
721
|
} finally {
|
|
705
722
|
this.#touch();
|
|
706
723
|
}
|
|
@@ -711,13 +728,21 @@ export class DingtalkController {
|
|
|
711
728
|
if (this.#closed) throw abortError();
|
|
712
729
|
await this.#stopRuntime(config.botId);
|
|
713
730
|
if (this.#closed) throw abortError();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
731
|
+
let runtime;
|
|
732
|
+
try {
|
|
733
|
+
runtime = await this.#createRuntime({
|
|
734
|
+
botId: config.botId,
|
|
735
|
+
config: structuredClone(config),
|
|
736
|
+
clientSecret,
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
throw dingtalkRuntimeStartError('dingtalk-runtime-prepare-failed', error);
|
|
740
|
+
}
|
|
719
741
|
if (!runtime || typeof runtime.start !== 'function' || typeof runtime.stop !== 'function') {
|
|
720
|
-
throw
|
|
742
|
+
throw dingtalkRuntimeStartError(
|
|
743
|
+
'dingtalk-runtime-prepare-failed',
|
|
744
|
+
new TypeError('createRuntime returned an invalid DingTalk runtime'),
|
|
745
|
+
);
|
|
721
746
|
}
|
|
722
747
|
if (this.#closed) {
|
|
723
748
|
await runtime.stop().catch(() => undefined);
|
|
@@ -733,10 +758,30 @@ export class DingtalkController {
|
|
|
733
758
|
} catch (error) {
|
|
734
759
|
if (this.#runtimes.get(config.botId) === runtime) this.#runtimes.delete(config.botId);
|
|
735
760
|
await runtime.stop().catch(() => undefined);
|
|
736
|
-
throw error;
|
|
761
|
+
throw dingtalkRuntimeStartError('dingtalk-stream-connect-failed', error);
|
|
737
762
|
}
|
|
738
763
|
}
|
|
739
764
|
|
|
765
|
+
#rememberConnectionFailure({
|
|
766
|
+
config,
|
|
767
|
+
clientSecret,
|
|
768
|
+
error,
|
|
769
|
+
fallbackMessage,
|
|
770
|
+
context,
|
|
771
|
+
}) {
|
|
772
|
+
const failure = describeDingtalkConnectionFailure(error, {
|
|
773
|
+
fallbackMessage,
|
|
774
|
+
clientId: config.clientId,
|
|
775
|
+
clientSecret,
|
|
776
|
+
});
|
|
777
|
+
this.#errors.set(config.botId, failure.publicError);
|
|
778
|
+
this.#logger.error?.(
|
|
779
|
+
`[dsh-dingtalk] ${context} [${failure.publicError.referenceId}]`,
|
|
780
|
+
failure.diagnostic,
|
|
781
|
+
);
|
|
782
|
+
return failure.publicError;
|
|
783
|
+
}
|
|
784
|
+
|
|
740
785
|
async #stopRuntime(botId) {
|
|
741
786
|
const runtime = this.#runtimes.get(botId);
|
|
742
787
|
this.#runtimes.delete(botId);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import { sendRememberedConnectionTest } from '../shared/connection-test.mjs';
|
|
7
7
|
import { t } from '../shared/i18n.mjs';
|
|
8
8
|
import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
|
|
9
|
+
import { dingtalkRuntimeStartError } from './connection-error.mjs';
|
|
9
10
|
|
|
10
11
|
function nonEmptyString(value) {
|
|
11
12
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
@@ -223,10 +224,12 @@ export class DingtalkRuntime {
|
|
|
223
224
|
this.#status.startedAt = new Date().toISOString();
|
|
224
225
|
this.#status.dingtalkStreamState = 'connecting';
|
|
225
226
|
this.#status.lastError = null;
|
|
227
|
+
let startStage = 'dingtalk-harness-connect-failed';
|
|
226
228
|
|
|
227
229
|
try {
|
|
228
230
|
await this.#harness.ensureRunning({ signal });
|
|
229
231
|
this.#status.harnessReachable = true;
|
|
232
|
+
startStage = 'dingtalk-runtime-prepare-failed';
|
|
230
233
|
if (typeof this.#state.removePendingSenderByStaffId === 'function') {
|
|
231
234
|
for (const staffId of approvedSenderIds(this.#config)) {
|
|
232
235
|
await this.#state.removePendingSenderByStaffId(staffId);
|
|
@@ -249,6 +252,7 @@ export class DingtalkRuntime {
|
|
|
249
252
|
signal,
|
|
250
253
|
});
|
|
251
254
|
|
|
255
|
+
startStage = 'dingtalk-stream-client-load-failed';
|
|
252
256
|
const created = await this.#streamFactory({
|
|
253
257
|
clientId: this.#config.clientId,
|
|
254
258
|
clientSecret: this.#clientSecret,
|
|
@@ -266,6 +270,7 @@ export class DingtalkRuntime {
|
|
|
266
270
|
|
|
267
271
|
const client = this.#client;
|
|
268
272
|
const bridge = this.#bridge;
|
|
273
|
+
startStage = 'dingtalk-stream-listener-failed';
|
|
269
274
|
client.registerCallbackListener(this.#topic, (response) => {
|
|
270
275
|
if (this.#client !== client || this.#bridge !== bridge) return;
|
|
271
276
|
const callbackMessageId = nonEmptyString(response?.headers?.messageId);
|
|
@@ -314,6 +319,7 @@ export class DingtalkRuntime {
|
|
|
314
319
|
this.#callbackTasks.add(task);
|
|
315
320
|
});
|
|
316
321
|
|
|
322
|
+
startStage = 'dingtalk-stream-connect-failed';
|
|
317
323
|
await connectStream(
|
|
318
324
|
client,
|
|
319
325
|
this.#connectTimeoutMs,
|
|
@@ -336,11 +342,12 @@ export class DingtalkRuntime {
|
|
|
336
342
|
return this.status;
|
|
337
343
|
} catch (error) {
|
|
338
344
|
const aborted = signal.aborted;
|
|
345
|
+
const failure = aborted ? error : dingtalkRuntimeStartError(startStage, error);
|
|
339
346
|
this.#status.ready = false;
|
|
340
347
|
this.#status.dingtalkStreamState = aborted ? 'idle' : 'failed';
|
|
341
|
-
this.#status.lastError = aborted ? null : (
|
|
348
|
+
this.#status.lastError = aborted ? null : (failure?.message ?? String(failure));
|
|
342
349
|
await this.stop({ preserveError: !aborted });
|
|
343
|
-
throw
|
|
350
|
+
throw failure;
|
|
344
351
|
}
|
|
345
352
|
}
|
|
346
353
|
|
|
@@ -38,6 +38,29 @@ export default {
|
|
|
38
38
|
'钉钉机器人凭据缺失,请移除后重新扫码。': 'The DingTalk bot credentials are missing. Remove the bot and scan the QR code again.',
|
|
39
39
|
'钉钉连接未就绪,请稍后重试。': 'The DingTalk connection is not ready. Please try again later.',
|
|
40
40
|
'钉钉连接仍未就绪,请稍后重试。': 'The DingTalk connection is still not ready. Please try again later.',
|
|
41
|
+
'钉钉 Stream 连接失败:检测到代理依赖 agent-base 6.0.0。': 'The DingTalk Stream connection failed because proxy dependency agent-base 6.0.0 was detected.',
|
|
42
|
+
'请将 DSH profile 中的 agent-base@6 固定为 6.0.2 后重新安装依赖,或升级 pnpm 后重新解析 lockfile。': 'Pin agent-base@6 to 6.0.2 in the DSH profile and reinstall dependencies, or upgrade pnpm and re-resolve the lockfile.',
|
|
43
|
+
'插件无法连接本机 Harness。': 'The plugin could not connect to the local Harness.',
|
|
44
|
+
'请确认 dsh web 正常运行,并查看 dsh web 日志中相同参考号对应的诊断信息。': 'Make sure dsh web is running, then find the matching reference in the dsh web log.',
|
|
45
|
+
'钉钉 Stream SDK 加载失败。': 'The DingTalk Stream SDK could not be loaded.',
|
|
46
|
+
'请重新安装当前 DSH profile 的插件依赖,并查看 dsh web 日志中相同参考号对应的诊断信息。': 'Reinstall plugin dependencies in the current DSH profile, then find the matching reference in the dsh web log.',
|
|
47
|
+
'钉钉拒绝了当前应用凭据。': 'DingTalk rejected the current app credentials.',
|
|
48
|
+
'请核对 Client ID、Client Secret 和机器人权限后重试。': 'Verify the app credentials and bot permissions, then try again.',
|
|
49
|
+
'连接钉钉 Stream 超时。': 'The DingTalk Stream connection timed out.',
|
|
50
|
+
'请检查网络、代理和防火墙后重试;详细原因可在 dsh web 日志中按参考号查找。': 'Check the network, proxy, and firewall, then try again. Find details in the dsh web log using the reference.',
|
|
51
|
+
'无法解析钉钉服务地址。': 'The DingTalk service address could not be resolved.',
|
|
52
|
+
'请检查 DNS、网络和代理设置;详细原因可在 dsh web 日志中按参考号查找。': 'Check DNS, network, and proxy settings. Find details in the dsh web log using the reference.',
|
|
53
|
+
'钉钉 Stream 的 TLS 连接校验失败。': 'TLS validation failed for the DingTalk Stream connection.',
|
|
54
|
+
'请检查系统证书、代理证书或 HTTPS 中间代理;详细原因可在 dsh web 日志中按参考号查找。': 'Check system certificates, proxy certificates, or the HTTPS interception proxy. Find details in the dsh web log using the reference.',
|
|
55
|
+
'钉钉 Stream 无法通过当前代理建立连接。': 'DingTalk Stream could not connect through the current proxy.',
|
|
56
|
+
'请检查 HTTP_PROXY、HTTPS_PROXY、NO_PROXY 和代理连通性;详细原因可在 dsh web 日志中按参考号查找。': 'Check HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and proxy connectivity. Find details in the dsh web log using the reference.',
|
|
57
|
+
'钉钉 Stream 消息连接建立失败。': 'The DingTalk Stream message connection could not be established.',
|
|
58
|
+
'请检查网络和机器人配置;详细原因可在 dsh web 日志中按参考号查找。': 'Check the network and bot configuration. Find details in the dsh web log using the reference.',
|
|
59
|
+
'钉钉 Stream 消息监听初始化失败。': 'DingTalk Stream message-listener initialization failed.',
|
|
60
|
+
'请确认 dsh-im 与 dingtalk-stream 版本兼容,并按参考号查看 dsh web 日志。': 'Make sure dsh-im and dingtalk-stream are compatible, then inspect the dsh web log using the reference.',
|
|
61
|
+
'钉钉机器人运行环境初始化失败。': 'The DingTalk bot runtime could not be initialized.',
|
|
62
|
+
'请检查 DSH 数据目录、工作区和插件依赖,并按参考号查看 dsh web 日志。': 'Check the DSH data directory, workspace, and plugin dependencies, then inspect the dsh web log using the reference.',
|
|
63
|
+
'请在 dsh web 日志中查找相同参考号,以获取已脱敏的具体错误。': 'Find the matching reference in the dsh web log for the redacted error details.',
|
|
41
64
|
'扫码接入已取消。': 'QR code setup has been cancelled.',
|
|
42
65
|
'无法生成钉钉二维码,请稍后重试。': 'Could not generate the DingTalk QR code. Please try again later.',
|
|
43
66
|
'二维码已过期,请重新生成。': 'The QR code has expired. Generate a new one.',
|