@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.
- package/.github/scripts/compute-version-override.cjs +51 -0
- package/CHANGELOG.md +9 -0
- package/dist/connectivity-manager.d.ts +7 -0
- package/dist/connectivity-manager.d.ts.map +1 -1
- package/dist/connectivity-manager.js +111 -12
- package/dist/connectivity-manager.js.map +1 -1
- package/dist/package.json +3 -3
- package/docs/variables/default.html +1 -1
- package/package.json +3 -3
- package/test/connectivity-manager.test.ts +155 -14
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
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
|
-
|
|
238
|
+
client.locks(),
|
|
177
239
|
new Promise((_resolve, reject) => setTimeout(() => reject(new TimeoutError('probe timed out')), PROBE_TIMEOUT_MS)),
|
|
178
240
|
]);
|
|
179
|
-
|
|
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":"AA+CA,OAAO,MAAM,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI3F;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AAErF,8EAA8E;AAC9E,MAAM,gBAAgB,GAAG,KAAK,CAAA;
|
|
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.
|
|
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.
|
|
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">=></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/
|
|
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">=></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.
|
|
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.
|
|
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
|
|
294
|
-
|
|
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 →
|
|
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(
|
|
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
|
-
//
|
|
314
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
368
|
-
|
|
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
|
})
|