@homebridge-plugins/homebridge-august 3.2.0 → 3.2.1

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.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ const child = require('node:child_process')
4
+ const fs = require('node:fs')
5
+
6
+ const bump = (process.argv[2] || 'patch').toLowerCase()
7
+ const allowed = new Set(['major', 'minor', 'patch'])
8
+
9
+ if (!allowed.has(bump)) {
10
+ console.error(`Invalid bump level '${bump}'. Use major, minor, or patch.`)
11
+ process.exit(1)
12
+ }
13
+
14
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
15
+
16
+ function inc(v, level) {
17
+ const parts = v.split('-')[0].split('+')[0].split('.').map(Number)
18
+ if (level === 'major') {
19
+ parts[0] += 1
20
+ parts[1] = 0
21
+ parts[2] = 0
22
+ } else if (level === 'minor') {
23
+ parts[1] += 1
24
+ parts[2] = 0
25
+ } else {
26
+ parts[2] += 1
27
+ }
28
+ return parts.join('.')
29
+ }
30
+
31
+ function latestStable(name) {
32
+ try {
33
+ const versions = JSON.parse(child.execSync(`npm info ${name} versions --json`).toString('utf8').trim())
34
+ const stable = (Array.isArray(versions) ? versions : [])
35
+ .filter(v => /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/.test(v) && !v.includes('-'))
36
+ stable.sort((a, b) => {
37
+ const ap = a.split('.').map(Number)
38
+ const bp = b.split('.').map(Number)
39
+ for (let i = 0; i < 3; i++) {
40
+ if (ap[i] !== bp[i]) return bp[i] - ap[i]
41
+ }
42
+ return 0
43
+ })
44
+ return stable[0] || ''
45
+ } catch {
46
+ return ''
47
+ }
48
+ }
49
+
50
+ const base = latestStable(pkg.name) || pkg.version
51
+ process.stdout.write(inc(base, bump))
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # [3.2.0](https://github.com/homebridge-plugins/homebridge-august/compare/v3.1.4...v3.2.0) (2026-04-29)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **platform:** use august-yale's typed exceptions for error classification ([93219ac](https://github.com/homebridge-plugins/homebridge-august/commit/93219accdf8571330b8f3fefa029d727ddf7a6c3))
7
+
8
+
9
+
1
10
  ## [3.1.4](https://github.com/homebridge-plugins/homebridge-august/compare/v3.1.3...v3.1.4) (2026-04-27)
2
11
 
3
12
 
@@ -27,9 +27,13 @@ export declare class ConnectivityManager {
27
27
  private state;
28
28
  private client?;
29
29
  private probeTimer?;
30
+ private heartbeatTimer?;
30
31
  private backoffIndex;
31
32
  private rebuildInFlight?;
32
33
  private listeners;
34
+ private offlineSinceMs?;
35
+ private probeAttempts;
36
+ private lastFailureMessage?;
33
37
  constructor(log: Logging, credentialsFactory: () => Promise<credentials>,
34
38
  /**
35
39
  * Called whenever the manager builds or rebuilds the August client.
@@ -83,6 +87,9 @@ export declare class ConnectivityManager {
83
87
  private rebuildClient;
84
88
  private classify;
85
89
  private transitionTo;
90
+ private startHeartbeat;
91
+ private stopHeartbeat;
92
+ private emitHeartbeat;
86
93
  private clearProbeTimer;
87
94
  }
88
95
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"connectivity-manager.d.ts","sourceRoot":"","sources":["../src/connectivity-manager.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,MAAiE,MAAM,aAAa,CAAA;AAE3F,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,CAAA;AAcjF,UAAU,cAAc;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,KAAK,EAAE,MAAM;CAI1B;AAED,qBAAa,mBAAmB;IAS5B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe;IAjBlC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,UAAU,CAAC,CAAgB;IACnC,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,eAAe,CAAC,CAAe;IACvC,OAAO,CAAC,SAAS,CAAwE;gBAGtE,GAAG,EAAE,OAAO,EACZ,kBAAkB,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC;IAC/D;;;;;;OAMG;IACc,eAAe,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAe;IAG7E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B;;;;;;;;;;;;OAYG;IACG,OAAO,CAAC,CAAC,EACb,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAgCzB;;;;;OAKG;IACH,iBAAiB,IAAI,IAAI;IASzB,QAAQ,IAAI,iBAAiB;IAI7B;;;;;OAKG;IACH,SAAS,IAAI,MAAM,GAAG,SAAS;IAI/B;;;;OAIG;IACH,aAAa,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxF,QAAQ,IAAI,IAAI;IAUhB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,aAAa;YAQP,QAAQ;YA8BR,aAAa;IAkB3B,OAAO,CAAC,QAAQ;IAkDhB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,eAAe;CAMxB"}
1
+ {"version":3,"file":"connectivity-manager.d.ts","sourceRoot":"","sources":["../src/connectivity-manager.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,MAAiE,MAAM,aAAa,CAAA;AAE3F,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,YAAY,CAAA;AA6BjF,UAAU,cAAc;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,KAAK,EAAE,MAAM;CAI1B;AAED,qBAAa,mBAAmB;IAiB5B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe;IAzBlC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,UAAU,CAAC,CAAgB;IACnC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,eAAe,CAAC,CAAe;IACvC,OAAO,CAAC,SAAS,CAAwE;IAKzF,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,kBAAkB,CAAC,CAAQ;gBAGhB,GAAG,EAAE,OAAO,EACZ,kBAAkB,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC;IAC/D;;;;;;OAMG;IACc,eAAe,GAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAe;IAG7E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B;;;;;;;;;;;;OAYG;IACG,OAAO,CAAC,CAAC,EACb,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAClC,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAgCzB;;;;;OAKG;IACH,iBAAiB,IAAI,IAAI;IAYzB,QAAQ,IAAI,iBAAiB;IAI7B;;;;;OAKG;IACH,SAAS,IAAI,MAAM,GAAG,SAAS;IAI/B;;;;OAIG;IACH,aAAa,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxF,QAAQ,IAAI,IAAI;IAWhB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,aAAa;YAQP,QAAQ;YAqER,aAAa;IAkB3B,OAAO,CAAC,QAAQ;IAkDhB,OAAO,CAAC,YAAY;IA8BpB,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,eAAe;CAMxB"}
@@ -7,6 +7,20 @@ import August, { AbortedError, InvalidAuth, NetworkError, TimeoutError } from 'a
7
7
  const BACKOFF_SCHEDULE_MS = [5_000, 10_000, 20_000, 40_000, 80_000, 160_000, 300_000];
8
8
  /** Probe timeout — short on purpose. A healthy August API responds in <1s. */
9
9
  const PROBE_TIMEOUT_MS = 8_000;
10
+ /**
11
+ * How often to emit a heartbeat log line while offline.
12
+ *
13
+ * Without it the log goes silent after the initial healthy → degraded →
14
+ * offline transition, making it impossible to distinguish "state machine
15
+ * is working hard, probes are failing every backoff slot" from "state
16
+ * machine is silently stuck". The heartbeat surfaces probe attempts and
17
+ * the most recent failure so future regressions are debuggable from the
18
+ * log alone.
19
+ *
20
+ * One hour matches the typical "I checked on it later" cadence; not so
21
+ * frequent it becomes log spam during a real ISP outage.
22
+ */
23
+ const OFFLINE_HEARTBEAT_INTERVAL_MS = 60 * 60 * 1000;
10
24
  export class OfflineError extends Error {
11
25
  constructor(label) {
12
26
  super(`Operation skipped — connectivity is offline: ${label}`);
@@ -20,9 +34,16 @@ export class ConnectivityManager {
20
34
  state = 'healthy';
21
35
  client;
22
36
  probeTimer;
37
+ heartbeatTimer;
23
38
  backoffIndex = 0;
24
39
  rebuildInFlight;
25
40
  listeners = new Set();
41
+ // Diagnostic counters. Reset on transition out of offline. Used by the
42
+ // heartbeat log so an ongoing outage is visible in the log without
43
+ // having to trace per-probe details.
44
+ offlineSinceMs;
45
+ probeAttempts = 0;
46
+ lastFailureMessage;
26
47
  constructor(log, credentialsFactory,
27
48
  /**
28
49
  * Called whenever the manager builds or rebuilds the August client.
@@ -97,7 +118,10 @@ export class ConnectivityManager {
97
118
  if (this.state === 'healthy') {
98
119
  return;
99
120
  }
100
- this.log.info('Connectivity: PubNub reconnect signal received — probing immediately');
121
+ const offlineFor = this.offlineSinceMs
122
+ ? ` (offline for ${Math.floor((Date.now() - this.offlineSinceMs) / 1000)}s)`
123
+ : '';
124
+ this.log.info(`Connectivity: PubNub reconnect signal received in ${this.state}${offlineFor} — probing immediately`);
101
125
  this.clearProbeTimer();
102
126
  void this.runProbe();
103
127
  }
@@ -124,6 +148,7 @@ export class ConnectivityManager {
124
148
  }
125
149
  shutdown() {
126
150
  this.clearProbeTimer();
151
+ this.stopHeartbeat();
127
152
  this.client?.destroy();
128
153
  this.client = undefined;
129
154
  this.onClientChanged(undefined);
@@ -164,25 +189,61 @@ export class ConnectivityManager {
164
189
  this.probeTimer = setTimeout(() => void this.runProbe(), jittered);
165
190
  }
166
191
  async runProbe() {
167
- if (!this.client) {
168
- return;
192
+ this.probeAttempts++;
193
+ let client = this.client;
194
+ if (!client) {
195
+ // No client at all — rebuild from credentials. This branch is
196
+ // hit only at startup if init() hasn't completed; in steady
197
+ // state we always have a client.
198
+ this.log.warn(`Connectivity: probe attempt #${this.probeAttempts} — no client to probe; trying to (re)build`);
199
+ try {
200
+ await this.rebuildClient('probe with no client');
201
+ }
202
+ catch (e) {
203
+ this.lastFailureMessage = `probe-build: ${e?.message ?? e}`;
204
+ this.scheduleProbe();
205
+ return;
206
+ }
207
+ client = this.client;
208
+ if (!client) {
209
+ // rebuildClient resolved but didn't actually populate this.client.
210
+ // Defensive: don't proceed without a client; reschedule and try
211
+ // again on the next backoff slot.
212
+ this.lastFailureMessage = 'probe-build: rebuildClient produced no client';
213
+ this.scheduleProbe();
214
+ return;
215
+ }
169
216
  }
217
+ // Reset the transport BEFORE probing.
218
+ //
219
+ // The previous implementation probed against the existing client's
220
+ // socket pool. That pool may hold stale half-open sockets from
221
+ // before the outage; probing against them just keeps failing on
222
+ // dead connections, so the system stayed offline even after the
223
+ // network had recovered. The PubNub-triggered immediate probe
224
+ // hit the same wall.
225
+ //
226
+ // resetTransport() throws away the dispatcher and creates a fresh
227
+ // one without losing auth state, the session token, or
228
+ // configuration. Distinct from destroy() + new August() — that
229
+ // would force a re-auth round-trip on the next call, which itself
230
+ // can fail on a still-flaky network and leave us stuck. With just
231
+ // a transport reset, the next request gets a clean connection
232
+ // pool and reuses the cached session token; if it succeeds, we're
233
+ // healthy in one round-trip.
234
+ client.resetTransport();
235
+ this.log.info(`Connectivity: probe attempt #${this.probeAttempts} (state=${this.state})`);
170
236
  try {
171
- // Cheap authenticated call against the EXISTING client. .locks()
172
- // returns a small map and is the lightest endpoint. Race against a
173
- // short timeout — the goal is "is the network working", not
174
- // "is everything healthy".
175
237
  await Promise.race([
176
- this.client.locks(),
238
+ client.locks(),
177
239
  new Promise((_resolve, reject) => setTimeout(() => reject(new TimeoutError('probe timed out')), PROBE_TIMEOUT_MS)),
178
240
  ]);
179
- // Network is back. Rebuild the client to shed any half-open sockets
180
- // accumulated before the outage, then mark healthy.
181
- await this.rebuildClient('probe succeeded');
241
+ this.log.info(`Connectivity: probe attempt #${this.probeAttempts} succeeded`);
182
242
  this.transitionTo('healthy');
183
243
  this.backoffIndex = 0;
184
244
  }
185
- catch {
245
+ catch (e) {
246
+ this.lastFailureMessage = `${this.classify(e)}: ${e?.message ?? e}`;
186
247
  if (this.state !== 'offline') {
187
248
  this.log.warn('Connectivity: probe failed — entering offline state');
188
249
  this.transitionTo('offline');
@@ -264,6 +325,19 @@ export class ConnectivityManager {
264
325
  }
265
326
  this.state = next;
266
327
  this.log.info(`Connectivity: ${prev} -> ${next}`);
328
+ // Heartbeat lifecycle: start when entering offline, stop when leaving.
329
+ // offlineSinceMs is set on entry; probe-attempt count is NOT reset
330
+ // here because runProbe() already incremented it for the failure
331
+ // that triggered this transition.
332
+ if (next === 'offline') {
333
+ this.offlineSinceMs = Date.now();
334
+ this.startHeartbeat();
335
+ }
336
+ else if (prev === 'offline') {
337
+ this.stopHeartbeat();
338
+ this.offlineSinceMs = undefined;
339
+ this.probeAttempts = 0;
340
+ }
267
341
  for (const l of this.listeners) {
268
342
  try {
269
343
  l(next, prev);
@@ -273,6 +347,31 @@ export class ConnectivityManager {
273
347
  }
274
348
  }
275
349
  }
350
+ startHeartbeat() {
351
+ this.stopHeartbeat();
352
+ this.heartbeatTimer = setInterval(() => this.emitHeartbeat(), OFFLINE_HEARTBEAT_INTERVAL_MS);
353
+ // Allow Node to exit if this is the only outstanding timer; we
354
+ // don't want the heartbeat to keep the process alive on shutdown.
355
+ if (typeof this.heartbeatTimer.unref === 'function') {
356
+ this.heartbeatTimer.unref();
357
+ }
358
+ }
359
+ stopHeartbeat() {
360
+ if (this.heartbeatTimer) {
361
+ clearInterval(this.heartbeatTimer);
362
+ this.heartbeatTimer = undefined;
363
+ }
364
+ }
365
+ emitHeartbeat() {
366
+ if (this.state !== 'offline' || this.offlineSinceMs === undefined) {
367
+ return;
368
+ }
369
+ const minutes = Math.floor((Date.now() - this.offlineSinceMs) / 60000);
370
+ const lastFailure = this.lastFailureMessage ?? 'unknown';
371
+ this.log.warn(`Connectivity: still offline after ${minutes} min — `
372
+ + `${this.probeAttempts} probe attempt${this.probeAttempts === 1 ? '' : 's'}, `
373
+ + `last failure: ${lastFailure}`);
374
+ }
276
375
  clearProbeTimer() {
277
376
  if (this.probeTimer) {
278
377
  clearTimeout(this.probeTimer);
@@ -1 +1 @@
1
- {"version":3,"file":"connectivity-manager.js","sourceRoot":"","sources":["../src/connectivity-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectivity-manager.js","sourceRoot":"","sources":["../src/connectivity-manager.ts"],"names":[],"mappings":"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"}
package/dist/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-august",
3
3
  "displayName": "August",
4
4
  "type": "module",
5
- "version": "3.2.0",
5
+ "version": "3.2.1",
6
6
  "description": "The August plugin allows you to access your August & Yale device(s) from HomeKit.",
7
7
  "author": {
8
8
  "name": "donavanbecker",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@homebridge/plugin-ui-utils": "^2.2.3",
65
- "august-yale": "^1.2.0",
65
+ "august-yale": "^1.2.1",
66
66
  "rxjs": "^7.8.2"
67
67
  },
68
68
  "devDependencies": {
@@ -80,4 +80,4 @@
80
80
  "typescript": "^6.0.3",
81
81
  "vitest": "^4.1.5"
82
82
  }
83
- }
83
+ }
@@ -1 +1 @@
1
- <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @homebridge-plugins/homebridge-august</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-august"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@homebridge-plugins/homebridge-august</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/homebridge-plugins/homebridge-august/blob/93219accdf8571330b8f3fefa029d727ddf7a6c3/src/index.ts#L26">index.ts:26</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@homebridge-plugins/homebridge-august</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @homebridge-plugins/homebridge-august</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-august"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@homebridge-plugins/homebridge-august</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/homebridge-plugins/homebridge-august/blob/6ea4f34cb710cbff333e71d84b048bb61d34ea64/src/index.ts#L26">index.ts:26</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@homebridge-plugins/homebridge-august</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-august",
3
3
  "displayName": "August",
4
4
  "type": "module",
5
- "version": "3.2.0",
5
+ "version": "3.2.1",
6
6
  "description": "The August plugin allows you to access your August & Yale device(s) from HomeKit.",
7
7
  "author": {
8
8
  "name": "donavanbecker",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@homebridge/plugin-ui-utils": "^2.2.3",
65
- "august-yale": "^1.2.0",
65
+ "august-yale": "^1.2.1",
66
66
  "rxjs": "^7.8.2"
67
67
  },
68
68
  "devDependencies": {
@@ -80,4 +80,4 @@
80
80
  "typescript": "^6.0.3",
81
81
  "vitest": "^4.1.5"
82
82
  }
83
- }
83
+ }
@@ -57,7 +57,9 @@ vi.mock('august-yale', () => {
57
57
  }
58
58
  }
59
59
  // Each new August() returns a unique object so tests can assert which
60
- // client is being used (initial vs. rebuilt).
60
+ // client is being used (initial vs. rebuilt). Tests can also override
61
+ // the methods of the NEXT client to be built by setting fields on
62
+ // `nextClientOverride`. After one use the override is cleared.
61
63
  let nextId = 0
62
64
  // eslint-disable-next-line prefer-arrow-callback
63
65
  const MockAugust = vi.fn().mockImplementation(function () {
@@ -69,6 +71,7 @@ vi.mock('august-yale', () => {
69
71
  lock: vi.fn().mockResolvedValue(undefined),
70
72
  unlock: vi.fn().mockResolvedValue(undefined),
71
73
  destroy: vi.fn(),
74
+ resetTransport: vi.fn(),
72
75
  end: vi.fn(),
73
76
  }
74
77
  })
@@ -282,7 +285,6 @@ describe('ConnectivityManager', () => {
282
285
  const onClientChanged = vi.fn()
283
286
  const m = new ConnectivityManager(makeLog(), fakeCredentials, onClientChanged)
284
287
  await m.init()
285
- const client = m.getClient() as any
286
288
  expect(onClientChanged).toHaveBeenCalledTimes(1)
287
289
 
288
290
  // Network failure: degrades and schedules probe.
@@ -290,15 +292,17 @@ describe('ConnectivityManager', () => {
290
292
  await m.execute('test', async () => { throw new TimeoutError('boom') })
291
293
  expect(m.getState()).toBe('degraded')
292
294
 
293
- // Probe will call client.locks() — ensure it succeeds.
294
- client.locks.mockResolvedValue({})
295
+ // Probe calls resetTransport() on the existing client and then its
296
+ // .locks(). Default mock = locks resolves, so probe succeeds.
295
297
 
296
298
  // Advance time past the first backoff slot (~5s plus jitter).
297
299
  await vi.advanceTimersByTimeAsync(7_000)
298
300
 
299
- // Probe success → rebuild + healthy. onClientChanged fires again.
301
+ // Probe success → healthy. onClientChanged was called only once
302
+ // (at init). The resetTransport-based design does NOT swap the
303
+ // client instance on recovery; it just recycles its transport.
300
304
  expect(m.getState()).toBe('healthy')
301
- expect(onClientChanged).toHaveBeenCalledTimes(2)
305
+ expect(onClientChanged).toHaveBeenCalledTimes(1)
302
306
  })
303
307
 
304
308
  it('escalates from degraded to offline when probe fails, with longer backoff', async () => {
@@ -307,14 +311,18 @@ describe('ConnectivityManager', () => {
307
311
  const client = m.getClient() as any
308
312
 
309
313
  const { TimeoutError } = await import('august-yale')
310
- await m.execute('test', async () => { throw new TimeoutError('boom') })
311
- expect(m.getState()).toBe('degraded')
312
314
 
313
- // Probe will fail (locks() rejects with timeout).
314
- client.locks.mockImplementation(() => new Promise((_resolve, reject) => {
315
+ // Make the existing client's .locks() always fail. With the
316
+ // resetTransport-based design, probes reuse this client, so a
317
+ // single mockImplementation is sufficient for every probe attempt.
318
+ client.locks = vi.fn().mockImplementation(() => new Promise((_resolve, reject) => {
315
319
  setTimeout(() => reject(new TimeoutError('still down')), 100)
316
320
  }))
317
321
 
322
+ // Trigger degraded.
323
+ await m.execute('test', async () => { throw new TimeoutError('boom') })
324
+ expect(m.getState()).toBe('degraded')
325
+
318
326
  // Advance past first backoff + probe timeout.
319
327
  await vi.advanceTimersByTimeAsync(15_000)
320
328
  expect(m.getState()).toBe('offline')
@@ -344,6 +352,74 @@ describe('ConnectivityManager', () => {
344
352
  expect(result).toBe('recovered')
345
353
  expect(m.getState()).toBe('healthy')
346
354
  })
355
+
356
+ it('probes call resetTransport() on the existing client (sheds stale sockets)', async () => {
357
+ // Regression test for the production bug: the original
358
+ // implementation probed against the existing client without
359
+ // resetting its socket pool. The pool kept stale half-open sockets
360
+ // from before the outage, probes failed on those sockets, system
361
+ // stayed offline.
362
+ //
363
+ // The fix: probe still uses the existing client (so we keep auth
364
+ // state), but resetTransport() is called first to throw away the
365
+ // dispatcher and its stale connections. New transport, same auth.
366
+ const m = new ConnectivityManager(makeLog(), fakeCredentials)
367
+ await m.init()
368
+ const client = m.getClient() as any
369
+
370
+ // Sanity: the mock client has a resetTransport method.
371
+ expect(typeof client.resetTransport).toBe('function')
372
+
373
+ // Trigger degraded.
374
+ const { TimeoutError } = await import('august-yale')
375
+ await m.execute('test', async () => { throw new TimeoutError('boom') })
376
+ expect(m.getState()).toBe('degraded')
377
+
378
+ // Advance past the first backoff: probe runs, default mock has
379
+ // .locks() resolving, recovery completes.
380
+ await vi.advanceTimersByTimeAsync(7_000)
381
+ expect(m.getState()).toBe('healthy')
382
+
383
+ // The probe must have called resetTransport on the existing
384
+ // client. This is the structural assertion that locks in the fix.
385
+ expect(client.resetTransport).toHaveBeenCalled()
386
+ // The same client instance is still in place (we did NOT
387
+ // construct a new client — this distinguishes the resetTransport
388
+ // approach from the previous fresh-client approach).
389
+ expect(m.getClient()).toBe(client)
390
+ })
391
+
392
+ it('failed probe leaves the existing client in place (no leak, no rotation)', async () => {
393
+ // With resetTransport(), failed probes don't construct a temp
394
+ // client, so there's nothing to leak. The existing client stays
395
+ // the current client throughout the offline period — only its
396
+ // transport gets recycled, not the August instance itself.
397
+ const m = new ConnectivityManager(makeLog(), fakeCredentials)
398
+ await m.init()
399
+ const client = m.getClient() as any
400
+
401
+ const { TimeoutError } = await import('august-yale')
402
+
403
+ // Make the client's .locks() always fail.
404
+ client.locks = vi.fn().mockImplementation(() => new Promise((_, reject) =>
405
+ setTimeout(() => reject(new TimeoutError('still down')), 100),
406
+ ))
407
+
408
+ // Trigger degraded.
409
+ await m.execute('test', async () => { throw new TimeoutError('boom') })
410
+ expect(m.getState()).toBe('degraded')
411
+
412
+ // Advance past first probe → offline.
413
+ await vi.advanceTimersByTimeAsync(15_000)
414
+ expect(m.getState()).toBe('offline')
415
+
416
+ // The existing client is still the current client; nothing got
417
+ // destroyed or replaced.
418
+ expect(m.getClient()).toBe(client)
419
+ expect(client.destroy).not.toHaveBeenCalled()
420
+ // resetTransport was called for each probe attempt.
421
+ expect(client.resetTransport).toHaveBeenCalled()
422
+ })
347
423
  })
348
424
 
349
425
  describe('onPubNubReconnect — fast recovery', () => {
@@ -351,21 +427,23 @@ describe('ConnectivityManager', () => {
351
427
  const onClientChanged = vi.fn()
352
428
  const m = new ConnectivityManager(makeLog(), fakeCredentials, onClientChanged)
353
429
  await m.init()
354
- const client = m.getClient() as any
355
430
 
356
431
  const { TimeoutError } = await import('august-yale')
357
432
  await m.execute('test', async () => { throw new TimeoutError('boom') })
358
433
  expect(m.getState()).toBe('degraded')
359
434
 
360
- client.locks.mockResolvedValue({})
435
+ // PubNub reconnect → immediate probe. Default mock has .locks()
436
+ // resolving, so the probe (which calls resetTransport on the
437
+ // existing client and then locks()) succeeds.
361
438
  m.onPubNubReconnect()
362
439
 
363
440
  // Probe runs synchronously inside onPubNubReconnect's microtask;
364
441
  // need to flush promises but no timer advance required.
365
442
  await vi.runAllTimersAsync()
366
443
  expect(m.getState()).toBe('healthy')
367
- // Initial build + rebuild after successful probe.
368
- expect(onClientChanged).toHaveBeenCalledTimes(2)
444
+ // onClientChanged was only called once (at init). Recovery via
445
+ // resetTransport does not swap the client instance.
446
+ expect(onClientChanged).toHaveBeenCalledTimes(1)
369
447
  })
370
448
 
371
449
  it('is a no-op when already healthy', async () => {
@@ -446,4 +524,67 @@ describe('ConnectivityManager', () => {
446
524
  expect(good).toHaveBeenCalledTimes(1)
447
525
  })
448
526
  })
527
+
528
+ describe('offline heartbeat', () => {
529
+ // Helper: drive the manager into offline state and capture the log.
530
+ async function getOfflineWithLog() {
531
+ const log = makeLog()
532
+ const m = new ConnectivityManager(log, fakeCredentials)
533
+ await m.init()
534
+ const client = m.getClient() as any
535
+
536
+ const { TimeoutError } = await import('august-yale')
537
+
538
+ // Make every probe fail by making the existing client's .locks
539
+ // always reject. With the resetTransport-based design, probes
540
+ // reuse this client across attempts, so a single mockImplementation
541
+ // covers every probe in the offline period.
542
+ client.locks = vi.fn().mockImplementation(() => new Promise((_resolve, reject) =>
543
+ setTimeout(() => reject(new TimeoutError('still down')), 100),
544
+ ))
545
+
546
+ // Trigger degraded.
547
+ await m.execute('test', async () => { throw new TimeoutError('boom') })
548
+
549
+ // Drive past first probe so we land in offline.
550
+ await vi.advanceTimersByTimeAsync(15_000)
551
+ return { m, log, client }
552
+ }
553
+
554
+ it('emits a heartbeat after one hour of being offline', async () => {
555
+ const { log } = await getOfflineWithLog()
556
+
557
+ // Less than an hour: no heartbeat yet.
558
+ await vi.advanceTimersByTimeAsync(30 * 60_000)
559
+ const beforeHour = log._calls.filter(c => c.msg.includes('still offline')).length
560
+ expect(beforeHour).toBe(0)
561
+
562
+ // Past one hour: heartbeat fires. Probes are still failing in the
563
+ // background, so we filter for the heartbeat-specific message.
564
+ await vi.advanceTimersByTimeAsync(35 * 60_000)
565
+ const afterHour = log._calls.filter(c => c.msg.includes('still offline')).length
566
+ expect(afterHour).toBeGreaterThan(0)
567
+ // Heartbeat content includes attempt count and last failure.
568
+ const heartbeatLine = log._calls.find(c => c.msg.includes('still offline'))
569
+ expect(heartbeatLine?.msg).toMatch(/\d+ probe attempts?/)
570
+ expect(heartbeatLine?.msg).toContain('last failure')
571
+ })
572
+
573
+ it('stops emitting heartbeats after recovery', async () => {
574
+ const { m, log, client } = await getOfflineWithLog()
575
+
576
+ // Allow recovery on the next probe by making .locks resolve again.
577
+ client.locks = vi.fn().mockResolvedValue({})
578
+
579
+ // Skip ahead enough for a probe to fire and succeed.
580
+ await vi.advanceTimersByTimeAsync(60 * 1000)
581
+ expect(m.getState()).toBe('healthy')
582
+
583
+ const beforeIdle = log._calls.filter(c => c.msg.includes('still offline')).length
584
+ // Advance two more hours — should NOT see new heartbeat lines.
585
+ await vi.advanceTimersByTimeAsync(2 * 60 * 60_000)
586
+ const afterIdle = log._calls.filter(c => c.msg.includes('still offline')).length
587
+ expect(afterIdle).toBe(beforeIdle)
588
+ })
589
+ })
449
590
  })