@apocaliss92/nodelink-js 0.4.6 → 0.4.7

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.
@@ -10130,6 +10130,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
10130
10130
  * - "replay:XXX" - dedicated per replay session
10131
10131
  */
10132
10132
  socketPool = /* @__PURE__ */ new Map();
10133
+ /**
10134
+ * Consecutive stream-start (cmdId=3) timeout counter per socket tag.
10135
+ * When a streaming socket has N consecutive timeouts, the socket is force-closed
10136
+ * so the next attempt creates a fresh connection. Resets on success.
10137
+ */
10138
+ consecutiveStreamTimeouts = /* @__PURE__ */ new Map();
10139
+ static MAX_CONSECUTIVE_STREAM_TIMEOUTS = 3;
10133
10140
  /** BaichuanClientOptions to use when creating new sockets */
10134
10141
  clientOptions;
10135
10142
  /**
@@ -10284,14 +10291,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
10284
10291
  if (!xml) return;
10285
10292
  const channel = frame.header.channelId;
10286
10293
  const battery = this.parseBatteryInfoXml(xml, channel);
10287
- if (battery.batteryPercent !== void 0 || battery.chargeStatus !== void 0 || battery.adapterStatus !== void 0) {
10288
- this.dispatchSimpleEvent({
10289
- type: "battery",
10290
- channel,
10291
- timestamp: Date.now(),
10292
- battery
10293
- });
10294
+ if (battery.batteryPercent === void 0 && battery.chargeStatus === void 0 && battery.adapterStatus === void 0) {
10295
+ return;
10296
+ }
10297
+ const key = `${battery.batteryPercent ?? ""}|${battery.chargeStatus ?? ""}|${battery.adapterStatus ?? ""}`;
10298
+ if (this.lastBatteryPushKey.get(channel) === key) {
10299
+ return;
10294
10300
  }
10301
+ this.lastBatteryPushKey.set(channel, key);
10302
+ this.dispatchSimpleEvent({
10303
+ type: "battery",
10304
+ channel,
10305
+ timestamp: Date.now(),
10306
+ battery
10307
+ });
10295
10308
  } catch (e) {
10296
10309
  this.logger.debug?.(
10297
10310
  "[ReolinkBaichuanApi] Error parsing battery push",
@@ -10457,6 +10470,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
10457
10470
  deviceCapabilitiesCache = /* @__PURE__ */ new Map();
10458
10471
  static CAPABILITIES_CACHE_TTL_MS = 5 * 60 * 1e3;
10459
10472
  // 5 minutes
10473
+ /**
10474
+ * Dedupe key for battery push events (cmd_id 252), per channel.
10475
+ * Cameras emit BatteryInfoList frequently while streaming (every few
10476
+ * seconds). We only forward an event when the meaningful fields change
10477
+ * (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
10478
+ * consumers and the UI event log.
10479
+ */
10480
+ lastBatteryPushKey = /* @__PURE__ */ new Map();
10460
10481
  // ─────────────────────────────────────────────────────────────────────────────
10461
10482
  // SOCKET POOL CONSTANTS
10462
10483
  // ─────────────────────────────────────────────────────────────────────────────
@@ -15929,6 +15950,7 @@ ${stderr}`)
15929
15950
  `${ch}:${profile}:${variant}`,
15930
15951
  frame.header.msgNum
15931
15952
  );
15953
+ this.resetStreamTimeoutCounter(targetClient);
15932
15954
  return;
15933
15955
  } catch (error) {
15934
15956
  lastError = error;
@@ -15943,6 +15965,10 @@ ${stderr}`)
15943
15965
  }
15944
15966
  }
15945
15967
  }
15968
+ const isTimeout = lastError instanceof Error && lastError.message?.includes("timeout");
15969
+ if (isTimeout) {
15970
+ this.trackStreamTimeout(targetClient);
15971
+ }
15946
15972
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
15947
15973
  }
15948
15974
  /**
@@ -16402,6 +16428,18 @@ ${stderr}`)
16402
16428
  notifyD2cDisc() {
16403
16429
  const now = Date.now();
16404
16430
  this.lastD2cDiscAtMs = now;
16431
+ const streamingTags = Array.from(this.socketPool.keys()).filter(
16432
+ (tag) => tag.startsWith("streaming:")
16433
+ );
16434
+ if (streamingTags.length > 0) {
16435
+ this.logger?.log?.(
16436
+ `[D2C_DISC] Force-closing ${streamingTags.length} streaming socket(s): ${streamingTags.join(", ")}`
16437
+ );
16438
+ for (const tag of streamingTags) {
16439
+ this.forceClosePooledSocket(tag, this.logger).catch(() => {
16440
+ });
16441
+ }
16442
+ }
16405
16443
  const immediateCooldownUntil = now + _ReolinkBaichuanApi.D2C_DISC_IMMEDIATE_COOLDOWN_MS;
16406
16444
  const existing = this.socketPoolCooldowns.get(this.host);
16407
16445
  if (!existing || existing.cooldownUntil < immediateCooldownUntil) {
@@ -16434,6 +16472,43 @@ ${stderr}`)
16434
16472
  }
16435
16473
  }
16436
16474
  }
16475
+ /**
16476
+ * Find the socket pool tag for a given BaichuanClient instance.
16477
+ * Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
16478
+ */
16479
+ findSocketTagForClient(client) {
16480
+ for (const [tag, entry] of this.socketPool) {
16481
+ if (entry.client === client) return tag;
16482
+ }
16483
+ return void 0;
16484
+ }
16485
+ /**
16486
+ * Reset the consecutive stream-start timeout counter for a streaming socket.
16487
+ * Called on successful stream start.
16488
+ */
16489
+ resetStreamTimeoutCounter(client) {
16490
+ const tag = this.findSocketTagForClient(client);
16491
+ if (tag) this.consecutiveStreamTimeouts.delete(tag);
16492
+ }
16493
+ /**
16494
+ * Track a stream-start timeout on a streaming socket.
16495
+ * After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
16496
+ * socket so the next attempt creates a fresh connection.
16497
+ */
16498
+ trackStreamTimeout(client) {
16499
+ const tag = this.findSocketTagForClient(client);
16500
+ if (!tag || !tag.startsWith("streaming:")) return;
16501
+ const count = (this.consecutiveStreamTimeouts.get(tag) ?? 0) + 1;
16502
+ this.consecutiveStreamTimeouts.set(tag, count);
16503
+ if (count >= _ReolinkBaichuanApi.MAX_CONSECUTIVE_STREAM_TIMEOUTS) {
16504
+ this.logger?.warn?.(
16505
+ `[SocketPool] ${count} consecutive stream timeouts on tag=${tag}, force-closing socket`
16506
+ );
16507
+ this.consecutiveStreamTimeouts.delete(tag);
16508
+ this.forceClosePooledSocket(tag, this.logger).catch(() => {
16509
+ });
16510
+ }
16511
+ }
16437
16512
  /**
16438
16513
  * Best-effort sleeping inference for battery/BCUDP cameras.
16439
16514
  *
@@ -21248,16 +21323,16 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
21248
21323
  return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("socket hang up") || message.includes("TCP connection timeout") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
21249
21324
  }
21250
21325
  async function pingHost(host, timeoutMs = 3e3) {
21326
+ const { exec } = await import("child_process");
21327
+ const platform2 = process.platform;
21328
+ const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
21329
+ // macOS: -W is in milliseconds (Linux: seconds)
21330
+ `ping -c 1 -W ${timeoutMs} ${host}`
21331
+ ) : (
21332
+ // Linux/BSD-ish: -W is in seconds on most distros
21333
+ `ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
21334
+ );
21251
21335
  return new Promise((resolve) => {
21252
- const { exec } = __require("child_process");
21253
- const platform2 = process.platform;
21254
- const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
21255
- // macOS: -W is in milliseconds (Linux: seconds)
21256
- `ping -c 1 -W ${timeoutMs} ${host}`
21257
- ) : (
21258
- // Linux/BSD-ish: -W is in seconds on most distros
21259
- `ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
21260
- );
21261
21336
  exec(pingCmd, (error) => {
21262
21337
  resolve(!error);
21263
21338
  });
@@ -21803,4 +21878,4 @@ export {
21803
21878
  isTcpFailureThatShouldFallbackToUdp,
21804
21879
  autoDetectDeviceType
21805
21880
  };
21806
- //# sourceMappingURL=chunk-F2Y5U3YP.js.map
21881
+ //# sourceMappingURL=chunk-GKLOJJ34.js.map