@hanoilab/zk-bridge 0.1.4 → 0.1.6
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 +198 -105
- package/dist/cli/daemon.js +229 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/zk-bridge.js +93 -18
- package/dist/cli/zk-bridge.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,43 +1,133 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/badge/ZK--Bridge-Attendance-007ACC?style=for-the-badge&logo=fingerprint&logoColor=white" alt="ZK-Bridge" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">ZK-Bridge</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>LAN-side bridge for ZKTeco attendance devices.</strong><br/>
|
|
9
|
+
Polls a ZKTeco fingerprint reader over TCP, pushes events to any HTTP backend.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/@hanoilab/zk-bridge">npm</a> •
|
|
14
|
+
<a href="#quick-start">Quick Start</a> •
|
|
15
|
+
<a href="#features">Features</a> •
|
|
16
|
+
<a href="#how-it-works">How It Works</a> •
|
|
17
|
+
<a href="#backend-contract">Backend Contract</a> •
|
|
18
|
+
<a href="#self-hosting">Self-Hosting</a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<a href="https://www.npmjs.com/package/@hanoilab/zk-bridge"><img src="https://img.shields.io/npm/v/@hanoilab/zk-bridge?style=flat-square&color=cb3837&label=npm" alt="npm" /></a>
|
|
23
|
+
<img src="https://img.shields.io/badge/node-%3E%3D20.0.0-339933?style=flat-square&logo=node.js&logoColor=white" alt="node" />
|
|
24
|
+
<img src="https://img.shields.io/badge/sqlite-bundled-003B57?style=flat-square&logo=sqlite&logoColor=white" alt="sqlite" />
|
|
25
|
+
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="license" />
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## What is this?
|
|
31
|
+
|
|
32
|
+
ZKTeco fingerprint readers don't speak HTTP — they only accept TCP connections from inside the LAN, and the C-HR / HRIS backend lives in the cloud. ZK-Bridge sits in the office, polls the device on a schedule, and pushes attendance events to any HTTP API you point it at.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ZKTeco device ZK-Bridge Your backend
|
|
36
|
+
┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
37
|
+
│ Fingerprint / │ ◄─TCP─┤ CLI + UI │ ◄──HTTPS──┤ /push │
|
|
38
|
+
│ face reader │ 4370 │ SQLite │ │ /ping │
|
|
39
|
+
│ 192.168.x.y │ │ Local LAN │ │ Cloud / VPS │
|
|
40
|
+
└─────────────────┘ └──────────────┘ └──────────────┘
|
|
41
|
+
Outbound only — no port
|
|
42
|
+
forwarding or VPN needed
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
No vendor lock-in: any backend that exposes a JSON `POST` endpoint with a JWT auth header works. Bridge handles the LAN side.
|
|
4
46
|
|
|
5
47
|
## Quick Start
|
|
6
48
|
|
|
49
|
+
### 1. Install globally
|
|
50
|
+
|
|
7
51
|
```bash
|
|
8
|
-
# Install globally
|
|
9
52
|
npm i -g @hanoilab/zk-bridge
|
|
53
|
+
```
|
|
10
54
|
|
|
11
|
-
|
|
55
|
+
### 2. Run it
|
|
56
|
+
|
|
57
|
+
```bash
|
|
12
58
|
zk-bridge start
|
|
13
59
|
```
|
|
14
60
|
|
|
15
|
-
That's it. You'll see:
|
|
16
|
-
|
|
17
61
|
```text
|
|
18
|
-
[zk-bridge]
|
|
19
|
-
|
|
20
|
-
|
|
62
|
+
[zk-bridge] started (PID 12345)
|
|
63
|
+
Logs: ~/.local/share/zk-bridge/zk-bridge.log
|
|
64
|
+
Stop: zk-bridge stop
|
|
65
|
+
Tail: zk-bridge logs -f
|
|
21
66
|
```
|
|
22
67
|
|
|
23
|
-
|
|
68
|
+
`start` detaches — closing the terminal won't stop the bridge. Useful one-liners:
|
|
24
69
|
|
|
25
|
-
|
|
70
|
+
```bash
|
|
71
|
+
zk-bridge status # is it running?
|
|
72
|
+
zk-bridge logs -f # follow the log
|
|
73
|
+
zk-bridge stop # stop it
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Open the admin UI
|
|
26
77
|
|
|
27
|
-
|
|
28
|
-
- **LAN scan** — Auto-discover ZKTeco devices on your subnet (TCP 4370)
|
|
29
|
-
- **Multi-device** — One bridge polls many devices on a schedule
|
|
30
|
-
- **Offline tolerant** — Queues events when the backend is unreachable, drains on next cycle
|
|
31
|
-
- **Idempotent push** — Backend dedupes by `(deviceId, eventLogId)`; safe to retry
|
|
32
|
-
- **Auto-start on boot** — One-click systemd / Windows Task / launchd registration
|
|
33
|
-
- **Cross-platform** — Linux, macOS, Windows
|
|
34
|
-
- **Secure** — bcrypt admin login, signed-cookie sessions, JWT per-device tokens
|
|
78
|
+
Visit **<http://localhost:7000>**, set the backend Push URL, paste the per-device JWT — bridge pushes attendance on the next cycle.
|
|
35
79
|
|
|
36
|
-
##
|
|
80
|
+
## Features
|
|
81
|
+
|
|
82
|
+
### Web Admin UI
|
|
83
|
+
- Single-user login (bcrypt + signed-cookie session, 7-day TTL)
|
|
84
|
+
- Configure backend Push / Ping URL + poll interval
|
|
85
|
+
- Add devices manually or via LAN scan
|
|
86
|
+
- Per-device events with cursor position + push status badge
|
|
87
|
+
- Cycle history with status, timing, error message, filter by device
|
|
88
|
+
|
|
89
|
+
### LAN Discovery
|
|
90
|
+
- Auto-scan `/24` subnet for ZKTeco devices on TCP 4370
|
|
91
|
+
- Identify each candidate over the ZK protocol (model, serial)
|
|
92
|
+
- One-click "Add as device" pre-fills name + host
|
|
93
|
+
|
|
94
|
+
### Multi-Device
|
|
95
|
+
- One bridge polls many devices on a single schedule
|
|
96
|
+
- Per-device cursor, queue, audit log, error state
|
|
97
|
+
- Enable / disable individually without removing config
|
|
98
|
+
|
|
99
|
+
### Offline Tolerance
|
|
100
|
+
- Queues events to local SQLite when the backend is unreachable
|
|
101
|
+
- Drains the queue on the next online cycle
|
|
102
|
+
- Cursor advances even on partial failure — no data loss, no double-send
|
|
103
|
+
|
|
104
|
+
### Idempotent Push
|
|
105
|
+
- Backend dedupes by `(deviceId, eventLogId)` — replay is a no-op
|
|
106
|
+
- Batches events at 200/request to stay under common body-parser limits
|
|
107
|
+
- JWT version counter for revocation (regenerate on the backend → old JWTs rejected immediately)
|
|
108
|
+
|
|
109
|
+
### Auto-Start on Boot
|
|
110
|
+
- One-click toggle in the System page registers a:
|
|
111
|
+
- **systemd** unit (Linux)
|
|
112
|
+
- **Scheduled Task** (Windows)
|
|
113
|
+
- **launchd** plist (macOS)
|
|
114
|
+
- `Restart=on-failure` so a crashed cycle never takes the bridge down
|
|
115
|
+
|
|
116
|
+
### Cross-platform
|
|
117
|
+
- Linux, macOS, Windows
|
|
118
|
+
- Node 20+ — no native build needed (sqlite3 ships prebuilds)
|
|
119
|
+
|
|
120
|
+
## CLI
|
|
37
121
|
|
|
38
122
|
```bash
|
|
39
|
-
zk-bridge start # Start
|
|
40
|
-
zk-bridge
|
|
123
|
+
zk-bridge start # Start as background daemon (writes PID + log)
|
|
124
|
+
zk-bridge stop # Stop the daemon
|
|
125
|
+
zk-bridge restart # Stop + start
|
|
126
|
+
zk-bridge status # PID, uptime, log path
|
|
127
|
+
zk-bridge logs -f # Follow the log
|
|
128
|
+
zk-bridge logs -n 200 # Last 200 lines
|
|
129
|
+
zk-bridge run # Run in the foreground (debug / systemd / Docker)
|
|
130
|
+
zk-bridge poll-once # Run a single cycle then exit
|
|
41
131
|
zk-bridge reset-user # Forgot-password recovery
|
|
42
132
|
zk-bridge recent-events # Print last N events from a device
|
|
43
133
|
zk-bridge upgrade [tag] # Self-update via npm
|
|
@@ -45,28 +135,17 @@ zk-bridge --help
|
|
|
45
135
|
zk-bridge --version
|
|
46
136
|
```
|
|
47
137
|
|
|
48
|
-
|
|
138
|
+
`start` detaches from the launching shell — `Ctrl+C` in that terminal does NOT kill the daemon. Use `zk-bridge stop`. Logs go to `<DATA_DIR>/zk-bridge.log`.
|
|
49
139
|
|
|
50
|
-
|
|
140
|
+
Environment overrides (otherwise default):
|
|
51
141
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
- Toggle auto-start on boot
|
|
142
|
+
```bash
|
|
143
|
+
PORT=8080 BIND_HOST=0.0.0.0 zk-bridge start
|
|
144
|
+
DATA_DIR=/var/lib/zk-bridge zk-bridge start
|
|
145
|
+
```
|
|
57
146
|
|
|
58
147
|
## How It Works
|
|
59
148
|
|
|
60
|
-
```text
|
|
61
|
-
ZK device zk-bridge Your backend
|
|
62
|
-
+----------+ +-----------+ +------------+
|
|
63
|
-
| ZKTeco | <-----> | bridge | <-----> | HTTP API |
|
|
64
|
-
| TCP 4370 | ZK | (this CLI)| HTTPS | /push |
|
|
65
|
-
+----------+ +-----------+ +------------+
|
|
66
|
-
SQLite
|
|
67
|
-
(config + queue)
|
|
68
|
-
```
|
|
69
|
-
|
|
70
149
|
Every cycle (default 5 min):
|
|
71
150
|
|
|
72
151
|
1. List enabled devices in local SQLite.
|
|
@@ -74,20 +153,32 @@ Every cycle (default 5 min):
|
|
|
74
153
|
3. Drain the offline queue, then push new events to the backend in batches of 200.
|
|
75
154
|
4. Advance the cursor, write a `cycle_log` row.
|
|
76
155
|
|
|
77
|
-
|
|
156
|
+
State that lives locally:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
~/.local/share/zk-bridge/zk-bridge.db (Linux default)
|
|
160
|
+
%APPDATA%\zk-bridge\zk-bridge.db (Windows)
|
|
161
|
+
~/Library/Application Support/zk-bridge/zk-bridge.db (macOS)
|
|
162
|
+
|
|
163
|
+
├── users (single admin row)
|
|
164
|
+
├── config (push URL, ping URL, poll interval, session secret)
|
|
165
|
+
├── devices (host, port, JWT, cursor, last status)
|
|
166
|
+
├── event_queue (offline-pending events)
|
|
167
|
+
└── cycle_log (per-device cycle history, rotated to last 1000)
|
|
168
|
+
```
|
|
78
169
|
|
|
79
170
|
## Backend Contract
|
|
80
171
|
|
|
81
|
-
|
|
172
|
+
Two HTTP endpoints. Configure their full URLs in the UI — the bridge appends nothing.
|
|
82
173
|
|
|
83
|
-
|
|
174
|
+
### Push (required)
|
|
84
175
|
|
|
85
176
|
```http
|
|
86
177
|
POST <push-url>
|
|
87
178
|
Content-Type: application/json
|
|
88
179
|
|
|
89
180
|
{
|
|
90
|
-
"token": "<JWT>",
|
|
181
|
+
"token": "<JWT signed by backend>",
|
|
91
182
|
"events": [
|
|
92
183
|
{
|
|
93
184
|
"eventLogId": "12345",
|
|
@@ -99,7 +190,16 @@ Content-Type: application/json
|
|
|
99
190
|
}
|
|
100
191
|
```
|
|
101
192
|
|
|
102
|
-
|
|
193
|
+
The backend should:
|
|
194
|
+
|
|
195
|
+
- Verify the JWT (signature + expiry / version).
|
|
196
|
+
- Resolve the device row from the JWT payload.
|
|
197
|
+
- Dedupe by `(deviceId, eventLogId)` — replays are safe.
|
|
198
|
+
- Persist or normalize the events as needed.
|
|
199
|
+
|
|
200
|
+
Response shape isn't enforced — bridge only checks the HTTP status (2xx = success, anything else = retry / queue).
|
|
201
|
+
|
|
202
|
+
### Ping (optional)
|
|
103
203
|
|
|
104
204
|
```http
|
|
105
205
|
POST <ping-url>
|
|
@@ -108,100 +208,93 @@ Content-Type: application/json
|
|
|
108
208
|
{ "token": "<JWT>" }
|
|
109
209
|
```
|
|
110
210
|
|
|
111
|
-
If you don't expose a separate ping endpoint, leave the field
|
|
112
|
-
|
|
113
|
-
The backend is responsible for: verifying the JWT, resolving devices, deduping by `(deviceId, eventLogId)`, and storing or normalizing events.
|
|
114
|
-
|
|
115
|
-
## Requirements
|
|
211
|
+
Used by the **Connect** button to verify the JWT + URL without sending events. If you don't expose a separate ping endpoint, leave the field blank — the bridge falls back to a push with an empty `events` array (your backend should respond 4xx for that case, which the bridge interprets as "auth + URL OK").
|
|
116
212
|
|
|
117
|
-
|
|
118
|
-
- macOS, Linux, or Windows
|
|
119
|
-
- Network reach: bridge host must see the ZK device on LAN AND the backend over HTTP(S)
|
|
213
|
+
### Reference implementation
|
|
120
214
|
|
|
121
|
-
|
|
215
|
+
See [c-hr backend](https://github.com/nguyendinhphongdx/c-hr) — `apps/backend/src/apps/attendance/attendance-device/` is a NestJS module that implements the contract end-to-end, including JWT version revocation and orphan-event reconcile.
|
|
122
216
|
|
|
123
|
-
|
|
217
|
+
## Self-Hosting
|
|
124
218
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `DATA_DIR` env (always wins)
|
|
134
|
-
- `./data/` next to cwd, if it exists (Docker bind mount, dev workflow)
|
|
135
|
-
- Globally installed: OS-standard user data dir
|
|
136
|
-
- Linux: `~/.local/share/zk-bridge`
|
|
137
|
-
- macOS: `~/Library/Application Support/zk-bridge`
|
|
138
|
-
- Windows: `%APPDATA%\zk-bridge`
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/nguyendinhphongdx/zkteco-bridge.git
|
|
221
|
+
cd zkteco-bridge
|
|
222
|
+
pnpm install
|
|
223
|
+
pnpm build
|
|
224
|
+
pnpm start
|
|
225
|
+
```
|
|
139
226
|
|
|
140
|
-
|
|
227
|
+
Or install your local checkout as the global CLI:
|
|
141
228
|
|
|
142
|
-
```
|
|
143
|
-
|
|
229
|
+
```bash
|
|
230
|
+
npm install -g .
|
|
231
|
+
zk-bridge start
|
|
144
232
|
```
|
|
145
233
|
|
|
146
|
-
|
|
234
|
+
### Auto-start (production)
|
|
147
235
|
|
|
148
|
-
Three options — pick one:
|
|
236
|
+
Three options — pick **one**, otherwise two processes will fight for port 7000:
|
|
149
237
|
|
|
150
|
-
- **Built-in toggle** (recommended)
|
|
151
|
-
- **PM2
|
|
238
|
+
- **Built-in toggle** (recommended) — *System → Auto-start on boot* registers a systemd / Windows Task / launchd entry pointing at the global CLI binary.
|
|
239
|
+
- **PM2** —
|
|
152
240
|
|
|
153
241
|
```bash
|
|
154
242
|
pm2 start "$(which zk-bridge)" --name zk-bridge -- start
|
|
155
243
|
pm2 startup && pm2 save
|
|
156
244
|
```
|
|
157
245
|
|
|
158
|
-
- **Docker** — see [`docker-compose.yml`](docker-compose.yml). Bind-mount `./data:/app/data` to persist
|
|
246
|
+
- **Docker** — see [`docker-compose.yml`](docker-compose.yml). Bind-mount `./data:/app/data` to persist SQLite across rebuilds.
|
|
159
247
|
|
|
160
|
-
|
|
248
|
+
## Environment Variables
|
|
161
249
|
|
|
162
|
-
|
|
250
|
+
| Variable | Default | Description |
|
|
251
|
+
|----------|---------|-------------|
|
|
252
|
+
| `PORT` | `7000` | Admin UI HTTP port |
|
|
253
|
+
| `BIND_HOST` | `127.0.0.1` | Listen address. Set `0.0.0.0` to allow LAN access |
|
|
254
|
+
| `DATA_DIR` | OS-standard user data dir | Override SQLite + admin login location |
|
|
255
|
+
| `PUSH_URL` | _(none)_ | First-run seed only — bridge stores it in SQLite then ignores env |
|
|
256
|
+
| `PING_URL` | _(none)_ | First-run seed only |
|
|
257
|
+
| `POLL_INTERVAL_MIN` | `5` | First-run seed only — minutes between cycles |
|
|
163
258
|
|
|
164
|
-
|
|
165
|
-
zk-bridge upgrade
|
|
166
|
-
sudo systemctl restart zk-bridge # or pm2 restart zk-bridge / docker compose pull
|
|
167
|
-
```
|
|
259
|
+
`DATA_DIR` resolution order on every start:
|
|
168
260
|
|
|
169
|
-
|
|
261
|
+
1. `DATA_DIR` env var (always wins)
|
|
262
|
+
2. `./data/` next to cwd, if it exists (Docker bind mount, dev workflow)
|
|
263
|
+
3. **Globally installed:** OS-standard user data dir
|
|
264
|
+
4. `./data/` next to cwd (dev fallback)
|
|
170
265
|
|
|
171
266
|
## Troubleshooting
|
|
172
267
|
|
|
173
268
|
| Symptom | Likely cause / fix |
|
|
174
|
-
|
|
175
|
-
| `ETIMEDOUT <ip>:<port>` on Connect | Bridge can't reach the backend Push URL
|
|
176
|
-
| `HTTP 401 Invalid token` | JWT was regenerated on the backend or device deleted. Re-paste the token. |
|
|
269
|
+
|---------|-------------------|
|
|
270
|
+
| `ETIMEDOUT <ip>:<port>` on Connect | Bridge can't reach the backend Push URL. `curl <url>` from the bridge host should work. |
|
|
271
|
+
| `HTTP 401 Invalid token` | JWT was regenerated on the backend, or the device row was deleted. Re-paste the token. |
|
|
177
272
|
| `Socket closed unexpectedly` | The ZK device only allows 1 active connection — another tool / cycle is holding it. Wait for the next cycle. |
|
|
178
|
-
| `port open but ZK probe fail` in scan | Same — device is busy. Try Add → Connect after a minute. |
|
|
273
|
+
| `port open but ZK probe fail` in scan | Same — device is busy. Try **Add** → **Connect** after a minute. |
|
|
179
274
|
| Dashboard shows "never run" | Push URL not set, or no devices configured. Check *API settings* + *Devices*. |
|
|
180
|
-
| Events arrive late | Lower the poll interval in *API settings* (min 1 min). |
|
|
275
|
+
| Events arrive late | Lower the *poll interval* in *API settings* (min 1 min). |
|
|
181
276
|
|
|
182
|
-
Every console line is prefixed with an ISO timestamp:
|
|
277
|
+
Every console line is prefixed with an ISO timestamp so logs from `pm2 logs` / `journalctl -u zk-bridge` / `docker compose logs` line up:
|
|
183
278
|
|
|
184
279
|
```text
|
|
185
280
|
[2026-05-07T08:23:50.747Z] [poll] "Front gate" pulled 2915 from ZK in 5291ms
|
|
186
281
|
```
|
|
187
282
|
|
|
188
|
-
##
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
git clone https://github.com/nguyendinhphongdx/c-hr.git
|
|
192
|
-
cd c-hr/services/zk-bridge
|
|
193
|
-
pnpm install
|
|
194
|
-
pnpm build
|
|
195
|
-
pnpm start
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
The package is standalone — it has its own `pnpm-workspace.yaml` and `pnpm-lock.yaml`, separate from any parent monorepo. Install your local copy as the global CLI:
|
|
283
|
+
## Tech Stack
|
|
199
284
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
285
|
+
| Layer | Technology |
|
|
286
|
+
|-------|-----------|
|
|
287
|
+
| Runtime | [Node.js 20+](https://nodejs.org/) |
|
|
288
|
+
| Local DB | [SQLite](https://www.sqlite.org/) via [sqlite3](https://github.com/TryGhost/node-sqlite3) + [Sequelize](https://sequelize.org/) |
|
|
289
|
+
| HTTP server | [Hono](https://hono.dev/) + [@hono/node-server](https://github.com/honojs/node-server) |
|
|
290
|
+
| ZK protocol | Custom client (`src/zklib`) — TCP raw, chunked streaming for large logs |
|
|
291
|
+
| Auth | [bcryptjs](https://github.com/dcodeIO/bcrypt.js) (admin) + JWT (per-device) |
|
|
292
|
+
| HTTP client | [axios](https://axios-http.com/) |
|
|
293
|
+
| Scheduler | [node-cron](https://github.com/node-cron/node-cron) |
|
|
294
|
+
| Build | [TypeScript](https://www.typescriptlang.org/) |
|
|
204
295
|
|
|
205
296
|
## License
|
|
206
297
|
|
|
207
|
-
MIT
|
|
298
|
+
MIT © [HanoiLab](mailto:opencode@hanoilab.vn)
|
|
299
|
+
|
|
300
|
+
---
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.startDaemon = startDaemon;
|
|
37
|
+
exports.stopDaemon = stopDaemon;
|
|
38
|
+
exports.statusDaemon = statusDaemon;
|
|
39
|
+
exports.tailLog = tailLog;
|
|
40
|
+
exports.formatUptime = formatUptime;
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const env_1 = require("../config/env");
|
|
45
|
+
const PID_FILE = 'zk-bridge.pid';
|
|
46
|
+
const LOG_FILE = 'zk-bridge.log';
|
|
47
|
+
function paths() {
|
|
48
|
+
const { dataDir } = (0, env_1.loadBootEnv)();
|
|
49
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
50
|
+
return {
|
|
51
|
+
dataDir,
|
|
52
|
+
pid: path.join(dataDir, PID_FILE),
|
|
53
|
+
log: path.join(dataDir, LOG_FILE),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function isAlive(pid) {
|
|
57
|
+
try {
|
|
58
|
+
process.kill(pid, 0);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readPid() {
|
|
66
|
+
const p = paths();
|
|
67
|
+
if (!fs.existsSync(p.pid))
|
|
68
|
+
return null;
|
|
69
|
+
const n = parseInt(fs.readFileSync(p.pid, 'utf8').trim(), 10);
|
|
70
|
+
if (!Number.isFinite(n) || !isAlive(n)) {
|
|
71
|
+
try {
|
|
72
|
+
fs.unlinkSync(p.pid);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// best-effort
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return n;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Spawn the bridge as a detached background child writing to a log file.
|
|
83
|
+
* Parent exits immediately — Ctrl+C in the launching shell does NOT kill
|
|
84
|
+
* the daemon (it's in a separate process group).
|
|
85
|
+
*/
|
|
86
|
+
function startDaemon() {
|
|
87
|
+
const existing = readPid();
|
|
88
|
+
if (existing) {
|
|
89
|
+
throw new Error(`Already running (PID ${existing}). Use 'zk-bridge stop' or 'zk-bridge restart' first.`);
|
|
90
|
+
}
|
|
91
|
+
const p = paths();
|
|
92
|
+
const fd = fs.openSync(p.log, 'a');
|
|
93
|
+
const node = process.execPath;
|
|
94
|
+
// dist/index.js — same entry `zk-bridge run` invokes, but spawned detached.
|
|
95
|
+
const entry = path.resolve(__dirname, '..', 'index.js');
|
|
96
|
+
const child = (0, node_child_process_1.spawn)(node, [entry], {
|
|
97
|
+
detached: true,
|
|
98
|
+
stdio: ['ignore', fd, fd],
|
|
99
|
+
env: process.env,
|
|
100
|
+
});
|
|
101
|
+
child.unref();
|
|
102
|
+
if (!child.pid)
|
|
103
|
+
throw new Error('Failed to spawn child process.');
|
|
104
|
+
fs.writeFileSync(p.pid, String(child.pid));
|
|
105
|
+
fs.closeSync(fd);
|
|
106
|
+
return { pid: child.pid, logPath: p.log, dataDir: p.dataDir };
|
|
107
|
+
}
|
|
108
|
+
function stopDaemon() {
|
|
109
|
+
const pid = readPid();
|
|
110
|
+
if (!pid)
|
|
111
|
+
return null;
|
|
112
|
+
try {
|
|
113
|
+
process.kill(pid, 'SIGTERM');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// already dead — fall through to PID cleanup
|
|
117
|
+
}
|
|
118
|
+
// Wait a moment for graceful shutdown, then verify.
|
|
119
|
+
const deadline = Date.now() + 5_000;
|
|
120
|
+
while (Date.now() < deadline && isAlive(pid)) {
|
|
121
|
+
// busy-wait short; the kill is async
|
|
122
|
+
sleepSync(100);
|
|
123
|
+
}
|
|
124
|
+
if (isAlive(pid)) {
|
|
125
|
+
try {
|
|
126
|
+
process.kill(pid, 'SIGKILL');
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// ignore
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const p = paths();
|
|
133
|
+
try {
|
|
134
|
+
fs.unlinkSync(p.pid);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore
|
|
138
|
+
}
|
|
139
|
+
return { pid };
|
|
140
|
+
}
|
|
141
|
+
function statusDaemon() {
|
|
142
|
+
const p = paths();
|
|
143
|
+
const pid = readPid();
|
|
144
|
+
if (!pid) {
|
|
145
|
+
return { running: false, logPath: p.log, dataDir: p.dataDir };
|
|
146
|
+
}
|
|
147
|
+
let uptimeMs;
|
|
148
|
+
try {
|
|
149
|
+
const stat = fs.statSync(p.pid);
|
|
150
|
+
uptimeMs = Date.now() - stat.mtimeMs;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// ignore
|
|
154
|
+
}
|
|
155
|
+
return { running: true, pid, logPath: p.log, dataDir: p.dataDir, uptimeMs };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Print last N lines of the log file, optionally follow new appends.
|
|
159
|
+
* Cross-platform tail — doesn't shell out.
|
|
160
|
+
*/
|
|
161
|
+
function tailLog(follow, lines) {
|
|
162
|
+
const p = paths();
|
|
163
|
+
if (!fs.existsSync(p.log)) {
|
|
164
|
+
console.log('(no log yet — daemon has not started.)');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const initial = readLastLines(p.log, lines);
|
|
168
|
+
process.stdout.write(initial);
|
|
169
|
+
if (!follow)
|
|
170
|
+
return;
|
|
171
|
+
let pos = fs.statSync(p.log).size;
|
|
172
|
+
const watcher = fs.watch(p.log, () => {
|
|
173
|
+
let stat;
|
|
174
|
+
try {
|
|
175
|
+
stat = fs.statSync(p.log);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (stat.size > pos) {
|
|
181
|
+
const fd = fs.openSync(p.log, 'r');
|
|
182
|
+
const buf = Buffer.alloc(stat.size - pos);
|
|
183
|
+
fs.readSync(fd, buf, 0, buf.length, pos);
|
|
184
|
+
fs.closeSync(fd);
|
|
185
|
+
process.stdout.write(buf.toString('utf8'));
|
|
186
|
+
pos = stat.size;
|
|
187
|
+
}
|
|
188
|
+
else if (stat.size < pos) {
|
|
189
|
+
pos = 0;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
process.on('SIGINT', () => {
|
|
193
|
+
watcher.close();
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
// Keep the event loop alive.
|
|
197
|
+
setInterval(() => undefined, 1 << 30);
|
|
198
|
+
}
|
|
199
|
+
function readLastLines(file, lines) {
|
|
200
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
201
|
+
const arr = content.split('\n');
|
|
202
|
+
// Trim trailing empty line caused by final newline.
|
|
203
|
+
if (arr[arr.length - 1] === '')
|
|
204
|
+
arr.pop();
|
|
205
|
+
const slice = arr.slice(-lines);
|
|
206
|
+
return slice.join('\n') + (slice.length > 0 ? '\n' : '');
|
|
207
|
+
}
|
|
208
|
+
function formatUptime(ms) {
|
|
209
|
+
const s = Math.floor(ms / 1000);
|
|
210
|
+
if (s < 60)
|
|
211
|
+
return `${s}s`;
|
|
212
|
+
const m = Math.floor(s / 60);
|
|
213
|
+
if (m < 60)
|
|
214
|
+
return `${m}m ${s % 60}s`;
|
|
215
|
+
const h = Math.floor(m / 60);
|
|
216
|
+
if (h < 24)
|
|
217
|
+
return `${h}h ${m % 60}m`;
|
|
218
|
+
const d = Math.floor(h / 24);
|
|
219
|
+
return `${d}d ${h % 24}h`;
|
|
220
|
+
}
|
|
221
|
+
function sleepSync(ms) {
|
|
222
|
+
// Atomics.wait on a SharedArrayBuffer would be precise but heavy; this
|
|
223
|
+
// ~100ms granularity is fine for stop's grace-period polling.
|
|
224
|
+
const end = Date.now() + ms;
|
|
225
|
+
while (Date.now() < end) {
|
|
226
|
+
// spin
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/zk-bridge.js
CHANGED
|
@@ -44,13 +44,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
const node_child_process_1 = require("node:child_process");
|
|
46
46
|
const path = __importStar(require("node:path"));
|
|
47
|
+
const daemon_1 = require("./daemon");
|
|
47
48
|
const command = process.argv[2];
|
|
48
49
|
function help() {
|
|
49
50
|
// eslint-disable-next-line no-console
|
|
50
51
|
console.log(`Usage: zk-bridge <command> [options]
|
|
51
52
|
|
|
52
53
|
Commands:
|
|
53
|
-
start Start bridge
|
|
54
|
+
start Start bridge in the background (writes PID + log)
|
|
55
|
+
stop Stop the running daemon
|
|
56
|
+
restart Stop + start
|
|
57
|
+
status Show daemon state (PID, uptime, log path)
|
|
58
|
+
logs [-f] [-n N] Print last N log lines (default 50). -f to follow.
|
|
59
|
+
run Run in the foreground (debug / systemd / Docker)
|
|
54
60
|
poll-once Run a single poll cycle then exit (no UI server)
|
|
55
61
|
reset-user Reset the local admin user (forgot password recovery)
|
|
56
62
|
recent-events Print recent events from a device
|
|
@@ -60,18 +66,18 @@ Commands:
|
|
|
60
66
|
version, --version, -v Show package version
|
|
61
67
|
|
|
62
68
|
Environment:
|
|
63
|
-
DATA_DIR
|
|
69
|
+
DATA_DIR Where SQLite + admin credentials + log live
|
|
70
|
+
(default: OS user data dir)
|
|
64
71
|
PORT=7000 UI HTTP port
|
|
65
72
|
BIND_HOST=127.0.0.1 Bind address. Set 0.0.0.0 to allow LAN access
|
|
66
73
|
|
|
67
74
|
Examples:
|
|
68
|
-
zk-bridge start
|
|
69
|
-
|
|
70
|
-
zk-bridge
|
|
71
|
-
zk-bridge
|
|
72
|
-
zk-bridge
|
|
73
|
-
zk-bridge
|
|
74
|
-
zk-bridge recent-events --device "Cửa chính" -n 30
|
|
75
|
+
zk-bridge start # daemonize
|
|
76
|
+
zk-bridge status # is it running?
|
|
77
|
+
zk-bridge logs -f # follow logs
|
|
78
|
+
zk-bridge stop # stop the daemon
|
|
79
|
+
PORT=8080 zk-bridge run # foreground on a custom port
|
|
80
|
+
zk-bridge recent-events --device "Front gate" -n 30
|
|
75
81
|
`);
|
|
76
82
|
}
|
|
77
83
|
function version() {
|
|
@@ -121,19 +127,87 @@ async function upgrade(tag = 'latest') {
|
|
|
121
127
|
});
|
|
122
128
|
// eslint-disable-next-line no-console
|
|
123
129
|
console.log(`[zk-bridge] ✓ upgraded ${target}.\n` +
|
|
124
|
-
'Restart the
|
|
125
|
-
'
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
'Restart the daemon to pick up the new code:\n' +
|
|
131
|
+
' zk-bridge restart');
|
|
132
|
+
}
|
|
133
|
+
function parseLogsArgs() {
|
|
134
|
+
const argv = process.argv.slice(3);
|
|
135
|
+
const follow = argv.includes('-f') || argv.includes('--follow');
|
|
136
|
+
const nIdx = argv.findIndex((a) => a === '-n' || a === '--lines');
|
|
137
|
+
const lines = nIdx >= 0 ? Number(argv[nIdx + 1]) : 50;
|
|
138
|
+
return { follow, lines: Number.isFinite(lines) && lines > 0 ? lines : 50 };
|
|
139
|
+
}
|
|
140
|
+
function cmdStart() {
|
|
141
|
+
try {
|
|
142
|
+
const r = (0, daemon_1.startDaemon)();
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.log(`[zk-bridge] started (PID ${r.pid})\n` +
|
|
145
|
+
` Logs: ${r.logPath}\n` +
|
|
146
|
+
` Stop: zk-bridge stop\n` +
|
|
147
|
+
` Tail: zk-bridge logs -f`);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.error(`[zk-bridge] ${err instanceof Error ? err.message : err}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function cmdStop() {
|
|
156
|
+
const r = (0, daemon_1.stopDaemon)();
|
|
157
|
+
if (!r) {
|
|
158
|
+
// eslint-disable-next-line no-console
|
|
159
|
+
console.log('[zk-bridge] not running.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// eslint-disable-next-line no-console
|
|
163
|
+
console.log(`[zk-bridge] stopped (PID ${r.pid}).`);
|
|
164
|
+
}
|
|
165
|
+
async function cmdRestart() {
|
|
166
|
+
cmdStop();
|
|
167
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
168
|
+
cmdStart();
|
|
169
|
+
}
|
|
170
|
+
function cmdStatus() {
|
|
171
|
+
const s = (0, daemon_1.statusDaemon)();
|
|
172
|
+
if (!s.running) {
|
|
173
|
+
// eslint-disable-next-line no-console
|
|
174
|
+
console.log(`[zk-bridge] not running.\n` +
|
|
175
|
+
` Data dir: ${s.dataDir}\n` +
|
|
176
|
+
` Logs: ${s.logPath} (last run, if any)`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const up = s.uptimeMs !== undefined ? (0, daemon_1.formatUptime)(s.uptimeMs) : 'unknown';
|
|
180
|
+
// eslint-disable-next-line no-console
|
|
181
|
+
console.log(`[zk-bridge] running.\n` +
|
|
182
|
+
` PID: ${s.pid}\n` +
|
|
183
|
+
` Uptime: ${up}\n` +
|
|
184
|
+
` Data dir: ${s.dataDir}\n` +
|
|
185
|
+
` Logs: ${s.logPath}`);
|
|
131
186
|
}
|
|
132
187
|
async function main() {
|
|
133
188
|
switch (command) {
|
|
189
|
+
// ── daemon control ───────────────────────────────────────────────────
|
|
134
190
|
case undefined:
|
|
135
191
|
case 'start':
|
|
136
|
-
|
|
192
|
+
cmdStart();
|
|
193
|
+
return;
|
|
194
|
+
case 'stop':
|
|
195
|
+
cmdStop();
|
|
196
|
+
return;
|
|
197
|
+
case 'restart':
|
|
198
|
+
await cmdRestart();
|
|
199
|
+
return;
|
|
200
|
+
case 'status':
|
|
201
|
+
cmdStatus();
|
|
202
|
+
return;
|
|
203
|
+
case 'logs': {
|
|
204
|
+
const { follow, lines } = parseLogsArgs();
|
|
205
|
+
(0, daemon_1.tailLog)(follow, lines);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// ── foreground / one-shot ────────────────────────────────────────────
|
|
209
|
+
case 'run':
|
|
210
|
+
// Foreground mode — what daemons / Docker / systemd should call.
|
|
137
211
|
process.argv.splice(2, 1);
|
|
138
212
|
await Promise.resolve().then(() => __importStar(require('../index')));
|
|
139
213
|
return;
|
|
@@ -149,11 +223,12 @@ async function main() {
|
|
|
149
223
|
process.argv.splice(2, 1);
|
|
150
224
|
await Promise.resolve().then(() => __importStar(require('./recent-events')));
|
|
151
225
|
return;
|
|
226
|
+
// ── self-update ──────────────────────────────────────────────────────
|
|
152
227
|
case 'upgrade':
|
|
153
228
|
case 'update':
|
|
154
|
-
// Optional next arg = npm dist-tag (e.g. `next`). Default `latest`.
|
|
155
229
|
await upgrade(process.argv[3] ?? 'latest');
|
|
156
230
|
return;
|
|
231
|
+
// ── meta ─────────────────────────────────────────────────────────────
|
|
157
232
|
case 'help':
|
|
158
233
|
case '--help':
|
|
159
234
|
case '-h':
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zk-bridge.js","sourceRoot":"","sources":["../../src/cli/zk-bridge.ts"],"names":[],"mappings":";;AAEA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,2DAA2C;AAC3C,gDAAkC;AAElC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,SAAS,IAAI;IACX,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC
|
|
1
|
+
{"version":3,"file":"zk-bridge.js","sourceRoot":"","sources":["../../src/cli/zk-bridge.ts"],"names":[],"mappings":";;AAEA;;;;;;;GAOG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,2DAA2C;AAC3C,gDAAkC;AAElC,qCAMkB;AAElB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAEhC,SAAS,IAAI;IACX,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Bb,CAAC,CAAC;AACH,CAAC;AAED,SAAS,OAAO;IACd,gDAAgD;IAChD,8DAA8D;IAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC1C,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,OAAO,CAAC,GAAG,GAAG,QAAQ;IACnC,MAAM,eAAe,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,GAAG,eAAe,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACjF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,sCAAsC;QACtC,OAAO,CAAC,KAAK,CACX,mEAAmE;YACjE,yFAAyF,CAC5F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,8DAA8D;IAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;IAChC,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,MAAM,CAAC,CAAC;IAEtD,MAAM,KAAK,GAAG,IAAA,0BAAK,EAAC,KAAK,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE;QACpD,KAAK,EAAE,SAAS;QAChB,4DAA4D;QAC5D,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;KACpC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,sCAAsC;IACtC,OAAO,CAAC,GAAG,CACT,0BAA0B,MAAM,KAAK;QACnC,+CAA+C;QAC/C,qBAAqB,CACxB,CAAC;AACJ,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC7E,CAAC;AAED,SAAS,QAAQ;IACf,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAA,oBAAW,GAAE,CAAC;QACxB,sCAAsC;QACtC,OAAO,CAAC,GAAG,CACT,4BAA4B,CAAC,CAAC,GAAG,KAAK;YACpC,YAAY,CAAC,CAAC,OAAO,IAAI;YACzB,2BAA2B;YAC3B,4BAA4B,CAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sCAAsC;QACtC,OAAO,CAAC,KAAK,CAAC,eAAe,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,OAAO;IACd,MAAM,CAAC,GAAG,IAAA,mBAAU,GAAE,CAAC;IACvB,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,sCAAsC;QACtC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;QACxC,OAAO;IACT,CAAC;IACD,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,OAAO,EAAE,CAAC;IACV,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7C,QAAQ,EAAE,CAAC;AACb,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,CAAC,GAAG,IAAA,qBAAY,GAAE,CAAC;IACzB,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,sCAAsC;QACtC,OAAO,CAAC,GAAG,CACT,4BAA4B;YAC1B,eAAe,CAAC,CAAC,OAAO,IAAI;YAC5B,eAAe,CAAC,CAAC,OAAO,qBAAqB,CAChD,CAAC;QACF,OAAO;IACT,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAA,qBAAY,EAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3E,sCAAsC;IACtC,OAAO,CAAC,GAAG,CACT,wBAAwB;QACtB,eAAe,CAAC,CAAC,GAAG,IAAI;QACxB,eAAe,EAAE,IAAI;QACrB,eAAe,CAAC,CAAC,OAAO,IAAI;QAC5B,eAAe,CAAC,CAAC,OAAO,EAAE,CAC7B,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,QAAQ,OAAO,EAAE,CAAC;QAChB,wEAAwE;QACxE,KAAK,SAAS,CAAC;QACf,KAAK,OAAO;YACV,QAAQ,EAAE,CAAC;YACX,OAAO;QAET,KAAK,MAAM;YACT,OAAO,EAAE,CAAC;YACV,OAAO;QAET,KAAK,SAAS;YACZ,MAAM,UAAU,EAAE,CAAC;YACnB,OAAO;QAET,KAAK,QAAQ;YACX,SAAS,EAAE,CAAC;YACZ,OAAO;QAET,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,EAAE,CAAC;YAC1C,IAAA,gBAAO,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QAED,wEAAwE;QACxE,KAAK,KAAK;YACR,iEAAiE;YACjE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,wDAAa,UAAU,GAAC,CAAC;YACzB,OAAO;QAET,KAAK,WAAW;YACd,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YACpC,wDAAa,UAAU,GAAC,CAAC;YACzB,OAAO;QAET,KAAK,YAAY;YACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,wDAAa,cAAc,GAAC,CAAC;YAC7B,OAAO;QAET,KAAK,eAAe;YAClB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1B,wDAAa,iBAAiB,GAAC,CAAC;YAChC,OAAO;QAET,wEAAwE;QACxE,KAAK,SAAS,CAAC;QACf,KAAK,QAAQ;YACX,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC;YAC3C,OAAO;QAET,wEAAwE;QACxE,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,IAAI,EAAE,CAAC;YACP,OAAO;QAET,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,EAAE,CAAC;YACV,OAAO;QAET;YACE,sCAAsC;YACtC,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,IAAI,CAAC,CAAC;YAC/C,IAAI,EAAE,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC3F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|