mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 01:53:21 +08:00
feat: send test messages during bot connection checks
This commit is contained in:
parent
94da24538f
commit
fb61c4235f
78 changed files with 3460 additions and 210 deletions
|
|
@ -85,6 +85,16 @@ function normalizeError(value, fallbackCode, fallbackMessage) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeTestMessage(value) {
|
||||
if (!isRecord(value)) return null;
|
||||
if (value.sent === true) return { sent: true };
|
||||
if (value.sent !== false) return null;
|
||||
const code = value.code === 'test-target-unavailable'
|
||||
? 'test-target-unavailable'
|
||||
: 'test-message-failed';
|
||||
return { sent: false, code };
|
||||
}
|
||||
|
||||
export function unwrapRpcResult(result) {
|
||||
if (!isRecord(result) || typeof result.ok !== 'boolean') {
|
||||
throw new Error('钉钉服务返回了无法识别的响应');
|
||||
|
|
@ -189,9 +199,18 @@ export function normalizeSnapshot(value) {
|
|||
connected: bots.filter((bot) => bot.connected).length,
|
||||
},
|
||||
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
||||
testMessage: normalizeTestMessage(source.testMessage),
|
||||
};
|
||||
}
|
||||
|
||||
export function connectionTestFeedback(result) {
|
||||
if (result?.sent === true) return '钉钉连接检查完成,测试消息已发送。';
|
||||
if (result?.code === 'test-target-unavailable') {
|
||||
return '钉钉连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
}
|
||||
return result ? '钉钉连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
export function presentError(error) {
|
||||
return {
|
||||
code: safeErrorCode(error?.code, 'DINGTALK_ERROR'),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|||
import {
|
||||
DINGTALK_ENDPOINTS,
|
||||
DINGTALK_RPC_CHANNEL,
|
||||
connectionTestFeedback,
|
||||
formatRemaining,
|
||||
normalizeProvisioning,
|
||||
normalizeSnapshot,
|
||||
|
|
@ -208,6 +209,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|||
export function AccountCard({
|
||||
account,
|
||||
busy,
|
||||
feedback,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -241,6 +243,10 @@ export function AccountCard({
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
feedback ? h('div', {
|
||||
className: 'ddt-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
}, feedback) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
|
||||
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
||||
|
|
@ -262,6 +268,7 @@ function AccountList(props) {
|
|||
h(AccountCard, {
|
||||
account,
|
||||
busy: props.busyByBot[account.botId],
|
||||
feedback: props.feedbackByBot[account.botId]?.message,
|
||||
removing: props.removeTarget === account.botId,
|
||||
onReconnect: () => props.onReconnect(account),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
||||
|
|
@ -280,6 +287,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
||||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
|
|
@ -325,6 +333,22 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const discardStaleFeedback = React.useCallback((snapshot) => {
|
||||
const botsById = new Map(snapshot.bots.map((bot) => [bot.botId, bot]));
|
||||
setFeedbackByBot((current) => {
|
||||
let changed = false;
|
||||
const next = { ...current };
|
||||
for (const [botId, feedback] of Object.entries(next)) {
|
||||
const bot = botsById.get(botId);
|
||||
if (!bot || (feedback.clearWhenDisconnected && (!bot.connected || bot.error))) {
|
||||
delete next[botId];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed ? next : current;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const focusAddButton = React.useCallback(() => {
|
||||
if (!mountedRef.current) return;
|
||||
if (focusFrameRef.current !== null) window.cancelAnimationFrame(focusFrameRef.current);
|
||||
|
|
@ -366,6 +390,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
revision: snapshot.revision,
|
||||
error: null,
|
||||
});
|
||||
discardStaleFeedback(snapshot);
|
||||
if (restoreProvisioning && snapshot.provisioning) {
|
||||
setProvision((current) => !current || current.attemptId === snapshot.provisioning.attemptId
|
||||
? {
|
||||
|
|
@ -386,7 +411,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
}));
|
||||
return undefined;
|
||||
}
|
||||
}, [invoke, workspaceFence]);
|
||||
}, [discardStaleFeedback, invoke, workspaceFence]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
|
|
@ -480,6 +505,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
revision: snapshot.revision,
|
||||
error: null,
|
||||
});
|
||||
discardStaleFeedback(snapshot);
|
||||
}
|
||||
setCredentialOpen(false);
|
||||
announce('钉钉机器人凭据已绑定。');
|
||||
|
|
@ -490,7 +516,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
||||
if (mountedRef.current) setBusy(false);
|
||||
}
|
||||
}, [announce, invoke, loadStatus, workspaceFence]);
|
||||
}, [announce, discardStaleFeedback, invoke, loadStatus, workspaceFence]);
|
||||
|
||||
const cancelProvisioning = React.useCallback(async () => {
|
||||
if (!mountedRef.current) return;
|
||||
|
|
@ -595,6 +621,13 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
if (!mountedRef.current) return undefined;
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(account.botId, operation);
|
||||
if (operation === 'reconnect') {
|
||||
setFeedbackByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
try {
|
||||
const snapshot = normalizeSnapshot(await invoke(endpoint, payload));
|
||||
if (!mountedRef.current) return undefined;
|
||||
|
|
@ -606,28 +639,50 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
revision: snapshot.revision,
|
||||
error: null,
|
||||
});
|
||||
discardStaleFeedback(snapshot);
|
||||
}
|
||||
announce(typeof success === 'function' ? success(snapshot) : success);
|
||||
const successMessage = typeof success === 'function' ? success(snapshot) : success;
|
||||
if (operation === 'reconnect') {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: {
|
||||
message: successMessage,
|
||||
clearWhenDisconnected: snapshot.testMessage?.sent === true,
|
||||
},
|
||||
}));
|
||||
}
|
||||
announce(successMessage);
|
||||
return snapshot;
|
||||
} catch (error) {
|
||||
if (!mountedRef.current) return undefined;
|
||||
announce(`操作失败:${presentError(error).message}`);
|
||||
const failureMessage = operation === 'reconnect'
|
||||
? '连接检查失败,请稍后重试。'
|
||||
: `操作失败:${presentError(error).message}`;
|
||||
if (operation === 'reconnect') {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: { message: failureMessage, clearWhenDisconnected: false },
|
||||
}));
|
||||
}
|
||||
announce(failureMessage);
|
||||
return undefined;
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true, restoreProvisioning: false });
|
||||
if (mountedRef.current) setBotBusy(account.botId, null);
|
||||
}
|
||||
}, [announce, invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
}, [announce, discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
|
||||
const reconnect = React.useCallback((account) => runBotAction({
|
||||
account,
|
||||
operation: 'reconnect',
|
||||
endpoint: DINGTALK_ENDPOINTS.reconnectBot,
|
||||
payload: { botId: account.botId },
|
||||
success: (snapshot) => snapshot?.bots.find((bot) => bot.botId === account.botId)?.connected
|
||||
? '钉钉连接检查完成。'
|
||||
: '钉钉仍未连接,插件会继续自动重试。',
|
||||
payload: { botId: account.botId, sendTest: true },
|
||||
success: (snapshot) => {
|
||||
const refreshed = snapshot?.bots.find((bot) => bot.botId === account.botId);
|
||||
if (!refreshed?.connected) return '钉钉仍未连接,插件会继续自动重试。';
|
||||
return connectionTestFeedback(snapshot.testMessage) ?? '钉钉连接检查完成。';
|
||||
},
|
||||
}), [runBotAction]);
|
||||
|
||||
const saveWorkspace = React.useCallback(async (account, workspace) => {
|
||||
|
|
@ -646,13 +701,14 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
revision: snapshot.revision,
|
||||
error: null,
|
||||
});
|
||||
discardStaleFeedback(snapshot);
|
||||
}
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
||||
if (mountedRef.current) setBotBusy(account.botId, null);
|
||||
}
|
||||
}, [invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
}, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
|
||||
|
||||
const remove = React.useCallback(async (account) => {
|
||||
const snapshot = await runBotAction({
|
||||
|
|
@ -738,6 +794,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|||
? h(AccountList, {
|
||||
bots: model.bots,
|
||||
busyByBot,
|
||||
feedbackByBot,
|
||||
removeTarget,
|
||||
onReconnect: (account) => void reconnect(account),
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
|
|
|
|||
|
|
@ -330,6 +330,16 @@ function formatCheckedTime(timestamp) {
|
|||
}
|
||||
}
|
||||
|
||||
function connectionTestNotice(value) {
|
||||
if (value?.testMessage?.sent === true) {
|
||||
return '测试消息已发送,请到飞书会话中确认。';
|
||||
}
|
||||
if (value?.testMessage?.code === 'test-target-unavailable') {
|
||||
return '连接检查完成,但当前没有可用的测试消息接收人。';
|
||||
}
|
||||
return value?.testMessage ? '连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
|
||||
const cancelRef = React.useRef(null);
|
||||
const idPart = bot.botId.replace(/[^a-zA-Z0-9_-]/g, "-");
|
||||
|
|
@ -364,6 +374,7 @@ export function BotCard({
|
|||
connection,
|
||||
busy,
|
||||
actionError,
|
||||
testNotice,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -420,6 +431,10 @@ export function BotCard({
|
|||
h("div", { className: "bxf-connectedFooter dim-cardFooter" },
|
||||
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
||||
summary) : null,
|
||||
testNotice ? h("div", {
|
||||
className: "bxf-healthSummary dim-cardSummary",
|
||||
role: "status",
|
||||
}, testNotice) : null,
|
||||
h("div", { className: "bxf-actions bxf-botActions dim-cardActions" },
|
||||
h(Button, {
|
||||
className: "dim-cardAction", onClick: onReconnect,
|
||||
|
|
@ -454,6 +469,7 @@ function BotList(props) {
|
|||
connection: bot,
|
||||
busy: props.busyByBot[bot.botId],
|
||||
actionError: props.errorsByBot[bot.botId],
|
||||
testNotice: props.testNoticesByBot[bot.botId],
|
||||
removing: props.removeTargetId === bot.botId,
|
||||
onReconnect: () => props.onReconnect(bot),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
||||
|
|
@ -527,6 +543,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [errorsByBot, setErrorsByBot] = React.useState({});
|
||||
const [testNoticesByBot, setTestNoticesByBot] = React.useState({});
|
||||
const [removeTargetId, setRemoveTargetId] = React.useState(null);
|
||||
const [announcement, setAnnouncement] = React.useState("");
|
||||
const [now, setNow] = React.useState(() => Date.now());
|
||||
|
|
@ -826,8 +843,14 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(botId, "reconnect");
|
||||
setBotError(botId, null);
|
||||
setTestNoticesByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[botId];
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
const snapshot = normalizeBotsSnapshot(await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId }));
|
||||
const value = await invoke(FEISHU_ENDPOINTS.reconnectBot, { botId, sendTest: true });
|
||||
const snapshot = normalizeBotsSnapshot(value);
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
mergeSnapshot(snapshot);
|
||||
}
|
||||
|
|
@ -839,12 +862,18 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
error.code = refreshed?.error?.code ?? "FEISHU_BOT_OFFLINE";
|
||||
throw error;
|
||||
}
|
||||
announce(connection.connected
|
||||
const testNotice = connectionTestNotice(value);
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setTestNoticesByBot((current) => ({ ...current, [botId]: testNotice }));
|
||||
}
|
||||
announce(testNotice ?? (connection.connected
|
||||
? `${bot.name}连接检查完成。`
|
||||
: `${bot.name}已重新连接。`);
|
||||
: `${bot.name}已重新连接。`));
|
||||
} catch (error) {
|
||||
setBotError(botId, error);
|
||||
announce(`${bot.name}操作失败,请查看机器人状态。`);
|
||||
const failure = new Error("连接检查失败,请稍后重试。");
|
||||
failure.code = error?.code;
|
||||
setBotError(botId, failure);
|
||||
announce(failure.message);
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
||||
|
|
@ -995,6 +1024,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|||
bots: model.bots,
|
||||
busyByBot,
|
||||
errorsByBot,
|
||||
testNoticesByBot,
|
||||
removeTargetId,
|
||||
onReconnect: (bot) => void reconnectOneBot(bot),
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|||
|
||||
const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
|
||||
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
||||
const TEST_MESSAGE_CODES = new Set(['test-target-unavailable', 'test-message-failed']);
|
||||
const QR_DATA_URL = /^data:image\/(?:png|webp);base64,[a-z\d+/]+={0,2}$/i;
|
||||
|
||||
function isRecord(value) {
|
||||
|
|
@ -96,18 +97,38 @@ function normalizeBot(value) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeTestMessage(value) {
|
||||
if (!isRecord(value) || typeof value.sent !== 'boolean') return undefined;
|
||||
if (value.sent) return { sent: true };
|
||||
const code = text(value.code, 'test-message-failed', 80);
|
||||
return {
|
||||
sent: false,
|
||||
code: TEST_MESSAGE_CODES.has(code) ? code : 'test-message-failed',
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeSnapshot(value) {
|
||||
const source = isRecord(value?.snapshot) ? value.snapshot : value;
|
||||
if (!isRecord(source) || !Array.isArray(source.bots)) throw new Error('QQ 服务没有返回有效的机器人列表');
|
||||
const bots = source.bots.map(normalizeBot).filter(Boolean);
|
||||
const testMessage = normalizeTestMessage(source.testMessage);
|
||||
return {
|
||||
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
|
||||
bots,
|
||||
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
||||
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
||||
...(testMessage ? { testMessage } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function connectionTestFeedback(result) {
|
||||
if (result?.sent === true) return '测试消息已发送,请到对应机器人会话中确认。';
|
||||
if (result?.code === 'test-target-unavailable') {
|
||||
return '连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
}
|
||||
return result ? '连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
export function presentError(error) {
|
||||
return {
|
||||
code: text(error?.code, 'QQ_ERROR', 80),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|||
import {
|
||||
QQ_ENDPOINTS,
|
||||
QQ_RPC_CHANNEL,
|
||||
connectionTestFeedback,
|
||||
formatRemaining,
|
||||
normalizeProvisioning,
|
||||
normalizeSnapshot,
|
||||
|
|
@ -151,6 +152,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|||
export function AccountCard({
|
||||
account,
|
||||
busy,
|
||||
feedback,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -180,6 +182,11 @@ export function AccountCard({
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
feedback ? h('div', {
|
||||
className: 'ddt-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
'aria-live': 'polite',
|
||||
}, feedback) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
||||
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
|
||||
|
|
@ -193,6 +200,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
||||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
|
|
@ -361,6 +369,35 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [invoke, loadStatus, workspaceFence]);
|
||||
|
||||
const reconnect = React.useCallback(async (account) => {
|
||||
setFeedbackByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
const snapshot = await botAction(
|
||||
account,
|
||||
'reconnect',
|
||||
QQ_ENDPOINTS.reconnectBot,
|
||||
{ botId: account.botId, sendTest: true },
|
||||
);
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: connectionTestFeedback(snapshot?.testMessage),
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: '连接检查失败,请稍后重试。',
|
||||
}));
|
||||
}
|
||||
}
|
||||
}, [botAction]);
|
||||
|
||||
let provisionView = null;
|
||||
if (provision?.status === 'starting') provisionView = h('div', { className: 'ddt-card ddt-loading dim-surfaceCard' }, h('div', { className: 'ddt-spinner' }), '正在申请 QQ 二维码…');
|
||||
else if (['pending', 'refreshing'].includes(provision?.status)) provisionView = h(QrPanel, {
|
||||
|
|
@ -378,8 +415,9 @@ export function QqSettingsTab({ rpcCall }) {
|
|||
h('li', { key: account.botId }, h(AccountCard, {
|
||||
account,
|
||||
busy: busyByBot[account.botId],
|
||||
feedback: feedbackByBot[account.botId],
|
||||
removing: removeTarget === account.botId,
|
||||
onReconnect: () => void botAction(account, 'reconnect', QQ_ENDPOINTS.reconnectBot, { botId: account.botId }),
|
||||
onReconnect: () => void reconnect(account),
|
||||
onWorkspaceSave: (workspace) => botAction(
|
||||
account,
|
||||
'workspace',
|
||||
|
|
|
|||
|
|
@ -30,6 +30,14 @@ function checkedTime(value) {
|
|||
}
|
||||
}
|
||||
|
||||
function connectionTestNotice(value) {
|
||||
if (value?.testMessage?.sent === true) return '测试消息已发送,请到对应机器人会话中确认。';
|
||||
if (value?.testMessage?.code === 'test-target-unavailable') {
|
||||
return '连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
}
|
||||
return value?.testMessage ? '连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
export function createTokenChannelSettings(definition) {
|
||||
const {
|
||||
channel,
|
||||
|
|
@ -53,7 +61,7 @@ export function createTokenChannelSettings(definition) {
|
|||
emptyActionLabel = '填写 Bot Token',
|
||||
} = definition;
|
||||
|
||||
function AccountCard({ account, busy, removing, onReconnect, onWorkspaceSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
||||
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
||||
const state = busy === 'reconnect' ? 'connecting' : account.state;
|
||||
const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
|
||||
const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
|
||||
|
|
@ -82,6 +90,7 @@ export function createTokenChannelSettings(definition) {
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
testNotice ? h('div', { className: 'ddt-summary dim-cardSummary', role: 'status' }, testNotice) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, {
|
||||
className: 'dim-cardAction',
|
||||
|
|
@ -111,6 +120,7 @@ export function createTokenChannelSettings(definition) {
|
|||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [testNoticeByBot, setTestNoticeByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const mounted = React.useRef(true);
|
||||
const workspaceFence = useWorkspaceSnapshotFence();
|
||||
|
|
@ -198,10 +208,25 @@ export function createTokenChannelSettings(definition) {
|
|||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
||||
try {
|
||||
const snapshot = api.normalizeSnapshot(await invoke(endpoint, payload));
|
||||
const value = await invoke(endpoint, payload);
|
||||
const snapshot = api.normalizeSnapshot(value);
|
||||
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setModel({ phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null });
|
||||
}
|
||||
if (mounted.current && operation === 'reconnect') {
|
||||
setTestNoticeByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: connectionTestNotice(value),
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (operation !== 'reconnect') throw error;
|
||||
if (mounted.current) {
|
||||
setTestNoticeByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: '连接检查失败,请稍后重试。',
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
if (shouldRefresh && mounted.current) void loadStatus({ silent: true });
|
||||
|
|
@ -221,12 +246,13 @@ export function createTokenChannelSettings(definition) {
|
|||
h('li', { key: account.botId }, h(AccountCard, {
|
||||
account,
|
||||
busy: busyByBot[account.botId],
|
||||
testNotice: testNoticeByBot[account.botId],
|
||||
removing: removeTarget === account.botId,
|
||||
onReconnect: () => void botAction(
|
||||
account,
|
||||
'reconnect',
|
||||
endpoints.reconnectBot,
|
||||
{ botId: account.botId },
|
||||
{ botId: account.botId, sendTest: true },
|
||||
),
|
||||
onWorkspaceSave: (workspace) => botAction(
|
||||
account,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,16 @@ function timestamp(value) {
|
|||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
}
|
||||
|
||||
function normalizeTestMessage(value) {
|
||||
if (!isRecord(value)) return null;
|
||||
if (value.sent === true) return { sent: true };
|
||||
if (value.sent !== false) return null;
|
||||
const code = value.code === 'test-target-unavailable'
|
||||
? 'test-target-unavailable'
|
||||
: 'test-message-failed';
|
||||
return { sent: false, code };
|
||||
}
|
||||
|
||||
export function unwrapRpcResult(result) {
|
||||
if (!isRecord(result) || typeof result.ok !== 'boolean') throw new Error('企业微信服务返回了无法识别的响应');
|
||||
if (!result.ok) {
|
||||
|
|
@ -105,6 +115,7 @@ export function normalizeSnapshot(value) {
|
|||
bots,
|
||||
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
||||
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
||||
testMessage: normalizeTestMessage(source.testMessage),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|||
export function AccountCard({
|
||||
account,
|
||||
busy,
|
||||
feedback,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -180,6 +181,11 @@ export function AccountCard({
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
feedback ? h('div', {
|
||||
className: 'ddt-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
'aria-live': 'polite',
|
||||
}, feedback) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
||||
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')))),
|
||||
|
|
@ -193,13 +199,31 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [credentialOpen, setCredentialOpen] = React.useState(false);
|
||||
const [credentialError, setCredentialError] = React.useState(null);
|
||||
const [notice, setNotice] = React.useState('');
|
||||
const [now, setNow] = React.useState(Date.now());
|
||||
const mounted = React.useRef(true);
|
||||
const workspaceFence = useWorkspaceSnapshotFence();
|
||||
const addButtonRef = React.useRef(null);
|
||||
const noticeFrameRef = React.useRef(null);
|
||||
|
||||
const announce = React.useCallback((message) => {
|
||||
if (!mounted.current) return;
|
||||
if (noticeFrameRef.current !== null) {
|
||||
window.cancelAnimationFrame(noticeFrameRef.current);
|
||||
noticeFrameRef.current = null;
|
||||
}
|
||||
setNotice('');
|
||||
if (message) {
|
||||
noticeFrameRef.current = window.requestAnimationFrame(() => {
|
||||
noticeFrameRef.current = null;
|
||||
if (mounted.current) setNotice(message);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposeDingtalk = installDingtalkStyles();
|
||||
|
|
@ -207,6 +231,10 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
if (noticeFrameRef.current !== null) {
|
||||
window.cancelAnimationFrame(noticeFrameRef.current);
|
||||
noticeFrameRef.current = null;
|
||||
}
|
||||
disposeWecom();
|
||||
disposeDingtalk();
|
||||
};
|
||||
|
|
@ -361,6 +389,46 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
}
|
||||
}, [invoke, loadStatus, workspaceFence]);
|
||||
|
||||
const reconnect = React.useCallback(async (account) => {
|
||||
setFeedbackByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
const snapshot = await botAction(
|
||||
account,
|
||||
'reconnect',
|
||||
WECOM_ENDPOINTS.reconnectBot,
|
||||
{ botId: account.botId, sendTest: true },
|
||||
);
|
||||
if (!snapshot) return;
|
||||
const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
|
||||
let feedback;
|
||||
if (!refreshed?.connected) {
|
||||
feedback = '企业微信仍未连接,插件会继续自动重试。';
|
||||
} else if (snapshot.testMessage?.sent) {
|
||||
feedback = '企业微信连接检查完成,测试消息已发送。';
|
||||
} else if (snapshot.testMessage?.code === 'test-target-unavailable') {
|
||||
feedback = '企业微信连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
} else if (snapshot.testMessage) {
|
||||
feedback = '企业微信连接检查完成,但测试消息发送失败。';
|
||||
} else {
|
||||
feedback = '企业微信连接检查完成。';
|
||||
}
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
||||
}
|
||||
announce(feedback);
|
||||
} catch {
|
||||
const feedback = '连接检查失败,请稍后重试。';
|
||||
if (mounted.current) {
|
||||
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
||||
}
|
||||
announce(feedback);
|
||||
}
|
||||
}, [announce, botAction]);
|
||||
|
||||
let provisionView = null;
|
||||
if (provision?.status === 'starting') provisionView = h('div', { className: 'ddt-card ddt-loading dim-surfaceCard' }, h('div', { className: 'ddt-spinner' }), '正在申请企业微信二维码…');
|
||||
else if (['pending', 'refreshing'].includes(provision?.status)) provisionView = h(QrPanel, {
|
||||
|
|
@ -378,8 +446,9 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
h('li', { key: account.botId }, h(AccountCard, {
|
||||
account,
|
||||
busy: busyByBot[account.botId],
|
||||
feedback: feedbackByBot[account.botId],
|
||||
removing: removeTarget === account.botId,
|
||||
onReconnect: () => void botAction(account, 'reconnect', WECOM_ENDPOINTS.reconnectBot, { botId: account.botId }),
|
||||
onReconnect: () => void reconnect(account),
|
||||
onWorkspaceSave: (workspace) => botAction(
|
||||
account,
|
||||
'workspace',
|
||||
|
|
@ -419,6 +488,7 @@ export function WecomSettingsTab({ rpcCall }) {
|
|||
credentialOpen,
|
||||
addButtonRef,
|
||||
}),
|
||||
h('div', { className: 'ddt-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice),
|
||||
model.phase === 'loading' ? h(LoadingView)
|
||||
: model.phase === 'error'
|
||||
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取企业微信机器人状态'), h('p', null, model.error?.message), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
||||
|
|
|
|||
|
|
@ -35,6 +35,16 @@ function timestamp(value) {
|
|||
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
function normalizeTestMessage(value) {
|
||||
if (!isRecord(value)) return null;
|
||||
if (value.sent === true) return { sent: true };
|
||||
if (value.sent !== false) return null;
|
||||
const code = value.code === 'test-target-unavailable'
|
||||
? 'test-target-unavailable'
|
||||
: 'test-message-failed';
|
||||
return { sent: false, code };
|
||||
}
|
||||
|
||||
export function unwrapRpcResult(result) {
|
||||
if (!isRecord(result) || typeof result.ok !== 'boolean') {
|
||||
throw new Error('微信服务返回了无法识别的响应');
|
||||
|
|
@ -142,6 +152,7 @@ export function normalizeSnapshot(value) {
|
|||
connected: bots.filter((bot) => bot.connected).length,
|
||||
},
|
||||
provisioning: value.provisioning ? normalizeProvisioning(value.provisioning) : null,
|
||||
testMessage: normalizeTestMessage(value.testMessage),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ function checkedTime(timestamp) {
|
|||
export function AccountCard({
|
||||
account,
|
||||
busy,
|
||||
feedback,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -229,6 +230,11 @@ export function AccountCard({
|
|||
}),
|
||||
h('div', { className: 'dxw-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'dxw-summary dim-cardSummary' }, summary) : null,
|
||||
feedback ? h('div', {
|
||||
className: 'dxw-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
'aria-live': 'polite',
|
||||
}, feedback) : null,
|
||||
h('div', { className: 'dxw-actions dim-cardActions' },
|
||||
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) },
|
||||
busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
||||
|
|
@ -250,6 +256,7 @@ function AccountList(props) {
|
|||
h(AccountCard, {
|
||||
account,
|
||||
busy: props.busyByBot[account.botId],
|
||||
feedback: props.feedbackByBot[account.botId],
|
||||
removing: props.removeTarget === account.botId,
|
||||
onReconnect: () => props.onReconnect(account),
|
||||
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
||||
|
|
@ -282,6 +289,7 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [feedbackByBot, setFeedbackByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [notice, setNotice] = React.useState('');
|
||||
const [now, setNow] = React.useState(() => Date.now());
|
||||
|
|
@ -496,15 +504,42 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|||
const reconnect = React.useCallback(async (account) => {
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBotBusy(account.botId, 'reconnect');
|
||||
setFeedbackByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
try {
|
||||
const snapshot = normalizeSnapshot(await invoke(WEIXIN_ENDPOINTS.reconnectBot, { botId: account.botId }));
|
||||
const snapshot = normalizeSnapshot(await invoke(
|
||||
WEIXIN_ENDPOINTS.reconnectBot,
|
||||
{ botId: account.botId, sendTest: true },
|
||||
));
|
||||
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setModel((current) => ({ ...current, bots: snapshot.bots, totals: snapshot.totals, revision: snapshot.revision }));
|
||||
}
|
||||
const refreshed = snapshot.bots.find((bot) => bot.botId === account.botId);
|
||||
announce(refreshed?.connected ? '微信连接检查完成。' : '微信仍未连接,插件会继续自动重试。');
|
||||
} catch (error) {
|
||||
announce(`连接检查失败:${presentError(error).message}`);
|
||||
let feedback;
|
||||
if (!refreshed?.connected) {
|
||||
feedback = '微信仍未连接,插件会继续自动重试。';
|
||||
} else if (snapshot.testMessage?.sent) {
|
||||
feedback = '微信连接检查完成,测试消息已发送。';
|
||||
} else if (snapshot.testMessage?.code === 'test-target-unavailable') {
|
||||
feedback = '微信连接检查完成。请先私聊机器人发送 /status,再重新检查。';
|
||||
} else if (snapshot.testMessage) {
|
||||
feedback = '微信连接检查完成,但测试消息发送失败。';
|
||||
} else {
|
||||
feedback = '微信连接检查完成。';
|
||||
}
|
||||
if (mountedRef.current) {
|
||||
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
||||
}
|
||||
announce(feedback);
|
||||
} catch {
|
||||
const feedback = '连接检查失败,请稍后重试。';
|
||||
if (mountedRef.current) {
|
||||
setFeedbackByBot((current) => ({ ...current, [account.botId]: feedback }));
|
||||
}
|
||||
announce(feedback);
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
||||
|
|
@ -611,6 +646,7 @@ export function WeixinSettingsTab({ rpcCall }) {
|
|||
? h(AccountList, {
|
||||
bots: model.bots,
|
||||
busyByBot,
|
||||
feedbackByBot,
|
||||
removeTarget,
|
||||
onReconnect: (account) => void reconnect(account),
|
||||
onWorkspaceSave: saveWorkspace,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,16 @@ function checkedTime(value) {
|
|||
}
|
||||
}
|
||||
|
||||
function connectionTestNotice(value) {
|
||||
if (value?.testMessage?.sent === true) {
|
||||
return '测试消息已发送,请到 WhatsApp 自聊会话中确认。';
|
||||
}
|
||||
if (value?.testMessage?.code === 'test-target-unavailable') {
|
||||
return '连接检查完成,但当前没有可用的 WhatsApp 自聊目标。';
|
||||
}
|
||||
return value?.testMessage ? '连接检查完成,但测试消息发送失败。' : null;
|
||||
}
|
||||
|
||||
function Heading({ totals, busy, onAdd, addButtonRef }) {
|
||||
return h('div', { className: 'ddt-heading' },
|
||||
h('div', { className: 'ddt-tools' },
|
||||
|
|
@ -159,6 +169,7 @@ function RemoveConfirmation({ account, busy, onConfirm, onCancel }) {
|
|||
export function WhatsappAccountCard({
|
||||
account,
|
||||
busy,
|
||||
testNotice,
|
||||
removing,
|
||||
onReconnect,
|
||||
onWorkspaceSave,
|
||||
|
|
@ -196,6 +207,10 @@ export function WhatsappAccountCard({
|
|||
}),
|
||||
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
||||
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
||||
testNotice ? h('div', {
|
||||
className: 'ddt-summary dim-cardSummary',
|
||||
role: 'status',
|
||||
}, testNotice) : null,
|
||||
h('div', { className: 'ddt-actions dim-cardActions' },
|
||||
h(Button, {
|
||||
className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy),
|
||||
|
|
@ -218,6 +233,7 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|||
const [provision, setProvision] = React.useState(null);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [busyByBot, setBusyByBot] = React.useState({});
|
||||
const [testNoticeByBot, setTestNoticeByBot] = React.useState({});
|
||||
const [removeTarget, setRemoveTarget] = React.useState(null);
|
||||
const [now, setNow] = React.useState(Date.now());
|
||||
const mounted = React.useRef(true);
|
||||
|
|
@ -365,10 +381,32 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|||
const botAction = React.useCallback(async (account, operation, endpoint, payload) => {
|
||||
const snapshotVersion = workspaceFence.beginMutation();
|
||||
setBusyByBot((current) => ({ ...current, [account.botId]: operation }));
|
||||
if (operation === 'reconnect') {
|
||||
setTestNoticeByBot((current) => {
|
||||
const next = { ...current };
|
||||
delete next[account.botId];
|
||||
return next;
|
||||
});
|
||||
}
|
||||
try {
|
||||
const snapshot = normalizeSnapshot(await invoke(endpoint, payload));
|
||||
const value = await invoke(endpoint, payload);
|
||||
const snapshot = normalizeSnapshot(value);
|
||||
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setModel({ phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null });
|
||||
if (operation === 'reconnect') {
|
||||
setTestNoticeByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: connectionTestNotice(value),
|
||||
}));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (operation !== 'reconnect') throw error;
|
||||
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
||||
setTestNoticeByBot((current) => ({
|
||||
...current,
|
||||
[account.botId]: '连接检查失败,请稍后重试。',
|
||||
}));
|
||||
}
|
||||
} finally {
|
||||
const shouldRefresh = workspaceFence.endMutation();
|
||||
|
|
@ -389,12 +427,13 @@ export function WhatsappSettingsTab({ rpcCall }) {
|
|||
h('li', { key: account.botId }, h(WhatsappAccountCard, {
|
||||
account,
|
||||
busy: busyByBot[account.botId],
|
||||
testNotice: testNoticeByBot[account.botId],
|
||||
removing: removeTarget === account.botId,
|
||||
onReconnect: () => void botAction(
|
||||
account,
|
||||
'reconnect',
|
||||
WHATSAPP_ENDPOINTS.reconnectBot,
|
||||
{ botId: account.botId },
|
||||
{ botId: account.botId, sendTest: true },
|
||||
),
|
||||
onWorkspaceSave: (workspace) => botAction(
|
||||
account,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,23 @@ const EN = Object.freeze({
|
|||
'刚刚': 'Just now',
|
||||
'检查连接': 'Check connection',
|
||||
'检查中…': 'Checking…',
|
||||
'连接检查失败,请稍后重试。': 'Connection check failed. Try again later.',
|
||||
'测试消息已发送,请到对应机器人会话中确认。': 'Test message sent. Check the matching bot conversation.',
|
||||
'连接检查完成。请先私聊机器人发送 /status,再重新检查。': 'Connection check completed. Send /status to the bot in a direct message, then check again.',
|
||||
'连接检查完成,但测试消息发送失败。': 'Connection check completed, but the test message could not be sent.',
|
||||
'测试消息已发送,请到飞书会话中确认。': 'Test message sent. Check the Feishu conversation.',
|
||||
'连接检查完成,但当前没有可用的测试消息接收人。': 'Connection check completed, but no test-message recipient is available.',
|
||||
'测试消息已发送,请到 WhatsApp 自聊会话中确认。': 'Test message sent. Check the WhatsApp self-chat.',
|
||||
'连接检查完成,但当前没有可用的 WhatsApp 自聊目标。': 'Connection check completed, but no WhatsApp self-chat target is available.',
|
||||
'钉钉连接检查完成,测试消息已发送。': 'DingTalk connection check completed and the test message was sent.',
|
||||
'钉钉连接检查完成。请先私聊机器人发送 /status,再重新检查。': 'DingTalk connection check completed. Send /status to the bot in a direct message, then check again.',
|
||||
'钉钉连接检查完成,但测试消息发送失败。': 'DingTalk connection check completed, but the test message could not be sent.',
|
||||
'微信连接检查完成,测试消息已发送。': 'WeChat connection check completed and the test message was sent.',
|
||||
'微信连接检查完成。请先私聊机器人发送 /status,再重新检查。': 'WeChat connection check completed. Send /status to the bot in a direct message, then check again.',
|
||||
'微信连接检查完成,但测试消息发送失败。': 'WeChat connection check completed, but the test message could not be sent.',
|
||||
'企业微信连接检查完成,测试消息已发送。': 'WeCom connection check completed and the test message was sent.',
|
||||
'企业微信连接检查完成。请先私聊机器人发送 /status,再重新检查。': 'WeCom connection check completed. Send /status to the bot in a direct message, then check again.',
|
||||
'企业微信连接检查完成,但测试消息发送失败。': 'WeCom connection check completed, but the test message could not be sent.',
|
||||
'重试连接': 'Reconnect',
|
||||
'重试中…': 'Retrying…',
|
||||
'移除接入': 'Remove connection',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import {
|
||||
connectionTestTargetUnavailable,
|
||||
publicConnectionTestResult,
|
||||
} from '../../../../src/channels/shared/connection-test.mjs';
|
||||
|
||||
export const DINGTALK_RPC_CHANNEL = '/dingtalk';
|
||||
export const DINGTALK_ENDPOINTS = Object.freeze({
|
||||
|
|
@ -69,9 +73,11 @@ function payloadFailure(endpoint, payload) {
|
|||
: 'bot.bind-credentials requires Client ID and Client Secret.';
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
||||
return exactKeys(payload, ['botId', 'sendTest'])
|
||||
&& validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || payload.sendTest === true)
|
||||
? null
|
||||
: 'bot.reconnect requires a botId.';
|
||||
: 'bot.reconnect requires a botId and optional sendTest=true.';
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.deleteBot) {
|
||||
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
||||
|
|
@ -210,7 +216,27 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|||
} else if (endpoint === DINGTALK_ENDPOINTS.bindCredentials) {
|
||||
value = await publicStatus(await controller.bindCredentials(payload), cachedEncode);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
const snapshot = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) return cancelled();
|
||||
let testMessage;
|
||||
if (payload.sendTest === true) {
|
||||
const connected = snapshot?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
||||
);
|
||||
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
||||
testMessage = publicConnectionTestResult(
|
||||
connectionTestTargetUnavailable('钉钉机器人'),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
testMessage = publicConnectionTestResult();
|
||||
} catch (error) {
|
||||
testMessage = publicConnectionTestResult(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
value = await publicStatus({ ...snapshot, ...(testMessage ? { testMessage } : {}) }, cachedEncode);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.deleteBot) {
|
||||
value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.setWorkspace) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import {
|
||||
|
|
@ -258,8 +259,14 @@ function validPayload(endpoint, payload) {
|
|||
? null
|
||||
: 'Disconnect requires removeCredentials=true.';
|
||||
}
|
||||
if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot
|
||||
|| endpoint === FEISHU_MULTI_ENDPOINTS.disconnectBot) {
|
||||
if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot) {
|
||||
return hasOnlyKeys(payload, new Set(['botId', 'sendTest']))
|
||||
&& safeOpaqueId(payload.botId)
|
||||
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
||||
? null
|
||||
: 'A valid botId and optional sendTest flag are required.';
|
||||
}
|
||||
if (endpoint === FEISHU_MULTI_ENDPOINTS.disconnectBot) {
|
||||
return hasOnlyKeys(payload, new Set(['botId'])) && safeOpaqueId(payload.botId)
|
||||
? null
|
||||
: 'A single valid botId is required.';
|
||||
|
|
@ -431,7 +438,31 @@ export function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl }
|
|||
value = await toPublicFeishuStatus(await controller.disconnect(), { encodeQr: cachedEncodeQr });
|
||||
} else if (endpoint === FEISHU_MULTI_ENDPOINTS.reconnectBot) {
|
||||
if (typeof controller.reconnectBot !== 'function') throw new Error('Multi-bot reconnect is unavailable');
|
||||
value = await toPublicFeishuStatus(await controller.reconnectBot(payload.botId), { encodeQr: cachedEncodeQr });
|
||||
const checked = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) return cancelled();
|
||||
value = await toPublicFeishuStatus(checked, { encodeQr: cachedEncodeQr });
|
||||
if (payload.sendTest === true) {
|
||||
let testError = null;
|
||||
const connected = checked?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot.connected === true,
|
||||
) === true;
|
||||
if (!connected) {
|
||||
testError = new Error('Feishu bot is not connected');
|
||||
testError.code = 'test-target-unavailable';
|
||||
} else {
|
||||
try {
|
||||
if (typeof controller.sendConnectionTest !== 'function') {
|
||||
const unavailable = new Error('Connection test is unavailable');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
} catch (error) {
|
||||
testError = error;
|
||||
}
|
||||
}
|
||||
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
||||
}
|
||||
} else if (endpoint === FEISHU_MULTI_ENDPOINTS.disconnectBot) {
|
||||
if (typeof controller.disconnectBot !== 'function') throw new Error('Multi-bot disconnect is unavailable');
|
||||
value = await toPublicFeishuStatus(await controller.disconnectBot(payload.botId), { encodeQr: cachedEncodeQr });
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import QRCode from 'qrcode';
|
||||
import {
|
||||
connectionTestTargetUnavailable,
|
||||
publicConnectionTestResult,
|
||||
} from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
|
||||
|
|
@ -53,7 +57,10 @@ function payloadFailure(endpoint, payload) {
|
|||
? null : 'bot.bind-credentials requires AppID and AppSecret.';
|
||||
}
|
||||
if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId) ? null : 'bot.reconnect requires a botId.';
|
||||
return exactKeys(payload, ['botId', 'sendTest'])
|
||||
&& validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || payload.sendTest === true)
|
||||
? null : 'bot.reconnect requires a botId and accepts only sendTest=true.';
|
||||
}
|
||||
if (endpoint === QQ_ENDPOINTS.deleteBot) {
|
||||
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
||||
|
|
@ -125,7 +132,33 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|||
} else if (endpoint === QQ_ENDPOINTS.bindCredentials) {
|
||||
value = await publicStatus(await controller.bindCredentials(payload), cachedEncode);
|
||||
} else if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
const snapshot = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) {
|
||||
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
||||
}
|
||||
let testMessage;
|
||||
if (payload.sendTest === true) {
|
||||
const connected = snapshot?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
||||
) === true;
|
||||
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
||||
testMessage = publicConnectionTestResult(
|
||||
connectionTestTargetUnavailable('QQ机器人'),
|
||||
);
|
||||
} else {
|
||||
let testError = null;
|
||||
try {
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
} catch (error) {
|
||||
testError = error;
|
||||
}
|
||||
testMessage = publicConnectionTestResult(testError);
|
||||
}
|
||||
}
|
||||
value = await publicStatus({
|
||||
...snapshot,
|
||||
...(testMessage ? { testMessage } : {}),
|
||||
}, cachedEncode);
|
||||
} else if (endpoint === QQ_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import {
|
||||
publicWorkspaceError,
|
||||
SET_WORKSPACE_ENDPOINT,
|
||||
|
|
@ -44,7 +45,8 @@ function payloadFailure(endpoint, payload) {
|
|||
? null : 'bot.bind-credentials requires a Bot Token.';
|
||||
}
|
||||
if (endpoint === TOKEN_BOT_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
||||
return exactKeys(payload, ['botId', 'sendTest']) && validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
||||
? null : 'bot.reconnect requires a botId.';
|
||||
}
|
||||
if (endpoint === TOKEN_BOT_ENDPOINTS.deleteBot) {
|
||||
|
|
@ -105,6 +107,28 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|||
value = await controller.bindCredentials(payload);
|
||||
} else if (endpoint === TOKEN_BOT_ENDPOINTS.reconnectBot) {
|
||||
value = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) {
|
||||
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
||||
}
|
||||
if (payload.sendTest === true) {
|
||||
let testError = null;
|
||||
try {
|
||||
if (value?.bots?.find((bot) => bot?.botId === payload.botId)?.connected !== true) {
|
||||
const unavailable = new Error('Bot is not connected');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
if (typeof controller.sendConnectionTest !== 'function') {
|
||||
const unavailable = new Error('Connection test is unavailable');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
} catch (error) {
|
||||
testError = error;
|
||||
}
|
||||
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
||||
}
|
||||
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await controller.updateWorkspace(payload.botId, payload.workspace);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import {
|
||||
publicWorkspaceError,
|
||||
SET_WORKSPACE_ENDPOINT,
|
||||
|
|
@ -53,7 +54,8 @@ function payloadFailure(endpoint, payload) {
|
|||
? null : 'bot.bind-credentials requires xoxb Bot Token and xapp App Token.';
|
||||
}
|
||||
if (endpoint === SLACK_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
||||
return exactKeys(payload, ['botId', 'sendTest']) && validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
||||
? null : 'bot.reconnect requires a botId.';
|
||||
}
|
||||
if (endpoint === SLACK_ENDPOINTS.deleteBot) {
|
||||
|
|
@ -114,7 +116,31 @@ export function createSlackRpcHandler(controller) {
|
|||
let value;
|
||||
if (endpoint === SLACK_ENDPOINTS.status) value = await controller.status();
|
||||
else if (endpoint === SLACK_ENDPOINTS.bindCredentials) value = await controller.bindCredentials(payload);
|
||||
else if (endpoint === SLACK_ENDPOINTS.reconnectBot) value = await controller.reconnectBot(payload.botId);
|
||||
else if (endpoint === SLACK_ENDPOINTS.reconnectBot) {
|
||||
value = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) {
|
||||
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
||||
}
|
||||
if (payload.sendTest === true) {
|
||||
let testError = null;
|
||||
try {
|
||||
if (value?.bots?.find((bot) => bot?.botId === payload.botId)?.connected !== true) {
|
||||
const unavailable = new Error('Bot is not connected');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
if (typeof controller.sendConnectionTest !== 'function') {
|
||||
const unavailable = new Error('Connection test is unavailable');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
} catch (error) {
|
||||
testError = error;
|
||||
}
|
||||
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
||||
}
|
||||
}
|
||||
else if (endpoint === SLACK_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await controller.updateWorkspace(payload.botId, payload.workspace);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import QRCode from 'qrcode';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
import {
|
||||
connectionTestTargetUnavailable,
|
||||
publicConnectionTestResult,
|
||||
} from '../../../../src/channels/shared/connection-test.mjs';
|
||||
|
||||
export const WECOM_RPC_CHANNEL = '/wecom';
|
||||
export const WECOM_ENDPOINTS = Object.freeze({
|
||||
|
|
@ -53,7 +57,10 @@ function payloadFailure(endpoint, payload) {
|
|||
? null : 'bot.bind-credentials requires Bot ID and Secret.';
|
||||
}
|
||||
if (endpoint === WECOM_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId) ? null : 'bot.reconnect requires a botId.';
|
||||
return exactKeys(payload, ['botId', 'sendTest'])
|
||||
&& validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || payload.sendTest === true)
|
||||
? null : 'bot.reconnect requires a botId and optional sendTest=true.';
|
||||
}
|
||||
if (endpoint === WECOM_ENDPOINTS.deleteBot) {
|
||||
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
||||
|
|
@ -130,7 +137,29 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
|
|||
} else if (endpoint === WECOM_ENDPOINTS.bindCredentials) {
|
||||
value = await publicStatus(await controller.bindCredentials(payload), cachedEncode);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
const snapshot = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) {
|
||||
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
||||
}
|
||||
let testMessage;
|
||||
if (payload.sendTest === true) {
|
||||
const connected = snapshot?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
||||
);
|
||||
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
||||
testMessage = publicConnectionTestResult(
|
||||
connectionTestTargetUnavailable('企业微信机器人'),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
testMessage = publicConnectionTestResult();
|
||||
} catch (error) {
|
||||
testMessage = publicConnectionTestResult(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
value = await publicStatus({ ...snapshot, ...(testMessage ? { testMessage } : {}) }, cachedEncode);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import {
|
|||
SET_WORKSPACE_ENDPOINT,
|
||||
validWorkspacePayload,
|
||||
} from '../shared/workspace-rpc.mjs';
|
||||
import {
|
||||
connectionTestTargetUnavailable,
|
||||
publicConnectionTestResult,
|
||||
} from '../../../../src/channels/shared/connection-test.mjs';
|
||||
|
||||
export const WEIXIN_RPC_CHANNEL = '/weixin';
|
||||
export const WEIXIN_ENDPOINTS = Object.freeze({
|
||||
|
|
@ -55,9 +59,11 @@ function payloadFailure(endpoint, payload) {
|
|||
: 'provision.verify requires an attemptId and a 4-to-8-digit code.';
|
||||
}
|
||||
if (endpoint === WEIXIN_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
||||
return exactKeys(payload, ['botId', 'sendTest'])
|
||||
&& validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || payload.sendTest === true)
|
||||
? null
|
||||
: 'bot.reconnect requires a botId.';
|
||||
: 'bot.reconnect requires a botId and optional sendTest=true.';
|
||||
}
|
||||
if (endpoint === WEIXIN_ENDPOINTS.deleteBot) {
|
||||
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId) && payload.confirm === true
|
||||
|
|
@ -165,7 +171,27 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|||
value = await controller.cancelProvisioning(payload.attemptId);
|
||||
if (!value) return badRequest('The provisioning attempt no longer exists.');
|
||||
} else if (endpoint === WEIXIN_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
const snapshot = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) return cancelled();
|
||||
let testMessage;
|
||||
if (payload.sendTest === true) {
|
||||
const connected = snapshot?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot?.connected === true,
|
||||
);
|
||||
if (!connected || typeof controller.sendConnectionTest !== 'function') {
|
||||
testMessage = publicConnectionTestResult(
|
||||
connectionTestTargetUnavailable('微信机器人'),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
testMessage = publicConnectionTestResult();
|
||||
} catch (error) {
|
||||
testMessage = publicConnectionTestResult(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
value = await publicStatus({ ...snapshot, ...(testMessage ? { testMessage } : {}) }, cachedEncode);
|
||||
} else if (endpoint === WEIXIN_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QRCode from 'qrcode';
|
||||
|
||||
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
||||
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
||||
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
||||
|
||||
|
|
@ -39,8 +40,9 @@ function payloadFailure(endpoint, payload) {
|
|||
? null : `${endpoint} requires an attemptId.`;
|
||||
}
|
||||
if (endpoint === WHATSAPP_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ['botId']) && validId(payload.botId)
|
||||
? null : 'bot.reconnect requires a botId.';
|
||||
return exactKeys(payload, ['botId', 'sendTest']) && validId(payload.botId)
|
||||
&& (payload.sendTest === undefined || typeof payload.sendTest === 'boolean')
|
||||
? null : 'bot.reconnect requires a botId and optional sendTest flag.';
|
||||
}
|
||||
if (endpoint === WHATSAPP_ENDPOINTS.deleteBot) {
|
||||
return exactKeys(payload, ['botId', 'confirm']) && validId(payload.botId)
|
||||
|
|
@ -119,7 +121,33 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|||
} else if (endpoint === WHATSAPP_ENDPOINTS.cancelProvisioning) {
|
||||
value = sanitizePublic(await controller.cancelProvisioning(payload.attemptId));
|
||||
} else if (endpoint === WHATSAPP_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
const checked = await controller.reconnectBot(payload.botId);
|
||||
if (signal?.aborted) {
|
||||
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
||||
}
|
||||
value = await publicStatus(checked, cachedEncode);
|
||||
if (payload.sendTest === true) {
|
||||
let testError = null;
|
||||
const connected = checked?.bots?.some(
|
||||
(bot) => bot?.botId === payload.botId && bot.connected === true,
|
||||
) === true;
|
||||
if (!connected) {
|
||||
testError = new Error('WhatsApp bot is not connected');
|
||||
testError.code = 'test-target-unavailable';
|
||||
} else {
|
||||
try {
|
||||
if (typeof controller.sendConnectionTest !== 'function') {
|
||||
const unavailable = new Error('Connection test is unavailable');
|
||||
unavailable.code = 'test-target-unavailable';
|
||||
throw unavailable;
|
||||
}
|
||||
await controller.sendConnectionTest(payload.botId);
|
||||
} catch (error) {
|
||||
testError = error;
|
||||
}
|
||||
}
|
||||
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
||||
}
|
||||
} else if (endpoint === WHATSAPP_ENDPOINTS.setWorkspace) {
|
||||
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
||||
value = await publicStatus(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue