@apocaliss92/nodelink-js 0.3.9 → 0.4.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.
@@ -4917,11 +4917,13 @@ __export(DiagnosticsTools_exports, {
4917
4917
  collectMultifocalDiagnostics: () => collectMultifocalDiagnostics,
4918
4918
  collectNativeDiagnostics: () => collectNativeDiagnostics,
4919
4919
  collectNvrDiagnostics: () => collectNvrDiagnostics,
4920
+ computeExpectedStreamCompatibility: () => computeExpectedStreamCompatibility,
4920
4921
  createDiagnosticsBundle: () => createDiagnosticsBundle,
4921
4922
  printNvrDiagnostics: () => printNvrDiagnostics,
4922
4923
  runAllDiagnosticsConsecutively: () => runAllDiagnosticsConsecutively,
4923
4924
  runMultifocalDiagnosticsConsecutively: () => runMultifocalDiagnosticsConsecutively,
4924
4925
  sampleStreams: () => sampleStreams,
4926
+ sanitizeFixtureData: () => sanitizeFixtureData,
4925
4927
  testChannelStreams: () => testChannelStreams
4926
4928
  });
4927
4929
  function safeStringifyError(error) {
@@ -7213,10 +7215,102 @@ async function runAllDiagnosticsConsecutively(params) {
7213
7215
  streamsDir
7214
7216
  };
7215
7217
  }
7218
+ function maskIp(ip) {
7219
+ const parts = ip.split(".");
7220
+ return `${parts[0]}.***.***.***`;
7221
+ }
7222
+ function maskMac(mac) {
7223
+ const sep = mac.includes("-") ? "-" : ":";
7224
+ const parts = mac.split(/[:-]/);
7225
+ return `${parts[0]}${sep}${parts[1]}${sep}**${sep}**${sep}**${sep}**`;
7226
+ }
7227
+ function maskSerial(val) {
7228
+ if (val.length <= 4) return "****";
7229
+ return val.slice(0, 2) + "*".repeat(val.length - 4) + val.slice(-2);
7230
+ }
7231
+ function sanitizeString(s) {
7232
+ let out = s;
7233
+ out = out.replace(/:\/\/([^:@]+):([^@]+)@/g, "://***:***@");
7234
+ out = out.replace(/(password=)[^&\s]*/gi, "$1***");
7235
+ out = out.replace(/(user=)[^&\s]*/gi, "$1***");
7236
+ out = out.replace(IPV4_RE, (match) => maskIp(match));
7237
+ out = out.replace(MAC_RE, (match) => maskMac(match));
7238
+ return out;
7239
+ }
7240
+ function sanitizeFixtureData(value) {
7241
+ if (value === null || value === void 0) return value;
7242
+ if (typeof value === "string") {
7243
+ return sanitizeString(value);
7244
+ }
7245
+ if (Array.isArray(value)) {
7246
+ return value.map(sanitizeFixtureData);
7247
+ }
7248
+ if (typeof value === "object") {
7249
+ const out = {};
7250
+ for (const [k, v] of Object.entries(value)) {
7251
+ const kLower = k.toLowerCase();
7252
+ if (REDACT_KEYS.has(kLower)) {
7253
+ out[k] = "***";
7254
+ } else if (MASK_KEYS.has(kLower) || kLower === "serialnumber") {
7255
+ out[k] = typeof v === "string" ? maskSerial(v) : "***";
7256
+ } else if (kLower === "mac") {
7257
+ out[k] = typeof v === "string" ? maskMac(v) : "***";
7258
+ } else if (kLower === "ip" || kLower === "ipaddress" || kLower === "host") {
7259
+ out[k] = typeof v === "string" ? maskIp(v) : v;
7260
+ } else if (kLower === "name" && typeof v === "string" && k !== "name") {
7261
+ out[k] = v;
7262
+ } else {
7263
+ out[k] = sanitizeFixtureData(v);
7264
+ }
7265
+ }
7266
+ return out;
7267
+ }
7268
+ return value;
7269
+ }
7270
+ function computeExpectedStreamCompatibility(params) {
7271
+ const { channelCount } = params;
7272
+ const profiles = params.profiles ?? ["main", "sub", "ext"];
7273
+ const expectedStreamType = { main: 0, sub: 1, ext: 0 };
7274
+ const allStreams = [];
7275
+ for (let ch = 0; ch < channelCount; ch++) {
7276
+ for (const p of profiles) {
7277
+ allStreams.push({ ch, profile: p, label: channelCount > 1 ? `ch${ch}_${p}` : p });
7278
+ }
7279
+ }
7280
+ const results = [];
7281
+ for (let i = 0; i < allStreams.length; i++) {
7282
+ for (let j = i + 1; j < allStreams.length; j++) {
7283
+ const a = allStreams[i];
7284
+ const b = allStreams[j];
7285
+ const stA = expectedStreamType[a.profile] ?? 0;
7286
+ const stB = expectedStreamType[b.profile] ?? 0;
7287
+ const sameChannel = a.ch === b.ch;
7288
+ let expectedOk;
7289
+ let reason;
7290
+ if (sameChannel && stA === stB) {
7291
+ expectedOk = false;
7292
+ reason = `same streamType (${stA}) on same channel`;
7293
+ } else if (!sameChannel && a.profile === b.profile) {
7294
+ expectedOk = false;
7295
+ reason = `multifocal rejects same profile (${a.profile}) across channels`;
7296
+ } else if (!sameChannel && stA === stB) {
7297
+ expectedOk = false;
7298
+ reason = `same streamType (${stA}) across channels`;
7299
+ } else {
7300
+ expectedOk = true;
7301
+ reason = `different streamTypes (${stA} vs ${stB})`;
7302
+ }
7303
+ results.push({ pair: [a.label, b.label], expectedOk, reason });
7304
+ }
7305
+ }
7306
+ return results;
7307
+ }
7216
7308
  async function captureModelFixtures(params) {
7217
7309
  const { api, channel, outDir } = params;
7218
7310
  const log = params.log ?? console.log;
7219
7311
  mkdirp(outDir);
7312
+ const writeJsonSafe = (filePath, data) => writeJson(filePath, sanitizeFixtureData(data));
7313
+ const writeTextSafe = (filePath, text) => writeText(filePath, sanitizeString(text));
7220
7314
  const calls = {};
7221
7315
  const errors = [];
7222
7316
  async function capture(name, fn, writer) {
@@ -7239,123 +7333,269 @@ async function captureModelFixtures(params) {
7239
7333
  const info = await capture(
7240
7334
  "getInfo",
7241
7335
  () => api.getInfo(channel),
7242
- (v) => writeJson(path4.join(outDir, "device-info.json"), v)
7336
+ (v) => writeJsonSafe(path4.join(outDir, "device-info.json"), v)
7243
7337
  );
7244
7338
  const support = await capture(
7245
7339
  "getSupportInfo",
7246
7340
  () => api.getSupportInfo(),
7247
- (v) => writeJson(path4.join(outDir, "support-info.json"), v)
7341
+ (v) => writeJsonSafe(path4.join(outDir, "support-info.json"), v)
7248
7342
  );
7249
7343
  const abilities = await capture(
7250
7344
  "getAbilityInfo",
7251
7345
  () => api.getAbilityInfo(),
7252
- (v) => writeJson(path4.join(outDir, "ability-info.json"), v)
7346
+ (v) => writeJsonSafe(path4.join(outDir, "ability-info.json"), v)
7253
7347
  );
7254
7348
  await capture(
7255
7349
  "getDeviceCapabilities",
7256
7350
  () => api.getDeviceCapabilities(channel),
7257
- (v) => writeJson(path4.join(outDir, "capabilities.json"), v)
7351
+ (v) => writeJsonSafe(path4.join(outDir, "capabilities.json"), v)
7258
7352
  );
7259
7353
  await capture("cmd289-WhiteLed", () => api.sendXml({
7260
7354
  cmdId: BC_CMD_ID_GET_WHITE_LED,
7261
7355
  channel,
7262
7356
  timeoutMs: 3e3
7263
- }), (v) => writeText(path4.join(outDir, "cmd289-white-led.xml"), v));
7357
+ }), (v) => writeTextSafe(path4.join(outDir, "cmd289-white-led.xml"), v));
7264
7358
  await capture(
7265
7359
  "getStreamMetadata",
7266
7360
  () => api.getStreamMetadata(channel),
7267
- (v) => writeJson(path4.join(outDir, "stream-metadata.json"), v)
7361
+ (v) => writeJsonSafe(path4.join(outDir, "stream-metadata.json"), v)
7268
7362
  );
7269
7363
  await capture(
7270
7364
  "getEncXml",
7271
7365
  () => api.getEncXml(channel),
7272
- (v) => writeText(path4.join(outDir, "enc-config.xml"), v)
7366
+ (v) => writeTextSafe(path4.join(outDir, "enc-config.xml"), v)
7273
7367
  );
7274
7368
  await capture(
7275
7369
  "getPorts",
7276
7370
  () => api.getPorts(),
7277
- (v) => writeJson(path4.join(outDir, "ports.json"), v)
7371
+ (v) => writeJsonSafe(path4.join(outDir, "ports.json"), v)
7278
7372
  );
7279
7373
  await capture(
7280
7374
  "getTalkAbility",
7281
7375
  () => api.getTalkAbility(channel),
7282
- (v) => writeJson(path4.join(outDir, "talk-ability.json"), v)
7376
+ (v) => writeJsonSafe(path4.join(outDir, "talk-ability.json"), v)
7283
7377
  );
7284
7378
  await capture(
7285
7379
  "getTwoWayAudioConfig",
7286
7380
  () => api.getTwoWayAudioConfig(channel),
7287
- (v) => writeJson(path4.join(outDir, "two-way-audio-config.json"), v)
7381
+ (v) => writeJsonSafe(path4.join(outDir, "two-way-audio-config.json"), v)
7288
7382
  );
7289
7383
  await capture(
7290
7384
  "getAiState",
7291
7385
  () => api.getAiState(channel),
7292
- (v) => writeJson(path4.join(outDir, "ai-state.json"), v)
7386
+ (v) => writeJsonSafe(path4.join(outDir, "ai-state.json"), v)
7293
7387
  );
7294
7388
  await capture(
7295
7389
  "getAiCfg",
7296
7390
  () => api.getAiCfg(channel),
7297
- (v) => writeJson(path4.join(outDir, "ai-cfg.json"), v)
7391
+ (v) => writeJsonSafe(path4.join(outDir, "ai-cfg.json"), v)
7298
7392
  );
7299
7393
  await capture(
7300
7394
  "getOsd",
7301
7395
  () => api.getOsd(channel),
7302
- (v) => writeJson(path4.join(outDir, "osd.json"), v)
7396
+ (v) => writeJsonSafe(path4.join(outDir, "osd.json"), v)
7303
7397
  );
7304
7398
  await capture(
7305
7399
  "getMotionAlarm",
7306
7400
  () => api.getMotionAlarm(channel),
7307
- (v) => writeJson(path4.join(outDir, "motion-alarm.json"), v)
7401
+ (v) => writeJsonSafe(path4.join(outDir, "motion-alarm.json"), v)
7308
7402
  );
7309
7403
  await capture(
7310
7404
  "getRecordCfg",
7311
7405
  () => api.getRecordCfg(channel),
7312
- (v) => writeJson(path4.join(outDir, "record-cfg.json"), v)
7406
+ (v) => writeJsonSafe(path4.join(outDir, "record-cfg.json"), v)
7313
7407
  );
7314
7408
  await capture(
7315
7409
  "getVideoInput",
7316
7410
  () => api.getVideoInput(channel),
7317
- (v) => writeJson(path4.join(outDir, "video-input.json"), v)
7411
+ (v) => writeJsonSafe(path4.join(outDir, "video-input.json"), v)
7318
7412
  );
7319
7413
  await capture(
7320
7414
  "getPtzPresets",
7321
7415
  () => api.getPtzPresets(channel),
7322
- (v) => writeJson(path4.join(outDir, "ptz-presets.json"), v)
7416
+ (v) => writeJsonSafe(path4.join(outDir, "ptz-presets.json"), v)
7323
7417
  );
7324
7418
  await capture(
7325
7419
  "getNetworkInfo",
7326
7420
  () => api.getNetworkInfo(),
7327
- (v) => writeJson(path4.join(outDir, "network-info.json"), v)
7421
+ (v) => writeJsonSafe(path4.join(outDir, "network-info.json"), v)
7328
7422
  );
7329
7423
  await capture(
7330
7424
  "getSystemGeneral",
7331
7425
  () => api.getSystemGeneral(),
7332
- (v) => writeJson(path4.join(outDir, "system-general.json"), v)
7426
+ (v) => writeJsonSafe(path4.join(outDir, "system-general.json"), v)
7333
7427
  );
7334
7428
  await capture(
7335
7429
  "getWifiSignal",
7336
7430
  () => api.getWifiSignal(channel),
7337
- (v) => writeJson(path4.join(outDir, "wifi-signal.json"), v)
7431
+ (v) => writeJsonSafe(path4.join(outDir, "wifi-signal.json"), v)
7338
7432
  );
7339
7433
  await capture(
7340
7434
  "getWhiteLedState",
7341
7435
  () => api.getWhiteLedState(channel),
7342
- (v) => writeJson(path4.join(outDir, "white-led-state.json"), v)
7436
+ (v) => writeJsonSafe(path4.join(outDir, "white-led-state.json"), v)
7343
7437
  );
7344
7438
  await capture(
7345
7439
  "getFloodlightOnMotion",
7346
7440
  () => api.getFloodlightOnMotion(channel),
7347
- (v) => writeJson(path4.join(outDir, "floodlight-on-motion.json"), v)
7441
+ (v) => writeJsonSafe(path4.join(outDir, "floodlight-on-motion.json"), v)
7348
7442
  );
7349
7443
  await capture(
7350
7444
  "buildVideoStreamOptions",
7351
7445
  () => api.buildVideoStreamOptions({ channel }),
7352
- (v) => writeJson(path4.join(outDir, "video-stream-options.json"), v)
7446
+ (v) => writeJsonSafe(path4.join(outDir, "video-stream-options.json"), v)
7353
7447
  );
7448
+ await capture(
7449
+ "getDualLensChannelInfo",
7450
+ () => api.getDualLensChannelInfo(channel),
7451
+ (v) => writeJsonSafe(path4.join(outDir, "dual-lens-info.json"), v)
7452
+ );
7453
+ await capture("streamCombinationTest", async () => {
7454
+ let dualLensInfo;
7455
+ try {
7456
+ dualLensInfo = await api.getDualLensChannelInfo(channel);
7457
+ } catch {
7458
+ }
7459
+ const isMultifocal = dualLensInfo?.isDualLens === true;
7460
+ const channelCount = dualLensInfo?.streamChannelCount ?? 1;
7461
+ const allStreams = [];
7462
+ const profiles = ["main", "sub", "ext"];
7463
+ for (let ch = 0; ch < channelCount; ch++) {
7464
+ for (const p of profiles) {
7465
+ const label = channelCount > 1 ? `ch${ch}_${p}` : p;
7466
+ allStreams.push({ ch, profile: p, label });
7467
+ }
7468
+ }
7469
+ const pairs = [];
7470
+ for (let i = 0; i < allStreams.length; i++) {
7471
+ for (let j = i + 1; j < allStreams.length; j++) {
7472
+ pairs.push([allStreams[i], allStreams[j]]);
7473
+ }
7474
+ }
7475
+ const expectedStreamType = { main: 0, sub: 1, ext: 0 };
7476
+ const expectedCompat = [];
7477
+ for (const [a, b] of pairs) {
7478
+ const stA = expectedStreamType[a.profile];
7479
+ const stB = expectedStreamType[b.profile];
7480
+ const sameChannel = a.ch === b.ch;
7481
+ let expectedOk;
7482
+ let reason;
7483
+ if (sameChannel && stA === stB) {
7484
+ expectedOk = false;
7485
+ reason = `same streamType (${stA}) on same channel`;
7486
+ } else if (!sameChannel && a.profile === b.profile) {
7487
+ expectedOk = false;
7488
+ reason = `multifocal rejects same profile (${a.profile}) across channels`;
7489
+ } else if (!sameChannel && stA === stB) {
7490
+ expectedOk = false;
7491
+ reason = `same streamType (${stA}) across channels`;
7492
+ } else {
7493
+ expectedOk = true;
7494
+ reason = `different streamTypes (${stA} vs ${stB})`;
7495
+ }
7496
+ expectedCompat.push({ pair: [a.label, b.label], expectedOk, reason });
7497
+ }
7498
+ const results = [];
7499
+ const TEST_DURATION_MS = 4e3;
7500
+ for (let pairIdx = 0; pairIdx < pairs.length; pairIdx++) {
7501
+ const [a, b] = pairs[pairIdx];
7502
+ const expected = expectedCompat[pairIdx];
7503
+ log(` testing ${a.label}+${b.label} on shared socket...`);
7504
+ let session;
7505
+ try {
7506
+ session = await api.createDedicatedSession(
7507
+ `test:combo:${a.label}_${b.label}`
7508
+ );
7509
+ } catch (e) {
7510
+ const result2 = {
7511
+ pair: [a.label, b.label],
7512
+ ok: false,
7513
+ framesA: 0,
7514
+ framesB: 0,
7515
+ mismatches: 0,
7516
+ error: `session: ${e instanceof Error ? e.message : String(e)}`,
7517
+ durationMs: 0,
7518
+ expectedOk: expected.expectedOk,
7519
+ expectedReason: expected.reason,
7520
+ matchesExpected: !expected.expectedOk
7521
+ // failed as expected if we expected failure
7522
+ };
7523
+ results.push(result2);
7524
+ continue;
7525
+ }
7526
+ const testClient = session.client;
7527
+ let framesA = 0;
7528
+ let framesB = 0;
7529
+ let mismatches = 0;
7530
+ const start = Date.now();
7531
+ const stTypes = {
7532
+ main: /* @__PURE__ */ new Set([0, 2]),
7533
+ sub: /* @__PURE__ */ new Set([1, 3]),
7534
+ ext: /* @__PURE__ */ new Set([0, 2])
7535
+ };
7536
+ const setA = stTypes[a.profile];
7537
+ const setB = stTypes[b.profile];
7538
+ const onFrame = (frame) => {
7539
+ if (frame.header?.cmdId !== BC_CMD_ID_VIDEO) return;
7540
+ const st = frame.header.streamType;
7541
+ if (setA.has(st)) framesA++;
7542
+ else if (setB.has(st)) framesB++;
7543
+ else mismatches++;
7544
+ };
7545
+ testClient.on("frame", onFrame);
7546
+ let error;
7547
+ try {
7548
+ await api.startVideoStream(a.ch, a.profile, { client: testClient });
7549
+ await api.startVideoStream(b.ch, b.profile, { client: testClient });
7550
+ await new Promise((r) => setTimeout(r, TEST_DURATION_MS));
7551
+ await api.stopVideoStream(a.ch, a.profile, { client: testClient }).catch(() => {
7552
+ });
7553
+ await api.stopVideoStream(b.ch, b.profile, { client: testClient }).catch(() => {
7554
+ });
7555
+ } catch (e) {
7556
+ error = e instanceof Error ? e.message : String(e);
7557
+ } finally {
7558
+ testClient.removeListener("frame", onFrame);
7559
+ await session.release().catch(() => {
7560
+ });
7561
+ }
7562
+ const elapsed = Date.now() - start;
7563
+ const ok2 = !error && framesA > 0 && framesB > 0 && mismatches === 0;
7564
+ const result = {
7565
+ pair: [a.label, b.label],
7566
+ ok: ok2,
7567
+ framesA,
7568
+ framesB,
7569
+ mismatches,
7570
+ ...error ? { error } : {},
7571
+ durationMs: elapsed,
7572
+ expectedOk: expected.expectedOk,
7573
+ expectedReason: expected.reason,
7574
+ matchesExpected: ok2 === expected.expectedOk
7575
+ };
7576
+ results.push(result);
7577
+ const matchStr = result.matchesExpected ? "" : " *** UNEXPECTED ***";
7578
+ log(` ${a.label}+${b.label}: ${ok2 ? "OK" : "FAIL"} (expected=${expected.expectedOk ? "OK" : "FAIL"}) A=${framesA} B=${framesB} mismatch=${mismatches}${matchStr}`);
7579
+ }
7580
+ const unexpected = results.filter((r) => !r.matchesExpected);
7581
+ return {
7582
+ isMultifocal,
7583
+ channelCount,
7584
+ results,
7585
+ summary: {
7586
+ total: results.length,
7587
+ ok: results.filter((r) => r.ok).length,
7588
+ failed: results.filter((r) => !r.ok).length,
7589
+ matchesExpected: results.filter((r) => r.matchesExpected).length,
7590
+ unexpected: unexpected.map((r) => `${r.pair[0]}+${r.pair[1]}: got ${r.ok ? "OK" : "FAIL"}, expected ${r.expectedOk ? "OK" : "FAIL"}`)
7591
+ }
7592
+ };
7593
+ }, (v) => writeJsonSafe(path4.join(outDir, "stream-combination-test.json"), v));
7354
7594
  const total = Object.keys(calls).length;
7355
7595
  const ok = Object.values(calls).filter((c) => c.ok).length;
7356
7596
  const failed = total - ok;
7357
7597
  const summary = { total, ok, failed, errors };
7358
- writeJson(path4.join(outDir, "_summary.json"), {
7598
+ writeJsonSafe(path4.join(outDir, "_summary.json"), {
7359
7599
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
7360
7600
  model: info?.type ?? "unknown",
7361
7601
  itemNo: info?.itemNo ?? "unknown",
@@ -7379,7 +7619,7 @@ async function captureModelFixtures(params) {
7379
7619
  }
7380
7620
  return { calls, outDir, summary };
7381
7621
  }
7382
- var fs4, path4, import_node_child_process2, import_node_path;
7622
+ var fs4, path4, import_node_child_process2, import_node_path, REDACT_KEYS, MASK_KEYS, IPV4_RE, MAC_RE;
7383
7623
  var init_DiagnosticsTools = __esm({
7384
7624
  "src/debug/DiagnosticsTools.ts"() {
7385
7625
  "use strict";
@@ -7395,6 +7635,27 @@ var init_DiagnosticsTools = __esm({
7395
7635
  init_H265Converter();
7396
7636
  init_constants();
7397
7637
  init_xml();
7638
+ REDACT_KEYS = /* @__PURE__ */ new Set([
7639
+ "password",
7640
+ "pass",
7641
+ "token",
7642
+ "secret",
7643
+ "apiKey",
7644
+ "api_key"
7645
+ ]);
7646
+ MASK_KEYS = /* @__PURE__ */ new Set([
7647
+ "serialNumber",
7648
+ "serial",
7649
+ "uid",
7650
+ "mac",
7651
+ "ssid",
7652
+ "wifiPassword",
7653
+ "userName",
7654
+ "username",
7655
+ "user"
7656
+ ]);
7657
+ IPV4_RE = /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g;
7658
+ MAC_RE = /\b([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\b/g;
7398
7659
  }
7399
7660
  });
7400
7661
 
@@ -14851,7 +15112,7 @@ function computeDeviceCapabilities(params) {
14851
15112
  const ptzControlRaw = supportItem ? supportItem.ptzControl : void 0;
14852
15113
  const supportExplicitlyDefinesPtz = ptzTypeRaw !== void 0 || ptzControlRaw !== void 0;
14853
15114
  const ptzExplicitlyDisabledBySupportItem = supportExplicitlyDefinesPtz && !isTruthyNumberLike(ptzTypeRaw) && !isTruthyNumberLike(ptzControlRaw);
14854
- const hasPtzFromSupportItem = isTruthyNumberLike(ptzTypeRaw) || isTruthyNumberLike(ptzControlRaw) || isTruthyNumberLike(supportItem?.ptzPreset);
15115
+ const hasPtzFromSupportItem = isTruthyNumberLike(ptzTypeRaw);
14855
15116
  const ptzDisabledBySupport = (ptzMode === "none" || ptzMode === "0") && !hasPtzFromSupportItem;
14856
15117
  const hasBatteryFromSupport = supportItem ? isTruthyNumberLike(supportItem.battery) : false;
14857
15118
  const ptzPresetRaw = supportItem ? supportItem.ptzPreset : void 0;