liangzimixin 0.3.45 → 0.3.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +209 -30
- package/dist/index.d.cts +33 -0
- package/dist/setup-entry.cjs +209 -30
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2233,7 +2233,7 @@ var require_websocket = __commonJS({
|
|
|
2233
2233
|
var http = require("http");
|
|
2234
2234
|
var net = require("net");
|
|
2235
2235
|
var tls = require("tls");
|
|
2236
|
-
var { randomBytes:
|
|
2236
|
+
var { randomBytes: randomBytes4, createHash: createHash3 } = require("crypto");
|
|
2237
2237
|
var { Duplex, Readable } = require("stream");
|
|
2238
2238
|
var { URL: URL2 } = require("url");
|
|
2239
2239
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2763,7 +2763,7 @@ var require_websocket = __commonJS({
|
|
|
2763
2763
|
}
|
|
2764
2764
|
}
|
|
2765
2765
|
const defaultPort = isSecure ? 443 : 80;
|
|
2766
|
-
const key =
|
|
2766
|
+
const key = randomBytes4(16).toString("base64");
|
|
2767
2767
|
const request = isSecure ? https.request : http.request;
|
|
2768
2768
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2769
2769
|
let perMessageDeflate;
|
|
@@ -2893,7 +2893,7 @@ var require_websocket = __commonJS({
|
|
|
2893
2893
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2894
2894
|
return;
|
|
2895
2895
|
}
|
|
2896
|
-
const digest =
|
|
2896
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
2897
2897
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2898
2898
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2899
2899
|
return;
|
|
@@ -3260,7 +3260,7 @@ var require_websocket_server = __commonJS({
|
|
|
3260
3260
|
var EventEmitter2 = require("events");
|
|
3261
3261
|
var http = require("http");
|
|
3262
3262
|
var { Duplex } = require("stream");
|
|
3263
|
-
var { createHash:
|
|
3263
|
+
var { createHash: createHash3 } = require("crypto");
|
|
3264
3264
|
var extension2 = require_extension();
|
|
3265
3265
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3266
3266
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3561,7 +3561,7 @@ var require_websocket_server = __commonJS({
|
|
|
3561
3561
|
);
|
|
3562
3562
|
}
|
|
3563
3563
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3564
|
-
const digest =
|
|
3564
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
3565
3565
|
const headers = [
|
|
3566
3566
|
"HTTP/1.1 101 Switching Protocols",
|
|
3567
3567
|
"Upgrade: websocket",
|
|
@@ -17836,8 +17836,8 @@ async function uploadMedia(params) {
|
|
|
17836
17836
|
const { readFile: readFile3 } = await import("fs/promises");
|
|
17837
17837
|
buffer = await readFile3(file2);
|
|
17838
17838
|
}
|
|
17839
|
-
const { createHash:
|
|
17840
|
-
const fileHash =
|
|
17839
|
+
const { createHash: createHash3 } = await import("crypto");
|
|
17840
|
+
const fileHash = createHash3("md5").update(buffer).digest("hex");
|
|
17841
17841
|
log2.debug("upload:fileHash", { fileName, fileHash });
|
|
17842
17842
|
const maxBytes = maxFileSizeMb * 1024 * 1024;
|
|
17843
17843
|
if (buffer.length > maxBytes) {
|
|
@@ -18017,6 +18017,65 @@ function inferMimeType(fileName) {
|
|
|
18017
18017
|
const ext = path.extname(fileName).toLowerCase();
|
|
18018
18018
|
return MIME_MAP[ext] || "application/octet-stream";
|
|
18019
18019
|
}
|
|
18020
|
+
function getImageDimensions(buffer) {
|
|
18021
|
+
try {
|
|
18022
|
+
if (buffer.length < 24) return void 0;
|
|
18023
|
+
if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71) {
|
|
18024
|
+
return {
|
|
18025
|
+
width: buffer.readUInt32BE(16),
|
|
18026
|
+
height: buffer.readUInt32BE(20)
|
|
18027
|
+
};
|
|
18028
|
+
}
|
|
18029
|
+
if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) {
|
|
18030
|
+
return {
|
|
18031
|
+
width: buffer.readUInt16LE(6),
|
|
18032
|
+
height: buffer.readUInt16LE(8)
|
|
18033
|
+
};
|
|
18034
|
+
}
|
|
18035
|
+
if (buffer[0] === 66 && buffer[1] === 77 && buffer.length >= 26) {
|
|
18036
|
+
return {
|
|
18037
|
+
width: buffer.readInt32LE(18),
|
|
18038
|
+
height: Math.abs(buffer.readInt32LE(22))
|
|
18039
|
+
};
|
|
18040
|
+
}
|
|
18041
|
+
if (buffer[0] === 82 && buffer[1] === 73 && buffer[8] === 87 && buffer[9] === 69) {
|
|
18042
|
+
if (buffer[12] === 86 && buffer[13] === 80 && buffer[14] === 56 && buffer[15] === 32) {
|
|
18043
|
+
if (buffer.length >= 30) {
|
|
18044
|
+
return {
|
|
18045
|
+
width: buffer.readUInt16LE(26) & 16383,
|
|
18046
|
+
height: buffer.readUInt16LE(28) & 16383
|
|
18047
|
+
};
|
|
18048
|
+
}
|
|
18049
|
+
}
|
|
18050
|
+
if (buffer[12] === 86 && buffer[13] === 80 && buffer[14] === 56 && buffer[15] === 76) {
|
|
18051
|
+
if (buffer.length >= 25) {
|
|
18052
|
+
const bits = buffer.readUInt32LE(21);
|
|
18053
|
+
return {
|
|
18054
|
+
width: (bits & 16383) + 1,
|
|
18055
|
+
height: (bits >> 14 & 16383) + 1
|
|
18056
|
+
};
|
|
18057
|
+
}
|
|
18058
|
+
}
|
|
18059
|
+
}
|
|
18060
|
+
if (buffer[0] === 255 && buffer[1] === 216) {
|
|
18061
|
+
let offset = 2;
|
|
18062
|
+
while (offset < buffer.length - 8) {
|
|
18063
|
+
if (buffer[offset] !== 255) break;
|
|
18064
|
+
const marker = buffer[offset + 1];
|
|
18065
|
+
if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) {
|
|
18066
|
+
return {
|
|
18067
|
+
height: buffer.readUInt16BE(offset + 5),
|
|
18068
|
+
width: buffer.readUInt16BE(offset + 7)
|
|
18069
|
+
};
|
|
18070
|
+
}
|
|
18071
|
+
const segLen = buffer.readUInt16BE(offset + 2);
|
|
18072
|
+
offset += 2 + segLen;
|
|
18073
|
+
}
|
|
18074
|
+
}
|
|
18075
|
+
} catch {
|
|
18076
|
+
}
|
|
18077
|
+
return void 0;
|
|
18078
|
+
}
|
|
18020
18079
|
async function resolveAndUploadMedia(params) {
|
|
18021
18080
|
const {
|
|
18022
18081
|
mediaUrl,
|
|
@@ -18067,7 +18126,26 @@ async function resolveAndUploadMedia(params) {
|
|
|
18067
18126
|
}
|
|
18068
18127
|
const fileType = detectFileType(fileName);
|
|
18069
18128
|
const mimeType = inferMimeType(fileName);
|
|
18070
|
-
|
|
18129
|
+
const ext = path.extname(fileName).replace(".", "").toLowerCase();
|
|
18130
|
+
let imageDimensions;
|
|
18131
|
+
if (fileType === "image") {
|
|
18132
|
+
imageDimensions = getImageDimensions(buffer);
|
|
18133
|
+
log3.info("media:imageDimensions", { fileName, ...imageDimensions });
|
|
18134
|
+
}
|
|
18135
|
+
log3.info("media:fileType", { fileName, fileType, mimeType, ext });
|
|
18136
|
+
let encryptionMeta;
|
|
18137
|
+
if (!params.skipEncrypt) {
|
|
18138
|
+
try {
|
|
18139
|
+
const originalSize = buffer.length;
|
|
18140
|
+
const result = await messagePipe.encryptFile(buffer);
|
|
18141
|
+
buffer = result.encryptedBuffer;
|
|
18142
|
+
encryptionMeta = { keyId: result.keyId, iv: result.iv };
|
|
18143
|
+
log3.info("media:encrypted", { fileName, originalSize, encryptedSize: buffer.length });
|
|
18144
|
+
} catch (err) {
|
|
18145
|
+
log3.error("media:encrypt failed", { fileName, error: err.message });
|
|
18146
|
+
throw err;
|
|
18147
|
+
}
|
|
18148
|
+
}
|
|
18071
18149
|
let uploadResult;
|
|
18072
18150
|
try {
|
|
18073
18151
|
uploadResult = await uploadMedia({
|
|
@@ -18092,11 +18170,17 @@ async function resolveAndUploadMedia(params) {
|
|
|
18092
18170
|
const msgType = fileType;
|
|
18093
18171
|
let contentPayload;
|
|
18094
18172
|
if (fileType === "file") {
|
|
18095
|
-
contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize,
|
|
18173
|
+
contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize, ext };
|
|
18096
18174
|
} else if (fileType === "image") {
|
|
18097
|
-
contentPayload = {
|
|
18175
|
+
contentPayload = {
|
|
18176
|
+
fileId: uploadResult.fileKey,
|
|
18177
|
+
width: imageDimensions?.width ?? 0,
|
|
18178
|
+
height: imageDimensions?.height ?? 0,
|
|
18179
|
+
altText: fileName,
|
|
18180
|
+
ext
|
|
18181
|
+
};
|
|
18098
18182
|
} else {
|
|
18099
|
-
contentPayload = { fileId: uploadResult.fileKey,
|
|
18183
|
+
contentPayload = { fileId: uploadResult.fileKey, ext };
|
|
18100
18184
|
}
|
|
18101
18185
|
await messagePipe.sendMessage({
|
|
18102
18186
|
chatId,
|
|
@@ -18104,12 +18188,15 @@ async function resolveAndUploadMedia(params) {
|
|
|
18104
18188
|
// 私聊场景: chatId 即为用户 ID
|
|
18105
18189
|
msgType,
|
|
18106
18190
|
content: JSON.stringify(contentPayload),
|
|
18107
|
-
skipEncrypt: params.skipEncrypt
|
|
18191
|
+
skipEncrypt: params.skipEncrypt,
|
|
18192
|
+
encryptionMeta
|
|
18193
|
+
// 文件加密的 keyId/iv → sendMessage 用于构建 extra
|
|
18108
18194
|
});
|
|
18109
18195
|
log3.info("media:sent", {
|
|
18110
18196
|
chatId,
|
|
18111
18197
|
fileId: uploadResult.fileKey,
|
|
18112
|
-
msgType
|
|
18198
|
+
msgType,
|
|
18199
|
+
encrypted: Boolean(encryptionMeta)
|
|
18113
18200
|
});
|
|
18114
18201
|
return {
|
|
18115
18202
|
channel: CHANNEL_ID,
|
|
@@ -20055,7 +20142,7 @@ var MessageDedup = class {
|
|
|
20055
20142
|
// src/transport/message-pipe.ts
|
|
20056
20143
|
var import_node_crypto3 = require("crypto");
|
|
20057
20144
|
var log21 = createLogger("transport/message-pipe");
|
|
20058
|
-
var MessagePipe = class {
|
|
20145
|
+
var MessagePipe = class _MessagePipe {
|
|
20059
20146
|
wsClient;
|
|
20060
20147
|
dedup;
|
|
20061
20148
|
crypto;
|
|
@@ -20097,6 +20184,74 @@ var MessagePipe = class {
|
|
|
20097
20184
|
async injectRawFrame(rawData) {
|
|
20098
20185
|
return this.handleInbound(rawData);
|
|
20099
20186
|
}
|
|
20187
|
+
/**
|
|
20188
|
+
* 加密文件 Buffer — 按 10MB 分片加密,返回加密后的 Buffer + 密钥信息。
|
|
20189
|
+
* 用于文件上传前的加密处理。
|
|
20190
|
+
*
|
|
20191
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
20192
|
+
*
|
|
20193
|
+
* @param buffer - 原始文件 Buffer
|
|
20194
|
+
* @returns { encryptedBuffer, keyId, iv } — 加密后的 buffer + 密钥标识 + 初始化向量
|
|
20195
|
+
*/
|
|
20196
|
+
async encryptFile(buffer) {
|
|
20197
|
+
const iv = (0, import_node_crypto3.randomBytes)(8).toString("hex");
|
|
20198
|
+
const chunkSize = 10 * 1024 * 1024;
|
|
20199
|
+
const totalChunks = Math.ceil(buffer.length / chunkSize);
|
|
20200
|
+
let keyId = "";
|
|
20201
|
+
let sessionKey = "";
|
|
20202
|
+
let fillKey = "";
|
|
20203
|
+
const encryptedChunks = [];
|
|
20204
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
20205
|
+
const start = i * chunkSize;
|
|
20206
|
+
const end = Math.min(start + chunkSize, buffer.length);
|
|
20207
|
+
const chunk = buffer.subarray(start, end);
|
|
20208
|
+
const options = keyId ? { keyId, sessionKey, fillKey } : void 0;
|
|
20209
|
+
const result = await this.crypto.encryptFileChunk(chunk, iv, options);
|
|
20210
|
+
encryptedChunks.push(result.fileBuffer);
|
|
20211
|
+
keyId = result.keyId;
|
|
20212
|
+
sessionKey = result.sessionKey;
|
|
20213
|
+
fillKey = result.fillKey;
|
|
20214
|
+
log21.debug("\u{1F512} \u6587\u4EF6\u5206\u7247\u52A0\u5BC6", {
|
|
20215
|
+
chunk: `${i + 1}/${totalChunks}`,
|
|
20216
|
+
originalSize: chunk.length,
|
|
20217
|
+
encryptedSize: result.fileBuffer.length,
|
|
20218
|
+
keyId
|
|
20219
|
+
});
|
|
20220
|
+
}
|
|
20221
|
+
const encryptedBuffer = Buffer.concat(encryptedChunks);
|
|
20222
|
+
log21.info("\u{1F512} \u6587\u4EF6\u52A0\u5BC6\u5B8C\u6210", {
|
|
20223
|
+
originalSize: buffer.length,
|
|
20224
|
+
encryptedSize: encryptedBuffer.length,
|
|
20225
|
+
totalChunks,
|
|
20226
|
+
keyId
|
|
20227
|
+
});
|
|
20228
|
+
return { encryptedBuffer, keyId, iv };
|
|
20229
|
+
}
|
|
20230
|
+
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
20231
|
+
static FILE_MSG_TYPES = /* @__PURE__ */ new Set(["image", "file", "voice", "video"]);
|
|
20232
|
+
/**
|
|
20233
|
+
* 构建文件类加密消息的 extra — 完整模板格式。
|
|
20234
|
+
* 文件消息不加密 content(需要保留 fileId 等元数据),只在 extra 中携带会话信息。
|
|
20235
|
+
*/
|
|
20236
|
+
static buildFileEncryptExtra(iv, keyId) {
|
|
20237
|
+
return JSON.stringify({
|
|
20238
|
+
containSensitive: false,
|
|
20239
|
+
cryptoIv: iv,
|
|
20240
|
+
decryptText: "",
|
|
20241
|
+
decrypted: false,
|
|
20242
|
+
destroyType: 0,
|
|
20243
|
+
encryptMsg: "",
|
|
20244
|
+
expireAt: null,
|
|
20245
|
+
level: 0,
|
|
20246
|
+
newDestroy: 1,
|
|
20247
|
+
phone: "",
|
|
20248
|
+
receiveShowTip: false,
|
|
20249
|
+
receiveTip: "",
|
|
20250
|
+
sessionId: keyId,
|
|
20251
|
+
showTip: false,
|
|
20252
|
+
tip: ""
|
|
20253
|
+
});
|
|
20254
|
+
}
|
|
20100
20255
|
/**
|
|
20101
20256
|
* 出站发送 — 通过 HTTP API 发送消息到 IM 服务器。
|
|
20102
20257
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
@@ -20105,22 +20260,28 @@ var MessagePipe = class {
|
|
|
20105
20260
|
* 加密行为取决于 encryptionMode:
|
|
20106
20261
|
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
20107
20262
|
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
20263
|
+
*
|
|
20264
|
+
* 文件类消息 (image/file/voice/video):
|
|
20265
|
+
* - content 保留原始文件元数据(fileId、fileName、size 等)
|
|
20266
|
+
* - extra 使用完整模板格式,encryptMsg 为空
|
|
20267
|
+
*
|
|
20268
|
+
* 文本类消息 (text/markdown):
|
|
20269
|
+
* - content 加密后置空,密文放入 extra.encryptMsg
|
|
20108
20270
|
*/
|
|
20109
20271
|
async sendMessage(msg) {
|
|
20110
20272
|
let content = msg.content;
|
|
20111
20273
|
let extra = "";
|
|
20274
|
+
const isFileMessage = _MessagePipe.FILE_MSG_TYPES.has(msg.msgType);
|
|
20112
20275
|
if (!msg.skipEncrypt && this.quantumAccount) {
|
|
20113
20276
|
if (this.encryptionMode === "quantum_only") {
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
} else {
|
|
20123
|
-
try {
|
|
20277
|
+
if (isFileMessage && msg.encryptionMeta) {
|
|
20278
|
+
extra = _MessagePipe.buildFileEncryptExtra(msg.encryptionMeta.iv, msg.encryptionMeta.keyId);
|
|
20279
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_only)", { sessionId: msg.encryptionMeta.keyId, msgType: msg.msgType });
|
|
20280
|
+
} else if (isFileMessage) {
|
|
20281
|
+
const { keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20282
|
+
extra = _MessagePipe.buildFileEncryptExtra(iv, keyId);
|
|
20283
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_only, fallback)", { sessionId: keyId, msgType: msg.msgType });
|
|
20284
|
+
} else {
|
|
20124
20285
|
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20125
20286
|
extra = JSON.stringify({
|
|
20126
20287
|
cryptoIv: iv,
|
|
@@ -20128,7 +20289,27 @@ var MessagePipe = class {
|
|
|
20128
20289
|
sessionId: keyId
|
|
20129
20290
|
});
|
|
20130
20291
|
content = JSON.stringify({ content: "" });
|
|
20131
|
-
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (
|
|
20292
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
|
|
20293
|
+
}
|
|
20294
|
+
} else {
|
|
20295
|
+
try {
|
|
20296
|
+
if (isFileMessage && msg.encryptionMeta) {
|
|
20297
|
+
extra = _MessagePipe.buildFileEncryptExtra(msg.encryptionMeta.iv, msg.encryptionMeta.keyId);
|
|
20298
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_and_plain)", { sessionId: msg.encryptionMeta.keyId, msgType: msg.msgType });
|
|
20299
|
+
} else if (isFileMessage) {
|
|
20300
|
+
const { keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20301
|
+
extra = _MessagePipe.buildFileEncryptExtra(iv, keyId);
|
|
20302
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_and_plain, fallback)", { sessionId: keyId, msgType: msg.msgType });
|
|
20303
|
+
} else {
|
|
20304
|
+
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20305
|
+
extra = JSON.stringify({
|
|
20306
|
+
cryptoIv: iv,
|
|
20307
|
+
encryptMsg: ciphertext,
|
|
20308
|
+
sessionId: keyId
|
|
20309
|
+
});
|
|
20310
|
+
content = JSON.stringify({ content: "" });
|
|
20311
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
|
|
20312
|
+
}
|
|
20132
20313
|
} catch (err) {
|
|
20133
20314
|
log21.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
|
|
20134
20315
|
error: err.message
|
|
@@ -20150,13 +20331,11 @@ var MessagePipe = class {
|
|
|
20150
20331
|
}
|
|
20151
20332
|
const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
|
|
20152
20333
|
if (!result) return;
|
|
20153
|
-
log21.info("outbound:sent \u2192 IM \u670D\u52A1\u5668", {
|
|
20334
|
+
log21.info("\u{1F4E4} outbound:sent \u2192 IM \u670D\u52A1\u5668", {
|
|
20154
20335
|
chatId: msg.chatId,
|
|
20155
|
-
receiveId: msg.senderId,
|
|
20156
|
-
msgType: msg.msgType,
|
|
20157
20336
|
encrypted: Boolean(extra),
|
|
20158
|
-
|
|
20159
|
-
|
|
20337
|
+
requestId: result.request_id,
|
|
20338
|
+
body
|
|
20160
20339
|
});
|
|
20161
20340
|
}
|
|
20162
20341
|
/**
|
package/dist/index.d.cts
CHANGED
|
@@ -191,6 +191,11 @@ interface OutboundMessage {
|
|
|
191
191
|
replyToMessageId?: string;
|
|
192
192
|
/** 跳过出站加密 — 用于"思考中"等无需加密的系统提示消息 */
|
|
193
193
|
skipEncrypt?: boolean;
|
|
194
|
+
/** 文件加密元数据 — 文件已在上传前加密时传入,sendMessage 直接使用此 keyId/iv 构建 extra */
|
|
195
|
+
encryptionMeta?: {
|
|
196
|
+
keyId: string;
|
|
197
|
+
iv: string;
|
|
198
|
+
};
|
|
194
199
|
}
|
|
195
200
|
/**
|
|
196
201
|
* 加密策略枚举 — 根据消息类型选择不同的加密等级
|
|
@@ -786,6 +791,27 @@ declare class MessagePipe {
|
|
|
786
791
|
* TODO: 验证通过后移除此方法
|
|
787
792
|
*/
|
|
788
793
|
injectRawFrame(rawData: string | Buffer): Promise<void>;
|
|
794
|
+
/**
|
|
795
|
+
* 加密文件 Buffer — 按 10MB 分片加密,返回加密后的 Buffer + 密钥信息。
|
|
796
|
+
* 用于文件上传前的加密处理。
|
|
797
|
+
*
|
|
798
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
799
|
+
*
|
|
800
|
+
* @param buffer - 原始文件 Buffer
|
|
801
|
+
* @returns { encryptedBuffer, keyId, iv } — 加密后的 buffer + 密钥标识 + 初始化向量
|
|
802
|
+
*/
|
|
803
|
+
encryptFile(buffer: Buffer): Promise<{
|
|
804
|
+
encryptedBuffer: Buffer;
|
|
805
|
+
keyId: string;
|
|
806
|
+
iv: string;
|
|
807
|
+
}>;
|
|
808
|
+
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
809
|
+
private static readonly FILE_MSG_TYPES;
|
|
810
|
+
/**
|
|
811
|
+
* 构建文件类加密消息的 extra — 完整模板格式。
|
|
812
|
+
* 文件消息不加密 content(需要保留 fileId 等元数据),只在 extra 中携带会话信息。
|
|
813
|
+
*/
|
|
814
|
+
private static buildFileEncryptExtra;
|
|
789
815
|
/**
|
|
790
816
|
* 出站发送 — 通过 HTTP API 发送消息到 IM 服务器。
|
|
791
817
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
@@ -794,6 +820,13 @@ declare class MessagePipe {
|
|
|
794
820
|
* 加密行为取决于 encryptionMode:
|
|
795
821
|
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
796
822
|
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
823
|
+
*
|
|
824
|
+
* 文件类消息 (image/file/voice/video):
|
|
825
|
+
* - content 保留原始文件元数据(fileId、fileName、size 等)
|
|
826
|
+
* - extra 使用完整模板格式,encryptMsg 为空
|
|
827
|
+
*
|
|
828
|
+
* 文本类消息 (text/markdown):
|
|
829
|
+
* - content 加密后置空,密文放入 extra.encryptMsg
|
|
797
830
|
*/
|
|
798
831
|
sendMessage(msg: OutboundMessage): Promise<void>;
|
|
799
832
|
/**
|
package/dist/setup-entry.cjs
CHANGED
|
@@ -2233,7 +2233,7 @@ var require_websocket = __commonJS({
|
|
|
2233
2233
|
var http = require("http");
|
|
2234
2234
|
var net = require("net");
|
|
2235
2235
|
var tls = require("tls");
|
|
2236
|
-
var { randomBytes:
|
|
2236
|
+
var { randomBytes: randomBytes4, createHash: createHash3 } = require("crypto");
|
|
2237
2237
|
var { Duplex, Readable } = require("stream");
|
|
2238
2238
|
var { URL: URL2 } = require("url");
|
|
2239
2239
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2763,7 +2763,7 @@ var require_websocket = __commonJS({
|
|
|
2763
2763
|
}
|
|
2764
2764
|
}
|
|
2765
2765
|
const defaultPort = isSecure ? 443 : 80;
|
|
2766
|
-
const key =
|
|
2766
|
+
const key = randomBytes4(16).toString("base64");
|
|
2767
2767
|
const request = isSecure ? https.request : http.request;
|
|
2768
2768
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2769
2769
|
let perMessageDeflate;
|
|
@@ -2893,7 +2893,7 @@ var require_websocket = __commonJS({
|
|
|
2893
2893
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2894
2894
|
return;
|
|
2895
2895
|
}
|
|
2896
|
-
const digest =
|
|
2896
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
2897
2897
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2898
2898
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2899
2899
|
return;
|
|
@@ -3260,7 +3260,7 @@ var require_websocket_server = __commonJS({
|
|
|
3260
3260
|
var EventEmitter2 = require("events");
|
|
3261
3261
|
var http = require("http");
|
|
3262
3262
|
var { Duplex } = require("stream");
|
|
3263
|
-
var { createHash:
|
|
3263
|
+
var { createHash: createHash3 } = require("crypto");
|
|
3264
3264
|
var extension2 = require_extension();
|
|
3265
3265
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3266
3266
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3561,7 +3561,7 @@ var require_websocket_server = __commonJS({
|
|
|
3561
3561
|
);
|
|
3562
3562
|
}
|
|
3563
3563
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3564
|
-
const digest =
|
|
3564
|
+
const digest = createHash3("sha1").update(key + GUID).digest("base64");
|
|
3565
3565
|
const headers = [
|
|
3566
3566
|
"HTTP/1.1 101 Switching Protocols",
|
|
3567
3567
|
"Upgrade: websocket",
|
|
@@ -3928,8 +3928,8 @@ async function uploadMedia(params) {
|
|
|
3928
3928
|
const { readFile: readFile3 } = await import("fs/promises");
|
|
3929
3929
|
buffer = await readFile3(file2);
|
|
3930
3930
|
}
|
|
3931
|
-
const { createHash:
|
|
3932
|
-
const fileHash =
|
|
3931
|
+
const { createHash: createHash3 } = await import("crypto");
|
|
3932
|
+
const fileHash = createHash3("md5").update(buffer).digest("hex");
|
|
3933
3933
|
log2.debug("upload:fileHash", { fileName, fileHash });
|
|
3934
3934
|
const maxBytes = maxFileSizeMb * 1024 * 1024;
|
|
3935
3935
|
if (buffer.length > maxBytes) {
|
|
@@ -4109,6 +4109,65 @@ function inferMimeType(fileName) {
|
|
|
4109
4109
|
const ext = path.extname(fileName).toLowerCase();
|
|
4110
4110
|
return MIME_MAP[ext] || "application/octet-stream";
|
|
4111
4111
|
}
|
|
4112
|
+
function getImageDimensions(buffer) {
|
|
4113
|
+
try {
|
|
4114
|
+
if (buffer.length < 24) return void 0;
|
|
4115
|
+
if (buffer[0] === 137 && buffer[1] === 80 && buffer[2] === 78 && buffer[3] === 71) {
|
|
4116
|
+
return {
|
|
4117
|
+
width: buffer.readUInt32BE(16),
|
|
4118
|
+
height: buffer.readUInt32BE(20)
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
if (buffer[0] === 71 && buffer[1] === 73 && buffer[2] === 70) {
|
|
4122
|
+
return {
|
|
4123
|
+
width: buffer.readUInt16LE(6),
|
|
4124
|
+
height: buffer.readUInt16LE(8)
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
if (buffer[0] === 66 && buffer[1] === 77 && buffer.length >= 26) {
|
|
4128
|
+
return {
|
|
4129
|
+
width: buffer.readInt32LE(18),
|
|
4130
|
+
height: Math.abs(buffer.readInt32LE(22))
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
if (buffer[0] === 82 && buffer[1] === 73 && buffer[8] === 87 && buffer[9] === 69) {
|
|
4134
|
+
if (buffer[12] === 86 && buffer[13] === 80 && buffer[14] === 56 && buffer[15] === 32) {
|
|
4135
|
+
if (buffer.length >= 30) {
|
|
4136
|
+
return {
|
|
4137
|
+
width: buffer.readUInt16LE(26) & 16383,
|
|
4138
|
+
height: buffer.readUInt16LE(28) & 16383
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
if (buffer[12] === 86 && buffer[13] === 80 && buffer[14] === 56 && buffer[15] === 76) {
|
|
4143
|
+
if (buffer.length >= 25) {
|
|
4144
|
+
const bits = buffer.readUInt32LE(21);
|
|
4145
|
+
return {
|
|
4146
|
+
width: (bits & 16383) + 1,
|
|
4147
|
+
height: (bits >> 14 & 16383) + 1
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
if (buffer[0] === 255 && buffer[1] === 216) {
|
|
4153
|
+
let offset = 2;
|
|
4154
|
+
while (offset < buffer.length - 8) {
|
|
4155
|
+
if (buffer[offset] !== 255) break;
|
|
4156
|
+
const marker = buffer[offset + 1];
|
|
4157
|
+
if (marker >= 192 && marker <= 195 || marker >= 197 && marker <= 199 || marker >= 201 && marker <= 203 || marker >= 205 && marker <= 207) {
|
|
4158
|
+
return {
|
|
4159
|
+
height: buffer.readUInt16BE(offset + 5),
|
|
4160
|
+
width: buffer.readUInt16BE(offset + 7)
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
const segLen = buffer.readUInt16BE(offset + 2);
|
|
4164
|
+
offset += 2 + segLen;
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
} catch {
|
|
4168
|
+
}
|
|
4169
|
+
return void 0;
|
|
4170
|
+
}
|
|
4112
4171
|
async function resolveAndUploadMedia(params) {
|
|
4113
4172
|
const {
|
|
4114
4173
|
mediaUrl,
|
|
@@ -4159,7 +4218,26 @@ async function resolveAndUploadMedia(params) {
|
|
|
4159
4218
|
}
|
|
4160
4219
|
const fileType = detectFileType(fileName);
|
|
4161
4220
|
const mimeType = inferMimeType(fileName);
|
|
4162
|
-
|
|
4221
|
+
const ext = path.extname(fileName).replace(".", "").toLowerCase();
|
|
4222
|
+
let imageDimensions;
|
|
4223
|
+
if (fileType === "image") {
|
|
4224
|
+
imageDimensions = getImageDimensions(buffer);
|
|
4225
|
+
log3.info("media:imageDimensions", { fileName, ...imageDimensions });
|
|
4226
|
+
}
|
|
4227
|
+
log3.info("media:fileType", { fileName, fileType, mimeType, ext });
|
|
4228
|
+
let encryptionMeta;
|
|
4229
|
+
if (!params.skipEncrypt) {
|
|
4230
|
+
try {
|
|
4231
|
+
const originalSize = buffer.length;
|
|
4232
|
+
const result = await messagePipe.encryptFile(buffer);
|
|
4233
|
+
buffer = result.encryptedBuffer;
|
|
4234
|
+
encryptionMeta = { keyId: result.keyId, iv: result.iv };
|
|
4235
|
+
log3.info("media:encrypted", { fileName, originalSize, encryptedSize: buffer.length });
|
|
4236
|
+
} catch (err) {
|
|
4237
|
+
log3.error("media:encrypt failed", { fileName, error: err.message });
|
|
4238
|
+
throw err;
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4163
4241
|
let uploadResult;
|
|
4164
4242
|
try {
|
|
4165
4243
|
uploadResult = await uploadMedia({
|
|
@@ -4184,11 +4262,17 @@ async function resolveAndUploadMedia(params) {
|
|
|
4184
4262
|
const msgType = fileType;
|
|
4185
4263
|
let contentPayload;
|
|
4186
4264
|
if (fileType === "file") {
|
|
4187
|
-
contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize,
|
|
4265
|
+
contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize, ext };
|
|
4188
4266
|
} else if (fileType === "image") {
|
|
4189
|
-
contentPayload = {
|
|
4267
|
+
contentPayload = {
|
|
4268
|
+
fileId: uploadResult.fileKey,
|
|
4269
|
+
width: imageDimensions?.width ?? 0,
|
|
4270
|
+
height: imageDimensions?.height ?? 0,
|
|
4271
|
+
altText: fileName,
|
|
4272
|
+
ext
|
|
4273
|
+
};
|
|
4190
4274
|
} else {
|
|
4191
|
-
contentPayload = { fileId: uploadResult.fileKey,
|
|
4275
|
+
contentPayload = { fileId: uploadResult.fileKey, ext };
|
|
4192
4276
|
}
|
|
4193
4277
|
await messagePipe.sendMessage({
|
|
4194
4278
|
chatId,
|
|
@@ -4196,12 +4280,15 @@ async function resolveAndUploadMedia(params) {
|
|
|
4196
4280
|
// 私聊场景: chatId 即为用户 ID
|
|
4197
4281
|
msgType,
|
|
4198
4282
|
content: JSON.stringify(contentPayload),
|
|
4199
|
-
skipEncrypt: params.skipEncrypt
|
|
4283
|
+
skipEncrypt: params.skipEncrypt,
|
|
4284
|
+
encryptionMeta
|
|
4285
|
+
// 文件加密的 keyId/iv → sendMessage 用于构建 extra
|
|
4200
4286
|
});
|
|
4201
4287
|
log3.info("media:sent", {
|
|
4202
4288
|
chatId,
|
|
4203
4289
|
fileId: uploadResult.fileKey,
|
|
4204
|
-
msgType
|
|
4290
|
+
msgType,
|
|
4291
|
+
encrypted: Boolean(encryptionMeta)
|
|
4205
4292
|
});
|
|
4206
4293
|
return {
|
|
4207
4294
|
channel: CHANNEL_ID,
|
|
@@ -19099,7 +19186,7 @@ var MessageDedup = class {
|
|
|
19099
19186
|
// src/transport/message-pipe.ts
|
|
19100
19187
|
var import_node_crypto3 = require("crypto");
|
|
19101
19188
|
var log12 = createLogger("transport/message-pipe");
|
|
19102
|
-
var MessagePipe = class {
|
|
19189
|
+
var MessagePipe = class _MessagePipe {
|
|
19103
19190
|
wsClient;
|
|
19104
19191
|
dedup;
|
|
19105
19192
|
crypto;
|
|
@@ -19141,6 +19228,74 @@ var MessagePipe = class {
|
|
|
19141
19228
|
async injectRawFrame(rawData) {
|
|
19142
19229
|
return this.handleInbound(rawData);
|
|
19143
19230
|
}
|
|
19231
|
+
/**
|
|
19232
|
+
* 加密文件 Buffer — 按 10MB 分片加密,返回加密后的 Buffer + 密钥信息。
|
|
19233
|
+
* 用于文件上传前的加密处理。
|
|
19234
|
+
*
|
|
19235
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
19236
|
+
*
|
|
19237
|
+
* @param buffer - 原始文件 Buffer
|
|
19238
|
+
* @returns { encryptedBuffer, keyId, iv } — 加密后的 buffer + 密钥标识 + 初始化向量
|
|
19239
|
+
*/
|
|
19240
|
+
async encryptFile(buffer) {
|
|
19241
|
+
const iv = (0, import_node_crypto3.randomBytes)(8).toString("hex");
|
|
19242
|
+
const chunkSize = 10 * 1024 * 1024;
|
|
19243
|
+
const totalChunks = Math.ceil(buffer.length / chunkSize);
|
|
19244
|
+
let keyId = "";
|
|
19245
|
+
let sessionKey = "";
|
|
19246
|
+
let fillKey = "";
|
|
19247
|
+
const encryptedChunks = [];
|
|
19248
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
19249
|
+
const start = i * chunkSize;
|
|
19250
|
+
const end = Math.min(start + chunkSize, buffer.length);
|
|
19251
|
+
const chunk = buffer.subarray(start, end);
|
|
19252
|
+
const options = keyId ? { keyId, sessionKey, fillKey } : void 0;
|
|
19253
|
+
const result = await this.crypto.encryptFileChunk(chunk, iv, options);
|
|
19254
|
+
encryptedChunks.push(result.fileBuffer);
|
|
19255
|
+
keyId = result.keyId;
|
|
19256
|
+
sessionKey = result.sessionKey;
|
|
19257
|
+
fillKey = result.fillKey;
|
|
19258
|
+
log12.debug("\u{1F512} \u6587\u4EF6\u5206\u7247\u52A0\u5BC6", {
|
|
19259
|
+
chunk: `${i + 1}/${totalChunks}`,
|
|
19260
|
+
originalSize: chunk.length,
|
|
19261
|
+
encryptedSize: result.fileBuffer.length,
|
|
19262
|
+
keyId
|
|
19263
|
+
});
|
|
19264
|
+
}
|
|
19265
|
+
const encryptedBuffer = Buffer.concat(encryptedChunks);
|
|
19266
|
+
log12.info("\u{1F512} \u6587\u4EF6\u52A0\u5BC6\u5B8C\u6210", {
|
|
19267
|
+
originalSize: buffer.length,
|
|
19268
|
+
encryptedSize: encryptedBuffer.length,
|
|
19269
|
+
totalChunks,
|
|
19270
|
+
keyId
|
|
19271
|
+
});
|
|
19272
|
+
return { encryptedBuffer, keyId, iv };
|
|
19273
|
+
}
|
|
19274
|
+
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
19275
|
+
static FILE_MSG_TYPES = /* @__PURE__ */ new Set(["image", "file", "voice", "video"]);
|
|
19276
|
+
/**
|
|
19277
|
+
* 构建文件类加密消息的 extra — 完整模板格式。
|
|
19278
|
+
* 文件消息不加密 content(需要保留 fileId 等元数据),只在 extra 中携带会话信息。
|
|
19279
|
+
*/
|
|
19280
|
+
static buildFileEncryptExtra(iv, keyId) {
|
|
19281
|
+
return JSON.stringify({
|
|
19282
|
+
containSensitive: false,
|
|
19283
|
+
cryptoIv: iv,
|
|
19284
|
+
decryptText: "",
|
|
19285
|
+
decrypted: false,
|
|
19286
|
+
destroyType: 0,
|
|
19287
|
+
encryptMsg: "",
|
|
19288
|
+
expireAt: null,
|
|
19289
|
+
level: 0,
|
|
19290
|
+
newDestroy: 1,
|
|
19291
|
+
phone: "",
|
|
19292
|
+
receiveShowTip: false,
|
|
19293
|
+
receiveTip: "",
|
|
19294
|
+
sessionId: keyId,
|
|
19295
|
+
showTip: false,
|
|
19296
|
+
tip: ""
|
|
19297
|
+
});
|
|
19298
|
+
}
|
|
19144
19299
|
/**
|
|
19145
19300
|
* 出站发送 — 通过 HTTP API 发送消息到 IM 服务器。
|
|
19146
19301
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
@@ -19149,22 +19304,28 @@ var MessagePipe = class {
|
|
|
19149
19304
|
* 加密行为取决于 encryptionMode:
|
|
19150
19305
|
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
19151
19306
|
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
19307
|
+
*
|
|
19308
|
+
* 文件类消息 (image/file/voice/video):
|
|
19309
|
+
* - content 保留原始文件元数据(fileId、fileName、size 等)
|
|
19310
|
+
* - extra 使用完整模板格式,encryptMsg 为空
|
|
19311
|
+
*
|
|
19312
|
+
* 文本类消息 (text/markdown):
|
|
19313
|
+
* - content 加密后置空,密文放入 extra.encryptMsg
|
|
19152
19314
|
*/
|
|
19153
19315
|
async sendMessage(msg) {
|
|
19154
19316
|
let content = msg.content;
|
|
19155
19317
|
let extra = "";
|
|
19318
|
+
const isFileMessage = _MessagePipe.FILE_MSG_TYPES.has(msg.msgType);
|
|
19156
19319
|
if (!msg.skipEncrypt && this.quantumAccount) {
|
|
19157
19320
|
if (this.encryptionMode === "quantum_only") {
|
|
19158
|
-
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
} else {
|
|
19167
|
-
try {
|
|
19321
|
+
if (isFileMessage && msg.encryptionMeta) {
|
|
19322
|
+
extra = _MessagePipe.buildFileEncryptExtra(msg.encryptionMeta.iv, msg.encryptionMeta.keyId);
|
|
19323
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_only)", { sessionId: msg.encryptionMeta.keyId, msgType: msg.msgType });
|
|
19324
|
+
} else if (isFileMessage) {
|
|
19325
|
+
const { keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19326
|
+
extra = _MessagePipe.buildFileEncryptExtra(iv, keyId);
|
|
19327
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_only, fallback)", { sessionId: keyId, msgType: msg.msgType });
|
|
19328
|
+
} else {
|
|
19168
19329
|
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19169
19330
|
extra = JSON.stringify({
|
|
19170
19331
|
cryptoIv: iv,
|
|
@@ -19172,7 +19333,27 @@ var MessagePipe = class {
|
|
|
19172
19333
|
sessionId: keyId
|
|
19173
19334
|
});
|
|
19174
19335
|
content = JSON.stringify({ content: "" });
|
|
19175
|
-
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (
|
|
19336
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
|
|
19337
|
+
}
|
|
19338
|
+
} else {
|
|
19339
|
+
try {
|
|
19340
|
+
if (isFileMessage && msg.encryptionMeta) {
|
|
19341
|
+
extra = _MessagePipe.buildFileEncryptExtra(msg.encryptionMeta.iv, msg.encryptionMeta.keyId);
|
|
19342
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_and_plain)", { sessionId: msg.encryptionMeta.keyId, msgType: msg.msgType });
|
|
19343
|
+
} else if (isFileMessage) {
|
|
19344
|
+
const { keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19345
|
+
extra = _MessagePipe.buildFileEncryptExtra(iv, keyId);
|
|
19346
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6 (quantum_and_plain, fallback)", { sessionId: keyId, msgType: msg.msgType });
|
|
19347
|
+
} else {
|
|
19348
|
+
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19349
|
+
extra = JSON.stringify({
|
|
19350
|
+
cryptoIv: iv,
|
|
19351
|
+
encryptMsg: ciphertext,
|
|
19352
|
+
sessionId: keyId
|
|
19353
|
+
});
|
|
19354
|
+
content = JSON.stringify({ content: "" });
|
|
19355
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
|
|
19356
|
+
}
|
|
19176
19357
|
} catch (err) {
|
|
19177
19358
|
log12.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
|
|
19178
19359
|
error: err.message
|
|
@@ -19194,13 +19375,11 @@ var MessagePipe = class {
|
|
|
19194
19375
|
}
|
|
19195
19376
|
const result = await this.callMessageApi("/messages/v1/send", body, "outbound");
|
|
19196
19377
|
if (!result) return;
|
|
19197
|
-
log12.info("outbound:sent \u2192 IM \u670D\u52A1\u5668", {
|
|
19378
|
+
log12.info("\u{1F4E4} outbound:sent \u2192 IM \u670D\u52A1\u5668", {
|
|
19198
19379
|
chatId: msg.chatId,
|
|
19199
|
-
receiveId: msg.senderId,
|
|
19200
|
-
msgType: msg.msgType,
|
|
19201
19380
|
encrypted: Boolean(extra),
|
|
19202
|
-
|
|
19203
|
-
|
|
19381
|
+
requestId: result.request_id,
|
|
19382
|
+
body
|
|
19204
19383
|
});
|
|
19205
19384
|
}
|
|
19206
19385
|
/**
|