mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-13 06:30:45 +08:00
Add manual IM bot credential access
This commit is contained in:
parent
d5d2c18ce0
commit
7123fce47a
42 changed files with 2458 additions and 889 deletions
1970
lib/client.js
1970
lib/client.js
File diff suppressed because it is too large
Load diff
248
lib/index.js
248
lib/index.js
|
|
@ -680,6 +680,49 @@ var DingtalkController = class {
|
|||
signal?.removeEventListener("abort", abortFromRequest);
|
||||
}
|
||||
}
|
||||
/** Binds one DingTalk application with an existing Client ID and Client Secret. */
|
||||
async bindCredentials({ clientId, clientSecret } = {}) {
|
||||
if (this.#closed) throw new Error("dsh-dingtalk controller is closed");
|
||||
const normalizedClientId = cleanString3(clientId);
|
||||
const normalizedSecret = cleanString3(clientSecret);
|
||||
if (!normalizedClientId || !normalizedSecret) {
|
||||
throw new TypeError("DingTalk Client ID and Client Secret are required");
|
||||
}
|
||||
if (this.#activeAttemptId) await this.cancelProvisioning(this.#activeAttemptId);
|
||||
if (this.#closed) throw new Error("dsh-dingtalk controller is closed");
|
||||
const identity = deriveDingtalkBotIdentity(normalizedClientId);
|
||||
await this.#withBotTransition(identity.botId, async () => {
|
||||
if (this.#closed) throw abortError();
|
||||
const previousConfig = this.#configStore.getByClientId(normalizedClientId);
|
||||
const previousSecret = await this.#credentials.resolve(identity.secretRef).catch(() => void 0);
|
||||
if (this.#closed) throw abortError();
|
||||
const config = {
|
||||
botId: identity.botId,
|
||||
clientId: normalizedClientId,
|
||||
secretRef: identity.secretRef,
|
||||
approvedSenders: previousConfig?.approvedSenders ?? []
|
||||
};
|
||||
await this.#credentials.set(identity.secretRef, normalizedSecret);
|
||||
try {
|
||||
await this.#configStore.save(config);
|
||||
} catch (error) {
|
||||
await this.#restoreCredential(identity.secretRef, previousSecret);
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
await this.#startRuntime(config, normalizedSecret);
|
||||
this.#errors.delete(identity.botId);
|
||||
} catch {
|
||||
this.#errors.set(
|
||||
identity.botId,
|
||||
safeError("connection-failed", "\u9489\u9489\u5DF2\u63A5\u5165\uFF0C\u4F46\u6D88\u606F\u8FDE\u63A5\u6682\u672A\u5C31\u7EEA\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002")
|
||||
);
|
||||
this.#logger.warn?.("[dsh-dingtalk] credential-bound bot saved but its connection is not ready");
|
||||
}
|
||||
this.#touch();
|
||||
});
|
||||
return this.status();
|
||||
}
|
||||
/** Polls one QR registration without exposing its device code or returned secret. */
|
||||
async registrationStatus(attemptId) {
|
||||
const record = this.#attempts.get(attemptId);
|
||||
|
|
@ -3024,6 +3067,7 @@ var DINGTALK_ENDPOINTS = Object.freeze({
|
|||
beginProvisioning: "provision.begin",
|
||||
pollProvisioning: "provision.poll",
|
||||
cancelProvisioning: "provision.cancel",
|
||||
bindCredentials: "bot.bind-credentials",
|
||||
reconnectBot: "bot.reconnect",
|
||||
deleteBot: "bot.delete",
|
||||
approveSender: "bot.sender.approve",
|
||||
|
|
@ -3051,6 +3095,9 @@ function exactKeys(value, allowed) {
|
|||
function validId(value) {
|
||||
return typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
|
||||
}
|
||||
function validCredential(value, maxLength) {
|
||||
return typeof value === "string" && value.trim().length > 0 && value.length <= maxLength;
|
||||
}
|
||||
function payloadFailure(endpoint, payload) {
|
||||
if (!isRecord(payload)) return "Payload must be an object.";
|
||||
if (endpoint === DINGTALK_ENDPOINTS.status) {
|
||||
|
|
@ -3062,6 +3109,9 @@ function payloadFailure(endpoint, payload) {
|
|||
if ([DINGTALK_ENDPOINTS.pollProvisioning, DINGTALK_ENDPOINTS.cancelProvisioning].includes(endpoint)) {
|
||||
return exactKeys(payload, ["attemptId"]) && validId(payload.attemptId) ? null : `${endpoint} requires an attemptId.`;
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.bindCredentials) {
|
||||
return exactKeys(payload, ["clientId", "clientSecret"]) && validCredential(payload.clientId, 256) && validCredential(payload.clientSecret, 1024) ? null : "bot.bind-credentials requires Client ID and Client Secret.";
|
||||
}
|
||||
if (endpoint === DINGTALK_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys(payload, ["botId"]) && validId(payload.botId) ? null : "bot.reconnect requires a botId.";
|
||||
}
|
||||
|
|
@ -3125,6 +3175,7 @@ function assertController(controller) {
|
|||
"startProvisioning",
|
||||
"registrationStatus",
|
||||
"cancelProvisioning",
|
||||
"bindCredentials",
|
||||
"reconnectBot",
|
||||
"deleteBot",
|
||||
"approveSender",
|
||||
|
|
@ -3171,6 +3222,8 @@ function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|||
value = await controller.cancelProvisioning(payload.attemptId);
|
||||
if (!value) return badRequest("The provisioning attempt no longer exists.");
|
||||
value = sanitizePublic(value);
|
||||
} 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);
|
||||
} else if (endpoint === DINGTALK_ENDPOINTS.deleteBot) {
|
||||
|
|
@ -3591,6 +3644,7 @@ function isBotSender(event) {
|
|||
}
|
||||
function isAllowedSender(event, allowedOpenIds) {
|
||||
if (!allowedOpenIds || allowedOpenIds.size === 0) return false;
|
||||
if (allowedOpenIds.has("*")) return true;
|
||||
const senderOpenId = event?.sender?.sender_id?.open_id;
|
||||
return typeof senderOpenId === "string" && allowedOpenIds.has(senderOpenId);
|
||||
}
|
||||
|
|
@ -4838,6 +4892,7 @@ var ACTIVE_REGISTRATION_STATES2 = /* @__PURE__ */ new Set([
|
|||
"domain_switched"
|
||||
]);
|
||||
var MUTABLE_REGISTRATION_STATES = /* @__PURE__ */ new Set([...ACTIVE_REGISTRATION_STATES2, "saving"]);
|
||||
var ALL_VISIBLE_SENDERS = "*";
|
||||
function idleConnection() {
|
||||
return {
|
||||
ready: false,
|
||||
|
|
@ -5029,6 +5084,68 @@ var MultiBotDshFeishuController = class {
|
|||
selectedBotId: botId
|
||||
});
|
||||
}
|
||||
async bindCredentials({ appId, appSecret, domain = "feishu" } = {}) {
|
||||
this.#assertOpen();
|
||||
const normalizedAppId = typeof appId === "string" ? appId.trim() : "";
|
||||
const normalizedSecret = typeof appSecret === "string" ? appSecret.trim() : "";
|
||||
const normalizedDomain = domain === "lark" ? "lark" : "feishu";
|
||||
if (!normalizedAppId || !normalizedSecret) {
|
||||
throw new TypeError("Feishu App ID and App Secret are required");
|
||||
}
|
||||
return this.#serializeConfig(async () => {
|
||||
this.#assertOpen();
|
||||
const bot = await this.#verifyApp({
|
||||
appId: normalizedAppId,
|
||||
appSecret: normalizedSecret,
|
||||
domain: normalizedDomain
|
||||
});
|
||||
this.#assertOpen();
|
||||
const existing = this.#configStore.list().find(
|
||||
(candidate) => candidate.appId === normalizedAppId
|
||||
);
|
||||
const botId = existing?.id ?? this.#createBotId();
|
||||
if (typeof botId !== "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(botId) || !existing && this.#configStore.getBot(botId)) {
|
||||
throw new Error("Bot id generator returned an invalid or duplicate id");
|
||||
}
|
||||
const secretRef = existing?.secretRef ?? secretRefFor(botId);
|
||||
const previousSecret = await this.#credentials.resolve(secretRef).catch(() => void 0);
|
||||
const config = {
|
||||
...existing,
|
||||
id: botId,
|
||||
appId: normalizedAppId,
|
||||
secretRef,
|
||||
ownerOpenIds: existing?.ownerOpenIds?.length ? existing.ownerOpenIds : [ALL_VISIBLE_SENDERS],
|
||||
domain: normalizedDomain,
|
||||
botName: bot.name,
|
||||
botOpenId: bot.openId,
|
||||
activated: bot.activated,
|
||||
deletionPending: false,
|
||||
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
||||
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
||||
};
|
||||
await this.#credentials.set(secretRef, normalizedSecret);
|
||||
let saved;
|
||||
try {
|
||||
saved = await this.#configStore.saveBot(config);
|
||||
} catch (error) {
|
||||
await this.#restoreCredential(secretRef, previousSecret);
|
||||
throw error;
|
||||
}
|
||||
await this.#withBotTransition(botId, async () => {
|
||||
try {
|
||||
await this.#startRuntime(saved, normalizedSecret);
|
||||
this.#botErrors.delete(botId);
|
||||
} catch {
|
||||
this.#botErrors.set(botId, {
|
||||
code: "connection_failed",
|
||||
message: "\u673A\u5668\u4EBA\u5DF2\u7ECF\u7ED1\u5B9A\uFF0C\u4F46\u957F\u8FDE\u63A5\u672A\u5C31\u7EEA\uFF0C\u8BF7\u70B9\u51FB\u91CD\u8BD5\u3002"
|
||||
});
|
||||
}
|
||||
});
|
||||
this.#touch();
|
||||
return this.status(botId);
|
||||
});
|
||||
}
|
||||
async reconnectBot(botId) {
|
||||
this.#assertOpen();
|
||||
return this.#withBotTransition(botId, async () => {
|
||||
|
|
@ -5571,6 +5688,7 @@ var FEISHU_ENDPOINTS = Object.freeze({
|
|||
beginProvisioning: "provision.begin",
|
||||
pollProvisioning: "provision.poll",
|
||||
cancelProvisioning: "provision.cancel",
|
||||
bindCredentials: "bot.bind-credentials",
|
||||
reconnectBot: "bot.reconnect",
|
||||
disconnectBot: "bot.disconnect",
|
||||
deleteBot: "bot.delete",
|
||||
|
|
@ -5642,6 +5760,9 @@ function finiteNumber(value) {
|
|||
function safeOpaqueId(value) {
|
||||
return typeof value === "string" && SAFE_ID.test(value);
|
||||
}
|
||||
function validCredential2(value, maxLength) {
|
||||
return typeof value === "string" && value.trim().length > 0 && value.length <= maxLength;
|
||||
}
|
||||
function publicError2(error) {
|
||||
if (!error || typeof error !== "object") return null;
|
||||
const code = typeof error.code === "string" && Object.hasOwn(PUBLIC_ERROR_MESSAGES, error.code) ? error.code : "registration_failed";
|
||||
|
|
@ -5795,6 +5916,9 @@ function validPayload(endpoint, payload) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
if (endpoint === FEISHU_ENDPOINTS.bindCredentials) {
|
||||
return hasOnlyKeys(payload, /* @__PURE__ */ new Set(["appId", "appSecret"])) && validCredential2(payload.appId, 256) && validCredential2(payload.appSecret, 1024) ? null : "Credential binding requires App ID and App Secret.";
|
||||
}
|
||||
if (endpoint === FEISHU_ENDPOINTS.pollProvisioning || endpoint === FEISHU_ENDPOINTS.cancelProvisioning) {
|
||||
return hasOnlyKeys(payload, /* @__PURE__ */ new Set(["attemptId"])) && safeOpaqueId(payload.attemptId) ? null : "A single valid attemptId is required.";
|
||||
}
|
||||
|
|
@ -5929,6 +6053,14 @@ function createFeishuRpcHandler(controller, { encodeQr = qrCodeDataUrl } = {}) {
|
|||
if (url) qrCache.delete(url);
|
||||
attemptQr.delete(payload.attemptId);
|
||||
value = { status: "failed", message: "Registration was cancelled." };
|
||||
} else if (endpoint === FEISHU_ENDPOINTS.bindCredentials) {
|
||||
if (typeof controller.bindCredentials !== "function") {
|
||||
throw new Error("Credential binding is unavailable");
|
||||
}
|
||||
value = await toPublicFeishuStatus(
|
||||
await controller.bindCredentials(payload),
|
||||
{ encodeQr: cachedEncodeQr }
|
||||
);
|
||||
} else if (endpoint === FEISHU_ENDPOINTS.testConnection) {
|
||||
const current = await controller.status();
|
||||
const alreadyConnected = current?.connected === true || connectionFacts(current?.connection).connected;
|
||||
|
|
@ -6532,6 +6664,47 @@ var QqController = class {
|
|||
registrationStatus(attemptId) {
|
||||
return publicAttempt2(this.#attempts.get(attemptId));
|
||||
}
|
||||
async bindCredentials({ appId, appSecret } = {}) {
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
const normalizedAppId = cleanString6(appId);
|
||||
const normalizedSecret = cleanString6(appSecret);
|
||||
if (!normalizedAppId || !normalizedSecret) {
|
||||
throw new TypeError("QQ AppID and AppSecret are required");
|
||||
}
|
||||
if (this.#activeAttemptId) await this.cancelProvisioning(this.#activeAttemptId);
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
const identity = deriveQqBotIdentity(normalizedAppId);
|
||||
await this.#withBotTransition(identity.botId, async () => {
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
const previousConfig = this.#configStore.getByAppId(normalizedAppId);
|
||||
const previousSecret = await this.#credentials.resolve(identity.secretRef).catch(() => void 0);
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
const config = {
|
||||
botId: identity.botId,
|
||||
appId: normalizedAppId,
|
||||
secretRef: identity.secretRef,
|
||||
ownerUserOpenid: previousConfig?.ownerUserOpenid ?? "*",
|
||||
createdAt: previousConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
||||
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
||||
};
|
||||
await this.#credentials.set(identity.secretRef, normalizedSecret);
|
||||
try {
|
||||
await this.#configStore.save(config);
|
||||
} catch (error) {
|
||||
await this.#restoreCredential(identity.secretRef, previousSecret);
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
await this.#startRuntime(config, normalizedSecret);
|
||||
this.#errors.delete(identity.botId);
|
||||
} catch (error) {
|
||||
this.#errors.set(identity.botId, safeError2("connection-failed", "QQ \u673A\u5668\u4EBA\u5DF2\u7ED1\u5B9A\uFF0C\u6D88\u606F\u8FDE\u63A5\u6682\u672A\u5C31\u7EEA\u3002"));
|
||||
this.#logger.warn?.(`[dsh-im:qq] bot ${identity.botId} credential connection failed:`, error);
|
||||
}
|
||||
this.#touch();
|
||||
});
|
||||
return this.status();
|
||||
}
|
||||
async cancelProvisioning(attemptId) {
|
||||
const record = this.#attempts.get(attemptId);
|
||||
if (!record) return null;
|
||||
|
|
@ -6627,6 +6800,7 @@ var QqController = class {
|
|||
if (this.#closed) return;
|
||||
this.#closed = true;
|
||||
if (this.#activeAttemptId) await this.cancelProvisioning(this.#activeAttemptId);
|
||||
await Promise.allSettled([...this.#transitions.values()]);
|
||||
await Promise.allSettled([...this.#runtimes.keys()].map((botId) => this.#stopRuntime(botId)));
|
||||
}
|
||||
async #completeProvisioning(record, credentials) {
|
||||
|
|
@ -6701,7 +6875,9 @@ var QqController = class {
|
|||
});
|
||||
}
|
||||
async #startRuntime(config, appSecret) {
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
await this.#stopRuntime(config.botId);
|
||||
if (this.#closed) throw new Error("QQ controller is closed");
|
||||
const runtime = await this.#createRuntime({ botId: config.botId, config, appSecret });
|
||||
if (!runtime || typeof runtime.start !== "function" || typeof runtime.stop !== "function") {
|
||||
throw new TypeError("createRuntime returned an invalid QQ runtime");
|
||||
|
|
@ -6824,7 +7000,7 @@ var QqHarnessBridge = class {
|
|||
if (!["c2c", "group"].includes(message.kind) || this.#state.hasSeen(messageId)) return;
|
||||
this.#status.messagesReceived += 1;
|
||||
this.#status.lastMessageAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
if (sender !== this.#ownerUserOpenid) {
|
||||
if (this.#ownerUserOpenid !== "*" && sender !== this.#ownerUserOpenid) {
|
||||
this.#status.messagesRejected += 1;
|
||||
this.#status.lastRejectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
||||
return;
|
||||
|
|
@ -7012,7 +7188,7 @@ var QqRuntime = class {
|
|||
});
|
||||
bot.use?.(this.#typingMiddleware({
|
||||
keepAlive: true,
|
||||
predicate: (ctx) => ctx?.message?.senderId === this.#config.ownerUserOpenid
|
||||
predicate: (ctx) => this.#config.ownerUserOpenid === "*" || ctx?.message?.senderId === this.#config.ownerUserOpenid
|
||||
}));
|
||||
const controller = new AbortController();
|
||||
this.#abortController = controller;
|
||||
|
|
@ -7426,6 +7602,7 @@ var QQ_ENDPOINTS = Object.freeze({
|
|||
beginProvisioning: "provision.begin",
|
||||
pollProvisioning: "provision.poll",
|
||||
cancelProvisioning: "provision.cancel",
|
||||
bindCredentials: "bot.bind-credentials",
|
||||
reconnectBot: "bot.reconnect",
|
||||
deleteBot: "bot.delete"
|
||||
});
|
||||
|
|
@ -7447,6 +7624,9 @@ function exactKeys2(value, allowed) {
|
|||
function validId2(value) {
|
||||
return typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
|
||||
}
|
||||
function validCredential3(value, maxLength) {
|
||||
return typeof value === "string" && value.trim().length > 0 && value.length <= maxLength;
|
||||
}
|
||||
function payloadFailure2(endpoint, payload) {
|
||||
if (!isRecord2(payload)) return "Payload must be an object.";
|
||||
if (endpoint === QQ_ENDPOINTS.status) return exactKeys2(payload, []) ? null : "connection.status does not accept fields.";
|
||||
|
|
@ -7456,6 +7636,9 @@ function payloadFailure2(endpoint, payload) {
|
|||
if ([QQ_ENDPOINTS.pollProvisioning, QQ_ENDPOINTS.cancelProvisioning].includes(endpoint)) {
|
||||
return exactKeys2(payload, ["attemptId"]) && validId2(payload.attemptId) ? null : `${endpoint} requires an attemptId.`;
|
||||
}
|
||||
if (endpoint === QQ_ENDPOINTS.bindCredentials) {
|
||||
return exactKeys2(payload, ["appId", "appSecret"]) && validCredential3(payload.appId, 256) && validCredential3(payload.appSecret, 1024) ? null : "bot.bind-credentials requires AppID and AppSecret.";
|
||||
}
|
||||
if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys2(payload, ["botId"]) && validId2(payload.botId) ? null : "bot.reconnect requires a botId.";
|
||||
}
|
||||
|
|
@ -7491,7 +7674,7 @@ async function publicStatus2(status, encodeQr) {
|
|||
return sanitizePublic2(value);
|
||||
}
|
||||
function createQqRpcHandler(controller, { encodeQr = qrDataUrl2 } = {}) {
|
||||
for (const method of ["status", "startProvisioning", "registrationStatus", "cancelProvisioning", "reconnectBot", "deleteBot"]) {
|
||||
for (const method of ["status", "startProvisioning", "registrationStatus", "cancelProvisioning", "bindCredentials", "reconnectBot", "deleteBot"]) {
|
||||
if (typeof controller?.[method] !== "function") throw new TypeError(`A complete QQ controller is required (${method})`);
|
||||
}
|
||||
const qrCache = /* @__PURE__ */ new Map();
|
||||
|
|
@ -7519,6 +7702,8 @@ function createQqRpcHandler(controller, { encodeQr = qrDataUrl2 } = {}) {
|
|||
value = await withEncodedQr2(current, cachedEncode);
|
||||
} else if (endpoint === QQ_ENDPOINTS.cancelProvisioning) {
|
||||
value = sanitizePublic2(await controller.cancelProvisioning(payload.attemptId));
|
||||
} else if (endpoint === QQ_ENDPOINTS.bindCredentials) {
|
||||
value = await publicStatus2(await controller.bindCredentials(payload), cachedEncode);
|
||||
} else if (endpoint === QQ_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus2(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
} else {
|
||||
|
|
@ -8024,6 +8209,49 @@ var WecomController = class {
|
|||
await record.polling.catch(() => void 0);
|
||||
return publicAttempt3(record);
|
||||
}
|
||||
async bindCredentials({ botId, secret } = {}) {
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
const remoteBotId = cleanString9(botId);
|
||||
const normalizedSecret = cleanString9(secret);
|
||||
if (!remoteBotId || !normalizedSecret) {
|
||||
throw new TypeError("Enterprise WeChat Bot ID and Secret are required");
|
||||
}
|
||||
if (this.#activeAttemptId) await this.cancelProvisioning(this.#activeAttemptId);
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
const identity = deriveWecomBotIdentity(remoteBotId);
|
||||
await this.#withBotTransition(identity.botId, async () => {
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
const previousConfig = this.#configStore.getByRemoteBotId(remoteBotId);
|
||||
const previousSecret = await this.#credentials.resolve(identity.secretRef).catch(() => void 0);
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
const config = {
|
||||
botId: identity.botId,
|
||||
remoteBotId,
|
||||
secretRef: identity.secretRef,
|
||||
createdAt: previousConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
||||
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
||||
};
|
||||
await this.#credentials.set(identity.secretRef, normalizedSecret);
|
||||
try {
|
||||
await this.#configStore.save(config);
|
||||
} catch (error) {
|
||||
await this.#restoreCredential(identity.secretRef, previousSecret);
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
await this.#startRuntime(config, normalizedSecret);
|
||||
this.#errors.delete(identity.botId);
|
||||
} catch {
|
||||
this.#errors.set(
|
||||
identity.botId,
|
||||
safeError3("connection-failed", "\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA\u5DF2\u7ED1\u5B9A\uFF0C\u6D88\u606F\u8FDE\u63A5\u6682\u672A\u5C31\u7EEA\u3002")
|
||||
);
|
||||
this.#logger.warn?.(`[dsh-im:wecom] bot ${identity.botId} credential connection failed`);
|
||||
}
|
||||
this.#touch();
|
||||
});
|
||||
return this.status();
|
||||
}
|
||||
async cancelProvisioning(attemptId) {
|
||||
const record = this.#attempts.get(attemptId);
|
||||
if (!record) return null;
|
||||
|
|
@ -8117,6 +8345,7 @@ var WecomController = class {
|
|||
if (this.#closed) return;
|
||||
this.#closed = true;
|
||||
if (this.#activeAttemptId) await this.cancelProvisioning(this.#activeAttemptId);
|
||||
await Promise.allSettled([...this.#transitions.values()]);
|
||||
await Promise.allSettled([...this.#runtimes.keys()].map((botId) => this.#stopRuntime(botId)));
|
||||
}
|
||||
async #pollAttempt(record) {
|
||||
|
|
@ -8213,7 +8442,9 @@ var WecomController = class {
|
|||
});
|
||||
}
|
||||
async #startRuntime(config, secret) {
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
await this.#stopRuntime(config.botId);
|
||||
if (this.#closed) throw new Error("Enterprise WeChat controller is closed");
|
||||
const runtime = await this.#createRuntime({ botId: config.botId, config, secret });
|
||||
if (!runtime || typeof runtime.start !== "function" || typeof runtime.stop !== "function") {
|
||||
throw new TypeError("createRuntime returned an invalid Enterprise WeChat runtime");
|
||||
|
|
@ -8907,6 +9138,7 @@ var WECOM_ENDPOINTS = Object.freeze({
|
|||
beginProvisioning: "provision.begin",
|
||||
pollProvisioning: "provision.poll",
|
||||
cancelProvisioning: "provision.cancel",
|
||||
bindCredentials: "bot.bind-credentials",
|
||||
reconnectBot: "bot.reconnect",
|
||||
deleteBot: "bot.delete"
|
||||
});
|
||||
|
|
@ -8929,6 +9161,9 @@ function exactKeys3(value, allowed) {
|
|||
function validId3(value) {
|
||||
return typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
|
||||
}
|
||||
function validCredential4(value, maxLength) {
|
||||
return typeof value === "string" && value.trim().length > 0 && value.length <= maxLength;
|
||||
}
|
||||
function payloadFailure3(endpoint, payload) {
|
||||
if (!isRecord3(payload)) return "Payload must be an object.";
|
||||
if (endpoint === WECOM_ENDPOINTS.status) return exactKeys3(payload, []) ? null : "connection.status does not accept fields.";
|
||||
|
|
@ -8938,6 +9173,9 @@ function payloadFailure3(endpoint, payload) {
|
|||
if ([WECOM_ENDPOINTS.pollProvisioning, WECOM_ENDPOINTS.cancelProvisioning].includes(endpoint)) {
|
||||
return exactKeys3(payload, ["attemptId"]) && validId3(payload.attemptId) ? null : `${endpoint} requires an attemptId.`;
|
||||
}
|
||||
if (endpoint === WECOM_ENDPOINTS.bindCredentials) {
|
||||
return exactKeys3(payload, ["botId", "secret"]) && validCredential4(payload.botId, 512) && validCredential4(payload.secret, 1024) ? null : "bot.bind-credentials requires Bot ID and Secret.";
|
||||
}
|
||||
if (endpoint === WECOM_ENDPOINTS.reconnectBot) {
|
||||
return exactKeys3(payload, ["botId"]) && validId3(payload.botId) ? null : "bot.reconnect requires a botId.";
|
||||
}
|
||||
|
|
@ -8973,7 +9211,7 @@ async function publicStatus3(status, encodeQr) {
|
|||
return sanitizePublic3(value);
|
||||
}
|
||||
function createWecomRpcHandler(controller, { encodeQr = qrDataUrl3 } = {}) {
|
||||
for (const method of ["status", "startProvisioning", "registrationStatus", "cancelProvisioning", "reconnectBot", "deleteBot"]) {
|
||||
for (const method of ["status", "startProvisioning", "registrationStatus", "cancelProvisioning", "bindCredentials", "reconnectBot", "deleteBot"]) {
|
||||
if (typeof controller?.[method] !== "function") {
|
||||
throw new TypeError(`A complete Enterprise WeChat controller is required (${method})`);
|
||||
}
|
||||
|
|
@ -9006,6 +9244,8 @@ function createWecomRpcHandler(controller, { encodeQr = qrDataUrl3 } = {}) {
|
|||
value = await withEncodedQr3(current, cachedEncode);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.cancelProvisioning) {
|
||||
value = sanitizePublic3(await controller.cancelProvisioning(payload.attemptId));
|
||||
} else if (endpoint === WECOM_ENDPOINTS.bindCredentials) {
|
||||
value = await publicStatus3(await controller.bindCredentials(payload), cachedEncode);
|
||||
} else if (endpoint === WECOM_ENDPOINTS.reconnectBot) {
|
||||
value = await publicStatus3(await controller.reconnectBot(payload.botId), cachedEncode);
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue