🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@wecom/aibot-node-sdk

Package Overview
Dependencies
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wecom/aibot-node-sdk - npm Package Compare versions

Comparing version
1.0.5-beta.1
to
1.0.5
+26
-0
dist/client.d.ts

@@ -184,2 +184,28 @@ import { EventEmitter } from 'eventemitter3';

/**
* 检查指定消息帧是否有未完成的 ack(即上一条消息还未收到回执)
*
* 用于流式场景:调用方可据此决定是否跳过当前中间帧,避免排队积压。
*
* @param frame - 收到的原始 WebSocket 帧
* @returns true 表示有消息正在等待 ack
*/
hasPendingReplyAck(frame: WsFrameHeaders): boolean;
/**
* 非阻塞流式文本回复
*
* 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
* 避免流式中间帧排队积压导致延迟。
*
* 注意:finish=true 的最终帧不受此限制,始终保证发送(走正常队列)。
*
* @param frame - 收到的原始 WebSocket 帧
* @param streamId - 流式消息 ID
* @param content - 回复内容
* @param finish - 是否结束流式消息
* @param msgItem - 图文混排项(仅在 finish=true 时有效),用于在结束时附带图片内容
* @param feedback - 反馈信息(仅在首次回复时设置)
* @returns Promise<WsFrame> 正常发送时返回回执帧,跳过时返回 'skipped'
*/
replyStreamNonBlocking(frame: WsFrameHeaders, streamId: string, content: string, finish?: boolean, msgItem?: ReplyMsgItem[], feedback?: ReplyFeedback): Promise<WsFrame | 'skipped'>;
/**
* 获取当前连接状态

@@ -186,0 +212,0 @@ */

+50
-144

@@ -9,3 +9,2 @@ 'use strict';

var WebSocket = require('ws');
var crypto$1 = require('node:crypto');

@@ -59,2 +58,4 @@ /**

MessageType["File"] = "file";
/** 视频消息 */
MessageType["Video"] = "video";
})(exports.MessageType || (exports.MessageType = {}));

@@ -724,2 +725,13 @@

/**
* 检查指定 reqId 是否有待回执的消息(即上一条消息还未收到 ack)
*
* 用于流式场景:调用方可据此决定是否跳过当前帧,避免排队积压。
*
* @param reqId - 要检查的 req_id
* @returns true 表示有消息正在等待 ack
*/
hasPendingAck(reqId) {
return this.pendingAcks.has(reqId);
}
/**
* 获取当前连接状态

@@ -793,2 +805,5 @@ */

break;
case exports.MessageType.Video:
emitter.emit('message.video', frame);
break;
default:

@@ -1342,153 +1357,48 @@ this.logger.debug(`Received unhandled message type: ${body.msgtype}`);

/**
* 获取当前连接状态
* 检查指定消息帧是否有未完成的 ack(即上一条消息还未收到回执)
*
* 用于流式场景:调用方可据此决定是否跳过当前中间帧,避免排队积压。
*
* @param frame - 收到的原始 WebSocket 帧
* @returns true 表示有消息正在等待 ack
*/
get isConnected() {
return this.wsManager.isConnected;
hasPendingReplyAck(frame) {
const reqId = frame.headers?.req_id || '';
return this.wsManager.hasPendingAck(reqId);
}
/**
* 获取 API 客户端实例(供高级用途使用,如文件下载)
* 非阻塞流式文本回复
*
* 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
* 避免流式中间帧排队积压导致延迟。
*
* 注意:finish=true 的最终帧不受此限制,始终保证发送(走正常队列)。
*
* @param frame - 收到的原始 WebSocket 帧
* @param streamId - 流式消息 ID
* @param content - 回复内容
* @param finish - 是否结束流式消息
* @param msgItem - 图文混排项(仅在 finish=true 时有效),用于在结束时附带图片内容
* @param feedback - 反馈信息(仅在首次回复时设置)
* @returns Promise<WsFrame> 正常发送时返回回执帧,跳过时返回 'skipped'
*/
get api() {
return this.apiClient;
}
}
/**
* WeCom 加解密通用核心
* 独立于 Webhook、WebSocket、Agent 的具体协议形态,统一提供基于 AES-256-CBC
* 的加解密与 SHA1 签名计算能力。
*/
const CRYPTO_CONSTANTS = {
/** PKCS#7 块大小 */
PKCS7_BLOCK_SIZE: 32,
/** AES Key 长度 */
AES_KEY_LENGTH: 32,
};
/**
* 解码企业微信提供的 Base64 encodingAESKey
*/
function decodeEncodingAESKey(encodingAESKey) {
const trimmed = encodingAESKey.trim();
if (!trimmed)
throw new Error("encodingAESKey missing");
const withPadding = trimmed.endsWith("=") ? trimmed : `${trimmed}=`;
const key = Buffer.from(withPadding, "base64");
if (key.length !== CRYPTO_CONSTANTS.AES_KEY_LENGTH) {
throw new Error(`invalid encodingAESKey (expected ${CRYPTO_CONSTANTS.AES_KEY_LENGTH} bytes, got ${key.length})`);
}
return key;
}
/**
* PKCS#7 填充
*/
function pkcs7Pad(buf, blockSize) {
const mod = buf.length % blockSize;
const pad = mod === 0 ? blockSize : blockSize - mod;
const padByte = Buffer.alloc(1, pad);
return Buffer.concat([buf, Buffer.alloc(pad, padByte[0])]);
}
/**
* PKCS#7 解除填充
*/
function pkcs7Unpad(buf, blockSize) {
if (buf.length === 0)
throw new Error("invalid pkcs7 payload");
const pad = buf[buf.length - 1];
if (pad < 1 || pad > blockSize) {
throw new Error("invalid pkcs7 padding value");
}
if (pad > buf.length) {
throw new Error("invalid pkcs7 payload length");
}
for (let i = 0; i < pad; i += 1) {
if (buf[buf.length - 1 - i] !== pad) {
throw new Error("invalid pkcs7 padding byte");
replyStreamNonBlocking(frame, streamId, content, finish = false, msgItem, feedback) {
// finish=true 的最终帧必须发送,不做跳过判断
if (!finish && this.hasPendingReplyAck(frame)) {
return Promise.resolve('skipped');
}
return this.replyStream(frame, streamId, content, finish, msgItem, feedback);
}
return buf.subarray(0, buf.length - pad);
}
/**
* 计算 SHA1 哈希
*/
function sha1Hex(input) {
return crypto$1.createHash("sha1").update(input).digest("hex");
}
class WecomCrypto {
constructor(token, encodingAESKey, receiveId // 对应企业微信的 corpId 或 botId (用于校验与追加)
) {
this.token = token;
this.encodingAESKey = encodingAESKey;
this.receiveId = receiveId;
if (!token)
throw new Error("token is required");
this.aesKey = decodeEncodingAESKey(encodingAESKey);
this.iv = this.aesKey.subarray(0, 16);
}
/**
* 计算 WeCom 消息签名
* 获取当前连接状态
*/
computeSignature(timestamp, nonce, encrypt) {
const parts = [this.token, timestamp, nonce, encrypt]
.map((v) => String(v ?? ""))
.sort();
return sha1Hex(parts.join(""));
get isConnected() {
return this.wsManager.isConnected;
}
/**
* 验证 WeCom 消息签名
* 获取 API 客户端实例(供高级用途使用,如文件下载)
*/
verifySignature(signature, timestamp, nonce, encrypt) {
const expected = this.computeSignature(timestamp, nonce, encrypt);
return expected === signature;
get api() {
return this.apiClient;
}
/**
* 消息解密
* 返回纯文本字符串(XML 或 JSON 根据上层业务而定)
*/
decrypt(encryptText) {
const decipher = crypto$1.createDecipheriv("aes-256-cbc", this.aesKey, this.iv);
decipher.setAutoPadding(false);
const decryptedPadded = Buffer.concat([
decipher.update(Buffer.from(encryptText, "base64")),
decipher.final(),
]);
const decrypted = pkcs7Unpad(decryptedPadded, CRYPTO_CONSTANTS.PKCS7_BLOCK_SIZE);
if (decrypted.length < 20) {
throw new Error(`invalid payload (expected >=20 bytes, got ${decrypted.length})`);
}
// 16 bytes random + 4 bytes length + msg + receiveId
const msgLen = decrypted.readUInt32BE(16);
const msgStart = 20;
const msgEnd = msgStart + msgLen;
if (msgEnd > decrypted.length) {
throw new Error(`invalid msg length (msgEnd=${msgEnd}, total=${decrypted.length})`);
}
const msg = decrypted.subarray(msgStart, msgEnd).toString("utf8");
const receiveId = this.receiveId ?? "";
if (receiveId) {
const trailing = decrypted.subarray(msgEnd).toString("utf8");
if (trailing !== receiveId) {
throw new Error(`receiveId mismatch (expected "${receiveId}", got "${trailing}")`);
}
}
return msg;
}
/**
* 消息加密
* 加密明文并返回 base64 格式密文与对应的新签名
*/
encrypt(plainText, timestamp, nonce) {
const random16 = crypto$1.randomBytes(16);
const msgBuf = Buffer.from(plainText ?? "", "utf8");
const msgLen = Buffer.alloc(4);
msgLen.writeUInt32BE(msgBuf.length, 0);
const receiveIdBuf = Buffer.from(this.receiveId ?? "", "utf8");
const raw = Buffer.concat([random16, msgLen, msgBuf, receiveIdBuf]);
const padded = pkcs7Pad(raw, CRYPTO_CONSTANTS.PKCS7_BLOCK_SIZE);
const cipher = crypto$1.createCipheriv("aes-256-cbc", this.aesKey, this.iv);
cipher.setAutoPadding(false);
const encryptedBuf = Buffer.concat([cipher.update(padded), cipher.final()]);
const encryptBase64 = encryptedBuf.toString("base64");
const signature = this.computeSignature(timestamp, nonce, encryptBase64);
return { encrypt: encryptBase64, signature };
}
}

@@ -1507,6 +1417,4 @@

exports.WeComApiClient = WeComApiClient;
exports.WecomCrypto = WecomCrypto;
exports.WsCmd = WsCmd;
exports.WsConnectionManager = WsConnectionManager;
exports.decodeEncodingAESKey = decodeEncodingAESKey;
exports.decryptFile = decryptFile;

@@ -1516,4 +1424,2 @@ exports.default = AiBot;

exports.generateReqId = generateReqId;
exports.pkcs7Pad = pkcs7Pad;
exports.pkcs7Unpad = pkcs7Unpad;
//# sourceMappingURL=index.cjs.js.map

@@ -84,3 +84,5 @@ import { EventEmitter } from 'eventemitter3';

/** 文件消息 */
File = "file"
File = "file",
/** 视频消息 */
Video = "video"
}

@@ -116,2 +118,9 @@ /** 消息发送者信息 */

}
/** 视频结构体 */
interface VideoContent {
/** 视频的下载 url(五分钟内有效,已加密) */
url: string;
/** 解密密钥,长连接模式下返回,每个下载链接的 aeskey 唯一 */
aeskey?: string;
}
/** 图文混排子项 */

@@ -199,2 +208,8 @@ interface MixedMsgItem {

}
/** 视频消息 */
interface VideoMessage extends BaseMessage {
msgtype: MessageType.Video;
/** 视频内容 */
video: VideoContent;
}
/** 回复消息选项 */

@@ -768,2 +783,4 @@ interface ReplyOptions {

'message.file': (data: WsFrame<FileMessage>) => void;
/** 收到视频消息,body 为 VideoMessage */
'message.video': (data: WsFrame<VideoMessage>) => void;
/** 收到事件回调(所有事件类型),body 为 EventMessage */

@@ -988,2 +1005,28 @@ event: (data: WsFrame<EventMessage>) => void;

/**
* 检查指定消息帧是否有未完成的 ack(即上一条消息还未收到回执)
*
* 用于流式场景:调用方可据此决定是否跳过当前中间帧,避免排队积压。
*
* @param frame - 收到的原始 WebSocket 帧
* @returns true 表示有消息正在等待 ack
*/
hasPendingReplyAck(frame: WsFrameHeaders): boolean;
/**
* 非阻塞流式文本回复
*
* 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
* 避免流式中间帧排队积压导致延迟。
*
* 注意:finish=true 的最终帧不受此限制,始终保证发送(走正常队列)。
*
* @param frame - 收到的原始 WebSocket 帧
* @param streamId - 流式消息 ID
* @param content - 回复内容
* @param finish - 是否结束流式消息
* @param msgItem - 图文混排项(仅在 finish=true 时有效),用于在结束时附带图片内容
* @param feedback - 反馈信息(仅在首次回复时设置)
* @returns Promise<WsFrame> 正常发送时返回回执帧,跳过时返回 'skipped'
*/
replyStreamNonBlocking(frame: WsFrameHeaders, streamId: string, content: string, finish?: boolean, msgItem?: ReplyMsgItem[], feedback?: ReplyFeedback): Promise<WsFrame | 'skipped'>;
/**
* 获取当前连接状态

@@ -1144,2 +1187,11 @@ */

/**
* 检查指定 reqId 是否有待回执的消息(即上一条消息还未收到 ack)
*
* 用于流式场景:调用方可据此决定是否跳过当前帧,避免排队积压。
*
* @param reqId - 要检查的 req_id
* @returns true 表示有消息正在等待 ack
*/
hasPendingAck(reqId: string): boolean;
/**
* 获取当前连接状态

@@ -1179,49 +1231,2 @@ */

/**
* WeCom 加解密通用核心
* 独立于 Webhook、WebSocket、Agent 的具体协议形态,统一提供基于 AES-256-CBC
* 的加解密与 SHA1 签名计算能力。
*/
/**
* 解码企业微信提供的 Base64 encodingAESKey
*/
declare function decodeEncodingAESKey(encodingAESKey: string): Buffer;
/**
* PKCS#7 填充
*/
declare function pkcs7Pad(buf: Buffer, blockSize: number): Buffer;
/**
* PKCS#7 解除填充
*/
declare function pkcs7Unpad(buf: Buffer, blockSize: number): Buffer;
declare class WecomCrypto {
private token;
private encodingAESKey;
private receiveId?;
private aesKey;
private iv;
constructor(token: string, encodingAESKey: string, receiveId?: string | undefined);
/**
* 计算 WeCom 消息签名
*/
computeSignature(timestamp: string, nonce: string, encrypt: string): string;
/**
* 验证 WeCom 消息签名
*/
verifySignature(signature: string, timestamp: string, nonce: string, encrypt: string): boolean;
/**
* 消息解密
* 返回纯文本字符串(XML 或 JSON 根据上层业务而定)
*/
decrypt(encryptText: string): string;
/**
* 消息加密
* 加密明文并返回 base64 格式密文与对应的新签名
*/
encrypt(plainText: string, timestamp: string, nonce: string): {
encrypt: string;
signature: string;
};
}
/**
* 加解密工具模块

@@ -1278,3 +1283,3 @@ * 提供文件加解密相关的功能函数

export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WecomCrypto, WsCmd, WsConnectionManager, decodeEncodingAESKey, decryptFile, AiBot as default, generateRandomString, generateReqId, pkcs7Pad, pkcs7Unpad };
export type { BaseMessage, EnterChatEvent, EventContent, EventFrom, EventMessage, EventMessageWith, FeedbackEventData, FileContent, FileMessage, ImageContent, ImageMessage, Logger, MessageFrom, MixedContent, MixedMessage, MixedMsgItem, QuoteContent, ReplyFeedback, ReplyMsgItem, ReplyOptions, SendMarkdownMsgBody, SendMarkdownParams, SendMediaMsgBody, SendMsgBody, SendTemplateCardMsgBody, SendTextParams, StreamReplyBody, StreamWithTemplateCardReplyBody, TemplateCard, TemplateCardAction, TemplateCardActionMenu, TemplateCardButton, TemplateCardCheckbox, TemplateCardEmphasisContent, TemplateCardEventData, TemplateCardHorizontalContent, TemplateCardImage, TemplateCardImageTextArea, TemplateCardJumpAction, TemplateCardMainTitle, TemplateCardQuoteArea, TemplateCardReplyBody, TemplateCardSelectionItem, TemplateCardSource, TemplateCardSubmitButton, TemplateCardVerticalContent, TextContent, TextMessage, UpdateTemplateCardBody, UploadMediaChunkBody, UploadMediaFinishBody, UploadMediaFinishResult, UploadMediaInitBody, UploadMediaInitResult, UploadMediaOptions, VoiceContent, VoiceMessage, WSClientEventMap, WSClientOptions, WeComMediaType, WelcomeReplyBody, WelcomeTemplateCardReplyBody, WelcomeTextReplyBody, WsFrame, WsFrameHeaders };
export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WsCmd, WsConnectionManager, decryptFile, AiBot as default, generateRandomString, generateReqId };
export type { BaseMessage, EnterChatEvent, EventContent, EventFrom, EventMessage, EventMessageWith, FeedbackEventData, FileContent, FileMessage, ImageContent, ImageMessage, Logger, MessageFrom, MixedContent, MixedMessage, MixedMsgItem, QuoteContent, ReplyFeedback, ReplyMsgItem, ReplyOptions, SendMarkdownMsgBody, SendMarkdownParams, SendMediaMsgBody, SendMsgBody, SendTemplateCardMsgBody, SendTextParams, StreamReplyBody, StreamWithTemplateCardReplyBody, TemplateCard, TemplateCardAction, TemplateCardActionMenu, TemplateCardButton, TemplateCardCheckbox, TemplateCardEmphasisContent, TemplateCardEventData, TemplateCardHorizontalContent, TemplateCardImage, TemplateCardImageTextArea, TemplateCardJumpAction, TemplateCardMainTitle, TemplateCardQuoteArea, TemplateCardReplyBody, TemplateCardSelectionItem, TemplateCardSource, TemplateCardSubmitButton, TemplateCardVerticalContent, TextContent, TextMessage, UpdateTemplateCardBody, UploadMediaChunkBody, UploadMediaFinishBody, UploadMediaFinishResult, UploadMediaInitBody, UploadMediaInitResult, UploadMediaOptions, VideoContent, VideoMessage, VoiceContent, VoiceMessage, WSClientEventMap, WSClientOptions, WeComMediaType, WelcomeReplyBody, WelcomeTemplateCardReplyBody, WelcomeTextReplyBody, WsFrame, WsFrameHeaders };

@@ -5,3 +5,2 @@ import { EventEmitter } from 'eventemitter3';

import WebSocket from 'ws';
import crypto$1 from 'node:crypto';

@@ -55,2 +54,4 @@ /**

MessageType["File"] = "file";
/** 视频消息 */
MessageType["Video"] = "video";
})(MessageType || (MessageType = {}));

@@ -720,2 +721,13 @@

/**
* 检查指定 reqId 是否有待回执的消息(即上一条消息还未收到 ack)
*
* 用于流式场景:调用方可据此决定是否跳过当前帧,避免排队积压。
*
* @param reqId - 要检查的 req_id
* @returns true 表示有消息正在等待 ack
*/
hasPendingAck(reqId) {
return this.pendingAcks.has(reqId);
}
/**
* 获取当前连接状态

@@ -789,2 +801,5 @@ */

break;
case MessageType.Video:
emitter.emit('message.video', frame);
break;
default:

@@ -1338,153 +1353,48 @@ this.logger.debug(`Received unhandled message type: ${body.msgtype}`);

/**
* 获取当前连接状态
* 检查指定消息帧是否有未完成的 ack(即上一条消息还未收到回执)
*
* 用于流式场景:调用方可据此决定是否跳过当前中间帧,避免排队积压。
*
* @param frame - 收到的原始 WebSocket 帧
* @returns true 表示有消息正在等待 ack
*/
get isConnected() {
return this.wsManager.isConnected;
hasPendingReplyAck(frame) {
const reqId = frame.headers?.req_id || '';
return this.wsManager.hasPendingAck(reqId);
}
/**
* 获取 API 客户端实例(供高级用途使用,如文件下载)
* 非阻塞流式文本回复
*
* 如果上一条同 reqId 的消息尚未收到 ack,则跳过本次发送(返回 'skipped'),
* 避免流式中间帧排队积压导致延迟。
*
* 注意:finish=true 的最终帧不受此限制,始终保证发送(走正常队列)。
*
* @param frame - 收到的原始 WebSocket 帧
* @param streamId - 流式消息 ID
* @param content - 回复内容
* @param finish - 是否结束流式消息
* @param msgItem - 图文混排项(仅在 finish=true 时有效),用于在结束时附带图片内容
* @param feedback - 反馈信息(仅在首次回复时设置)
* @returns Promise<WsFrame> 正常发送时返回回执帧,跳过时返回 'skipped'
*/
get api() {
return this.apiClient;
}
}
/**
* WeCom 加解密通用核心
* 独立于 Webhook、WebSocket、Agent 的具体协议形态,统一提供基于 AES-256-CBC
* 的加解密与 SHA1 签名计算能力。
*/
const CRYPTO_CONSTANTS = {
/** PKCS#7 块大小 */
PKCS7_BLOCK_SIZE: 32,
/** AES Key 长度 */
AES_KEY_LENGTH: 32,
};
/**
* 解码企业微信提供的 Base64 encodingAESKey
*/
function decodeEncodingAESKey(encodingAESKey) {
const trimmed = encodingAESKey.trim();
if (!trimmed)
throw new Error("encodingAESKey missing");
const withPadding = trimmed.endsWith("=") ? trimmed : `${trimmed}=`;
const key = Buffer.from(withPadding, "base64");
if (key.length !== CRYPTO_CONSTANTS.AES_KEY_LENGTH) {
throw new Error(`invalid encodingAESKey (expected ${CRYPTO_CONSTANTS.AES_KEY_LENGTH} bytes, got ${key.length})`);
}
return key;
}
/**
* PKCS#7 填充
*/
function pkcs7Pad(buf, blockSize) {
const mod = buf.length % blockSize;
const pad = mod === 0 ? blockSize : blockSize - mod;
const padByte = Buffer.alloc(1, pad);
return Buffer.concat([buf, Buffer.alloc(pad, padByte[0])]);
}
/**
* PKCS#7 解除填充
*/
function pkcs7Unpad(buf, blockSize) {
if (buf.length === 0)
throw new Error("invalid pkcs7 payload");
const pad = buf[buf.length - 1];
if (pad < 1 || pad > blockSize) {
throw new Error("invalid pkcs7 padding value");
}
if (pad > buf.length) {
throw new Error("invalid pkcs7 payload length");
}
for (let i = 0; i < pad; i += 1) {
if (buf[buf.length - 1 - i] !== pad) {
throw new Error("invalid pkcs7 padding byte");
replyStreamNonBlocking(frame, streamId, content, finish = false, msgItem, feedback) {
// finish=true 的最终帧必须发送,不做跳过判断
if (!finish && this.hasPendingReplyAck(frame)) {
return Promise.resolve('skipped');
}
return this.replyStream(frame, streamId, content, finish, msgItem, feedback);
}
return buf.subarray(0, buf.length - pad);
}
/**
* 计算 SHA1 哈希
*/
function sha1Hex(input) {
return crypto$1.createHash("sha1").update(input).digest("hex");
}
class WecomCrypto {
constructor(token, encodingAESKey, receiveId // 对应企业微信的 corpId 或 botId (用于校验与追加)
) {
this.token = token;
this.encodingAESKey = encodingAESKey;
this.receiveId = receiveId;
if (!token)
throw new Error("token is required");
this.aesKey = decodeEncodingAESKey(encodingAESKey);
this.iv = this.aesKey.subarray(0, 16);
}
/**
* 计算 WeCom 消息签名
* 获取当前连接状态
*/
computeSignature(timestamp, nonce, encrypt) {
const parts = [this.token, timestamp, nonce, encrypt]
.map((v) => String(v ?? ""))
.sort();
return sha1Hex(parts.join(""));
get isConnected() {
return this.wsManager.isConnected;
}
/**
* 验证 WeCom 消息签名
* 获取 API 客户端实例(供高级用途使用,如文件下载)
*/
verifySignature(signature, timestamp, nonce, encrypt) {
const expected = this.computeSignature(timestamp, nonce, encrypt);
return expected === signature;
get api() {
return this.apiClient;
}
/**
* 消息解密
* 返回纯文本字符串(XML 或 JSON 根据上层业务而定)
*/
decrypt(encryptText) {
const decipher = crypto$1.createDecipheriv("aes-256-cbc", this.aesKey, this.iv);
decipher.setAutoPadding(false);
const decryptedPadded = Buffer.concat([
decipher.update(Buffer.from(encryptText, "base64")),
decipher.final(),
]);
const decrypted = pkcs7Unpad(decryptedPadded, CRYPTO_CONSTANTS.PKCS7_BLOCK_SIZE);
if (decrypted.length < 20) {
throw new Error(`invalid payload (expected >=20 bytes, got ${decrypted.length})`);
}
// 16 bytes random + 4 bytes length + msg + receiveId
const msgLen = decrypted.readUInt32BE(16);
const msgStart = 20;
const msgEnd = msgStart + msgLen;
if (msgEnd > decrypted.length) {
throw new Error(`invalid msg length (msgEnd=${msgEnd}, total=${decrypted.length})`);
}
const msg = decrypted.subarray(msgStart, msgEnd).toString("utf8");
const receiveId = this.receiveId ?? "";
if (receiveId) {
const trailing = decrypted.subarray(msgEnd).toString("utf8");
if (trailing !== receiveId) {
throw new Error(`receiveId mismatch (expected "${receiveId}", got "${trailing}")`);
}
}
return msg;
}
/**
* 消息加密
* 加密明文并返回 base64 格式密文与对应的新签名
*/
encrypt(plainText, timestamp, nonce) {
const random16 = crypto$1.randomBytes(16);
const msgBuf = Buffer.from(plainText ?? "", "utf8");
const msgLen = Buffer.alloc(4);
msgLen.writeUInt32BE(msgBuf.length, 0);
const receiveIdBuf = Buffer.from(this.receiveId ?? "", "utf8");
const raw = Buffer.concat([random16, msgLen, msgBuf, receiveIdBuf]);
const padded = pkcs7Pad(raw, CRYPTO_CONSTANTS.PKCS7_BLOCK_SIZE);
const cipher = crypto$1.createCipheriv("aes-256-cbc", this.aesKey, this.iv);
cipher.setAutoPadding(false);
const encryptedBuf = Buffer.concat([cipher.update(padded), cipher.final()]);
const encryptBase64 = encryptedBuf.toString("base64");
const signature = this.computeSignature(timestamp, nonce, encryptBase64);
return { encrypt: encryptBase64, signature };
}
}

@@ -1497,3 +1407,3 @@

export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WecomCrypto, WsCmd, WsConnectionManager, decodeEncodingAESKey, decryptFile, AiBot as default, generateRandomString, generateReqId, pkcs7Pad, pkcs7Unpad };
export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WsCmd, WsConnectionManager, decryptFile, AiBot as default, generateRandomString, generateReqId };
//# sourceMappingURL=index.esm.js.map
/**
* 事件相关类型定义
*/
import type { BaseMessage, TextMessage, ImageMessage, MixedMessage, VoiceMessage, FileMessage } from './message';
import type { BaseMessage, TextMessage, ImageMessage, MixedMessage, VoiceMessage, FileMessage, VideoMessage } from './message';
import type { WsFrame } from './api';

@@ -87,2 +87,4 @@ /** 事件类型枚举 */

'message.file': (data: WsFrame<FileMessage>) => void;
/** 收到视频消息,body 为 VideoMessage */
'message.video': (data: WsFrame<VideoMessage>) => void;
/** 收到事件回调(所有事件类型),body 为 EventMessage */

@@ -89,0 +91,0 @@ event: (data: WsFrame<EventMessage>) => void;

@@ -7,5 +7,5 @@ /**

export type { WSClientOptions } from './config';
export { MessageType, type BaseMessage, type TextMessage, type ImageMessage, type MixedMessage, type VoiceMessage, type FileMessage, type MessageFrom, type TextContent, type ImageContent, type MixedContent, type MixedMsgItem, type VoiceContent, type FileContent, type QuoteContent, type ReplyOptions, type SendTextParams, type SendMarkdownParams, } from './message';
export { MessageType, type BaseMessage, type TextMessage, type ImageMessage, type MixedMessage, type VoiceMessage, type FileMessage, type VideoMessage, type MessageFrom, type TextContent, type ImageContent, type MixedContent, type MixedMsgItem, type VoiceContent, type VideoContent, type FileContent, type QuoteContent, type ReplyOptions, type SendTextParams, type SendMarkdownParams, } from './message';
export { WsCmd, TemplateCardType } from './api';
export type { WsFrame, WsFrameHeaders, StreamReplyBody, ReplyMsgItem, ReplyFeedback, WelcomeTextReplyBody, WelcomeTemplateCardReplyBody, WelcomeReplyBody, TemplateCardMainTitle, TemplateCardButton, TemplateCardSource, TemplateCardActionMenu, TemplateCardEmphasisContent, TemplateCardQuoteArea, TemplateCardHorizontalContent, TemplateCardJumpAction, TemplateCardAction, TemplateCardVerticalContent, TemplateCardImage, TemplateCardImageTextArea, TemplateCardSubmitButton, TemplateCardSelectionItem, TemplateCardCheckbox, TemplateCard, TemplateCardReplyBody, StreamWithTemplateCardReplyBody, UpdateTemplateCardBody, SendMarkdownMsgBody, SendTemplateCardMsgBody, SendMsgBody, SendMediaMsgBody, WeComMediaType, UploadMediaOptions, UploadMediaFinishResult, UploadMediaInitBody, UploadMediaInitResult, UploadMediaChunkBody, UploadMediaFinishBody, } from './api';
export { EventType, type EventFrom, type EnterChatEvent, type TemplateCardEventData, type FeedbackEventData, type DisconnectedEventData, type EventContent, type EventMessage, type EventMessageWith, type WSClientEventMap, } from './event';

@@ -16,3 +16,5 @@ /**

/** 文件消息 */
File = "file"
File = "file",
/** 视频消息 */
Video = "video"
}

@@ -48,2 +50,9 @@ /** 消息发送者信息 */

}
/** 视频结构体 */
export interface VideoContent {
/** 视频的下载 url(五分钟内有效,已加密) */
url: string;
/** 解密密钥,长连接模式下返回,每个下载链接的 aeskey 唯一 */
aeskey?: string;
}
/** 图文混排子项 */

@@ -131,2 +140,8 @@ export interface MixedMsgItem {

}
/** 视频消息 */
export interface VideoMessage extends BaseMessage {
msgtype: MessageType.Video;
/** 视频内容 */
video: VideoContent;
}
/** 回复消息选项 */

@@ -133,0 +148,0 @@ export interface ReplyOptions {

@@ -149,2 +149,11 @@ import { type ClientOptions as WsClientOptions } from 'ws';

/**
* 检查指定 reqId 是否有待回执的消息(即上一条消息还未收到 ack)
*
* 用于流式场景:调用方可据此决定是否跳过当前帧,避免排队积压。
*
* @param reqId - 要检查的 req_id
* @returns true 表示有消息正在等待 ack
*/
hasPendingAck(reqId: string): boolean;
/**
* 获取当前连接状态

@@ -151,0 +160,0 @@ */

{
"name": "@wecom/aibot-node-sdk",
"version": "1.0.5-beta.1",
"version": "1.0.5",
"description": "企业微信智能机器人 Node.js SDK - WebSocket 长连接通道",

@@ -18,4 +18,3 @@ "main": "dist/index.cjs.js",

"release:dry": "node scripts/publish-all.mjs --dry-run",
"example": "ts-node examples/basic.ts",
"test": "vitest run"
"example": "ts-node examples/basic.ts"
},

@@ -40,6 +39,6 @@ "keywords": [

"dependencies": {
"ws": "^8.16.0",
"axios": "^1.6.7",
"eventemitter3": "^5.0.1",
"ws": "^8.16.0"
"eventemitter3": "^5.0.1"
}
}
+100
-79

@@ -7,3 +7,3 @@ # @wecom/aibot-node-sdk

- 🔗 **WebSocket 长连接** — 基于 `wss://openws.work.weixin.qq.com` 内置默认地址,开箱即用
- 🔗 **WebSocket 长连接** — 基于 `wss://openws.work.weixin.qq.com` 内置默认地址,开箱即用【注: 私有部署企业需要在企业管理端查看该长连接地址】
- 🔐 **自动认证** — 连接建立后自动发送认证帧(botId + secret)

@@ -99,24 +99,24 @@ - 💓 **心跳保活** — 自动维护心跳,连续未收到 ack 时自动判定连接异常

| 方法 | 说明 | 返回值 |
| --- | --- | --- |
| `connect()` | 建立 WebSocket 连接,连接后自动认证 | `this`(支持链式调用) |
| `disconnect()` | 主动断开连接 | `void` |
| `reply(frame, body, cmd?)` | 通过 WebSocket 通道发送回复消息(通用方法) | `Promise<WsFrame>` |
| `replyStream(frame, streamId, content, finish?, msgItem?, feedback?)` | 发送流式文本回复(支持 Markdown) | `Promise<WsFrame>` |
| `replyWelcome(frame, body)` | 发送欢迎语回复(文本或模板卡片),需 5s 内调用 | `Promise<WsFrame>` |
| `replyTemplateCard(frame, templateCard, feedback?)` | 回复模板卡片消息 | `Promise<WsFrame>` |
| `replyStreamWithCard(frame, streamId, content, finish?, options?)` | 流式消息 + 模板卡片组合回复 | `Promise<WsFrame>` |
| `updateTemplateCard(frame, templateCard, userids?)` | 更新模板卡片(响应 template_card_event),需 5s 内调用 | `Promise<WsFrame>` |
| `sendMessage(chatid, body)` | 主动发送消息(Markdown / 模板卡片 / 媒体),无需回调帧 | `Promise<WsFrame>` |
| `uploadMedia(fileBuffer, options)` | 上传临时素材(三步分片上传),返回 `media_id` | `Promise<UploadMediaFinishResult>` |
| `replyMedia(frame, mediaType, mediaId, videoOptions?)` | 被动回复媒体消息(file/image/voice/video) | `Promise<WsFrame>` |
| `sendMediaMessage(chatid, mediaType, mediaId, videoOptions?)` | 主动发送媒体消息 | `Promise<WsFrame>` |
| `downloadFile(url, aesKey)` | 下载文件并 AES 解密,返回 Buffer 及文件名 | `Promise<{ buffer: Buffer; filename?: string }>` |
| 方法 | 说明 | 返回值 |
| --------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------ |
| `connect()` | 建立 WebSocket 连接,连接后自动认证 | `this`(支持链式调用) |
| `disconnect()` | 主动断开连接 | `void` |
| `reply(frame, body, cmd?)` | 通过 WebSocket 通道发送回复消息(通用方法) | `Promise<WsFrame>` |
| `replyStream(frame, streamId, content, finish?, msgItem?, feedback?)` | 发送流式文本回复(支持 Markdown) | `Promise<WsFrame>` |
| `replyWelcome(frame, body)` | 发送欢迎语回复(文本或模板卡片),需 5s 内调用 | `Promise<WsFrame>` |
| `replyTemplateCard(frame, templateCard, feedback?)` | 回复模板卡片消息 | `Promise<WsFrame>` |
| `replyStreamWithCard(frame, streamId, content, finish?, options?)` | 流式消息 + 模板卡片组合回复 | `Promise<WsFrame>` |
| `updateTemplateCard(frame, templateCard, userids?)` | 更新模板卡片(响应 template_card_event),需 5s 内调用 | `Promise<WsFrame>` |
| `sendMessage(chatid, body)` | 主动发送消息(Markdown / 模板卡片 / 媒体),无需回调帧 | `Promise<WsFrame>` |
| `uploadMedia(fileBuffer, options)` | 上传临时素材(三步分片上传),返回 `media_id` | `Promise<UploadMediaFinishResult>` |
| `replyMedia(frame, mediaType, mediaId, videoOptions?)` | 被动回复媒体消息(file/image/voice/video) | `Promise<WsFrame>` |
| `sendMediaMessage(chatid, mediaType, mediaId, videoOptions?)` | 主动发送媒体消息 | `Promise<WsFrame>` |
| `downloadFile(url, aesKey)` | 下载文件并 AES 解密,返回 Buffer 及文件名 | `Promise<{ buffer: Buffer; filename?: string }>` |
#### 属性
| 属性 | 说明 | 类型 |
| --- | --- | --- |
| `isConnected` | 当前 WebSocket 连接状态 | `boolean` |
| `api` | 内部 API 客户端实例(高级用途) | `WeComApiClient` |
| 属性 | 说明 | 类型 |
| ------------- | ------------------------------- | ---------------- |
| `isConnected` | 当前 WebSocket 连接状态 | `boolean` |
| `api` | 内部 API 客户端实例(高级用途) | `WeComApiClient` |

@@ -367,2 +367,3 @@ ---

| `wsUrl` | `string` | — | `wss://openws.work.weixin.qq.com` | 自定义 WebSocket 连接地址 |
| `wsOptions` | `string` | — | `string` | 自签证书地址 |
| `logger` | `Logger` | — | `DefaultLogger` | 自定义日志实例 |

@@ -376,19 +377,20 @@

| 事件 | 回调参数 | 说明 |
| --- | --- | --- |
| `connected` | — | WebSocket 连接建立 |
| `authenticated` | — | 认证成功 |
| `disconnected` | `reason: string` | 连接断开 |
| `reconnecting` | `attempt: number` | 正在重连(第 N 次) |
| `error` | `error: Error` | 发生错误 |
| `message` | `frame: WsFrame<BaseMessage>` | 收到消息(所有类型) |
| `message.text` | `frame: WsFrame<TextMessage>` | 收到文本消息 |
| `message.image` | `frame: WsFrame<ImageMessage>` | 收到图片消息 |
| `message.mixed` | `frame: WsFrame<MixedMessage>` | 收到图文混排消息 |
| `message.voice` | `frame: WsFrame<VoiceMessage>` | 收到语音消息 |
| `message.file` | `frame: WsFrame<FileMessage>` | 收到文件消息 |
| `event` | `frame: WsFrame<EventMessage>` | 收到事件回调(所有事件类型) |
| `event.enter_chat` | `frame: WsFrame<EventMessage>` | 收到进入会话事件(用户当天首次进入单聊会话) |
| `event.template_card_event` | `frame: WsFrame<EventMessage>` | 收到模板卡片事件(用户点击卡片按钮) |
| `event.feedback_event` | `frame: WsFrame<EventMessage>` | 收到用户反馈事件 |
| 事件 | 回调参数 | 说明 |
| --------------------------- | ------------------------------ | -------------------------------------------- |
| `connected` | — | WebSocket 连接建立 |
| `authenticated` | — | 认证成功 |
| `disconnected` | `reason: string` | 连接断开 |
| `reconnecting` | `attempt: number` | 正在重连(第 N 次) |
| `error` | `error: Error` | 发生错误 |
| `message` | `frame: WsFrame<BaseMessage>` | 收到消息(所有类型) |
| `message.text` | `frame: WsFrame<TextMessage>` | 收到文本消息 |
| `message.image` | `frame: WsFrame<ImageMessage>` | 收到图片消息 |
| `message.mixed` | `frame: WsFrame<MixedMessage>` | 收到图文混排消息 |
| `message.voice` | `frame: WsFrame<VoiceMessage>` | 收到语音消息 |
| `message.file` | `frame: WsFrame<FileMessage>` | 收到文件消息 |
| `message.video` | `frame: WsFrame<VideoMessage>` | 收到视频消息 |
| `event` | `frame: WsFrame<EventMessage>` | 收到事件回调(所有事件类型) |
| `event.enter_chat` | `frame: WsFrame<EventMessage>` | 收到进入会话事件(用户当天首次进入单聊会话) |
| `event.template_card_event` | `frame: WsFrame<EventMessage>` | 收到模板卡片事件(用户点击卡片按钮) |
| `event.feedback_event` | `frame: WsFrame<EventMessage>` | 收到用户反馈事件 |

@@ -401,26 +403,27 @@ ---

| 类型 | 值 | 说明 |
| --- | --- | --- |
| `Text` | `'text'` | 文本消息 |
| 类型 | 值 | 说明 |
| ------- | --------- | -------------------------------------------------------- |
| `Text` | `'text'` | 文本消息 |
| `Image` | `'image'` | 图片消息(URL 已加密,使用消息中的 `image.aeskey` 解密) |
| `Mixed` | `'mixed'` | 图文混排消息(包含 text / image 子项) |
| `Voice` | `'voice'` | 语音消息(已转文本) |
| `File` | `'file'` | 文件消息(URL 已加密,使用消息中的 `file.aeskey` 解密) |
| `Mixed` | `'mixed'` | 图文混排消息(包含 text / image 子项) |
| `Voice` | `'voice'` | 语音消息(已转文本) |
| `File` | `'file'` | 文件消息(URL 已加密,使用消息中的 `file.aeskey` 解密) |
| `Video` | `'video'` | 视频消息(URL 已加密,使用消息中的 `video.aeskey` 解密) |
SDK 支持以下事件类型(`EventType` 枚举):
| 类型 | 值 | 说明 |
| --- | --- | --- |
| `EnterChat` | `'enter_chat'` | 进入会话事件:用户当天首次进入机器人单聊会话 |
| `TemplateCardEvent` | `'template_card_event'` | 模板卡片事件:用户点击模板卡片按钮 |
| `FeedbackEvent` | `'feedback_event'` | 用户反馈事件:用户对机器人回复进行反馈 |
| 类型 | 值 | 说明 |
| ------------------- | ----------------------- | -------------------------------------------- |
| `EnterChat` | `'enter_chat'` | 进入会话事件:用户当天首次进入机器人单聊会话 |
| `TemplateCardEvent` | `'template_card_event'` | 模板卡片事件:用户点击模板卡片按钮 |
| `FeedbackEvent` | `'feedback_event'` | 用户反馈事件:用户对机器人回复进行反馈 |
SDK 支持以下媒体类型(`WeComMediaType` 类型):
| 类型 | 值 | 说明 |
| --- | --- | --- |
| — | `'file'` | 文件 |
| — | `'image'` | 图片 |
| — | `'voice'` | 语音 |
| — | `'video'` | 视频 |
| 类型 | 值 | 说明 |
| ---- | --------- | ---- |
| — | `'file'` | 文件 |
| — | `'image'` | 图片 |
| — | `'voice'` | 语音 |
| — | `'video'` | 视频 |

@@ -433,8 +436,8 @@ ---

| 类型 | 值 | 说明 |
| --- | --- | --- |
| `TextNotice` | `'text_notice'` | 文本通知模版卡片 |
| `NewsNotice` | `'news_notice'` | 图文展示模版卡片 |
| `ButtonInteraction` | `'button_interaction'` | 按钮交互模版卡片 |
| `VoteInteraction` | `'vote_interaction'` | 投票选择模版卡片 |
| 类型 | 值 | 说明 |
| --------------------- | ------------------------ | ---------------- |
| `TextNotice` | `'text_notice'` | 文本通知模版卡片 |
| `NewsNotice` | `'news_notice'` | 图文展示模版卡片 |
| `ButtonInteraction` | `'button_interaction'` | 按钮交互模版卡片 |
| `VoteInteraction` | `'vote_interaction'` | 投票选择模版卡片 |
| `MultipleInteraction` | `'multiple_interaction'` | 多项选择模版卡片 |

@@ -522,2 +525,20 @@

私有部署企业使用示例:
```ts
const wsClient = new AiBot.WSClient({
botId: 'your-bot-id',
secret: 'your-bot-secret',
wsUrl: 'your-wsUrl',
wsOptions: {
ca: fs.readFileSync('your-ca-path'),
},
logger: {
debug: () => {}, // 静默 debug 日志
info: console.log,
warn: console.warn,
error: console.error,
},
});
```
---

@@ -529,15 +550,15 @@

| 方向 | 常量 | 值 | 说明 |
| --- | --- | --- | --- |
| 开发者 → 企微 | `SUBSCRIBE` | `aibot_subscribe` | 认证订阅 |
| 开发者 → 企微 | `HEARTBEAT` | `ping` | 心跳 |
| 开发者 → 企微 | `RESPONSE` | `aibot_respond_msg` | 回复消息 |
| 开发者 → 企微 | `RESPONSE_WELCOME` | `aibot_respond_welcome_msg` | 回复欢迎语 |
| 开发者 → 企微 | `RESPONSE_UPDATE` | `aibot_respond_update_msg` | 更新模板卡片 |
| 开发者 → 企微 | `SEND_MSG` | `aibot_send_msg` | 主动发送消息 |
| 开发者 → 企微 | `UPLOAD_MEDIA_INIT` | `aibot_upload_media_init` | 上传素材 - 初始化 |
| 开发者 → 企微 | `UPLOAD_MEDIA_CHUNK` | `aibot_upload_media_chunk` | 上传素材 - 分片 |
| 开发者 → 企微 | `UPLOAD_MEDIA_FINISH` | `aibot_upload_media_finish` | 上传素材 - 完成 |
| 企微 → 开发者 | `CALLBACK` | `aibot_msg_callback` | 消息推送回调 |
| 企微 → 开发者 | `EVENT_CALLBACK` | `aibot_event_callback` | 事件推送回调 |
| 方向 | 常量 | 值 | 说明 |
| ------------- | --------------------- | --------------------------- | ----------------- |
| 开发者 → 企微 | `SUBSCRIBE` | `aibot_subscribe` | 认证订阅 |
| 开发者 → 企微 | `HEARTBEAT` | `ping` | 心跳 |
| 开发者 → 企微 | `RESPONSE` | `aibot_respond_msg` | 回复消息 |
| 开发者 → 企微 | `RESPONSE_WELCOME` | `aibot_respond_welcome_msg` | 回复欢迎语 |
| 开发者 → 企微 | `RESPONSE_UPDATE` | `aibot_respond_update_msg` | 更新模板卡片 |
| 开发者 → 企微 | `SEND_MSG` | `aibot_send_msg` | 主动发送消息 |
| 开发者 → 企微 | `UPLOAD_MEDIA_INIT` | `aibot_upload_media_init` | 上传素材 - 初始化 |
| 开发者 → 企微 | `UPLOAD_MEDIA_CHUNK` | `aibot_upload_media_chunk` | 上传素材 - 分片 |
| 开发者 → 企微 | `UPLOAD_MEDIA_FINISH` | `aibot_upload_media_finish` | 上传素材 - 完成 |
| 企微 → 开发者 | `CALLBACK` | `aibot_msg_callback` | 消息推送回调 |
| 企微 → 开发者 | `EVENT_CALLBACK` | `aibot_event_callback` | 事件推送回调 |

@@ -762,8 +783,8 @@ ---

| 类别 | 导出项 |
| --- | --- |
| **类** | `WSClient`、`WeComApiClient`、`WsConnectionManager`、`MessageHandler`、`DefaultLogger` |
| **函数** | `generateReqId`、`generateRandomString`、`decryptFile` |
| **枚举** | `MessageType`、`EventType`、`TemplateCardType`、`WsCmd` |
| **类型** | `WSClientOptions`、`WSClientEventMap`、`WsFrame`、`WsFrameHeaders`、`BaseMessage`、`TextMessage`、`ImageMessage`、`MixedMessage`、`VoiceMessage`、`FileMessage`、`EventMessage`、`TemplateCard`、`StreamReplyBody`、`ReplyMsgItem`、`ReplyFeedback`、`Logger` 等 |
| 类别 | 导出项 |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **类** | `WSClient`、`WeComApiClient`、`WsConnectionManager`、`MessageHandler`、`DefaultLogger` |
| **函数** | `generateReqId`、`generateRandomString`、`decryptFile` |
| **枚举** | `MessageType`、`EventType`、`TemplateCardType`、`WsCmd` |
| **类型** | `WSClientOptions`、`WSClientEventMap`、`WsFrame`、`WsFrameHeaders`、`BaseMessage`、`TextMessage`、`ImageMessage`、`MixedMessage`、`VoiceMessage`、`FileMessage`、`VideoMessage`、`EventMessage`、`TemplateCard`、`StreamReplyBody`、`ReplyMsgItem`、`ReplyFeedback`、`Logger` 等 |

@@ -770,0 +791,0 @@ ---

/**
* WeCom 加解密通用核心
* 独立于 Webhook、WebSocket、Agent 的具体协议形态,统一提供基于 AES-256-CBC
* 的加解密与 SHA1 签名计算能力。
*/
/**
* 解码企业微信提供的 Base64 encodingAESKey
*/
export declare function decodeEncodingAESKey(encodingAESKey: string): Buffer;
/**
* PKCS#7 填充
*/
export declare function pkcs7Pad(buf: Buffer, blockSize: number): Buffer;
/**
* PKCS#7 解除填充
*/
export declare function pkcs7Unpad(buf: Buffer, blockSize: number): Buffer;
export declare class WecomCrypto {
private token;
private encodingAESKey;
private receiveId?;
private aesKey;
private iv;
constructor(token: string, encodingAESKey: string, receiveId?: string | undefined);
/**
* 计算 WeCom 消息签名
*/
computeSignature(timestamp: string, nonce: string, encrypt: string): string;
/**
* 验证 WeCom 消息签名
*/
verifySignature(signature: string, timestamp: string, nonce: string, encrypt: string): boolean;
/**
* 消息解密
* 返回纯文本字符串(XML 或 JSON 根据上层业务而定)
*/
decrypt(encryptText: string): string;
/**
* 消息加密
* 加密明文并返回 base64 格式密文与对应的新签名
*/
encrypt(plainText: string, timestamp: string, nonce: string): {
encrypt: string;
signature: string;
};
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display