homebridge-denon-tv 6.2.1 → 6.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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/constants.js +29 -0
- package/src/mainzone.js +2 -2
- package/src/restful.js +43 -0
- package/src/zone2.js +2 -2
- package/src/zone3.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -21,6 +21,18 @@ 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.3] - (24.09.2026)
|
|
25
|
+
|
|
26
|
+
### Changes
|
|
27
|
+
|
|
28
|
+
- changed: HA Discovery, sound modes are shown with readable names (e.g. `Pure Direct`, `DTS Surround` instead of `PURE DIRECT`, `DTS SURROUND`). Commands are unchanged
|
|
29
|
+
|
|
30
|
+
## [6.2.2] - (24.09.2026)
|
|
31
|
+
|
|
32
|
+
### Changes
|
|
33
|
+
|
|
34
|
+
- 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.
|
|
35
|
+
|
|
24
36
|
## [6.2.1] - (23.09.2026)
|
|
25
37
|
|
|
26
38
|
### Changes
|
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -661,3 +661,32 @@ export const ZonePrefixMap = {
|
|
|
661
661
|
3: 'MS',
|
|
662
662
|
4: 'SI'
|
|
663
663
|
};
|
|
664
|
+
|
|
665
|
+
// Display names for the normalized sound modes (Home Assistant)
|
|
666
|
+
export const SoundModeDisplayName = {
|
|
667
|
+
"AUTO": "Auto",
|
|
668
|
+
"MOVIE": "Movie",
|
|
669
|
+
"MUSIC": "Music",
|
|
670
|
+
"GAME": "Game",
|
|
671
|
+
"DIRECT": "Direct",
|
|
672
|
+
"PURE DIRECT": "Pure Direct",
|
|
673
|
+
"STEREO": "Stereo",
|
|
674
|
+
"MCH STEREO": "Multi Ch Stereo",
|
|
675
|
+
"DOLBY DIGITAL": "Dolby Digital",
|
|
676
|
+
"DTS SURROUND": "DTS Surround",
|
|
677
|
+
"AURO3D": "Auro-3D",
|
|
678
|
+
"AURO2DSURR": "Auro-2D Surround",
|
|
679
|
+
"WIDE SCREEN": "Wide Screen",
|
|
680
|
+
"SUPER STADIUM": "Super Stadium",
|
|
681
|
+
"ROCK ARENA": "Rock Arena",
|
|
682
|
+
"JAZZ CLUB": "Jazz Club",
|
|
683
|
+
"CLASSIC CONCERT": "Classic Concert",
|
|
684
|
+
"MONO MOVIE": "Mono Movie",
|
|
685
|
+
"MATRIX": "Matrix",
|
|
686
|
+
"VIDEO GAME": "Video Game",
|
|
687
|
+
"VIRTUAL": "Virtual",
|
|
688
|
+
"NEURAL": "Neural",
|
|
689
|
+
"STANDARD": "Standard",
|
|
690
|
+
"LEFT": "Left",
|
|
691
|
+
"RIGHT": "Right"
|
|
692
|
+
};
|
package/src/mainzone.js
CHANGED
|
@@ -2,7 +2,7 @@ import EventEmitter from 'events';
|
|
|
2
2
|
import Zone from './zone.js';
|
|
3
3
|
import Functions from './functions.js';
|
|
4
4
|
import HaDiscovery from './hadiscovery.js';
|
|
5
|
-
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, PictureModesDenonString, DirectSoundMode, SoundModeConversion } from './constants.js';
|
|
5
|
+
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, PictureModesDenonString, DirectSoundMode, SoundModeConversion, SoundModeDisplayName } from './constants.js';
|
|
6
6
|
let Accessory, Characteristic, Service, Categories, Encode, AccessoryUUID;
|
|
7
7
|
|
|
8
8
|
class MainZone extends EventEmitter {
|
|
@@ -1004,7 +1004,7 @@ class MainZone extends EventEmitter {
|
|
|
1004
1004
|
|
|
1005
1005
|
try {
|
|
1006
1006
|
const sources = (this.inputsServices ?? []).map(input => ({ id: `${input.zonePrefix}${input.reference}`, name: input.name }));
|
|
1007
|
-
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: mode })) : [];
|
|
1007
|
+
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: SoundModeDisplayName[mode] ?? mode })) : [];
|
|
1008
1008
|
await this.ha.publishConfig({ sources, soundModes });
|
|
1009
1009
|
await this.haUpdateState();
|
|
1010
1010
|
} catch (error) {
|
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>"
|
package/src/zone2.js
CHANGED
|
@@ -2,7 +2,7 @@ import EventEmitter from 'events';
|
|
|
2
2
|
import Zone from './zone.js';
|
|
3
3
|
import Functions from './functions.js';
|
|
4
4
|
import HaDiscovery from './hadiscovery.js';
|
|
5
|
-
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, SoundModeConversion } from './constants.js';
|
|
5
|
+
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, SoundModeConversion, SoundModeDisplayName } from './constants.js';
|
|
6
6
|
let Accessory, Characteristic, Service, Categories, Encode, AccessoryUUID;
|
|
7
7
|
|
|
8
8
|
class Zone2 extends EventEmitter {
|
|
@@ -918,7 +918,7 @@ class Zone2 extends EventEmitter {
|
|
|
918
918
|
|
|
919
919
|
try {
|
|
920
920
|
const sources = (this.inputsServices ?? []).map(input => ({ id: `${input.zonePrefix}${input.reference}`, name: input.name }));
|
|
921
|
-
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: mode })) : [];
|
|
921
|
+
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: SoundModeDisplayName[mode] ?? mode })) : [];
|
|
922
922
|
await this.ha.publishConfig({ sources, soundModes });
|
|
923
923
|
await this.haUpdateState();
|
|
924
924
|
} catch (error) {
|
package/src/zone3.js
CHANGED
|
@@ -2,7 +2,7 @@ import EventEmitter from 'events';
|
|
|
2
2
|
import Zone from './zone.js';
|
|
3
3
|
import Functions from './functions.js';
|
|
4
4
|
import HaDiscovery from './hadiscovery.js';
|
|
5
|
-
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, SoundModeConversion } from './constants.js';
|
|
5
|
+
import { PictureModesConversionToHomeKit, PictureModesDenonNumber, SoundModeConversion, SoundModeDisplayName } from './constants.js';
|
|
6
6
|
let Accessory, Characteristic, Service, Categories, Encode, AccessoryUUID;
|
|
7
7
|
|
|
8
8
|
class Zone3 extends EventEmitter {
|
|
@@ -918,7 +918,7 @@ class Zone3 extends EventEmitter {
|
|
|
918
918
|
|
|
919
919
|
try {
|
|
920
920
|
const sources = (this.inputsServices ?? []).map(input => ({ id: `${input.zonePrefix}${input.reference}`, name: input.name }));
|
|
921
|
-
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: mode })) : [];
|
|
921
|
+
const soundModes = this.ha.commands.sound_mode ? [...new Set(Object.values(SoundModeConversion))].map(mode => ({ id: mode, name: SoundModeDisplayName[mode] ?? mode })) : [];
|
|
922
922
|
await this.ha.publishConfig({ sources, soundModes });
|
|
923
923
|
await this.haUpdateState();
|
|
924
924
|
} catch (error) {
|