@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
package/dist/cli/rtsp-server.cjs
CHANGED
|
@@ -17495,6 +17495,13 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17495
17495
|
* - "replay:XXX" - dedicated per replay session
|
|
17496
17496
|
*/
|
|
17497
17497
|
socketPool = /* @__PURE__ */ new Map();
|
|
17498
|
+
/**
|
|
17499
|
+
* Consecutive stream-start (cmdId=3) timeout counter per socket tag.
|
|
17500
|
+
* When a streaming socket has N consecutive timeouts, the socket is force-closed
|
|
17501
|
+
* so the next attempt creates a fresh connection. Resets on success.
|
|
17502
|
+
*/
|
|
17503
|
+
consecutiveStreamTimeouts = /* @__PURE__ */ new Map();
|
|
17504
|
+
static MAX_CONSECUTIVE_STREAM_TIMEOUTS = 3;
|
|
17498
17505
|
/** BaichuanClientOptions to use when creating new sockets */
|
|
17499
17506
|
clientOptions;
|
|
17500
17507
|
/**
|
|
@@ -17649,14 +17656,20 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17649
17656
|
if (!xml) return;
|
|
17650
17657
|
const channel = frame.header.channelId;
|
|
17651
17658
|
const battery = this.parseBatteryInfoXml(xml, channel);
|
|
17652
|
-
if (battery.batteryPercent
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
});
|
|
17659
|
+
if (battery.batteryPercent === void 0 && battery.chargeStatus === void 0 && battery.adapterStatus === void 0) {
|
|
17660
|
+
return;
|
|
17661
|
+
}
|
|
17662
|
+
const key = `${battery.batteryPercent ?? ""}|${battery.chargeStatus ?? ""}|${battery.adapterStatus ?? ""}`;
|
|
17663
|
+
if (this.lastBatteryPushKey.get(channel) === key) {
|
|
17664
|
+
return;
|
|
17659
17665
|
}
|
|
17666
|
+
this.lastBatteryPushKey.set(channel, key);
|
|
17667
|
+
this.dispatchSimpleEvent({
|
|
17668
|
+
type: "battery",
|
|
17669
|
+
channel,
|
|
17670
|
+
timestamp: Date.now(),
|
|
17671
|
+
battery
|
|
17672
|
+
});
|
|
17660
17673
|
} catch (e) {
|
|
17661
17674
|
this.logger.debug?.(
|
|
17662
17675
|
"[ReolinkBaichuanApi] Error parsing battery push",
|
|
@@ -17822,6 +17835,14 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
17822
17835
|
deviceCapabilitiesCache = /* @__PURE__ */ new Map();
|
|
17823
17836
|
static CAPABILITIES_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
17824
17837
|
// 5 minutes
|
|
17838
|
+
/**
|
|
17839
|
+
* Dedupe key for battery push events (cmd_id 252), per channel.
|
|
17840
|
+
* Cameras emit BatteryInfoList frequently while streaming (every few
|
|
17841
|
+
* seconds). We only forward an event when the meaningful fields change
|
|
17842
|
+
* (percent, chargeStatus, adapterStatus) to avoid flooding SSE/MQTT
|
|
17843
|
+
* consumers and the UI event log.
|
|
17844
|
+
*/
|
|
17845
|
+
lastBatteryPushKey = /* @__PURE__ */ new Map();
|
|
17825
17846
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17826
17847
|
// SOCKET POOL CONSTANTS
|
|
17827
17848
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -23294,6 +23315,7 @@ ${stderr}`)
|
|
|
23294
23315
|
`${ch}:${profile}:${variant}`,
|
|
23295
23316
|
frame.header.msgNum
|
|
23296
23317
|
);
|
|
23318
|
+
this.resetStreamTimeoutCounter(targetClient);
|
|
23297
23319
|
return;
|
|
23298
23320
|
} catch (error) {
|
|
23299
23321
|
lastError = error;
|
|
@@ -23308,6 +23330,10 @@ ${stderr}`)
|
|
|
23308
23330
|
}
|
|
23309
23331
|
}
|
|
23310
23332
|
}
|
|
23333
|
+
const isTimeout = lastError instanceof Error && lastError.message?.includes("timeout");
|
|
23334
|
+
if (isTimeout) {
|
|
23335
|
+
this.trackStreamTimeout(targetClient);
|
|
23336
|
+
}
|
|
23311
23337
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
23312
23338
|
}
|
|
23313
23339
|
/**
|
|
@@ -23767,6 +23793,18 @@ ${stderr}`)
|
|
|
23767
23793
|
notifyD2cDisc() {
|
|
23768
23794
|
const now = Date.now();
|
|
23769
23795
|
this.lastD2cDiscAtMs = now;
|
|
23796
|
+
const streamingTags = Array.from(this.socketPool.keys()).filter(
|
|
23797
|
+
(tag) => tag.startsWith("streaming:")
|
|
23798
|
+
);
|
|
23799
|
+
if (streamingTags.length > 0) {
|
|
23800
|
+
this.logger?.log?.(
|
|
23801
|
+
`[D2C_DISC] Force-closing ${streamingTags.length} streaming socket(s): ${streamingTags.join(", ")}`
|
|
23802
|
+
);
|
|
23803
|
+
for (const tag of streamingTags) {
|
|
23804
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
23805
|
+
});
|
|
23806
|
+
}
|
|
23807
|
+
}
|
|
23770
23808
|
const immediateCooldownUntil = now + _ReolinkBaichuanApi.D2C_DISC_IMMEDIATE_COOLDOWN_MS;
|
|
23771
23809
|
const existing = this.socketPoolCooldowns.get(this.host);
|
|
23772
23810
|
if (!existing || existing.cooldownUntil < immediateCooldownUntil) {
|
|
@@ -23799,6 +23837,43 @@ ${stderr}`)
|
|
|
23799
23837
|
}
|
|
23800
23838
|
}
|
|
23801
23839
|
}
|
|
23840
|
+
/**
|
|
23841
|
+
* Find the socket pool tag for a given BaichuanClient instance.
|
|
23842
|
+
* Returns undefined if the client is not in the pool (e.g. it's the general socket used directly).
|
|
23843
|
+
*/
|
|
23844
|
+
findSocketTagForClient(client) {
|
|
23845
|
+
for (const [tag, entry] of this.socketPool) {
|
|
23846
|
+
if (entry.client === client) return tag;
|
|
23847
|
+
}
|
|
23848
|
+
return void 0;
|
|
23849
|
+
}
|
|
23850
|
+
/**
|
|
23851
|
+
* Reset the consecutive stream-start timeout counter for a streaming socket.
|
|
23852
|
+
* Called on successful stream start.
|
|
23853
|
+
*/
|
|
23854
|
+
resetStreamTimeoutCounter(client) {
|
|
23855
|
+
const tag = this.findSocketTagForClient(client);
|
|
23856
|
+
if (tag) this.consecutiveStreamTimeouts.delete(tag);
|
|
23857
|
+
}
|
|
23858
|
+
/**
|
|
23859
|
+
* Track a stream-start timeout on a streaming socket.
|
|
23860
|
+
* After MAX_CONSECUTIVE_STREAM_TIMEOUTS consecutive timeouts, force-close the
|
|
23861
|
+
* socket so the next attempt creates a fresh connection.
|
|
23862
|
+
*/
|
|
23863
|
+
trackStreamTimeout(client) {
|
|
23864
|
+
const tag = this.findSocketTagForClient(client);
|
|
23865
|
+
if (!tag || !tag.startsWith("streaming:")) return;
|
|
23866
|
+
const count = (this.consecutiveStreamTimeouts.get(tag) ?? 0) + 1;
|
|
23867
|
+
this.consecutiveStreamTimeouts.set(tag, count);
|
|
23868
|
+
if (count >= _ReolinkBaichuanApi.MAX_CONSECUTIVE_STREAM_TIMEOUTS) {
|
|
23869
|
+
this.logger?.warn?.(
|
|
23870
|
+
`[SocketPool] ${count} consecutive stream timeouts on tag=${tag}, force-closing socket`
|
|
23871
|
+
);
|
|
23872
|
+
this.consecutiveStreamTimeouts.delete(tag);
|
|
23873
|
+
this.forceClosePooledSocket(tag, this.logger).catch(() => {
|
|
23874
|
+
});
|
|
23875
|
+
}
|
|
23876
|
+
}
|
|
23802
23877
|
/**
|
|
23803
23878
|
* Best-effort sleeping inference for battery/BCUDP cameras.
|
|
23804
23879
|
*
|
|
@@ -28036,16 +28111,16 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
|
28036
28111
|
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");
|
|
28037
28112
|
}
|
|
28038
28113
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
28114
|
+
const { exec } = await import("child_process");
|
|
28115
|
+
const platform2 = process.platform;
|
|
28116
|
+
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
28117
|
+
// macOS: -W is in milliseconds (Linux: seconds)
|
|
28118
|
+
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
28119
|
+
) : (
|
|
28120
|
+
// Linux/BSD-ish: -W is in seconds on most distros
|
|
28121
|
+
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
28122
|
+
);
|
|
28039
28123
|
return new Promise((resolve) => {
|
|
28040
|
-
const { exec } = require("child_process");
|
|
28041
|
-
const platform2 = process.platform;
|
|
28042
|
-
const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
|
|
28043
|
-
// macOS: -W is in milliseconds (Linux: seconds)
|
|
28044
|
-
`ping -c 1 -W ${timeoutMs} ${host}`
|
|
28045
|
-
) : (
|
|
28046
|
-
// Linux/BSD-ish: -W is in seconds on most distros
|
|
28047
|
-
`ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
|
|
28048
|
-
);
|
|
28049
28124
|
exec(pingCmd, (error) => {
|
|
28050
28125
|
resolve(!error);
|
|
28051
28126
|
});
|