@apocaliss92/nodelink-js 0.3.4 → 0.3.9
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/README.md +48 -82
- package/dist/{DiagnosticsTools-2JQRV5FE.js → DiagnosticsTools-DQDDBRM6.js} +4 -2
- package/dist/{chunk-APEEZ4UN.js → chunk-6Q6MK4WG.js} +172 -1
- package/dist/chunk-6Q6MK4WG.js.map +1 -0
- package/dist/{chunk-YSEFEQYV.js → chunk-OGIKBDON.js} +49 -8
- package/dist/chunk-OGIKBDON.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +215 -4
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +2 -2
- package/dist/index.cjs +756 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -1
- package/dist/index.d.ts +165 -0
- package/dist/index.js +541 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/dist/chunk-APEEZ4UN.js.map +0 -1
- package/dist/chunk-YSEFEQYV.js.map +0 -1
- /package/dist/{DiagnosticsTools-2JQRV5FE.js.map → DiagnosticsTools-DQDDBRM6.js.map} +0 -0
package/README.md
CHANGED
|
@@ -42,10 +42,10 @@ The library includes a **complete web-based management interface** for easy came
|
|
|
42
42
|
- 📡 **NVR / Hub Support** - Add NVRs as first-class entities, discover channels, and manage child cameras. All cameras on an NVR share a single connection (like Scrypted). Connect/disconnect at the NVR level; add or remove cameras at any time via channel discovery
|
|
43
43
|
- 🔋 **Battery Camera Support** - Cameras are auto-detected as battery-powered when they emit sleep/wake events. Per-camera battery mode setting: **Stream Only** (default — camera sleeps when no stream clients) or **Always On** (stays awake while connected). Live awake/sleeping badge on each camera card. Controls and stream discovery are paused while the camera sleeps to avoid unnecessary wake-ups
|
|
44
44
|
- 💡 **Camera Controls** - Toggle floodlight, siren, floodlight-on-motion, siren-on-motion, PTZ auto-tracking, and PIR sensor directly from the camera card. PTZ directional controls and preset navigation via a dedicated modal
|
|
45
|
-
- 📹 **Live Streaming** -
|
|
45
|
+
- 📹 **Live Streaming via go2rtc** - WebRTC, MSE/MP4, HLS, RTSP, and snapshot output powered by an embedded go2rtc restreamer. Stream options are cached so battery cameras show available streams even while sleeping
|
|
46
46
|
- 🔔 **Real-time Events** - Per-camera event viewer with live SSE updates (motion, doorbell, people, vehicle, animal, face, package, day/night, sleep/wake). Events are broadcast via SSE, NDJSON stream, and MQTT
|
|
47
|
-
- 📊 **Real-time Logs** - Monitor camera events and
|
|
48
|
-
- ⚙️ **Settings** - Configure
|
|
47
|
+
- 📊 **Real-time Logs** - Monitor camera events, system logs, and go2rtc process output
|
|
48
|
+
- ⚙️ **Settings** - Configure go2rtc ports, auto-start options, MQTT broker, and Home Assistant discovery
|
|
49
49
|
- 📱 **PWA Support** - Install as a Progressive Web App on mobile devices
|
|
50
50
|
- 🌐 **Responsive Design** - Works on desktop, tablet, and mobile
|
|
51
51
|
|
|
@@ -129,12 +129,12 @@ Recommended example:
|
|
|
129
129
|
services:
|
|
130
130
|
nodelink-manager:
|
|
131
131
|
ports:
|
|
132
|
-
- "3000:3000"
|
|
133
|
-
- "
|
|
134
|
-
- "
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
# -
|
|
132
|
+
- "3000:3000" # Web UI and API
|
|
133
|
+
- "11984:11984" # go2rtc API + dashboard
|
|
134
|
+
- "18554:18554" # go2rtc RTSP output
|
|
135
|
+
- "18555:18555/udp" # go2rtc WebRTC ICE
|
|
136
|
+
# Then configure Settings → go2rtc:
|
|
137
|
+
# - ICE servers if needed for NAT traversal
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
Notes:
|
|
@@ -144,11 +144,16 @@ Notes:
|
|
|
144
144
|
|
|
145
145
|
**Environment Variables:**
|
|
146
146
|
|
|
147
|
-
| Variable
|
|
148
|
-
|
|
|
149
|
-
| `PORT`
|
|
150
|
-
| `RTSP_PORT` | `8554` | RTSP proxy port |
|
|
147
|
+
| Variable | Default | Description |
|
|
148
|
+
| --- | --- | --- |
|
|
149
|
+
| `PORT` | `3000` | HTTP server port |
|
|
151
150
|
| `DATA_PATH` | `/data` | Directory for settings.json and logs |
|
|
151
|
+
| `GO2RTC_PATH` | (auto) | Path to go2rtc binary (falls back to bundled `go2rtc-static`) |
|
|
152
|
+
| `GO2RTC_API_PORT` | `11984` | go2rtc REST API + web dashboard port |
|
|
153
|
+
| `GO2RTC_RTSP_PORT` | `18554` | go2rtc RTSP output port |
|
|
154
|
+
| `GO2RTC_WEBRTC_PORT` | `18555` | go2rtc WebRTC ICE port |
|
|
155
|
+
|
|
156
|
+
Environment variables override `settings.json` values. Ports are also configurable in Settings → go2rtc.
|
|
152
157
|
|
|
153
158
|
**WebRTC / ICE (Docker bridge mode):**
|
|
154
159
|
|
|
@@ -162,88 +167,46 @@ Notes:
|
|
|
162
167
|
| `AUTH_ENABLED` | (unset) | Enable auth when set to `1/true` (or disable with `0/false`). If unset, auth auto-enables when `ADMIN_PASSWORD` is set. |
|
|
163
168
|
| `ADMIN_PASSWORD` | (unset) | Sets the `admin` password. This credential works for both the web login form and HTTP Basic auth. |
|
|
164
169
|
|
|
165
|
-
### Streaming
|
|
166
|
-
|
|
167
|
-
When authentication is enabled (see `AUTH_ENABLED` / `ADMIN_PASSWORD`), **all streaming endpoints are protected**.
|
|
170
|
+
### Streaming via go2rtc
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
All streaming is handled by an embedded **go2rtc** process (default API port `11984`, RTSP port `18554`). go2rtc provides:
|
|
170
173
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
| Format | URL | Notes |
|
|
175
|
+
|--------|-----|-------|
|
|
176
|
+
| **WebRTC** | `POST http://HOST:11984/api/webrtc?src={name}` | WHEP signaling (SDP offer/answer) |
|
|
177
|
+
| **MSE/MP4** | `http://HOST:11984/api/stream.mp4?src={name}` | Fragmented MP4 for browsers |
|
|
178
|
+
| **HLS** | `http://HOST:11984/api/stream.m3u8?src={name}` | Adaptive streaming |
|
|
179
|
+
| **RTSP** | `rtsp://HOST:18554/{name}` | For VLC, ffmpeg, NVR software |
|
|
180
|
+
| **Snapshot** | `http://HOST:11984/api/frame.jpeg?src={name}` | Single JPEG (requires ffmpeg) |
|
|
181
|
+
| **Dashboard** | `http://HOST:11984/` | go2rtc web UI |
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
Stream names follow the pattern `{sanitized_camera_name}_{profile}` (e.g. `studio_main`, `garage_sub`).
|
|
181
184
|
|
|
182
|
-
|
|
185
|
+
go2rtc has CORS enabled (`origin: "*"`) so browser-based players can connect directly.
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
- Credentials: the same **Users** list used by the dashboard.
|
|
186
|
-
- Digest realm: `RTSP Proxy`
|
|
187
|
-
- You can toggle whether auth is required via the Manager UI setting **“Require auth for RTSP connections”**.
|
|
187
|
+
### Authentication
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# ffmpeg (Digest)
|
|
193
|
-
ffmpeg -rtsp_transport tcp -i "rtsp://USERNAME:PASSWORD@HOST:8554/camera/main" -f null -
|
|
189
|
+
When authentication is enabled (see `AUTH_ENABLED` / `ADMIN_PASSWORD`), the Manager API endpoints are protected. go2rtc streaming endpoints are currently unauthenticated (accessible on the local network).
|
|
194
190
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
2. **HTTP-based streaming (MJPEG / HLS): token in query string**
|
|
200
|
-
|
|
201
|
-
Browsers cannot reliably attach custom headers (like `Authorization`) to media tags (`<img>`, `<video>`), so MJPEG/HLS streams must be accessed with the auth token in the URL query string:
|
|
202
|
-
|
|
203
|
-
- MJPEG: `/api/mpeg/<camera>/<profile>?token=...`
|
|
204
|
-
- HLS playlist: `/api/hls/<camera>/<profile>/playlist.m3u8?token=...` (and segment requests will inherit the query param)
|
|
191
|
+
- Manager API: `Authorization: Bearer <token>` header or session cookie
|
|
192
|
+
- WebSocket logs: `?token=...` in the WS URL
|
|
205
193
|
|
|
206
194
|
Examples:
|
|
207
195
|
|
|
208
|
-
```text
|
|
209
|
-
MJPEG:
|
|
210
|
-
http://HOST:3000/api/mpeg/camera/main?token=YOUR_TOKEN
|
|
211
|
-
|
|
212
|
-
HLS:
|
|
213
|
-
http://HOST:3000/api/hls/camera/main/playlist.m3u8?token=YOUR_TOKEN
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Security note: query tokens may end up in logs/history. Treat them like passwords.
|
|
217
|
-
|
|
218
|
-
3. **WebRTC control endpoints: Bearer token in Authorization header**
|
|
219
|
-
|
|
220
|
-
WebRTC signaling uses JSON endpoints (create session, send ICE candidates, send answer) and supports standard Bearer auth:
|
|
221
|
-
|
|
222
196
|
```bash
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
-d '{"username":"admin","password":"YOUR_PASSWORD"}'
|
|
227
|
-
|
|
228
|
-
# 2) Use the returned token for WebRTC signaling
|
|
229
|
-
curl -sS http://HOST:3000/api/webrtc/status \
|
|
230
|
-
-H "Authorization: Bearer YOUR_TOKEN"
|
|
231
|
-
```
|
|
197
|
+
# RTSP via go2rtc (no auth)
|
|
198
|
+
ffmpeg -rtsp_transport tcp -i “rtsp://HOST:18554/studio_main” -f null -
|
|
199
|
+
vlc “rtsp://HOST:18554/studio_main”
|
|
232
200
|
|
|
233
|
-
|
|
201
|
+
# WebRTC WHEP signaling
|
|
202
|
+
curl -X POST “http://HOST:11984/api/webrtc?src=studio_main” \
|
|
203
|
+
-H “Content-Type: application/sdp” \
|
|
204
|
+
--data-binary @offer.sdp
|
|
234
205
|
|
|
235
|
-
|
|
236
|
-
curl -
|
|
237
|
-
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
238
|
-
-H 'content-type: application/json' \
|
|
239
|
-
-d '{}'
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
4. **WebSocket logs: token in query string**
|
|
206
|
+
# Snapshot
|
|
207
|
+
curl -o snap.jpg “http://HOST:11984/api/frame.jpeg?src=studio_main”
|
|
243
208
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
```text
|
|
209
|
+
# WebSocket logs (auth)
|
|
247
210
|
ws://HOST:3000/ws/logs?token=YOUR_TOKEN
|
|
248
211
|
```
|
|
249
212
|
|
|
@@ -262,10 +225,13 @@ The Manager UI exposes a REST API for integrations, scripts, and third-party app
|
|
|
262
225
|
| Category | Endpoints |
|
|
263
226
|
|----------|-----------|
|
|
264
227
|
| **Auth** | `GET /api/auth/config`, `POST /api/auth/login`, `POST /api/auth/personal-token` |
|
|
265
|
-
| **Streaming** |
|
|
228
|
+
| **go2rtc Streaming** | Served directly by go2rtc (default port `11984`): WebRTC, MSE/MP4, HLS, RTSP, Snapshot |
|
|
229
|
+
| **go2rtc Management** | tRPC: `go2rtc.start`, `go2rtc.stop`, `go2rtc.status`, `go2rtc.listStreams` |
|
|
266
230
|
| **Events** | `GET /api/events/sse` (SSE), `GET /api/events/stream` (NDJSON), `GET /api/events/status` |
|
|
267
231
|
| **System** | `GET /api/health`, `GET /api/metrics`, `GET /api/updates` |
|
|
268
232
|
|
|
233
|
+
**Streaming** — All video output (WebRTC, MSE, HLS, RTSP, snapshots) is handled by an embedded go2rtc restreamer. The Manager creates internal RTSP servers per stream and registers them with go2rtc, which provides multi-format output with audio support.
|
|
234
|
+
|
|
269
235
|
**Events** — Real-time camera events (motion, doorbell, people, vehicle, etc.) via Server-Sent Events or NDJSON stream. When MQTT is configured, events are also published to the broker.
|
|
270
236
|
|
|
271
237
|
📖 **[Full Manager API documentation →](./documentation/manager-api.md)**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
captureModelFixtures,
|
|
2
3
|
collectCgiDiagnostics,
|
|
3
4
|
collectMultifocalDiagnostics,
|
|
4
5
|
collectNativeDiagnostics,
|
|
@@ -9,8 +10,9 @@ import {
|
|
|
9
10
|
runMultifocalDiagnosticsConsecutively,
|
|
10
11
|
sampleStreams,
|
|
11
12
|
testChannelStreams
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6Q6MK4WG.js";
|
|
13
14
|
export {
|
|
15
|
+
captureModelFixtures,
|
|
14
16
|
collectCgiDiagnostics,
|
|
15
17
|
collectMultifocalDiagnostics,
|
|
16
18
|
collectNativeDiagnostics,
|
|
@@ -22,4 +24,4 @@ export {
|
|
|
22
24
|
sampleStreams,
|
|
23
25
|
testChannelStreams
|
|
24
26
|
};
|
|
25
|
-
//# sourceMappingURL=DiagnosticsTools-
|
|
27
|
+
//# sourceMappingURL=DiagnosticsTools-DQDDBRM6.js.map
|
|
@@ -4903,6 +4903,10 @@ function writeJson(filePath, obj) {
|
|
|
4903
4903
|
mkdirp(path4.dirname(filePath));
|
|
4904
4904
|
fs4.writeFileSync(filePath, JSON.stringify(obj, null, 2));
|
|
4905
4905
|
}
|
|
4906
|
+
function writeText(filePath, text) {
|
|
4907
|
+
mkdirp(path4.dirname(filePath));
|
|
4908
|
+
fs4.writeFileSync(filePath, text);
|
|
4909
|
+
}
|
|
4906
4910
|
function appendNdjson(filePath, obj) {
|
|
4907
4911
|
mkdirp(path4.dirname(filePath));
|
|
4908
4912
|
fs4.appendFileSync(filePath, JSON.stringify(obj) + "\n");
|
|
@@ -7166,6 +7170,172 @@ async function runAllDiagnosticsConsecutively(params) {
|
|
|
7166
7170
|
streamsDir
|
|
7167
7171
|
};
|
|
7168
7172
|
}
|
|
7173
|
+
async function captureModelFixtures(params) {
|
|
7174
|
+
const { api, channel, outDir } = params;
|
|
7175
|
+
const log = params.log ?? console.log;
|
|
7176
|
+
mkdirp(outDir);
|
|
7177
|
+
const calls = {};
|
|
7178
|
+
const errors = [];
|
|
7179
|
+
async function capture(name, fn, writer) {
|
|
7180
|
+
try {
|
|
7181
|
+
const value = await fn();
|
|
7182
|
+
calls[name] = { ok: true, value };
|
|
7183
|
+
if (writer && value !== void 0 && value !== null) {
|
|
7184
|
+
writer(value);
|
|
7185
|
+
}
|
|
7186
|
+
log(` \u2713 ${name}`);
|
|
7187
|
+
return value;
|
|
7188
|
+
} catch (e) {
|
|
7189
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7190
|
+
calls[name] = { ok: false, error: msg };
|
|
7191
|
+
errors.push(`${name}: ${msg}`);
|
|
7192
|
+
log(` \u2717 ${name}: ${msg}`);
|
|
7193
|
+
return void 0;
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
const info = await capture(
|
|
7197
|
+
"getInfo",
|
|
7198
|
+
() => api.getInfo(channel),
|
|
7199
|
+
(v) => writeJson(path4.join(outDir, "device-info.json"), v)
|
|
7200
|
+
);
|
|
7201
|
+
const support = await capture(
|
|
7202
|
+
"getSupportInfo",
|
|
7203
|
+
() => api.getSupportInfo(),
|
|
7204
|
+
(v) => writeJson(path4.join(outDir, "support-info.json"), v)
|
|
7205
|
+
);
|
|
7206
|
+
const abilities = await capture(
|
|
7207
|
+
"getAbilityInfo",
|
|
7208
|
+
() => api.getAbilityInfo(),
|
|
7209
|
+
(v) => writeJson(path4.join(outDir, "ability-info.json"), v)
|
|
7210
|
+
);
|
|
7211
|
+
await capture(
|
|
7212
|
+
"getDeviceCapabilities",
|
|
7213
|
+
() => api.getDeviceCapabilities(channel),
|
|
7214
|
+
(v) => writeJson(path4.join(outDir, "capabilities.json"), v)
|
|
7215
|
+
);
|
|
7216
|
+
await capture("cmd289-WhiteLed", () => api.sendXml({
|
|
7217
|
+
cmdId: BC_CMD_ID_GET_WHITE_LED,
|
|
7218
|
+
channel,
|
|
7219
|
+
timeoutMs: 3e3
|
|
7220
|
+
}), (v) => writeText(path4.join(outDir, "cmd289-white-led.xml"), v));
|
|
7221
|
+
await capture(
|
|
7222
|
+
"getStreamMetadata",
|
|
7223
|
+
() => api.getStreamMetadata(channel),
|
|
7224
|
+
(v) => writeJson(path4.join(outDir, "stream-metadata.json"), v)
|
|
7225
|
+
);
|
|
7226
|
+
await capture(
|
|
7227
|
+
"getEncXml",
|
|
7228
|
+
() => api.getEncXml(channel),
|
|
7229
|
+
(v) => writeText(path4.join(outDir, "enc-config.xml"), v)
|
|
7230
|
+
);
|
|
7231
|
+
await capture(
|
|
7232
|
+
"getPorts",
|
|
7233
|
+
() => api.getPorts(),
|
|
7234
|
+
(v) => writeJson(path4.join(outDir, "ports.json"), v)
|
|
7235
|
+
);
|
|
7236
|
+
await capture(
|
|
7237
|
+
"getTalkAbility",
|
|
7238
|
+
() => api.getTalkAbility(channel),
|
|
7239
|
+
(v) => writeJson(path4.join(outDir, "talk-ability.json"), v)
|
|
7240
|
+
);
|
|
7241
|
+
await capture(
|
|
7242
|
+
"getTwoWayAudioConfig",
|
|
7243
|
+
() => api.getTwoWayAudioConfig(channel),
|
|
7244
|
+
(v) => writeJson(path4.join(outDir, "two-way-audio-config.json"), v)
|
|
7245
|
+
);
|
|
7246
|
+
await capture(
|
|
7247
|
+
"getAiState",
|
|
7248
|
+
() => api.getAiState(channel),
|
|
7249
|
+
(v) => writeJson(path4.join(outDir, "ai-state.json"), v)
|
|
7250
|
+
);
|
|
7251
|
+
await capture(
|
|
7252
|
+
"getAiCfg",
|
|
7253
|
+
() => api.getAiCfg(channel),
|
|
7254
|
+
(v) => writeJson(path4.join(outDir, "ai-cfg.json"), v)
|
|
7255
|
+
);
|
|
7256
|
+
await capture(
|
|
7257
|
+
"getOsd",
|
|
7258
|
+
() => api.getOsd(channel),
|
|
7259
|
+
(v) => writeJson(path4.join(outDir, "osd.json"), v)
|
|
7260
|
+
);
|
|
7261
|
+
await capture(
|
|
7262
|
+
"getMotionAlarm",
|
|
7263
|
+
() => api.getMotionAlarm(channel),
|
|
7264
|
+
(v) => writeJson(path4.join(outDir, "motion-alarm.json"), v)
|
|
7265
|
+
);
|
|
7266
|
+
await capture(
|
|
7267
|
+
"getRecordCfg",
|
|
7268
|
+
() => api.getRecordCfg(channel),
|
|
7269
|
+
(v) => writeJson(path4.join(outDir, "record-cfg.json"), v)
|
|
7270
|
+
);
|
|
7271
|
+
await capture(
|
|
7272
|
+
"getVideoInput",
|
|
7273
|
+
() => api.getVideoInput(channel),
|
|
7274
|
+
(v) => writeJson(path4.join(outDir, "video-input.json"), v)
|
|
7275
|
+
);
|
|
7276
|
+
await capture(
|
|
7277
|
+
"getPtzPresets",
|
|
7278
|
+
() => api.getPtzPresets(channel),
|
|
7279
|
+
(v) => writeJson(path4.join(outDir, "ptz-presets.json"), v)
|
|
7280
|
+
);
|
|
7281
|
+
await capture(
|
|
7282
|
+
"getNetworkInfo",
|
|
7283
|
+
() => api.getNetworkInfo(),
|
|
7284
|
+
(v) => writeJson(path4.join(outDir, "network-info.json"), v)
|
|
7285
|
+
);
|
|
7286
|
+
await capture(
|
|
7287
|
+
"getSystemGeneral",
|
|
7288
|
+
() => api.getSystemGeneral(),
|
|
7289
|
+
(v) => writeJson(path4.join(outDir, "system-general.json"), v)
|
|
7290
|
+
);
|
|
7291
|
+
await capture(
|
|
7292
|
+
"getWifiSignal",
|
|
7293
|
+
() => api.getWifiSignal(channel),
|
|
7294
|
+
(v) => writeJson(path4.join(outDir, "wifi-signal.json"), v)
|
|
7295
|
+
);
|
|
7296
|
+
await capture(
|
|
7297
|
+
"getWhiteLedState",
|
|
7298
|
+
() => api.getWhiteLedState(channel),
|
|
7299
|
+
(v) => writeJson(path4.join(outDir, "white-led-state.json"), v)
|
|
7300
|
+
);
|
|
7301
|
+
await capture(
|
|
7302
|
+
"getFloodlightOnMotion",
|
|
7303
|
+
() => api.getFloodlightOnMotion(channel),
|
|
7304
|
+
(v) => writeJson(path4.join(outDir, "floodlight-on-motion.json"), v)
|
|
7305
|
+
);
|
|
7306
|
+
await capture(
|
|
7307
|
+
"buildVideoStreamOptions",
|
|
7308
|
+
() => api.buildVideoStreamOptions({ channel }),
|
|
7309
|
+
(v) => writeJson(path4.join(outDir, "video-stream-options.json"), v)
|
|
7310
|
+
);
|
|
7311
|
+
const total = Object.keys(calls).length;
|
|
7312
|
+
const ok = Object.values(calls).filter((c) => c.ok).length;
|
|
7313
|
+
const failed = total - ok;
|
|
7314
|
+
const summary = { total, ok, failed, errors };
|
|
7315
|
+
writeJson(path4.join(outDir, "_summary.json"), {
|
|
7316
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7317
|
+
model: info?.type ?? "unknown",
|
|
7318
|
+
itemNo: info?.itemNo ?? "unknown",
|
|
7319
|
+
firmwareVersion: info?.firmwareVersion ?? "unknown",
|
|
7320
|
+
channel,
|
|
7321
|
+
...summary,
|
|
7322
|
+
calls: Object.fromEntries(
|
|
7323
|
+
Object.entries(calls).map(([k, v]) => [
|
|
7324
|
+
k,
|
|
7325
|
+
v.ok ? "ok" : `FAILED: ${v.error}`
|
|
7326
|
+
])
|
|
7327
|
+
)
|
|
7328
|
+
});
|
|
7329
|
+
log(`
|
|
7330
|
+
Summary: ${ok}/${total} ok, ${failed} failed`);
|
|
7331
|
+
if (errors.length) {
|
|
7332
|
+
log(` Errors:`);
|
|
7333
|
+
for (const err of errors) {
|
|
7334
|
+
log(` - ${err}`);
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
return { calls, outDir, summary };
|
|
7338
|
+
}
|
|
7169
7339
|
|
|
7170
7340
|
export {
|
|
7171
7341
|
__require,
|
|
@@ -7348,7 +7518,8 @@ export {
|
|
|
7348
7518
|
collectMultifocalDiagnostics,
|
|
7349
7519
|
runMultifocalDiagnosticsConsecutively,
|
|
7350
7520
|
runAllDiagnosticsConsecutively,
|
|
7521
|
+
captureModelFixtures,
|
|
7351
7522
|
parseRecordingFileName,
|
|
7352
7523
|
ReolinkCgiApi
|
|
7353
7524
|
};
|
|
7354
|
-
//# sourceMappingURL=chunk-
|
|
7525
|
+
//# sourceMappingURL=chunk-6Q6MK4WG.js.map
|