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 +120 -69
- package/examples/rest-basic.json +387 -72
- package/lib/client.js +177 -76
- package/lib/outputs.js +2 -0
- package/lib/subscription.js +117 -0
- package/nodes/rest-events.html +133 -0
- package/nodes/rest-events.js +172 -0
- package/nodes/rest-item-status.html +15 -7
- package/nodes/rest-item-status.js +53 -96
- package/nodes/rest-lists.html +379 -60
- package/nodes/rest-lists.js +214 -75
- package/nodes/rest-server.html +36 -9
- package/nodes/rest-server.js +108 -53
- package/package.json +3 -2
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 |
|
|
5
|
+
| Node | Endpoint (under the base path) | Mode |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| `get
|
|
8
|
-
| `get
|
|
9
|
-
| `get items` |
|
|
10
|
-
| `
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
17
|
-
2. **Status
|
|
18
|
-
3. **Error**
|
|
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
|
|
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
|
|
32
|
+
## Server address
|
|
32
33
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
+
## List nodes
|
|
84
91
|
|
|
85
|
-
|
|
92
|
+
### Input
|
|
86
93
|
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
+
### Output 1
|
|
106
117
|
|
|
107
118
|
```js
|
|
108
|
-
msg.payload
|
|
109
|
-
msg.
|
|
110
|
-
msg.
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
This needs REST Server v8.30+ and the **RESTStatus** licence.
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
## Event updates node
|
|
119
137
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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`.
|