@7365admin1/core 3.75.1 → 3.76.0
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/CHANGELOG.md +13 -0
- package/dist/index.d.ts +64 -3
- package/dist/index.js +122 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/camera-live-link.test.mjs +163 -0
package/dist/index.mjs
CHANGED
|
@@ -21240,6 +21240,37 @@ function signPlaybackLink(params) {
|
|
|
21240
21240
|
expiresAt: new Date(exp * 1e3).toISOString()
|
|
21241
21241
|
};
|
|
21242
21242
|
}
|
|
21243
|
+
function signLiveLink(host, {
|
|
21244
|
+
relays = playbackRelays(),
|
|
21245
|
+
now = /* @__PURE__ */ new Date()
|
|
21246
|
+
} = {}) {
|
|
21247
|
+
const parsed = host ? parseCameraHost(host) : null;
|
|
21248
|
+
if (!parsed || parsed.channel === null)
|
|
21249
|
+
return null;
|
|
21250
|
+
const key = relays[parsed.authority];
|
|
21251
|
+
if (!key)
|
|
21252
|
+
return null;
|
|
21253
|
+
const exp = Math.floor(now.getTime() / 1e3) + PLAYBACK_LINK_SECONDS;
|
|
21254
|
+
const channel = String(parsed.channel);
|
|
21255
|
+
const sig = createHmac("sha256", key).update(`${channel}|${exp}`).digest("hex");
|
|
21256
|
+
const liveAuth = `exp=${exp}&sig=${sig}`;
|
|
21257
|
+
return {
|
|
21258
|
+
/** The player page, ready for an iframe or a WebView. */
|
|
21259
|
+
liveUrl: `https://${parsed.authority}/${channel}?${liveAuth}`,
|
|
21260
|
+
/**
|
|
21261
|
+
* The same signature, on its own, to append to any of the relay's live
|
|
21262
|
+
* routes — the socket, the still, the clip. One signature serves all four
|
|
21263
|
+
* because it covers the CHANNEL and not the path, which is what lets
|
|
21264
|
+
* `insertScreenshot` keep splicing `screenshot` into the path.
|
|
21265
|
+
*/
|
|
21266
|
+
liveAuth,
|
|
21267
|
+
liveExpiresAt: new Date(exp * 1e3).toISOString()
|
|
21268
|
+
};
|
|
21269
|
+
}
|
|
21270
|
+
function withLiveLink(fields, options) {
|
|
21271
|
+
const live = signLiveLink(fields.host, options);
|
|
21272
|
+
return live ? { ...fields, ...live } : fields;
|
|
21273
|
+
}
|
|
21243
21274
|
|
|
21244
21275
|
// src/utils/camera-capability.util.ts
|
|
21245
21276
|
var CAMERA_CAPABILITIES = [
|
|
@@ -22595,7 +22626,9 @@ function useCameraViewService() {
|
|
|
22595
22626
|
// player-page URL, which is the one picture path proven to work, and the
|
|
22596
22627
|
// plain `GET /site-cameras` already returns it to every authenticated
|
|
22597
22628
|
// caller. The credential fields are still never included.
|
|
22598
|
-
...
|
|
22629
|
+
...withLiveLink(
|
|
22630
|
+
publicCameraFields({ ...camera, siteName: site?.name })
|
|
22631
|
+
),
|
|
22599
22632
|
// Answered before anything is requested, so a camera with no address or
|
|
22600
22633
|
// an inactive one renders with a reason and costs zero device requests.
|
|
22601
22634
|
// (An ANPR unit never reaches here — it is filtered out above.)
|
|
@@ -22638,7 +22671,7 @@ function useCameraViewService() {
|
|
|
22638
22671
|
async function probeCamera(camera, cacheSeconds) {
|
|
22639
22672
|
const id = camera?._id?.toString?.() ?? "";
|
|
22640
22673
|
const identity = {
|
|
22641
|
-
...publicCameraFields(camera),
|
|
22674
|
+
...withLiveLink(publicCameraFields(camera)),
|
|
22642
22675
|
unavailableReason: snapshotRefusalReason(camera)
|
|
22643
22676
|
};
|
|
22644
22677
|
const lastFrameAt = await getCache(`lastframe:${id}`) || null;
|
|
@@ -22772,7 +22805,7 @@ function useCameraViewService() {
|
|
|
22772
22805
|
);
|
|
22773
22806
|
const { capabilities } = capabilitiesOf(camera, probe);
|
|
22774
22807
|
const base = {
|
|
22775
|
-
camera: publicCameraFields(camera),
|
|
22808
|
+
camera: withLiveLink(publicCameraFields(camera)),
|
|
22776
22809
|
snapshotSupported: refusal === null,
|
|
22777
22810
|
capabilities,
|
|
22778
22811
|
// Filled in from the probe when the device answers. Today it does not, so
|
|
@@ -37981,6 +38014,87 @@ function useVehicleController() {
|
|
|
37981
38014
|
next(error);
|
|
37982
38015
|
}
|
|
37983
38016
|
}
|
|
38017
|
+
const MAX_BULK_VEHICLE_ROWS = 100;
|
|
38018
|
+
async function addBulk(req, res, next) {
|
|
38019
|
+
const rowSchema = Joi57.object({
|
|
38020
|
+
// The sheet's own row id. Opaque to the server, handed straight back.
|
|
38021
|
+
id: Joi57.string().trim().max(64).required(),
|
|
38022
|
+
block: Joi57.string().hex().length(24).required(),
|
|
38023
|
+
level: Joi57.string().hex().length(24).required(),
|
|
38024
|
+
unit: Joi57.string().hex().length(24).required(),
|
|
38025
|
+
name: Joi57.string().trim().min(1).required(),
|
|
38026
|
+
plateNumber: Joi57.string().trim().min(1).required(),
|
|
38027
|
+
nric: Joi57.string().trim().optional().allow("", null),
|
|
38028
|
+
phoneNumber: Joi57.string().trim().optional().allow("", null)
|
|
38029
|
+
});
|
|
38030
|
+
const schema2 = Joi57.object({
|
|
38031
|
+
site: Joi57.string().hex().length(24).required(),
|
|
38032
|
+
org: Joi57.string().hex().length(24).required(),
|
|
38033
|
+
category: Joi57.string().optional().valid(...Object.values(VehicleCategory)).default("resident" /* RESIDENT */),
|
|
38034
|
+
rows: Joi57.array().items(rowSchema).min(1).max(MAX_BULK_VEHICLE_ROWS).required()
|
|
38035
|
+
});
|
|
38036
|
+
const { error, value } = schema2.validate(req.body, { abortEarly: false });
|
|
38037
|
+
if (error) {
|
|
38038
|
+
const messages = error.details.map((d) => d.message).join(", ");
|
|
38039
|
+
logger79.log({ level: "error", message: messages });
|
|
38040
|
+
next(new BadRequestError102(messages));
|
|
38041
|
+
return;
|
|
38042
|
+
}
|
|
38043
|
+
try {
|
|
38044
|
+
await requireSiteReach(req, value.site);
|
|
38045
|
+
const user = req.user?.user || req.user?._id;
|
|
38046
|
+
const results = [];
|
|
38047
|
+
for (const row of value.rows) {
|
|
38048
|
+
try {
|
|
38049
|
+
const message = await _add(
|
|
38050
|
+
{
|
|
38051
|
+
site: value.site,
|
|
38052
|
+
org: value.org,
|
|
38053
|
+
type: "whitelist" /* WHITELIST */,
|
|
38054
|
+
category: value.category,
|
|
38055
|
+
name: row.name,
|
|
38056
|
+
nric: row.nric || "",
|
|
38057
|
+
phoneNumber: row.phoneNumber || "",
|
|
38058
|
+
block: row.block,
|
|
38059
|
+
level: row.level,
|
|
38060
|
+
unit: row.unit,
|
|
38061
|
+
plateNumber: [row.plateNumber]
|
|
38062
|
+
},
|
|
38063
|
+
void 0,
|
|
38064
|
+
user
|
|
38065
|
+
);
|
|
38066
|
+
results.push({
|
|
38067
|
+
id: row.id,
|
|
38068
|
+
ok: true,
|
|
38069
|
+
message: String(message ?? "Vehicle added successfully.")
|
|
38070
|
+
});
|
|
38071
|
+
} catch (rowError) {
|
|
38072
|
+
logger79.log({
|
|
38073
|
+
level: "error",
|
|
38074
|
+
message: `bulk vehicle row ${row.id}: ${rowError?.message ?? rowError}`
|
|
38075
|
+
});
|
|
38076
|
+
results.push({
|
|
38077
|
+
id: row.id,
|
|
38078
|
+
ok: false,
|
|
38079
|
+
message: String(
|
|
38080
|
+
rowError?.message ?? "The vehicle could not be added."
|
|
38081
|
+
)
|
|
38082
|
+
});
|
|
38083
|
+
}
|
|
38084
|
+
}
|
|
38085
|
+
const added = results.filter((result) => result.ok).length;
|
|
38086
|
+
const failed = results.length - added;
|
|
38087
|
+
res.status(200).json({
|
|
38088
|
+
message: failed === 0 ? `${added} vehicle${added === 1 ? "" : "s"} added.` : `${added} of ${results.length} vehicles added; ${failed} could not be added.`,
|
|
38089
|
+
added,
|
|
38090
|
+
failed,
|
|
38091
|
+
results
|
|
38092
|
+
});
|
|
38093
|
+
} catch (error2) {
|
|
38094
|
+
logger79.log({ level: "error", message: error2.message });
|
|
38095
|
+
next(error2);
|
|
38096
|
+
}
|
|
38097
|
+
}
|
|
37984
38098
|
async function add(req, res, next) {
|
|
37985
38099
|
const { error, value } = vehicleSchema.validate(req.body, {
|
|
37986
38100
|
abortEarly: false
|
|
@@ -38456,6 +38570,7 @@ function useVehicleController() {
|
|
|
38456
38570
|
}
|
|
38457
38571
|
return {
|
|
38458
38572
|
add,
|
|
38573
|
+
addBulk,
|
|
38459
38574
|
getVehicles,
|
|
38460
38575
|
getSeasonPassTypes,
|
|
38461
38576
|
getVehicleById,
|
|
@@ -91783,6 +91898,7 @@ export {
|
|
|
91783
91898
|
sessionSchema,
|
|
91784
91899
|
setIO,
|
|
91785
91900
|
shiftSchema,
|
|
91901
|
+
signLiveLink,
|
|
91786
91902
|
signPlaybackLink,
|
|
91787
91903
|
siteDayBounds,
|
|
91788
91904
|
siteModulesHandlers,
|
|
@@ -92033,6 +92149,7 @@ export {
|
|
|
92033
92149
|
vehicles_namespace_collection,
|
|
92034
92150
|
visitors_namespace_collection,
|
|
92035
92151
|
wallConfig,
|
|
92152
|
+
withLiveLink,
|
|
92036
92153
|
workOrderSchema,
|
|
92037
92154
|
work_orders2_namespace_collection,
|
|
92038
92155
|
work_orders_namespace_collection
|