@7365admin1/core 3.82.0 → 3.83.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 +29 -0
- package/dist/index.d.ts +213 -1
- package/dist/index.js +281 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +267 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/robot-adapter.test.mjs +179 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7365admin1/core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.83.0",
|
|
5
5
|
"author": "7365admin1",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "tsup src/index.ts --format cjs,esm --dts && shx cp -r src/public dist && shx cp -r src/utils/handlebars dist",
|
|
14
14
|
"release": "yarn run build && changeset publish",
|
|
15
15
|
"lint": "tsc",
|
|
16
|
-
"test": "tsup src/config.ts src/utils/robot-cloud.util.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/lift-contract.util.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
|
|
16
|
+
"test": "tsup src/config.ts src/utils/robot-cloud.util.ts src/utils/robot-adapter.util.ts src/utils/robot-tracking.util.ts src/utils/robot-apps.util.ts src/utils/robot-permission.util.ts src/utils/lift-contract.util.ts src/utils/anpr-revoke.util.ts src/utils/anpr-plate.util.ts src/utils/vehicle-request.util.ts src/utils/camera-view.util.ts src/utils/camera-bulk-import.util.ts src/utils/dashboard-metrics.util.ts src/utils/camera-capability.util.ts src/utils/camera-playback.util.ts src/services/camera-device-http.service.ts src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts src/utils/site-name.util.ts src/utils/service-provider-invite.util.ts src/utils/notification-category.util.ts src/utils/hid-card.util.ts src/utils/hid-json.util.ts src/utils/hid-access.util.ts src/models/customer-site.model.ts src/models/site-camera.model.ts src/models/member.model.ts src/models/subscription.model.ts src/utils/session-id.util.ts src/utils/occurrence-query.util.ts src/utils/occurrence-book-create.util.ts src/utils/update-result.util.ts src/utils/hid-amico-authz.util.ts src/utils/promo-code-currency.util.ts src/utils/console-audit.util.ts src/utils/org-suspension.util.ts src/utils/session-revoke.util.ts src/models/hid-amico.model.ts src/models/patrol-email.model.ts src/utils/patrol-email-pdf.util.ts src/utils/visitor-status.util.ts src/utils/self-service-email.util.ts src/utils/hid-secret.util.ts src/models/visitor-invite.model.ts src/models/guard-invite.model.ts src/utils/site-timezone.util.ts src/utils/bulletin-service-apps.util.ts src/models/bulletin-board.model.ts src/utils/vehicle-import.util.ts src/utils/expired-vehicle-sweep.util.ts src/models/vehicle.model.ts src/controllers/chat.controller.ts src/utils/file-owner.util.ts src/utils/console-permission.util.ts src/utils/role-scope.util.ts src/utils/org-default-roles.util.ts src/utils/org-modules.util.ts src/utils/site-modules.util.ts src/utils/resident-app-modules.util.ts src/utils/resident-default-role.util.ts src/utils/invite-role-scope.util.ts src/models/personal-emergency-chain.model.ts src/utils/self-signup-files.util.ts src/models/person.model.ts src/utils/module-gate.util.ts src/utils/member-lookup.util.ts src/utils/notification-access.util.ts src/utils/module-access.util.ts src/services/module-gate.service.ts src/controllers/site.controller.ts src/controllers/organization.controller.ts src/controllers/member.controller.ts src/utils/org-owner-repair.util.ts src/utils/socket-session.util.ts src/utils/people-create-gate.util.ts src/utils/service-app-type.util.ts --format esm --out-dir test/.build --no-dts --silent && node --test test/*.test.mjs",
|
|
17
17
|
"test:camera": "yarn test",
|
|
18
18
|
"test:e2e": "yarn build && node --test --test-concurrency=1 --test-timeout=600000 \"test/e2e/*.test.mjs\""
|
|
19
19
|
},
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ROBOT_CAPABILITIES,
|
|
6
|
+
clearRobotAdapters,
|
|
7
|
+
commandSupported,
|
|
8
|
+
missingForTier,
|
|
9
|
+
registerRobotAdapter,
|
|
10
|
+
registeredRobotBrands,
|
|
11
|
+
robotAdapterFor,
|
|
12
|
+
robotTier,
|
|
13
|
+
supports,
|
|
14
|
+
uncontrollableFleetWarning,
|
|
15
|
+
} from "./.build/utils/robot-adapter.util.mjs";
|
|
16
|
+
|
|
17
|
+
/* --------------------------------------------------------------------------
|
|
18
|
+
* The goal changed: fleet management for every brand and model on the market,
|
|
19
|
+
* not one vendor. That only works if the screens ask the ROBOT what it can do
|
|
20
|
+
* instead of assuming, so these guard the capability model and the registry.
|
|
21
|
+
*
|
|
22
|
+
* The capability names and the tier requirements are Open-RMF's, verbatim from
|
|
23
|
+
* the "Fleet Adapters" table in ros2multirobotbook. Keeping them is what lets a
|
|
24
|
+
* brand's datasheet be read straight into a tier.
|
|
25
|
+
* ----------------------------------------------------------------------- */
|
|
26
|
+
|
|
27
|
+
const ALL = Object.fromEntries(ROBOT_CAPABILITIES.map((c) => [c, true]));
|
|
28
|
+
const caps = (...names) => Object.fromEntries(names.map((n) => [n, true]));
|
|
29
|
+
|
|
30
|
+
/* ------------------------------------------------------------------ tiers */
|
|
31
|
+
|
|
32
|
+
test("everything supported is Full Control", () => {
|
|
33
|
+
assert.equal(robotTier(ALL), "full-control");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("nothing supported is no-interface — a real answer, not an error", () => {
|
|
37
|
+
// Our own security robot is this today: a web page on a building's LAN.
|
|
38
|
+
assert.equal(robotTier({}), "no-interface");
|
|
39
|
+
assert.equal(robotTier(null), "no-interface");
|
|
40
|
+
assert.equal(robotTier(undefined), "no-interface");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("a tier needs EVERY requirement — partial credit misses the point", () => {
|
|
44
|
+
const nearlyFull = { ...ALL };
|
|
45
|
+
delete nearlyFull.relocalize;
|
|
46
|
+
assert.notEqual(robotTier(nearlyFull), "full-control");
|
|
47
|
+
|
|
48
|
+
const trafficLight = caps(
|
|
49
|
+
"readLocation", "pauseResume", "battery", "sendToDock",
|
|
50
|
+
"startProcess", "pauseProcess", "processCompletion",
|
|
51
|
+
);
|
|
52
|
+
assert.equal(robotTier(trafficLight), "traffic-light");
|
|
53
|
+
|
|
54
|
+
const { battery, ...noBattery } = trafficLight;
|
|
55
|
+
assert.notEqual(robotTier(noBattery), "traffic-light");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("Ounitech today does not reach even Read Only", () => {
|
|
59
|
+
// What their documented API actually gives: pose, dock, and "start a process"
|
|
60
|
+
// (start-area-cleaning). No battery, no path/ETA, no reliable completion.
|
|
61
|
+
const ounitech = caps("readLocation", "sendToDock", "startProcess");
|
|
62
|
+
assert.equal(robotTier(ounitech), "no-interface");
|
|
63
|
+
|
|
64
|
+
// And this is the ask that moves them, in order.
|
|
65
|
+
assert.deepEqual(missingForTier(ounitech, "read-only"), [
|
|
66
|
+
"pathAndEta",
|
|
67
|
+
"battery",
|
|
68
|
+
"processCompletion",
|
|
69
|
+
]);
|
|
70
|
+
assert.deepEqual(missingForTier(ounitech, "traffic-light"), [
|
|
71
|
+
"pauseResume",
|
|
72
|
+
"battery",
|
|
73
|
+
"pauseProcess",
|
|
74
|
+
"processCompletion",
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a capability set with false values does not count as support", () => {
|
|
79
|
+
assert.equal(supports({ battery: false }, "battery"), false);
|
|
80
|
+
assert.equal(supports({ battery: true }, "battery"), true);
|
|
81
|
+
assert.equal(supports({}, "battery"), false);
|
|
82
|
+
assert.equal(supports(null, "battery"), false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("a prototype key is not a capability", () => {
|
|
86
|
+
// hasOwnProperty, not a bare lookup - otherwise "constructor" reads as true.
|
|
87
|
+
assert.equal(supports({}, "constructor"), false);
|
|
88
|
+
assert.equal(supports({}, "toString"), false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/* ------------------------------------------- the shared-space safety rule */
|
|
92
|
+
|
|
93
|
+
test("two uncontrollable fleets in one space is refused", () => {
|
|
94
|
+
// Open-RMF: at most ONE read-only fleet in a shared space, "having none is
|
|
95
|
+
// ideal". A read-only cleaning fleet plus a no-interface security robot is
|
|
96
|
+
// exactly the pair to warn about.
|
|
97
|
+
assert.equal(uncontrollableFleetWarning(["read-only"]).ok, true);
|
|
98
|
+
assert.equal(uncontrollableFleetWarning(["no-interface"]).ok, true);
|
|
99
|
+
assert.equal(uncontrollableFleetWarning(["full-control", "read-only"]).ok, true);
|
|
100
|
+
|
|
101
|
+
const bad = uncontrollableFleetWarning(["read-only", "no-interface"]);
|
|
102
|
+
assert.equal(bad.ok, false);
|
|
103
|
+
assert.equal(bad.count, 2);
|
|
104
|
+
assert.match(bad.message, /at most one/i);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("controllable fleets never trigger the warning, however many", () => {
|
|
108
|
+
const many = ["full-control", "full-control", "traffic-light", "traffic-light"];
|
|
109
|
+
assert.equal(uncontrollableFleetWarning(many).ok, true);
|
|
110
|
+
assert.equal(uncontrollableFleetWarning([]).ok, true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/* --------------------------------------------------------------- commands */
|
|
114
|
+
|
|
115
|
+
test("a command the brand never claimed is refused", () => {
|
|
116
|
+
const readOnlyish = caps("readLocation");
|
|
117
|
+
for (const kind of ["full", "area", "spot", "return", "pause", "navigate", "relocalize"]) {
|
|
118
|
+
assert.equal(commandSupported(readOnlyish, kind), false, kind);
|
|
119
|
+
}
|
|
120
|
+
assert.equal(commandSupported(ALL, "navigate"), true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("each command maps to the capability it actually needs", () => {
|
|
124
|
+
assert.equal(commandSupported(caps("startProcess"), "area"), true);
|
|
125
|
+
assert.equal(commandSupported(caps("startProcess"), "return"), false);
|
|
126
|
+
assert.equal(commandSupported(caps("sendToDock"), "return"), true);
|
|
127
|
+
assert.equal(commandSupported(caps("pauseProcess"), "cancel"), true);
|
|
128
|
+
assert.equal(commandSupported(caps("navigateToPoint"), "navigate"), true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("an unknown command kind is refused, not defaulted", () => {
|
|
132
|
+
for (const kind of ["selfDestruct", "", null, undefined, "constructor"]) {
|
|
133
|
+
assert.equal(commandSupported(ALL, kind), false, String(kind));
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/* --------------------------------------------------------------- registry */
|
|
138
|
+
|
|
139
|
+
test("an adapter is found by brand, and never by fallback", () => {
|
|
140
|
+
clearRobotAdapters();
|
|
141
|
+
const a = { brand: "brand-a", displayName: "Brand A", capabilities: ALL, listRobots: async () => [], getRobot: async () => null };
|
|
142
|
+
registerRobotAdapter(a);
|
|
143
|
+
|
|
144
|
+
assert.equal(robotAdapterFor("brand-a"), a);
|
|
145
|
+
// Falling back to the only registered adapter would send one brand's robot a
|
|
146
|
+
// command in a protocol it does not speak.
|
|
147
|
+
assert.equal(robotAdapterFor("brand-b"), null);
|
|
148
|
+
assert.equal(robotAdapterFor(""), null);
|
|
149
|
+
assert.equal(robotAdapterFor(null), null);
|
|
150
|
+
assert.equal(robotAdapterFor(undefined), null);
|
|
151
|
+
assert.equal(robotAdapterFor(42), null);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("an adapter without a brand is refused at registration", () => {
|
|
155
|
+
clearRobotAdapters();
|
|
156
|
+
assert.throws(() => registerRobotAdapter({ displayName: "x" }), /must name its brand/);
|
|
157
|
+
assert.throws(() => registerRobotAdapter(null), /must name its brand/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("the brand list reports each brand's tier, for a settings picker", () => {
|
|
161
|
+
clearRobotAdapters();
|
|
162
|
+
registerRobotAdapter({ brand: "good", displayName: "Good", capabilities: ALL, listRobots: async () => [], getRobot: async () => null });
|
|
163
|
+
registerRobotAdapter({ brand: "poor", displayName: "Poor", capabilities: caps("readLocation"), listRobots: async () => [], getRobot: async () => null });
|
|
164
|
+
|
|
165
|
+
assert.deepEqual(registeredRobotBrands().sort((x, y) => x.brand.localeCompare(y.brand)), [
|
|
166
|
+
{ brand: "good", displayName: "Good", tier: "full-control" },
|
|
167
|
+
{ brand: "poor", displayName: "Poor", tier: "no-interface" },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("re-registering a brand replaces it rather than duplicating", () => {
|
|
172
|
+
clearRobotAdapters();
|
|
173
|
+
const base = { brand: "x", displayName: "One", capabilities: {}, listRobots: async () => [], getRobot: async () => null };
|
|
174
|
+
registerRobotAdapter(base);
|
|
175
|
+
registerRobotAdapter({ ...base, displayName: "Two" });
|
|
176
|
+
|
|
177
|
+
assert.equal(registeredRobotBrands().length, 1);
|
|
178
|
+
assert.equal(robotAdapterFor("x").displayName, "Two");
|
|
179
|
+
});
|