divoom-timesgate-sdk 0.1.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 +21 -0
- package/README.md +371 -0
- package/dist/common-D8oHDNi6.d.cts +54 -0
- package/dist/common-D8oHDNi6.d.ts +54 -0
- package/dist/image/index.cjs +515 -0
- package/dist/image/index.cjs.map +1 -0
- package/dist/image/index.d.cts +279 -0
- package/dist/image/index.d.ts +279 -0
- package/dist/image/index.js +498 -0
- package/dist/image/index.js.map +1 -0
- package/dist/index.cjs +1021 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +921 -0
- package/dist/index.d.ts +921 -0
- package/dist/index.js +991 -0
- package/dist/index.js.map +1 -0
- package/package.json +87 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Praful
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# divoom-timesgate-sdk
|
|
2
|
+
|
|
3
|
+
> A fully type-safe Node.js & TypeScript SDK for controlling the **Divoom Times Gate** over your local network — with a high-resolution image & animation pipeline, complete command coverage, and zero-guesswork docs.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/divoom-timesgate-sdk)
|
|
6
|
+
[](https://github.com/jasonpraful/divoom-timesgate-sdk/actions/workflows/ci.yml)
|
|
7
|
+
[](https://www.npmjs.com/package/divoom-timesgate-sdk)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- 🧊 **Every documented command, fully typed** — system settings, dial/channel control, built-in tools, animations, and the image pipeline.
|
|
15
|
+
- 🖼️ **High-resolution image pipeline** — push crisp 128×128 JPEG frames (LANCZOS3 + q95, 4:4:4 chroma) via [`sharp`](https://sharp.pixelplumbing.com/). Album-art styling, accent extraction, designed "now-playing" panels, and multi-panel panoramas.
|
|
16
|
+
- 🧠 **Ergonomic + safe** — grouped client API (`client.system`, `client.draw`, …), argument validation with descriptive errors, and a typed error hierarchy.
|
|
17
|
+
- 🔌 **Modern packaging** — dual ESM/CJS, first-class types, tree-shakeable. The image module is a separate entry point so core-only users never pull in `sharp`.
|
|
18
|
+
- 🔎 **LAN discovery + cloud helpers** — find your device's IP, and browse dials/images/fonts from Divoom's cloud.
|
|
19
|
+
- ✅ **Tested & documented** — extensive unit tests and TSDoc on every public symbol.
|
|
20
|
+
|
|
21
|
+
## Table of contents
|
|
22
|
+
|
|
23
|
+
- [Install](#install)
|
|
24
|
+
- [Quick start](#quick-start)
|
|
25
|
+
- [Getting your `LocalToken`](#getting-your-localtoken)
|
|
26
|
+
- [Finding your device](#finding-your-device)
|
|
27
|
+
- [Core concepts](#core-concepts)
|
|
28
|
+
- [The image pipeline](#the-image-pipeline)
|
|
29
|
+
- [Command reference](#command-reference)
|
|
30
|
+
- [Cloud discovery](#cloud-discovery)
|
|
31
|
+
- [Error handling](#error-handling)
|
|
32
|
+
- [Configuration](#configuration)
|
|
33
|
+
- [Examples](#examples)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install divoom-timesgate-sdk
|
|
40
|
+
# or
|
|
41
|
+
pnpm add divoom-timesgate-sdk
|
|
42
|
+
# or
|
|
43
|
+
yarn add divoom-timesgate-sdk
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Requires **Node.js 18+** (uses the global `fetch`). `sharp` is included for the image pipeline and ships prebuilt binaries for common platforms.
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { TimesGateClient } from 'divoom-timesgate-sdk';
|
|
52
|
+
import { encodePanel } from 'divoom-timesgate-sdk/image';
|
|
53
|
+
|
|
54
|
+
const client = new TimesGateClient({
|
|
55
|
+
host: '192.168.1.50', // your Times Gate's LAN IP
|
|
56
|
+
localToken: 229930, // from the Divoom app → device → Settings (see below)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Is it reachable?
|
|
60
|
+
if (!(await client.ping())) throw new Error('Times Gate not reachable');
|
|
61
|
+
|
|
62
|
+
// Set the brightness
|
|
63
|
+
await client.system.setBrightness(80);
|
|
64
|
+
|
|
65
|
+
// Push album art (or any image — file path, URL, or Buffer) to panel 0
|
|
66
|
+
const frame = await encodePanel('https://example.com/cover.jpg');
|
|
67
|
+
await client.draw.sendImage(0, frame.data);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Getting your `LocalToken`
|
|
71
|
+
|
|
72
|
+
Many control commands (such as `Channel/SetBrightness`) are authenticated with a **`LocalToken`** — a stable, per-device number.
|
|
73
|
+
|
|
74
|
+
> ⚠️ The current public API docs don't list `LocalToken`, but real devices still use it. You can read it in the **Divoom mobile app → your Times Gate → Settings**. It looks like a number, e.g. `229930`.
|
|
75
|
+
|
|
76
|
+
Pass it once when you create the client and the SDK injects it into every request:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const client = new TimesGateClient({ host: '192.168.1.50', localToken: 229930 });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Read-only commands generally work without it, so for quick experiments you can omit it (it defaults to `0`). If a command comes back with a `DivoomDeviceError`, a missing/incorrect `LocalToken` is the usual cause.
|
|
83
|
+
|
|
84
|
+
## Finding your device
|
|
85
|
+
|
|
86
|
+
If you don't know the device's IP, discover it from the same network:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { discoverDevices, TimesGateClient } from 'divoom-timesgate-sdk';
|
|
90
|
+
|
|
91
|
+
const devices = await discoverDevices();
|
|
92
|
+
// → [{ name: 'Times Gate', id: 300038420, ip: '192.168.1.50', mac: 'a8032aff46b1' }]
|
|
93
|
+
|
|
94
|
+
const gate = devices.find((d) => d.name.toLowerCase().includes('times gate'));
|
|
95
|
+
const client = new TimesGateClient({ host: gate!.ip, localToken: 229930 });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> Discovery goes through Divoom's cloud (it reports devices seen from your public IP), so it needs outbound internet and won't work across different networks.
|
|
99
|
+
|
|
100
|
+
## Core concepts
|
|
101
|
+
|
|
102
|
+
- **5 panels.** The Times Gate has five LCD panels, indexed `0`–`4` (`PanelIndex`), each **128×128**.
|
|
103
|
+
- **Panel masks.** Some commands act on multiple panels at once via an `LcdArray` — a 5-element mask like `[1, 0, 0, 0, 1]` (`1` = act, `0` = skip). The SDK builds these for you from panel indices, with helpers `panelToLcdArray` / `panelsToLcdArray` if you need them.
|
|
104
|
+
- **PicIDs.** The device caches image frames by a strictly-increasing `PicID`. `client.draw` manages this automatically (you can override or read the device's value with `getPicId()`).
|
|
105
|
+
- **Hardware versions.** Hardware 400 uses `http://IP:80/post` (the default). Hardware 402 uses `http://IP:9000/divoom_api` — pass `port: 9000, path: '/divoom_api'`.
|
|
106
|
+
|
|
107
|
+
## The image pipeline
|
|
108
|
+
|
|
109
|
+
The image helpers live in the **`divoom-timesgate-sdk/image`** entry point (so projects that only send pre-encoded bytes never load `sharp`). Every helper returns an `EncodedFrame` whose `.data` is the base64 string you hand to `client.draw.sendImage(...)`.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
encodePanel,
|
|
114
|
+
prepareAlbumArt,
|
|
115
|
+
renderTextPanel,
|
|
116
|
+
splitImageAcrossPanels,
|
|
117
|
+
getAccentColor,
|
|
118
|
+
solidFrame,
|
|
119
|
+
} from 'divoom-timesgate-sdk/image';
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Resize & encode any image
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const frame = await encodePanel('./photo.png', { fit: 'cover' }); // 128×128 JPEG q95
|
|
126
|
+
await client.draw.sendImage(0, frame.data);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`encodePanel` accepts a **file path, an `http(s)` URL, a `Buffer`/`Uint8Array`, or a `sharp` instance**.
|
|
130
|
+
|
|
131
|
+
### Album art (smooth or pixel)
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const art = await prepareAlbumArt(coverUrl, { style: 'smooth' }); // or 'pixel'
|
|
135
|
+
const accent = await getAccentColor(coverUrl); // vivid color for theming → '#E94F37'
|
|
136
|
+
await client.draw.sendImage(0, art.data);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Designed "now-playing" text panel
|
|
140
|
+
|
|
141
|
+
Crisp, auto-wrapped text rendered with Pango — no reliance on host system fonts:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const panel = await renderTextPanel({
|
|
145
|
+
eyebrow: 'Now Playing',
|
|
146
|
+
title: 'Bohemian Rhapsody',
|
|
147
|
+
subtitle: 'Queen',
|
|
148
|
+
accent: '#E94F37',
|
|
149
|
+
progress: 0.42, // draws a progress bar
|
|
150
|
+
});
|
|
151
|
+
await client.draw.sendImage(1, panel.data);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### One image across all five panels
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const tiles = await splitImageAcrossPanels('./wide-banner.png'); // panorama
|
|
158
|
+
for (const { panel, frame } of tiles) {
|
|
159
|
+
await client.draw.sendImage(panel, frame.data);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Animations
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// Build frames however you like (here: a color cycle) and play them.
|
|
167
|
+
const frames = await Promise.all(
|
|
168
|
+
['#FF0066', '#FF9900', '#33CC66', '#3399FF'].map((c) => solidFrame(c)),
|
|
169
|
+
);
|
|
170
|
+
await client.draw.sendAnimation(
|
|
171
|
+
[0],
|
|
172
|
+
frames.map((f) => f.data),
|
|
173
|
+
{ picSpeed: 120 },
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Command reference
|
|
178
|
+
|
|
179
|
+
Everything hangs off a `TimesGateClient`, grouped by area. A few highlights:
|
|
180
|
+
|
|
181
|
+
### `client.system`
|
|
182
|
+
|
|
183
|
+
| Method | Command | Notes |
|
|
184
|
+
| --------------------------------------------- | ----------------------- | -------------------------- |
|
|
185
|
+
| `setBrightness(0–100)` | `Channel/SetBrightness` | |
|
|
186
|
+
| `getAllConfig()` | `Channel/GetAllConf` | brightness, formats, flags |
|
|
187
|
+
| `setScreen(on)` | `Channel/OnOffScreen` | |
|
|
188
|
+
| `setTimeZone('GMT-5')` | `Sys/TimeZone` | |
|
|
189
|
+
| `setSystemTime(date)` | `Device/SetUTC` | `Date` or epoch seconds |
|
|
190
|
+
| `setWeatherLocation(lon, lat)` | `Sys/LogAndLat` | |
|
|
191
|
+
| `getWeather()` | `Device/GetWeatherInfo` | |
|
|
192
|
+
| `setTemperatureMode('celsius'\|'fahrenheit')` | `Device/SetDisTempMode` | |
|
|
193
|
+
| `setMirrorMode(enabled)` | `Device/SetMirrorMode` | |
|
|
194
|
+
| `setHourMode(use24Hour)` | `Device/SetTime24Flag` | |
|
|
195
|
+
| `getDeviceTime()` | `Device/GetDeviceTime` | |
|
|
196
|
+
|
|
197
|
+
### `client.draw` (image pipeline)
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
await client.draw.sendImage(panel, frame.data, { picSpeed: 1000 });
|
|
201
|
+
await client.draw.sendImageToPanels([0, 2, 4], frame.data);
|
|
202
|
+
await client.draw.sendAnimation([0], frames, { picSpeed: 100 });
|
|
203
|
+
await client.draw.resetCache(); // clear stuck frames / reset the PicID counter
|
|
204
|
+
const id = await client.draw.getPicId();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `client.dial` (what each panel shows)
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
await client.dial.selectWholeDial(7); // one dial across all 5 panels
|
|
211
|
+
await client.dial.setChannelMode('independent', 978);
|
|
212
|
+
await client.dial.selectSubDial(0, 10, 978); // panel 0 → dial 10
|
|
213
|
+
const { SelectIndex } = await client.dial.getIndex();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `client.tool`
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
await client.tool.setCountdown(1, 30); // 1:30 countdown, start
|
|
220
|
+
await client.tool.setStopwatch('start'); // 'start' | 'stop' | 'reset'
|
|
221
|
+
await client.tool.setScoreboard(3, 5); // red, blue
|
|
222
|
+
await client.tool.setNoiseMeter(true);
|
|
223
|
+
await client.tool.playBuzzer({ activeMs: 200, offMs: 100, totalMs: 900 });
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### `client.animation`
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
await client.animation.playGifUrls([0], ['http://f.divoom-gz.com/64_64.gif']);
|
|
230
|
+
await client.animation.playStoredGif([0], fileId); // FileId from the cloud client
|
|
231
|
+
await client.animation.sendText(0, 'Hello!', { align: 'center', color: '#FFD400' });
|
|
232
|
+
await client.animation.sendItemList(1, items, { backgroundGif });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `client.batch`
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
await client.batch.run([
|
|
239
|
+
{ Command: 'Channel/SetBrightness', Brightness: 100 },
|
|
240
|
+
{ Command: 'Channel/OnOffScreen', OnOff: 1 },
|
|
241
|
+
]);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Escape hatch
|
|
245
|
+
|
|
246
|
+
For anything not yet wrapped, send a raw payload (still typed, `LocalToken` still injected):
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
const res = await client.send<{ error_code: number; PicId: number }>({
|
|
250
|
+
Command: 'Draw/GetHttpGifId',
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Cloud discovery
|
|
255
|
+
|
|
256
|
+
Two cloud-backed helpers (HTTPS to `app.divoom-gz.com`) complement the on-device API:
|
|
257
|
+
|
|
258
|
+
- **`discoverDevices(options?)` → `Promise<DiscoveredDevice[]>`** — find Times Gates on your network and read each one's IP, id, and MAC. See [Finding your device](#finding-your-device) for a full example.
|
|
259
|
+
- **`DivoomCloudClient`** — browse dials, stored images, and fonts; their ids then drive on-device commands.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { DivoomCloudClient } from 'divoom-timesgate-sdk';
|
|
263
|
+
|
|
264
|
+
const cloud = new DivoomCloudClient();
|
|
265
|
+
const { ClockList } = await cloud.getWholeDialList(1);
|
|
266
|
+
await client.dial.selectWholeDial(ClockList[0].ClockId);
|
|
267
|
+
|
|
268
|
+
const { DialTypeList } = await cloud.getDialTypes();
|
|
269
|
+
const { DialList } = await cloud.getDialList(DialTypeList[0], 1);
|
|
270
|
+
const { FontList } = await cloud.getFontList();
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Error handling
|
|
274
|
+
|
|
275
|
+
Every error extends `DivoomError`, so you can catch the whole family or narrow to a specific cause:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import {
|
|
279
|
+
DivoomError,
|
|
280
|
+
DivoomDeviceError,
|
|
281
|
+
DivoomTimeoutError,
|
|
282
|
+
DivoomValidationError,
|
|
283
|
+
} from 'divoom-timesgate-sdk';
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await client.system.setBrightness(150); // invalid → throws before any request
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (err instanceof DivoomValidationError) console.error('Bad argument:', err.message);
|
|
289
|
+
else if (err instanceof DivoomTimeoutError) console.error('Device too slow');
|
|
290
|
+
else if (err instanceof DivoomDeviceError) console.error('Device said no:', err.errorCode);
|
|
291
|
+
else if (err instanceof DivoomError) console.error('Divoom error:', err.message);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
| Error | When |
|
|
296
|
+
| ----------------------- | ------------------------------------------- |
|
|
297
|
+
| `DivoomValidationError` | Bad argument, caught before sending. |
|
|
298
|
+
| `DivoomConnectionError` | Device unreachable. |
|
|
299
|
+
| `DivoomTimeoutError` | Request exceeded the timeout. |
|
|
300
|
+
| `DivoomHttpError` | Non-2xx HTTP response. |
|
|
301
|
+
| `DivoomDeviceError` | Device returned a non-zero `error_code`. |
|
|
302
|
+
| `DivoomCloudError` | Cloud API returned a non-zero `ReturnCode`. |
|
|
303
|
+
|
|
304
|
+
## Configuration
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const client = new TimesGateClient({
|
|
308
|
+
host: '192.168.1.50',
|
|
309
|
+
localToken: 229930,
|
|
310
|
+
port: 80, // 9000 for hardware version 402
|
|
311
|
+
path: '/post', // '/divoom_api' for hardware version 402
|
|
312
|
+
protocol: 'http',
|
|
313
|
+
timeoutMs: 8000, // per-request timeout
|
|
314
|
+
retries: 2, // automatic retries for transient failures
|
|
315
|
+
retryDelayMs: 300, // exponential backoff base
|
|
316
|
+
fetch: customFetch, // inject your own fetch (proxies, tests, …)
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Transient failures (network errors, timeouts, 5xx) are retried with exponential backoff; device-level and 4xx errors are surfaced immediately.
|
|
321
|
+
|
|
322
|
+
## Examples
|
|
323
|
+
|
|
324
|
+
Runnable example apps live in [`examples/`](./examples):
|
|
325
|
+
|
|
326
|
+
| Example | What it shows |
|
|
327
|
+
| --------------------------- | ------------------------------------------------------ |
|
|
328
|
+
| `01-quickstart` | Connect, ping, set brightness, push a frame. |
|
|
329
|
+
| `02-spotify-now-playing` | Album art + live now-playing panel (the flagship). |
|
|
330
|
+
| `03-system-and-weather` | Configure the device and read weather/config. |
|
|
331
|
+
| `04-tools` | Countdown, stopwatch, scoreboard, noise meter, buzzer. |
|
|
332
|
+
| `05-panorama` | One image spanning all five panels. |
|
|
333
|
+
| `06-animation` | Multi-frame animations and hosted GIFs. |
|
|
334
|
+
| `07-text-and-notifications` | Designed text panels and scrolling text. |
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
pnpm install
|
|
338
|
+
pnpm --filter @divoom-timesgate-examples/01-quickstart start
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Security notes
|
|
342
|
+
|
|
343
|
+
- **Your LAN is the trust boundary.** The device API is plain HTTP and unauthenticated on the local network aside from the per-device `LocalToken`. The optional cloud helpers (`discoverDevices`, `DivoomCloudClient`) use HTTPS to `app.divoom-gz.com`.
|
|
344
|
+
- **Image sources.** The `image` helpers treat a string source as an `http(s)` URL or a **local file path**. Don't pass untrusted/attacker-influenced strings — a URL can trigger a server-side request (SSRF) and a path can read local files. URL downloads are bounded by a timeout and a size cap; for untrusted input, validate the source yourself or pass a `Buffer` you fetched under your own controls.
|
|
345
|
+
- **Device-side fetches.** `animation.playGifUrls` / `playGifPerPanel` / `sendItemList({ backgroundGif })` and `batch.useCommandSource` make the **device** fetch a URL you supply (and `useCommandSource` executes the returned commands). Only pass URLs you trust; allowlist hosts if end users can influence them.
|
|
346
|
+
|
|
347
|
+
See [SECURITY.md](./SECURITY.md) for reporting and more detail.
|
|
348
|
+
|
|
349
|
+
## Development
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
pnpm install
|
|
353
|
+
pnpm build # tsup → dual ESM/CJS + d.ts
|
|
354
|
+
pnpm test # vitest
|
|
355
|
+
pnpm test:coverage # coverage report
|
|
356
|
+
pnpm typecheck # tsc --noEmit
|
|
357
|
+
pnpm lint # eslint
|
|
358
|
+
pnpm format # prettier --write
|
|
359
|
+
pnpm check # typecheck + lint + format:check + test
|
|
360
|
+
pnpm docs # typedoc → ./docs
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the contribution workflow (we use [Changesets](https://github.com/changesets/changesets) for versioning).
|
|
364
|
+
|
|
365
|
+
## Acknowledgements
|
|
366
|
+
|
|
367
|
+
Command shapes are transcribed from Divoom's official [ShowDoc API](https://doc.divoom-gz.com/web/#/12?page_id=538). This project is not affiliated with or endorsed by Divoom.
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
[MIT](./LICENSE) © Jason Praful
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, transport-level types used across the whole SDK.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A `fetch`-compatible function. The SDK uses the global {@link fetch} by
|
|
8
|
+
* default (Node 18+), but you can inject your own implementation — handy for
|
|
9
|
+
* testing, proxies, or custom networking.
|
|
10
|
+
*/
|
|
11
|
+
type FetchLike = typeof fetch;
|
|
12
|
+
/**
|
|
13
|
+
* Identifies one of the Times Gate's five LCD panels, numbered left-to-right
|
|
14
|
+
* as `0 | 1 | 2 | 3 | 4`.
|
|
15
|
+
*/
|
|
16
|
+
type PanelIndex = 0 | 1 | 2 | 3 | 4;
|
|
17
|
+
/**
|
|
18
|
+
* A five-element panel selection mask — one entry per panel, where `1` means
|
|
19
|
+
* "apply to this panel" and `0` means "leave it alone".
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const onlyFirstPanel: LcdArray = [1, 0, 0, 0, 0];
|
|
24
|
+
* const allPanels: LcdArray = [1, 1, 1, 1, 1];
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
type LcdArray = [number, number, number, number, number];
|
|
28
|
+
/**
|
|
29
|
+
* The base shape returned by every Times Gate command. A `error_code` of `0`
|
|
30
|
+
* indicates success; any other value is surfaced as a
|
|
31
|
+
* {@link DivoomDeviceError}.
|
|
32
|
+
*/
|
|
33
|
+
interface DivoomBaseResponse {
|
|
34
|
+
/** `0` on success; non-zero indicates a device-side error. */
|
|
35
|
+
error_code: number;
|
|
36
|
+
/** Additional, command-specific fields returned by the device. */
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The minimal shape of any command request body. Every command carries a
|
|
41
|
+
* string {@link DivoomCommandRequest.Command | Command} discriminator and may
|
|
42
|
+
* include the device's {@link DivoomCommandRequest.LocalToken | LocalToken}.
|
|
43
|
+
*/
|
|
44
|
+
interface DivoomCommandRequest {
|
|
45
|
+
/** The Divoom command name, e.g. `"Channel/SetBrightness"`. */
|
|
46
|
+
Command: string;
|
|
47
|
+
/**
|
|
48
|
+
* The device's local authentication token. Injected automatically by the
|
|
49
|
+
* {@link HttpTransport} when configured, so you rarely set this by hand.
|
|
50
|
+
*/
|
|
51
|
+
LocalToken?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type { DivoomBaseResponse as D, FetchLike as F, LcdArray as L, PanelIndex as P, DivoomCommandRequest as a };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, transport-level types used across the whole SDK.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* A `fetch`-compatible function. The SDK uses the global {@link fetch} by
|
|
8
|
+
* default (Node 18+), but you can inject your own implementation — handy for
|
|
9
|
+
* testing, proxies, or custom networking.
|
|
10
|
+
*/
|
|
11
|
+
type FetchLike = typeof fetch;
|
|
12
|
+
/**
|
|
13
|
+
* Identifies one of the Times Gate's five LCD panels, numbered left-to-right
|
|
14
|
+
* as `0 | 1 | 2 | 3 | 4`.
|
|
15
|
+
*/
|
|
16
|
+
type PanelIndex = 0 | 1 | 2 | 3 | 4;
|
|
17
|
+
/**
|
|
18
|
+
* A five-element panel selection mask — one entry per panel, where `1` means
|
|
19
|
+
* "apply to this panel" and `0` means "leave it alone".
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const onlyFirstPanel: LcdArray = [1, 0, 0, 0, 0];
|
|
24
|
+
* const allPanels: LcdArray = [1, 1, 1, 1, 1];
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
type LcdArray = [number, number, number, number, number];
|
|
28
|
+
/**
|
|
29
|
+
* The base shape returned by every Times Gate command. A `error_code` of `0`
|
|
30
|
+
* indicates success; any other value is surfaced as a
|
|
31
|
+
* {@link DivoomDeviceError}.
|
|
32
|
+
*/
|
|
33
|
+
interface DivoomBaseResponse {
|
|
34
|
+
/** `0` on success; non-zero indicates a device-side error. */
|
|
35
|
+
error_code: number;
|
|
36
|
+
/** Additional, command-specific fields returned by the device. */
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The minimal shape of any command request body. Every command carries a
|
|
41
|
+
* string {@link DivoomCommandRequest.Command | Command} discriminator and may
|
|
42
|
+
* include the device's {@link DivoomCommandRequest.LocalToken | LocalToken}.
|
|
43
|
+
*/
|
|
44
|
+
interface DivoomCommandRequest {
|
|
45
|
+
/** The Divoom command name, e.g. `"Channel/SetBrightness"`. */
|
|
46
|
+
Command: string;
|
|
47
|
+
/**
|
|
48
|
+
* The device's local authentication token. Injected automatically by the
|
|
49
|
+
* {@link HttpTransport} when configured, so you rarely set this by hand.
|
|
50
|
+
*/
|
|
51
|
+
LocalToken?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type { DivoomBaseResponse as D, FetchLike as F, LcdArray as L, PanelIndex as P, DivoomCommandRequest as a };
|