homebridge-xbox-tv 4.1.21 → 4.1.22

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
@@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
18
  - For plugin < v4.1.0 use Homebridge UI <= v5.5.0
19
19
  - For plugin >= v4.1.0 use Homebridge UI >= v5.13.0
20
20
 
21
+ ## [4.1.22] - (18.07.2026)
22
+
23
+ ## Changes
24
+
25
+ - fix: Local API — UDP socket leak causing socket count to grow continuously while the Xbox is in standby; each 6 s reconnect heartbeat that found the console ping-reachable but not yet handshaked opened a brand-new socket on a fresh ephemeral port without closing the previous, still-open one; `connect()` now closes any stale socket before binding a new one, and the `acknowledge` inactivity watchdog now closes its socket instead of only clearing the in-memory reference
26
+ - chore: bump `@homebridge/plugin-ui-utils` to ^2.2.5, `mqtt` to ^5.15.2, `uuid` to ^14.0.1, `axios` to ^1.18.1
27
+
21
28
  ## [4.1.21] - (04.06.2026)
22
29
 
23
30
  ## Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "Xbox TV",
3
3
  "name": "homebridge-xbox-tv",
4
- "version": "4.1.21",
4
+ "version": "4.1.22",
5
5
  "description": "Homebridge plugin to control Xbox game consoles.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -35,12 +35,12 @@
35
35
  "node": "^20 || ^22 || ^24 || ^25 || ^26"
36
36
  },
37
37
  "dependencies": {
38
- "@homebridge/plugin-ui-utils": "^2.2.4",
39
- "mqtt": "^5.15.1",
38
+ "@homebridge/plugin-ui-utils": "^2.2.5",
39
+ "mqtt": "^5.15.2",
40
40
  "hex-to-binary": "^1.0.1",
41
- "uuid": "^14.0.0",
41
+ "uuid": "^14.0.1",
42
42
  "express": "^5.2.1",
43
- "axios": "^1.17.0"
43
+ "axios": "^1.18.1"
44
44
  },
45
45
  "keywords": [
46
46
  "homebridge",
@@ -123,6 +123,19 @@ class XboxLocalApi extends EventEmitter {
123
123
  async connect() {
124
124
  return new Promise((resolve, reject) => {
125
125
  try {
126
+ // A previous attempt may have bound a socket that never completed
127
+ // the handshake (eg. console in standby, no discoveryResponse).
128
+ // Close it before binding a new one, otherwise every retry leaks
129
+ // a UDP socket on a fresh ephemeral port. Listeners are stripped
130
+ // first so the stale socket's 'close' handler can't fire
131
+ // updateState() against the session we're about to create.
132
+ if (this.socket) {
133
+ const staleSocket = this.socket;
134
+ this.socket = null;
135
+ staleSocket.removeAllListeners();
136
+ staleSocket.close();
137
+ }
138
+
126
139
  this.socket = dgram.createSocket('udp4')
127
140
  .on('error', (error) => {
128
141
  if (this.logError) this.emit('error', `Socket error: ${error}`);
@@ -431,14 +444,26 @@ class XboxLocalApi extends EventEmitter {
431
444
 
432
445
  if (elapsed >= 14) {
433
446
  clearInterval(this.acknowledgeInterval);
447
+ this.acknowledgeInterval = null;
448
+
449
+ try {
450
+ const sequenceNumber = await this.getSequenceNumber();
451
+ const disconnect = new MessagePacket('disconnect');
452
+ disconnect.set('reason', 2);
453
+ disconnect.set('errorCode', 0);
454
+ const message = disconnect.pack(this.crypto, sequenceNumber, this.targetParticipantId, this.sourceParticipantId);
455
+ await this.sendSocketMessage(message, 'disconnect');
456
+ } catch (error) {
457
+ if (this.logError) this.emit('error', `Send disconnect error: ${error}`);
458
+ }
434
459
 
435
- const sequenceNumber = await this.getSequenceNumber();
436
- const disconnect = new MessagePacket('disconnect');
437
- disconnect.set('reason', 2);
438
- disconnect.set('errorCode', 0);
439
- const message = disconnect.pack(this.crypto, sequenceNumber, this.targetParticipantId, this.sourceParticipantId);
440
- await this.sendSocketMessage(message, 'disconnect');
441
- await this.updateState();
460
+ // Close socket first so on('close') triggers updateState().
461
+ // Calling updateState() directly would null this.socket before
462
+ // close(), causing the old socket to leak in the OS.
463
+ const socketToClose = this.socket;
464
+ this.socket = null;
465
+ this.connected = false;
466
+ if (socketToClose) socketToClose.close();
442
467
  }
443
468
  }, 1000);
444
469
  }