@7365admin1/core 3.42.3 → 3.44.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 +302 -0
- package/dist/index.d.ts +1001 -2
- package/dist/index.js +8748 -6427
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8492 -6233
- package/dist/index.mjs.map +1 -1
- package/docs/camera-integration-config.md +191 -0
- package/package.json +3 -2
- package/test/camera-capability.util.test.mjs +545 -0
- package/test/camera-device-http.test.mjs +792 -0
- package/test/camera-entitlement-wiring.test.mjs +134 -0
- package/test/camera-view.util.test.mjs +892 -0
- package/test/camera-write-gate.test.mjs +121 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Camera integration — configuration reference
|
|
2
|
+
|
|
3
|
+
How to switch each camera capability on, and who has to approve it.
|
|
4
|
+
|
|
5
|
+
Everything here is **configuration**. Nothing in this document requires a code
|
|
6
|
+
change, which is the point of the integration layer: the transports, the Dahua
|
|
7
|
+
adapter and the capability descriptor are already built and tested, and each
|
|
8
|
+
capability starts answering `supported` when the variables below are set.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. What works with no configuration at all
|
|
13
|
+
|
|
14
|
+
| Capability | Transport | Needs |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `liveVideo` | `RELAY_PLAYER` | nothing — the camera record's `host` is the player-page URL, and a client renders it in a WebView |
|
|
17
|
+
| `digitalZoom` | `RELAY_PLAYER` / `RTSP_FRAME` | nothing — it is client-side, on whichever picture arrived |
|
|
18
|
+
|
|
19
|
+
## 2. What needs the RTSP recorder (already set on staging)
|
|
20
|
+
|
|
21
|
+
| Capability | Transport | Needs |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `stillFrame` | `RTSP_FRAME` | `ffmpeg` on the API host, and `CAMERA_RTSP_DEVICES` |
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
CAMERA_RTSP_DEVICES={"<relay-authority>":{"host":"<recorder-host>","port":554,"username":"<user>","password":"<password>"}}
|
|
27
|
+
CAMERA_FFMPEG_PATH=/usr/bin/ffmpeg # optional; "ffmpeg" on PATH by default
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Keyed by **relay authority** — the host part of `site.cameras.host` — because
|
|
31
|
+
that is the only stable identifier a camera record carries. The numeric last
|
|
32
|
+
path segment of the stored host is the **channel** on that recorder.
|
|
33
|
+
|
|
34
|
+
A camera whose relay has no entry here reports
|
|
35
|
+
`stillFrame: unsupported / no-recorder-configured`, which is a deployment fix. A
|
|
36
|
+
camera whose address carries no channel reports `no-channel-in-address`, which is
|
|
37
|
+
a data fix. The two are deliberately different answers.
|
|
38
|
+
|
|
39
|
+
### 2.1 The same entry is what makes camera SETUP self-service
|
|
40
|
+
|
|
41
|
+
`CAMERA_RTSP_DEVICES` is read in both directions now.
|
|
42
|
+
|
|
43
|
+
- **Viewing** asks "given this relay, which recorder?" — the direction the stored
|
|
44
|
+
records need.
|
|
45
|
+
- **Setting a camera up** asks the opposite: the person at the form has the
|
|
46
|
+
recorder in front of them and no idea what a relay is, so the server looks the
|
|
47
|
+
entry up by recorder host and builds `https://<relay-authority>/<channel>`
|
|
48
|
+
itself. There is deliberately no second map.
|
|
49
|
+
|
|
50
|
+
So **one entry per recorder unlocks every channel on it** for the site's own
|
|
51
|
+
staff. A recorder with no entry is refused at the form with *"This recorder is
|
|
52
|
+
not connected to the video service yet…"* — the same deployment fix as
|
|
53
|
+
`no-recorder-configured`, said to a property manager instead of to a lead.
|
|
54
|
+
|
|
55
|
+
The **"Test this camera"** button in the form reads the same entry. It is
|
|
56
|
+
rationed before it can reach the device — one test per camera per 15 s, and 4
|
|
57
|
+
per recorder per 5 minutes — and it checks the shared authentication budget
|
|
58
|
+
first, so pressing it cannot walk a recorder into its own
|
|
59
|
+
3-failures-in-30-seconds lockout.
|
|
60
|
+
|
|
61
|
+
## 3. What needs the camera's own HTTP interface — OFF today
|
|
62
|
+
|
|
63
|
+
**Blocked on infrastructure, not on code.** Measured 2026-08-10 from this
|
|
64
|
+
workstation and from the staging API host: the recorder answers on **RTSP/554
|
|
65
|
+
only**. Ports 80, 443 and the Dahua native port 37777 time out. Until that
|
|
66
|
+
changes, every variable in this section can be set and nothing will answer.
|
|
67
|
+
|
|
68
|
+
| Capability | Needs |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `deviceInfo` | §2 not required · `CAMERA_DEVICE_HTTP` + `CAMERA_DEVICE_HTTP_ENABLED` + reachability |
|
|
71
|
+
| `playback` | the same |
|
|
72
|
+
| `events` | the same |
|
|
73
|
+
| `stillFrame` (fallback) | the same — used only when no RTSP recorder is configured |
|
|
74
|
+
| `ptz` | the above **plus** `CAMERA_DEVICE_CONTROL_ENABLED` **plus** a PTZ-capable unit **plus** `CAMERA_PTZ_ENABLED` |
|
|
75
|
+
| `presets` | the same as `ptz` |
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
# 1. Where the recorder's HTTP interface is. The credential is REFERENCED by
|
|
79
|
+
# environment-variable NAME and never written here, so this value is safe to
|
|
80
|
+
# paste into a PR or a ticket.
|
|
81
|
+
CAMERA_DEVICE_HTTP={"<relay-authority>":{"baseUrl":"https://<recorder-host>:443","credentialRef":"CAMERA_DEVICE_CRED_MAIN"}}
|
|
82
|
+
|
|
83
|
+
# 2. The secret itself, set separately, alongside the host's other secrets.
|
|
84
|
+
CAMERA_DEVICE_CRED_MAIN=<username>:<password>
|
|
85
|
+
|
|
86
|
+
# 3. Reads. Off by default. Nothing contacts a device while this is unset.
|
|
87
|
+
CAMERA_DEVICE_HTTP_ENABLED=true
|
|
88
|
+
|
|
89
|
+
# 4. Optional tuning.
|
|
90
|
+
CAMERA_DEVICE_HTTP_TIMEOUT_MS=8000
|
|
91
|
+
CAMERA_DEVICE_HTTP_PROBE_TTL_SECONDS=900
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Validation, and what happens when it fails
|
|
95
|
+
|
|
96
|
+
`CAMERA_DEVICE_HTTP` is validated at startup and every rejection is logged with
|
|
97
|
+
the variable name and the reason. A rejected entry is **dropped, never
|
|
98
|
+
half-used**, and its cameras then report `device-http-not-configured` truthfully
|
|
99
|
+
rather than hanging on a request.
|
|
100
|
+
|
|
101
|
+
- `baseUrl` must be an `http(s)` **origin** — scheme, host, optional port. A path,
|
|
102
|
+
query or fragment is rejected.
|
|
103
|
+
- `credentialRef` must look like an environment variable name (`A-Z`, `0-9`, `_`).
|
|
104
|
+
A credential pasted into this field is **rejected, not used**.
|
|
105
|
+
- The referenced variable must exist and be `username:password`. The split is on
|
|
106
|
+
the first colon only, so a password may contain colons.
|
|
107
|
+
- Malformed JSON yields no recorders at all.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 4. Arming camera control — needs a named human
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
CAMERA_DEVICE_CONTROL_ENABLED=true
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**This is the only variable in this document that can move physical hardware, and
|
|
118
|
+
it must not be set without a backend lead's sign-off** (Benar or Kyle). It is a
|
|
119
|
+
separate variable from `CAMERA_DEVICE_HTTP_ENABLED` on purpose: turning reads on
|
|
120
|
+
must not arm motors.
|
|
121
|
+
|
|
122
|
+
Three independent switches must all be on before a PTZ command can be sent —
|
|
123
|
+
`CAMERA_PTZ_ENABLED`, `CAMERA_DEVICE_HTTP_ENABLED` and
|
|
124
|
+
`CAMERA_DEVICE_CONTROL_ENABLED` — and the unit must have told us through §8.1.3
|
|
125
|
+
that it actually pans. All three ship off, and no device has been moved.
|
|
126
|
+
|
|
127
|
+
While control is off, the adapter's `control` object is `null`: the mutating
|
|
128
|
+
functions are not merely guarded, they do not exist to be called.
|
|
129
|
+
|
|
130
|
+
**Never implemented, and it must stay that way:** any `configManager.cgi` write,
|
|
131
|
+
`setCurrentTime`, `SetPreset` / `ClearPreset`, tours, patterns, reboot, and any
|
|
132
|
+
barrier or gate command. This integration issues no configuration writes to any
|
|
133
|
+
device.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 5. The lockout budget
|
|
138
|
+
|
|
139
|
+
A Dahua unit locks an account for **1800 s after 3 failed logins in 30 s**
|
|
140
|
+
(V3.37 §4.7.x). On a unit that also runs ANPR and a barrier, that is half an hour
|
|
141
|
+
of a site's access control gone.
|
|
142
|
+
|
|
143
|
+
The failure budget is therefore **not configurable**. It counts failures per
|
|
144
|
+
device in the shared cache, stops at **2**, and once spent refuses to send
|
|
145
|
+
anything until the device's own lock time has elapsed — reporting
|
|
146
|
+
`device-http-locked-out` instead of retrying. It is shared across API workers
|
|
147
|
+
(the limit belongs to the device, not to a process) and persistent across
|
|
148
|
+
restarts (a deploy must not hand the budget back).
|
|
149
|
+
|
|
150
|
+
If the budget is spent, the cause is almost always a wrong credential in
|
|
151
|
+
`CAMERA_DEVICE_CRED_*`. Fix the credential; do not shorten the cooldown.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 6. Reading the descriptor
|
|
156
|
+
|
|
157
|
+
Each camera in the wall and status responses carries:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
"capabilities": {
|
|
161
|
+
"liveVideo": { "state": "supported", "transport": "RELAY_PLAYER", "reason": null, "detail": null },
|
|
162
|
+
"stillFrame": { "state": "supported", "transport": "RTSP_FRAME", "reason": null, "detail": null },
|
|
163
|
+
"ptz": { "state": "unsupported", "transport": null,
|
|
164
|
+
"reason": "device-http-not-configured",
|
|
165
|
+
"detail": "No direct connection to this camera's recorder is configured on this server." }
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**A client should switch on `reason`, never on `detail`.** `detail` is there so a
|
|
170
|
+
surface with nothing better to show has something honest to show.
|
|
171
|
+
|
|
172
|
+
Reason codes: `not-patrol-cctv-camera`, `camera-inactive`, `no-address`,
|
|
173
|
+
`invalid-address`, `not-a-relay-player-url`, `no-recorder-configured`,
|
|
174
|
+
`no-channel-in-address`, `device-http-not-configured`, `device-http-disabled`,
|
|
175
|
+
`device-http-unreachable`, `device-http-locked-out`, `device-not-probed`,
|
|
176
|
+
`device-no-ptz`, `control-not-enabled`, `no-transport`.
|
|
177
|
+
|
|
178
|
+
## 7. Adding a transport later
|
|
179
|
+
|
|
180
|
+
Register one — `registerCameraTransport({ id, provides, evaluate })` — and list
|
|
181
|
+
the capabilities it serves. Registration order is preference order. **No service,
|
|
182
|
+
controller or descriptor change is needed**, which is how an HLS relay, a MediaMTX
|
|
183
|
+
playback URL or an audio path would land: a capability no transport claims already
|
|
184
|
+
answers `no-transport`, and starts answering `supported` the moment something
|
|
185
|
+
claims it.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
_Written 2026-08-10 alongside the integration layer. Update when the recorder's
|
|
190
|
+
HTTP interface becomes reachable, when a transport is added, or when control is
|
|
191
|
+
armed anywhere._
|
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.44.0",
|
|
5
5
|
"author": "7365admin1",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"release": "yarn run build && changeset publish",
|
|
15
15
|
"lint": "tsc",
|
|
16
16
|
"test:e2e": "yarn build && node --test --test-concurrency=1 --test-timeout=600000 \"test/e2e/*.test.mjs\"",
|
|
17
|
-
"test:anpr": "tsup src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts --format esm --out-dir test/.build/utils --no-dts --silent && node --test \"test/camera-alert.util.test.mjs\" \"test/dahua-protocol.util.test.mjs\""
|
|
17
|
+
"test:anpr": "tsup src/utils/camera-alert.util.ts src/utils/dahua-protocol.util.ts --format esm --out-dir test/.build/utils --no-dts --silent && node --test \"test/camera-alert.util.test.mjs\" \"test/dahua-protocol.util.test.mjs\"",
|
|
18
|
+
"test:camera": "tsup src/utils/camera-view.util.ts src/utils/camera-capability.util.ts src/services/camera-device-http.service.ts --format esm --out-dir test/.build --no-dts --silent && node --test \"test/camera-view.util.test.mjs\" \"test/camera-capability.util.test.mjs\" \"test/camera-device-http.test.mjs\" \"test/camera-write-gate.test.mjs\" \"test/camera-entitlement-wiring.test.mjs\""
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"@changesets/cli": "^2.26.0",
|