homebridge-myleviton 3.0.0
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/LICENSE +202 -0
- package/README.md +112 -0
- package/config.schema.json +136 -0
- package/dist/api/cache.d.ts +108 -0
- package/dist/api/cache.d.ts.map +1 -0
- package/dist/api/cache.js +206 -0
- package/dist/api/cache.js.map +1 -0
- package/dist/api/circuit-breaker.d.ts +118 -0
- package/dist/api/circuit-breaker.d.ts.map +1 -0
- package/dist/api/circuit-breaker.js +223 -0
- package/dist/api/circuit-breaker.js.map +1 -0
- package/dist/api/client.d.ts +116 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +358 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/index.d.ts +23 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +47 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/persistence.d.ts +107 -0
- package/dist/api/persistence.d.ts.map +1 -0
- package/dist/api/persistence.js +285 -0
- package/dist/api/persistence.js.map +1 -0
- package/dist/api/rate-limiter.d.ts +102 -0
- package/dist/api/rate-limiter.d.ts.map +1 -0
- package/dist/api/rate-limiter.js +173 -0
- package/dist/api/rate-limiter.js.map +1 -0
- package/dist/api/request-queue.d.ts +104 -0
- package/dist/api/request-queue.d.ts.map +1 -0
- package/dist/api/request-queue.js +223 -0
- package/dist/api/request-queue.js.map +1 -0
- package/dist/api/websocket.d.ts +116 -0
- package/dist/api/websocket.d.ts.map +1 -0
- package/dist/api/websocket.js +319 -0
- package/dist/api/websocket.js.map +1 -0
- package/dist/errors/index.d.ts +182 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +273 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +139 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +664 -0
- package/dist/platform.js.map +1 -0
- package/dist/types/index.d.ts +225 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +34 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +52 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +103 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +184 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +56 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +141 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/sanitizers.d.ts +37 -0
- package/dist/utils/sanitizers.d.ts.map +1 -0
- package/dist/utils/sanitizers.js +128 -0
- package/dist/utils/sanitizers.js.map +1 -0
- package/dist/utils/validators.d.ts +51 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +243 -0
- package/dist/utils/validators.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Response caching with TTL and LRU eviction
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.ResponseCache = exports.DEFAULT_CACHE_CONFIG = void 0;
|
|
12
|
+
exports.getResponseCache = getResponseCache;
|
|
13
|
+
exports.resetGlobalCache = resetGlobalCache;
|
|
14
|
+
/**
|
|
15
|
+
* Default cache configuration
|
|
16
|
+
*/
|
|
17
|
+
exports.DEFAULT_CACHE_CONFIG = {
|
|
18
|
+
ttlMs: 2000, // 2 seconds
|
|
19
|
+
maxSize: 1000,
|
|
20
|
+
updateOnAccess: false,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Response cache with TTL and optional LRU eviction
|
|
24
|
+
*/
|
|
25
|
+
class ResponseCache {
|
|
26
|
+
cache = new Map();
|
|
27
|
+
ttlMs;
|
|
28
|
+
maxSize;
|
|
29
|
+
updateOnAccess;
|
|
30
|
+
// Metrics
|
|
31
|
+
hits = 0;
|
|
32
|
+
misses = 0;
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
const merged = { ...exports.DEFAULT_CACHE_CONFIG, ...config };
|
|
35
|
+
this.ttlMs = merged.ttlMs;
|
|
36
|
+
this.maxSize = merged.maxSize;
|
|
37
|
+
this.updateOnAccess = merged.updateOnAccess ?? false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if entry is expired
|
|
41
|
+
*/
|
|
42
|
+
isExpired(entry) {
|
|
43
|
+
return Date.now() - entry.timestamp > this.ttlMs;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Evict oldest entries if over capacity
|
|
47
|
+
*/
|
|
48
|
+
evictIfNeeded() {
|
|
49
|
+
while (this.cache.size >= this.maxSize) {
|
|
50
|
+
// Remove oldest entry (first in map)
|
|
51
|
+
const oldestKey = this.cache.keys().next().value;
|
|
52
|
+
if (oldestKey) {
|
|
53
|
+
this.cache.delete(oldestKey);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get cached value if still valid
|
|
62
|
+
* @returns Cached data or null if expired/missing
|
|
63
|
+
*/
|
|
64
|
+
get(key) {
|
|
65
|
+
const entry = this.cache.get(key);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
this.misses++;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (this.isExpired(entry)) {
|
|
71
|
+
this.cache.delete(key);
|
|
72
|
+
this.misses++;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
this.hits++;
|
|
76
|
+
// Optionally update timestamp on access (LRU-style)
|
|
77
|
+
if (this.updateOnAccess) {
|
|
78
|
+
entry.timestamp = Date.now();
|
|
79
|
+
// Move to end of map for LRU ordering
|
|
80
|
+
this.cache.delete(key);
|
|
81
|
+
this.cache.set(key, entry);
|
|
82
|
+
}
|
|
83
|
+
return entry.data;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Set cache value
|
|
87
|
+
*/
|
|
88
|
+
set(key, data) {
|
|
89
|
+
this.evictIfNeeded();
|
|
90
|
+
this.cache.set(key, {
|
|
91
|
+
data,
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if key exists and is not expired
|
|
97
|
+
*/
|
|
98
|
+
has(key) {
|
|
99
|
+
const entry = this.cache.get(key);
|
|
100
|
+
if (!entry) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (this.isExpired(entry)) {
|
|
104
|
+
this.cache.delete(key);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Delete a specific key
|
|
111
|
+
*/
|
|
112
|
+
delete(key) {
|
|
113
|
+
return this.cache.delete(key);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clear expired entries
|
|
117
|
+
*/
|
|
118
|
+
clearExpired() {
|
|
119
|
+
let cleared = 0;
|
|
120
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
121
|
+
if (this.isExpired(entry)) {
|
|
122
|
+
this.cache.delete(key);
|
|
123
|
+
cleared++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return cleared;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear all cache entries
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.cache.clear();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get number of entries
|
|
136
|
+
*/
|
|
137
|
+
get size() {
|
|
138
|
+
return this.cache.size;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get cache hit ratio
|
|
142
|
+
*/
|
|
143
|
+
get hitRatio() {
|
|
144
|
+
const total = this.hits + this.misses;
|
|
145
|
+
return total === 0 ? 0 : this.hits / total;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get cache statistics
|
|
149
|
+
*/
|
|
150
|
+
getStats() {
|
|
151
|
+
return {
|
|
152
|
+
size: this.size,
|
|
153
|
+
maxSize: this.maxSize,
|
|
154
|
+
ttlMs: this.ttlMs,
|
|
155
|
+
hits: this.hits,
|
|
156
|
+
misses: this.misses,
|
|
157
|
+
hitRatio: this.hitRatio,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Reset statistics
|
|
162
|
+
*/
|
|
163
|
+
resetStats() {
|
|
164
|
+
this.hits = 0;
|
|
165
|
+
this.misses = 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get all keys
|
|
169
|
+
*/
|
|
170
|
+
keys() {
|
|
171
|
+
return Array.from(this.cache.keys());
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get or set a value using a factory function
|
|
175
|
+
*/
|
|
176
|
+
async getOrSet(key, factory) {
|
|
177
|
+
const cached = this.get(key);
|
|
178
|
+
if (cached !== null) {
|
|
179
|
+
return cached;
|
|
180
|
+
}
|
|
181
|
+
const data = await factory();
|
|
182
|
+
this.set(key, data);
|
|
183
|
+
return data;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.ResponseCache = ResponseCache;
|
|
187
|
+
/**
|
|
188
|
+
* Global response cache instance
|
|
189
|
+
*/
|
|
190
|
+
let globalCache = null;
|
|
191
|
+
/**
|
|
192
|
+
* Get or create the global cache
|
|
193
|
+
*/
|
|
194
|
+
function getResponseCache(config) {
|
|
195
|
+
if (!globalCache) {
|
|
196
|
+
globalCache = new ResponseCache(config);
|
|
197
|
+
}
|
|
198
|
+
return globalCache;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Reset the global cache (for testing)
|
|
202
|
+
*/
|
|
203
|
+
function resetGlobalCache() {
|
|
204
|
+
globalCache = null;
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/api/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Circuit breaker pattern for API resilience
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Circuit breaker states
|
|
11
|
+
*/
|
|
12
|
+
export declare enum CircuitState {
|
|
13
|
+
/** Normal operation - requests flow through */
|
|
14
|
+
CLOSED = "CLOSED",
|
|
15
|
+
/** Circuit tripped - requests fail immediately */
|
|
16
|
+
OPEN = "OPEN",
|
|
17
|
+
/** Testing if service recovered */
|
|
18
|
+
HALF_OPEN = "HALF_OPEN"
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Circuit breaker configuration
|
|
22
|
+
*/
|
|
23
|
+
export interface CircuitBreakerConfig {
|
|
24
|
+
/** Number of failures before opening circuit */
|
|
25
|
+
failureThreshold: number;
|
|
26
|
+
/** Time in ms before trying half-open */
|
|
27
|
+
resetTimeout: number;
|
|
28
|
+
/** Max requests allowed in half-open state */
|
|
29
|
+
halfOpenMax: number;
|
|
30
|
+
/** Window for counting failures (ms) */
|
|
31
|
+
failureWindow?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Default circuit breaker configuration
|
|
35
|
+
*/
|
|
36
|
+
export declare const DEFAULT_CIRCUIT_BREAKER_CONFIG: CircuitBreakerConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Circuit breaker status
|
|
39
|
+
*/
|
|
40
|
+
export interface CircuitBreakerStatus {
|
|
41
|
+
state: CircuitState;
|
|
42
|
+
failures: number;
|
|
43
|
+
successes: number;
|
|
44
|
+
lastFailureTime: number | null;
|
|
45
|
+
halfOpenRequests: number;
|
|
46
|
+
isOpen: boolean;
|
|
47
|
+
remainingResetTime: number | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Circuit breaker implementation for API resilience
|
|
51
|
+
* Prevents cascading failures when the Leviton API is down
|
|
52
|
+
*/
|
|
53
|
+
export declare class CircuitBreaker {
|
|
54
|
+
private readonly failureThreshold;
|
|
55
|
+
private readonly resetTimeout;
|
|
56
|
+
private readonly halfOpenMax;
|
|
57
|
+
private readonly failureWindow;
|
|
58
|
+
private _state;
|
|
59
|
+
private failures;
|
|
60
|
+
private successes;
|
|
61
|
+
private lastFailureTime;
|
|
62
|
+
private halfOpenRequests;
|
|
63
|
+
private failureTimestamps;
|
|
64
|
+
constructor(config?: Partial<CircuitBreakerConfig>);
|
|
65
|
+
/**
|
|
66
|
+
* Current circuit state
|
|
67
|
+
*/
|
|
68
|
+
get state(): CircuitState;
|
|
69
|
+
/**
|
|
70
|
+
* Check if circuit is open
|
|
71
|
+
*/
|
|
72
|
+
get isOpen(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Clean up old failure timestamps
|
|
75
|
+
*/
|
|
76
|
+
private cleanupFailures;
|
|
77
|
+
/**
|
|
78
|
+
* Check if circuit allows requests
|
|
79
|
+
*/
|
|
80
|
+
canRequest(): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Record a successful request
|
|
83
|
+
*/
|
|
84
|
+
recordSuccess(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Record a failed request
|
|
87
|
+
*/
|
|
88
|
+
recordFailure(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Track half-open request
|
|
91
|
+
*/
|
|
92
|
+
trackHalfOpenRequest(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Reset the circuit breaker to closed state
|
|
95
|
+
*/
|
|
96
|
+
reset(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Get current circuit breaker status
|
|
99
|
+
*/
|
|
100
|
+
getStatus(): CircuitBreakerStatus;
|
|
101
|
+
/**
|
|
102
|
+
* Execute a function with circuit breaker protection
|
|
103
|
+
*/
|
|
104
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
105
|
+
/**
|
|
106
|
+
* Create a wrapped version of a function with circuit breaker protection
|
|
107
|
+
*/
|
|
108
|
+
wrap<T extends unknown[], R>(fn: (...args: T) => Promise<R>): (...args: T) => Promise<R>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get or create the global circuit breaker
|
|
112
|
+
*/
|
|
113
|
+
export declare function getCircuitBreaker(config?: Partial<CircuitBreakerConfig>): CircuitBreaker;
|
|
114
|
+
/**
|
|
115
|
+
* Reset the global circuit breaker (for testing)
|
|
116
|
+
*/
|
|
117
|
+
export declare function resetGlobalCircuitBreaker(): void;
|
|
118
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/api/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;GAEG;AACH,oBAAY,YAAY;IACtB,+CAA+C;IAC/C,MAAM,WAAW;IACjB,kDAAkD;IAClD,IAAI,SAAS;IACb,mCAAmC;IACnC,SAAS,cAAc;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAA;IACxB,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAA;IACpB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAA;IACnB,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,8BAA8B,EAAE,oBAK5C,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,YAAY,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAEtC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAe;gBAE5B,MAAM,GAAE,OAAO,CAAC,oBAAoB,CAAM;IAQtD;;OAEG;IACH,IAAI,KAAK,IAAI,YAAY,CAExB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,UAAU,IAAI,OAAO;IAwBrB;;OAEG;IACH,aAAa,IAAI,IAAI;IAarB;;OAEG;IACH,aAAa,IAAI,IAAI;IAkBrB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAM5B;;OAEG;IACH,KAAK,IAAI,IAAI;IASb;;OAEG;IACH,SAAS,IAAI,oBAAoB;IAmBjC;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAoBlD;;OAEG;IACH,IAAI,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC;CAGzF;AAOD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,cAAc,CAKxF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Circuit breaker pattern for API resilience
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.CircuitBreaker = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.CircuitState = void 0;
|
|
12
|
+
exports.getCircuitBreaker = getCircuitBreaker;
|
|
13
|
+
exports.resetGlobalCircuitBreaker = resetGlobalCircuitBreaker;
|
|
14
|
+
const errors_1 = require("../errors");
|
|
15
|
+
/**
|
|
16
|
+
* Circuit breaker states
|
|
17
|
+
*/
|
|
18
|
+
var CircuitState;
|
|
19
|
+
(function (CircuitState) {
|
|
20
|
+
/** Normal operation - requests flow through */
|
|
21
|
+
CircuitState["CLOSED"] = "CLOSED";
|
|
22
|
+
/** Circuit tripped - requests fail immediately */
|
|
23
|
+
CircuitState["OPEN"] = "OPEN";
|
|
24
|
+
/** Testing if service recovered */
|
|
25
|
+
CircuitState["HALF_OPEN"] = "HALF_OPEN";
|
|
26
|
+
})(CircuitState || (exports.CircuitState = CircuitState = {}));
|
|
27
|
+
/**
|
|
28
|
+
* Default circuit breaker configuration
|
|
29
|
+
*/
|
|
30
|
+
exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
31
|
+
failureThreshold: 5,
|
|
32
|
+
resetTimeout: 30000,
|
|
33
|
+
halfOpenMax: 3,
|
|
34
|
+
failureWindow: 60000,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Circuit breaker implementation for API resilience
|
|
38
|
+
* Prevents cascading failures when the Leviton API is down
|
|
39
|
+
*/
|
|
40
|
+
class CircuitBreaker {
|
|
41
|
+
failureThreshold;
|
|
42
|
+
resetTimeout;
|
|
43
|
+
halfOpenMax;
|
|
44
|
+
failureWindow;
|
|
45
|
+
_state = CircuitState.CLOSED;
|
|
46
|
+
failures = 0;
|
|
47
|
+
successes = 0;
|
|
48
|
+
lastFailureTime = null;
|
|
49
|
+
halfOpenRequests = 0;
|
|
50
|
+
failureTimestamps = [];
|
|
51
|
+
constructor(config = {}) {
|
|
52
|
+
const merged = { ...exports.DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config };
|
|
53
|
+
this.failureThreshold = merged.failureThreshold;
|
|
54
|
+
this.resetTimeout = merged.resetTimeout;
|
|
55
|
+
this.halfOpenMax = merged.halfOpenMax;
|
|
56
|
+
this.failureWindow = merged.failureWindow ?? 60000;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Current circuit state
|
|
60
|
+
*/
|
|
61
|
+
get state() {
|
|
62
|
+
return this._state;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if circuit is open
|
|
66
|
+
*/
|
|
67
|
+
get isOpen() {
|
|
68
|
+
return this._state === CircuitState.OPEN;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clean up old failure timestamps
|
|
72
|
+
*/
|
|
73
|
+
cleanupFailures() {
|
|
74
|
+
const cutoff = Date.now() - this.failureWindow;
|
|
75
|
+
this.failureTimestamps = this.failureTimestamps.filter(ts => ts > cutoff);
|
|
76
|
+
this.failures = this.failureTimestamps.length;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if circuit allows requests
|
|
80
|
+
*/
|
|
81
|
+
canRequest() {
|
|
82
|
+
if (this._state === CircuitState.CLOSED) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (this._state === CircuitState.OPEN) {
|
|
86
|
+
// Check if enough time has passed to try again
|
|
87
|
+
if (this.lastFailureTime && (Date.now() - this.lastFailureTime) >= this.resetTimeout) {
|
|
88
|
+
this._state = CircuitState.HALF_OPEN;
|
|
89
|
+
this.halfOpenRequests = 0;
|
|
90
|
+
this.successes = 0;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (this._state === CircuitState.HALF_OPEN) {
|
|
96
|
+
// Allow limited requests in half-open state
|
|
97
|
+
return this.halfOpenRequests < this.halfOpenMax;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Record a successful request
|
|
103
|
+
*/
|
|
104
|
+
recordSuccess() {
|
|
105
|
+
if (this._state === CircuitState.HALF_OPEN) {
|
|
106
|
+
this.successes++;
|
|
107
|
+
// After enough successes in half-open, close the circuit
|
|
108
|
+
if (this.successes >= this.halfOpenMax) {
|
|
109
|
+
this.reset();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (this._state === CircuitState.CLOSED) {
|
|
113
|
+
// Gradually reduce failure count on success
|
|
114
|
+
this.cleanupFailures();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Record a failed request
|
|
119
|
+
*/
|
|
120
|
+
recordFailure() {
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
this.lastFailureTime = now;
|
|
123
|
+
this.failureTimestamps.push(now);
|
|
124
|
+
if (this._state === CircuitState.HALF_OPEN) {
|
|
125
|
+
// Any failure in half-open state opens the circuit again
|
|
126
|
+
this._state = CircuitState.OPEN;
|
|
127
|
+
this.halfOpenRequests = 0;
|
|
128
|
+
this.successes = 0;
|
|
129
|
+
}
|
|
130
|
+
else if (this._state === CircuitState.CLOSED) {
|
|
131
|
+
this.cleanupFailures();
|
|
132
|
+
if (this.failures >= this.failureThreshold) {
|
|
133
|
+
this._state = CircuitState.OPEN;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Track half-open request
|
|
139
|
+
*/
|
|
140
|
+
trackHalfOpenRequest() {
|
|
141
|
+
if (this._state === CircuitState.HALF_OPEN) {
|
|
142
|
+
this.halfOpenRequests++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Reset the circuit breaker to closed state
|
|
147
|
+
*/
|
|
148
|
+
reset() {
|
|
149
|
+
this._state = CircuitState.CLOSED;
|
|
150
|
+
this.failures = 0;
|
|
151
|
+
this.successes = 0;
|
|
152
|
+
this.lastFailureTime = null;
|
|
153
|
+
this.halfOpenRequests = 0;
|
|
154
|
+
this.failureTimestamps = [];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get current circuit breaker status
|
|
158
|
+
*/
|
|
159
|
+
getStatus() {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
let remainingResetTime = null;
|
|
162
|
+
if (this._state === CircuitState.OPEN && this.lastFailureTime) {
|
|
163
|
+
remainingResetTime = Math.max(0, this.resetTimeout - (now - this.lastFailureTime));
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
state: this._state,
|
|
167
|
+
failures: this.failures,
|
|
168
|
+
successes: this.successes,
|
|
169
|
+
lastFailureTime: this.lastFailureTime,
|
|
170
|
+
halfOpenRequests: this.halfOpenRequests,
|
|
171
|
+
isOpen: this.isOpen,
|
|
172
|
+
remainingResetTime,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Execute a function with circuit breaker protection
|
|
177
|
+
*/
|
|
178
|
+
async execute(fn) {
|
|
179
|
+
if (!this.canRequest()) {
|
|
180
|
+
const status = this.getStatus();
|
|
181
|
+
throw new errors_1.CircuitBreakerError(status.remainingResetTime ?? this.resetTimeout);
|
|
182
|
+
}
|
|
183
|
+
if (this._state === CircuitState.HALF_OPEN) {
|
|
184
|
+
this.trackHalfOpenRequest();
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const result = await fn();
|
|
188
|
+
this.recordSuccess();
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.recordFailure();
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create a wrapped version of a function with circuit breaker protection
|
|
198
|
+
*/
|
|
199
|
+
wrap(fn) {
|
|
200
|
+
return (...args) => this.execute(() => fn(...args));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
204
|
+
/**
|
|
205
|
+
* Global circuit breaker instance
|
|
206
|
+
*/
|
|
207
|
+
let globalCircuitBreaker = null;
|
|
208
|
+
/**
|
|
209
|
+
* Get or create the global circuit breaker
|
|
210
|
+
*/
|
|
211
|
+
function getCircuitBreaker(config) {
|
|
212
|
+
if (!globalCircuitBreaker) {
|
|
213
|
+
globalCircuitBreaker = new CircuitBreaker(config);
|
|
214
|
+
}
|
|
215
|
+
return globalCircuitBreaker;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Reset the global circuit breaker (for testing)
|
|
219
|
+
*/
|
|
220
|
+
function resetGlobalCircuitBreaker() {
|
|
221
|
+
globalCircuitBreaker = null;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/api/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Leviton API HTTP client
|
|
8
|
+
*/
|
|
9
|
+
import { RateLimiter } from './rate-limiter';
|
|
10
|
+
import { CircuitBreaker } from './circuit-breaker';
|
|
11
|
+
import { ResponseCache } from './cache';
|
|
12
|
+
import type { DeviceInfo, DeviceStatus, LoginResponse, ResidentialPermission, ResidentialAccount, Residence, PowerState } from '../types';
|
|
13
|
+
/**
|
|
14
|
+
* API configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface ApiClientConfig {
|
|
17
|
+
/** Base URL for Leviton API */
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
/** Request timeout in ms */
|
|
20
|
+
timeout: number;
|
|
21
|
+
/** Whether to use response caching */
|
|
22
|
+
useCache: boolean;
|
|
23
|
+
/** Cache TTL in ms */
|
|
24
|
+
cacheTtl: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default API configuration
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_API_CONFIG: ApiClientConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Leviton API client
|
|
32
|
+
*/
|
|
33
|
+
export declare class LevitonApiClient {
|
|
34
|
+
private readonly config;
|
|
35
|
+
private readonly rateLimiter;
|
|
36
|
+
private readonly circuitBreaker;
|
|
37
|
+
private readonly cache;
|
|
38
|
+
private readonly deduplicator;
|
|
39
|
+
constructor(config?: Partial<ApiClientConfig>);
|
|
40
|
+
/**
|
|
41
|
+
* Make an API request with all protections
|
|
42
|
+
*/
|
|
43
|
+
private request;
|
|
44
|
+
/**
|
|
45
|
+
* Execute the actual request
|
|
46
|
+
*/
|
|
47
|
+
private executeRequest;
|
|
48
|
+
/**
|
|
49
|
+
* Login and get authentication token
|
|
50
|
+
*/
|
|
51
|
+
login(email: string, password: string, debugLog?: (msg: string) => void): Promise<LoginResponse>;
|
|
52
|
+
/**
|
|
53
|
+
* Get residential permissions for a person
|
|
54
|
+
*/
|
|
55
|
+
getResidentialPermissions(personId: string, token: string, debugLog?: (msg: string) => void): Promise<ResidentialPermission[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Get residential account details
|
|
58
|
+
*/
|
|
59
|
+
getResidentialAccount(accountId: string, token: string, debugLog?: (msg: string) => void): Promise<ResidentialAccount>;
|
|
60
|
+
/**
|
|
61
|
+
* Get residences using v2 API
|
|
62
|
+
*/
|
|
63
|
+
getResidences(residenceObjectId: string, token: string, debugLog?: (msg: string) => void): Promise<Residence[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Get IoT switches for a residence
|
|
66
|
+
*/
|
|
67
|
+
getDevices(residenceId: string, token: string, debugLog?: (msg: string) => void): Promise<DeviceInfo[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Get status of a specific device
|
|
70
|
+
*/
|
|
71
|
+
getDeviceStatus(deviceId: string, token: string, debugLog?: (msg: string) => void): Promise<DeviceStatus>;
|
|
72
|
+
/**
|
|
73
|
+
* Update device state
|
|
74
|
+
*/
|
|
75
|
+
setDeviceState(deviceId: string, token: string, state: {
|
|
76
|
+
power?: PowerState;
|
|
77
|
+
brightness?: number;
|
|
78
|
+
}, debugLog?: (msg: string) => void): Promise<DeviceStatus>;
|
|
79
|
+
/**
|
|
80
|
+
* Set device power
|
|
81
|
+
*/
|
|
82
|
+
setPower(deviceId: string, token: string, power: boolean, debugLog?: (msg: string) => void): Promise<DeviceStatus>;
|
|
83
|
+
/**
|
|
84
|
+
* Set device brightness
|
|
85
|
+
*/
|
|
86
|
+
setBrightness(deviceId: string, token: string, brightness: number, debugLog?: (msg: string) => void): Promise<DeviceStatus>;
|
|
87
|
+
/**
|
|
88
|
+
* Clear response cache
|
|
89
|
+
*/
|
|
90
|
+
clearCache(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Invalidate cache for a specific device
|
|
93
|
+
*/
|
|
94
|
+
invalidateDeviceCache(deviceId: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Get client status
|
|
97
|
+
*/
|
|
98
|
+
getStatus(): {
|
|
99
|
+
circuitBreaker: ReturnType<CircuitBreaker['getStatus']>;
|
|
100
|
+
rateLimiter: ReturnType<RateLimiter['getStatus']>;
|
|
101
|
+
cache: ReturnType<ResponseCache['getStats']>;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Reset all client state (for testing)
|
|
105
|
+
*/
|
|
106
|
+
reset(): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get or create the global API client
|
|
110
|
+
*/
|
|
111
|
+
export declare function getApiClient(config?: Partial<ApiClientConfig>): LevitonApiClient;
|
|
112
|
+
/**
|
|
113
|
+
* Reset the global client (for testing)
|
|
114
|
+
*/
|
|
115
|
+
export declare function resetGlobalClient(): void;
|
|
116
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|