@apocaliss92/nodelink-js 0.5.1-beta.8 → 0.5.1

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.
@@ -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;
@@ -656,6 +657,10 @@ function sleep(ms) {
656
657
  return new Promise((r) => setTimeout(r, ms));
657
658
  }
658
659
  var P2P_RELAY_HOSTNAMES = [
660
+ // Master anycast routers — try first.
661
+ "p2pm-abr.reolink.com",
662
+ "p2pm-ali.reolink.com",
663
+ // Numbered regional relays.
659
664
  "p2p.reolink.com",
660
665
  "p2p1.reolink.com",
661
666
  "p2p2.reolink.com",
@@ -667,8 +672,29 @@ var P2P_RELAY_HOSTNAMES = [
667
672
  "p2p8.reolink.com",
668
673
  "p2p9.reolink.com",
669
674
  "p2p10.reolink.com",
670
- "p2p11.reolink.com"
675
+ "p2p11.reolink.com",
676
+ // China-region fallbacks (intentionally last).
677
+ "p2p.reolink.com.cn",
678
+ "p2p1.reolink.com.cn",
679
+ "p2p2.reolink.com.cn",
680
+ "p2p3.reolink.com.cn",
681
+ "p2p4.reolink.com.cn",
682
+ "p2p5.reolink.com.cn",
683
+ "p2p6.reolink.com.cn",
684
+ "p2p7.reolink.com.cn",
685
+ "p2p8.reolink.com.cn",
686
+ "p2p9.reolink.com.cn"
671
687
  ];
688
+ function isUnroutableForP2P(ip) {
689
+ if (!ip) return true;
690
+ if (ip === "0.0.0.0") return true;
691
+ if (ip.startsWith("127.")) return true;
692
+ if (ip.startsWith("10.")) return true;
693
+ if (ip.startsWith("192.168.")) return true;
694
+ if (/^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(ip)) return true;
695
+ if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true;
696
+ return false;
697
+ }
672
698
  var P2P_LOOKUP_PORT = 9999;
673
699
  var P2P_MAX_WAIT_MS = 15e3;
674
700
  var P2P_RESEND_WAIT_MS = 500;
@@ -835,19 +861,30 @@ var BcUdpStream = class extends EventEmitter {
835
861
  }
836
862
  async p2pUidLookup(sock, uid) {
837
863
  const resolved = [];
864
+ const sinkholed = [];
838
865
  for (const host of P2P_RELAY_HOSTNAMES) {
839
866
  try {
840
867
  const answers = await dns.lookup(host, { family: 4, all: true });
841
868
  for (const a of answers) {
842
- if (a.address && !resolved.includes(a.address))
843
- resolved.push(a.address);
869
+ if (!a.address) continue;
870
+ if (isUnroutableForP2P(a.address)) {
871
+ sinkholed.push({ host, ip: a.address });
872
+ continue;
873
+ }
874
+ if (!resolved.includes(a.address)) resolved.push(a.address);
844
875
  }
845
876
  } catch {
846
877
  }
847
878
  }
848
879
  if (resolved.length === 0) {
880
+ if (sinkholed.length > 0) {
881
+ const samples = sinkholed.slice(0, 3).map((s) => `${s.host} \u2192 ${s.ip}`).join(", ");
882
+ throw new Error(
883
+ `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.`
884
+ );
885
+ }
849
886
  throw new Error(
850
- "P2P UID lookup failed: no p2p.reolink.com addresses resolved"
887
+ "P2P UID lookup failed: no p2p.reolink.com addresses resolved (DNS failure)"
851
888
  );
852
889
  }
853
890
  const start = Date.now();
@@ -1181,7 +1218,8 @@ var BcUdpStream = class extends EventEmitter {
1181
1218
  throw new Error("Internal: discoveryUidLocal called for non-uid mode");
1182
1219
  const ports = [
1183
1220
  BCUDP_DISCOVERY_PORT_LOCAL_ANY,
1184
- BCUDP_DISCOVERY_PORT_LOCAL_UID
1221
+ BCUDP_DISCOVERY_PORT_LOCAL_UID,
1222
+ BCUDP_DISCOVERY_PORT_P2P_SCAN
1185
1223
  ];
1186
1224
  const broadcastHosts = ["255.255.255.255"];
1187
1225
  const ifaces = networkInterfaces();
@@ -23478,7 +23516,11 @@ async function discoverViaUdpDirect(host, options) {
23478
23516
  });
23479
23517
  socket.bind(() => {
23480
23518
  const localPort = socket.address().port;
23481
- const discoveryPorts = [BCUDP_DISCOVERY_PORT_LOCAL_ANY, BCUDP_DISCOVERY_PORT_LOCAL_UID];
23519
+ const discoveryPorts = [
23520
+ BCUDP_DISCOVERY_PORT_LOCAL_ANY,
23521
+ BCUDP_DISCOVERY_PORT_LOCAL_UID,
23522
+ BCUDP_DISCOVERY_PORT_P2P_SCAN
23523
+ ];
23482
23524
  for (const port of discoveryPorts) {
23483
23525
  try {
23484
23526
  const tid = Math.floor(Math.random() * 255) || 1;
@@ -23767,7 +23809,11 @@ async function discoverViaUdpBroadcast(options) {
23767
23809
  socket.bind(() => {
23768
23810
  socket.setBroadcast(true);
23769
23811
  const localPort = socket.address().port;
23770
- const discoveryPorts = [BCUDP_DISCOVERY_PORT_LOCAL_ANY, BCUDP_DISCOVERY_PORT_LOCAL_UID];
23812
+ const discoveryPorts = [
23813
+ BCUDP_DISCOVERY_PORT_LOCAL_ANY,
23814
+ BCUDP_DISCOVERY_PORT_LOCAL_UID,
23815
+ BCUDP_DISCOVERY_PORT_P2P_SCAN
23816
+ ];
23771
23817
  for (const port of discoveryPorts) {
23772
23818
  try {
23773
23819
  const tid = Math.floor(Math.random() * 255) || 1;
@@ -24262,19 +24308,95 @@ function isTcpFailureThatShouldFallbackToUdp(e) {
24262
24308
  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");
24263
24309
  }
24264
24310
  async function pingHost(host, timeoutMs = 3e3) {
24265
- const { exec } = await import("child_process");
24311
+ if (!host || typeof host !== "string") return false;
24266
24312
  const platform2 = process.platform;
24267
- const pingCmd = platform2 === "win32" ? `ping -n 1 -w ${timeoutMs} ${host}` : platform2 === "darwin" ? (
24268
- // macOS: -W is in milliseconds (Linux: seconds)
24269
- `ping -c 1 -W ${timeoutMs} ${host}`
24270
- ) : (
24271
- // Linux/BSD-ish: -W is in seconds on most distros
24272
- `ping -c 1 -W ${Math.max(1, Math.floor(timeoutMs / 1e3))} ${host}`
24273
- );
24313
+ const pingCandidates = platform2 === "win32" ? ["ping"] : platform2 === "darwin" ? ["/sbin/ping", "/usr/sbin/ping", "ping"] : ["/bin/ping", "/usr/bin/ping", "ping"];
24314
+ const pingArgs = (bin) => {
24315
+ void bin;
24316
+ if (platform2 === "win32") {
24317
+ return ["-n", "1", "-w", String(timeoutMs), host];
24318
+ }
24319
+ if (platform2 === "darwin") {
24320
+ return ["-c", "1", "-W", String(timeoutMs), host];
24321
+ }
24322
+ return ["-c", "1", "-W", String(Math.max(1, Math.floor(timeoutMs / 1e3))), host];
24323
+ };
24324
+ const { spawn: spawn3 } = await import("child_process");
24325
+ for (const bin of pingCandidates) {
24326
+ const ranOk = await new Promise((resolve) => {
24327
+ let settled = false;
24328
+ let child;
24329
+ try {
24330
+ child = spawn3(bin, pingArgs(bin), { stdio: "ignore" });
24331
+ } catch {
24332
+ resolve("spawn-failed");
24333
+ return;
24334
+ }
24335
+ const timer = setTimeout(() => {
24336
+ if (settled) return;
24337
+ settled = true;
24338
+ try {
24339
+ child?.kill("SIGKILL");
24340
+ } catch {
24341
+ }
24342
+ resolve(false);
24343
+ }, timeoutMs + 500);
24344
+ child.on("error", () => {
24345
+ if (settled) return;
24346
+ settled = true;
24347
+ clearTimeout(timer);
24348
+ resolve("spawn-failed");
24349
+ });
24350
+ child.on("exit", (code) => {
24351
+ if (settled) return;
24352
+ settled = true;
24353
+ clearTimeout(timer);
24354
+ resolve(code === 0);
24355
+ });
24356
+ });
24357
+ if (ranOk === true) return true;
24358
+ if (ranOk === "spawn-failed") continue;
24359
+ break;
24360
+ }
24361
+ for (const port of [9e3, 443, 80]) {
24362
+ if (await tcpReachabilityProbe(host, port, 800)) return true;
24363
+ }
24364
+ return false;
24365
+ }
24366
+ async function tcpReachabilityProbe(host, port, timeoutMs) {
24367
+ const net4 = await import("net");
24274
24368
  return new Promise((resolve) => {
24275
- exec(pingCmd, (error) => {
24276
- resolve(!error);
24369
+ let settled = false;
24370
+ const socket = new net4.Socket();
24371
+ const timer = setTimeout(() => {
24372
+ if (settled) return;
24373
+ settled = true;
24374
+ try {
24375
+ socket.destroy();
24376
+ } catch {
24377
+ }
24378
+ resolve(false);
24379
+ }, timeoutMs);
24380
+ const finish = (reachable) => {
24381
+ if (settled) return;
24382
+ settled = true;
24383
+ clearTimeout(timer);
24384
+ try {
24385
+ socket.destroy();
24386
+ } catch {
24387
+ }
24388
+ resolve(reachable);
24389
+ };
24390
+ socket.once("connect", () => finish(true));
24391
+ socket.once("error", (err) => {
24392
+ if (err?.code === "ECONNREFUSED") finish(true);
24393
+ else finish(false);
24277
24394
  });
24395
+ try {
24396
+ socket.connect(port, host);
24397
+ } catch {
24398
+ finish(false);
24399
+ }
24278
24400
  });
24279
24401
  }
24280
24402
  function createBaichuanApi(inputs, transport) {
@@ -24347,6 +24469,7 @@ async function autoDetectDeviceType(inputs) {
24347
24469
  const sleepMs2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24348
24470
  const shouldRetryTcp = (e) => {
24349
24471
  const msg = fmtErr(e);
24472
+ if (msg.includes("ECONNREFUSED")) return false;
24350
24473
  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
24474
  };
24352
24475
  const shouldRetryUdp = (e) => {
@@ -24401,6 +24524,11 @@ async function autoDetectDeviceType(inputs) {
24401
24524
  }
24402
24525
  };
24403
24526
  const effectiveUid = normalizeUid(uid);
24527
+ 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);
24528
+ speculativeUidPromise.then(
24529
+ () => void 0,
24530
+ () => void 0
24531
+ );
24404
24532
  logger?.log?.(`[AutoDetect] Pinging ${host}...`);
24405
24533
  const isReachable = await pingHost(host);
24406
24534
  if (!isReachable) {
@@ -24414,19 +24542,11 @@ async function autoDetectDeviceType(inputs) {
24414
24542
  logger?.log?.(
24415
24543
  `[AutoDetect] Forced mode=udp, skipping TCP and starting UDP discovery/login...`
24416
24544
  );
24417
- let normalizedUid = effectiveUid;
24545
+ let normalizedUid = await speculativeUidPromise;
24418
24546
  if (!normalizedUid) {
24419
- logger?.log?.(
24420
- `[AutoDetect] UID not provided; attempting UDP discovery for UID...`
24547
+ throw new Error(
24548
+ `Forced UDP autodetect requires UID (or successful UDP UID discovery), but none was provided/discovered (ip=${host}).`
24421
24549
  );
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
24550
  }
24431
24551
  const methodsToTry = inputs.udpDiscoveryMethod ? [inputs.udpDiscoveryMethod] : ["local-direct", "local-broadcast", "remote", "relay", "map"];
24432
24552
  return await runUdpMethodsParallel(
@@ -24673,26 +24793,15 @@ async function autoDetectDeviceType(inputs) {
24673
24793
  throw tcpError;
24674
24794
  }
24675
24795
  logger?.log?.(`[AutoDetect] TCP failed, trying UDP...`);
24676
- let normalizedUid = effectiveUid;
24796
+ let normalizedUid = await speculativeUidPromise;
24677
24797
  if (!normalizedUid) {
24678
24798
  logger?.log?.(
24679
- `[AutoDetect] UID not provided; attempting UDP broadcast discovery for UID...`
24799
+ `[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.`
24800
+ );
24801
+ } else if (effectiveUid === void 0) {
24802
+ logger?.log?.(
24803
+ `[AutoDetect] UID resolved via concurrent broadcast discovery: ${normalizedUid}`
24680
24804
  );
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
24805
  }
24697
24806
  try {
24698
24807
  const detectOverUdpApi = async (udpApi, udpDiscoveryMethod) => {
@@ -24790,6 +24899,7 @@ export {
24790
24899
  encodeHeader,
24791
24900
  decodeHeader,
24792
24901
  BaichuanFrameParser,
24902
+ isUnroutableForP2P,
24793
24903
  BcUdpStream,
24794
24904
  asLogger,
24795
24905
  createNullLogger,
@@ -24854,6 +24964,7 @@ export {
24854
24964
  normalizeUid,
24855
24965
  maskUid,
24856
24966
  isTcpFailureThatShouldFallbackToUdp,
24967
+ tcpReachabilityProbe,
24857
24968
  autoDetectDeviceType
24858
24969
  };
24859
- //# sourceMappingURL=chunk-27IU7NXS.js.map
24970
+ //# sourceMappingURL=chunk-OJQLZETO.js.map