@apocaliss92/nodelink-js 0.6.2 → 0.6.4
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-D4TKRGUP.js → chunk-UL34MR4L.js} +153 -28
- package/dist/chunk-UL34MR4L.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +144 -27
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +1 -1
- package/dist/index.cjs +155 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -2
- package/dist/index.d.ts +59 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-D4TKRGUP.js.map +0 -1
package/dist/cli/rtsp-server.cjs
CHANGED
|
@@ -11340,6 +11340,7 @@ function readCache(uid, now) {
|
|
|
11340
11340
|
}
|
|
11341
11341
|
async function getServerBinding(uid, options = {}) {
|
|
11342
11342
|
if (!uid || typeof uid !== "string") return void 0;
|
|
11343
|
+
uid = uid.toUpperCase();
|
|
11343
11344
|
const now = Date.now();
|
|
11344
11345
|
const cached = readCache(uid, now);
|
|
11345
11346
|
if (cached?.kind === "ok") return cached.response;
|
|
@@ -11382,8 +11383,14 @@ async function getServerBinding(uid, options = {}) {
|
|
|
11382
11383
|
headers: { Accept: "application/json" }
|
|
11383
11384
|
});
|
|
11384
11385
|
if (!res.ok) {
|
|
11386
|
+
let bodyPreview;
|
|
11387
|
+
try {
|
|
11388
|
+
const text = await res.text();
|
|
11389
|
+
bodyPreview = text.slice(0, 512).replace(/\s+/g, " ").trim();
|
|
11390
|
+
} catch {
|
|
11391
|
+
}
|
|
11385
11392
|
logger?.log?.(
|
|
11386
|
-
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText} from ${url}`
|
|
11393
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText} from ${url}` + (bodyPreview ? ` \u2014 body=${bodyPreview}` : "")
|
|
11387
11394
|
);
|
|
11388
11395
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
11389
11396
|
return void 0;
|
|
@@ -11499,6 +11506,68 @@ function parseServerBindingResponse(raw) {
|
|
|
11499
11506
|
}
|
|
11500
11507
|
|
|
11501
11508
|
// src/bcudp/BcUdpStream.ts
|
|
11509
|
+
async function probeEgressForHost(destHost, destPort) {
|
|
11510
|
+
return await new Promise((resolve, reject) => {
|
|
11511
|
+
const probe = import_node_dgram.default.createSocket("udp4");
|
|
11512
|
+
let settled = false;
|
|
11513
|
+
const finish = (err, out) => {
|
|
11514
|
+
if (settled) return;
|
|
11515
|
+
settled = true;
|
|
11516
|
+
try {
|
|
11517
|
+
probe.close();
|
|
11518
|
+
} catch {
|
|
11519
|
+
}
|
|
11520
|
+
if (err || !out) reject(err ?? new Error("egress probe failed"));
|
|
11521
|
+
else resolve(out);
|
|
11522
|
+
};
|
|
11523
|
+
probe.on("error", (e) => finish(e));
|
|
11524
|
+
try {
|
|
11525
|
+
probe.connect(destPort, destHost, () => {
|
|
11526
|
+
try {
|
|
11527
|
+
const a = probe.address();
|
|
11528
|
+
if (typeof a === "string") return finish(new Error("probe address is string"));
|
|
11529
|
+
finish(void 0, {
|
|
11530
|
+
localAddress: a.address,
|
|
11531
|
+
localPort: a.port
|
|
11532
|
+
});
|
|
11533
|
+
} catch (e) {
|
|
11534
|
+
finish(e);
|
|
11535
|
+
}
|
|
11536
|
+
});
|
|
11537
|
+
} catch (e) {
|
|
11538
|
+
finish(e);
|
|
11539
|
+
}
|
|
11540
|
+
});
|
|
11541
|
+
}
|
|
11542
|
+
function isSameSubnetAsAnyLocalIface(destHost, srcInfo) {
|
|
11543
|
+
if (!/^\d+\.\d+\.\d+\.\d+$/.test(destHost)) return "unknown";
|
|
11544
|
+
const dest = destHost.split(".").map((s) => Number(s));
|
|
11545
|
+
if (dest.some((n) => !Number.isFinite(n) || n < 0 || n > 255))
|
|
11546
|
+
return "unknown";
|
|
11547
|
+
const ifaces = (0, import_node_os.networkInterfaces)();
|
|
11548
|
+
let ownerSubnet;
|
|
11549
|
+
for (const name of Object.keys(ifaces)) {
|
|
11550
|
+
const entries = ifaces[name];
|
|
11551
|
+
if (!entries) continue;
|
|
11552
|
+
for (const e of entries) {
|
|
11553
|
+
if (e.family !== "IPv4" || e.internal) continue;
|
|
11554
|
+
if (e.address !== srcInfo.localAddress) continue;
|
|
11555
|
+
const addr = e.address.split(".").map((s) => Number(s));
|
|
11556
|
+
const mask = e.netmask.split(".").map((s) => Number(s));
|
|
11557
|
+
if (addr.length !== 4 || mask.length !== 4 || addr.some((n) => !Number.isFinite(n)) || mask.some((n) => !Number.isFinite(n)))
|
|
11558
|
+
continue;
|
|
11559
|
+
ownerSubnet = { addr, mask };
|
|
11560
|
+
break;
|
|
11561
|
+
}
|
|
11562
|
+
if (ownerSubnet) break;
|
|
11563
|
+
}
|
|
11564
|
+
if (!ownerSubnet) return "unknown";
|
|
11565
|
+
for (let i = 0; i < 4; i++) {
|
|
11566
|
+
if ((ownerSubnet.addr[i] & ownerSubnet.mask[i]) !== (dest[i] & ownerSubnet.mask[i]))
|
|
11567
|
+
return "mismatch";
|
|
11568
|
+
}
|
|
11569
|
+
return "same";
|
|
11570
|
+
}
|
|
11502
11571
|
var AckLatency = class {
|
|
11503
11572
|
currentValues = [];
|
|
11504
11573
|
lastReceiveTime = null;
|
|
@@ -11577,6 +11646,11 @@ function isUnroutableForP2P(ip) {
|
|
|
11577
11646
|
var P2P_LOOKUP_PORT = 9999;
|
|
11578
11647
|
var P2P_MAX_WAIT_MS = 15e3;
|
|
11579
11648
|
var P2P_RESEND_WAIT_MS = 500;
|
|
11649
|
+
var inflightP2pLookups = /* @__PURE__ */ new Map();
|
|
11650
|
+
var cachedP2pLookups = /* @__PURE__ */ new Map();
|
|
11651
|
+
var negCachedP2pLookups = /* @__PURE__ */ new Map();
|
|
11652
|
+
var P2P_LOOKUP_CACHE_TTL_MS = 3e4;
|
|
11653
|
+
var P2P_LOOKUP_NEG_CACHE_TTL_MS = 15e3;
|
|
11580
11654
|
var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
11581
11655
|
opts;
|
|
11582
11656
|
/**
|
|
@@ -11665,31 +11739,14 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11665
11739
|
});
|
|
11666
11740
|
sock.on("error", (e) => this.emit("error", e));
|
|
11667
11741
|
sock.on("close", () => this.emit("close"));
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
await new Promise((resolve, reject) => {
|
|
11677
|
-
sock.once("error", reject);
|
|
11678
|
-
sock.bind(port, "0.0.0.0", () => {
|
|
11679
|
-
sock.removeListener("error", reject);
|
|
11680
|
-
resolve();
|
|
11681
|
-
});
|
|
11682
|
-
});
|
|
11683
|
-
bound = true;
|
|
11684
|
-
break;
|
|
11685
|
-
} catch {
|
|
11686
|
-
}
|
|
11687
|
-
}
|
|
11688
|
-
if (!bound) {
|
|
11689
|
-
await new Promise(
|
|
11690
|
-
(resolve) => sock.bind(0, "0.0.0.0", () => resolve())
|
|
11691
|
-
);
|
|
11692
|
-
}
|
|
11742
|
+
await new Promise((resolve, reject) => {
|
|
11743
|
+
const onErr = (e) => reject(e);
|
|
11744
|
+
sock.once("error", onErr);
|
|
11745
|
+
sock.bind(0, "0.0.0.0", () => {
|
|
11746
|
+
sock.removeListener("error", onErr);
|
|
11747
|
+
resolve();
|
|
11748
|
+
});
|
|
11749
|
+
});
|
|
11693
11750
|
if (this.opts.mode === "direct") {
|
|
11694
11751
|
this.remote = { host: this.opts.host, port: this.opts.port };
|
|
11695
11752
|
this.clientId = this.opts.clientId;
|
|
@@ -11760,6 +11817,49 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
11760
11817
|
this.remote = { host: connected.rhost, port: connected.rport };
|
|
11761
11818
|
}
|
|
11762
11819
|
async p2pUidLookup(sock, uid) {
|
|
11820
|
+
const log = (msg) => this.discoveryLogger?.log?.(`[P2P] ${msg}`);
|
|
11821
|
+
const shortUid = uid.length > 7 ? `${uid.slice(0, 5)}\u2026${uid.slice(-2)}` : uid;
|
|
11822
|
+
const cached = cachedP2pLookups.get(uid);
|
|
11823
|
+
if (cached && cached.expires > Date.now()) {
|
|
11824
|
+
log(
|
|
11825
|
+
`UID=${shortUid} cached lookup hit (relay=${cached.result.relay.ip}:${cached.result.relay.port})`
|
|
11826
|
+
);
|
|
11827
|
+
return cached.result;
|
|
11828
|
+
}
|
|
11829
|
+
const negCached = negCachedP2pLookups.get(uid);
|
|
11830
|
+
if (negCached && negCached.expires > Date.now()) {
|
|
11831
|
+
const remaining = negCached.expires - Date.now();
|
|
11832
|
+
log(
|
|
11833
|
+
`UID=${shortUid} negative-cache hit (fail-fast, retry in ${Math.ceil(remaining / 1e3)}s)`
|
|
11834
|
+
);
|
|
11835
|
+
throw negCached.error;
|
|
11836
|
+
}
|
|
11837
|
+
const inflight = inflightP2pLookups.get(uid);
|
|
11838
|
+
if (inflight) {
|
|
11839
|
+
log(`UID=${shortUid} sharing in-flight lookup with concurrent race lane`);
|
|
11840
|
+
return await inflight;
|
|
11841
|
+
}
|
|
11842
|
+
const work = this._doP2pUidLookupWork(sock, uid);
|
|
11843
|
+
inflightP2pLookups.set(uid, work);
|
|
11844
|
+
try {
|
|
11845
|
+
const result = await work;
|
|
11846
|
+
cachedP2pLookups.set(uid, {
|
|
11847
|
+
result,
|
|
11848
|
+
expires: Date.now() + P2P_LOOKUP_CACHE_TTL_MS
|
|
11849
|
+
});
|
|
11850
|
+
return result;
|
|
11851
|
+
} catch (e) {
|
|
11852
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
11853
|
+
negCachedP2pLookups.set(uid, {
|
|
11854
|
+
error: err,
|
|
11855
|
+
expires: Date.now() + P2P_LOOKUP_NEG_CACHE_TTL_MS
|
|
11856
|
+
});
|
|
11857
|
+
throw err;
|
|
11858
|
+
} finally {
|
|
11859
|
+
inflightP2pLookups.delete(uid);
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
async _doP2pUidLookupWork(sock, uid) {
|
|
11763
11863
|
const log = (msg) => this.discoveryLogger?.log?.(`[P2P] ${msg}`);
|
|
11764
11864
|
const shortUid = uid.length > 7 ? `${uid.slice(0, 5)}\u2026${uid.slice(-2)}` : uid;
|
|
11765
11865
|
const t0 = Date.now();
|
|
@@ -12227,6 +12327,23 @@ var BcUdpStream = class extends import_node_events3.EventEmitter {
|
|
|
12227
12327
|
log(
|
|
12228
12328
|
`local discovery: mode=${localMode} uid=${shortUid} ports=[${ports.join(", ")}] broadcasts=[${broadcastHosts.join(", ")}]${directHost ? ` direct=${directHost}` : ""} localBindPort=${localPort} timeout=${discoveryTimeout}ms`
|
|
12229
12329
|
);
|
|
12330
|
+
if (directHost && localMode === "local-direct") {
|
|
12331
|
+
try {
|
|
12332
|
+
const egress = await probeEgressForHost(directHost, ports[0] ?? 2015);
|
|
12333
|
+
const sameSubnet = isSameSubnetAsAnyLocalIface(directHost, egress);
|
|
12334
|
+
if (sameSubnet === "mismatch") {
|
|
12335
|
+
log(
|
|
12336
|
+
`WARN: kernel-chosen source IP ${egress.localAddress} is NOT in the same subnet as ${directHost}. Some Reolink battery cams silently drop discovery packets with off-subnet source IPs. If discovery fails, check your routing table \u2014 likely a Tailscale / VPN / secondary NIC stealing the default route.`
|
|
12337
|
+
);
|
|
12338
|
+
} else {
|
|
12339
|
+
log(
|
|
12340
|
+
`egress for ${directHost} \u2192 src=${egress.localAddress}` + (sameSubnet === "same" ? ` (same subnet \u2713)` : ` (subnet relationship unknown)`)
|
|
12341
|
+
);
|
|
12342
|
+
}
|
|
12343
|
+
} catch (e) {
|
|
12344
|
+
this.emit("debug", "egress_probe_failed", e);
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12230
12347
|
let bytesSent = 0;
|
|
12231
12348
|
let pktsRecv = 0;
|
|
12232
12349
|
sock.on("message", () => {
|
|
@@ -31811,7 +31928,7 @@ function selectViableUdpMethods(hasUid, methods = ALL_UDP_DISCOVERY_METHODS) {
|
|
|
31811
31928
|
return methods.filter((m) => m === "local-direct");
|
|
31812
31929
|
}
|
|
31813
31930
|
function normalizeUid(uid) {
|
|
31814
|
-
const v = uid?.trim();
|
|
31931
|
+
const v = uid?.trim().toUpperCase();
|
|
31815
31932
|
return v ? v : void 0;
|
|
31816
31933
|
}
|
|
31817
31934
|
function maskUid(uid) {
|