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