yoto-nodejs-client 0.0.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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +736 -0
  3. package/bin/auth.d.ts +3 -0
  4. package/bin/auth.d.ts.map +1 -0
  5. package/bin/auth.js +130 -0
  6. package/bin/content.d.ts +3 -0
  7. package/bin/content.d.ts.map +1 -0
  8. package/bin/content.js +117 -0
  9. package/bin/devices.d.ts +3 -0
  10. package/bin/devices.d.ts.map +1 -0
  11. package/bin/devices.js +239 -0
  12. package/bin/groups.d.ts +3 -0
  13. package/bin/groups.d.ts.map +1 -0
  14. package/bin/groups.js +80 -0
  15. package/bin/icons.d.ts +3 -0
  16. package/bin/icons.d.ts.map +1 -0
  17. package/bin/icons.js +100 -0
  18. package/bin/lib/cli-helpers.d.ts +21 -0
  19. package/bin/lib/cli-helpers.d.ts.map +1 -0
  20. package/bin/lib/cli-helpers.js +140 -0
  21. package/bin/lib/token-helpers.d.ts +14 -0
  22. package/bin/lib/token-helpers.d.ts.map +1 -0
  23. package/bin/lib/token-helpers.js +151 -0
  24. package/bin/refresh-token.d.ts +3 -0
  25. package/bin/refresh-token.d.ts.map +1 -0
  26. package/bin/refresh-token.js +168 -0
  27. package/bin/token-info.d.ts +3 -0
  28. package/bin/token-info.d.ts.map +1 -0
  29. package/bin/token-info.js +351 -0
  30. package/index.d.ts +218 -0
  31. package/index.d.ts.map +1 -0
  32. package/index.js +689 -0
  33. package/lib/api-endpoints/auth.d.ts +56 -0
  34. package/lib/api-endpoints/auth.d.ts.map +1 -0
  35. package/lib/api-endpoints/auth.js +209 -0
  36. package/lib/api-endpoints/auth.test.js +27 -0
  37. package/lib/api-endpoints/constants.d.ts +6 -0
  38. package/lib/api-endpoints/constants.d.ts.map +1 -0
  39. package/lib/api-endpoints/constants.js +31 -0
  40. package/lib/api-endpoints/content.d.ts +275 -0
  41. package/lib/api-endpoints/content.d.ts.map +1 -0
  42. package/lib/api-endpoints/content.js +518 -0
  43. package/lib/api-endpoints/content.test.js +250 -0
  44. package/lib/api-endpoints/devices.d.ts +202 -0
  45. package/lib/api-endpoints/devices.d.ts.map +1 -0
  46. package/lib/api-endpoints/devices.js +404 -0
  47. package/lib/api-endpoints/devices.test.js +483 -0
  48. package/lib/api-endpoints/family-library-groups.d.ts +75 -0
  49. package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
  50. package/lib/api-endpoints/family-library-groups.js +247 -0
  51. package/lib/api-endpoints/family-library-groups.test.js +272 -0
  52. package/lib/api-endpoints/family.d.ts +39 -0
  53. package/lib/api-endpoints/family.d.ts.map +1 -0
  54. package/lib/api-endpoints/family.js +166 -0
  55. package/lib/api-endpoints/family.test.js +184 -0
  56. package/lib/api-endpoints/helpers.d.ts +29 -0
  57. package/lib/api-endpoints/helpers.d.ts.map +1 -0
  58. package/lib/api-endpoints/helpers.js +104 -0
  59. package/lib/api-endpoints/icons.d.ts +62 -0
  60. package/lib/api-endpoints/icons.d.ts.map +1 -0
  61. package/lib/api-endpoints/icons.js +201 -0
  62. package/lib/api-endpoints/icons.test.js +118 -0
  63. package/lib/api-endpoints/media.d.ts +37 -0
  64. package/lib/api-endpoints/media.d.ts.map +1 -0
  65. package/lib/api-endpoints/media.js +155 -0
  66. package/lib/api-endpoints/test-helpers.d.ts +7 -0
  67. package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
  68. package/lib/api-endpoints/test-helpers.js +64 -0
  69. package/lib/mqtt/client.d.ts +124 -0
  70. package/lib/mqtt/client.d.ts.map +1 -0
  71. package/lib/mqtt/client.js +558 -0
  72. package/lib/mqtt/commands.d.ts +69 -0
  73. package/lib/mqtt/commands.d.ts.map +1 -0
  74. package/lib/mqtt/commands.js +238 -0
  75. package/lib/mqtt/factory.d.ts +12 -0
  76. package/lib/mqtt/factory.d.ts.map +1 -0
  77. package/lib/mqtt/factory.js +107 -0
  78. package/lib/mqtt/index.d.ts +5 -0
  79. package/lib/mqtt/index.d.ts.map +1 -0
  80. package/lib/mqtt/index.js +81 -0
  81. package/lib/mqtt/mqtt.test.js +168 -0
  82. package/lib/mqtt/topics.d.ts +34 -0
  83. package/lib/mqtt/topics.d.ts.map +1 -0
  84. package/lib/mqtt/topics.js +295 -0
  85. package/lib/pkg.cjs +3 -0
  86. package/lib/pkg.d.cts +70 -0
  87. package/lib/pkg.d.cts.map +1 -0
  88. package/lib/token.d.ts +29 -0
  89. package/lib/token.d.ts.map +1 -0
  90. package/lib/token.js +240 -0
  91. package/package.json +91 -0
  92. package/yoto.png +0 -0
@@ -0,0 +1,483 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { getDevices, getDeviceStatus, getDeviceConfig } from './devices.js'
4
+ import { YotoAPIError } from './helpers.js'
5
+ import { loadTestTokens, logResponse } from './test-helpers.js'
6
+
7
+ const { accessToken } = loadTestTokens()
8
+
9
+ test('getDevices', async (t) => {
10
+ await t.test('should fetch user devices', async () => {
11
+ const response = await getDevices({
12
+ accessToken
13
+ })
14
+
15
+ // Log response for type verification and documentation
16
+ logResponse('GET /device-v2/devices/mine', response)
17
+
18
+ // Validate response structure matches YotoDevicesResponse
19
+ assert.ok(response, 'Response should exist')
20
+ assert.ok(Array.isArray(response.devices), 'Response should have devices array')
21
+ assert.ok(response.devices.length > 0, 'User should have at least one device')
22
+
23
+ // Validate device structure
24
+ const device = response.devices[0]
25
+ assert.ok(device, 'Device should exist')
26
+ assert.ok(typeof device.deviceId === 'string', 'Device should have deviceId string')
27
+ assert.ok(typeof device.name === 'string', 'Device should have name string')
28
+ assert.ok(typeof device.description === 'string', 'Device should have description string')
29
+ assert.ok(typeof device.online === 'boolean', 'Device should have online boolean')
30
+ assert.ok(typeof device.releaseChannel === 'string', 'Device should have releaseChannel string')
31
+ assert.ok(typeof device.deviceType === 'string', 'Device should have deviceType string')
32
+ assert.ok(typeof device.deviceFamily === 'string', 'Device should have deviceFamily string')
33
+ assert.ok(typeof device.deviceGroup === 'string', 'Device should have deviceGroup string')
34
+ })
35
+
36
+ await t.test('should fail with invalid token', async () => {
37
+ await assert.rejects(
38
+ async () => {
39
+ await getDevices({
40
+ accessToken: 'invalid-token'
41
+ })
42
+ },
43
+ (err) => {
44
+ assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
45
+ assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
46
+ assert.ok(err.body, 'Error should have body')
47
+ return true
48
+ }
49
+ )
50
+ })
51
+ })
52
+
53
+ test('getDeviceStatus', async (t) => {
54
+ /** @type {string[]} */
55
+ let testDeviceIds = []
56
+ /** @type {string | undefined} */
57
+ let onlineDeviceId
58
+ /** @type {string | undefined} */
59
+ let offlineDeviceId
60
+
61
+ // Get real device IDs to test with
62
+ await t.test('setup - get device IDs', async () => {
63
+ const response = await getDevices({
64
+ accessToken
65
+ })
66
+
67
+ assert.ok(response.devices.length > 0, 'User should have at least one device for getDeviceStatus tests')
68
+
69
+ // Try to find one online and one offline device
70
+ onlineDeviceId = response.devices.find(d => d.online)?.deviceId
71
+ offlineDeviceId = response.devices.find(d => !d.online)?.deviceId
72
+
73
+ testDeviceIds = response.devices.slice(0, 2).map(device => device.deviceId).filter(Boolean)
74
+ assert.ok(testDeviceIds.length > 0, 'Should have extracted at least one device ID')
75
+ })
76
+
77
+ await t.test('should fetch device status for valid device ID', async () => {
78
+ const deviceId = testDeviceIds[0]
79
+ assert.ok(deviceId, 'Device ID should exist')
80
+
81
+ const status = await getDeviceStatus({
82
+ accessToken,
83
+ deviceId
84
+ })
85
+
86
+ // Log response for type verification and documentation
87
+ logResponse('GET /device-v2/{deviceId}/status', status)
88
+
89
+ // Validate response structure matches YotoDeviceStatusResponse
90
+ assert.ok(status, 'Response should exist')
91
+ assert.strictEqual(status.deviceId, deviceId, 'Returned device ID should match requested ID')
92
+ assert.ok(typeof status.updatedAt === 'string', 'Status should have updatedAt string')
93
+
94
+ // Optional fields - only validate type if present
95
+ if (status.batteryLevelPercentage !== undefined) {
96
+ assert.ok(typeof status.batteryLevelPercentage === 'number', 'Battery level should be number')
97
+ }
98
+ if (status.isCharging !== undefined) {
99
+ assert.ok(typeof status.isCharging === 'boolean', 'isCharging should be boolean')
100
+ }
101
+ if (status.isOnline !== undefined) {
102
+ assert.ok(typeof status.isOnline === 'boolean', 'isOnline should be boolean')
103
+ }
104
+ if (status.userVolumePercentage !== undefined) {
105
+ assert.ok(typeof status.userVolumePercentage === 'number', 'User volume should be number')
106
+ }
107
+ if (status.systemVolumePercentage !== undefined) {
108
+ assert.ok(typeof status.systemVolumePercentage === 'number', 'System volume should be number')
109
+ }
110
+ if (status.temperatureCelcius !== undefined && status.temperatureCelcius !== null) {
111
+ assert.ok(
112
+ typeof status.temperatureCelcius === 'number' || typeof status.temperatureCelcius === 'string',
113
+ 'Temperature should be number or string'
114
+ )
115
+ }
116
+ if (status.wifiStrength !== undefined) {
117
+ assert.ok(typeof status.wifiStrength === 'number', 'WiFi strength should be number')
118
+ }
119
+ if (status.cardInsertionState !== undefined) {
120
+ assert.ok([0, 1, 2].includes(status.cardInsertionState), 'Card insertion state should be 0, 1, or 2')
121
+ }
122
+ if (status.dayMode !== undefined) {
123
+ assert.ok([-1, 0, 1].includes(status.dayMode), 'Day mode should be -1, 0, or 1')
124
+ }
125
+ if (status.powerSource !== undefined) {
126
+ assert.ok([0, 1, 2, 3].includes(status.powerSource), 'Power source should be 0, 1, 2, or 3')
127
+ }
128
+ })
129
+
130
+ await t.test('should work with multiple device IDs', async () => {
131
+ // Test with multiple devices to ensure consistency
132
+ for (const deviceId of testDeviceIds) {
133
+ const status = await getDeviceStatus({
134
+ accessToken,
135
+ deviceId
136
+ })
137
+
138
+ assert.ok(status, 'Response should exist')
139
+ assert.strictEqual(status.deviceId, deviceId, 'Returned device ID should match requested ID')
140
+ }
141
+ })
142
+
143
+ await t.test('should fetch status for online device if available', async () => {
144
+ if (!onlineDeviceId) {
145
+ return // Skip if no online devices
146
+ }
147
+
148
+ const status = await getDeviceStatus({
149
+ accessToken,
150
+ deviceId: onlineDeviceId
151
+ })
152
+
153
+ // Log online device status
154
+ logResponse('GET /device-v2/{deviceId}/status (ONLINE)', status)
155
+
156
+ assert.ok(status, 'Response should exist')
157
+ assert.strictEqual(status.deviceId, onlineDeviceId, 'Returned device ID should match')
158
+ })
159
+
160
+ await t.test('should fetch status for offline device if available', async () => {
161
+ if (!offlineDeviceId) {
162
+ return // Skip if no offline devices
163
+ }
164
+
165
+ const status = await getDeviceStatus({
166
+ accessToken,
167
+ deviceId: offlineDeviceId
168
+ })
169
+
170
+ // Log offline device status
171
+ logResponse('GET /device-v2/{deviceId}/status (OFFLINE)', status)
172
+
173
+ assert.ok(status, 'Response should exist')
174
+ assert.strictEqual(status.deviceId, offlineDeviceId, 'Returned device ID should match')
175
+ })
176
+
177
+ await t.test('should fail with invalid device ID', async () => {
178
+ await assert.rejects(
179
+ async () => {
180
+ await getDeviceStatus({
181
+ accessToken,
182
+ deviceId: 'invalid-device-id-12345'
183
+ })
184
+ },
185
+ (err) => {
186
+ assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
187
+ assert.ok(err.statusCode === 404, 'Should return 404 for invalid device ID')
188
+ assert.ok(err.body, 'Error should have body')
189
+ return true
190
+ }
191
+ )
192
+ })
193
+
194
+ await t.test('should fail with invalid token', async () => {
195
+ const deviceId = testDeviceIds[0]
196
+ assert.ok(deviceId, 'Device ID should exist')
197
+
198
+ await assert.rejects(
199
+ async () => {
200
+ await getDeviceStatus({
201
+ accessToken: 'invalid-token',
202
+ deviceId
203
+ })
204
+ },
205
+ (err) => {
206
+ assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
207
+ assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
208
+ assert.ok(err.body, 'Error should have body')
209
+ return true
210
+ }
211
+ )
212
+ })
213
+ })
214
+
215
+ test('getDeviceConfig', async (t) => {
216
+ /** @type {string[]} */
217
+ let testDeviceIds = []
218
+ /** @type {Map<string, string>} */
219
+ const deviceTypeMap = new Map()
220
+ /** @type {string | undefined} */
221
+ let onlineDeviceId
222
+ /** @type {string | undefined} */
223
+ let deviceWithAlarmsId
224
+
225
+ // Get real device IDs to test with
226
+ await t.test('setup - get device IDs', async () => {
227
+ const response = await getDevices({
228
+ accessToken
229
+ })
230
+
231
+ assert.ok(response.devices.length > 0, 'User should have at least one device for getDeviceConfig tests')
232
+
233
+ // Try to get different device types for testing
234
+ for (const device of response.devices) {
235
+ if (!deviceTypeMap.has(device.deviceType)) {
236
+ deviceTypeMap.set(device.deviceType, device.deviceId)
237
+ }
238
+ // Also track an online device
239
+ if (device.online && !onlineDeviceId) {
240
+ onlineDeviceId = device.deviceId
241
+ }
242
+ }
243
+
244
+ testDeviceIds = response.devices.slice(0, 2).map(device => device.deviceId).filter(Boolean)
245
+ assert.ok(testDeviceIds.length > 0, 'Should have extracted at least one device ID')
246
+
247
+ console.log(`\nTesting device types: ${Array.from(deviceTypeMap.keys()).join(', ')}`)
248
+ console.log(`Online device for testing: ${onlineDeviceId || 'none'}`)
249
+
250
+ // Try to find a device with alarms configured
251
+ for (const deviceId of testDeviceIds) {
252
+ try {
253
+ const config = await getDeviceConfig({
254
+ accessToken,
255
+ deviceId
256
+ })
257
+ if (config.device.config.alarms && config.device.config.alarms.length > 0) {
258
+ deviceWithAlarmsId = deviceId
259
+ console.log(`Device with alarms found: ${deviceId}`)
260
+ break
261
+ }
262
+ } catch (err) {
263
+ // Skip devices that error
264
+ }
265
+ }
266
+ if (!deviceWithAlarmsId) {
267
+ console.log('No devices with alarms found')
268
+ }
269
+ })
270
+
271
+ await t.test('should fetch device config for valid device ID', async () => {
272
+ const deviceId = testDeviceIds[0]
273
+ assert.ok(deviceId, 'Device ID should exist')
274
+
275
+ const config = await getDeviceConfig({
276
+ accessToken,
277
+ deviceId
278
+ })
279
+
280
+ // Log response for type verification and documentation
281
+ logResponse('GET /device-v2/{deviceId}/config', config)
282
+
283
+ // Validate response structure matches YotoDeviceConfigResponse
284
+ assert.ok(config, 'Response should exist')
285
+ assert.ok(config.device, 'Response should have device property')
286
+ assert.strictEqual(config.device.deviceId, deviceId, 'Returned device ID should match requested ID')
287
+
288
+ // Validate device structure
289
+ assert.ok(typeof config.device.deviceFamily === 'string', 'Device should have deviceFamily string')
290
+ assert.ok(typeof config.device.deviceType === 'string', 'Device should have deviceType string')
291
+ assert.ok(typeof config.device.online === 'boolean', 'Device should have online boolean')
292
+
293
+ // Validate config object exists
294
+ assert.ok(config.device.config, 'Device should have config object')
295
+ assert.ok(typeof config.device.config === 'object', 'Config should be an object')
296
+
297
+ // Validate some config fields
298
+ if (config.device.config.ambientColour !== undefined) {
299
+ assert.ok(typeof config.device.config.ambientColour === 'string', 'ambientColour should be string')
300
+ }
301
+ if (config.device.config.clockFace !== undefined) {
302
+ assert.ok(typeof config.device.config.clockFace === 'string', 'clockFace should be string')
303
+ }
304
+ if (config.device.config.repeatAll !== undefined) {
305
+ assert.ok(typeof config.device.config.repeatAll === 'boolean', 'repeatAll should be boolean')
306
+ }
307
+ if (config.device.config.volumeLevel !== undefined) {
308
+ assert.ok(typeof config.device.config.volumeLevel === 'string', 'volumeLevel should be string')
309
+ }
310
+
311
+ // Validate shortcuts if present (beta feature)
312
+ if (config.device.shortcuts) {
313
+ assert.ok(typeof config.device.shortcuts === 'object', 'Shortcuts should be an object')
314
+ if (config.device.shortcuts.versionId) {
315
+ assert.ok(typeof config.device.shortcuts.versionId === 'string', 'versionId should be string')
316
+ }
317
+ }
318
+ })
319
+
320
+ await t.test('should work with multiple device IDs and types', async () => {
321
+ // Test with different device types to ensure config properties vary appropriately
322
+ for (const [deviceType, deviceId] of deviceTypeMap) {
323
+ const config = await getDeviceConfig({
324
+ accessToken,
325
+ deviceId
326
+ })
327
+
328
+ console.log(`\nDevice type: ${deviceType}, Device ID: ${deviceId}`)
329
+ console.log(` Config keys: ${Object.keys(config.device.config).join(', ')}`)
330
+
331
+ assert.ok(config, 'Response should exist')
332
+ assert.ok(config.device, 'Response should have device property')
333
+ assert.strictEqual(config.device.deviceId, deviceId, 'Returned device ID should match requested ID')
334
+ assert.strictEqual(config.device.deviceType, deviceType, 'Returned device type should match')
335
+
336
+ // Config should always be present but properties may vary by device type
337
+ assert.ok(config.device.config, 'Config should exist for all device types')
338
+ assert.ok(typeof config.device.config === 'object', 'Config should be object')
339
+ }
340
+ })
341
+
342
+ await t.test('should log config for each device type', async () => {
343
+ // Log configurations for different device types to see variations
344
+ for (const [deviceType, deviceId] of deviceTypeMap) {
345
+ const config = await getDeviceConfig({
346
+ accessToken,
347
+ deviceId
348
+ })
349
+
350
+ logResponse(`GET /device-v2/{deviceId}/config (${deviceType})`, config)
351
+ }
352
+ })
353
+
354
+ await t.test('should fetch config for online device if available', async () => {
355
+ if (!onlineDeviceId) {
356
+ console.log('Skipping online device config test - no online devices found')
357
+ return // Skip if no online devices
358
+ }
359
+
360
+ const config = await getDeviceConfig({
361
+ accessToken,
362
+ deviceId: onlineDeviceId
363
+ })
364
+
365
+ // Log online device config
366
+ logResponse('GET /device-v2/{deviceId}/config (ONLINE)', config)
367
+
368
+ assert.ok(config, 'Response should exist')
369
+ assert.ok(config.device, 'Response should have device property')
370
+ assert.strictEqual(config.device.online, true, 'Device should be online')
371
+ assert.strictEqual(config.device.deviceId, onlineDeviceId, 'Device ID should match')
372
+ })
373
+
374
+ await t.test('should fetch config with alarms if available', async () => {
375
+ if (!deviceWithAlarmsId) {
376
+ console.log('Skipping alarm test - no devices with alarms found')
377
+ return // Skip if no devices with alarms
378
+ }
379
+
380
+ const config = await getDeviceConfig({
381
+ accessToken,
382
+ deviceId: deviceWithAlarmsId
383
+ })
384
+
385
+ // Log device with alarms
386
+ logResponse('GET /device-v2/{deviceId}/config (WITH ALARMS)', config)
387
+
388
+ assert.ok(config, 'Response should exist')
389
+ assert.ok(config.device, 'Response should have device property')
390
+ assert.ok(config.device.config, 'Config should exist')
391
+ assert.ok(Array.isArray(config.device.config.alarms), 'Alarms should be an array')
392
+ assert.ok(config.device.config.alarms.length > 0, 'Should have at least one alarm')
393
+
394
+ // Validate alarm structure
395
+ const alarm = config.device.config.alarms[0]
396
+ console.log(`\nAlarm structure: ${JSON.stringify(alarm, null, 2)}`)
397
+ })
398
+
399
+ // TODO: Add tests for updateDeviceConfig (mutation endpoint)
400
+ // - should update device config successfully
401
+ // - should return status 'ok' on successful update
402
+ // - should update device name
403
+ // - should update individual config properties (locale, bluetoothEnabled, etc.)
404
+ // - should update day/night mode settings
405
+ // - should update volume limits
406
+ // - should update display brightness settings
407
+ // - should update alarms array
408
+ // - should validate response structure matches YotoUpdateDeviceConfigResponse
409
+ // - should fail with invalid device ID
410
+ // - should fail with invalid token
411
+ // - should fail with invalid config values
412
+
413
+ // TODO: Add tests for updateDeviceShortcuts (mutation endpoint - beta)
414
+ // - should update shortcuts successfully
415
+ // - should return status 'ok' on successful update
416
+ // - should update day mode shortcuts
417
+ // - should update night mode shortcuts
418
+ // - should update shortcuts with track-play commands
419
+ // - should set card/chapter/track parameters correctly
420
+ // - should handle Yoto Daily dynamic tracks (<yyyymmdd>)
421
+ // - should allow empty content arrays
422
+ // - should validate response structure matches YotoUpdateShortcutsResponse
423
+ // - should fail with invalid device ID
424
+ // - should fail with invalid token
425
+ // - should fail with invalid shortcuts structure
426
+
427
+ // TODO: Add tests for sendDeviceCommand (MQTT mutation endpoint)
428
+ // - should send volume/set command successfully
429
+ // - should send ambients/set command with RGB values
430
+ // - should send sleep-timer/set command
431
+ // - should send card/start command with URI
432
+ // - should send card/stop command
433
+ // - should send card/pause command
434
+ // - should send card/resume command
435
+ // - should send bluetooth/on command
436
+ // - should send bluetooth/off command
437
+ // - should send reboot command
438
+ // - should send status/request command
439
+ // - should send events/request command
440
+ // - should send display/preview command
441
+ // - should return status 'ok' on successful command
442
+ // - should validate response structure matches YotoDeviceCommandResponse
443
+ // - should fail with invalid device ID
444
+ // - should fail with invalid token
445
+ // - should fail with invalid command payload
446
+
447
+ await t.test('should fail with invalid device ID', async () => {
448
+ await assert.rejects(
449
+ async () => {
450
+ await getDeviceConfig({
451
+ accessToken,
452
+ deviceId: 'invalid-device-id-12345'
453
+ })
454
+ },
455
+ (err) => {
456
+ assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
457
+ assert.ok(err.statusCode >= 400, 'Should return error status code for invalid device ID')
458
+ assert.ok(err.body, 'Error should have body')
459
+ return true
460
+ }
461
+ )
462
+ })
463
+
464
+ await t.test('should fail with invalid token', async () => {
465
+ const deviceId = testDeviceIds[0]
466
+ assert.ok(deviceId, 'Device ID should exist')
467
+
468
+ await assert.rejects(
469
+ async () => {
470
+ await getDeviceConfig({
471
+ accessToken: 'invalid-token',
472
+ deviceId
473
+ })
474
+ },
475
+ (err) => {
476
+ assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
477
+ assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
478
+ assert.ok(err.body, 'Error should have body')
479
+ return true
480
+ }
481
+ )
482
+ })
483
+ })
@@ -0,0 +1,75 @@
1
+ export function getGroups({ accessToken, userAgent, requestOptions }: {
2
+ accessToken: string;
3
+ userAgent?: string | undefined;
4
+ requestOptions?: ({
5
+ dispatcher?: import("undici").Dispatcher;
6
+ } & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
7
+ }): Promise<YotoGroup[]>;
8
+ export function createGroup({ token, userAgent, group, requestOptions }: {
9
+ token: string;
10
+ group: YotoCreateGroupRequest;
11
+ userAgent?: string | undefined;
12
+ requestOptions?: ({
13
+ dispatcher?: import("undici").Dispatcher;
14
+ } & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
15
+ }): Promise<YotoGroup>;
16
+ export function getGroup({ accessToken, userAgent, groupId, requestOptions }: {
17
+ accessToken: string;
18
+ groupId: string;
19
+ userAgent?: string | undefined;
20
+ requestOptions?: ({
21
+ dispatcher?: import("undici").Dispatcher;
22
+ } & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
23
+ }): Promise<YotoGroup>;
24
+ export function updateGroup({ accessToken, userAgent, groupId, group, requestOptions }: {
25
+ accessToken: string;
26
+ groupId: string;
27
+ group: YotoUpdateGroupRequest;
28
+ userAgent?: string | undefined;
29
+ requestOptions?: ({
30
+ dispatcher?: import("undici").Dispatcher;
31
+ } & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
32
+ }): Promise<YotoGroup>;
33
+ export function deleteGroup({ accessToken, userAgent, groupId, requestOptions }: {
34
+ accessToken: string;
35
+ groupId: string;
36
+ userAgent?: string | undefined;
37
+ requestOptions?: ({
38
+ dispatcher?: import("undici").Dispatcher;
39
+ } & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
40
+ }): Promise<YotoDeleteGroupResponse>;
41
+ export type YotoGroupsResponse = {
42
+ groups: YotoGroup[];
43
+ };
44
+ export type YotoGroup = {
45
+ id: string;
46
+ name: string;
47
+ familyId: string;
48
+ imageId: string;
49
+ imageUrl: string;
50
+ items: YotoGroupItem[];
51
+ cards: any[];
52
+ createdAt: string;
53
+ lastModifiedAt: string;
54
+ };
55
+ export type YotoGroupItem = {
56
+ contentId: string;
57
+ addedAt: string;
58
+ };
59
+ export type YotoCreateGroupRequest = {
60
+ name: string;
61
+ imageId: string;
62
+ items: YotoGroupItemInput[];
63
+ };
64
+ export type YotoGroupItemInput = {
65
+ contentId: string;
66
+ };
67
+ export type YotoUpdateGroupRequest = {
68
+ name: string;
69
+ imageId: string;
70
+ items: YotoGroupItemInput[];
71
+ };
72
+ export type YotoDeleteGroupResponse = {
73
+ id: string;
74
+ };
75
+ //# sourceMappingURL=family-library-groups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"family-library-groups.d.ts","sourceRoot":"","sources":["family-library-groups.js"],"names":[],"mappings":"AA2DA,sEAhBG;IAAyB,WAAW,EAA3B,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,SAAS,EAAE,CAAC,CA6B/B;AAwCD,yEAnBG;IAAyB,KAAK,EAArB,MAAM;IAC0B,KAAK,EAArC,sBAAsB;IACL,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,SAAS,CAAC,CAoC7B;AAaD,8EANG;IAAyB,WAAW,EAA3B,MAAM;IACU,OAAO,EAAvB,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,SAAS,CAAC,CAmB7B;AAsBD,wFAPG;IAAyB,WAAW,EAA3B,MAAM;IACU,OAAO,EAAvB,MAAM;IAC0B,KAAK,EAArC,sBAAsB;IACL,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,SAAS,CAAC,CAwB7B;AAmBD,iFANG;IAAyB,WAAW,EAA3B,MAAM;IACU,OAAO,EAAvB,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,uBAAuB,CAAC,CAmB3C;;YAxOa,SAAS,EAAE;;;QAMX,MAAM;UACN,MAAM;cACN,MAAM;aACN,MAAM;cACN,MAAM;WACN,aAAa,EAAE;WACf,GAAG,EAAE;eACL,MAAM;oBACN,MAAM;;;eAMN,MAAM;aACN,MAAM;;;UA6CN,MAAM;aACN,MAAM;WACN,kBAAkB,EAAE;;;eAMpB,MAAM;;;UAmFN,MAAM;aACN,MAAM;WACN,kBAAkB,EAAE;;;QA0CpB,MAAM"}