homebridge-winix-purifiers 2.2.5 → 2.2.6

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.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CharacteristicWrapper = exports.CharacteristicManager = void 0;
4
+ const winix_api_1 = require("winix-api");
4
5
  const errors_1 = require("./errors");
5
6
  /**
6
7
  * Manager to provide a consistent way to get and set characteristics
@@ -62,7 +63,15 @@ class CharacteristicWrapper {
62
63
  }
63
64
  catch (e) {
64
65
  (0, errors_1.assertError)(e);
65
- this.log.error('error calling Winix API:', e.message);
66
+ // Known transient upstream conditions (rate limit, 5xx, DNS, malformed body)
67
+ // still fail the HAP call so HomeKit knows the action didn't land, but log at
68
+ // warn without the implication of a plugin bug.
69
+ if (e instanceof winix_api_1.RateLimitError || e instanceof winix_api_1.UpstreamUnavailableError) {
70
+ this.log.warn(`Winix API transient: ${e.constructor.name}: ${e.message}`);
71
+ }
72
+ else {
73
+ this.log.error('error calling Winix API:', e.message);
74
+ }
66
75
  throw new this.platform.HapStatusError(-70402 /* HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
67
76
  }
68
77
  }
package/dist/device.js CHANGED
@@ -5,6 +5,9 @@ const winix_api_1 = require("winix-api");
5
5
  const MAX_BACKOFF_MS = 5 * 60 * 1000;
6
6
  const COMMAND_DELAY_MS = 1500;
7
7
  const UNREACHABLE_THRESHOLD = 3;
8
+ // 90s clears WinixClient's internal 60s rate-limit cooldown plus a small buffer,
9
+ // so the first poll after a rate-limited initialFetch actually has a chance to succeed.
10
+ const FAILED_FETCH_RETRY_MS = 90 * 1000;
8
11
  class Device {
9
12
  constructor(deviceId, pollIntervalMs, log, client) {
10
13
  this.deviceId = deviceId;
@@ -41,8 +44,8 @@ class Device {
41
44
  this.log.debug('device:initialFetch()', JSON.stringify(this.state));
42
45
  }
43
46
  catch (e) {
44
- if (e instanceof winix_api_1.RateLimitError) {
45
- this.log.warn('device:initialFetch() rate limited, using defaults');
47
+ if (e instanceof winix_api_1.RateLimitError || e instanceof winix_api_1.UpstreamUnavailableError) {
48
+ this.log.warn(`device:initialFetch() ${e.constructor.name}, using defaults (will retry soon)`);
46
49
  return;
47
50
  }
48
51
  this.log.warn('device:initialFetch() failed, using defaults:', e.message);
@@ -50,10 +53,14 @@ class Device {
50
53
  }
51
54
  startPolling(onUpdate) {
52
55
  this.onUpdate = onUpdate;
53
- // Stagger the first poll with a random delay to avoid all devices
54
- // hitting the API at the same time
55
- const jitter = Math.floor(Math.random() * this.pollIntervalMs);
56
- this.schedulePoll(jitter);
56
+ // If initialFetch failed (rate limited, upstream unavailable, etc.), the device
57
+ // is unreachable in HomeKit until the first successful poll. Skip the full-interval
58
+ // jitter and retry soon to close that window. Otherwise stagger the first poll
59
+ // with a random delay to avoid all devices hitting the API at the same time.
60
+ const delay = this.hasReceivedData
61
+ ? Math.floor(Math.random() * this.pollIntervalMs)
62
+ : Math.min(FAILED_FETCH_RETRY_MS, this.pollIntervalMs);
63
+ this.schedulePoll(delay);
57
64
  }
58
65
  resetPollTimer(delayMs = 3000) {
59
66
  this.schedulePoll(delayMs);
@@ -168,10 +175,10 @@ class Device {
168
175
  this.schedulePoll(this.pollIntervalMs);
169
176
  return;
170
177
  }
171
- // Winix occasionally returns "no data" for a healthy device. Treat as
172
- // transient: keep last-known state and don't count toward unreachable.
173
- if (e instanceof winix_api_1.NoDataError) {
174
- this.log.debug('device:poll() got NoDataError, retaining last-known state');
178
+ // Transient upstream conditions (Winix "no data", 5xx, DNS/connect failures,
179
+ // malformed bodies). Keep last-known state and don't count toward unreachable.
180
+ if (e instanceof winix_api_1.NoDataError || e instanceof winix_api_1.UpstreamUnavailableError) {
181
+ this.log.debug(`device:poll() ${e.constructor.name}, retaining last-known state`);
175
182
  this.schedulePoll(this.pollIntervalMs);
176
183
  return;
177
184
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "Winix Air Purifiers",
3
3
  "name": "homebridge-winix-purifiers",
4
- "version": "2.2.5",
4
+ "version": "2.2.6",
5
5
  "description": "Homebridge plugin for Winix air purifiers",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -66,7 +66,7 @@
66
66
  ],
67
67
  "dependencies": {
68
68
  "@homebridge/plugin-ui-utils": "1.0.3",
69
- "winix-api": "2.0.1"
69
+ "winix-api": "2.0.2"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "20.11.0",