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.
Files changed (92) hide show
  1. package/README.md +523 -30
  2. package/bin/auth.js +36 -46
  3. package/bin/content.js +0 -0
  4. package/bin/device-model.d.ts +3 -0
  5. package/bin/device-model.d.ts.map +1 -0
  6. package/bin/device-model.js +360 -0
  7. package/bin/device-tui.TODO.md +125 -0
  8. package/bin/device-tui.d.ts +31 -0
  9. package/bin/device-tui.d.ts.map +1 -0
  10. package/bin/device-tui.js +1123 -0
  11. package/bin/devices.js +166 -28
  12. package/bin/groups.js +0 -0
  13. package/bin/icons.js +0 -0
  14. package/bin/lib/cli-helpers.d.ts +33 -1
  15. package/bin/lib/cli-helpers.d.ts.map +1 -1
  16. package/bin/lib/cli-helpers.js +5 -5
  17. package/bin/lib/token-helpers.d.ts +32 -0
  18. package/bin/lib/token-helpers.d.ts.map +1 -1
  19. package/bin/refresh-token.js +6 -6
  20. package/bin/token-info.js +3 -3
  21. package/index.d.ts +4 -217
  22. package/index.d.ts.map +1 -1
  23. package/index.js +11 -689
  24. package/lib/api-client.d.ts +576 -0
  25. package/lib/api-client.d.ts.map +1 -0
  26. package/lib/api-client.js +681 -0
  27. package/lib/api-endpoints/auth.d.ts +280 -4
  28. package/lib/api-endpoints/auth.d.ts.map +1 -1
  29. package/lib/api-endpoints/auth.js +224 -7
  30. package/lib/api-endpoints/auth.test.js +54 -2
  31. package/lib/api-endpoints/constants.d.ts +30 -2
  32. package/lib/api-endpoints/constants.d.ts.map +1 -1
  33. package/lib/api-endpoints/constants.js +17 -10
  34. package/lib/api-endpoints/content.d.ts +760 -0
  35. package/lib/api-endpoints/content.d.ts.map +1 -1
  36. package/lib/api-endpoints/content.test.js +1 -1
  37. package/lib/api-endpoints/devices.d.ts +917 -48
  38. package/lib/api-endpoints/devices.d.ts.map +1 -1
  39. package/lib/api-endpoints/devices.js +114 -52
  40. package/lib/api-endpoints/devices.test.js +1 -1
  41. package/lib/api-endpoints/endpoint-test-helpers.d.ts +28 -0
  42. package/lib/api-endpoints/endpoint-test-helpers.d.ts.map +1 -0
  43. package/lib/api-endpoints/family-library-groups.d.ts +187 -0
  44. package/lib/api-endpoints/family-library-groups.d.ts.map +1 -1
  45. package/lib/api-endpoints/family-library-groups.test.js +1 -1
  46. package/lib/api-endpoints/family.d.ts +88 -0
  47. package/lib/api-endpoints/family.d.ts.map +1 -1
  48. package/lib/api-endpoints/family.test.js +1 -1
  49. package/lib/api-endpoints/helpers.d.ts +37 -3
  50. package/lib/api-endpoints/helpers.d.ts.map +1 -1
  51. package/lib/api-endpoints/icons.d.ts +196 -0
  52. package/lib/api-endpoints/icons.d.ts.map +1 -1
  53. package/lib/api-endpoints/icons.test.js +1 -1
  54. package/lib/api-endpoints/media.d.ts +83 -0
  55. package/lib/api-endpoints/media.d.ts.map +1 -1
  56. package/lib/helpers/power-state.d.ts +53 -0
  57. package/lib/helpers/power-state.d.ts.map +1 -0
  58. package/lib/helpers/power-state.js +73 -0
  59. package/lib/helpers/power-state.test.js +100 -0
  60. package/lib/helpers/temperature.d.ts +24 -0
  61. package/lib/helpers/temperature.d.ts.map +1 -0
  62. package/lib/helpers/temperature.js +61 -0
  63. package/lib/helpers/temperature.test.js +58 -0
  64. package/lib/helpers/typed-keys.d.ts +7 -0
  65. package/lib/helpers/typed-keys.d.ts.map +1 -0
  66. package/lib/helpers/typed-keys.js +8 -0
  67. package/lib/mqtt/client.d.ts +610 -7
  68. package/lib/mqtt/client.d.ts.map +1 -1
  69. package/lib/mqtt/client.js +213 -31
  70. package/lib/mqtt/commands.d.ts +195 -0
  71. package/lib/mqtt/commands.d.ts.map +1 -1
  72. package/lib/mqtt/factory.d.ts +62 -1
  73. package/lib/mqtt/factory.d.ts.map +1 -1
  74. package/lib/mqtt/factory.js +27 -5
  75. package/lib/mqtt/mqtt.test.js +85 -28
  76. package/lib/mqtt/topics.d.ts +186 -1
  77. package/lib/mqtt/topics.d.ts.map +1 -1
  78. package/lib/mqtt/topics.js +54 -20
  79. package/lib/pkg.d.cts +9 -0
  80. package/lib/token.d.ts +106 -3
  81. package/lib/token.d.ts.map +1 -1
  82. package/lib/token.js +30 -23
  83. package/lib/yoto-account.d.ts +163 -0
  84. package/lib/yoto-account.d.ts.map +1 -0
  85. package/lib/yoto-account.js +340 -0
  86. package/lib/yoto-device.d.ts +656 -0
  87. package/lib/yoto-device.d.ts.map +1 -0
  88. package/lib/yoto-device.js +2850 -0
  89. package/package.json +22 -15
  90. package/lib/api-endpoints/test-helpers.d.ts +0 -7
  91. package/lib/api-endpoints/test-helpers.d.ts.map +0 -1
  92. /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
+ }