homebridge-denon-tv 6.2.0 → 6.2.2

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/CHANGELOG.md CHANGED
@@ -21,6 +21,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
21
  - For plugin < v6.1.0 use Homebridge UI <= v5.5.0
22
22
  - For plugin >= v6.1.0 use Homebridge UI >= v5.13.0
23
23
 
24
+ ## [6.2.2] - (24.09.2026)
25
+
26
+ ### Changes
27
+
28
+ - added: RESTFul request rate limit, 600 requests per minute per client and at most 1000 clients per minute, answered with `429` and `Retry-After`, one log warning per client and minute.
29
+
30
+ ## [6.2.1] - (23.09.2026)
31
+
32
+ ### Changes
33
+
34
+ - fix: MQTT `Enable` was shown in the config UI for the Surrounds and Pass Through Inputs zones, which do not support MQTT
35
+ - fix: RESTFul server and MQTT client are no longer started for the Surrounds and Pass Through Inputs zones when enabled in an old config
36
+
24
37
  ## [6.2.0] - (23.09.2026)
25
38
 
26
39
  ### Changes
package/README.md CHANGED
@@ -42,7 +42,7 @@
42
42
  * Surrounds/Pass Trough Inputs control as a [extra accessory tile](https://github.com/grzegorz914/homebridge-denon-tv?tab=readme-ov-file#extra-accessory-tile).
43
43
  * Siri, some times need to create buttons/switches/sensors.
44
44
  * Automations, some times need to create buttons/switches/sensors.
45
- * External integrations, [RESTFul](https://github.com/grzegorz914/homebridge-denon-tv?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-denon-tv?tab=readme-ov-file#mqtt-integration).
45
+ * External integrations, [RESTFul](https://github.com/grzegorz914/homebridge-denon-tv?tab=readme-ov-file#restful-integration), [MQTT](https://github.com/grzegorz914/homebridge-denon-tv?tab=readme-ov-file#mqtt-integration), [HA Discovery](https://github.com/grzegorz914/homeassistant-mqtt-media-player).
46
46
 
47
47
  <p align="center">
48
48
  <a href="https://github.com/grzegorz914/homebridge-denon-tv"><img src="https://raw.githubusercontent.com/grzegorz914/homebridge-denon-tv/main/graphics/homekit.png" width="382"></a>
@@ -168,6 +168,10 @@
168
168
 
169
169
  * Only for Main Zone, Zone 2, Zone 3
170
170
  * Subscribe data as a JSON Object `{Power: true}`
171
+ * HA Discovery - if enabled, the device is published to Home Assistant as one media player (class `receiver`) with power, volume, mute, source selection and more. Main Zone, Zone 2 and Zone 3 are published as separate media players. Requires the [MQTT Universal Media Player](https://github.com/grzegorz914/homeassistant-mqtt-media-player) integration. Additional retained topics:
172
+ * `homeassistant/media_player/<id>/config` - discovery message.
173
+ * `HA State` - `{"power": true, "volume": 49, "muted": false, "source": "SITV", "sound_mode": "MOVIE"}`.
174
+ * `Availability` - `online`, `offline` (last will).
171
175
 
172
176
  | Method | Topic | Message | Type |
173
177
  | --- | --- | --- | --- |
@@ -7007,7 +7007,10 @@
7007
7007
  "title": "Enable",
7008
7008
  "type": "boolean",
7009
7009
  "default": false,
7010
- "description": "This enable MQTT client."
7010
+ "description": "This enable MQTT client.",
7011
+ "condition": {
7012
+ "functionBody": "return model.devices[arrayIndices].zoneControl <= 2;"
7013
+ }
7011
7014
  },
7012
7015
  "host": {
7013
7016
  "title": "IP/Hostname",
package/index.js CHANGED
@@ -182,7 +182,10 @@ class DenonPlatform {
182
182
  const state = zoneStates.get(zone);
183
183
  const manufacturer = denonInfo?.info?.manufacturer || 'denon';
184
184
 
185
- if (!state.restFul1 && zone.restFul?.enable) {
185
+ // RESTFul and MQTT are used only by Main Zone, Zone 2 and Zone 3
186
+ const externalIntegrations = zone.zoneControl <= 2;
187
+
188
+ if (!state.restFul1 && externalIntegrations && zone.restFul?.enable) {
186
189
  try {
187
190
  await new Promise((resolve) => {
188
191
  const timer = setTimeout(resolve, 5000);
@@ -214,7 +217,7 @@ class DenonPlatform {
214
217
  }
215
218
  }
216
219
 
217
- if (!state.mqtt1 && zone.mqtt?.enable) {
220
+ if (!state.mqtt1 && externalIntegrations && zone.mqtt?.enable) {
218
221
  try {
219
222
  await new Promise((resolve) => {
220
223
  const timer = setTimeout(resolve, 10000);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "Denon TV",
3
3
  "name": "homebridge-denon-tv",
4
- "version": "6.2.0",
4
+ "version": "6.2.2",
5
5
  "description": "Homebridge plugin to control Denon/Marantz AV Receivers.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
package/src/restful.js CHANGED
@@ -26,6 +26,49 @@ class RestFul extends EventEmitter {
26
26
  try {
27
27
  const app = express();
28
28
  app.set('json spaces', 2);
29
+
30
+ // Request rate limit per client, protects Homebridge from request floods (CWE-770)
31
+ const rateLimitWindow = 60 * 1000;
32
+ const rateLimitMax = 600;
33
+ const rateLimitClients = 1000;
34
+ const clients = new Map();
35
+ let clientsLimitWarned = 0;
36
+ app.use((req, res, next) => {
37
+ const now = Date.now();
38
+
39
+ // Drop expired windows so the map cannot grow without limit
40
+ if (clients.size > 100) {
41
+ for (const [ip, client] of clients) {
42
+ if (now - client.start >= rateLimitWindow) clients.delete(ip);
43
+ }
44
+ }
45
+
46
+ let client = clients.get(req.ip);
47
+ if (!client || now - client.start >= rateLimitWindow) {
48
+ // Hard limit of tracked addresses, new addresses wait until older windows expire
49
+ if (!client && clients.size >= rateLimitClients) {
50
+ if (now - clientsLimitWarned >= rateLimitWindow) {
51
+ clientsLimitWarned = now;
52
+ if (this.logWarn) this.emit('warn', `RESTFul too many clients (${rateLimitClients}) in one minute, new clients are rejected until the window ends`);
53
+ }
54
+ res.set('Retry-After', String(Math.ceil(rateLimitWindow / 1000)));
55
+ return res.status(429).json({ error: 'RESTFul Too Many Requests' });
56
+ }
57
+ client = { count: 0, start: now, warned: false };
58
+ clients.set(req.ip, client);
59
+ }
60
+
61
+ if (++client.count > rateLimitMax) {
62
+ // Warn once per client and window, a flood must not flood the log too
63
+ if (!client.warned && this.logWarn) this.emit('warn', `RESTFul rate limit ${rateLimitMax} requests per minute exceeded by: ${req.ip}, further requests are rejected until the window ends`);
64
+ client.warned = true;
65
+ res.set('Retry-After', String(Math.ceil((client.start + rateLimitWindow - now) / 1000)));
66
+ return res.status(429).json({ error: 'RESTFul Too Many Requests' });
67
+ }
68
+
69
+ next();
70
+ });
71
+
29
72
  app.use(json());
30
73
 
31
74
  // Optional token auth, when a token is configured every route requires "Authorization: Bearer <token>"