@7365admin1/core 3.64.5-staging.302 → 3.64.5-staging.304
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/.changeset/manpower-authenticated-namespace.md +27 -0
- package/.changeset/notify-access-filter-on.md +24 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.js +77 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/notification-access.test.mjs +35 -6
- package/test/socket-session.test.mjs +119 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Manpower live status: an authenticated namespace beside the open one
|
|
6
|
+
|
|
7
|
+
`/manpower` streams every client site's live staffing level — how many guards are
|
|
8
|
+
actually on shift against how many were promised — to anyone who connects, with no
|
|
9
|
+
credential of any kind.
|
|
10
|
+
|
|
11
|
+
This adds `/manpower-live`, carrying exactly the same `manpower-status-update`
|
|
12
|
+
events, which admits a socket only when the handshake presents a session id that
|
|
13
|
+
resolves to a live session. Every refusal is an identical `Unauthorized`, and a
|
|
14
|
+
session store that is down refuses rather than letting everyone in.
|
|
15
|
+
|
|
16
|
+
`/manpower` itself is deliberately unchanged: the Capacitor guard app installed on
|
|
17
|
+
phones today and the Nuxt security screen both connect to it with no credential and
|
|
18
|
+
would be cut off the moment it were tightened. Every update is emitted to both, so
|
|
19
|
+
the two feeds can never disagree, and both namespaces share one connection handler,
|
|
20
|
+
so a client on the authenticated feed still starts the poll and still gets an
|
|
21
|
+
immediate snapshot.
|
|
22
|
+
|
|
23
|
+
A live session is the whole check, deliberately. Scoping the feed to the caller's
|
|
24
|
+
own sites is not possible today: manpower lives in HRM Labs' vendor id space, none
|
|
25
|
+
of those site ids match ours, and no field links the two — a site check would empty
|
|
26
|
+
the screen for the one customer using it. That mapping is a schema change and
|
|
27
|
+
belongs to the leads.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
"@7365admin1/core": minor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Notifications: stop sending alerts for modules a client was never given
|
|
6
|
+
|
|
7
|
+
`NOTIFY_ACCESS_FILTER` now defaults to `on` instead of `log`, so the module-based
|
|
8
|
+
drop is real: a recipient no longer gets a notification when every membership they
|
|
9
|
+
hold at the site denies every module the notification's category is about.
|
|
10
|
+
|
|
11
|
+
Only the module drop acts. The role-based check is still computed and logged and
|
|
12
|
+
never drops anybody, because most apps do not enforce their role gates yet and a
|
|
13
|
+
role-based drop would hide alerts for screens people can still open.
|
|
14
|
+
|
|
15
|
+
Unchanged: residents, anyone with no membership at the site, Seven365 staff, sends
|
|
16
|
+
with no site, and categories the catalogue does not govern are all exempt; any
|
|
17
|
+
error sends to everyone; and `MODULE_LIST_GATE=off` still disables it entirely.
|
|
18
|
+
`NOTIFY_ACCESS_FILTER=log` or `off` overrides this default with no deploy.
|
|
19
|
+
|
|
20
|
+
This is inert until a client's module list is enforced — measured read-only on
|
|
21
|
+
staging at the time of the flip, no organisation (0 of 199) and no site (0 of 256)
|
|
22
|
+
had a stored list and there were no acknowledged-save markers, so nothing is
|
|
23
|
+
dropped for anybody until a Super Admin saves a module list with the acknowledged
|
|
24
|
+
preview.
|
package/dist/index.d.ts
CHANGED
|
@@ -14397,13 +14397,29 @@ declare function isPlatformOwner(userId?: string | ObjectId | null): Promise<boo
|
|
|
14397
14397
|
* with no site (marketplace, broadcasts); a category not in the catalogue or
|
|
14398
14398
|
* with no permission resources (resident-only and marketplace ones).
|
|
14399
14399
|
*
|
|
14400
|
-
* `NOTIFY_ACCESS_FILTER`: `
|
|
14401
|
-
*
|
|
14402
|
-
*
|
|
14400
|
+
* `NOTIFY_ACCESS_FILTER`: `on` (code default) DROPS; `log` counts, logs and
|
|
14401
|
+
* sends to everyone; `off` reads nothing. `MODULE_LIST_GATE=off` is the same as
|
|
14402
|
+
* `off`. Any error sends to everyone.
|
|
14403
|
+
*
|
|
14404
|
+
* The default was `log` from the day this shipped until the owner turned it on
|
|
14405
|
+
* (2026-09-13), having accepted that some staff will notice alerts they used to
|
|
14406
|
+
* get stopping. Only the MODULE drop became real; the role check above stays
|
|
14407
|
+
* logged only, because most apps still do not enforce their role gates.
|
|
14408
|
+
*
|
|
14409
|
+
* Turning it on changed nothing on its own: the filter returns early unless the
|
|
14410
|
+
* site or its organisation has an ENFORCED module list, and on the day of the
|
|
14411
|
+
* flip no organisation and no site had one (measured read-only on staging: 0 of
|
|
14412
|
+
* 199 organisations, 0 of 256 sites, 0 acknowledged-save markers). It takes
|
|
14413
|
+
* effect for a client the first time a Super Admin saves that client's module
|
|
14414
|
+
* list with the acknowledged preview.
|
|
14415
|
+
*
|
|
14416
|
+
* The env var still wins over this default, so a lead can put it back to `log`
|
|
14417
|
+
* or `off` without waiting for a deploy.
|
|
14403
14418
|
*
|
|
14404
14419
|
* Both senders call this: core's `notification.service.ts send()` here, and
|
|
14405
14420
|
* API-core's own `send()` (a separate class; memory
|
|
14406
|
-
* `two-notification-senders-not-one`), which
|
|
14421
|
+
* `two-notification-senders-not-one`), which imports this very function — so
|
|
14422
|
+
* this default governs both, and neither can drift from the other.
|
|
14407
14423
|
*/
|
|
14408
14424
|
type TNotifyAccessMode = "log" | "on" | "off";
|
|
14409
14425
|
declare const NOTIFY_ACCESS_FILTER_DEFAULT: TNotifyAccessMode;
|
package/dist/index.js
CHANGED
|
@@ -536,13 +536,13 @@ var require_logger = __commonJS({
|
|
|
536
536
|
"use strict";
|
|
537
537
|
exports.__esModule = true;
|
|
538
538
|
var _utils = require_utils();
|
|
539
|
-
var
|
|
539
|
+
var logger227 = {
|
|
540
540
|
methodMap: ["debug", "info", "warn", "error"],
|
|
541
541
|
level: "info",
|
|
542
542
|
// Maps a given level value to the `methodMap` indexes above.
|
|
543
543
|
lookupLevel: function lookupLevel(level) {
|
|
544
544
|
if (typeof level === "string") {
|
|
545
|
-
var levelMap = _utils.indexOf(
|
|
545
|
+
var levelMap = _utils.indexOf(logger227.methodMap, level.toLowerCase());
|
|
546
546
|
if (levelMap >= 0) {
|
|
547
547
|
level = levelMap;
|
|
548
548
|
} else {
|
|
@@ -553,9 +553,9 @@ var require_logger = __commonJS({
|
|
|
553
553
|
},
|
|
554
554
|
// Can be overridden in the host environment
|
|
555
555
|
log: function log(level) {
|
|
556
|
-
level =
|
|
557
|
-
if (typeof console !== "undefined" &&
|
|
558
|
-
var method =
|
|
556
|
+
level = logger227.lookupLevel(level);
|
|
557
|
+
if (typeof console !== "undefined" && logger227.lookupLevel(logger227.level) <= level) {
|
|
558
|
+
var method = logger227.methodMap[level];
|
|
559
559
|
if (!console[method]) {
|
|
560
560
|
method = "log";
|
|
561
561
|
}
|
|
@@ -566,7 +566,7 @@ var require_logger = __commonJS({
|
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
};
|
|
569
|
-
exports["default"] =
|
|
569
|
+
exports["default"] = logger227;
|
|
570
570
|
module2.exports = exports["default"];
|
|
571
571
|
}
|
|
572
572
|
});
|
|
@@ -16560,7 +16560,7 @@ async function siteModulePreview(site, org, proposed, data = moduleGateData()) {
|
|
|
16560
16560
|
}
|
|
16561
16561
|
|
|
16562
16562
|
// src/utils/notification-access.util.ts
|
|
16563
|
-
var NOTIFY_ACCESS_FILTER_DEFAULT = "
|
|
16563
|
+
var NOTIFY_ACCESS_FILTER_DEFAULT = "on";
|
|
16564
16564
|
function notifyAccessMode(env2 = process.env) {
|
|
16565
16565
|
return gateMode(env2.NOTIFY_ACCESS_FILTER, ["log", "on", "off"], NOTIFY_ACCESS_FILTER_DEFAULT);
|
|
16566
16566
|
}
|
|
@@ -87847,6 +87847,52 @@ var isWithinHour = (alertTime, timeNow) => {
|
|
|
87847
87847
|
var import_node_server_utils264 = require("@7365admin1/node-server-utils");
|
|
87848
87848
|
var import_mongodb162 = require("mongodb");
|
|
87849
87849
|
var import_moment_timezone11 = __toESM(require("moment-timezone"));
|
|
87850
|
+
|
|
87851
|
+
// src/utils/socket-session.util.ts
|
|
87852
|
+
var MANPOWER_LIVE_NAMESPACE = "/manpower-live";
|
|
87853
|
+
function sidFromHandshake(handshake) {
|
|
87854
|
+
const cookie = String(handshake?.headers?.cookie ?? "");
|
|
87855
|
+
const fromCookie = /(?:^|;\s*)sid=([^;]*)/.exec(cookie);
|
|
87856
|
+
if (fromCookie) {
|
|
87857
|
+
const value = decodeURIComponent(fromCookie[1]).trim();
|
|
87858
|
+
if (value)
|
|
87859
|
+
return value;
|
|
87860
|
+
}
|
|
87861
|
+
for (const key of ["sid", "token"]) {
|
|
87862
|
+
const value = handshake?.auth?.[key];
|
|
87863
|
+
if (typeof value === "string" && value.trim())
|
|
87864
|
+
return value.trim();
|
|
87865
|
+
}
|
|
87866
|
+
const header = handshake?.headers?.authorization;
|
|
87867
|
+
if (typeof header === "string" && header.trim()) {
|
|
87868
|
+
const value = header.trim();
|
|
87869
|
+
return value.toLowerCase().startsWith("bearer ") ? value.slice("bearer ".length).trim() : value;
|
|
87870
|
+
}
|
|
87871
|
+
return "";
|
|
87872
|
+
}
|
|
87873
|
+
function userIdOfSession(session) {
|
|
87874
|
+
if (!session || typeof session !== "object")
|
|
87875
|
+
return "";
|
|
87876
|
+
const record = session;
|
|
87877
|
+
return String(record.user ?? record._id ?? "").trim();
|
|
87878
|
+
}
|
|
87879
|
+
async function authoriseSocketSession(handshake, readSession) {
|
|
87880
|
+
const sid = sidFromHandshake(handshake);
|
|
87881
|
+
if (!sid)
|
|
87882
|
+
throw new Error("Unauthorized");
|
|
87883
|
+
let session;
|
|
87884
|
+
try {
|
|
87885
|
+
session = await readSession(sid);
|
|
87886
|
+
} catch {
|
|
87887
|
+
throw new Error("Unauthorized");
|
|
87888
|
+
}
|
|
87889
|
+
const userId = userIdOfSession(session);
|
|
87890
|
+
if (!userId)
|
|
87891
|
+
throw new Error("Unauthorized");
|
|
87892
|
+
return userId;
|
|
87893
|
+
}
|
|
87894
|
+
|
|
87895
|
+
// src/events/manpower.event.ts
|
|
87850
87896
|
async function manpowerEvents(io) {
|
|
87851
87897
|
let intervalId = null;
|
|
87852
87898
|
let activeConnections = 0;
|
|
@@ -87907,8 +87953,7 @@ async function manpowerEvents(io) {
|
|
|
87907
87953
|
newStatus = "Over";
|
|
87908
87954
|
else if (actual < expected)
|
|
87909
87955
|
newStatus = "Under";
|
|
87910
|
-
const
|
|
87911
|
-
io.of(namespace2).emit("manpower-status-update", {
|
|
87956
|
+
const update = {
|
|
87912
87957
|
siteId: doc.siteId,
|
|
87913
87958
|
siteName: doc.siteName,
|
|
87914
87959
|
shift: shift.key,
|
|
@@ -87916,7 +87961,9 @@ async function manpowerEvents(io) {
|
|
|
87916
87961
|
actual,
|
|
87917
87962
|
expected,
|
|
87918
87963
|
selectedDateSGT
|
|
87919
|
-
}
|
|
87964
|
+
};
|
|
87965
|
+
io.of("/manpower").emit("manpower-status-update", update);
|
|
87966
|
+
io.of(MANPOWER_LIVE_NAMESPACE).emit("manpower-status-update", update);
|
|
87920
87967
|
}
|
|
87921
87968
|
}
|
|
87922
87969
|
};
|
|
@@ -87936,7 +87983,7 @@ async function manpowerEvents(io) {
|
|
|
87936
87983
|
console.log(" Interval stopped.");
|
|
87937
87984
|
}
|
|
87938
87985
|
};
|
|
87939
|
-
|
|
87986
|
+
const onConnection = (socket) => {
|
|
87940
87987
|
activeConnections += 1;
|
|
87941
87988
|
const date = socket.handshake.query.date;
|
|
87942
87989
|
if (activeConnections === 1) {
|
|
@@ -87951,7 +87998,26 @@ async function manpowerEvents(io) {
|
|
|
87951
87998
|
stopInterval();
|
|
87952
87999
|
}
|
|
87953
88000
|
});
|
|
88001
|
+
};
|
|
88002
|
+
io.of("/manpower").on("connection", onConnection);
|
|
88003
|
+
const live = io.of(MANPOWER_LIVE_NAMESPACE);
|
|
88004
|
+
live.use(async (socket, next) => {
|
|
88005
|
+
try {
|
|
88006
|
+
const { getCache } = (0, import_node_server_utils264.useCache)("sessions");
|
|
88007
|
+
socket.data.userId = await authoriseSocketSession(
|
|
88008
|
+
socket.handshake,
|
|
88009
|
+
(sid) => getCache(`sid:${sid}`)
|
|
88010
|
+
);
|
|
88011
|
+
next();
|
|
88012
|
+
} catch (error) {
|
|
88013
|
+
import_node_server_utils264.logger.log({
|
|
88014
|
+
level: "debug",
|
|
88015
|
+
message: `[manpower] refused a socket: ${error?.message}`
|
|
88016
|
+
});
|
|
88017
|
+
next(new Error("Unauthorized"));
|
|
88018
|
+
}
|
|
87954
88019
|
});
|
|
88020
|
+
live.on("connection", onConnection);
|
|
87955
88021
|
}
|
|
87956
88022
|
|
|
87957
88023
|
// src/services/reddot-payment.service.ts
|