@apocaliss92/nodelink-js 0.6.0 → 0.6.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/{DiagnosticsTools-K4MF2VXZ.js → DiagnosticsTools-QJ3CRYGA.js} +2 -2
- package/dist/{chunk-7HSTETZR.js → chunk-D4TKRGUP.js} +124 -20
- package/dist/chunk-D4TKRGUP.js.map +1 -0
- package/dist/{chunk-XDVBNZGR.js → chunk-IQVVVSXO.js} +48 -16
- package/dist/{chunk-XDVBNZGR.js.map → chunk-IQVVVSXO.js.map} +1 -1
- package/dist/cli/rtsp-server.cjs +168 -32
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +168 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-7HSTETZR.js.map +0 -1
- /package/dist/{DiagnosticsTools-K4MF2VXZ.js.map → DiagnosticsTools-QJ3CRYGA.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
sampleStreams,
|
|
13
13
|
sanitizeFixtureData,
|
|
14
14
|
testChannelStreams
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-IQVVVSXO.js";
|
|
16
16
|
import "./chunk-MZUSWKF3.js";
|
|
17
17
|
export {
|
|
18
18
|
captureModelFixtures,
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
sanitizeFixtureData,
|
|
30
30
|
testChannelStreams
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=DiagnosticsTools-
|
|
32
|
+
//# sourceMappingURL=DiagnosticsTools-QJ3CRYGA.js.map
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
runAllDiagnosticsConsecutively,
|
|
31
31
|
runMultifocalDiagnosticsConsecutively,
|
|
32
32
|
xmlEscape
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-IQVVVSXO.js";
|
|
34
34
|
import {
|
|
35
35
|
BC_CLASS_FILE_DOWNLOAD,
|
|
36
36
|
BC_CLASS_LEGACY,
|
|
@@ -232,35 +232,88 @@ function decodeHeader(buf) {
|
|
|
232
232
|
return { header, headerLen, messageKey };
|
|
233
233
|
}
|
|
234
234
|
var BaichuanFrameParser = class {
|
|
235
|
+
/** Retained-but-unconsumed contiguous bytes from previous push() calls. */
|
|
235
236
|
buffer = Buffer.alloc(0);
|
|
237
|
+
/** Chunks received since the last materialization, not yet concatenated. */
|
|
238
|
+
pending = [];
|
|
239
|
+
/** Total bytes held in `pending` (kept in sync to avoid re-summing). */
|
|
240
|
+
pendingLen = 0;
|
|
241
|
+
/**
|
|
242
|
+
* Total contiguous bytes (`buffer` + `pending`) required before the next
|
|
243
|
+
* parse attempt can make progress. While buffered bytes stay below this,
|
|
244
|
+
* incoming chunks are merely stashed in `pending` with no copy. This is
|
|
245
|
+
* the mechanism that turns the worst case (a large frame fragmented over
|
|
246
|
+
* many small TCP chunks) from O(n²) into O(n): we concatenate once, when
|
|
247
|
+
* enough bytes have arrived, instead of on every chunk.
|
|
248
|
+
*
|
|
249
|
+
* Starts at 4 — the minimum needed to inspect the magic header.
|
|
250
|
+
*/
|
|
251
|
+
needed = 4;
|
|
252
|
+
/**
|
|
253
|
+
* Collapse `this.buffer` + all `pending` chunks into a single contiguous
|
|
254
|
+
* buffer. The retained leftover is copied at most once per materialize(),
|
|
255
|
+
* and materialize() only runs when `needed` bytes are available — so a
|
|
256
|
+
* fragmented frame is assembled with a single concat, not one per chunk.
|
|
257
|
+
*/
|
|
258
|
+
materialize() {
|
|
259
|
+
if (this.pendingLen === 0) return;
|
|
260
|
+
if (this.buffer.length === 0 && this.pending.length === 1) {
|
|
261
|
+
this.buffer = this.pending[0];
|
|
262
|
+
} else {
|
|
263
|
+
const parts = this.buffer.length === 0 ? this.pending : [this.buffer, ...this.pending];
|
|
264
|
+
this.buffer = Buffer.concat(parts);
|
|
265
|
+
}
|
|
266
|
+
this.pending = [];
|
|
267
|
+
this.pendingLen = 0;
|
|
268
|
+
}
|
|
269
|
+
/** Total buffered bytes, whether materialized or still pending. */
|
|
270
|
+
get available() {
|
|
271
|
+
return this.buffer.length + this.pendingLen;
|
|
272
|
+
}
|
|
236
273
|
push(chunk) {
|
|
237
274
|
if (chunk.length === 0) return [];
|
|
238
|
-
|
|
239
|
-
this.
|
|
275
|
+
this.pending.push(chunk);
|
|
276
|
+
this.pendingLen += chunk.length;
|
|
277
|
+
if (this.available < this.needed) return [];
|
|
278
|
+
this.materialize();
|
|
240
279
|
const out = [];
|
|
241
280
|
while (true) {
|
|
242
|
-
if (this.buffer.length < 4)
|
|
281
|
+
if (this.buffer.length < 4) {
|
|
282
|
+
this.needed = 4;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
243
285
|
if (!this.buffer.subarray(0, 4).equals(BC_MAGIC) && !this.buffer.subarray(0, 4).equals(BC_MAGIC_REV)) {
|
|
244
286
|
const idx = this.buffer.indexOf(BC_MAGIC);
|
|
245
287
|
const idxRev = this.buffer.indexOf(BC_MAGIC_REV);
|
|
246
288
|
const next = idx === -1 ? idxRev : idxRev === -1 ? idx : Math.min(idx, idxRev);
|
|
247
289
|
if (next === -1) {
|
|
248
290
|
this.buffer = this.buffer.subarray(Math.max(0, this.buffer.length - 3));
|
|
291
|
+
this.needed = 4;
|
|
249
292
|
break;
|
|
250
293
|
}
|
|
251
294
|
this.buffer = this.buffer.subarray(next);
|
|
252
|
-
if (this.buffer.length < 20)
|
|
295
|
+
if (this.buffer.length < 20) {
|
|
296
|
+
this.needed = 20;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (this.buffer.length < 20) {
|
|
301
|
+
this.needed = 20;
|
|
302
|
+
break;
|
|
253
303
|
}
|
|
254
|
-
if (this.buffer.length < 20) break;
|
|
255
304
|
let headerInfo;
|
|
256
305
|
try {
|
|
257
306
|
headerInfo = decodeHeader(this.buffer);
|
|
258
307
|
} catch {
|
|
308
|
+
this.needed = 24;
|
|
259
309
|
break;
|
|
260
310
|
}
|
|
261
311
|
const { header, headerLen, messageKey } = headerInfo;
|
|
262
312
|
const frameLen = headerLen + header.bodyLen;
|
|
263
|
-
if (this.buffer.length < frameLen)
|
|
313
|
+
if (this.buffer.length < frameLen) {
|
|
314
|
+
this.needed = frameLen;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
264
317
|
const raw = this.buffer.subarray(0, frameLen);
|
|
265
318
|
const body = raw.subarray(headerLen);
|
|
266
319
|
let extLen = 0;
|
|
@@ -272,6 +325,7 @@ var BaichuanFrameParser = class {
|
|
|
272
325
|
const payload = body.subarray(extLen);
|
|
273
326
|
out.push({ header, body, extension, payload, messageKey, raw });
|
|
274
327
|
this.buffer = this.buffer.subarray(frameLen);
|
|
328
|
+
this.needed = 4;
|
|
275
329
|
}
|
|
276
330
|
return out;
|
|
277
331
|
}
|
|
@@ -645,12 +699,28 @@ async function getServerBinding(uid, options = {}) {
|
|
|
645
699
|
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
646
700
|
const logger = options.logger;
|
|
647
701
|
if (typeof fetchImpl !== "function") {
|
|
648
|
-
logger?.
|
|
702
|
+
logger?.log?.(
|
|
649
703
|
`[server-binding] global fetch unavailable; skipping cloud lookup`
|
|
650
704
|
);
|
|
651
705
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
652
706
|
return void 0;
|
|
653
707
|
}
|
|
708
|
+
try {
|
|
709
|
+
const apiHostname = new URL(baseUrl).hostname;
|
|
710
|
+
const dns2 = await import("dns/promises");
|
|
711
|
+
const answers = await dns2.lookup(apiHostname, { family: 4, all: true });
|
|
712
|
+
const sinkholed = answers.find(
|
|
713
|
+
(a) => a.address?.startsWith("127.") || a.address === "0.0.0.0" || a.address?.startsWith("10.") || a.address?.startsWith("192.168.") || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(a.address ?? "")
|
|
714
|
+
);
|
|
715
|
+
if (sinkholed) {
|
|
716
|
+
logger?.log?.(
|
|
717
|
+
`[server-binding] ${uid}: DNS for ${apiHostname} resolves to ${sinkholed.address} (sinkhole / /etc/hosts override). Cloud directory unreachable \u2014 falling back to the 22-hostname P2P sweep. Whitelist ${apiHostname} to enable.`
|
|
718
|
+
);
|
|
719
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
654
724
|
const url = `${baseUrl}/devices/${encodeURIComponent(uid)}/server-binding?language=${encodeURIComponent(language)}`;
|
|
655
725
|
const controller = new AbortController();
|
|
656
726
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -661,8 +731,8 @@ async function getServerBinding(uid, options = {}) {
|
|
|
661
731
|
headers: { Accept: "application/json" }
|
|
662
732
|
});
|
|
663
733
|
if (!res.ok) {
|
|
664
|
-
logger?.
|
|
665
|
-
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText}`
|
|
734
|
+
logger?.log?.(
|
|
735
|
+
`[server-binding] ${uid}: HTTP ${res.status} ${res.statusText} from ${url}`
|
|
666
736
|
);
|
|
667
737
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
668
738
|
return void 0;
|
|
@@ -670,8 +740,15 @@ async function getServerBinding(uid, options = {}) {
|
|
|
670
740
|
const json = await res.json();
|
|
671
741
|
const parsed = parseServerBindingResponse(json);
|
|
672
742
|
if (!parsed) {
|
|
673
|
-
logger?.
|
|
674
|
-
`[server-binding] ${uid}: response shape did not match expectations`
|
|
743
|
+
logger?.log?.(
|
|
744
|
+
`[server-binding] ${uid}: response shape did not match expectations (Reolink schema change?)`
|
|
745
|
+
);
|
|
746
|
+
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
747
|
+
return void 0;
|
|
748
|
+
}
|
|
749
|
+
if (parsed.availableZones.length === 0) {
|
|
750
|
+
logger?.log?.(
|
|
751
|
+
`[server-binding] ${uid}: cloud returned 0 zones \u2014 UID not registered with Reolink cloud (or wrong region)`
|
|
675
752
|
);
|
|
676
753
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
677
754
|
return void 0;
|
|
@@ -690,9 +767,23 @@ async function getServerBinding(uid, options = {}) {
|
|
|
690
767
|
);
|
|
691
768
|
return parsed;
|
|
692
769
|
} catch (e) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
)
|
|
770
|
+
const msg = e?.message ?? String(e);
|
|
771
|
+
const errName = e?.name;
|
|
772
|
+
if (errName === "AbortError" || msg.includes("aborted")) {
|
|
773
|
+
logger?.log?.(
|
|
774
|
+
`[server-binding] ${uid}: timed out after ${timeoutMs}ms (cloud unreachable)`
|
|
775
|
+
);
|
|
776
|
+
} else if (msg.includes("ENOTFOUND") || msg.includes("EAI_AGAIN")) {
|
|
777
|
+
logger?.log?.(
|
|
778
|
+
`[server-binding] ${uid}: DNS failed (${msg}) \u2014 apis.reolink.com may be blocked at resolver`
|
|
779
|
+
);
|
|
780
|
+
} else if (msg.includes("ECONNREFUSED") || msg.includes("EHOSTUNREACH") || msg.includes("ENETUNREACH")) {
|
|
781
|
+
logger?.log?.(
|
|
782
|
+
`[server-binding] ${uid}: network unreachable (${msg}) \u2014 cloud port blocked`
|
|
783
|
+
);
|
|
784
|
+
} else {
|
|
785
|
+
logger?.log?.(`[server-binding] ${uid}: fetch failed \u2014 ${msg}`);
|
|
786
|
+
}
|
|
696
787
|
cache.set(uid, { kind: "err", expires: now + NEGATIVE_TTL_MS });
|
|
697
788
|
return void 0;
|
|
698
789
|
} finally {
|
|
@@ -1128,12 +1219,19 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1128
1219
|
const tid = (Math.floor(Math.random() * 2147483647) | 0) >>> 0;
|
|
1129
1220
|
const xml = buildC2mQ({ uid });
|
|
1130
1221
|
const pkt = encodeDiscoveryPacket(tid, xml);
|
|
1222
|
+
const counters = { sentBytes: 0, rxBytes: 0 };
|
|
1131
1223
|
return await new Promise((resolve, reject) => {
|
|
1132
1224
|
const deadline = setTimeout(() => {
|
|
1133
1225
|
cleanup();
|
|
1134
|
-
|
|
1226
|
+
const err = new Error(
|
|
1227
|
+
`P2P UID lookup timeout (${dest.host}:${dest.port}) \u2014 sent=${counters.sentBytes}B rx=${counters.rxBytes}B`
|
|
1228
|
+
);
|
|
1229
|
+
err.sentBytes = counters.sentBytes;
|
|
1230
|
+
err.rxBytes = counters.rxBytes;
|
|
1231
|
+
reject(err);
|
|
1135
1232
|
}, timeoutMs);
|
|
1136
1233
|
const onMsg = (msg) => {
|
|
1234
|
+
counters.rxBytes += msg.length;
|
|
1137
1235
|
try {
|
|
1138
1236
|
const p = decodeBcUdpPacket(msg);
|
|
1139
1237
|
if (p.kind !== "discovery") return;
|
|
@@ -1141,13 +1239,19 @@ var BcUdpStream = class extends EventEmitter {
|
|
|
1141
1239
|
const qr = parseM2cQr(p.xml);
|
|
1142
1240
|
if (!qr?.reg || !qr?.relay) return;
|
|
1143
1241
|
cleanup();
|
|
1144
|
-
resolve({
|
|
1242
|
+
resolve({
|
|
1243
|
+
reg: qr.reg,
|
|
1244
|
+
relay: qr.relay,
|
|
1245
|
+
sentBytes: counters.sentBytes,
|
|
1246
|
+
rxBytes: counters.rxBytes
|
|
1247
|
+
});
|
|
1145
1248
|
} catch {
|
|
1146
1249
|
}
|
|
1147
1250
|
};
|
|
1148
1251
|
const send = () => {
|
|
1149
1252
|
try {
|
|
1150
1253
|
sock.send(pkt, dest.port, dest.host);
|
|
1254
|
+
counters.sentBytes += pkt.length;
|
|
1151
1255
|
} catch {
|
|
1152
1256
|
}
|
|
1153
1257
|
};
|
|
@@ -20467,7 +20571,7 @@ ${xml}`
|
|
|
20467
20571
|
* @returns Test results for all stream types and profiles
|
|
20468
20572
|
*/
|
|
20469
20573
|
async testChannelStreams(channel, logger) {
|
|
20470
|
-
const { testChannelStreams } = await import("./DiagnosticsTools-
|
|
20574
|
+
const { testChannelStreams } = await import("./DiagnosticsTools-QJ3CRYGA.js");
|
|
20471
20575
|
return await testChannelStreams({
|
|
20472
20576
|
api: this,
|
|
20473
20577
|
channel: this.normalizeChannel(channel),
|
|
@@ -20483,7 +20587,7 @@ ${xml}`
|
|
|
20483
20587
|
* @returns Complete diagnostics for all channels and streams
|
|
20484
20588
|
*/
|
|
20485
20589
|
async collectMultifocalDiagnostics(logger) {
|
|
20486
|
-
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-
|
|
20590
|
+
const { collectMultifocalDiagnostics } = await import("./DiagnosticsTools-QJ3CRYGA.js");
|
|
20487
20591
|
return await collectMultifocalDiagnostics({
|
|
20488
20592
|
api: this,
|
|
20489
20593
|
logger
|
|
@@ -25268,4 +25372,4 @@ export {
|
|
|
25268
25372
|
tcpReachabilityProbe,
|
|
25269
25373
|
autoDetectDeviceType
|
|
25270
25374
|
};
|
|
25271
|
-
//# sourceMappingURL=chunk-
|
|
25375
|
+
//# sourceMappingURL=chunk-D4TKRGUP.js.map
|