@felixgeelhaar/govee-api-client 3.1.0 โ 3.1.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/README.md +84 -550
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,661 +1,195 @@
|
|
|
1
|
-
# Govee API
|
|
1
|
+
# Govee API Client
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@felixgeelhaar/govee-api-client)
|
|
4
|
+
[](https://www.npmjs.com/package/@felixgeelhaar/govee-api-client)
|
|
5
5
|
[](https://github.com/felixgeelhaar/govee-api-client/actions)
|
|
6
|
-
[](https://snyk.io/test/github/felixgeelhaar/govee-api-client)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://www.typescriptlang.org)
|
|
10
|
+
[](https://docs.npmjs.com/generating-provenance-statements)
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- ๐๏ธ **Domain-Driven Design**: Clean architecture following DDD principles
|
|
14
|
-
- โ
**Runtime Validation**: Zod-based API response validation for production safety
|
|
15
|
-
- โก **Rate Limiting**: High-performance sliding window rate limiter with burst capability
|
|
16
|
-
- ๐ **Retry Logic**: Enterprise-grade retry with exponential backoff, jitter, and circuit breaker
|
|
17
|
-
- ๐ก๏ธ **Error Handling**: Comprehensive error hierarchy with specific error types
|
|
18
|
-
- ๐ **Observability**: Built-in metrics and monitoring for rate limiting and retries
|
|
19
|
-
- ๐ **Logging**: Configurable logging with Pino integration
|
|
20
|
-
- ๐งช **Well Tested**: >95% test coverage with unit and integration tests
|
|
21
|
-
- ๐ **Production Ready**: Enterprise-grade reliability and performance
|
|
12
|
+
A TypeScript client for the [Govee Developer API](https://developer.govee.com). Control your Govee lights, appliances, and other smart devices from code.
|
|
22
13
|
|
|
23
|
-
##
|
|
14
|
+
## Install
|
|
24
15
|
|
|
25
16
|
```bash
|
|
26
17
|
npm install @felixgeelhaar/govee-api-client
|
|
27
18
|
```
|
|
28
19
|
|
|
29
|
-
##
|
|
20
|
+
## Getting Started
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
import {
|
|
33
|
-
GoveeClient,
|
|
34
|
-
Brightness,
|
|
35
|
-
ColorRgb,
|
|
36
|
-
ColorTemperature,
|
|
37
|
-
} from '@felixgeelhaar/govee-api-client';
|
|
22
|
+
You'll need a Govee API key. Get one from the [Govee Developer Platform](https://developer.govee.com).
|
|
38
23
|
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
```typescript
|
|
25
|
+
import { GoveeClient, ColorRgb, Brightness } from '@felixgeelhaar/govee-api-client';
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
// const client = new GoveeClient({ apiKey: 'your-govee-api-key' });
|
|
27
|
+
const client = new GoveeClient({ apiKey: 'your-api-key' });
|
|
44
28
|
|
|
45
|
-
//
|
|
29
|
+
// List your devices
|
|
46
30
|
const devices = await client.getDevices();
|
|
47
|
-
console.log(`Found ${devices.length} devices`);
|
|
48
|
-
|
|
49
|
-
// Find a specific device
|
|
50
|
-
const livingRoomLight = await client.findDeviceByName('Living Room');
|
|
51
|
-
|
|
52
|
-
if (livingRoomLight) {
|
|
53
|
-
// Turn on the light with warm white and 75% brightness
|
|
54
|
-
await client.turnOnWithColorTemperature(
|
|
55
|
-
livingRoomLight.deviceId,
|
|
56
|
-
livingRoomLight.model,
|
|
57
|
-
ColorTemperature.warmWhite(),
|
|
58
|
-
new Brightness(75)
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Configuration
|
|
64
31
|
|
|
65
|
-
|
|
32
|
+
// Find and control a light
|
|
33
|
+
const light = await client.findDeviceByName('Living Room');
|
|
66
34
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Or use a .env file
|
|
74
|
-
echo "GOVEE_API_KEY=your-govee-api-key" > .env
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import { GoveeClient } from '@felixgeelhaar/govee-api-client';
|
|
79
|
-
|
|
80
|
-
// Uses GOVEE_API_KEY environment variable automatically
|
|
81
|
-
const client = new GoveeClient();
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
You can also provide the API key explicitly (not recommended for production):
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
const client = new GoveeClient({
|
|
88
|
-
apiKey: 'your-govee-api-key', // Explicit API key (overrides environment variable)
|
|
89
|
-
});
|
|
35
|
+
if (light) {
|
|
36
|
+
await client.turnOn(light.deviceId, light.model);
|
|
37
|
+
await client.setBrightness(light.deviceId, light.model, new Brightness(75));
|
|
38
|
+
await client.setColor(light.deviceId, light.model, new ColorRgb(255, 120, 50));
|
|
39
|
+
}
|
|
90
40
|
```
|
|
91
41
|
|
|
92
|
-
|
|
42
|
+
You can also set the `GOVEE_API_KEY` environment variable and skip passing it to the constructor:
|
|
93
43
|
|
|
94
44
|
```typescript
|
|
95
|
-
|
|
96
|
-
import { GoveeClient, RetryPolicy } from '@felixgeelhaar/govee-api-client';
|
|
97
|
-
|
|
98
|
-
const client = new GoveeClient({
|
|
99
|
-
// apiKey is optional - uses GOVEE_API_KEY environment variable by default
|
|
100
|
-
timeout: 30000, // Request timeout in milliseconds (default: 30000)
|
|
101
|
-
rateLimit: 95, // Requests per minute (default: 95, with 5 buffer under Govee's limit)
|
|
102
|
-
logger: pino({ level: 'info' }), // Optional logger (silent by default)
|
|
103
|
-
enableRetries: true, // Enable retry functionality (default: false)
|
|
104
|
-
retryPolicy: 'production', // Retry policy preset or custom RetryPolicy instance
|
|
105
|
-
});
|
|
45
|
+
const client = new GoveeClient(); // reads from GOVEE_API_KEY
|
|
106
46
|
```
|
|
107
47
|
|
|
108
|
-
##
|
|
48
|
+
## What You Can Do
|
|
109
49
|
|
|
110
|
-
###
|
|
50
|
+
### Basic Controls
|
|
111
51
|
|
|
112
52
|
```typescript
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Get only controllable devices
|
|
117
|
-
const controllableDevices = await client.getControllableDevices();
|
|
118
|
-
|
|
119
|
-
// Find device by name (case-insensitive)
|
|
120
|
-
const device = await client.findDeviceByName('bedroom');
|
|
121
|
-
|
|
122
|
-
// Find devices by model
|
|
123
|
-
const devices = await client.findDevicesByModel('H6159');
|
|
124
|
-
|
|
125
|
-
// Get device state
|
|
126
|
-
const state = await client.getDeviceState(deviceId, model);
|
|
127
|
-
console.log(`Power: ${state.getPowerState()}`);
|
|
128
|
-
console.log(`Online: ${state.isOnline()}`);
|
|
53
|
+
await client.turnOn(deviceId, model);
|
|
54
|
+
await client.turnOff(deviceId, model);
|
|
55
|
+
await client.setBrightness(deviceId, model, new Brightness(75));
|
|
129
56
|
```
|
|
130
57
|
|
|
131
|
-
###
|
|
58
|
+
### Colors
|
|
132
59
|
|
|
133
60
|
```typescript
|
|
134
|
-
|
|
135
|
-
await client.turnOn(deviceId, model);
|
|
136
|
-
await client.turnOff(deviceId, model);
|
|
137
|
-
await client.setBrightness(deviceId, model, new Brightness(75));
|
|
61
|
+
import { ColorRgb, ColorTemperature } from '@felixgeelhaar/govee-api-client';
|
|
138
62
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
await client.setColor(deviceId, model, red);
|
|
63
|
+
// Set an RGB color
|
|
64
|
+
await client.setColor(deviceId, model, new ColorRgb(255, 0, 0));
|
|
142
65
|
|
|
143
|
-
|
|
144
|
-
await client.setColorTemperature(deviceId, model,
|
|
66
|
+
// Set a color temperature
|
|
67
|
+
await client.setColorTemperature(deviceId, model, ColorTemperature.warmWhite());
|
|
145
68
|
|
|
146
|
-
//
|
|
147
|
-
await client.
|
|
148
|
-
await client.turnOnWithColor(deviceId, model, red, new Brightness(75));
|
|
149
|
-
await client.turnOnWithColorTemperature(deviceId, model, coolWhite, new Brightness(100));
|
|
69
|
+
// Turn on with color and brightness in one call
|
|
70
|
+
await client.turnOnWithColor(deviceId, model, new ColorRgb(0, 255, 0), new Brightness(80));
|
|
150
71
|
```
|
|
151
72
|
|
|
152
|
-
###
|
|
153
|
-
|
|
154
|
-
#### Dynamic Light Scenes
|
|
73
|
+
### Scenes
|
|
155
74
|
|
|
156
75
|
```typescript
|
|
157
76
|
import { LightScene } from '@felixgeelhaar/govee-api-client';
|
|
158
77
|
|
|
159
|
-
//
|
|
78
|
+
// Browse available scenes for a device
|
|
160
79
|
const scenes = await client.getDynamicScenes(deviceId, model);
|
|
161
|
-
console.log(`Available scenes: ${scenes.map(s => s.name).join(', ')}`);
|
|
162
80
|
|
|
163
|
-
//
|
|
164
|
-
await client.setLightScene(deviceId, model, LightScene.sunrise());
|
|
81
|
+
// Apply a built-in scene
|
|
165
82
|
await client.setLightScene(deviceId, model, LightScene.sunset());
|
|
166
|
-
await client.setLightScene(deviceId, model, LightScene.rainbow());
|
|
167
83
|
await client.setLightScene(deviceId, model, LightScene.aurora());
|
|
168
|
-
|
|
169
|
-
// Or set a custom scene
|
|
170
|
-
const customScene = new LightScene(3853, 4280, 'My Scene');
|
|
171
|
-
await client.setLightScene(deviceId, model, customScene);
|
|
172
84
|
```
|
|
173
85
|
|
|
174
|
-
|
|
86
|
+
### Segments (for LED strips and curtain lights)
|
|
175
87
|
|
|
176
88
|
```typescript
|
|
177
89
|
import { SegmentColor, ColorRgb } from '@felixgeelhaar/govee-api-client';
|
|
178
90
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// Set multiple segments at once
|
|
185
|
-
await client.setSegmentColors(deviceId, model, [segment1, segment2, segment3]);
|
|
186
|
-
|
|
187
|
-
// Set brightness for individual segments
|
|
188
|
-
await client.setSegmentBrightness(deviceId, model, [
|
|
189
|
-
{ index: 0, brightness: new Brightness(100) },
|
|
190
|
-
{ index: 1, brightness: new Brightness(75) },
|
|
191
|
-
{ index: 2, brightness: new Brightness(50) },
|
|
91
|
+
await client.setSegmentColors(deviceId, model, [
|
|
92
|
+
new SegmentColor(0, new ColorRgb(255, 0, 0)),
|
|
93
|
+
new SegmentColor(1, new ColorRgb(0, 255, 0)),
|
|
94
|
+
new SegmentColor(2, new ColorRgb(0, 0, 255)),
|
|
192
95
|
]);
|
|
193
96
|
```
|
|
194
97
|
|
|
195
|
-
|
|
98
|
+
### Music Mode
|
|
196
99
|
|
|
197
100
|
```typescript
|
|
198
101
|
import { MusicMode } from '@felixgeelhaar/govee-api-client';
|
|
199
102
|
|
|
200
|
-
|
|
201
|
-
const musicMode = new MusicMode(1, 75); // Mode 1, 75% sensitivity
|
|
202
|
-
await client.setMusicMode(deviceId, model, musicMode);
|
|
203
|
-
|
|
204
|
-
// Music mode without sensitivity (uses device default)
|
|
205
|
-
const basicMode = new MusicMode(2);
|
|
206
|
-
await client.setMusicMode(deviceId, model, basicMode);
|
|
103
|
+
await client.setMusicMode(deviceId, model, new MusicMode(1, 75));
|
|
207
104
|
```
|
|
208
105
|
|
|
209
|
-
|
|
106
|
+
### Toggles
|
|
210
107
|
|
|
211
108
|
```typescript
|
|
212
|
-
|
|
213
|
-
await client.setNightlightToggle(deviceId, model, true); // Enable
|
|
214
|
-
await client.setNightlightToggle(deviceId, model, false); // Disable
|
|
215
|
-
|
|
216
|
-
// Gradient toggle
|
|
109
|
+
await client.setNightlightToggle(deviceId, model, true);
|
|
217
110
|
await client.setGradientToggle(deviceId, model, true);
|
|
218
|
-
|
|
219
|
-
// Preset scenes
|
|
220
|
-
await client.setNightlightScene(deviceId, model, 1); // Scene ID 1
|
|
221
|
-
await client.setPresetScene(deviceId, model, 'cozy'); // Named scene
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Value Objects
|
|
225
|
-
|
|
226
|
-
#### ColorRgb
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
// Create RGB colors
|
|
230
|
-
const red = new ColorRgb(255, 0, 0);
|
|
231
|
-
const blue = ColorRgb.fromHex('#0000FF');
|
|
232
|
-
const green = ColorRgb.fromObject({ r: 0, g: 255, b: 0 });
|
|
233
|
-
|
|
234
|
-
console.log(red.toHex()); // "#ff0000"
|
|
235
|
-
console.log(blue.toString()); // "rgb(0, 0, 255)"
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
#### ColorTemperature
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
// Create color temperatures
|
|
242
|
-
const warm = ColorTemperature.warmWhite(); // 2700K
|
|
243
|
-
const cool = ColorTemperature.coolWhite(); // 6500K
|
|
244
|
-
const daylight = ColorTemperature.daylight(); // 5600K
|
|
245
|
-
const custom = new ColorTemperature(4000);
|
|
246
|
-
|
|
247
|
-
console.log(warm.isWarm()); // true
|
|
248
|
-
console.log(cool.isCool()); // true
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
#### Brightness
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
// Create brightness levels
|
|
255
|
-
const dim = Brightness.dim(); // 25%
|
|
256
|
-
const medium = Brightness.medium(); // 50%
|
|
257
|
-
const bright = Brightness.bright(); // 75%
|
|
258
|
-
const custom = new Brightness(85);
|
|
259
|
-
|
|
260
|
-
console.log(bright.asPercent()); // 0.75
|
|
261
|
-
console.log(custom.isDim()); // false
|
|
111
|
+
await client.setSceneStageToggle(deviceId, model, true);
|
|
262
112
|
```
|
|
263
113
|
|
|
264
|
-
|
|
114
|
+
### Device State
|
|
265
115
|
|
|
266
116
|
```typescript
|
|
267
|
-
|
|
117
|
+
const state = await client.getDeviceState(deviceId, model);
|
|
268
118
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const rainbow = LightScene.rainbow();
|
|
273
|
-
const aurora = LightScene.aurora();
|
|
274
|
-
const candlelight = LightScene.candlelight();
|
|
275
|
-
const nightlight = LightScene.nightlight();
|
|
276
|
-
const romantic = LightScene.romantic();
|
|
277
|
-
const blinking = LightScene.blinking();
|
|
278
|
-
|
|
279
|
-
// Custom scene
|
|
280
|
-
const custom = new LightScene(3853, 4280, 'My Custom Scene');
|
|
281
|
-
|
|
282
|
-
// Scene properties
|
|
283
|
-
console.log(sunrise.name); // "Sunrise"
|
|
284
|
-
console.log(sunrise.id); // 3853
|
|
285
|
-
console.log(sunrise.paramId); // 4280
|
|
119
|
+
console.log(state.getPowerState()); // true / false
|
|
120
|
+
console.log(state.isOnline()); // true / false
|
|
121
|
+
console.log(state.getBrightness()); // 0-100
|
|
286
122
|
```
|
|
287
123
|
|
|
288
|
-
|
|
124
|
+
## Configuration
|
|
289
125
|
|
|
290
126
|
```typescript
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
// Check if segment has brightness
|
|
300
|
-
console.log(segment1.hasBrightness()); // false
|
|
301
|
-
console.log(segment2.hasBrightness()); // true
|
|
302
|
-
|
|
303
|
-
// Access segment properties
|
|
304
|
-
console.log(segment2.index); // 1
|
|
305
|
-
console.log(segment2.color.toHex()); // "#00ff00"
|
|
306
|
-
console.log(segment2.brightness?.level); // 75
|
|
127
|
+
const client = new GoveeClient({
|
|
128
|
+
apiKey: 'your-api-key',
|
|
129
|
+
timeout: 30000, // request timeout in ms (default: 30000)
|
|
130
|
+
rateLimit: 95, // requests per minute (default: 95)
|
|
131
|
+
enableRetries: true, // retry failed requests (default: false)
|
|
132
|
+
retryPolicy: 'production', // 'development', 'testing', or 'production'
|
|
133
|
+
});
|
|
307
134
|
```
|
|
308
135
|
|
|
309
|
-
|
|
136
|
+
### Rate Limiting
|
|
310
137
|
|
|
311
|
-
|
|
312
|
-
import { MusicMode } from '@felixgeelhaar/govee-api-client';
|
|
138
|
+
The client automatically stays within Govee's API rate limits (100 requests/min). By default it allows 95 requests/min, leaving a small buffer.
|
|
313
139
|
|
|
314
|
-
|
|
315
|
-
const mode1 = new MusicMode(1, 75);
|
|
140
|
+
### Retries
|
|
316
141
|
|
|
317
|
-
|
|
318
|
-
const mode2 = new MusicMode(2);
|
|
142
|
+
When enabled, failed requests are retried with exponential backoff. The built-in presets handle common scenarios:
|
|
319
143
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
144
|
+
- **production** โ 3 attempts, circuit breaker enabled
|
|
145
|
+
- **development** โ 5 attempts, circuit breaker disabled
|
|
146
|
+
- **testing** โ 2 attempts, conservative settings
|
|
323
147
|
|
|
324
|
-
|
|
325
|
-
console.log(mode1.modeId); // 1
|
|
326
|
-
console.log(mode1.sensitivity); // 75
|
|
327
|
-
```
|
|
148
|
+
You can also pass a custom `RetryPolicy` instance for full control. See the [examples](docs/EXAMPLES.md) for details.
|
|
328
149
|
|
|
329
150
|
## Error Handling
|
|
330
151
|
|
|
331
|
-
The library provides a comprehensive error hierarchy for different types of failures:
|
|
332
|
-
|
|
333
152
|
```typescript
|
|
334
153
|
import {
|
|
335
154
|
GoveeApiError,
|
|
336
155
|
InvalidApiKeyError,
|
|
337
156
|
RateLimitError,
|
|
338
157
|
NetworkError,
|
|
339
|
-
ValidationError,
|
|
340
158
|
} from '@felixgeelhaar/govee-api-client';
|
|
341
159
|
|
|
342
160
|
try {
|
|
343
|
-
await client.
|
|
161
|
+
await client.turnOn(deviceId, model);
|
|
344
162
|
} catch (error) {
|
|
345
|
-
if (error instanceof
|
|
346
|
-
|
|
347
|
-
console.log('Validation error:', error.message);
|
|
348
|
-
|
|
349
|
-
// Get detailed validation errors
|
|
350
|
-
const details = error.getValidationDetails();
|
|
351
|
-
details.forEach(detail => {
|
|
352
|
-
console.log(` ${detail.path}: ${detail.message}`);
|
|
353
|
-
console.log(` Received: ${JSON.stringify(detail.received)}`);
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Or get a summary string for logging
|
|
357
|
-
console.log('Summary:', error.getValidationSummary());
|
|
358
|
-
} else if (error instanceof InvalidApiKeyError) {
|
|
359
|
-
console.log('API key is invalid or expired');
|
|
360
|
-
console.log(error.getRecommendation());
|
|
163
|
+
if (error instanceof InvalidApiKeyError) {
|
|
164
|
+
console.log('Check your API key');
|
|
361
165
|
} else if (error instanceof RateLimitError) {
|
|
362
|
-
console.log(`
|
|
363
|
-
} else if (error instanceof GoveeApiError) {
|
|
364
|
-
console.log(`API Error: ${error.message}`);
|
|
365
|
-
if (error.isDeviceOffline()) {
|
|
366
|
-
console.log('Device is currently offline');
|
|
367
|
-
}
|
|
166
|
+
console.log(`Too many requests. Retry in ${error.getRetryAfterMs()}ms`);
|
|
368
167
|
} else if (error instanceof NetworkError) {
|
|
369
|
-
console.log(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
168
|
+
console.log('Network issue โ retryable:', error.isRetryable());
|
|
169
|
+
} else if (error instanceof GoveeApiError) {
|
|
170
|
+
console.log('API error:', error.message);
|
|
373
171
|
}
|
|
374
172
|
}
|
|
375
173
|
```
|
|
376
174
|
|
|
377
|
-
### Runtime Validation
|
|
378
|
-
|
|
379
|
-
All API responses are validated at runtime using Zod schemas to ensure data integrity:
|
|
380
|
-
|
|
381
|
-
- **Automatic**: All API calls are validated transparently
|
|
382
|
-
- **Type-safe**: Catches malformed responses before they reach your code
|
|
383
|
-
- **Detailed errors**: `ValidationError` provides specific information about what failed
|
|
384
|
-
- **Production-ready**: Protects against unexpected API changes
|
|
385
|
-
|
|
386
|
-
If you need custom validation, the Zod schemas are exported:
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
import {
|
|
390
|
-
GoveeDevicesResponseSchema,
|
|
391
|
-
GoveeStateResponseSchema,
|
|
392
|
-
GoveeCommandResponseSchema,
|
|
393
|
-
} from '@felixgeelhaar/govee-api-client';
|
|
394
|
-
|
|
395
|
-
// Use for custom validation scenarios
|
|
396
|
-
const result = GoveeDevicesResponseSchema.safeParse(rawApiData);
|
|
397
|
-
if (result.success) {
|
|
398
|
-
console.log('Valid data:', result.data);
|
|
399
|
-
} else {
|
|
400
|
-
console.log('Validation errors:', result.error);
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
## Rate Limiting & Retry Features
|
|
405
|
-
|
|
406
|
-
The client includes enterprise-grade rate limiting and retry capabilities designed for production environments.
|
|
407
|
-
|
|
408
|
-
### Rate Limiting
|
|
409
|
-
|
|
410
|
-
Uses a high-performance sliding window rate limiter that allows bursts up to the limit while maintaining the average rate over time:
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
const client = new GoveeClient({
|
|
414
|
-
apiKey: 'your-api-key',
|
|
415
|
-
rateLimit: 95, // Default: 95 req/min (5 request buffer under Govee's 100/min limit)
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// Monitor rate limiter performance
|
|
419
|
-
const stats = client.getRateLimiterStats();
|
|
420
|
-
console.log(`Current utilization: ${stats.utilizationPercent}%`);
|
|
421
|
-
console.log(`Queue size: ${stats.queueSize}`);
|
|
422
|
-
console.log(`Can execute immediately: ${stats.canExecuteImmediately}`);
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Retry Logic
|
|
426
|
-
|
|
427
|
-
Comprehensive retry functionality with exponential backoff, jitter, and circuit breaker:
|
|
428
|
-
|
|
429
|
-
```typescript
|
|
430
|
-
const client = new GoveeClient({
|
|
431
|
-
apiKey: 'your-api-key',
|
|
432
|
-
enableRetries: true,
|
|
433
|
-
retryPolicy: 'production', // 'development', 'testing', 'production'
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Monitor retry performance
|
|
437
|
-
const retryMetrics = client.getRetryMetrics();
|
|
438
|
-
if (retryMetrics) {
|
|
439
|
-
console.log(`Total retry attempts: ${retryMetrics.totalAttempts}`);
|
|
440
|
-
console.log(`Success rate: ${retryMetrics.successfulRetries}/${retryMetrics.totalAttempts}`);
|
|
441
|
-
console.log(`Circuit breaker state: ${retryMetrics.circuitBreakerState}`);
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### Custom Retry Policies
|
|
446
|
-
|
|
447
|
-
Create custom retry policies for specific requirements:
|
|
448
|
-
|
|
449
|
-
```typescript
|
|
450
|
-
import {
|
|
451
|
-
GoveeClient,
|
|
452
|
-
RetryPolicy,
|
|
453
|
-
RateLimitError,
|
|
454
|
-
NetworkError,
|
|
455
|
-
GoveeApiError,
|
|
456
|
-
} from '@felixgeelhaar/govee-api-client';
|
|
457
|
-
|
|
458
|
-
const customRetryPolicy = new RetryPolicy({
|
|
459
|
-
backoff: {
|
|
460
|
-
type: 'exponential',
|
|
461
|
-
initialDelayMs: 1000,
|
|
462
|
-
maxDelayMs: 30000,
|
|
463
|
-
multiplier: 2.0,
|
|
464
|
-
},
|
|
465
|
-
jitter: {
|
|
466
|
-
type: 'equal',
|
|
467
|
-
factor: 0.1,
|
|
468
|
-
},
|
|
469
|
-
condition: {
|
|
470
|
-
maxAttempts: 3,
|
|
471
|
-
maxTotalTimeMs: 60000,
|
|
472
|
-
retryableStatusCodes: [408, 429, 502, 503, 504],
|
|
473
|
-
retryableErrorTypes: [RateLimitError, NetworkError, GoveeApiError],
|
|
474
|
-
},
|
|
475
|
-
circuitBreaker: {
|
|
476
|
-
enabled: true,
|
|
477
|
-
failureThreshold: 5,
|
|
478
|
-
recoveryTimeoutMs: 30000,
|
|
479
|
-
halfOpenSuccessThreshold: 2,
|
|
480
|
-
},
|
|
481
|
-
enableMetrics: true,
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
const client = new GoveeClient({
|
|
485
|
-
apiKey: 'your-api-key',
|
|
486
|
-
enableRetries: true,
|
|
487
|
-
retryPolicy: customRetryPolicy,
|
|
488
|
-
});
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
### Monitoring & Observability
|
|
492
|
-
|
|
493
|
-
Get comprehensive metrics for monitoring and debugging:
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
// Get complete service statistics
|
|
497
|
-
const serviceStats = client.getServiceStats();
|
|
498
|
-
console.log('Rate Limiter:', serviceStats.rateLimiter);
|
|
499
|
-
console.log('Retry Metrics:', serviceStats.retries);
|
|
500
|
-
console.log('Configuration:', serviceStats.configuration);
|
|
501
|
-
|
|
502
|
-
// Rate limiter specific stats
|
|
503
|
-
const rateLimiterStats = client.getRateLimiterStats();
|
|
504
|
-
console.log({
|
|
505
|
-
currentRequests: rateLimiterStats.currentRequests,
|
|
506
|
-
maxRequests: rateLimiterStats.maxRequests,
|
|
507
|
-
utilizationPercent: rateLimiterStats.utilizationPercent,
|
|
508
|
-
queueSize: rateLimiterStats.queueSize,
|
|
509
|
-
canExecuteImmediately: rateLimiterStats.canExecuteImmediately,
|
|
510
|
-
nextAvailableSlot: rateLimiterStats.nextAvailableSlot,
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// Retry metrics (when retries are enabled)
|
|
514
|
-
const retryMetrics = client.getRetryMetrics();
|
|
515
|
-
if (retryMetrics) {
|
|
516
|
-
console.log({
|
|
517
|
-
totalAttempts: retryMetrics.totalAttempts,
|
|
518
|
-
successfulRetries: retryMetrics.successfulRetries,
|
|
519
|
-
failedRetries: retryMetrics.failedRetries,
|
|
520
|
-
totalRetryTimeMs: retryMetrics.totalRetryTimeMs,
|
|
521
|
-
averageRetryDelayMs: retryMetrics.averageRetryDelayMs,
|
|
522
|
-
circuitBreakerState: retryMetrics.circuitBreakerState,
|
|
523
|
-
lastError: retryMetrics.lastError?.message,
|
|
524
|
-
lastRetryTimestamp: retryMetrics.lastRetryTimestamp,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Reset metrics for clean monitoring periods
|
|
529
|
-
client.resetRetryMetrics();
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Retry Policy Presets
|
|
533
|
-
|
|
534
|
-
The library includes three built-in retry policy presets optimized for different environments:
|
|
535
|
-
|
|
536
|
-
#### Production (Default)
|
|
537
|
-
|
|
538
|
-
- **Max attempts**: 3
|
|
539
|
-
- **Backoff**: Exponential with 1s initial delay, 30s max
|
|
540
|
-
- **Circuit breaker**: Enabled (5 failures to open, 30s recovery)
|
|
541
|
-
- **Jitter**: Equal jitter to prevent thundering herd
|
|
542
|
-
|
|
543
|
-
#### Development
|
|
544
|
-
|
|
545
|
-
- **Max attempts**: 5
|
|
546
|
-
- **Backoff**: Exponential with 500ms initial delay, 15s max
|
|
547
|
-
- **Circuit breaker**: Disabled for easier debugging
|
|
548
|
-
- **Jitter**: Full jitter for maximum randomization
|
|
549
|
-
|
|
550
|
-
#### Testing
|
|
551
|
-
|
|
552
|
-
- **Max attempts**: 2
|
|
553
|
-
- **Backoff**: Exponential with 2s initial delay, 60s max
|
|
554
|
-
- **Circuit breaker**: Enabled with conservative settings
|
|
555
|
-
- **Jitter**: Decorrelated jitter for sophisticated patterns
|
|
556
|
-
|
|
557
|
-
### Performance Characteristics
|
|
558
|
-
|
|
559
|
-
- **Rate Limiter**: High-performance sliding window allows concurrent execution within limits
|
|
560
|
-
- **Memory Efficient**: Automatic cleanup of expired timestamps and bounded queue sizes
|
|
561
|
-
- **Production Ready**: Circuit breaker prevents cascade failures
|
|
562
|
-
- **Observable**: Comprehensive metrics for monitoring and alerting
|
|
563
|
-
|
|
564
|
-
## Domain-Driven Design
|
|
565
|
-
|
|
566
|
-
The library follows DDD principles with clear separation of concerns:
|
|
567
|
-
|
|
568
|
-
- **Entities**: `GoveeDevice`, `DeviceState`, `Command`
|
|
569
|
-
- **Value Objects**: `ColorRgb`, `ColorTemperature`, `Brightness`
|
|
570
|
-
- **Repositories**: `IGoveeDeviceRepository`, `GoveeDeviceRepository`
|
|
571
|
-
- **Services**: `GoveeControlService`
|
|
572
|
-
- **Errors**: Comprehensive error hierarchy
|
|
573
|
-
|
|
574
|
-
## Advanced Usage
|
|
575
|
-
|
|
576
|
-
### Custom Repository
|
|
577
|
-
|
|
578
|
-
```typescript
|
|
579
|
-
import { GoveeControlService } from '@felixgeelhaar/govee-api-client';
|
|
580
|
-
|
|
581
|
-
// Use your own repository implementation
|
|
582
|
-
const service = new GoveeControlService({
|
|
583
|
-
repository: new CustomGoveeRepository(),
|
|
584
|
-
rateLimit: 120,
|
|
585
|
-
});
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
### Command Pattern
|
|
589
|
-
|
|
590
|
-
```typescript
|
|
591
|
-
import { CommandFactory } from '@felixgeelhaar/govee-api-client';
|
|
592
|
-
|
|
593
|
-
// Create commands manually
|
|
594
|
-
const powerOn = CommandFactory.powerOn();
|
|
595
|
-
const setBrightness = CommandFactory.brightness(new Brightness(75));
|
|
596
|
-
|
|
597
|
-
// Send custom commands
|
|
598
|
-
await client.sendCommand(deviceId, model, powerOn);
|
|
599
|
-
await client.sendCommand(deviceId, model, setBrightness);
|
|
600
|
-
```
|
|
601
|
-
|
|
602
175
|
## Requirements
|
|
603
176
|
|
|
604
|
-
- Node.js 20
|
|
605
|
-
-
|
|
177
|
+
- Node.js 20 or higher
|
|
178
|
+
- A [Govee Developer API key](https://developer.govee.com)
|
|
606
179
|
|
|
607
180
|
## Development
|
|
608
181
|
|
|
609
182
|
```bash
|
|
610
|
-
# Install dependencies
|
|
611
183
|
npm install
|
|
612
|
-
|
|
613
|
-
# Build the library
|
|
614
184
|
npm run build
|
|
615
|
-
|
|
616
|
-
# Run tests
|
|
617
185
|
npm test
|
|
618
|
-
|
|
619
|
-
# Run tests with coverage
|
|
620
186
|
npm run test:coverage
|
|
621
|
-
|
|
622
|
-
# Type checking
|
|
623
|
-
npm run lint
|
|
624
187
|
```
|
|
625
188
|
|
|
626
|
-
## License
|
|
627
|
-
|
|
628
|
-
MIT ยฉ [Felix Geelhaar]
|
|
629
|
-
|
|
630
189
|
## Contributing
|
|
631
190
|
|
|
632
|
-
Contributions are welcome!
|
|
633
|
-
|
|
634
|
-
- Code of conduct
|
|
635
|
-
- Development workflow
|
|
636
|
-
- Code standards and architecture
|
|
637
|
-
- Testing requirements
|
|
638
|
-
- Commit guidelines
|
|
639
|
-
- Pull request process
|
|
640
|
-
|
|
641
|
-
For feature requests and discussions, visit our [GitHub Discussions](https://github.com/felixgeelhaar/govee-api-client/discussions).
|
|
642
|
-
|
|
643
|
-
## Roadmap
|
|
191
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
644
192
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
- **Short-term (1-3 months):** Additional scene factory methods, animation utilities, performance optimizations
|
|
648
|
-
- **Medium-term (3-6 months):** Device discovery, advanced scheduling, real-time monitoring
|
|
649
|
-
- **Long-term (6-12 months):** Cloud integration, UI components, AI/ML features, ecosystem integrations
|
|
650
|
-
|
|
651
|
-
We welcome feedback and contributions for any roadmap items!
|
|
652
|
-
|
|
653
|
-
## API Documentation
|
|
654
|
-
|
|
655
|
-
For more information about the Govee Developer API, visit the official documentation:
|
|
193
|
+
## License
|
|
656
194
|
|
|
657
|
-
|
|
658
|
-
- **Getting Started**: https://developer.govee.com/docs
|
|
659
|
-
- **Device Control**: https://developer.govee.com/reference/control-you-devices
|
|
660
|
-
- **Device State**: https://developer.govee.com/reference/get-devices-status
|
|
661
|
-
- **Get Devices**: https://developer.govee.com/reference/get-you-devices
|
|
195
|
+
MIT
|