mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 06:30:45 +08:00
fix: add actionable DingTalk connection diagnostics
This commit is contained in:
parent
3010535409
commit
1b05fa8d32
18 changed files with 1038 additions and 299 deletions
|
|
@ -85,11 +85,20 @@ function sanitizeMessage(value, fallback) {
|
|||
return message.replace(/([=:]\s*)[^\s,;,。]+/g, '$1••••••').slice(0, 240);
|
||||
}
|
||||
|
||||
function safeReferenceId(value) {
|
||||
const referenceId = optionalString(value, 40);
|
||||
return referenceId && /^DT-CONN-[A-F0-9]{8}$/.test(referenceId) ? referenceId : undefined;
|
||||
}
|
||||
|
||||
function normalizeError(value, fallbackCode, fallbackMessage) {
|
||||
if (!isRecord(value)) return undefined;
|
||||
const hint = sanitizeMessage(value.hint, '');
|
||||
const referenceId = safeReferenceId(value.referenceId);
|
||||
return {
|
||||
code: safeErrorCode(value.code, fallbackCode),
|
||||
message: sanitizeMessage(value.message, fallbackMessage),
|
||||
...(hint ? { hint } : {}),
|
||||
...(referenceId ? { referenceId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +117,15 @@ export function unwrapRpcResult(result) {
|
|||
throw new Error('钉钉服务返回了无法识别的响应');
|
||||
}
|
||||
if (!result.ok) {
|
||||
const error = new Error(sanitizeMessage(result.error?.message, '钉钉操作失败'));
|
||||
error.code = safeErrorCode(result.error?.code, 'DINGTALK_RPC_ERROR');
|
||||
const visible = normalizeError(
|
||||
result.error,
|
||||
'DINGTALK_RPC_ERROR',
|
||||
'钉钉操作失败',
|
||||
) ?? { code: 'DINGTALK_RPC_ERROR', message: '钉钉操作失败' };
|
||||
const error = new Error(visible.message);
|
||||
error.code = visible.code;
|
||||
if (visible.hint) error.hint = visible.hint;
|
||||
if (visible.referenceId) error.referenceId = visible.referenceId;
|
||||
throw error;
|
||||
}
|
||||
return result.value;
|
||||
|
|
@ -227,10 +243,11 @@ export function connectionTestFeedback(result) {
|
|||
}
|
||||
|
||||
export function presentError(error) {
|
||||
return {
|
||||
code: safeErrorCode(error?.code, 'DINGTALK_ERROR'),
|
||||
message: sanitizeMessage(error?.message, '钉钉操作失败,请稍后重试'),
|
||||
};
|
||||
return normalizeError(
|
||||
error,
|
||||
'DINGTALK_ERROR',
|
||||
'钉钉操作失败,请稍后重试',
|
||||
) ?? { code: 'DINGTALK_ERROR', message: '钉钉操作失败,请稍后重试' };
|
||||
}
|
||||
|
||||
export function formatRemaining(milliseconds) {
|
||||
|
|
|
|||
|
|
@ -169,19 +169,36 @@ function ProgressPanel({ status, busy, onCancel }) {
|
|||
h(Button, { onClick: onCancel, disabled: busy }, '取消接入')));
|
||||
}
|
||||
|
||||
function ConnectionErrorDiagnostic({ error }) {
|
||||
if (!error) return null;
|
||||
return h('div', { className: 'ddt-errorDiagnostic' },
|
||||
error.hint ? h('p', { className: 'ddt-errorHint' }, error.hint) : null,
|
||||
h('span', { className: 'ddt-errorCode' },
|
||||
h('span', null, '错误码'), `: ${error.code}`,
|
||||
error.referenceId
|
||||
? h(React.Fragment, null, ' · ', h('span', null, '参考号'), `: ${error.referenceId}`)
|
||||
: null));
|
||||
}
|
||||
|
||||
function ProvisionError({ provision, busy, onRetry, onClose }) {
|
||||
const error = provision.error ?? {
|
||||
code: 'DINGTALK_PROVISION_FAILED',
|
||||
message: '钉钉机器人没有接入完成',
|
||||
};
|
||||
const connectionFailed = Boolean(error.referenceId);
|
||||
return h('div', { className: 'ddt-card dim-surfaceCard' },
|
||||
h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' },
|
||||
h('h3', null, provision.status === 'expired' ? '二维码已过期' : '钉钉机器人没有接入完成'),
|
||||
h('h3', null, provision.status === 'expired'
|
||||
? '二维码已过期'
|
||||
: connectionFailed ? '机器人已保存,但连接未就绪' : '钉钉机器人没有接入完成'),
|
||||
h('p', null, error.message),
|
||||
h('span', { className: 'ddt-errorCode' }, error.code),
|
||||
h(ConnectionErrorDiagnostic, { error }),
|
||||
h('div', { className: 'ddt-actions dim-viewActions' },
|
||||
h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
|
||||
h(Button, { onClick: onClose, disabled: busy }, '关闭'))));
|
||||
connectionFailed
|
||||
? h(Button, { kind: 'primary', onClick: onClose, disabled: busy }, '查看已保存的机器人')
|
||||
: h(React.Fragment, null,
|
||||
h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
|
||||
h(Button, { onClick: onClose, disabled: busy }, '关闭')))));
|
||||
}
|
||||
|
||||
function checkedTime(value) {
|
||||
|
|
@ -278,6 +295,7 @@ export function AccountCard({
|
|||
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) },
|
||||
'移除接入')),
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
account.error ? h(ConnectionErrorDiagnostic, { error: account.error }) : null,
|
||||
account.lastMessageError ? h(LastMessageErrorSummary, {
|
||||
className: 'ddt-summary',
|
||||
error: account.lastMessageError,
|
||||
|
|
@ -614,6 +632,12 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
? snapshot?.bots.find((bot) => bot.botId === result.botId)
|
||||
: snapshot?.bots.find((bot) => bot.connected);
|
||||
if (!account?.connected) {
|
||||
if (account?.error) {
|
||||
setProvision((current) => current?.attemptId === attemptId
|
||||
? { ...current, ...result, status: 'failed', error: account.error }
|
||||
: current);
|
||||
return;
|
||||
}
|
||||
setProvision((current) => current?.attemptId === attemptId
|
||||
? { ...current, ...result, status: 'connecting' }
|
||||
: current);
|
||||
|
|
@ -698,9 +722,12 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
return snapshot;
|
||||
} catch (error) {
|
||||
if (!mountedRef.current) return undefined;
|
||||
const visibleError = presentError(error);
|
||||
const failureMessage = operation === 'reconnect'
|
||||
? '连接检查失败,请稍后重试。'
|
||||
: `操作失败:${presentError(error).message}`;
|
||||
? visibleError.referenceId
|
||||
? `连接检查失败:${visibleError.message}(参考号:${visibleError.referenceId})`
|
||||
: '连接检查失败,请稍后重试。'
|
||||
: `操作失败:${visibleError.message}`;
|
||||
if (operation === 'reconnect') {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ const CSS = String.raw`
|
|||
.ddt-inlineError h3 { font-size: 17px; }
|
||||
.ddt-inlineError p { line-height: 1.55; }
|
||||
.ddt-errorCode { font: 11px ui-monospace, SFMono-Regular, monospace; opacity: .8; }
|
||||
.ddt-errorDiagnostic { display: grid; gap: 5px; color: var(--ddt-error); }
|
||||
.ddt-errorHint { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.55; }
|
||||
.ddt-listHeading { display: flex; align-items: center; justify-content: space-between; margin: 2px 0 9px; }
|
||||
.ddt-listHeading h3 { margin: 0; font-size: 14px; }
|
||||
.ddt-list { display: grid; gap: 12px; margin: 0; padding: 0; list-style: none; }
|
||||
|
|
|
|||
|
|
@ -351,6 +351,8 @@ const EN = Object.freeze({
|
|||
'检查连接': 'Check connection',
|
||||
'检查中…': 'Checking…',
|
||||
'连接检查失败,请稍后重试。': 'Connection check failed. Try again later.',
|
||||
'机器人已保存,但连接未就绪': 'The bot was saved, but the connection is not ready',
|
||||
'查看已保存的机器人': 'View saved bot',
|
||||
'测试消息已发送,请到对应机器人会话中确认。': 'Test message sent. Check the matching bot conversation.',
|
||||
'连接检查完成。机器人尚未收到可用于测试的私聊消息。': 'Connection check completed. The bot has not received a direct message it can use for testing.',
|
||||
'连接检查完成,但测试消息发送失败。': 'Connection check completed, but the test message could not be sent.',
|
||||
|
|
@ -921,6 +923,8 @@ function translateDynamic(text) {
|
|||
if (match) return `Automatic status refresh failed: ${match[1]}`;
|
||||
match = /^操作失败:(.+)$/.exec(text);
|
||||
if (match) return `Operation failed: ${match[1]}`;
|
||||
match = /^连接检查失败:(.+)(参考号:(.+))$/.exec(text);
|
||||
if (match) return `Connection check failed: ${localizeText(match[1])} (reference: ${match[2]})`;
|
||||
match = /^连接检查失败:(.+)$/.exec(text);
|
||||
if (match) return `Connection check failed: ${match[1]}`;
|
||||
match = /^移除失败:(.+)$/.exec(text);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue