@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 +173 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +173 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/test/camera-alert.util.test.mjs +109 -0
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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)
|