@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.
@@ -4,24 +4,28 @@ import {
4
4
  collectMultifocalDiagnostics,
5
5
  collectNativeDiagnostics,
6
6
  collectNvrDiagnostics,
7
+ computeExpectedStreamCompatibility,
7
8
  createDiagnosticsBundle,
8
9
  printNvrDiagnostics,
9
10
  runAllDiagnosticsConsecutively,
10
11
  runMultifocalDiagnosticsConsecutively,
11
12
  sampleStreams,
13
+ sanitizeFixtureData,
12
14
  testChannelStreams
13
- } from "./chunk-6Q6MK4WG.js";
15
+ } from "./chunk-DUHWTZ7U.js";
14
16
  export {
15
17
  captureModelFixtures,
16
18
  collectCgiDiagnostics,
17
19
  collectMultifocalDiagnostics,
18
20
  collectNativeDiagnostics,
19
21
  collectNvrDiagnostics,
22
+ computeExpectedStreamCompatibility,
20
23
  createDiagnosticsBundle,
21
24
  printNvrDiagnostics,
22
25
  runAllDiagnosticsConsecutively,
23
26
  runMultifocalDiagnosticsConsecutively,
24
27
  sampleStreams,
28
+ sanitizeFixtureData,
25
29
  testChannelStreams
26
30
  };
27
- //# sourceMappingURL=DiagnosticsTools-DQDDBRM6.js.map
31
+ //# sourceMappingURL=DiagnosticsTools-XIIYZDXL.js.map
@@ -7170,10 +7170,123 @@ async function runAllDiagnosticsConsecutively(params) {
7170
7170
  streamsDir
7171
7171
  };
7172
7172
  }
7173
+ var REDACT_KEYS = /* @__PURE__ */ new Set([
7174
+ "password",
7175
+ "pass",
7176
+ "token",
7177
+ "secret",
7178
+ "apiKey",
7179
+ "api_key"
7180
+ ]);
7181
+ var MASK_KEYS = /* @__PURE__ */ new Set([
7182
+ "serialNumber",
7183
+ "serial",
7184
+ "uid",
7185
+ "mac",
7186
+ "ssid",
7187
+ "wifiPassword",
7188
+ "userName",
7189
+ "username",
7190
+ "user"
7191
+ ]);
7192
+ var IPV4_RE = /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/g;
7193
+ var MAC_RE = /\b([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\b/g;
7194
+ function maskIp(ip) {
7195
+ const parts = ip.split(".");
7196
+ return `${parts[0]}.***.***.***`;
7197
+ }
7198
+ function maskMac(mac) {
7199
+ const sep = mac.includes("-") ? "-" : ":";
7200
+ const parts = mac.split(/[:-]/);
7201
+ return `${parts[0]}${sep}${parts[1]}${sep}**${sep}**${sep}**${sep}**`;
7202
+ }
7203
+ function maskSerial(val) {
7204
+ if (val.length <= 4) return "****";
7205
+ return val.slice(0, 2) + "*".repeat(val.length - 4) + val.slice(-2);
7206
+ }
7207
+ function sanitizeString(s) {
7208
+ let out = s;
7209
+ out = out.replace(/:\/\/([^:@]+):([^@]+)@/g, "://***:***@");
7210
+ out = out.replace(/(password=)[^&\s]*/gi, "$1***");
7211
+ out = out.replace(/(user=)[^&\s]*/gi, "$1***");
7212
+ out = out.replace(IPV4_RE, (match) => maskIp(match));
7213
+ out = out.replace(MAC_RE, (match) => maskMac(match));
7214
+ return out;
7215
+ }
7216
+ function sanitizeFixtureData(value) {
7217
+ if (value === null || value === void 0) return value;
7218
+ if (typeof value === "string") {
7219
+ return sanitizeString(value);
7220
+ }
7221
+ if (Array.isArray(value)) {
7222
+ return value.map(sanitizeFixtureData);
7223
+ }
7224
+ if (typeof value === "object") {
7225
+ const out = {};
7226
+ for (const [k, v] of Object.entries(value)) {
7227
+ const kLower = k.toLowerCase();
7228
+ if (REDACT_KEYS.has(kLower)) {
7229
+ out[k] = "***";
7230
+ } else if (MASK_KEYS.has(kLower) || kLower === "serialnumber") {
7231
+ out[k] = typeof v === "string" ? maskSerial(v) : "***";
7232
+ } else if (kLower === "mac") {
7233
+ out[k] = typeof v === "string" ? maskMac(v) : "***";
7234
+ } else if (kLower === "ip" || kLower === "ipaddress" || kLower === "host") {
7235
+ out[k] = typeof v === "string" ? maskIp(v) : v;
7236
+ } else if (kLower === "name" && typeof v === "string" && k !== "name") {
7237
+ out[k] = v;
7238
+ } else {
7239
+ out[k] = sanitizeFixtureData(v);
7240
+ }
7241
+ }
7242
+ return out;
7243
+ }
7244
+ return value;
7245
+ }
7246
+ function computeExpectedStreamCompatibility(params) {
7247
+ const { channelCount } = params;
7248
+ const profiles = params.profiles ?? ["main", "sub", "ext"];
7249
+ const expectedStreamType = { main: 0, sub: 1, ext: 0 };
7250
+ const allStreams = [];
7251
+ for (let ch = 0; ch < channelCount; ch++) {
7252
+ for (const p of profiles) {
7253
+ allStreams.push({ ch, profile: p, label: channelCount > 1 ? `ch${ch}_${p}` : p });
7254
+ }
7255
+ }
7256
+ const results = [];
7257
+ for (let i = 0; i < allStreams.length; i++) {
7258
+ for (let j = i + 1; j < allStreams.length; j++) {
7259
+ const a = allStreams[i];
7260
+ const b = allStreams[j];
7261
+ const stA = expectedStreamType[a.profile] ?? 0;
7262
+ const stB = expectedStreamType[b.profile] ?? 0;
7263
+ const sameChannel = a.ch === b.ch;
7264
+ let expectedOk;
7265
+ let reason;
7266
+ if (sameChannel && stA === stB) {
7267
+ expectedOk = false;
7268
+ reason = `same streamType (${stA}) on same channel`;
7269
+ } else if (!sameChannel && a.profile === b.profile) {
7270
+ expectedOk = false;
7271
+ reason = `multifocal rejects same profile (${a.profile}) across channels`;
7272
+ } else if (!sameChannel && stA === stB) {
7273
+ expectedOk = false;
7274
+ reason = `same streamType (${stA}) across channels`;
7275
+ } else {
7276
+ expectedOk = true;
7277
+ reason = `different streamTypes (${stA} vs ${stB})`;
7278
+ }
7279
+ results.push({ pair: [a.label, b.label], expectedOk, reason });
7280
+ }
7281
+ }
7282
+ return results;
7283
+ }
7173
7284
  async function captureModelFixtures(params) {
7174
7285
  const { api, channel, outDir } = params;
7175
7286
  const log = params.log ?? console.log;
7176
7287
  mkdirp(outDir);
7288
+ const writeJsonSafe = (filePath, data) => writeJson(filePath, sanitizeFixtureData(data));
7289
+ const writeTextSafe = (filePath, text) => writeText(filePath, sanitizeString(text));
7177
7290
  const calls = {};
7178
7291
  const errors = [];
7179
7292
  async function capture(name, fn, writer) {
@@ -7196,123 +7309,269 @@ async function captureModelFixtures(params) {
7196
7309
  const info = await capture(
7197
7310
  "getInfo",
7198
7311
  () => api.getInfo(channel),
7199
- (v) => writeJson(path4.join(outDir, "device-info.json"), v)
7312
+ (v) => writeJsonSafe(path4.join(outDir, "device-info.json"), v)
7200
7313
  );
7201
7314
  const support = await capture(
7202
7315
  "getSupportInfo",
7203
7316
  () => api.getSupportInfo(),
7204
- (v) => writeJson(path4.join(outDir, "support-info.json"), v)
7317
+ (v) => writeJsonSafe(path4.join(outDir, "support-info.json"), v)
7205
7318
  );
7206
7319
  const abilities = await capture(
7207
7320
  "getAbilityInfo",
7208
7321
  () => api.getAbilityInfo(),
7209
- (v) => writeJson(path4.join(outDir, "ability-info.json"), v)
7322
+ (v) => writeJsonSafe(path4.join(outDir, "ability-info.json"), v)
7210
7323
  );
7211
7324
  await capture(
7212
7325
  "getDeviceCapabilities",
7213
7326
  () => api.getDeviceCapabilities(channel),
7214
- (v) => writeJson(path4.join(outDir, "capabilities.json"), v)
7327
+ (v) => writeJsonSafe(path4.join(outDir, "capabilities.json"), v)
7215
7328
  );
7216
7329
  await capture("cmd289-WhiteLed", () => api.sendXml({
7217
7330
  cmdId: BC_CMD_ID_GET_WHITE_LED,
7218
7331
  channel,
7219
7332
  timeoutMs: 3e3
7220
- }), (v) => writeText(path4.join(outDir, "cmd289-white-led.xml"), v));
7333
+ }), (v) => writeTextSafe(path4.join(outDir, "cmd289-white-led.xml"), v));
7221
7334
  await capture(
7222
7335
  "getStreamMetadata",
7223
7336
  () => api.getStreamMetadata(channel),
7224
- (v) => writeJson(path4.join(outDir, "stream-metadata.json"), v)
7337
+ (v) => writeJsonSafe(path4.join(outDir, "stream-metadata.json"), v)
7225
7338
  );
7226
7339
  await capture(
7227
7340
  "getEncXml",
7228
7341
  () => api.getEncXml(channel),
7229
- (v) => writeText(path4.join(outDir, "enc-config.xml"), v)
7342
+ (v) => writeTextSafe(path4.join(outDir, "enc-config.xml"), v)
7230
7343
  );
7231
7344
  await capture(
7232
7345
  "getPorts",
7233
7346
  () => api.getPorts(),
7234
- (v) => writeJson(path4.join(outDir, "ports.json"), v)
7347
+ (v) => writeJsonSafe(path4.join(outDir, "ports.json"), v)
7235
7348
  );
7236
7349
  await capture(
7237
7350
  "getTalkAbility",
7238
7351
  () => api.getTalkAbility(channel),
7239
- (v) => writeJson(path4.join(outDir, "talk-ability.json"), v)
7352
+ (v) => writeJsonSafe(path4.join(outDir, "talk-ability.json"), v)
7240
7353
  );
7241
7354
  await capture(
7242
7355
  "getTwoWayAudioConfig",
7243
7356
  () => api.getTwoWayAudioConfig(channel),
7244
- (v) => writeJson(path4.join(outDir, "two-way-audio-config.json"), v)
7357
+ (v) => writeJsonSafe(path4.join(outDir, "two-way-audio-config.json"), v)
7245
7358
  );
7246
7359
  await capture(
7247
7360
  "getAiState",
7248
7361
  () => api.getAiState(channel),
7249
- (v) => writeJson(path4.join(outDir, "ai-state.json"), v)
7362
+ (v) => writeJsonSafe(path4.join(outDir, "ai-state.json"), v)
7250
7363
  );
7251
7364
  await capture(
7252
7365
  "getAiCfg",
7253
7366
  () => api.getAiCfg(channel),
7254
- (v) => writeJson(path4.join(outDir, "ai-cfg.json"), v)
7367
+ (v) => writeJsonSafe(path4.join(outDir, "ai-cfg.json"), v)
7255
7368
  );
7256
7369
  await capture(
7257
7370
  "getOsd",
7258
7371
  () => api.getOsd(channel),
7259
- (v) => writeJson(path4.join(outDir, "osd.json"), v)
7372
+ (v) => writeJsonSafe(path4.join(outDir, "osd.json"), v)
7260
7373
  );
7261
7374
  await capture(
7262
7375
  "getMotionAlarm",
7263
7376
  () => api.getMotionAlarm(channel),
7264
- (v) => writeJson(path4.join(outDir, "motion-alarm.json"), v)
7377
+ (v) => writeJsonSafe(path4.join(outDir, "motion-alarm.json"), v)
7265
7378
  );
7266
7379
  await capture(
7267
7380
  "getRecordCfg",
7268
7381
  () => api.getRecordCfg(channel),
7269
- (v) => writeJson(path4.join(outDir, "record-cfg.json"), v)
7382
+ (v) => writeJsonSafe(path4.join(outDir, "record-cfg.json"), v)
7270
7383
  );
7271
7384
  await capture(
7272
7385
  "getVideoInput",
7273
7386
  () => api.getVideoInput(channel),
7274
- (v) => writeJson(path4.join(outDir, "video-input.json"), v)
7387
+ (v) => writeJsonSafe(path4.join(outDir, "video-input.json"), v)
7275
7388
  );
7276
7389
  await capture(
7277
7390
  "getPtzPresets",
7278
7391
  () => api.getPtzPresets(channel),
7279
- (v) => writeJson(path4.join(outDir, "ptz-presets.json"), v)
7392
+ (v) => writeJsonSafe(path4.join(outDir, "ptz-presets.json"), v)
7280
7393
  );
7281
7394
  await capture(
7282
7395
  "getNetworkInfo",
7283
7396
  () => api.getNetworkInfo(),
7284
- (v) => writeJson(path4.join(outDir, "network-info.json"), v)
7397
+ (v) => writeJsonSafe(path4.join(outDir, "network-info.json"), v)
7285
7398
  );
7286
7399
  await capture(
7287
7400
  "getSystemGeneral",
7288
7401
  () => api.getSystemGeneral(),
7289
- (v) => writeJson(path4.join(outDir, "system-general.json"), v)
7402
+ (v) => writeJsonSafe(path4.join(outDir, "system-general.json"), v)
7290
7403
  );
7291
7404
  await capture(
7292
7405
  "getWifiSignal",
7293
7406
  () => api.getWifiSignal(channel),
7294
- (v) => writeJson(path4.join(outDir, "wifi-signal.json"), v)
7407
+ (v) => writeJsonSafe(path4.join(outDir, "wifi-signal.json"), v)
7295
7408
  );
7296
7409
  await capture(
7297
7410
  "getWhiteLedState",
7298
7411
  () => api.getWhiteLedState(channel),
7299
- (v) => writeJson(path4.join(outDir, "white-led-state.json"), v)
7412
+ (v) => writeJsonSafe(path4.join(outDir, "white-led-state.json"), v)
7300
7413
  );
7301
7414
  await capture(
7302
7415
  "getFloodlightOnMotion",
7303
7416
  () => api.getFloodlightOnMotion(channel),
7304
- (v) => writeJson(path4.join(outDir, "floodlight-on-motion.json"), v)
7417
+ (v) => writeJsonSafe(path4.join(outDir, "floodlight-on-motion.json"), v)
7305
7418
  );
7306
7419
  await capture(
7307
7420
  "buildVideoStreamOptions",
7308
7421
  () => api.buildVideoStreamOptions({ channel }),
7309
- (v) => writeJson(path4.join(outDir, "video-stream-options.json"), v)
7422
+ (v) => writeJsonSafe(path4.join(outDir, "video-stream-options.json"), v)
7423
+ );
7424
+ await capture(
7425
+ "getDualLensChannelInfo",
7426
+ () => api.getDualLensChannelInfo(channel),
7427
+ (v) => writeJsonSafe(path4.join(outDir, "dual-lens-info.json"), v)
7310
7428
  );
7429
+ await capture("streamCombinationTest", async () => {
7430
+ let dualLensInfo;
7431
+ try {
7432
+ dualLensInfo = await api.getDualLensChannelInfo(channel);
7433
+ } catch {
7434
+ }
7435
+ const isMultifocal = dualLensInfo?.isDualLens === true;
7436
+ const channelCount = dualLensInfo?.streamChannelCount ?? 1;
7437
+ const allStreams = [];
7438
+ const profiles = ["main", "sub", "ext"];
7439
+ for (let ch = 0; ch < channelCount; ch++) {
7440
+ for (const p of profiles) {
7441
+ const label = channelCount > 1 ? `ch${ch}_${p}` : p;
7442
+ allStreams.push({ ch, profile: p, label });
7443
+ }
7444
+ }
7445
+ const pairs = [];
7446
+ for (let i = 0; i < allStreams.length; i++) {
7447
+ for (let j = i + 1; j < allStreams.length; j++) {
7448
+ pairs.push([allStreams[i], allStreams[j]]);
7449
+ }
7450
+ }
7451
+ const expectedStreamType = { main: 0, sub: 1, ext: 0 };
7452
+ const expectedCompat = [];
7453
+ for (const [a, b] of pairs) {
7454
+ const stA = expectedStreamType[a.profile];
7455
+ const stB = expectedStreamType[b.profile];
7456
+ const sameChannel = a.ch === b.ch;
7457
+ let expectedOk;
7458
+ let reason;
7459
+ if (sameChannel && stA === stB) {
7460
+ expectedOk = false;
7461
+ reason = `same streamType (${stA}) on same channel`;
7462
+ } else if (!sameChannel && a.profile === b.profile) {
7463
+ expectedOk = false;
7464
+ reason = `multifocal rejects same profile (${a.profile}) across channels`;
7465
+ } else if (!sameChannel && stA === stB) {
7466
+ expectedOk = false;
7467
+ reason = `same streamType (${stA}) across channels`;
7468
+ } else {
7469
+ expectedOk = true;
7470
+ reason = `different streamTypes (${stA} vs ${stB})`;
7471
+ }
7472
+ expectedCompat.push({ pair: [a.label, b.label], expectedOk, reason });
7473
+ }
7474
+ const results = [];
7475
+ const TEST_DURATION_MS = 4e3;
7476
+ for (let pairIdx = 0; pairIdx < pairs.length; pairIdx++) {
7477
+ const [a, b] = pairs[pairIdx];
7478
+ const expected = expectedCompat[pairIdx];
7479
+ log(` testing ${a.label}+${b.label} on shared socket...`);
7480
+ let session;
7481
+ try {
7482
+ session = await api.createDedicatedSession(
7483
+ `test:combo:${a.label}_${b.label}`
7484
+ );
7485
+ } catch (e) {
7486
+ const result2 = {
7487
+ pair: [a.label, b.label],
7488
+ ok: false,
7489
+ framesA: 0,
7490
+ framesB: 0,
7491
+ mismatches: 0,
7492
+ error: `session: ${e instanceof Error ? e.message : String(e)}`,
7493
+ durationMs: 0,
7494
+ expectedOk: expected.expectedOk,
7495
+ expectedReason: expected.reason,
7496
+ matchesExpected: !expected.expectedOk
7497
+ // failed as expected if we expected failure
7498
+ };
7499
+ results.push(result2);
7500
+ continue;
7501
+ }
7502
+ const testClient = session.client;
7503
+ let framesA = 0;
7504
+ let framesB = 0;
7505
+ let mismatches = 0;
7506
+ const start = Date.now();
7507
+ const stTypes = {
7508
+ main: /* @__PURE__ */ new Set([0, 2]),
7509
+ sub: /* @__PURE__ */ new Set([1, 3]),
7510
+ ext: /* @__PURE__ */ new Set([0, 2])
7511
+ };
7512
+ const setA = stTypes[a.profile];
7513
+ const setB = stTypes[b.profile];
7514
+ const onFrame = (frame) => {
7515
+ if (frame.header?.cmdId !== BC_CMD_ID_VIDEO) return;
7516
+ const st = frame.header.streamType;
7517
+ if (setA.has(st)) framesA++;
7518
+ else if (setB.has(st)) framesB++;
7519
+ else mismatches++;
7520
+ };
7521
+ testClient.on("frame", onFrame);
7522
+ let error;
7523
+ try {
7524
+ await api.startVideoStream(a.ch, a.profile, { client: testClient });
7525
+ await api.startVideoStream(b.ch, b.profile, { client: testClient });
7526
+ await new Promise((r) => setTimeout(r, TEST_DURATION_MS));
7527
+ await api.stopVideoStream(a.ch, a.profile, { client: testClient }).catch(() => {
7528
+ });
7529
+ await api.stopVideoStream(b.ch, b.profile, { client: testClient }).catch(() => {
7530
+ });
7531
+ } catch (e) {
7532
+ error = e instanceof Error ? e.message : String(e);
7533
+ } finally {
7534
+ testClient.removeListener("frame", onFrame);
7535
+ await session.release().catch(() => {
7536
+ });
7537
+ }
7538
+ const elapsed = Date.now() - start;
7539
+ const ok2 = !error && framesA > 0 && framesB > 0 && mismatches === 0;
7540
+ const result = {
7541
+ pair: [a.label, b.label],
7542
+ ok: ok2,
7543
+ framesA,
7544
+ framesB,
7545
+ mismatches,
7546
+ ...error ? { error } : {},
7547
+ durationMs: elapsed,
7548
+ expectedOk: expected.expectedOk,
7549
+ expectedReason: expected.reason,
7550
+ matchesExpected: ok2 === expected.expectedOk
7551
+ };
7552
+ results.push(result);
7553
+ const matchStr = result.matchesExpected ? "" : " *** UNEXPECTED ***";
7554
+ log(` ${a.label}+${b.label}: ${ok2 ? "OK" : "FAIL"} (expected=${expected.expectedOk ? "OK" : "FAIL"}) A=${framesA} B=${framesB} mismatch=${mismatches}${matchStr}`);
7555
+ }
7556
+ const unexpected = results.filter((r) => !r.matchesExpected);
7557
+ return {
7558
+ isMultifocal,
7559
+ channelCount,
7560
+ results,
7561
+ summary: {
7562
+ total: results.length,
7563
+ ok: results.filter((r) => r.ok).length,
7564
+ failed: results.filter((r) => !r.ok).length,
7565
+ matchesExpected: results.filter((r) => r.matchesExpected).length,
7566
+ unexpected: unexpected.map((r) => `${r.pair[0]}+${r.pair[1]}: got ${r.ok ? "OK" : "FAIL"}, expected ${r.expectedOk ? "OK" : "FAIL"}`)
7567
+ }
7568
+ };
7569
+ }, (v) => writeJsonSafe(path4.join(outDir, "stream-combination-test.json"), v));
7311
7570
  const total = Object.keys(calls).length;
7312
7571
  const ok = Object.values(calls).filter((c) => c.ok).length;
7313
7572
  const failed = total - ok;
7314
7573
  const summary = { total, ok, failed, errors };
7315
- writeJson(path4.join(outDir, "_summary.json"), {
7574
+ writeJsonSafe(path4.join(outDir, "_summary.json"), {
7316
7575
  collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
7317
7576
  model: info?.type ?? "unknown",
7318
7577
  itemNo: info?.itemNo ?? "unknown",
@@ -7518,8 +7777,10 @@ export {
7518
7777
  collectMultifocalDiagnostics,
7519
7778
  runMultifocalDiagnosticsConsecutively,
7520
7779
  runAllDiagnosticsConsecutively,
7780
+ sanitizeFixtureData,
7781
+ computeExpectedStreamCompatibility,
7521
7782
  captureModelFixtures,
7522
7783
  parseRecordingFileName,
7523
7784
  ReolinkCgiApi
7524
7785
  };
7525
- //# sourceMappingURL=chunk-6Q6MK4WG.js.map
7786
+ //# sourceMappingURL=chunk-DUHWTZ7U.js.map