🎩 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
to
1.0.6-beta.0
+46
dist/wecom-crypto/index.d.ts
/**
* 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;
};
}
+145
-0

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

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

@@ -1402,2 +1403,142 @@ /**

/**
* 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");
}
}
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(""));
}
/**
* 验证 WeCom 消息签名
*/
verifySignature(signature, timestamp, nonce, encrypt) {
const expected = this.computeSignature(timestamp, nonce, encrypt);
return expected === signature;
}
/**
* 消息解密
* 返回纯文本字符串(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 };
}
}
/** 默认导出 AiBot 命名空间 */

@@ -1414,4 +1555,6 @@ const AiBot = {

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

@@ -1421,2 +1564,4 @@ exports.default = AiBot;

exports.generateReqId = generateReqId;
exports.pkcs7Pad = pkcs7Pad;
exports.pkcs7Unpad = pkcs7Unpad;
//# sourceMappingURL=index.cjs.js.map
+48
-1

@@ -1225,2 +1225,49 @@ import { EventEmitter } from 'eventemitter3';

/**
* 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;
};
}
/**
* 加解密工具模块

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

export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WsCmd, WsConnectionManager, decryptFile, AiBot as default, generateRandomString, generateReqId };
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, VideoContent, VideoMessage, VoiceContent, VoiceMessage, WSClientEventMap, WSClientOptions, WeComMediaType, WelcomeReplyBody, WelcomeTemplateCardReplyBody, WelcomeTextReplyBody, WsFrame, WsFrameHeaders };

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

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

@@ -1398,2 +1399,142 @@ /**

/**
* 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");
}
}
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(""));
}
/**
* 验证 WeCom 消息签名
*/
verifySignature(signature, timestamp, nonce, encrypt) {
const expected = this.computeSignature(timestamp, nonce, encrypt);
return expected === signature;
}
/**
* 消息解密
* 返回纯文本字符串(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 };
}
}
/** 默认导出 AiBot 命名空间 */

@@ -1404,3 +1545,3 @@ const AiBot = {

export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WsCmd, WsConnectionManager, decryptFile, AiBot as default, generateRandomString, generateReqId };
export { DefaultLogger, EventType, MessageHandler, MessageType, TemplateCardType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, WeComApiClient, WecomCrypto, WsCmd, WsConnectionManager, decodeEncodingAESKey, decryptFile, AiBot as default, generateRandomString, generateReqId, pkcs7Pad, pkcs7Unpad };
//# sourceMappingURL=index.esm.js.map
+5
-4
{
"name": "@wecom/aibot-node-sdk",
"version": "1.0.5",
"version": "1.0.6-beta.0",
"description": "企业微信智能机器人 Node.js SDK - WebSocket 长连接通道",

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

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

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

"dependencies": {
"ws": "^8.16.0",
"axios": "^1.6.7",
"eventemitter3": "^5.0.1"
"eventemitter3": "^5.0.1",
"ws": "^8.16.0"
}
}

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

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