liangzimixin 0.3.35 → 0.3.37

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 CHANGED
@@ -17956,6 +17956,56 @@ function detectFileType(fileName) {
17956
17956
  if (VIDEO_EXTS.has(ext)) return "video";
17957
17957
  return "file";
17958
17958
  }
17959
+ var MIME_MAP = {
17960
+ // 图片
17961
+ ".jpg": "image/jpeg",
17962
+ ".jpeg": "image/jpeg",
17963
+ ".png": "image/png",
17964
+ ".gif": "image/gif",
17965
+ ".bmp": "image/bmp",
17966
+ ".webp": "image/webp",
17967
+ ".svg": "image/svg+xml",
17968
+ ".ico": "image/x-icon",
17969
+ ".tiff": "image/tiff",
17970
+ ".tif": "image/tiff",
17971
+ // 音频
17972
+ ".mp3": "audio/mpeg",
17973
+ ".wav": "audio/wav",
17974
+ ".ogg": "audio/ogg",
17975
+ ".opus": "audio/opus",
17976
+ ".flac": "audio/flac",
17977
+ ".aac": "audio/aac",
17978
+ ".m4a": "audio/mp4",
17979
+ ".wma": "audio/x-ms-wma",
17980
+ // 视频
17981
+ ".mp4": "video/mp4",
17982
+ ".mov": "video/quicktime",
17983
+ ".avi": "video/x-msvideo",
17984
+ ".mkv": "video/x-matroska",
17985
+ ".wmv": "video/x-ms-wmv",
17986
+ ".flv": "video/x-flv",
17987
+ ".webm": "video/webm",
17988
+ ".m4v": "video/mp4",
17989
+ // 文档
17990
+ ".pdf": "application/pdf",
17991
+ ".doc": "application/msword",
17992
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
17993
+ ".xls": "application/vnd.ms-excel",
17994
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
17995
+ ".ppt": "application/vnd.ms-powerpoint",
17996
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
17997
+ ".txt": "text/plain",
17998
+ ".csv": "text/csv",
17999
+ ".json": "application/json",
18000
+ ".zip": "application/zip",
18001
+ ".rar": "application/x-rar-compressed",
18002
+ ".7z": "application/x-7z-compressed",
18003
+ ".gz": "application/gzip"
18004
+ };
18005
+ function inferMimeType(fileName) {
18006
+ const ext = path.extname(fileName).toLowerCase();
18007
+ return MIME_MAP[ext] || "application/octet-stream";
18008
+ }
17959
18009
  async function resolveAndUploadMedia(params) {
17960
18010
  const {
17961
18011
  mediaUrl,
@@ -18005,12 +18055,14 @@ async function resolveAndUploadMedia(params) {
18005
18055
  });
18006
18056
  }
18007
18057
  const fileType = detectFileType(fileName);
18008
- log3.info("media:fileType", { fileName, fileType });
18058
+ const mimeType = inferMimeType(fileName);
18059
+ log3.info("media:fileType", { fileName, fileType, mimeType });
18009
18060
  let uploadResult;
18010
18061
  try {
18011
18062
  uploadResult = await uploadMedia({
18012
18063
  file: buffer,
18013
18064
  fileName,
18065
+ mimeType,
18014
18066
  tokenManager,
18015
18067
  serverUrl,
18016
18068
  maxFileSizeMb,
@@ -18027,7 +18079,14 @@ async function resolveAndUploadMedia(params) {
18027
18079
  };
18028
18080
  }
18029
18081
  const msgType = fileType;
18030
- const contentPayload = fileType === "file" ? { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize } : { fileId: uploadResult.fileKey };
18082
+ let contentPayload;
18083
+ if (fileType === "file") {
18084
+ contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize, mimeType };
18085
+ } else if (fileType === "image") {
18086
+ contentPayload = { fileId: uploadResult.fileKey };
18087
+ } else {
18088
+ contentPayload = { fileId: uploadResult.fileKey, mimeType };
18089
+ }
18031
18090
  await messagePipe.sendMessage({
18032
18091
  chatId,
18033
18092
  senderId: chatId,
@@ -18929,7 +18988,11 @@ var quantumImPlugin = {
18929
18988
  })
18930
18989
  },
18931
18990
  messaging: {
18932
- normalizeTarget: (raw) => raw ?? void 0
18991
+ normalizeTarget: (raw) => {
18992
+ if (!raw) return void 0;
18993
+ const normalized = parseChannelAddress(raw).trim();
18994
+ return normalized || void 0;
18995
+ }
18933
18996
  },
18934
18997
  outbound: quantumImOutbound,
18935
18998
  setup: {
@@ -19865,6 +19928,9 @@ var WSClient = class extends import_node_events.EventEmitter {
19865
19928
  ws.on("message", (data) => {
19866
19929
  this.emit("message", data);
19867
19930
  });
19931
+ ws.on("pong", () => {
19932
+ this.emit("pong");
19933
+ });
19868
19934
  ws.on("close", (code, reason) => {
19869
19935
  log19.info("ws:closed", { code, reason: reason.toString() });
19870
19936
  this.emit("close", code, reason.toString());
@@ -19887,6 +19953,12 @@ var WSClient = class extends import_node_events.EventEmitter {
19887
19953
  }
19888
19954
  this.ws.send(data);
19889
19955
  }
19956
+ /** 发送 WebSocket 协议级 Ping 帧 */
19957
+ ping() {
19958
+ if (this.ws && this.ws.readyState === wrapper_default.OPEN) {
19959
+ this.ws.ping();
19960
+ }
19961
+ }
19890
19962
  /** 关闭连接 */
19891
19963
  close(code, reason) {
19892
19964
  if (this.ws) {
@@ -20243,6 +20315,8 @@ var ConnectionManager = class {
20243
20315
  appId = "";
20244
20316
  /** 是否正在重连中 (防止并发重连) */
20245
20317
  reconnecting = false;
20318
+ /** 是否收到了最近一次 pong 响应 */
20319
+ pongReceived = true;
20246
20320
  constructor(client, options) {
20247
20321
  this.client = client;
20248
20322
  this.options = {
@@ -20276,6 +20350,15 @@ var ConnectionManager = class {
20276
20350
  ...this.appId ? { "X-App-ID": this.appId } : {}
20277
20351
  }
20278
20352
  });
20353
+ this.registerEvents();
20354
+ this.startHeartbeat();
20355
+ log22.info("ConnectionManager started \u2713", { url: url2 });
20356
+ }
20357
+ /** 注册 close / error / pong 事件 */
20358
+ registerEvents() {
20359
+ this.client.removeAllListeners("close");
20360
+ this.client.removeAllListeners("error");
20361
+ this.client.removeAllListeners("pong");
20279
20362
  this.client.on("close", () => {
20280
20363
  if (this.running) {
20281
20364
  log22.warn("ws:disconnected, scheduling reconnect");
@@ -20285,17 +20368,30 @@ var ConnectionManager = class {
20285
20368
  this.client.on("error", (err) => {
20286
20369
  log22.error("ws:connection-error", { error: err.message });
20287
20370
  });
20288
- this.startHeartbeat();
20289
- log22.info("ConnectionManager started \u2713", { url: url2 });
20371
+ this.client.on("pong", () => {
20372
+ this.pongReceived = true;
20373
+ log22.info("heartbeat: pong received");
20374
+ });
20290
20375
  }
20291
- /** 启动心跳保活 — 定时检查连接状态 */
20376
+ /** 启动心跳保活 — 定时发送 WebSocket Ping 帧 */
20292
20377
  startHeartbeat() {
20293
20378
  this.stopHeartbeat();
20379
+ this.pongReceived = true;
20294
20380
  this.heartbeatTimer = setInterval(() => {
20295
20381
  if (this.client.readyState !== wrapper_default.OPEN) {
20296
20382
  log22.warn("heartbeat: connection not open, scheduling reconnect");
20297
20383
  this.scheduleReconnect();
20384
+ return;
20385
+ }
20386
+ if (!this.pongReceived) {
20387
+ log22.warn("heartbeat: pong timeout, connection dead");
20388
+ this.client.close(4e3, "pong timeout");
20389
+ this.scheduleReconnect();
20390
+ return;
20298
20391
  }
20392
+ this.pongReceived = false;
20393
+ this.client.ping();
20394
+ log22.info("heartbeat: ping sent");
20299
20395
  }, this.options.heartbeatIntervalMs);
20300
20396
  }
20301
20397
  /** 停止心跳定时器 */
@@ -20337,6 +20433,7 @@ var ConnectionManager = class {
20337
20433
  }
20338
20434
  });
20339
20435
  this.reconnectAttempts = 0;
20436
+ this.registerEvents();
20340
20437
  this.startHeartbeat();
20341
20438
  log22.info("ws:reconnected", { attempt: this.reconnectAttempts, url: this.url });
20342
20439
  return;
package/dist/index.d.cts CHANGED
@@ -684,6 +684,8 @@ declare class WSClient extends EventEmitter {
684
684
  connect(options: WSClientOptions): Promise<void>;
685
685
  /** 发送数据到服务器 — 前置检查连接状态 */
686
686
  send(data: string | Buffer): void;
687
+ /** 发送 WebSocket 协议级 Ping 帧 */
688
+ ping(): void;
687
689
  /** 关闭连接 */
688
690
  close(code?: number, reason?: string): void;
689
691
  /** 当前连接状态 (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED) */
@@ -865,6 +867,8 @@ declare class ConnectionManager {
865
867
  private appId;
866
868
  /** 是否正在重连中 (防止并发重连) */
867
869
  private reconnecting;
870
+ /** 是否收到了最近一次 pong 响应 */
871
+ private pongReceived;
868
872
  constructor(client: WSClient, options?: ConnectionManagerOptions);
869
873
  /**
870
874
  * 启动连接 — 连接 WS + 开始心跳 + 注册重连逻辑
@@ -873,7 +877,9 @@ declare class ConnectionManager {
873
877
  * @param appId - 应用 ID (用于 X-App-ID 头)
874
878
  */
875
879
  start(url: string, tokenFn: () => Promise<string>, appId?: string): Promise<void>;
876
- /** 启动心跳保活 定时检查连接状态 */
880
+ /** 注册 close / error / pong 事件 */
881
+ private registerEvents;
882
+ /** 启动心跳保活 — 定时发送 WebSocket Ping 帧 */
877
883
  private startHeartbeat;
878
884
  /** 停止心跳定时器 */
879
885
  private stopHeartbeat;
@@ -4051,6 +4051,56 @@ function detectFileType(fileName) {
4051
4051
  if (VIDEO_EXTS.has(ext)) return "video";
4052
4052
  return "file";
4053
4053
  }
4054
+ var MIME_MAP = {
4055
+ // 图片
4056
+ ".jpg": "image/jpeg",
4057
+ ".jpeg": "image/jpeg",
4058
+ ".png": "image/png",
4059
+ ".gif": "image/gif",
4060
+ ".bmp": "image/bmp",
4061
+ ".webp": "image/webp",
4062
+ ".svg": "image/svg+xml",
4063
+ ".ico": "image/x-icon",
4064
+ ".tiff": "image/tiff",
4065
+ ".tif": "image/tiff",
4066
+ // 音频
4067
+ ".mp3": "audio/mpeg",
4068
+ ".wav": "audio/wav",
4069
+ ".ogg": "audio/ogg",
4070
+ ".opus": "audio/opus",
4071
+ ".flac": "audio/flac",
4072
+ ".aac": "audio/aac",
4073
+ ".m4a": "audio/mp4",
4074
+ ".wma": "audio/x-ms-wma",
4075
+ // 视频
4076
+ ".mp4": "video/mp4",
4077
+ ".mov": "video/quicktime",
4078
+ ".avi": "video/x-msvideo",
4079
+ ".mkv": "video/x-matroska",
4080
+ ".wmv": "video/x-ms-wmv",
4081
+ ".flv": "video/x-flv",
4082
+ ".webm": "video/webm",
4083
+ ".m4v": "video/mp4",
4084
+ // 文档
4085
+ ".pdf": "application/pdf",
4086
+ ".doc": "application/msword",
4087
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
4088
+ ".xls": "application/vnd.ms-excel",
4089
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4090
+ ".ppt": "application/vnd.ms-powerpoint",
4091
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
4092
+ ".txt": "text/plain",
4093
+ ".csv": "text/csv",
4094
+ ".json": "application/json",
4095
+ ".zip": "application/zip",
4096
+ ".rar": "application/x-rar-compressed",
4097
+ ".7z": "application/x-7z-compressed",
4098
+ ".gz": "application/gzip"
4099
+ };
4100
+ function inferMimeType(fileName) {
4101
+ const ext = path.extname(fileName).toLowerCase();
4102
+ return MIME_MAP[ext] || "application/octet-stream";
4103
+ }
4054
4104
  async function resolveAndUploadMedia(params) {
4055
4105
  const {
4056
4106
  mediaUrl,
@@ -4100,12 +4150,14 @@ async function resolveAndUploadMedia(params) {
4100
4150
  });
4101
4151
  }
4102
4152
  const fileType = detectFileType(fileName);
4103
- log3.info("media:fileType", { fileName, fileType });
4153
+ const mimeType = inferMimeType(fileName);
4154
+ log3.info("media:fileType", { fileName, fileType, mimeType });
4104
4155
  let uploadResult;
4105
4156
  try {
4106
4157
  uploadResult = await uploadMedia({
4107
4158
  file: buffer,
4108
4159
  fileName,
4160
+ mimeType,
4109
4161
  tokenManager,
4110
4162
  serverUrl,
4111
4163
  maxFileSizeMb,
@@ -4122,7 +4174,14 @@ async function resolveAndUploadMedia(params) {
4122
4174
  };
4123
4175
  }
4124
4176
  const msgType = fileType;
4125
- const contentPayload = fileType === "file" ? { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize } : { fileId: uploadResult.fileKey };
4177
+ let contentPayload;
4178
+ if (fileType === "file") {
4179
+ contentPayload = { fileId: uploadResult.fileKey, fileName, size: uploadResult.fileSize, mimeType };
4180
+ } else if (fileType === "image") {
4181
+ contentPayload = { fileId: uploadResult.fileKey };
4182
+ } else {
4183
+ contentPayload = { fileId: uploadResult.fileKey, mimeType };
4184
+ }
4126
4185
  await messagePipe.sendMessage({
4127
4186
  chatId,
4128
4187
  senderId: chatId,
@@ -18928,6 +18987,9 @@ var WSClient = class extends import_node_events.EventEmitter {
18928
18987
  ws.on("message", (data) => {
18929
18988
  this.emit("message", data);
18930
18989
  });
18990
+ ws.on("pong", () => {
18991
+ this.emit("pong");
18992
+ });
18931
18993
  ws.on("close", (code, reason) => {
18932
18994
  log10.info("ws:closed", { code, reason: reason.toString() });
18933
18995
  this.emit("close", code, reason.toString());
@@ -18950,6 +19012,12 @@ var WSClient = class extends import_node_events.EventEmitter {
18950
19012
  }
18951
19013
  this.ws.send(data);
18952
19014
  }
19015
+ /** 发送 WebSocket 协议级 Ping 帧 */
19016
+ ping() {
19017
+ if (this.ws && this.ws.readyState === wrapper_default.OPEN) {
19018
+ this.ws.ping();
19019
+ }
19020
+ }
18953
19021
  /** 关闭连接 */
18954
19022
  close(code, reason) {
18955
19023
  if (this.ws) {
@@ -19306,6 +19374,8 @@ var ConnectionManager = class {
19306
19374
  appId = "";
19307
19375
  /** 是否正在重连中 (防止并发重连) */
19308
19376
  reconnecting = false;
19377
+ /** 是否收到了最近一次 pong 响应 */
19378
+ pongReceived = true;
19309
19379
  constructor(client, options) {
19310
19380
  this.client = client;
19311
19381
  this.options = {
@@ -19339,6 +19409,15 @@ var ConnectionManager = class {
19339
19409
  ...this.appId ? { "X-App-ID": this.appId } : {}
19340
19410
  }
19341
19411
  });
19412
+ this.registerEvents();
19413
+ this.startHeartbeat();
19414
+ log13.info("ConnectionManager started \u2713", { url: url2 });
19415
+ }
19416
+ /** 注册 close / error / pong 事件 */
19417
+ registerEvents() {
19418
+ this.client.removeAllListeners("close");
19419
+ this.client.removeAllListeners("error");
19420
+ this.client.removeAllListeners("pong");
19342
19421
  this.client.on("close", () => {
19343
19422
  if (this.running) {
19344
19423
  log13.warn("ws:disconnected, scheduling reconnect");
@@ -19348,17 +19427,30 @@ var ConnectionManager = class {
19348
19427
  this.client.on("error", (err) => {
19349
19428
  log13.error("ws:connection-error", { error: err.message });
19350
19429
  });
19351
- this.startHeartbeat();
19352
- log13.info("ConnectionManager started \u2713", { url: url2 });
19430
+ this.client.on("pong", () => {
19431
+ this.pongReceived = true;
19432
+ log13.info("heartbeat: pong received");
19433
+ });
19353
19434
  }
19354
- /** 启动心跳保活 — 定时检查连接状态 */
19435
+ /** 启动心跳保活 — 定时发送 WebSocket Ping 帧 */
19355
19436
  startHeartbeat() {
19356
19437
  this.stopHeartbeat();
19438
+ this.pongReceived = true;
19357
19439
  this.heartbeatTimer = setInterval(() => {
19358
19440
  if (this.client.readyState !== wrapper_default.OPEN) {
19359
19441
  log13.warn("heartbeat: connection not open, scheduling reconnect");
19360
19442
  this.scheduleReconnect();
19443
+ return;
19361
19444
  }
19445
+ if (!this.pongReceived) {
19446
+ log13.warn("heartbeat: pong timeout, connection dead");
19447
+ this.client.close(4e3, "pong timeout");
19448
+ this.scheduleReconnect();
19449
+ return;
19450
+ }
19451
+ this.pongReceived = false;
19452
+ this.client.ping();
19453
+ log13.info("heartbeat: ping sent");
19362
19454
  }, this.options.heartbeatIntervalMs);
19363
19455
  }
19364
19456
  /** 停止心跳定时器 */
@@ -19400,6 +19492,7 @@ var ConnectionManager = class {
19400
19492
  }
19401
19493
  });
19402
19494
  this.reconnectAttempts = 0;
19495
+ this.registerEvents();
19403
19496
  this.startHeartbeat();
19404
19497
  log13.info("ws:reconnected", { attempt: this.reconnectAttempts, url: this.url });
19405
19498
  return;
@@ -20661,7 +20754,11 @@ var quantumImPlugin = {
20661
20754
  })
20662
20755
  },
20663
20756
  messaging: {
20664
- normalizeTarget: (raw) => raw ?? void 0
20757
+ normalizeTarget: (raw) => {
20758
+ if (!raw) return void 0;
20759
+ const normalized = parseChannelAddress(raw).trim();
20760
+ return normalized || void 0;
20761
+ }
20665
20762
  },
20666
20763
  outbound: quantumImOutbound,
20667
20764
  setup: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liangzimixin",
3
- "version": "0.3.35",
3
+ "version": "0.3.37",
4
4
  "description": "Quantum-encrypted IM channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",