@7365admin1/core 3.42.1 → 3.42.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/index.mjs CHANGED
@@ -21947,7 +21947,6 @@ async function sendEmail({ to, subject, text, html }) {
21947
21947
  pass: MAILER_PASSWORD
21948
21948
  }
21949
21949
  });
21950
- console.log(MAILER_EMAIL, MAILER_PASSWORD, MAILER_TRANSPORT_HOST, MAILER_TRANSPORT_PORT);
21951
21950
  const mailOptions = {
21952
21951
  from: `Seven 365 <${MAILER_EMAIL}>`,
21953
21952
  to,
@@ -23993,6 +23992,86 @@ var loggerDahua = winston.createLogger({
23993
23992
  ]
23994
23993
  });
23995
23994
 
23995
+ // src/utils/camera-alert.util.ts
23996
+ var DIRECTION_WORDS = {
23997
+ entry: "entry",
23998
+ exit: "exit",
23999
+ both: "entry and exit",
24000
+ visitors: "visitor",
24001
+ residents: "resident"
24002
+ };
24003
+ function describeCamera(camera) {
24004
+ const site = String(camera?.siteName ?? "").trim();
24005
+ const name = String(camera?.name ?? "").trim();
24006
+ const at = site ? ` at ${site}` : "";
24007
+ if (name)
24008
+ return `Camera "${name}"${at}`;
24009
+ const words = DIRECTION_WORDS[String(camera?.direction ?? "").trim().toLowerCase()];
24010
+ return words ? `The ${words} ANPR camera${at}` : `The ANPR camera${at}`;
24011
+ }
24012
+ function cameraUnreachableMessage(camera) {
24013
+ return `${describeCamera(camera)} is not responding. Plate reads and automatic barrier opening are stopped while it is down. The server keeps trying to reconnect on its own. If it does not come back, ask your site technician or the security-system installer to check the camera's power and network.`;
24014
+ }
24015
+ function cameraRecoveredMessage(camera) {
24016
+ return `${describeCamera(camera)} is responding again. Plate reads have resumed.`;
24017
+ }
24018
+ function cameraCredentialsMessage(camera) {
24019
+ return `${describeCamera(camera)} refused the login, so it cannot read plates. The username or password saved for it is wrong. An administrator can correct them in Site Settings > Cameras; if they are already correct, the camera's own account needs checking by your site technician.`;
24020
+ }
24021
+ function cameraNotAuthorisedMessage(camera) {
24022
+ return `${describeCamera(camera)} rejected our connection, so it cannot read plates. This usually means its account is not allowed to use the plate-reading feed. Please ask your site technician or the security-system installer to check the camera's account permissions.`;
24023
+ }
24024
+ var CAMERA_ALERT_AT_ATTEMPT = [1, 6, 30, 90];
24025
+ var CAMERA_ALERT_EVERY_AFTER = 90;
24026
+ function shouldAlertOnFailure(consecutiveFailures) {
24027
+ if (!Number.isInteger(consecutiveFailures) || consecutiveFailures < 1)
24028
+ return false;
24029
+ if (CAMERA_ALERT_AT_ATTEMPT.includes(consecutiveFailures))
24030
+ return true;
24031
+ if (consecutiveFailures < CAMERA_ALERT_EVERY_AFTER)
24032
+ return false;
24033
+ return (consecutiveFailures - CAMERA_ALERT_EVERY_AFTER) % CAMERA_ALERT_EVERY_AFTER === 0;
24034
+ }
24035
+
24036
+ // src/utils/dahua-protocol.util.ts
24037
+ var DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT = 2;
24038
+ var AUTH_BACKOFF_MS = [6e4, 3e5, 9e5];
24039
+ function dahuaAuthBackoffMs(consecutiveAuthFailures) {
24040
+ const index = Math.min(
24041
+ Math.max(consecutiveAuthFailures, 1),
24042
+ AUTH_BACKOFF_MS.length
24043
+ );
24044
+ return AUTH_BACKOFF_MS[index - 1];
24045
+ }
24046
+ function classifyDahuaStatus(statusCode) {
24047
+ if (statusCode === 401)
24048
+ return "challenge";
24049
+ if (statusCode === 403)
24050
+ return "rejected";
24051
+ if (statusCode === 400 || statusCode === 500)
24052
+ return "fatal";
24053
+ return "proceed";
24054
+ }
24055
+ function classifyDahuaFailure(message) {
24056
+ const text = String(message ?? "");
24057
+ if (/\b403\b|forbidden/i.test(text))
24058
+ return "rejected";
24059
+ if (/\b401\b|unauthorized|invalid authority|digest/i.test(text)) {
24060
+ return "challenge";
24061
+ }
24062
+ return "transient";
24063
+ }
24064
+ var enc = (value) => encodeURIComponent(String(value));
24065
+ function buildPlateUpdateEndpoint(value) {
24066
+ return `/cgi-bin/recordUpdater.cgi?action=update&name=${enc(value.mode)}&recno=${enc(value.recno)}&PlateNumber=${enc(value.plateNumber)}&BeginTime=${enc(value.start)}&CancelTime=${enc(value.end)}&+OpenGate=${value.openGate}&MasterOfCar=${enc(value.owner)}`;
24067
+ }
24068
+ function buildPlateInsertEndpoint(value) {
24069
+ return `/cgi-bin/recordUpdater.cgi?action=insert&name=${enc(value.mode)}&PlateNumber=${enc(value.plateNumber)}&VehicleType=${enc(value.vehicleType)}&VehicleColor=${enc(value.vehicleColor)}&BeginTime=${enc(value.start)}&CancelTime=${enc(value.end)}&+OpenGate=${value.openGate}&MasterOfCar=${enc(value.owner)}`;
24070
+ }
24071
+ function buildPlateRemoveEndpoint(value) {
24072
+ return `/cgi-bin/recordUpdater.cgi?action=remove&recno=${enc(value.recno)}&name=${enc(value.mode)}`;
24073
+ }
24074
+
23996
24075
  // src/services/dahua.service.ts
23997
24076
  var cameraRegistry = /* @__PURE__ */ new Map();
23998
24077
  var _savedOnDetected;
@@ -24038,7 +24117,8 @@ async function useDahuaDigestWithRetry({
24038
24117
  retryDelayMs = 0
24039
24118
  }) {
24040
24119
  let lastError;
24041
- for (let attempt = 1; attempt <= retries; attempt++) {
24120
+ const effectiveRetries = Math.min(retries, DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT);
24121
+ for (let attempt = 1; attempt <= effectiveRetries; attempt++) {
24042
24122
  try {
24043
24123
  const effectiveTimeout = streaming ? 0 : timeout;
24044
24124
  const response = await useDahuaDigest({
@@ -24065,9 +24145,9 @@ async function useDahuaDigestWithRetry({
24065
24145
  }
24066
24146
  const errorText = streaming ? `HTTP ${statusCode}` : getDahuaResponseText(response);
24067
24147
  const shouldRetry = isDahuaInvalidAuthority(errorText);
24068
- if (shouldRetry && attempt < retries) {
24148
+ if (shouldRetry && attempt < effectiveRetries) {
24069
24149
  loggerDahua.warn(
24070
- `[${host}] Dahua Invalid Authority. Retrying ${attempt}/${retries}`
24150
+ `[${host}] Dahua Invalid Authority. Retrying ${attempt}/${effectiveRetries}`
24071
24151
  );
24072
24152
  if (retryDelayMs > 0) {
24073
24153
  await sleep(retryDelayMs);
@@ -24079,9 +24159,9 @@ async function useDahuaDigestWithRetry({
24079
24159
  lastError = error;
24080
24160
  const message = error?.message || String(error);
24081
24161
  const shouldRetry = isDahuaInvalidAuthority(message);
24082
- if (shouldRetry && attempt < retries) {
24162
+ if (shouldRetry && attempt < effectiveRetries) {
24083
24163
  loggerDahua.warn(
24084
- `[${host}] Dahua Invalid Authority error. Retrying ${attempt}/${retries}`
24164
+ `[${host}] Dahua Invalid Authority error. Retrying ${attempt}/${effectiveRetries}`
24085
24165
  );
24086
24166
  if (retryDelayMs > 0) {
24087
24167
  await sleep(retryDelayMs);
@@ -24185,7 +24265,9 @@ function useDahuaService() {
24185
24265
  return;
24186
24266
  try {
24187
24267
  const result = await _checkOutBySiteAndPlate(site, plateNumber2);
24188
- console.log("checkOutBySiteAndPlate result", result);
24268
+ loggerDahua.info(
24269
+ `checkOutBySiteAndPlate matched=${Boolean(result)} type=${result?.type ?? "none"}`
24270
+ );
24189
24271
  if (onDetected2 && result?.type != "resident" && result?.site) {
24190
24272
  onDetected2({ reload: true, site: result?.site?.toString(), siteName: camera?.siteName, cameraDirection: camera?.direction, direction });
24191
24273
  }
@@ -24484,9 +24566,12 @@ function useDahuaService() {
24484
24566
  );
24485
24567
  }
24486
24568
  async function getTrafficJunction(camera, signal, onDetected) {
24487
- console.log(`getTrafficJunction camera object`, camera);
24569
+ loggerDahua.info(
24570
+ `getTrafficJunction starting for camera ${camera?._id} [${camera?.siteName}-${camera?.direction}] host ${camera?.host}`
24571
+ );
24488
24572
  let authFailureCount = 0;
24489
- const MAX_AUTH_RETRIES = 10;
24573
+ let connectionFailureCount = 0;
24574
+ let alertedFailureCount = 0;
24490
24575
  while (!signal.aborted) {
24491
24576
  let bufferQueue = null;
24492
24577
  let response = null;
@@ -24501,31 +24586,48 @@ function useDahuaService() {
24501
24586
  streaming: true
24502
24587
  });
24503
24588
  const statusCode = getDahuaStatusCode(response);
24504
- if (statusCode === 401) {
24505
- loggerDahua.error(`[${camera?.siteName}-${camera?.direction}] 401 Unauthorized - Handshake or Wrong Credentials`);
24506
- console.log(`[${camera?.siteName}-${camera?.direction}] 401 Unauthorized - Handshake or Wrong Credentials`);
24507
- throw new Error("401 Unauthorized - Handshake or Wrong Credentials");
24508
- } else if (statusCode === 403) {
24589
+ const verdict = classifyDahuaStatus(statusCode);
24590
+ if (verdict === "challenge") {
24591
+ loggerDahua.warn(`[${camera?.siteName}-${camera?.direction}] 401 - digest challenge not completed; will re-authenticate after backoff.`);
24592
+ throw new Error("401 Unauthorized - digest challenge not completed");
24593
+ } else if (verdict === "rejected") {
24509
24594
  loggerDahua.error(
24510
- `[${camera?.siteName}-${camera?.direction}] 403 Forbidden. Account locked or invalid permissions.`
24595
+ `[${camera?.siteName}-${camera?.direction}] 403 Forbidden - credentials rejected by the device. Not retrying.`
24511
24596
  );
24512
24597
  if (onDetected) {
24513
24598
  onDetected({
24514
24599
  site: camera?.site,
24515
24600
  direction: camera?.direction,
24516
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password and try after 10 minutes.`
24601
+ // Superseded #1803's own wording: cameraCredentialsMessage already
24602
+ // says the login was refused and where to correct it, and no longer
24603
+ // claims a ten-minute lockout.
24604
+ messagePermanent: cameraCredentialsMessage(camera)
24517
24605
  });
24518
24606
  }
24519
24607
  return;
24520
- } else if ([400, 500].includes(statusCode)) {
24608
+ } else if (verdict === "fatal") {
24521
24609
  loggerDahua.error(`[${camera?.siteName}-${camera?.direction}] Connection error: ${statusCode}`);
24522
24610
  if (onDetected) {
24523
- onDetected({ site: camera?.site, messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Possible Not Authorized.` });
24611
+ onDetected({ site: camera?.site, messagePermanent: cameraNotAuthorisedMessage(camera) });
24524
24612
  }
24525
24613
  return;
24526
24614
  }
24527
24615
  authFailureCount = 0;
24528
24616
  isAuthFailure = false;
24617
+ if (alertedFailureCount > 0 && onDetected) {
24618
+ onDetected({
24619
+ site: camera?.site,
24620
+ direction: camera?.direction,
24621
+ // `event` and `camera` let a client group or clear an alert per
24622
+ // camera instead of matching on the message text. Older clients
24623
+ // ignore them and just render `message`, as they always have.
24624
+ event: "camera-recovered",
24625
+ camera: camera?._id?.toString(),
24626
+ message: cameraRecoveredMessage(camera)
24627
+ });
24628
+ }
24629
+ connectionFailureCount = 0;
24630
+ alertedFailureCount = 0;
24529
24631
  loggerDahua.info(`[${camera?.siteName}-${camera?.direction}] Successfully connected to ANPR.`);
24530
24632
  const contentType = response.res.headers["content-type"];
24531
24633
  const boundaryMatch = contentType?.match(/boundary=(.*)$/i);
@@ -24571,50 +24673,53 @@ function useDahuaService() {
24571
24673
  break;
24572
24674
  }
24573
24675
  const errMsg = String(error?.message || error);
24574
- if (errMsg.includes("403") || errMsg.includes("Forbidden")) {
24676
+ const failure = classifyDahuaFailure(errMsg);
24677
+ if (failure === "rejected") {
24575
24678
  loggerDahua.error(
24576
- `[${camera?.siteName}-${camera?.direction}] 403 Forbidden thrown by Dahua client. Account locked or invalid credentials.`
24679
+ `[${camera?.siteName}-${camera?.direction}] 403 Forbidden thrown by Dahua client - credentials rejected. Not retrying.`
24577
24680
  );
24578
24681
  if (onDetected) {
24579
24682
  onDetected({
24580
24683
  site: camera?.site,
24581
24684
  direction: camera?.direction,
24582
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password and try after 10 minutes.`
24685
+ messagePermanent: cameraCredentialsMessage(camera)
24583
24686
  });
24584
24687
  }
24585
24688
  return;
24586
24689
  }
24587
- const isAuthError = errMsg.includes("401") || errMsg.includes("Unauthorized") || errMsg.includes("Invalid Authority");
24588
- if (isAuthError) {
24690
+ if (failure === "challenge") {
24589
24691
  authFailureCount++;
24590
24692
  isAuthFailure = true;
24591
24693
  loggerDahua.warn(
24592
- `[${camera?.siteName}-${camera?.direction}] Auth attempt ${authFailureCount}/${MAX_AUTH_RETRIES}`
24694
+ `[${camera?.siteName}-${camera?.direction}] Authentication attempt ${authFailureCount} failed; next attempt in ${dahuaAuthBackoffMs(authFailureCount) / 1e3}s`
24593
24695
  );
24594
- if (authFailureCount >= MAX_AUTH_RETRIES) {
24696
+ if (authFailureCount === DAHUA_AUTH_ATTEMPTS_BEFORE_ALERT) {
24595
24697
  loggerDahua.error(
24596
- `[${camera?.siteName}-${camera?.direction}] Max auth failures reached (${MAX_AUTH_RETRIES}). Wrong credentials.`
24698
+ `[${camera?.siteName}-${camera?.direction}] ${authFailureCount} consecutive authentication failures. Slowing down and alerting the operator.`
24597
24699
  );
24598
24700
  if (onDetected) {
24599
24701
  onDetected({
24600
24702
  site: camera?.site,
24601
24703
  direction: camera?.direction,
24602
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password.`
24704
+ messagePermanent: cameraCredentialsMessage(camera)
24603
24705
  });
24604
24706
  }
24605
- return;
24606
24707
  }
24607
24708
  } else {
24608
24709
  authFailureCount = 0;
24609
24710
  isAuthFailure = false;
24711
+ connectionFailureCount++;
24610
24712
  loggerDahua.error(
24611
- `[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds...`
24713
+ `[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds... (failure ${connectionFailureCount})`
24612
24714
  );
24613
- if (onDetected) {
24715
+ if (onDetected && shouldAlertOnFailure(connectionFailureCount)) {
24716
+ alertedFailureCount = connectionFailureCount;
24614
24717
  onDetected({
24615
24718
  site: camera?.site,
24616
24719
  direction: camera?.direction,
24617
- message: `Camera in [${camera?.siteName}-${camera?.direction}] Connection Lost. Retrying in 10 seconds. Check the camera if this issue persists, Or set the camera to Inactive to stop this notification.`
24720
+ event: "camera-fault",
24721
+ camera: camera?._id?.toString(),
24722
+ message: cameraUnreachableMessage(camera)
24618
24723
  });
24619
24724
  }
24620
24725
  }
@@ -24638,7 +24743,7 @@ function useDahuaService() {
24638
24743
  }
24639
24744
  }
24640
24745
  if (!signal.aborted) {
24641
- const waitMs = isAuthFailure ? 1e3 : 1e4;
24746
+ const waitMs = isAuthFailure ? dahuaAuthBackoffMs(authFailureCount) : 1e4;
24642
24747
  await new Promise((res) => setTimeout(res, waitMs));
24643
24748
  }
24644
24749
  }
@@ -24651,7 +24756,9 @@ function useDahuaService() {
24651
24756
  return match ? match[1] : null;
24652
24757
  }
24653
24758
  async function addPlateNumber(value) {
24654
- console.log("addPlateNumber called with value:", value);
24759
+ loggerDahua.info(
24760
+ `addPlateNumber called for host ${value?.host} mode ${value?.mode}`
24761
+ );
24655
24762
  let recno = null;
24656
24763
  const validation = Joi42.object({
24657
24764
  host: Joi42.string().required(),
@@ -24796,7 +24903,15 @@ function useDahuaService() {
24796
24903
  const openGateString = String(value.isOpenGate);
24797
24904
  _isOpenGate = openGateString ? openGateString : "true";
24798
24905
  }
24799
- const endpoint = `/cgi-bin/recordUpdater.cgi?action=update&name=${value.mode}&recno=${value.recno}&PlateNumber=${value.plateNumber}&BeginTime=${value.start}&CancelTime=${value.end}&+OpenGate=${_isOpenGate}&MasterOfCar=${value.owner}`;
24906
+ const endpoint = buildPlateUpdateEndpoint({
24907
+ mode: value.mode,
24908
+ recno: value.recno,
24909
+ plateNumber: value.plateNumber,
24910
+ start: value.start,
24911
+ end: value.end,
24912
+ owner: value.owner,
24913
+ openGate: _isOpenGate
24914
+ });
24800
24915
  try {
24801
24916
  const response = await useDahuaDigest({
24802
24917
  host: value.host,
@@ -24829,7 +24944,10 @@ function useDahuaService() {
24829
24944
  host: value.host,
24830
24945
  username: value.username,
24831
24946
  password: value.password,
24832
- endpoint: `/cgi-bin/recordUpdater.cgi?action=remove&recno=${value.recno}&name=${value.mode}`
24947
+ endpoint: buildPlateRemoveEndpoint({
24948
+ recno: value.recno,
24949
+ mode: value.mode
24950
+ })
24833
24951
  });
24834
24952
  return response;
24835
24953
  } catch (error2) {
@@ -24885,7 +25003,16 @@ function useDahuaService() {
24885
25003
  value.vehicleColor = String(value.vehicleColor ?? "").substring(0, 31) || "unknown";
24886
25004
  const _openGate = String(value.isOpenGate);
24887
25005
  const isOpenGateString = _openGate && _openGate !== "undefined" ? _openGate : "true";
24888
- const endpoint = `/cgi-bin/recordUpdater.cgi?action=insert&name=${value.mode}&PlateNumber=${value.plateNumber}&VehicleType=${value.vehicleType}&VehicleColor=${value.vehicleColor}&BeginTime=${value.start}&CancelTime=${value.end}&+OpenGate=${isOpenGateString}&MasterOfCar=${value.owner}`;
25006
+ const endpoint = buildPlateInsertEndpoint({
25007
+ mode: value.mode,
25008
+ plateNumber: value.plateNumber,
25009
+ vehicleType: value.vehicleType,
25010
+ vehicleColor: value.vehicleColor,
25011
+ start: value.start,
25012
+ end: value.end,
25013
+ owner: value.owner,
25014
+ openGate: isOpenGateString
25015
+ });
24889
25016
  try {
24890
25017
  const response = await useDahuaDigest({
24891
25018
  host: value.host,
@@ -25311,7 +25438,7 @@ function useSiteCameraRepo() {
25311
25438
  }
25312
25439
  if (result?._id) {
25313
25440
  const result2 = await findOne({ _id: new ObjectId47(result._id) }, void 0, { session });
25314
- console.log("updateById result2", result2);
25441
+ logger51.info(`Site camera ${result2?._id} re-read after update; reconnecting listener`);
25315
25442
  if (result2?._id) {
25316
25443
  const { listenToCamera } = useDahuaService();
25317
25444
  await listenToCamera(result2);
@@ -27625,7 +27752,9 @@ function useVehicleService() {
27625
27752
  siteCameras.push(...siteCameraReq.items);
27626
27753
  page++;
27627
27754
  } while (page <= pages);
27628
- console.log("add vehicle service siteCameras", siteCameras);
27755
+ logger55.info(
27756
+ `addVehicle resolved ${siteCameras.length} site camera(s) for site ${value?.site}`
27757
+ );
27629
27758
  }
27630
27759
  for (const plateNumber of plateNumbers) {
27631
27760
  const vehicleValue = {