@apocaliss92/nodelink-js 0.5.1-beta.9 → 0.5.2
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/BaichuanVideoStream-OCLOM452.js +7 -0
- package/dist/{DiagnosticsTools-7BIWJDZS.js → DiagnosticsTools-K4MF2VXZ.js} +3 -3
- package/dist/{chunk-27IU7NXS.js → chunk-7HSTETZR.js} +556 -144
- package/dist/chunk-7HSTETZR.js.map +1 -0
- package/dist/{chunk-GVWJGQPT.js → chunk-MZUSWKF3.js} +5 -1
- package/dist/chunk-MZUSWKF3.js.map +1 -0
- package/dist/{chunk-VOPEOB4H.js → chunk-XDVBNZGR.js} +2 -2
- package/dist/cli/rtsp-server.cjs +548 -138
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -3
- package/dist/index.cjs +585 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -1
- package/dist/index.d.ts +59 -0
- package/dist/index.js +37 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/BaichuanVideoStream-NTIGPHYJ.js +0 -7
- package/dist/chunk-27IU7NXS.js.map +0 -1
- package/dist/chunk-GVWJGQPT.js.map +0 -1
- /package/dist/{BaichuanVideoStream-NTIGPHYJ.js.map → BaichuanVideoStream-OCLOM452.js.map} +0 -0
- /package/dist/{DiagnosticsTools-7BIWJDZS.js.map → DiagnosticsTools-K4MF2VXZ.js.map} +0 -0
- /package/dist/{chunk-VOPEOB4H.js.map → chunk-XDVBNZGR.js.map} +0 -0
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runAllDiagnosticsConsecutively,
|
|
31
31
|
runMultifocalDiagnosticsConsecutively,
|
|
32
32
|
xmlEscape
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-XDVBNZGR.js";
|
|
34
34
|
import {
|
|
35
35
|
BC_CLASS_FILE_DOWNLOAD,
|
|
36
36
|
BC_CLASS_LEGACY,
|
|
@@ -178,7 +178,7 @@ import {
|
|
|
178
178
|
splitAnnexBToNalPayloads2,
|
|
179
179
|
talkTraceLog,
|
|
180
180
|
traceLog
|
|
181
|
-
} from "./chunk-
|
|
181
|
+
} from "./chunk-MZUSWKF3.js";
|
|
182
182
|
|
|
183
183
|
// src/protocol/framing.ts
|
|
184
184
|
function encodeHeader(h) {
|
|
@@ -292,6 +292,7 @@ var BCUDP_DEFAULT_MTU = 1350;
|
|
|
292
292
|
var BCUDP_DATA_HEADER_SIZE = 20;
|
|
293
293
|
var BCUDP_DISCOVERY_PORT_LOCAL_ANY = 2015;
|
|
294
294
|
var BCUDP_DISCOVERY_PORT_LOCAL_UID = 2018;
|
|
295
|
+
var BCUDP_DISCOVERY_PORT_P2P_SCAN = 9999;
|
|
295
296
|
|
|
296
297
|
// src/bcudp/crc.ts
|
|
297
298
|
var table;
|
|
@@ -618,6 +619,143 @@ function parseD2cHb(xml) {
|
|
|
618
619
|
return { cid: Number(cid), did: Number(did) };
|
|
619
620
|
}
|
|
620
621
|
|
|
622
|
+
// src/cloud/server-binding.ts
|
|
623
|
+
var REOLINK_API_V2_BASE = "https://apis.reolink.com/v2";
|
|
624
|
+
var POSITIVE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
625
|
+
var NEGATIVE_TTL_MS = 30 * 1e3;
|
|
626
|
+
var cache = /* @__PURE__ */ new Map();
|
|
627
|
+
function readCache(uid, now) {
|
|
628
|
+
const e = cache.get(uid);
|
|
629
|
+
if (!e) return void 0;
|
|
630
|
+
if (now >= e.expires) {
|
|
631
|
+
cache.delete(uid);
|
|
632
|
+
return void 0;
|
|
633
|
+
}
|
|
634
|
+
return e;
|
|
635
|
+
}
|
|
636
|
+
async function getServerBinding(uid, options = {}) {
|
|
637
|
+
if (!uid || typeof uid !== "string") return void 0;
|
|
638
|
+
const now = Date.now();
|
|
639
|
+
const cached = readCache(uid, now);
|
|
640
|
+
if (cached?.kind === "ok") return cached.response;
|
|
641
|
+
if (cached?.kind === "err") return void 0;
|
|
642
|
+
const language = options.language ?? "en";
|
|
643
|
+
const baseUrl = options.baseUrl ?? REOLINK_API_V2_BASE;
|
|
644
|
+
const timeoutMs = options.timeoutMs ?? 4e3;
|
|
645
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
646
|
+
const logger = options.logger;
|
|
647
|
+
if (typeof fetchImpl !== "function") {
|
|
648
|
+
logger?.debug?.(
|
|
649
|
+
`[server-binding] global fetch unavailable; skipping cloud lookup`
|
|
650
|
+
);
|
|
651
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
const url = `${baseUrl}/devices/${encodeURIComponent(uid)}/server-binding?language=${encodeURIComponent(language)}`;
|
|
655
|
+
const controller = new AbortController();
|
|
656
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
657
|
+
try {
|
|
658
|
+
const res = await fetchImpl(url, {
|
|
659
|
+
method: "GET",
|
|
660
|
+
signal: controller.signal,
|
|
661
|
+
headers: { Accept: "application/json" }
|
|
662
|
+
});
|
|
663
|
+
if (!res.ok) {
|
|
664
|
+
logger?.debug?.(
|
|
665
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText}`
|
|
666
|
+
);
|
|
667
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
668
|
+
return void 0;
|
|
669
|
+
}
|
|
670
|
+
const json = await res.json();
|
|
671
|
+
const parsed = parseServerBindingResponse(json);
|
|
672
|
+
if (!parsed) {
|
|
673
|
+
logger?.debug?.(
|
|
674
|
+
`[server-binding] ${uid}: response shape did not match expectations`
|
|
675
|
+
);
|
|
676
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
677
|
+
return void 0;
|
|
678
|
+
}
|
|
679
|
+
cache.set(uid, {
|
|
680
|
+
kind: "ok",
|
|
681
|
+
response: parsed,
|
|
682
|
+
expires: now + POSITIVE_TTL_MS
|
|
683
|
+
});
|
|
684
|
+
const pick = parsed.availableZones.find(
|
|
685
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
686
|
+
);
|
|
687
|
+
const hint = pick?.services.p2p?.server ?? parsed.availableZones[0]?.services.p2p?.server;
|
|
688
|
+
logger?.log?.(
|
|
689
|
+
`[server-binding] ${uid}: ${parsed.availableZones.length} zone(s)${hint ? `, p2p hint=${hint}` : ""}`
|
|
690
|
+
);
|
|
691
|
+
return parsed;
|
|
692
|
+
} catch (e) {
|
|
693
|
+
logger?.debug?.(
|
|
694
|
+
`[server-binding] ${uid}: ${e?.message ?? String(e)}`
|
|
695
|
+
);
|
|
696
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
697
|
+
return void 0;
|
|
698
|
+
} finally {
|
|
699
|
+
clearTimeout(timer);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function pickP2pHostFromBinding(response) {
|
|
703
|
+
if (!response) return void 0;
|
|
704
|
+
const zones = response.availableZones;
|
|
705
|
+
if (!zones || zones.length === 0) return void 0;
|
|
706
|
+
const active = zones.find(
|
|
707
|
+
(z) => z.status === "active" && z.services.p2p?.server
|
|
708
|
+
);
|
|
709
|
+
if (active?.services.p2p?.server) return active.services.p2p.server;
|
|
710
|
+
const def = zones.find(
|
|
711
|
+
(z) => z.status === "default" && z.services.p2p?.server
|
|
712
|
+
);
|
|
713
|
+
if (def?.services.p2p?.server) return def.services.p2p.server;
|
|
714
|
+
const any = zones.find((z) => z.services.p2p?.server);
|
|
715
|
+
return any?.services.p2p?.server;
|
|
716
|
+
}
|
|
717
|
+
function isString(v) {
|
|
718
|
+
return typeof v === "string";
|
|
719
|
+
}
|
|
720
|
+
function parseServerBindingResponse(raw) {
|
|
721
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
722
|
+
const rawZones = raw.availableZones;
|
|
723
|
+
if (!Array.isArray(rawZones)) return void 0;
|
|
724
|
+
const zones = [];
|
|
725
|
+
for (const r of rawZones) {
|
|
726
|
+
if (!r || typeof r !== "object") continue;
|
|
727
|
+
const rec = r;
|
|
728
|
+
const id = rec.id;
|
|
729
|
+
const name = rec.name;
|
|
730
|
+
const status = rec.status;
|
|
731
|
+
if (!isString(id) || !isString(name) || !isString(status)) continue;
|
|
732
|
+
const servicesRaw = rec.services;
|
|
733
|
+
const services = {};
|
|
734
|
+
if (servicesRaw && typeof servicesRaw === "object") {
|
|
735
|
+
const s = servicesRaw;
|
|
736
|
+
for (const key of ["p2p", "cloud", "roms_ota", "alarm_push"]) {
|
|
737
|
+
const v = s[key];
|
|
738
|
+
if (v && typeof v === "object") {
|
|
739
|
+
const server = v.server;
|
|
740
|
+
if (isString(server) && server.length > 0) {
|
|
741
|
+
services[key] = { server };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const locationsRaw = rec.locations;
|
|
747
|
+
const locations = Array.isArray(locationsRaw) && locationsRaw.every(isString) ? locationsRaw : void 0;
|
|
748
|
+
zones.push({
|
|
749
|
+
id,
|
|
750
|
+
name,
|
|
751
|
+
status,
|
|
752
|
+
services,
|
|
753
|
+
...locations ? { locations } : {}
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return { availableZones: zones };
|
|
757
|
+
}
|
|
758
|
+
|
|
621
759
|
// src/bcudp/BcUdpStream.ts
|
|
622
760
|
var AckLatency = class {
|
|
623
761
|
currentValues = [];
|
|
@@ -656,6 +794,10 @@ function sleep(ms) {
|
|
|
656
794
|
return new Promise((r) => setTimeout(r, ms));
|
|
657
795
|
}
|
|
658
796
|
var P2P_RELAY_HOSTNAMES = [
|
|
797
|
+
// Master anycast routers — try first.
|
|
798
|
+
"p2pm-abr.reolink.com",
|
|
799
|
+
"p2pm-ali.reolink.com",
|
|
800
|
+
// Numbered regional relays.
|
|
659
801
|
"p2p.reolink.com",
|
|
660
802
|
"p2p1.reolink.com",
|
|
661
803
|
"p2p2.reolink.com",
|
|
@@ -667,13 +809,44 @@ var P2P_RELAY_HOSTNAMES = [
|
|
|
667
809
|
"p2p8.reolink.com",
|
|
668
810
|
"p2p9.reolink.com",
|
|
669
811
|
"p2p10.reolink.com",
|
|
670
|
-
"p2p11.reolink.com"
|
|
812
|
+
"p2p11.reolink.com",
|
|
813
|
+
// China-region fallbacks (intentionally last).
|
|
814
|
+
"p2p.reolink.com.cn",
|
|
815
|
+
"p2p1.reolink.com.cn",
|
|
816
|
+
"p2p2.reolink.com.cn",
|
|
817
|
+
"p2p3.reolink.com.cn",
|
|
818
|
+
"p2p4.reolink.com.cn",
|
|
819
|
+
"p2p5.reolink.com.cn",
|
|
820
|
+
"p2p6.reolink.com.cn",
|
|
821
|
+
"p2p7.reolink.com.cn",
|
|
822
|
+
"p2p8.reolink.com.cn",
|
|
823
|
+
"p2p9.reolink.com.cn"
|
|
671
824
|
];
|
|
825
|
+
function isUnroutableForP2P(ip) {
|
|
826
|
+
if (!ip) return true;
|
|
827
|
+
if (ip === "0.0.0.0") return true;
|
|
828
|
+
if (ip.startsWith("127.")) return true;
|
|
829
|
+
if (ip.startsWith("10.")) return true;
|
|
830
|
+
if (ip.startsWith("192.168.")) return true;
|
|
831
|
+
if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip)) return true;
|
|
832
|
+
if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true;
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
672
835
|
var P2P_LOOKUP_PORT = 9999;
|
|
673
836
|
var P2P_MAX_WAIT_MS = 15e3;
|
|
674
837
|
var P2P_RESEND_WAIT_MS = 500;
|
|
675
838
|
var BcUdpStream = class extends EventEmitter {
|
|
676
839
|
opts;
|
|
840
|
+
/**
|
|
841
|
+
* Optional info-level logger for diagnostic milestones — set via
|
|
842
|
+
* {@link BcUdpStream.setLogger} by `BaichuanClient` so the lib's
|
|
843
|
+
* standard logger sink sees BCUDP / P2P progress (DNS resolutions,
|
|
844
|
+
* outgoing UDP probes, timeouts with elapsed times) without the user
|
|
845
|
+
* having to opt into the per-packet `debug` event firehose. Kept
|
|
846
|
+
* separate from `emit('debug', ...)` because that channel is intended
|
|
847
|
+
* for the per-packet debug trace and is gated by debugOptions.
|
|
848
|
+
*/
|
|
849
|
+
discoveryLogger;
|
|
677
850
|
sock;
|
|
678
851
|
remote;
|
|
679
852
|
mtu;
|
|
@@ -717,6 +890,17 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
717
890
|
this.mtu = BCUDP_DEFAULT_MTU;
|
|
718
891
|
}
|
|
719
892
|
/** True if the underlying UDP socket is open and the remote peer is known. */
|
|
893
|
+
/**
|
|
894
|
+
* Attach an info-level logger for high-signal diagnostic milestones
|
|
895
|
+
* (DNS resolution, outgoing UDP probe sends, P2P UID lookup wins/losses,
|
|
896
|
+
* BCUDP local discovery timeouts). The lib's `BaichuanClient` calls
|
|
897
|
+
* this immediately after constructing the stream so consumers get
|
|
898
|
+
* actionable progress logs without enabling the per-packet debug trace.
|
|
899
|
+
* Safe to call repeatedly; only the most recent logger is used.
|
|
900
|
+
*/
|
|
901
|
+
setLogger(logger) {
|
|
902
|
+
this.discoveryLogger = logger;
|
|
903
|
+
}
|
|
720
904
|
isConnected() {
|
|
721
905
|
return !!this.sock && !!this.remote && this.cameraId != null;
|
|
722
906
|
}
|
|
@@ -834,27 +1018,89 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
834
1018
|
this.remote = { host: connected.rhost, port: connected.rport };
|
|
835
1019
|
}
|
|
836
1020
|
async p2pUidLookup(sock, uid) {
|
|
837
|
-
const
|
|
1021
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[P2P] ${msg}`);
|
|
1022
|
+
const shortUid = uid.length > 7 ? `${uid.slice(0, 5)}\u2026${uid.slice(-2)}` : uid;
|
|
1023
|
+
const t0 = Date.now();
|
|
1024
|
+
const hostnamesToTry = [];
|
|
1025
|
+
const binding = await getServerBinding(uid, {
|
|
1026
|
+
...this.discoveryLogger ? { logger: this.discoveryLogger } : {}
|
|
1027
|
+
}).catch(() => void 0);
|
|
1028
|
+
const hintedHost = pickP2pHostFromBinding(binding);
|
|
1029
|
+
if (hintedHost) {
|
|
1030
|
+
hostnamesToTry.push(hintedHost);
|
|
1031
|
+
log(
|
|
1032
|
+
`UID=${shortUid} cloud server-binding \u2192 hint=${hintedHost} (will try first)`
|
|
1033
|
+
);
|
|
1034
|
+
} else {
|
|
1035
|
+
log(
|
|
1036
|
+
`UID=${shortUid} cloud server-binding \u2192 no hint (apis.reolink.com unreachable / no zone match) \u2192 sweeping ${P2P_RELAY_HOSTNAMES.length} fallback hostnames`
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
838
1039
|
for (const host of P2P_RELAY_HOSTNAMES) {
|
|
1040
|
+
if (!hostnamesToTry.includes(host)) hostnamesToTry.push(host);
|
|
1041
|
+
}
|
|
1042
|
+
const resolved = [];
|
|
1043
|
+
const sinkholed = [];
|
|
1044
|
+
for (const host of hostnamesToTry) {
|
|
839
1045
|
try {
|
|
840
1046
|
const answers = await dns.lookup(host, { family: 4, all: true });
|
|
1047
|
+
let publicCount = 0;
|
|
1048
|
+
let sinkCount = 0;
|
|
841
1049
|
for (const a of answers) {
|
|
842
|
-
if (
|
|
1050
|
+
if (!a.address) continue;
|
|
1051
|
+
if (isUnroutableForP2P(a.address)) {
|
|
1052
|
+
sinkholed.push({ host, ip: a.address });
|
|
1053
|
+
sinkCount++;
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (!resolved.includes(a.address)) {
|
|
843
1057
|
resolved.push(a.address);
|
|
1058
|
+
publicCount++;
|
|
1059
|
+
}
|
|
844
1060
|
}
|
|
845
|
-
|
|
1061
|
+
if (sinkCount > 0 && publicCount === 0) {
|
|
1062
|
+
log(
|
|
1063
|
+
`DNS ${host} \u2192 sinkhole (${sinkholed[sinkholed.length - 1]?.ip}) \u2014 DNS filter / /etc/hosts override`
|
|
1064
|
+
);
|
|
1065
|
+
} else if (publicCount > 0) {
|
|
1066
|
+
if (host === hintedHost) {
|
|
1067
|
+
log(`DNS ${host} \u2192 ${answers.find((a) => !isUnroutableForP2P(a.address))?.address} \u2713`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
} catch (e) {
|
|
1071
|
+
log(`DNS ${host} \u2192 ENOTFOUND/timeout (${e?.code ?? "?"})`);
|
|
1072
|
+
}
|
|
1073
|
+
if (hintedHost && host === hintedHost && resolved.length > 0 && sinkholed.length === 0) {
|
|
1074
|
+
break;
|
|
846
1075
|
}
|
|
847
1076
|
}
|
|
848
1077
|
if (resolved.length === 0) {
|
|
1078
|
+
if (sinkholed.length > 0) {
|
|
1079
|
+
const samples = sinkholed.slice(0, 3).map((s) => `${s.host} \u2192 ${s.ip}`).join(", ");
|
|
1080
|
+
throw new Error(
|
|
1081
|
+
`P2P UID lookup failed: DNS resolves Reolink P2P hostnames to non-routable IPs (${samples}). This is almost certainly an /etc/hosts rewrite or a DNS filter (Pi-hole, AdGuard, NextDNS) blocking *.reolink.com. Battery cameras cannot connect without P2P \u2014 whitelist *.reolink.com (at least p2p*.reolink.com on UDP/9999) or remove the override. Verify with \`getent hosts p2p.reolink.com\` inside your container \u2014 if it differs from \`nslookup p2p.reolink.com\`, the offender is /etc/hosts.`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
849
1084
|
throw new Error(
|
|
850
|
-
"P2P UID lookup failed: no p2p.reolink.com addresses resolved"
|
|
1085
|
+
"P2P UID lookup failed: no p2p.reolink.com addresses resolved (DNS failure)"
|
|
851
1086
|
);
|
|
852
1087
|
}
|
|
1088
|
+
log(
|
|
1089
|
+
`Resolved ${resolved.length} P2P relay IP(s) (${resolved.slice(0, 3).join(", ")}${resolved.length > 3 ? "\u2026" : ""}). Sending C2M_Q probes (3s budget each, ${P2P_MAX_WAIT_MS}ms total)`
|
|
1090
|
+
);
|
|
853
1091
|
const start = Date.now();
|
|
854
1092
|
let lastErr;
|
|
1093
|
+
let attemptsMade = 0;
|
|
855
1094
|
for (const ip of resolved) {
|
|
856
1095
|
const remaining = P2P_MAX_WAIT_MS - (Date.now() - start);
|
|
857
|
-
if (remaining <= 0)
|
|
1096
|
+
if (remaining <= 0) {
|
|
1097
|
+
log(
|
|
1098
|
+
`Aborting after ${attemptsMade} attempt(s) \u2014 total budget ${P2P_MAX_WAIT_MS}ms exhausted`
|
|
1099
|
+
);
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
attemptsMade++;
|
|
1103
|
+
const probeStart = Date.now();
|
|
858
1104
|
try {
|
|
859
1105
|
const res = await this.p2pUidLookupOne(
|
|
860
1106
|
sock,
|
|
@@ -862,11 +1108,20 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
862
1108
|
{ host: ip, port: P2P_LOOKUP_PORT },
|
|
863
1109
|
Math.min(remaining, 3e3)
|
|
864
1110
|
);
|
|
1111
|
+
log(
|
|
1112
|
+
`${ip}:${P2P_LOOKUP_PORT} replied in ${Date.now() - probeStart}ms \u2713 (total ${Date.now() - t0}ms)`
|
|
1113
|
+
);
|
|
865
1114
|
return res;
|
|
866
1115
|
} catch (e) {
|
|
1116
|
+
const ms = Date.now() - probeStart;
|
|
1117
|
+
const msg = e?.message ?? String(e);
|
|
1118
|
+
log(`${ip}:${P2P_LOOKUP_PORT} no reply after ${ms}ms (${msg})`);
|
|
867
1119
|
lastErr = e instanceof Error ? e : new Error(String(e));
|
|
868
1120
|
}
|
|
869
1121
|
}
|
|
1122
|
+
log(
|
|
1123
|
+
`Exhausted all ${attemptsMade} relay candidate(s) after ${Date.now() - t0}ms \u2014 UID lookup failed`
|
|
1124
|
+
);
|
|
870
1125
|
throw lastErr ?? new Error("P2P UID lookup failed");
|
|
871
1126
|
}
|
|
872
1127
|
async p2pUidLookupOne(sock, uid, dest, timeoutMs) {
|
|
@@ -1181,7 +1436,8 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1181
1436
|
throw new Error("Internal: discoveryUidLocal called for non-uid mode");
|
|
1182
1437
|
const ports = [
|
|
1183
1438
|
BCUDP_DISCOVERY_PORT_LOCAL_ANY,
|
|
1184
|
-
BCUDP_DISCOVERY_PORT_LOCAL_UID
|
|
1439
|
+
BCUDP_DISCOVERY_PORT_LOCAL_UID,
|
|
1440
|
+
BCUDP_DISCOVERY_PORT_P2P_SCAN
|
|
1185
1441
|
];
|
|
1186
1442
|
const broadcastHosts = ["255.255.255.255"];
|
|
1187
1443
|
const ifaces = networkInterfaces();
|
|
@@ -1204,13 +1460,23 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1204
1460
|
const directHost = (this.opts.directHost ?? "").trim();
|
|
1205
1461
|
const localMode = opts?.localMode ?? "local-broadcast";
|
|
1206
1462
|
const directFirstWindowMs = localMode === "local-direct" && directHost ? 3e3 : 0;
|
|
1207
|
-
const discoveryTimeout =
|
|
1463
|
+
const discoveryTimeout = typeof this.opts.localDiscoveryTimeoutMs === "number" && this.opts.localDiscoveryTimeoutMs > 0 ? this.opts.localDiscoveryTimeoutMs : 15e3;
|
|
1208
1464
|
const retryInterval = 500;
|
|
1209
1465
|
const startMs = Date.now();
|
|
1210
1466
|
sock.setBroadcast(true);
|
|
1211
1467
|
const addr = sock.address();
|
|
1212
1468
|
const localPort = typeof addr === "string" ? 0 : addr.port;
|
|
1213
1469
|
const cid = Math.floor(Math.random() * 2147483647) | 0 || 82e3;
|
|
1470
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[BCUDP] ${msg}`);
|
|
1471
|
+
const shortUid = this.opts.uid.length > 7 ? `${this.opts.uid.slice(0, 5)}\u2026${this.opts.uid.slice(-2)}` : this.opts.uid;
|
|
1472
|
+
log(
|
|
1473
|
+
`local discovery: mode=${localMode} uid=${shortUid} ports=[${ports.join(", ")}] broadcasts=[${broadcastHosts.join(", ")}]${directHost ? ` direct=${directHost}` : ""} localBindPort=${localPort} timeout=${discoveryTimeout}ms`
|
|
1474
|
+
);
|
|
1475
|
+
let bytesSent = 0;
|
|
1476
|
+
let pktsRecv = 0;
|
|
1477
|
+
sock.on("message", () => {
|
|
1478
|
+
pktsRecv++;
|
|
1479
|
+
});
|
|
1214
1480
|
const xml = buildC2dC({
|
|
1215
1481
|
uid: this.opts.uid,
|
|
1216
1482
|
clientPort: localPort,
|
|
@@ -1221,6 +1487,9 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1221
1487
|
const timeout = setTimeout(() => {
|
|
1222
1488
|
if (retryTimer) clearInterval(retryTimer);
|
|
1223
1489
|
sock.off("message", onMsg);
|
|
1490
|
+
log(
|
|
1491
|
+
`local discovery timeout after ${discoveryTimeout}ms \u2014 sent=${bytesSent}B replies=${pktsRecv} (camera likely sleeping / off-LAN / firewall dropping replies)`
|
|
1492
|
+
);
|
|
1224
1493
|
reject(
|
|
1225
1494
|
new Error(
|
|
1226
1495
|
`BCUDP discovery timeout after ${discoveryTimeout}ms (camera may be sleeping or unreachable)`
|
|
@@ -1414,6 +1683,7 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1414
1683
|
for (const port of ports) {
|
|
1415
1684
|
try {
|
|
1416
1685
|
sock.send(packet, port, host);
|
|
1686
|
+
bytesSent += packet.length;
|
|
1417
1687
|
retryCount++;
|
|
1418
1688
|
this.emit("debug", "discovery_send", { retryCount, host, port });
|
|
1419
1689
|
} catch {
|
|
@@ -2950,6 +3220,7 @@ var BaichuanClient = class _BaichuanClient extends EventEmitter2 {
|
|
|
2950
3220
|
sock.on("debug", (event, data) => {
|
|
2951
3221
|
this.logDebug(`udp_${event}`, data);
|
|
2952
3222
|
});
|
|
3223
|
+
sock.setLogger(this.logger);
|
|
2953
3224
|
await sock.connect();
|
|
2954
3225
|
const shortUid = this.opts.uid ? this.opts.uid.substring(0, 5) : "";
|
|
2955
3226
|
const udpDiscoveryMethod = this.opts.udpDiscoveryMethod ?? "local-direct";
|
|
@@ -13466,7 +13737,7 @@ var ReolinkBaichuanApi = class _ReolinkBaichuanApi {
|
|
|
13466
13737
|
return;
|
|
13467
13738
|
}
|
|
13468
13739
|
entry.startInFlight = (async () => {
|
|
13469
|
-
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-
|
|
13740
|
+
const { BaichuanVideoStream: BaichuanVideoStream2 } = await import("./BaichuanVideoStream-OCLOM452.js");
|
|
13470
13741
|
const sessionKey = `live:object-detections:ch${entry.channel}:${entry.profile}`;
|
|
13471
13742
|
const dedicated = await this.createDedicatedSession(sessionKey);
|
|
13472
13743
|
const stream = new BaichuanVideoStream2({
|
|
@@ -20196,7 +20467,7 @@ ${xml}`
|
|
|
20196
20467
|
* @returns Test results for all stream types and profiles
|
|
20197
20468
|
*/
|
|
20198
20469
|
async testChannelStreams(channel, logger) {
|
|
20199
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
20470
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-K4MF2VXZ.js");
|
|
20200
20471
|
return await testChannelStreams({
|
|
20201
20472
|
api: this,
|
|
20202
20473
|
channel: this.normalizeChannel(channel),
|
|
@@ -20212,7 +20483,7 @@ ${xml}`
|
|
|
20212
20483
|
* @returns Complete diagnostics for all channels and streams
|
|
20213
20484
|
*/
|
|
20214
20485
|
async collectMultifocalDiagnostics(logger) {
|
|
20215
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
20486
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-K4MF2VXZ.js");
|
|
20216
20487
|
return await collectMultifocalDiagnostics({
|
|
20217
20488
|
api: this,
|
|
20218
20489
|
logger
|
|
@@ -23478,7 +23749,11 @@ async function discoverViaUdpDirect(host, options) {
|
|
|
23478
23749
|
});
|
|
23479
23750
|
socket.bind(() => {
|
|
23480
23751
|
const localPort = socket.address().port;
|
|
23481
|
-
const discoveryPorts = [
|
|
23752
|
+
const discoveryPorts = [
|
|
23753
|
+
BCUDP_DISCOVERY_PORT_LOCAL_ANY,
|
|
23754
|
+
BCUDP_DISCOVERY_PORT_LOCAL_UID,
|
|
23755
|
+
BCUDP_DISCOVERY_PORT_P2P_SCAN
|
|
23756
|
+
];
|
|
23482
23757
|
for (const port of discoveryPorts) {
|
|
23483
23758
|
try {
|
|
23484
23759
|
const tid = Math.floor(Math.random() * 255) || 1;
|
|
@@ -23767,7 +24042,11 @@ async function discoverViaUdpBroadcast(options) {
|
|
|
23767
24042
|
socket.bind(() => {
|
|
23768
24043
|
socket.setBroadcast(true);
|
|
23769
24044
|
const localPort = socket.address().port;
|
|
23770
|
-
const discoveryPorts = [
|
|
24045
|
+
const discoveryPorts = [
|
|
24046
|
+
BCUDP_DISCOVERY_PORT_LOCAL_ANY,
|
|
24047
|
+
BCUDP_DISCOVERY_PORT_LOCAL_UID,
|
|
24048
|
+
BCUDP_DISCOVERY_PORT_P2P_SCAN
|
|
24049
|
+
];
|
|
23771
24050
|
for (const port of discoveryPorts) {
|
|
23772
24051
|
try {
|
|
23773
24052
|
const tid = Math.floor(Math.random() * 255) || 1;
|
|
@@ -24259,22 +24538,102 @@ async function discoverUidForHost(host, logger) {
|
|
|
24259
24538
|
function isTcpFailureThatShouldFallbackToUdp(e) {
|
|
24260
24539
|
const message = e?.message || e?.toString?.() || "";
|
|
24261
24540
|
if (typeof message !== "string") return false;
|
|
24262
|
-
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("
|
|
24541
|
+
return message.includes("ECONNREFUSED") || message.includes("ETIMEDOUT") || message.includes("EHOSTDOWN") || message.includes("EHOSTUNREACH") || message.includes("ENETUNREACH") || message.includes("ENETDOWN") || message.includes("socket hang up") || message.includes("TCP connection timeout") || // Autodetect's own hard deadline on the TCP login attempt — see
|
|
24542
|
+
// `withTcpDeadline` in `autoDetectDeviceType`. Without this entry the
|
|
24543
|
+
// catch block would rethrow the deadline error instead of awaiting
|
|
24544
|
+
// the speculative UDP race.
|
|
24545
|
+
message.includes("TCP login deadline exceeded") || message.includes("Baichuan socket closed") || message.includes("timeout waiting for nonce") || message.includes("expected encryption info") || message.includes("ECONNRESET") || message.includes("EPIPE");
|
|
24263
24546
|
}
|
|
24264
24547
|
async function pingHost(host, timeoutMs = 3e3) {
|
|
24265
|
-
|
|
24548
|
+
if (!host || typeof host !== "string") return false;
|
|
24266
24549
|
const platform2 = process.platform;
|
|
24267
|
-
const
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24550
|
+
const pingCandidates = platform2 === "win32" ? ["ping"] : platform2 === "darwin" ? ["/sbin/ping", "/usr/sbin/ping", "ping"] : ["/bin/ping", "/usr/bin/ping", "ping"];
|
|
24551
|
+
const pingArgs = (bin) => {
|
|
24552
|
+
void bin;
|
|
24553
|
+
if (platform2 === "win32") {
|
|
24554
|
+
return ["-n", "1", "-w", String(timeoutMs), host];
|
|
24555
|
+
}
|
|
24556
|
+
if (platform2 === "darwin") {
|
|
24557
|
+
return ["-c", "1", "-W", String(timeoutMs), host];
|
|
24558
|
+
}
|
|
24559
|
+
return ["-c", "1", "-W", String(Math.max(1, Math.floor(timeoutMs / 1e3))), host];
|
|
24560
|
+
};
|
|
24561
|
+
const { spawn: spawn3 } = await import("child_process");
|
|
24562
|
+
for (const bin of pingCandidates) {
|
|
24563
|
+
const ranOk = await new Promise((resolve) => {
|
|
24564
|
+
let settled = false;
|
|
24565
|
+
let child;
|
|
24566
|
+
try {
|
|
24567
|
+
child = spawn3(bin, pingArgs(bin), { stdio: "ignore" });
|
|
24568
|
+
} catch {
|
|
24569
|
+
resolve("spawn-failed");
|
|
24570
|
+
return;
|
|
24571
|
+
}
|
|
24572
|
+
const timer = setTimeout(() => {
|
|
24573
|
+
if (settled) return;
|
|
24574
|
+
settled = true;
|
|
24575
|
+
try {
|
|
24576
|
+
child?.kill("SIGKILL");
|
|
24577
|
+
} catch {
|
|
24578
|
+
}
|
|
24579
|
+
resolve(false);
|
|
24580
|
+
}, timeoutMs + 500);
|
|
24581
|
+
child.on("error", () => {
|
|
24582
|
+
if (settled) return;
|
|
24583
|
+
settled = true;
|
|
24584
|
+
clearTimeout(timer);
|
|
24585
|
+
resolve("spawn-failed");
|
|
24586
|
+
});
|
|
24587
|
+
child.on("exit", (code) => {
|
|
24588
|
+
if (settled) return;
|
|
24589
|
+
settled = true;
|
|
24590
|
+
clearTimeout(timer);
|
|
24591
|
+
resolve(code === 0);
|
|
24592
|
+
});
|
|
24593
|
+
});
|
|
24594
|
+
if (ranOk === true) return true;
|
|
24595
|
+
if (ranOk === "spawn-failed") continue;
|
|
24596
|
+
break;
|
|
24597
|
+
}
|
|
24598
|
+
for (const port of [9e3, 443, 80]) {
|
|
24599
|
+
if (await tcpReachabilityProbe(host, port, 800)) return true;
|
|
24600
|
+
}
|
|
24601
|
+
return false;
|
|
24602
|
+
}
|
|
24603
|
+
async function tcpReachabilityProbe(host, port, timeoutMs) {
|
|
24604
|
+
const net4 = await import("net");
|
|
24274
24605
|
return new Promise((resolve) => {
|
|
24275
|
-
|
|
24276
|
-
|
|
24606
|
+
let settled = false;
|
|
24607
|
+
const socket = new net4.Socket();
|
|
24608
|
+
const timer = setTimeout(() => {
|
|
24609
|
+
if (settled) return;
|
|
24610
|
+
settled = true;
|
|
24611
|
+
try {
|
|
24612
|
+
socket.destroy();
|
|
24613
|
+
} catch {
|
|
24614
|
+
}
|
|
24615
|
+
resolve(false);
|
|
24616
|
+
}, timeoutMs);
|
|
24617
|
+
const finish = (reachable) => {
|
|
24618
|
+
if (settled) return;
|
|
24619
|
+
settled = true;
|
|
24620
|
+
clearTimeout(timer);
|
|
24621
|
+
try {
|
|
24622
|
+
socket.destroy();
|
|
24623
|
+
} catch {
|
|
24624
|
+
}
|
|
24625
|
+
resolve(reachable);
|
|
24626
|
+
};
|
|
24627
|
+
socket.once("connect", () => finish(true));
|
|
24628
|
+
socket.once("error", (err) => {
|
|
24629
|
+
if (err?.code === "ECONNREFUSED") finish(true);
|
|
24630
|
+
else finish(false);
|
|
24277
24631
|
});
|
|
24632
|
+
try {
|
|
24633
|
+
socket.connect(port, host);
|
|
24634
|
+
} catch {
|
|
24635
|
+
finish(false);
|
|
24636
|
+
}
|
|
24278
24637
|
});
|
|
24279
24638
|
}
|
|
24280
24639
|
function createBaichuanApi(inputs, transport) {
|
|
@@ -24331,6 +24690,7 @@ function attachErrorHandler(api, transport, inputs) {
|
|
|
24331
24690
|
}
|
|
24332
24691
|
async function autoDetectDeviceType(inputs) {
|
|
24333
24692
|
const { host, uid, logger } = inputs;
|
|
24693
|
+
const autodetectStartedAt = Date.now();
|
|
24334
24694
|
const mode = inputs.mode ?? "auto";
|
|
24335
24695
|
const maxRetriesRaw = inputs.maxRetries;
|
|
24336
24696
|
const maxRetries = Math.max(
|
|
@@ -24347,8 +24707,31 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24347
24707
|
const sleepMs2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
24348
24708
|
const shouldRetryTcp = (e) => {
|
|
24349
24709
|
const msg = fmtErr(e);
|
|
24710
|
+
if (msg.includes("ECONNREFUSED") || msg.includes("EHOSTDOWN") || msg.includes("EHOSTUNREACH") || msg.includes("ENETUNREACH") || msg.includes("ENETDOWN")) {
|
|
24711
|
+
return false;
|
|
24712
|
+
}
|
|
24350
24713
|
return isTcpFailureThatShouldFallbackToUdp(e) || msg.includes("timeout waiting for nonce") || msg.includes("expected encryption info") || msg.includes("Baichuan socket closed") || msg.includes("ECONNRESET") || msg.includes("EPIPE");
|
|
24351
24714
|
};
|
|
24715
|
+
const tcpDeadlineMs = typeof inputs.tcpConnectTimeoutMs === "number" && Number.isFinite(inputs.tcpConnectTimeoutMs) && inputs.tcpConnectTimeoutMs > 0 ? inputs.tcpConnectTimeoutMs : 8e3;
|
|
24716
|
+
const withTcpDeadline = async (op) => {
|
|
24717
|
+
let timer;
|
|
24718
|
+
const deadline = new Promise((_, reject) => {
|
|
24719
|
+
timer = setTimeout(
|
|
24720
|
+
() => reject(
|
|
24721
|
+
new Error(
|
|
24722
|
+
`TCP login deadline exceeded (${tcpDeadlineMs}ms) \u2014 host unreachable`
|
|
24723
|
+
)
|
|
24724
|
+
),
|
|
24725
|
+
tcpDeadlineMs
|
|
24726
|
+
);
|
|
24727
|
+
timer.unref?.();
|
|
24728
|
+
});
|
|
24729
|
+
try {
|
|
24730
|
+
return await Promise.race([op, deadline]);
|
|
24731
|
+
} finally {
|
|
24732
|
+
if (timer) clearTimeout(timer);
|
|
24733
|
+
}
|
|
24734
|
+
};
|
|
24352
24735
|
const shouldRetryUdp = (e) => {
|
|
24353
24736
|
const msg = fmtErr(e);
|
|
24354
24737
|
return msg.includes("Not running") || msg.includes("Baichuan UDP stream closed") || msg.includes("Baichuan socket closed") || msg.includes("ETIMEDOUT") || msg.toLowerCase().includes("timeout");
|
|
@@ -24401,6 +24784,11 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24401
24784
|
}
|
|
24402
24785
|
};
|
|
24403
24786
|
const effectiveUid = normalizeUid(uid);
|
|
24787
|
+
const speculativeUidPromise = effectiveUid !== void 0 ? Promise.resolve(effectiveUid) : mode === "tcp" ? Promise.resolve(void 0) : discoverUidForHost(host, logger).then((d) => normalizeUid(d) ?? void 0).catch(() => void 0);
|
|
24788
|
+
speculativeUidPromise.then(
|
|
24789
|
+
() => void 0,
|
|
24790
|
+
() => void 0
|
|
24791
|
+
);
|
|
24404
24792
|
logger?.log?.(`[AutoDetect] Pinging ${host}...`);
|
|
24405
24793
|
const isReachable = await pingHost(host);
|
|
24406
24794
|
if (!isReachable) {
|
|
@@ -24414,19 +24802,11 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24414
24802
|
logger?.log?.(
|
|
24415
24803
|
`[AutoDetect] Forced mode=udp, skipping TCP and starting UDP discovery/login...`
|
|
24416
24804
|
);
|
|
24417
|
-
let normalizedUid =
|
|
24805
|
+
let normalizedUid = await speculativeUidPromise;
|
|
24418
24806
|
if (!normalizedUid) {
|
|
24419
|
-
|
|
24420
|
-
`
|
|
24807
|
+
throw new Error(
|
|
24808
|
+
`Forced UDP autodetect requires UID (or successful UDP UID discovery), but none was provided/discovered (ip=${host}).`
|
|
24421
24809
|
);
|
|
24422
|
-
const discovered = await discoverUidForHost(host, logger);
|
|
24423
|
-
const normalizedDiscovered = normalizeUid(discovered);
|
|
24424
|
-
if (!normalizedDiscovered) {
|
|
24425
|
-
throw new Error(
|
|
24426
|
-
`Forced UDP autodetect requires UID (or successful UDP UID discovery), but none was provided/discovered (ip=${host}).`
|
|
24427
|
-
);
|
|
24428
|
-
}
|
|
24429
|
-
normalizedUid = normalizedDiscovered;
|
|
24430
24810
|
}
|
|
24431
24811
|
const methodsToTry = inputs.udpDiscoveryMethod ? [inputs.udpDiscoveryMethod] : ["local-direct", "local-broadcast", "remote", "relay", "map"];
|
|
24432
24812
|
return await runUdpMethodsParallel(
|
|
@@ -24504,6 +24884,127 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24504
24884
|
"Forced UDP autodetect failed for all methods."
|
|
24505
24885
|
);
|
|
24506
24886
|
}
|
|
24887
|
+
const detectOverUdpApi = async (udpApi, udpDiscoveryMethod, resolvedUid) => {
|
|
24888
|
+
const [deviceInfo, capabilities, hostNetworkInfo] = await Promise.all([
|
|
24889
|
+
udpApi.getInfo(),
|
|
24890
|
+
udpApi.getDeviceCapabilities(),
|
|
24891
|
+
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
24892
|
+
]);
|
|
24893
|
+
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
24894
|
+
const model = deviceInfo.type?.trim();
|
|
24895
|
+
const normalizedModel = model ? model.trim() : void 0;
|
|
24896
|
+
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
24897
|
+
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
24898
|
+
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
24899
|
+
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
24900
|
+
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
24901
|
+
udpApi.setIdleDisconnect(hasBattery);
|
|
24902
|
+
if (isMultifocal) {
|
|
24903
|
+
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
24904
|
+
logger?.log?.(
|
|
24905
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
24906
|
+
);
|
|
24907
|
+
return {
|
|
24908
|
+
type: "multifocal",
|
|
24909
|
+
transport: "udp",
|
|
24910
|
+
uid: resolvedUid,
|
|
24911
|
+
udpDiscoveryMethod,
|
|
24912
|
+
deviceInfo,
|
|
24913
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
24914
|
+
channelNum,
|
|
24915
|
+
hasBattery,
|
|
24916
|
+
api: udpApi
|
|
24917
|
+
};
|
|
24918
|
+
}
|
|
24919
|
+
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
24920
|
+
logger?.log?.(
|
|
24921
|
+
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
24922
|
+
);
|
|
24923
|
+
return {
|
|
24924
|
+
type: deviceType,
|
|
24925
|
+
transport: "udp",
|
|
24926
|
+
uid: resolvedUid,
|
|
24927
|
+
udpDiscoveryMethod,
|
|
24928
|
+
deviceInfo,
|
|
24929
|
+
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
24930
|
+
channelNum: 1,
|
|
24931
|
+
hasBattery,
|
|
24932
|
+
api: udpApi
|
|
24933
|
+
};
|
|
24934
|
+
};
|
|
24935
|
+
const udpRaceAbort = new AbortController();
|
|
24936
|
+
const speculativeUdpRace = mode === "auto" ? (async () => {
|
|
24937
|
+
const resolvedUid = await speculativeUidPromise;
|
|
24938
|
+
const viableMethods = selectViableUdpMethods(Boolean(resolvedUid));
|
|
24939
|
+
return await runUdpMethodsParallel(
|
|
24940
|
+
viableMethods,
|
|
24941
|
+
async (m, isInnerAborted) => {
|
|
24942
|
+
const isAborted = () => udpRaceAbort.signal.aborted || isInnerAborted();
|
|
24943
|
+
if (isAborted()) {
|
|
24944
|
+
throw new Error(
|
|
24945
|
+
`UDP(${m}) speculative race aborted before start`
|
|
24946
|
+
);
|
|
24947
|
+
}
|
|
24948
|
+
logger?.log?.(
|
|
24949
|
+
`[AutoDetect] (race) Trying UDP discovery method: ${m}...`
|
|
24950
|
+
);
|
|
24951
|
+
const udpApi = await withRetries(
|
|
24952
|
+
`UDP(${m})`,
|
|
24953
|
+
maxRetries,
|
|
24954
|
+
async (attempt) => {
|
|
24955
|
+
const apiInputs = {
|
|
24956
|
+
...inputs,
|
|
24957
|
+
udpDiscoveryMethod: m
|
|
24958
|
+
};
|
|
24959
|
+
if (resolvedUid) apiInputs.uid = resolvedUid;
|
|
24960
|
+
const api = createBaichuanApi(apiInputs, "udp");
|
|
24961
|
+
try {
|
|
24962
|
+
await api.login();
|
|
24963
|
+
return api;
|
|
24964
|
+
} catch (e) {
|
|
24965
|
+
try {
|
|
24966
|
+
await api.close({
|
|
24967
|
+
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
24968
|
+
});
|
|
24969
|
+
} catch {
|
|
24970
|
+
}
|
|
24971
|
+
throw e;
|
|
24972
|
+
}
|
|
24973
|
+
},
|
|
24974
|
+
shouldRetryUdp,
|
|
24975
|
+
isAborted
|
|
24976
|
+
);
|
|
24977
|
+
if (isAborted()) {
|
|
24978
|
+
try {
|
|
24979
|
+
await udpApi.close({
|
|
24980
|
+
reason: "autodetect:udp_aborted_after_tcp_won"
|
|
24981
|
+
});
|
|
24982
|
+
} catch {
|
|
24983
|
+
}
|
|
24984
|
+
throw new Error(
|
|
24985
|
+
`UDP(${m}) speculative race aborted after login`
|
|
24986
|
+
);
|
|
24987
|
+
}
|
|
24988
|
+
return detectOverUdpApi(udpApi, m, resolvedUid ?? "");
|
|
24989
|
+
},
|
|
24990
|
+
"Speculative UDP race failed for all methods."
|
|
24991
|
+
);
|
|
24992
|
+
})() : void 0;
|
|
24993
|
+
speculativeUdpRace?.then(
|
|
24994
|
+
(udpResult) => {
|
|
24995
|
+
if (udpRaceAbort.signal.aborted && udpResult?.api) {
|
|
24996
|
+
udpResult.api.close({ reason: "autodetect:tcp_won_race" }).catch(() => void 0);
|
|
24997
|
+
}
|
|
24998
|
+
},
|
|
24999
|
+
() => void 0
|
|
25000
|
+
);
|
|
25001
|
+
const _tcpWin = (result) => {
|
|
25002
|
+
udpRaceAbort.abort();
|
|
25003
|
+
logger?.log?.(
|
|
25004
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via TCP \u2014 type=${result.type} model=${result.deviceInfo?.type ?? "?"} channels=${result.channelNum}`
|
|
25005
|
+
);
|
|
25006
|
+
return result;
|
|
25007
|
+
};
|
|
24507
25008
|
let tcpApi;
|
|
24508
25009
|
try {
|
|
24509
25010
|
logger?.log?.(`[AutoDetect] Trying TCP connection to ${host}...`);
|
|
@@ -24513,7 +25014,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24513
25014
|
async (attempt) => {
|
|
24514
25015
|
const api2 = createBaichuanApi(inputs, "tcp");
|
|
24515
25016
|
try {
|
|
24516
|
-
await api2.login();
|
|
25017
|
+
await withTcpDeadline(api2.login());
|
|
24517
25018
|
return api2;
|
|
24518
25019
|
} catch (e) {
|
|
24519
25020
|
try {
|
|
@@ -24625,7 +25126,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24625
25126
|
logger?.log?.(
|
|
24626
25127
|
`[AutoDetect] Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum})`
|
|
24627
25128
|
);
|
|
24628
|
-
return {
|
|
25129
|
+
return _tcpWin({
|
|
24629
25130
|
type: "multifocal",
|
|
24630
25131
|
transport: "tcp",
|
|
24631
25132
|
uid: effectiveUid || uid || "",
|
|
@@ -24633,13 +25134,13 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24633
25134
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
24634
25135
|
channelNum: effectiveChannelNum,
|
|
24635
25136
|
api
|
|
24636
|
-
};
|
|
25137
|
+
});
|
|
24637
25138
|
}
|
|
24638
25139
|
if (effectiveChannelNum > 1) {
|
|
24639
25140
|
logger?.log?.(
|
|
24640
25141
|
`[AutoDetect] Detected NVR (${effectiveChannelNum} channels)`
|
|
24641
25142
|
);
|
|
24642
|
-
return {
|
|
25143
|
+
return _tcpWin({
|
|
24643
25144
|
type: "nvr",
|
|
24644
25145
|
transport: "tcp",
|
|
24645
25146
|
uid: effectiveUid || uid || "",
|
|
@@ -24647,10 +25148,10 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24647
25148
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
24648
25149
|
channelNum: effectiveChannelNum,
|
|
24649
25150
|
api
|
|
24650
|
-
};
|
|
25151
|
+
});
|
|
24651
25152
|
}
|
|
24652
25153
|
logger?.log?.(`[AutoDetect] Detected regular camera (single channel)`);
|
|
24653
|
-
return {
|
|
25154
|
+
return _tcpWin({
|
|
24654
25155
|
type: "camera",
|
|
24655
25156
|
transport: "tcp",
|
|
24656
25157
|
uid: effectiveUid || uid || "",
|
|
@@ -24658,7 +25159,7 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24658
25159
|
// ...(hostNetworkInfo ? { hostNetworkInfo } : {}),
|
|
24659
25160
|
channelNum: 1,
|
|
24660
25161
|
api
|
|
24661
|
-
};
|
|
25162
|
+
});
|
|
24662
25163
|
} catch (tcpError) {
|
|
24663
25164
|
if (mode === "tcp") {
|
|
24664
25165
|
throw tcpError;
|
|
@@ -24673,111 +25174,20 @@ async function autoDetectDeviceType(inputs) {
|
|
|
24673
25174
|
throw tcpError;
|
|
24674
25175
|
}
|
|
24675
25176
|
logger?.log?.(`[AutoDetect] TCP failed, trying UDP...`);
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
`[AutoDetect] UID not provided; attempting UDP broadcast discovery for UID...`
|
|
25177
|
+
if (!speculativeUdpRace) {
|
|
25178
|
+
throw new Error(
|
|
25179
|
+
`AutoDetect internal: speculative UDP race missing in mode=${mode}`
|
|
24680
25180
|
);
|
|
24681
|
-
const discovered = await discoverUidForHost(host, logger);
|
|
24682
|
-
if (discovered) {
|
|
24683
|
-
const normalizedDiscovered = normalizeUid(discovered);
|
|
24684
|
-
if (normalizedDiscovered) {
|
|
24685
|
-
normalizedUid = normalizedDiscovered;
|
|
24686
|
-
logger?.log?.(
|
|
24687
|
-
`[AutoDetect] UID discovered via broadcast: ${normalizedUid}`
|
|
24688
|
-
);
|
|
24689
|
-
}
|
|
24690
|
-
}
|
|
24691
|
-
if (!normalizedUid) {
|
|
24692
|
-
logger?.log?.(
|
|
24693
|
-
`[AutoDetect] UID discovery failed; only local-direct can run without a UID. If the camera is sleeping or on a different subnet, supply its UID to enable BCUDP P2P fallback (remote/relay/map) which can wake it via Reolink's servers.`
|
|
24694
|
-
);
|
|
24695
|
-
}
|
|
24696
25181
|
}
|
|
24697
25182
|
try {
|
|
24698
|
-
const
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
udpApi.getDeviceCapabilities(),
|
|
24702
|
-
udpApi.getNetworkInfo(void 0, { timeoutMs: 1200 }).catch(() => void 0)
|
|
24703
|
-
]);
|
|
24704
|
-
const channelNum = capabilities?.support?.channelNum ?? 1;
|
|
24705
|
-
const model = deviceInfo.type?.trim();
|
|
24706
|
-
const normalizedModel = model ? model.trim() : void 0;
|
|
24707
|
-
const isMultifocalByModel = normalizedModel ? isDualLenseModel(normalizedModel) : false;
|
|
24708
|
-
const channelNumValue = typeof channelNum === "string" ? Number.parseInt(channelNum, 10) : channelNum;
|
|
24709
|
-
const hasDualLensChannelCount = (channelNumValue === 2 || channelNumValue === 3) && Number.isFinite(channelNumValue);
|
|
24710
|
-
const isMultifocal = isMultifocalByModel || hasDualLensChannelCount;
|
|
24711
|
-
const hasBattery = capabilities?.capabilities?.hasBattery === true;
|
|
24712
|
-
udpApi.setIdleDisconnect(hasBattery);
|
|
24713
|
-
if (isMultifocal) {
|
|
24714
|
-
const detectionMethod = isMultifocalByModel ? "model match" : "channelNum fallback";
|
|
24715
|
-
logger?.log?.(
|
|
24716
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected multi-focal device (${detectionMethod}: model=${normalizedModel ?? "unknown"}, channelNum=${channelNum}, hasBattery=${hasBattery}).`
|
|
24717
|
-
);
|
|
24718
|
-
return {
|
|
24719
|
-
type: "multifocal",
|
|
24720
|
-
transport: "udp",
|
|
24721
|
-
uid: normalizedUid ?? "",
|
|
24722
|
-
udpDiscoveryMethod,
|
|
24723
|
-
deviceInfo,
|
|
24724
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
24725
|
-
channelNum,
|
|
24726
|
-
hasBattery,
|
|
24727
|
-
api: udpApi
|
|
24728
|
-
};
|
|
24729
|
-
}
|
|
24730
|
-
const deviceType = hasBattery ? "battery-cam" : "udp-camera";
|
|
24731
|
-
logger?.log?.(
|
|
24732
|
-
`[AutoDetect] UDP (${udpDiscoveryMethod}) connection successful. Detected ${deviceType} (hasBattery=${hasBattery}, model=${normalizedModel ?? "unknown"}).`
|
|
24733
|
-
);
|
|
24734
|
-
return {
|
|
24735
|
-
type: deviceType,
|
|
24736
|
-
transport: "udp",
|
|
24737
|
-
uid: normalizedUid ?? "",
|
|
24738
|
-
udpDiscoveryMethod,
|
|
24739
|
-
deviceInfo,
|
|
24740
|
-
...hostNetworkInfo ? { hostNetworkInfo } : {},
|
|
24741
|
-
channelNum: 1,
|
|
24742
|
-
hasBattery,
|
|
24743
|
-
api: udpApi
|
|
24744
|
-
};
|
|
24745
|
-
};
|
|
24746
|
-
const viableMethods = selectViableUdpMethods(Boolean(normalizedUid));
|
|
24747
|
-
return await runUdpMethodsParallel(
|
|
24748
|
-
viableMethods,
|
|
24749
|
-
async (m, isAborted) => {
|
|
24750
|
-
logger?.log?.(`[AutoDetect] Trying UDP discovery method: ${m}...`);
|
|
24751
|
-
const udpApi = await withRetries(
|
|
24752
|
-
`UDP(${m})`,
|
|
24753
|
-
maxRetries,
|
|
24754
|
-
async (attempt) => {
|
|
24755
|
-
const apiInputs = { ...inputs, udpDiscoveryMethod: m };
|
|
24756
|
-
if (normalizedUid) apiInputs.uid = normalizedUid;
|
|
24757
|
-
const api = createBaichuanApi(apiInputs, "udp");
|
|
24758
|
-
try {
|
|
24759
|
-
await api.login();
|
|
24760
|
-
return api;
|
|
24761
|
-
} catch (e) {
|
|
24762
|
-
try {
|
|
24763
|
-
await api.close({
|
|
24764
|
-
reason: `autodetect:udp_failed:${m}:attempt_${attempt}`
|
|
24765
|
-
});
|
|
24766
|
-
} catch {
|
|
24767
|
-
}
|
|
24768
|
-
throw e;
|
|
24769
|
-
}
|
|
24770
|
-
},
|
|
24771
|
-
shouldRetryUdp,
|
|
24772
|
-
isAborted
|
|
24773
|
-
);
|
|
24774
|
-
return detectOverUdpApi(udpApi, m);
|
|
24775
|
-
},
|
|
24776
|
-
"UDP discovery failed for all methods."
|
|
25183
|
+
const udpResult = await speculativeUdpRace;
|
|
25184
|
+
logger?.log?.(
|
|
25185
|
+
`[AutoDetect] DONE in ${Date.now() - autodetectStartedAt}ms via UDP \u2014 type=${udpResult.type} method=${udpResult.udpDiscoveryMethod ?? "n/a"} model=${udpResult.deviceInfo?.type ?? "?"} channels=${udpResult.channelNum}`
|
|
24777
25186
|
);
|
|
25187
|
+
return udpResult;
|
|
24778
25188
|
} catch (udpError) {
|
|
24779
25189
|
logger?.log?.(
|
|
24780
|
-
`[AutoDetect]
|
|
25190
|
+
`[AutoDetect] FAILED after ${Date.now() - autodetectStartedAt}ms \u2014 neither TCP nor UDP could reach the camera. TCP: ${tcpError?.message ?? tcpError}. UDP: ${udpError?.message ?? udpError}`
|
|
24781
25191
|
);
|
|
24782
25192
|
throw new Error(
|
|
24783
25193
|
`Failed to connect via both TCP and UDP. TCP: ${tcpError?.message || tcpError}, UDP: ${udpError?.message || udpError}`
|
|
@@ -24790,6 +25200,7 @@ export {
|
|
|
24790
25200
|
encodeHeader,
|
|
24791
25201
|
decodeHeader,
|
|
24792
25202
|
BaichuanFrameParser,
|
|
25203
|
+
isUnroutableForP2P,
|
|
24793
25204
|
BcUdpStream,
|
|
24794
25205
|
asLogger,
|
|
24795
25206
|
createNullLogger,
|
|
@@ -24854,6 +25265,7 @@ export {
|
|
|
24854
25265
|
normalizeUid,
|
|
24855
25266
|
maskUid,
|
|
24856
25267
|
isTcpFailureThatShouldFallbackToUdp,
|
|
25268
|
+
tcpReachabilityProbe,
|
|
24857
25269
|
autoDetectDeviceType
|
|
24858
25270
|
};
|
|
24859
|
-
//# sourceMappingURL=chunk-
|
|
25271
|
+
//# sourceMappingURL=chunk-7HSTETZR.js.map
|