node-red-contrib-tdn-eventsub 0.2.2 → 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,41 +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
- | Plain HTTP (insecure) | Unencrypted `http://` whatever the URL says; certificate settings are ignored and the key travels in clear text. For bench testing against a non-TLS endpoint only. A TLS error like `unexpected message` or `wrong version number` usually means the endpoint needs this; the error text says so. |
43
- | Verify server certificate | Normal CA check when no fingerprint is set. |
44
- | 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. |
45
- | 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:
46
35
 
47
- ## 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
+ ```
48
41
 
49
- 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):
50
43
 
51
44
  ```js
52
- {
53
- apiKey: "XXXX-...", // required
54
- authType: "authorization" | "header", // default: server config
55
- authScheme: "", // authorization only; blank = Basic
56
- 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"
57
50
  }
58
51
  ```
59
52
 
60
- 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). |
61
70
 
62
71
  Get the server fingerprint with:
63
72
 
@@ -65,63 +74,98 @@ Get the server fingerprint with:
65
74
  openssl s_client -connect rest-server:8904 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
66
75
  ```
67
76
 
68
- ## List nodes (access groups / cardholders / items)
69
-
70
- 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
71
78
 
72
- **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:
73
80
 
74
81
  ```js
75
- msg.payload = { results: [...all pages merged], count, pageCount }
76
- msg.pages = [ raw page 1, raw page 2, ... ] // unmodified server bodies (toggle)
77
- msg.request = { href, params }
78
- msg.contextKey
82
+ { apiKey: "XXXX-...", authType: "authorization" | "header", authScheme: "", headerName: "X-API-Key" }
79
83
  ```
80
84
 
81
- **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.
82
89
 
83
- **Per-message overrides**: `msg.query` (object or `"a=1&b=2"`), `msg.fields`, `msg.top`, `msg.maxPages`, `msg.contextKey`.
90
+ ## List nodes
84
91
 
85
- 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
86
93
 
87
- ## 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 |
88
99
 
89
- 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]` |
90
109
 
91
- 1. `POST features.items.updates.href` with `{"itemIds":[...]}` → current status of every item (output, `phase:"subscribe"`)
92
- 2. `GET next.href` – update-wait (long poll, server returns after ~50 s if nothing changes)
93
- 3. Updates arrive → output (`phase:"update"`), then a new update-wait
94
- 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)
95
- 5. `404` → the server dropped the subscription (>30 s gap or server restart) → re-POST immediately
96
- 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.
97
111
 
98
- **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`.
99
113
 
100
- - `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)
101
- - `msg.itemIds` takes precedence over `payload`
102
- - a new trigger with ids replaces the running subscription
103
- - `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).
104
115
 
105
- **Output 1**
116
+ ### Output 1
106
117
 
107
118
  ```js
108
- msg.payload = { updates: [{ id, status, statusText, statusFlags }], next: { href } } // full server response
109
- msg.updates = msg.payload.updates
110
- msg.phase = "subscribe" | "update" | "timeout"
111
- 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
112
122
  ```
113
123
 
114
- 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.
115
133
 
116
- 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.
117
135
 
118
- 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
119
137
 
120
- ## 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.
121
139
 
122
- `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.
123
145
 
124
- 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.
125
169
 
126
170
  ## Test
127
171
 
@@ -129,4 +173,11 @@ The "waiting" and "timed out" states of each long poll are shown on the node bad
129
173
  npm test
130
174
  ```
131
175
 
132
- 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`.