@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.
- package/dist/{chunk-F2Y5U3YP.js → chunk-GKLOJJ34.js} +92 -17
- package/dist/chunk-GKLOJJ34.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +91 -16
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +153 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +63 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-F2Y5U3YP.js.map +0 -1
|
@@ -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
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
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-
|
|
21881
|
+
//# sourceMappingURL=chunk-GKLOJJ34.js.map
|