@7365admin1/core 3.32.2-staging.73 → 3.32.2-staging.75

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.js CHANGED
@@ -24737,6 +24737,47 @@ var loggerDahua = winston.createLogger({
24737
24737
  ]
24738
24738
  });
24739
24739
 
24740
+ // src/utils/camera-alert.util.ts
24741
+ var DIRECTION_WORDS = {
24742
+ entry: "entry",
24743
+ exit: "exit",
24744
+ both: "entry and exit",
24745
+ visitors: "visitor",
24746
+ residents: "resident"
24747
+ };
24748
+ function describeCamera(camera) {
24749
+ const site = String(camera?.siteName ?? "").trim();
24750
+ const name = String(camera?.name ?? "").trim();
24751
+ const at = site ? ` at ${site}` : "";
24752
+ if (name)
24753
+ return `Camera "${name}"${at}`;
24754
+ const words = DIRECTION_WORDS[String(camera?.direction ?? "").trim().toLowerCase()];
24755
+ return words ? `The ${words} ANPR camera${at}` : `The ANPR camera${at}`;
24756
+ }
24757
+ function cameraUnreachableMessage(camera) {
24758
+ 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.`;
24759
+ }
24760
+ function cameraRecoveredMessage(camera) {
24761
+ return `${describeCamera(camera)} is responding again. Plate reads have resumed.`;
24762
+ }
24763
+ function cameraCredentialsMessage(camera) {
24764
+ 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.`;
24765
+ }
24766
+ function cameraNotAuthorisedMessage(camera) {
24767
+ 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.`;
24768
+ }
24769
+ var CAMERA_ALERT_AT_ATTEMPT = [1, 6, 30, 90];
24770
+ var CAMERA_ALERT_EVERY_AFTER = 90;
24771
+ function shouldAlertOnFailure(consecutiveFailures) {
24772
+ if (!Number.isInteger(consecutiveFailures) || consecutiveFailures < 1)
24773
+ return false;
24774
+ if (CAMERA_ALERT_AT_ATTEMPT.includes(consecutiveFailures))
24775
+ return true;
24776
+ if (consecutiveFailures < CAMERA_ALERT_EVERY_AFTER)
24777
+ return false;
24778
+ return (consecutiveFailures - CAMERA_ALERT_EVERY_AFTER) % CAMERA_ALERT_EVERY_AFTER === 0;
24779
+ }
24780
+
24740
24781
  // src/services/dahua.service.ts
24741
24782
  var cameraRegistry = /* @__PURE__ */ new Map();
24742
24783
  var _savedOnDetected;
@@ -25230,6 +25271,8 @@ function useDahuaService() {
25230
25271
  async function getTrafficJunction(camera, signal, onDetected) {
25231
25272
  console.log(`getTrafficJunction camera object`, camera);
25232
25273
  let authFailureCount = 0;
25274
+ let connectionFailureCount = 0;
25275
+ let alertedFailureCount = 0;
25233
25276
  const MAX_AUTH_RETRIES = 10;
25234
25277
  while (!signal.aborted) {
25235
25278
  let bufferQueue = null;
@@ -25257,19 +25300,33 @@ function useDahuaService() {
25257
25300
  onDetected({
25258
25301
  site: camera?.site,
25259
25302
  direction: camera?.direction,
25260
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password and try after 10 minutes.`
25303
+ messagePermanent: cameraCredentialsMessage(camera)
25261
25304
  });
25262
25305
  }
25263
25306
  return;
25264
25307
  } else if ([400, 500].includes(statusCode)) {
25265
25308
  loggerDahua.error(`[${camera?.siteName}-${camera?.direction}] Connection error: ${statusCode}`);
25266
25309
  if (onDetected) {
25267
- onDetected({ site: camera?.site, messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Possible Not Authorized.` });
25310
+ onDetected({ site: camera?.site, messagePermanent: cameraNotAuthorisedMessage(camera) });
25268
25311
  }
25269
25312
  return;
25270
25313
  }
25271
25314
  authFailureCount = 0;
25272
25315
  isAuthFailure = false;
25316
+ if (alertedFailureCount > 0 && onDetected) {
25317
+ onDetected({
25318
+ site: camera?.site,
25319
+ direction: camera?.direction,
25320
+ // `event` and `camera` let a client group or clear an alert per
25321
+ // camera instead of matching on the message text. Older clients
25322
+ // ignore them and just render `message`, as they always have.
25323
+ event: "camera-recovered",
25324
+ camera: camera?._id?.toString(),
25325
+ message: cameraRecoveredMessage(camera)
25326
+ });
25327
+ }
25328
+ connectionFailureCount = 0;
25329
+ alertedFailureCount = 0;
25273
25330
  loggerDahua.info(`[${camera?.siteName}-${camera?.direction}] Successfully connected to ANPR.`);
25274
25331
  const contentType = response.res.headers["content-type"];
25275
25332
  const boundaryMatch = contentType?.match(/boundary=(.*)$/i);
@@ -25323,7 +25380,7 @@ function useDahuaService() {
25323
25380
  onDetected({
25324
25381
  site: camera?.site,
25325
25382
  direction: camera?.direction,
25326
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password and try after 10 minutes.`
25383
+ messagePermanent: cameraCredentialsMessage(camera)
25327
25384
  });
25328
25385
  }
25329
25386
  return;
@@ -25343,7 +25400,7 @@ function useDahuaService() {
25343
25400
  onDetected({
25344
25401
  site: camera?.site,
25345
25402
  direction: camera?.direction,
25346
- messagePermanent: `Camera [${camera?.siteName}-${camera?.direction}]. Connection Error. Check username and password.`
25403
+ messagePermanent: cameraCredentialsMessage(camera)
25347
25404
  });
25348
25405
  }
25349
25406
  return;
@@ -25351,14 +25408,18 @@ function useDahuaService() {
25351
25408
  } else {
25352
25409
  authFailureCount = 0;
25353
25410
  isAuthFailure = false;
25411
+ connectionFailureCount++;
25354
25412
  loggerDahua.error(
25355
- `[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds...`
25413
+ `[${camera?.siteName}-${camera?.direction}] Connection lost or error: ${error.message || error}. Retrying in 10 seconds... (failure ${connectionFailureCount})`
25356
25414
  );
25357
- if (onDetected) {
25415
+ if (onDetected && shouldAlertOnFailure(connectionFailureCount)) {
25416
+ alertedFailureCount = connectionFailureCount;
25358
25417
  onDetected({
25359
25418
  site: camera?.site,
25360
25419
  direction: camera?.direction,
25361
- 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.`
25420
+ event: "camera-fault",
25421
+ camera: camera?._id?.toString(),
25422
+ message: cameraUnreachableMessage(camera)
25362
25423
  });
25363
25424
  }
25364
25425
  }
@@ -75089,6 +75150,91 @@ async function configureVisitorQrReader(client, qrFormat) {
75089
75150
  identifier: { qrcode_identification_enabled: "1" }
75090
75151
  });
75091
75152
  }
75153
+ async function ensureVisitorAccessAuthorization(client, readerId, readerLocation, hidUserId) {
75154
+ const accessRuleId = createStableHidId(`visitor-access-rule:${readerId}`);
75155
+ const existingRuleResponse = await client.loadObjects({
75156
+ object: "access_rules",
75157
+ where: { access_rules: { id: accessRuleId } },
75158
+ limit: 1,
75159
+ offset: 0
75160
+ });
75161
+ if (getHidObjectRows(existingRuleResponse, "access_rules").length) {
75162
+ await client.modifyObjects({
75163
+ object: "access_rules",
75164
+ where: { access_rules: { id: accessRuleId } },
75165
+ values: {
75166
+ name: "iService365 Visitor Access",
75167
+ type: 1,
75168
+ priority: 0
75169
+ }
75170
+ });
75171
+ } else {
75172
+ await client.createObjects({
75173
+ object: "access_rules",
75174
+ values: [{
75175
+ id: accessRuleId,
75176
+ name: "iService365 Visitor Access",
75177
+ type: 1,
75178
+ priority: 0
75179
+ }]
75180
+ });
75181
+ }
75182
+ const portalsResponse = await client.loadObjects({
75183
+ object: "portals",
75184
+ limit: 100,
75185
+ offset: 0
75186
+ });
75187
+ const portals = getHidObjectRows(portalsResponse, "portals").map((portal) => ({
75188
+ id: Number(portal.id),
75189
+ name: String(portal.name || "").trim()
75190
+ })).filter((portal) => Number.isSafeInteger(portal.id) && portal.id > 0);
75191
+ if (!portals.length) {
75192
+ throw new import_node_server_utils266.BadRequestError(
75193
+ "The HID reader has no portal configured for visitor access."
75194
+ );
75195
+ }
75196
+ const normalizedLocation = String(readerLocation || "").trim().toLowerCase();
75197
+ const locationMatches = normalizedLocation ? portals.filter((portal) => {
75198
+ const portalName = portal.name.toLowerCase();
75199
+ return portalName === normalizedLocation || portalName.includes(normalizedLocation) || normalizedLocation.includes(portalName);
75200
+ }) : [];
75201
+ const selectedPortals = locationMatches.length === 1 ? locationMatches : portals.length === 1 ? portals : [];
75202
+ if (!selectedPortals.length) {
75203
+ throw new import_node_server_utils266.BadRequestError(
75204
+ "The HID reader has multiple portals. Match the reader location to one portal before issuing visitor access."
75205
+ );
75206
+ }
75207
+ const portalIds = selectedPortals.map((portal) => portal.id);
75208
+ const userAccessRulesResponse = await client.loadObjects({
75209
+ object: "user_access_rules",
75210
+ where: { user_access_rules: { user_id: hidUserId, access_rule_id: accessRuleId } },
75211
+ limit: 1,
75212
+ offset: 0
75213
+ });
75214
+ if (!getHidObjectRows(userAccessRulesResponse, "user_access_rules").length) {
75215
+ await client.createObjects({
75216
+ object: "user_access_rules",
75217
+ values: [{ user_id: hidUserId, access_rule_id: accessRuleId }]
75218
+ });
75219
+ }
75220
+ const portalAccessRulesResponse = await client.loadObjects({
75221
+ object: "portal_access_rules",
75222
+ where: { portal_access_rules: { access_rule_id: accessRuleId } },
75223
+ limit: 100,
75224
+ offset: 0
75225
+ });
75226
+ const linkedPortalIds = new Set(
75227
+ getHidObjectRows(portalAccessRulesResponse, "portal_access_rules").map((relation) => Number(relation.portal_id)).filter((portalId) => Number.isSafeInteger(portalId) && portalId > 0)
75228
+ );
75229
+ const missingPortalLinks = portalIds.filter((portalId) => !linkedPortalIds.has(portalId)).map((portalId) => ({ portal_id: portalId, access_rule_id: accessRuleId }));
75230
+ if (missingPortalLinks.length) {
75231
+ await client.createObjects({
75232
+ object: "portal_access_rules",
75233
+ values: missingPortalLinks
75234
+ });
75235
+ }
75236
+ return { accessRuleId, portalIds };
75237
+ }
75092
75238
  function getRelayAuth() {
75093
75239
  const username = process.env.HID_AMICO_RELAY_USERNAME;
75094
75240
  const password = process.env.HID_AMICO_RELAY_PASSWORD;
@@ -75712,6 +75858,12 @@ function useHidAmicoService() {
75712
75858
  values: [{ id: hidUserId, ...user }]
75713
75859
  });
75714
75860
  }
75861
+ const authorization = await ensureVisitorAccessAuthorization(
75862
+ client,
75863
+ readerId,
75864
+ reader.location,
75865
+ hidUserId
75866
+ );
75715
75867
  for (const object of ["qrcodes", "cards"]) {
75716
75868
  const existingCredentialResponse = await client.loadObjects({
75717
75869
  object,
@@ -75761,6 +75913,8 @@ function useHidAmicoService() {
75761
75913
  qrFormat: value.qrFormat,
75762
75914
  credentialObject: credential.object,
75763
75915
  credentialId: credential.credentialId,
75916
+ accessRuleId: authorization.accessRuleId,
75917
+ portalIds: authorization.portalIds,
75764
75918
  issuedAt: issuedAt.toISOString(),
75765
75919
  expiresAt: expiresAt.toISOString()
75766
75920
  }
@@ -75784,6 +75938,8 @@ function useHidAmicoService() {
75784
75938
  visitor: value.visitorId,
75785
75939
  hidUserId,
75786
75940
  credentialObject: credential.object,
75941
+ accessRuleId: authorization.accessRuleId,
75942
+ portalIds: authorization.portalIds,
75787
75943
  issuedAt: issuedAt.toISOString(),
75788
75944
  expiresAt: expiresAt.toISOString()
75789
75945
  }
@@ -75975,6 +76131,12 @@ function useHidAmicoService() {
75975
76131
  values: [{ id: hidUserId, ...user }]
75976
76132
  });
75977
76133
  }
76134
+ const authorization = await ensureVisitorAccessAuthorization(
76135
+ client,
76136
+ readerId,
76137
+ reader.location,
76138
+ hidUserId
76139
+ );
75978
76140
  const result = await client.setUserImage(hidUserId, image, timestamp, match);
75979
76141
  const existingIdentity = await repo.findExistingIdentity({ reader: readerId, hidUserId });
75980
76142
  const existingMetadata = existingIdentity && isUnknownRecord(existingIdentity.metadata) ? existingIdentity.metadata : {};
@@ -75990,6 +76152,8 @@ function useHidAmicoService() {
75990
76152
  temporary: true,
75991
76153
  facialEnrolled: true,
75992
76154
  facialEnrolledAt: issuedAt.toISOString(),
76155
+ accessRuleId: authorization.accessRuleId,
76156
+ portalIds: authorization.portalIds,
75993
76157
  expiresAt: expiresAt.toISOString()
75994
76158
  }
75995
76159
  };
@@ -76011,6 +76175,8 @@ function useHidAmicoService() {
76011
76175
  visitor: visitorId,
76012
76176
  hidUserId,
76013
76177
  imageBytes: image.length,
76178
+ accessRuleId: authorization.accessRuleId,
76179
+ portalIds: authorization.portalIds,
76014
76180
  issuedAt: issuedAt.toISOString(),
76015
76181
  expiresAt: expiresAt.toISOString(),
76016
76182
  result: summarizeUserImageResult(result)