yoto-nodejs-client 0.0.1 → 0.0.3
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 +523 -30
- package/bin/auth.js +36 -46
- package/bin/content.js +0 -0
- package/bin/device-model.d.ts +3 -0
- package/bin/device-model.d.ts.map +1 -0
- package/bin/device-model.js +360 -0
- package/bin/device-tui.TODO.md +125 -0
- package/bin/device-tui.d.ts +31 -0
- package/bin/device-tui.d.ts.map +1 -0
- package/bin/device-tui.js +1123 -0
- package/bin/devices.js +166 -28
- package/bin/groups.js +0 -0
- package/bin/icons.js +0 -0
- package/bin/lib/cli-helpers.d.ts +33 -1
- package/bin/lib/cli-helpers.d.ts.map +1 -1
- package/bin/lib/cli-helpers.js +5 -5
- package/bin/lib/token-helpers.d.ts +32 -0
- package/bin/lib/token-helpers.d.ts.map +1 -1
- package/bin/refresh-token.js +6 -6
- package/bin/token-info.js +3 -3
- package/index.d.ts +4 -217
- package/index.d.ts.map +1 -1
- package/index.js +11 -689
- package/lib/api-client.d.ts +576 -0
- package/lib/api-client.d.ts.map +1 -0
- package/lib/api-client.js +681 -0
- package/lib/api-endpoints/auth.d.ts +280 -4
- package/lib/api-endpoints/auth.d.ts.map +1 -1
- package/lib/api-endpoints/auth.js +224 -7
- package/lib/api-endpoints/auth.test.js +54 -2
- package/lib/api-endpoints/constants.d.ts +30 -2
- package/lib/api-endpoints/constants.d.ts.map +1 -1
- package/lib/api-endpoints/constants.js +17 -10
- package/lib/api-endpoints/content.d.ts +760 -0
- package/lib/api-endpoints/content.d.ts.map +1 -1
- package/lib/api-endpoints/content.test.js +1 -1
- package/lib/api-endpoints/devices.d.ts +917 -48
- package/lib/api-endpoints/devices.d.ts.map +1 -1
- package/lib/api-endpoints/devices.js +114 -52
- package/lib/api-endpoints/devices.test.js +1 -1
- package/lib/api-endpoints/endpoint-test-helpers.d.ts +28 -0
- package/lib/api-endpoints/endpoint-test-helpers.d.ts.map +1 -0
- package/lib/api-endpoints/family-library-groups.d.ts +187 -0
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -1
- package/lib/api-endpoints/family-library-groups.test.js +1 -1
- package/lib/api-endpoints/family.d.ts +88 -0
- package/lib/api-endpoints/family.d.ts.map +1 -1
- package/lib/api-endpoints/family.test.js +1 -1
- package/lib/api-endpoints/helpers.d.ts +37 -3
- package/lib/api-endpoints/helpers.d.ts.map +1 -1
- package/lib/api-endpoints/icons.d.ts +196 -0
- package/lib/api-endpoints/icons.d.ts.map +1 -1
- package/lib/api-endpoints/icons.test.js +1 -1
- package/lib/api-endpoints/media.d.ts +83 -0
- package/lib/api-endpoints/media.d.ts.map +1 -1
- package/lib/helpers/power-state.d.ts +53 -0
- package/lib/helpers/power-state.d.ts.map +1 -0
- package/lib/helpers/power-state.js +73 -0
- package/lib/helpers/power-state.test.js +100 -0
- package/lib/helpers/temperature.d.ts +24 -0
- package/lib/helpers/temperature.d.ts.map +1 -0
- package/lib/helpers/temperature.js +61 -0
- package/lib/helpers/temperature.test.js +58 -0
- package/lib/helpers/typed-keys.d.ts +7 -0
- package/lib/helpers/typed-keys.d.ts.map +1 -0
- package/lib/helpers/typed-keys.js +8 -0
- package/lib/mqtt/client.d.ts +610 -7
- package/lib/mqtt/client.d.ts.map +1 -1
- package/lib/mqtt/client.js +213 -31
- package/lib/mqtt/commands.d.ts +195 -0
- package/lib/mqtt/commands.d.ts.map +1 -1
- package/lib/mqtt/factory.d.ts +62 -1
- package/lib/mqtt/factory.d.ts.map +1 -1
- package/lib/mqtt/factory.js +27 -5
- package/lib/mqtt/mqtt.test.js +85 -28
- package/lib/mqtt/topics.d.ts +186 -1
- package/lib/mqtt/topics.d.ts.map +1 -1
- package/lib/mqtt/topics.js +54 -20
- package/lib/pkg.d.cts +9 -0
- package/lib/token.d.ts +106 -3
- package/lib/token.d.ts.map +1 -1
- package/lib/token.js +30 -23
- package/lib/yoto-account.d.ts +163 -0
- package/lib/yoto-account.d.ts.map +1 -0
- package/lib/yoto-account.js +340 -0
- package/lib/yoto-device.d.ts +656 -0
- package/lib/yoto-device.d.ts.map +1 -0
- package/lib/yoto-device.js +2850 -0
- package/package.json +22 -15
- package/lib/api-endpoints/test-helpers.d.ts +0 -7
- package/lib/api-endpoints/test-helpers.d.ts.map +0 -1
- /package/lib/api-endpoints/{test-helpers.js → endpoint-test-helpers.js} +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { YotoClientConstructorOptions } from './api-client.js'
|
|
3
|
+
* @import { YotoDeviceModelOptions } from './yoto-device.js'
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EventEmitter } from 'events'
|
|
7
|
+
import { YotoClient } from './api-client.js'
|
|
8
|
+
import { YotoDeviceModel } from './yoto-device.js'
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type Definitions
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Yoto Account initialization options
|
|
16
|
+
* @typedef {Object} YotoAccountOptions
|
|
17
|
+
* @property {YotoClientConstructorOptions} clientOptions - Yoto API client Options
|
|
18
|
+
* @property {YotoDeviceModelOptions} deviceOptions - Yoto Device Model Options
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Error context information
|
|
23
|
+
* @typedef {Object} YotoAccountErrorContext
|
|
24
|
+
* @property {string} source - Error source ('account', 'client', or deviceId)
|
|
25
|
+
* @property {string} [deviceId] - Device ID if error is device-specific
|
|
26
|
+
* @property {string} [operation] - Operation that failed
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Started event metadata
|
|
31
|
+
* @typedef {Object} YotoAccountStartedMetadata
|
|
32
|
+
* @property {number} deviceCount - Number of devices managed
|
|
33
|
+
* @property {string[]} devices - Array of device IDs
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Event map for YotoAccount
|
|
38
|
+
* @typedef {{
|
|
39
|
+
* 'started': [YotoAccountStartedMetadata],
|
|
40
|
+
* 'stopped': [],
|
|
41
|
+
* 'deviceAdded': [string, YotoDeviceModel],
|
|
42
|
+
* 'deviceRemoved': [string],
|
|
43
|
+
* 'error': [Error, YotoAccountErrorContext]
|
|
44
|
+
* }} YotoAccountEventMap
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// YotoAccount Class
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Yoto Account client that manages all devices for an account
|
|
53
|
+
*
|
|
54
|
+
* Events:
|
|
55
|
+
* - 'started' - Emitted when account starts, passes metadata with deviceCount and devices array
|
|
56
|
+
* - 'stopped' - Emitted when account stops
|
|
57
|
+
* - 'deviceAdded' - Emitted when a device is added, passes (deviceId, deviceModel)
|
|
58
|
+
* - 'deviceRemoved' - Emitted when a device is removed, passes deviceId
|
|
59
|
+
* - 'error' - Emitted when an error occurs, passes (error, context)
|
|
60
|
+
*
|
|
61
|
+
* Note: To listen to individual device events (statusUpdate, configUpdate, playbackUpdate, online, offline, etc.),
|
|
62
|
+
* access the device models directly via account.devices or account.getDevice(deviceId) and attach listeners.
|
|
63
|
+
*
|
|
64
|
+
* @extends {EventEmitter<YotoAccountEventMap>}
|
|
65
|
+
*/
|
|
66
|
+
export class YotoAccount extends EventEmitter {
|
|
67
|
+
/** @type {YotoClient} */ #client
|
|
68
|
+
/** @type {Map<string, YotoDeviceModel>} */ #devices = new Map()
|
|
69
|
+
/** @type {YotoAccountOptions} */ #options
|
|
70
|
+
/** @type {boolean} */ #running = false
|
|
71
|
+
/** @type {boolean} */ #initialized = false
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a Yoto account client
|
|
75
|
+
* @param {YotoAccountOptions} options - Account configuration options
|
|
76
|
+
*/
|
|
77
|
+
constructor (options) {
|
|
78
|
+
super()
|
|
79
|
+
|
|
80
|
+
this.#options = options
|
|
81
|
+
|
|
82
|
+
// Create YotoClient with provided clientOptions
|
|
83
|
+
this.#client = new YotoClient(options.clientOptions)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
// Public API - State Accessors
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the underlying YotoClient instance
|
|
92
|
+
* @returns {YotoClient}
|
|
93
|
+
*/
|
|
94
|
+
get client () { return this.#client }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all device models managed by this account
|
|
98
|
+
* @returns {Map<string, YotoDeviceModel>}
|
|
99
|
+
*/
|
|
100
|
+
get devices () { return this.#devices }
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if account is currently running
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
get running () { return this.#running }
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if account has been initialized
|
|
110
|
+
* @returns {boolean}
|
|
111
|
+
*/
|
|
112
|
+
get initialized () { return this.#initialized }
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a specific device by ID
|
|
116
|
+
* @param {string} deviceId - Device ID
|
|
117
|
+
* @returns {YotoDeviceModel | undefined}
|
|
118
|
+
*/
|
|
119
|
+
getDevice (deviceId) { return this.#devices.get(deviceId) }
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all device IDs
|
|
123
|
+
* @returns {string[]}
|
|
124
|
+
*/
|
|
125
|
+
getDeviceIds () { return Array.from(this.#devices.keys()) }
|
|
126
|
+
|
|
127
|
+
// ==========================================================================
|
|
128
|
+
// Public API - Lifecycle Management
|
|
129
|
+
// ==========================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Start the account client - creates YotoClient, discovers devices, starts all device clients
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
* @throws {Error} If start fails
|
|
135
|
+
*/
|
|
136
|
+
async start () {
|
|
137
|
+
if (this.#running) {
|
|
138
|
+
return // Already running
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Discover devices
|
|
143
|
+
const devicesResponse = await this.#client.getDevices()
|
|
144
|
+
|
|
145
|
+
// Create and start device models
|
|
146
|
+
for (const device of devicesResponse.devices) {
|
|
147
|
+
const deviceModel = new YotoDeviceModel(
|
|
148
|
+
this.#client,
|
|
149
|
+
device,
|
|
150
|
+
this.#options.deviceOptions
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// Set up device error forwarding
|
|
154
|
+
this.#setupDeviceErrorHandling(deviceModel, device.deviceId)
|
|
155
|
+
|
|
156
|
+
// Track device
|
|
157
|
+
this.#devices.set(device.deviceId, deviceModel)
|
|
158
|
+
|
|
159
|
+
// Start device (await one at a time)
|
|
160
|
+
try {
|
|
161
|
+
await deviceModel.start()
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const error = /** @type {Error} */ (err)
|
|
164
|
+
this.emit('error', error, {
|
|
165
|
+
source: device.deviceId,
|
|
166
|
+
deviceId: device.deviceId,
|
|
167
|
+
operation: 'start'
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.emit('deviceAdded', device.deviceId, deviceModel)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Mark as initialized and running
|
|
175
|
+
this.#initialized = true
|
|
176
|
+
this.#running = true
|
|
177
|
+
|
|
178
|
+
this.emit('started', {
|
|
179
|
+
deviceCount: this.#devices.size,
|
|
180
|
+
devices: Array.from(this.#devices.keys())
|
|
181
|
+
})
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const error = /** @type {Error} */ (err)
|
|
184
|
+
this.emit('error', error, {
|
|
185
|
+
source: 'account',
|
|
186
|
+
operation: 'start'
|
|
187
|
+
})
|
|
188
|
+
throw error
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Stop the account client - stops all device clients
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
async stop () {
|
|
197
|
+
if (!this.#running) {
|
|
198
|
+
return // Not running
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// Stop all devices
|
|
203
|
+
const stopPromises = []
|
|
204
|
+
for (const [deviceId, deviceModel] of this.#devices) {
|
|
205
|
+
stopPromises.push(
|
|
206
|
+
deviceModel.stop().catch((error) => {
|
|
207
|
+
this.emit('error', error, {
|
|
208
|
+
source: deviceId,
|
|
209
|
+
deviceId,
|
|
210
|
+
operation: 'stop'
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Wait for all devices to stop (or fail gracefully)
|
|
217
|
+
await Promise.allSettled(stopPromises)
|
|
218
|
+
|
|
219
|
+
// Clear devices
|
|
220
|
+
this.#devices.clear()
|
|
221
|
+
|
|
222
|
+
// Mark as stopped
|
|
223
|
+
this.#running = false
|
|
224
|
+
|
|
225
|
+
this.emit('stopped')
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const error = /** @type {Error} */ (err)
|
|
228
|
+
this.emit('error', error, {
|
|
229
|
+
source: 'account',
|
|
230
|
+
operation: 'stop'
|
|
231
|
+
})
|
|
232
|
+
throw error
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Restart the account client - stops and starts again
|
|
238
|
+
* @returns {Promise<void>}
|
|
239
|
+
*/
|
|
240
|
+
async restart () {
|
|
241
|
+
await this.stop()
|
|
242
|
+
await this.start()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Refresh device list - discovers new devices and removes missing ones
|
|
247
|
+
* @returns {Promise<void>}
|
|
248
|
+
*/
|
|
249
|
+
async refreshDevices () {
|
|
250
|
+
if (!this.#client) {
|
|
251
|
+
throw new Error('Account not started. Call start() first.')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// Get current devices from API
|
|
256
|
+
const devicesResponse = await this.#client.getDevices()
|
|
257
|
+
const currentDeviceIds = new Set(devicesResponse.devices.map(d => d.deviceId))
|
|
258
|
+
const trackedDeviceIds = new Set(this.#devices.keys())
|
|
259
|
+
|
|
260
|
+
// Find devices to add
|
|
261
|
+
const devicesToAdd = devicesResponse.devices.filter(
|
|
262
|
+
d => !trackedDeviceIds.has(d.deviceId)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
// Find devices to remove
|
|
266
|
+
const devicesToRemove = Array.from(trackedDeviceIds).filter(
|
|
267
|
+
id => !currentDeviceIds.has(id)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
// Remove missing devices
|
|
271
|
+
for (const deviceId of devicesToRemove) {
|
|
272
|
+
const deviceModel = this.#devices.get(deviceId)
|
|
273
|
+
if (deviceModel) {
|
|
274
|
+
await deviceModel.stop().catch((error) => {
|
|
275
|
+
this.emit('error', error, {
|
|
276
|
+
source: deviceId,
|
|
277
|
+
deviceId,
|
|
278
|
+
operation: 'remove'
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
this.#devices.delete(deviceId)
|
|
282
|
+
this.emit('deviceRemoved', deviceId)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Add new devices
|
|
287
|
+
for (const device of devicesToAdd) {
|
|
288
|
+
const deviceModel = new YotoDeviceModel(
|
|
289
|
+
this.#client,
|
|
290
|
+
device,
|
|
291
|
+
this.#options.deviceOptions
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
// Set up device error forwarding
|
|
295
|
+
this.#setupDeviceErrorHandling(deviceModel, device.deviceId)
|
|
296
|
+
|
|
297
|
+
// Track device
|
|
298
|
+
this.#devices.set(device.deviceId, deviceModel)
|
|
299
|
+
|
|
300
|
+
// Start device
|
|
301
|
+
await deviceModel.start().catch((error) => {
|
|
302
|
+
this.emit('error', error, {
|
|
303
|
+
source: device.deviceId,
|
|
304
|
+
deviceId: device.deviceId,
|
|
305
|
+
operation: 'add'
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
this.emit('deviceAdded', device.deviceId, deviceModel)
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
const error = /** @type {Error} */ (err)
|
|
313
|
+
this.emit('error', error, {
|
|
314
|
+
source: 'account',
|
|
315
|
+
operation: 'refreshDevices'
|
|
316
|
+
})
|
|
317
|
+
throw error
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ==========================================================================
|
|
322
|
+
// Private - Device Management
|
|
323
|
+
// ==========================================================================
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Set up error handling for a device model
|
|
327
|
+
* @param {YotoDeviceModel} deviceModel - Device model instance
|
|
328
|
+
* @param {string} deviceId - Device ID
|
|
329
|
+
*/
|
|
330
|
+
#setupDeviceErrorHandling (deviceModel, deviceId) {
|
|
331
|
+
// Forward device errors to account-level error event
|
|
332
|
+
deviceModel.on('error', (error) => {
|
|
333
|
+
this.emit('error', error, {
|
|
334
|
+
source: deviceId,
|
|
335
|
+
deviceId,
|
|
336
|
+
operation: 'device-error'
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
}
|