node-red-contrib-tdn-eventsub 0.2.1 → 0.2.3

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 CHANGED
@@ -2,20 +2,21 @@
2
2
 
3
3
  Node-RED nodes for an access-control **REST Server API**.
4
4
 
5
- | Node | What it does | Mode |
5
+ | Node | Endpoint (under the base path) | Mode |
6
6
  |---|---|---|
7
- | `get access groups` | Downloads all access groups, stores to global context | request/reply |
8
- | `get cardholders` | Downloads all cardholders, stores to global context | request/reply |
9
- | `get items` | Downloads all items (optionally filtered by type/division), stores to global context | request/reply |
10
- | `item status` | Subscribes to status of one or more item (source) ids and keeps waiting for updates indefinitely | continuous |
11
-
12
- All share one **REST server** config node: URL/host, port (default 8904), API key (optional: can come from a credentials object instead), how to send it (Authorization or API key header), optional server certificate fingerprint, optional client certificate.
7
+ | `get cardholders` | `/cardholders`, `/cardholders/{id}` | request/reply |
8
+ | `get access groups` | `/access_groups`, `/access_groups/{id}/cardholders` | request/reply |
9
+ | `get items` | `/items`, `/items/{id}` | request/reply |
10
+ | `get personal data fields` | `/personal_data_fields`, `/personal_data_fields/{id}` | request/reply |
11
+ | `get event groups` | `/events/groups`, `/events/groups/{id}` (event types) | request/reply |
12
+ | `item status` | `POST /items/updates`, then long-poll | continuous |
13
+ | `event updates` | `/events/updates`, long-poll | continuous |
13
14
 
14
15
  Every node has a trigger input and **three outputs**:
15
16
 
16
- 1. **Response** – full server response in `msg`
17
- 2. **Status** – state changes (`msg.payload.state`, `text`, `time`, node id/name/type) for wiring to a shared log/dashboard
18
- 3. **Error** – `msg.payload.message`, `statusCode`, `body`, `url`, `code`
17
+ 1. **Response**: the full server response in `msg`.
18
+ 2. **Status**: one message per **successful** server response, for wiring to a shared log or dashboard.
19
+ 3. **Error**: **every** non-success, whether an HTTP error, a network failure, a certificate problem or bad input. Errors never appear on the status output.
19
20
 
20
21
  No runtime dependencies. Node 16+, Node-RED 3+.
21
22
 
@@ -23,40 +24,49 @@ No runtime dependencies. Node 16+, Node-RED 3+.
23
24
 
24
25
  ```sh
25
26
  cd ~/.node-red
26
- npm install /path/to/node-red-contrib-tdn-eventsub # or the .tgz
27
+ npm install node-red-contrib-tdn-eventsub@latest
27
28
  ```
28
29
 
29
30
  Restart Node-RED. Nodes appear under **TDN REST**. Import the example via *Import → Examples → node-red-contrib-tdn-eventsub*.
30
31
 
31
- ## Server config
32
+ ## Server address
32
33
 
33
- | Field | Notes |
34
- |---|---|
35
- | URL / host | `rest-server`, `192.168.1.10` or `https://rest-server`. `/api` path assumed. |
36
- | Port | Default `8904`. |
37
- | API key | Optional. Stored as a Node-RED credential; treated as opaque. When set it is always used. When blank, each node reads a [credentials object](#credentials-object). |
38
- | Send key as | **Authorization header** (default) or **API key header**. |
39
- | Auth scheme | Authorization header only. Blank (default): `Authorization: Basic base64(":" + key)`, accepted by server v9.0+. For older servers enter the API-key scheme token from your server's REST API docs; the key is then sent as `Authorization: <scheme> <key>`. |
40
- | Header name | API key header only. Sent as `<header name>: <key>`; default `X-API-Key`. |
41
- | Cert fingerprint | Optional. SHA-256 (64 hex) or SHA-1 (40 hex) of the **server** certificate, colons optional. When set, the server's self-signed cert is accepted only if it matches; the check runs before any request is written, so the key is never sent to an impostor. A mismatch error prints the actual fingerprint. |
42
- | Verify server certificate | Normal CA check when no fingerprint is set. |
43
- | Force links onto this host:port | The server builds `href`s from its own machine name. With this on, followed links keep path + query but use your configured host/port. With it off, a link that would downgrade `https` to `http` is refused. |
44
- | Client cert / key / PFX | Only if the REST Client item on the server has a thumbprint (pinned client certificate). |
34
+ Every request goes to `<protocol>://<host>:<port><basePath><endpoint>`, for example:
45
35
 
46
- ## Credentials object
36
+ ```
37
+ http://localhost:80/api/cardholders
38
+ https://192.168.0.20:8904/api/access_groups
39
+ https://testserver:8911/api/items
40
+ ```
47
41
 
48
- If the server config has **no API key stored** in Node-RED, every node takes the key from its **Credentials** property: a typedInput pointing at `msg`, `flow`, `global` or `env`, default `msg.credentials`. The value is either a bare key string or:
42
+ The address is read from **global context** at every request, key `rest_server` (configurable in the server config node):
49
43
 
50
44
  ```js
51
- {
52
- apiKey: "XXXX-...", // required
53
- authType: "authorization" | "header", // default: server config
54
- authScheme: "", // authorization only; blank = Basic
55
- headerName: "X-API-Key" // header only
45
+ global.rest_server = {
46
+ host: "192.168.0.20", // hostname or IP (a full URL like "https://testserver:8911/api" also works)
47
+ port: 8904,
48
+ basePath: "/api",
49
+ protocol: "https" // or "http"
56
50
  }
57
51
  ```
58
52
 
59
- Missing fields fall back to the server config. A stored key always takes precedence, and the credentials object is then ignored. A msg-borne credentials property is deleted from the msg before it is passed on, so the key doesn't reach debug nodes or downstream flows. The item status node reads the credentials once per trigger and keeps them for that subscription.
53
+ Any field that's missing falls back to the server config node. A plain string value is treated as the host or URL. You can change the value with a change node to switch servers without redeploying. The protocol is decided in this order: `protocol`, then the scheme in `host`, then the config node's *Plain HTTP* checkbox, then https.
54
+
55
+ ## Server config
56
+
57
+ | Field | Notes |
58
+ |---|---|
59
+ | Global key | Default `rest_server`. Leave blank to ignore global context. |
60
+ | Host / IP, Port, Base path | Fallbacks when global context doesn't set them. Defaults: port `8904`, base path `/api`. |
61
+ | API key | Optional. Stored as a Node-RED credential and treated as opaque. When it's set, it is always used. When it's blank, each node reads a [credentials object](#credentials-object). |
62
+ | Send key as | **Authorization header** (default) or **API key header**. |
63
+ | Auth scheme | Authorization header only. Blank (the default) sends `Authorization: Basic base64(":" + key)`, which server v9.0+ accepts. For older servers, enter the API-key scheme token from your server's REST API docs; the key is then sent as `Authorization: <scheme> <key>`. |
64
+ | Header name | API key header only. Sent as `<header name>: <key>`; default `X-API-Key`. |
65
+ | Plain HTTP (insecure) | Uses unencrypted `http://` when nothing else sets the protocol. Certificate settings are ignored and the key travels in clear text, so use it for bench testing only. |
66
+ | Cert fingerprint | Optional. The SHA-256 (64 hex) or SHA-1 (40 hex) fingerprint of the **server** certificate; colons are optional. When set, a self-signed certificate is accepted only if it matches. The check runs before any request is written, so the key is never sent to an impostor. |
67
+ | Verify server certificate | Normal CA check when no fingerprint is set. |
68
+ | Force links onto this host:port | Paging and subscription links (`next.href`) are built from the server's own machine name. With this option on, they keep their path and query but use your host and port. With it off, a link that would downgrade `https` to `http` is refused. |
69
+ | Client cert / key / PFX | Only needed if the REST Client item on the server has a thumbprint (pinned client certificate). |
60
70
 
61
71
  Get the server fingerprint with:
62
72
 
@@ -64,63 +74,98 @@ Get the server fingerprint with:
64
74
  openssl s_client -connect rest-server:8904 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
65
75
  ```
66
76
 
67
- ## List nodes (access groups / cardholders / items)
68
-
69
- Discovers the URL from `GET /api` (`features.accessGroups.accessGroups`, `features.cardholders.cardholders`, `features.items.items`), requests with `sort=id&top=1000`, and follows `next.href` until done.
77
+ ## Credentials object
70
78
 
71
- **Output 1**
79
+ If the server config has **no API key stored** in Node-RED, every node takes the key from its **Credentials** property. This is a typedInput pointing at `msg`, `flow`, `global` or `env`; the default is `msg.credentials`. The value is either a bare key string or:
72
80
 
73
81
  ```js
74
- msg.payload = { results: [...all pages merged], count, pageCount }
75
- msg.pages = [ raw page 1, raw page 2, ... ] // unmodified server bodies (toggle)
76
- msg.request = { href, params }
77
- msg.contextKey
82
+ { apiKey: "XXXX-...", authType: "authorization" | "header", authScheme: "", headerName: "X-API-Key" }
78
83
  ```
79
84
 
80
- **Global context**: `global.<key>` = results array, `global.<key>_updated` = ISO time. Defaults: `rest_accessGroups`, `rest_cardholders`, `rest_items`. Any context store can be picked.
85
+ - Missing fields fall back to the server config.
86
+ - A stored key always takes precedence.
87
+ - A credentials property on the msg is deleted before the msg is passed on.
88
+ - Subscriptions read the credentials once per trigger.
81
89
 
82
- **Per-message overrides**: `msg.query` (object or `"a=1&b=2"`), `msg.fields`, `msg.top`, `msg.maxPages`, `msg.contextKey`.
90
+ ## List nodes
83
91
 
84
- Useful settings: cardholders `fields=defaults,cards,accessGroups`; items `query=type=11` (use the item types list to find ids). A trigger while a download is running is ignored and reported as `busy` on the status output.
92
+ ### Input
85
93
 
86
- ## Item status node
94
+ | msg | Request |
95
+ |---|---|
96
+ | no id, or an inject timestamp or boolean | **list**: `GET <endpoint>` with `top`, `sort` and `fields`; every page is followed and merged |
97
+ | an id: `msg.id`, a string or number `msg.payload`, or `msg.payload.id` | **details**: `GET <endpoint>/{id}`. For **access groups** this is the group's members instead: `GET /access_groups/{id}/cardholders` |
98
+ | `msg.action` set | takes precedence over the defaults above |
87
99
 
88
- Implements the REST Server status-subscription loop:
100
+ | `msg.action` | Request | Stored in global context |
101
+ |---|---|---|
102
+ | `list` | `GET <endpoint>` (all pages) | `<key>` (array) and `<key>_updated` |
103
+ | `get` | `GET <endpoint>/{id}` | `<key>_byId[id]` |
104
+ | `members` (access groups) | `GET /access_groups/{id}/cardholders` (all pages) | `<key>_members[id]` |
105
+ | `groups` (cardholders) | `GET /cardholders/{id}?fields=accessGroups` | `<key>_groups[id]` |
106
+ | `create` | `POST <endpoint>`, body `msg.payload` | none (`msg.location` = new resource) |
107
+ | `update` | `PATCH <endpoint>/{id}`, body `msg.payload` | none |
108
+ | `delete` | `DELETE <endpoint>/{id}` (only with an explicit action) | removes `<key>_byId[id]` |
89
109
 
90
- 1. `POST features.items.updates.href` with `{"itemIds":[...]}` → current status of every item (output, `phase:"subscribe"`)
91
- 2. `GET next.href` – update-wait (long poll, server returns after ~50 s if nothing changes)
92
- 3. Updates arrive → output (`phase:"update"`), then a new update-wait
93
- 4. Update-wait times out (empty `updates`) → another update-wait (optionally output with `phase:"timeout"`; the status output is not used, only the node badge)
94
- 5. `404` → the server dropped the subscription (>30 s gap or server restart) → re-POST immediately
95
- 6. Any other error → error output, exponential back-off (5 s → 60 s), re-subscribe. Runs until stopped.
110
+ Default keys are `rest_cardholders`, `rest_accessGroups`, `rest_items`, `rest_pdfs` and `rest_eventGroups`. Any context store can be picked. Id responses go into maps, so they never overwrite the full list.
96
111
 
97
- **Input** – one trigger starts it:
112
+ Per-message overrides: `msg.query` (an object or `"a=1&b=2"`), `msg.fields`, `msg.top`, `msg.maxPages` and `msg.contextKey`.
98
113
 
99
- - `msg.payload = "508"` / `"508,526"` / `["508","526"]` / `{itemIds:[...]}` / array of item objects with `id` (so you can wire straight from a filtered `get items` result)
100
- - `msg.itemIds` takes precedence over `payload`
101
- - a new trigger with ids replaces the running subscription
102
- - `msg.payload = "stop"` or `msg.stop = true` stops it
114
+ Requests are **queued** and run one at a time, up to the *Queue limit* (default 100).
103
115
 
104
- **Output 1**
116
+ ### Output 1
105
117
 
106
118
  ```js
107
- msg.payload = { updates: [{ id, status, statusText, statusFlags }], next: { href } } // full server response
108
- msg.updates = msg.payload.updates
109
- msg.phase = "subscribe" | "update" | "timeout"
110
- msg.itemIds, msg.seq, msg.statusCode, msg.url
119
+ msg.payload // list/members: { results: [...all pages], count, pageCount }; details/writes: the server's body
120
+ msg.pages // raw page bodies (toggle)
121
+ msg.action, msg.id, msg.statusCode, msg.location, msg.request, msg.contextKey
111
122
  ```
112
123
 
113
- Optional global key keeps a map `id → latest status` (+ `updated` time).
124
+ ## Item status node
125
+
126
+ 1. `POST /items/updates` with `{"itemIds":[...]}` returns the current status of every item. It is sent on output 1 with `phase:"subscribe"`.
127
+ 2. `GET next.href` is the update-wait: a long poll that the server answers on a change, or after about 50 s.
128
+ 3. When updates arrive they're output with `phase:"update"`, and the next update-wait goes out immediately. Empty update-waits only update the node badge (or use the *Also output empty responses* option).
129
+
130
+ **Input** is `"508"`, `"508,526"`, an array, `{itemIds:[...]}`, or item objects with `id`. `msg.itemIds` and then `msg.id` take precedence. A new trigger replaces the running subscription. `payload:"stop"`, `action:"stop"` or `msg.stop=true` stops it.
131
+
132
+ There's an optional global map of item id → latest status. The delay between waits is capped at 20 s, so each request stays inside the server's 30 s window.
114
133
 
115
- Delay between waits defaults to 1 s and is capped at 20 s so the next GET always lands inside the server's 30 s window.
134
+ This needs REST Server v8.30+ and the **RESTStatus** licence.
116
135
 
117
- Requires REST Server v8.30+ and the **RESTStatus** licence. Before 9.50 the first update-wait repeats the POST results; that is passed through as the server sends it.
136
+ ## Event updates node
118
137
 
119
- ## Status output states
138
+ `GET /events/updates?<filter>` long-polls for new events. Each response's `updates.href` (or `next.href`) is followed immediately, indefinitely.
120
139
 
121
- `requesting`, `done`, `busy` (lists) · `subscribing`, `subscribed`, `update`, `resubscribing`, `retrying`, `stopped` (item status) · `error`.
140
+ - **Filter**: set it in the node (`group=23&type=20001&source=508`) or in `msg.query` at start. Use *get event groups* to find group and type ids.
141
+ - **Output 1**: `msg.payload` (the full response), `msg.events`, `msg.phase`, `msg.seq` and `msg.query`.
142
+ - **Global context** (optional): keeps the last N events.
143
+ - **Start on deploy**: an option that takes credentials from flow, global or env.
144
+ - **Stopping**: same as item status.
122
145
 
123
- The "waiting" and "timed out" states of each long poll are shown on the node badge only, not sent on output 2, so the status log isn't flooded every 50 s.
146
+ ## Failures and back-off
147
+
148
+ | Failure | List nodes | Item status / event updates |
149
+ |---|---|---|
150
+ | **401** wrong API key | error, no retry; the rest of the queue is dropped (one `EDROPPED` error) so a bad key can't trigger the server's failed-login alarms | error, **polling stops** |
151
+ | **Certificate / TLS** (pin mismatch, CA, client cert, not TLS) | error, no retry; queue dropped | error, **polling stops** |
152
+ | **403** licence or privilege | error, no retry | error, **polling stops** |
153
+ | **404** | error, no retry | on the start endpoint: error, **polling stops**. On a followed link (expired subscription): error, then an immediate re-subscribe |
154
+ | other 4xx | error, no retry | error, polling stops |
155
+ | **5xx, 408, 429, timeout, network** | GET/DELETE retried *Retries* times (default 3) at delay × 2ⁿ (5 s, 10 s, 20 s…, capped); each attempt is an error with `attempt` and `retryInMs`. POST/PATCH are never retried, to avoid duplicates | error, back-off 5 s → 60 s, re-subscribe, indefinitely |
156
+
157
+ A stopped subscription restarts on the next trigger or on redeploy. Error payloads carry `category` (`fatal`, `transient` or `expired`) and, for subscriptions, `stopped: true` when polling has ended.
158
+
159
+ ## Status output
160
+
161
+ Status messages are `{ topic: "status", payload: { state, text, node, name, type, time, ... } }`:
162
+
163
+ - **lists**: `response` (one per successful HTTP response, with `statusCode`, `method`, `url` and `page`), then `done`;
164
+ - **item status**: `subscribed` and `update`;
165
+ - **event updates**: `update`;
166
+ - **subscriptions**: `stopped` on a stop input.
167
+
168
+ Empty long-poll returns (about every 50 s) show on the node badge only.
124
169
 
125
170
  ## Test
126
171
 
@@ -128,4 +173,11 @@ The "waiting" and "timed out" states of each long poll are shown on the node bad
128
173
  npm test
129
174
  ```
130
175
 
131
- Spins up a self-signed HTTPS mock REST Server and a minimal Node-RED harness; covers fingerprint pin/mismatch, Basic and custom auth schemes, link-downgrade refusal, paging, href rewrite, global context, the update-wait loop, 404 re-subscribe, stop/replace, and recovery after the server drops.
176
+ This spins up a mock REST Server (self-signed HTTPS, plain HTTP, and a TLS echo endpoint) and a minimal Node-RED harness. It covers:
177
+ - addressing and the global-context override;
178
+ - every list node and action, paging, the queue, back-off, and the hard-stop rules;
179
+ - the credentials object and both auth header modes;
180
+ - certificate pinning;
181
+ - the item-status and events long-poll loops, including expiry, stop/replace and recovery.
182
+
183
+ After changing a list-node definition, regenerate the editor HTML with `node tools/gen-lists-html.js`.