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,73 @@
1
+ /**
2
+ * Power state detection helper for Yoto device status
3
+ *
4
+ * Analyzes shutdown field and uptime to determine device power state changes
5
+ */
6
+
7
+ /**
8
+ * Power state detection result
9
+ * @typedef {Object} PowerStateResult
10
+ * @property {'running' | 'shutdown' | 'startup'} state - Device power state
11
+ * @property {string | null} shutDownReason - Shutdown reason if state is 'shutdown'
12
+ * @property {number | null} upTime - Device uptime in seconds if state is 'startup'
13
+ */
14
+
15
+ /**
16
+ * Detect device power state from legacy status
17
+ *
18
+ * @param {string | null | undefined} shutDown - shutDown field from legacy status
19
+ * @param {number | null | undefined} upTime - upTime field from legacy status in seconds
20
+ * @returns {PowerStateResult}
21
+ *
22
+ * @example
23
+ * // Device running normally
24
+ * detectPowerState('nA', 3600)
25
+ * // { state: 'running', shutDownReason: null, upTime: null }
26
+ *
27
+ * @example
28
+ * // Device just started (low uptime)
29
+ * detectPowerState('nA', 45)
30
+ * // { state: 'startup', shutDownReason: null, upTime: 45 }
31
+ *
32
+ * @example
33
+ * // Device shutting down
34
+ * detectPowerState('userShutdown', 3600)
35
+ * // { state: 'shutdown', shutDownReason: 'userShutdown', upTime: null }
36
+ */
37
+ export function detectPowerState (shutDown, upTime) {
38
+ // No shutdown field - assume running
39
+ if (shutDown == null) {
40
+ return {
41
+ state: 'running',
42
+ shutDownReason: null,
43
+ upTime: null
44
+ }
45
+ }
46
+
47
+ // shutDown: 'nA' means "not applicable" - device is running
48
+ if (shutDown === 'nA') {
49
+ // Low uptime indicates recent startup (< 2 minutes)
50
+ if (upTime != null && upTime < 120) {
51
+ return {
52
+ state: 'startup',
53
+ shutDownReason: null,
54
+ upTime
55
+ }
56
+ }
57
+
58
+ // Normal running state
59
+ return {
60
+ state: 'running',
61
+ shutDownReason: null,
62
+ upTime: null
63
+ }
64
+ }
65
+
66
+ // Any other value means device is shutting down or has shut down
67
+ // Examples: 'userShutdown', 'lowBattery', 'timeout', etc.
68
+ return {
69
+ state: 'shutdown',
70
+ shutDownReason: shutDown,
71
+ upTime: null
72
+ }
73
+ }
@@ -0,0 +1,100 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { detectPowerState } from './power-state.js'
4
+
5
+ test('detectPowerState', async (t) => {
6
+ await t.test('should detect normal running state', () => {
7
+ const result = detectPowerState('nA', 3600)
8
+ assert.strictEqual(result.state, 'running')
9
+ assert.strictEqual(result.shutDownReason, null)
10
+ assert.strictEqual(result.upTime, null)
11
+ })
12
+
13
+ await t.test('should detect startup state with low uptime', () => {
14
+ const result = detectPowerState('nA', 45)
15
+ assert.strictEqual(result.state, 'startup')
16
+ assert.strictEqual(result.shutDownReason, null)
17
+ assert.strictEqual(result.upTime, 45)
18
+ })
19
+
20
+ await t.test('should detect startup at exactly 119 seconds', () => {
21
+ const result = detectPowerState('nA', 119)
22
+ assert.strictEqual(result.state, 'startup')
23
+ assert.strictEqual(result.upTime, 119)
24
+ })
25
+
26
+ await t.test('should detect running at exactly 120 seconds (threshold)', () => {
27
+ const result = detectPowerState('nA', 120)
28
+ assert.strictEqual(result.state, 'running')
29
+ assert.strictEqual(result.upTime, null)
30
+ })
31
+
32
+ await t.test('should detect user shutdown', () => {
33
+ const result = detectPowerState('userShutdown', 3600)
34
+ assert.strictEqual(result.state, 'shutdown')
35
+ assert.strictEqual(result.shutDownReason, 'userShutdown')
36
+ assert.strictEqual(result.upTime, null)
37
+ })
38
+
39
+ await t.test('should detect any non-nA value as shutdown', () => {
40
+ const testCases = [
41
+ 'lowBattery',
42
+ 'timeout',
43
+ 'powerLoss',
44
+ 'unknown',
45
+ 'anyOtherValue'
46
+ ]
47
+
48
+ for (const shutDownValue of testCases) {
49
+ const result = detectPowerState(shutDownValue, 1000)
50
+ assert.strictEqual(result.state, 'shutdown')
51
+ assert.strictEqual(result.shutDownReason, shutDownValue)
52
+ assert.strictEqual(result.upTime, null)
53
+ }
54
+ })
55
+
56
+ await t.test('should handle null shutDown as running', () => {
57
+ const result = detectPowerState(null, 500)
58
+ assert.strictEqual(result.state, 'running')
59
+ assert.strictEqual(result.shutDownReason, null)
60
+ assert.strictEqual(result.upTime, null)
61
+ })
62
+
63
+ await t.test('should handle undefined shutDown as running', () => {
64
+ const result = detectPowerState(undefined, 500)
65
+ assert.strictEqual(result.state, 'running')
66
+ assert.strictEqual(result.shutDownReason, null)
67
+ assert.strictEqual(result.upTime, null)
68
+ })
69
+
70
+ await t.test('should handle null upTime with nA', () => {
71
+ const result = detectPowerState('nA', null)
72
+ assert.strictEqual(result.state, 'running')
73
+ assert.strictEqual(result.upTime, null)
74
+ })
75
+
76
+ await t.test('should handle undefined upTime with nA', () => {
77
+ const result = detectPowerState('nA', undefined)
78
+ assert.strictEqual(result.state, 'running')
79
+ assert.strictEqual(result.upTime, null)
80
+ })
81
+
82
+ await t.test('should detect startup with uptime 0', () => {
83
+ const result = detectPowerState('nA', 0)
84
+ assert.strictEqual(result.state, 'startup')
85
+ assert.strictEqual(result.upTime, 0)
86
+ })
87
+
88
+ await t.test('should detect startup with uptime 1', () => {
89
+ const result = detectPowerState('nA', 1)
90
+ assert.strictEqual(result.state, 'startup')
91
+ assert.strictEqual(result.upTime, 1)
92
+ })
93
+
94
+ await t.test('should ignore upTime when shutting down', () => {
95
+ const result = detectPowerState('userShutdown', 10)
96
+ assert.strictEqual(result.state, 'shutdown')
97
+ assert.strictEqual(result.shutDownReason, 'userShutdown')
98
+ assert.strictEqual(result.upTime, null)
99
+ })
100
+ })
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Temperature parsing helper for Yoto device status
3
+ *
4
+ * Temperature field format: colon-separated string where second value (index [1]) is temperature in Celsius
5
+ * Examples: '0:19' → 19°C, '12:20:23' → 20°C, '0:0' → 0°C, '0:unavailable' → null
6
+ * Plain string format: '19' → 19°C, '0' → 0°C
7
+ */
8
+ /**
9
+ * Parse temperature from Yoto status message
10
+ *
11
+ * @param {string | number | null | undefined} tempValue - Temperature value from status message
12
+ * @returns {number | null} Temperature in Celsius, or null if unavailable/invalid
13
+ *
14
+ * @example
15
+ * parseTemperature('0:19') // 19
16
+ * parseTemperature('12:20:23') // 20
17
+ * parseTemperature('0:0') // 0
18
+ * parseTemperature('0:unavailable') // null
19
+ * parseTemperature('0') // 0
20
+ * parseTemperature('19') // 19
21
+ * parseTemperature(null) // null
22
+ */
23
+ export function parseTemperature(tempValue: string | number | null | undefined): number | null;
24
+ //# sourceMappingURL=temperature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temperature.d.ts","sourceRoot":"","sources":["temperature.js"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;;GAcG;AACH,4CAZW,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,MAAM,GAAG,IAAI,CAgDzB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Temperature parsing helper for Yoto device status
3
+ *
4
+ * Temperature field format: colon-separated string where second value (index [1]) is temperature in Celsius
5
+ * Examples: '0:19' → 19°C, '12:20:23' → 20°C, '0:0' → 0°C, '0:unavailable' → null
6
+ * Plain string format: '19' → 19°C, '0' → 0°C
7
+ */
8
+
9
+ /**
10
+ * Parse temperature from Yoto status message
11
+ *
12
+ * @param {string | number | null | undefined} tempValue - Temperature value from status message
13
+ * @returns {number | null} Temperature in Celsius, or null if unavailable/invalid
14
+ *
15
+ * @example
16
+ * parseTemperature('0:19') // 19
17
+ * parseTemperature('12:20:23') // 20
18
+ * parseTemperature('0:0') // 0
19
+ * parseTemperature('0:unavailable') // null
20
+ * parseTemperature('0') // 0
21
+ * parseTemperature('19') // 19
22
+ * parseTemperature(null) // null
23
+ */
24
+ export function parseTemperature (tempValue) {
25
+ // Handle null/undefined
26
+ if (tempValue == null) {
27
+ return null
28
+ }
29
+
30
+ // Convert to string
31
+ const tempStr = String(tempValue)
32
+
33
+ // Empty string
34
+ if (!tempStr) {
35
+ return null
36
+ }
37
+
38
+ // Colon-separated format (e.g., '0:19', '12:20:23', '0:unavailable')
39
+ if (tempStr.includes(':')) {
40
+ const parts = tempStr.split(':')
41
+ const secondValue = parts[1] ?? ''
42
+
43
+ // Parse second value as number
44
+ const parsed = parseFloat(secondValue)
45
+
46
+ // Return null if not a valid number (e.g., 'unavailable', 'notSupported')
47
+ if (isNaN(parsed)) {
48
+ return null
49
+ }
50
+
51
+ return parsed
52
+ }
53
+
54
+ // Fallback: try to parse as plain number (e.g., '19' or '0')
55
+ const parsed = parseFloat(tempStr)
56
+ if (isNaN(parsed)) {
57
+ return null
58
+ }
59
+
60
+ return parsed
61
+ }
@@ -0,0 +1,58 @@
1
+ import test from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { parseTemperature } from './temperature.js'
4
+
5
+ test('parseTemperature', async (t) => {
6
+ await t.test('should parse standard two-part format', () => {
7
+ assert.strictEqual(parseTemperature('0:19'), 19)
8
+ assert.strictEqual(parseTemperature('0:25'), 25)
9
+ assert.strictEqual(parseTemperature('0:0'), 0)
10
+ })
11
+
12
+ await t.test('should parse three-part format using middle value', () => {
13
+ assert.strictEqual(parseTemperature('12:20:23'), 20)
14
+ assert.strictEqual(parseTemperature('10:15:20'), 15)
15
+ assert.strictEqual(parseTemperature('0:42:100'), 42)
16
+ })
17
+
18
+ await t.test('should return null for unavailable/unsupported', () => {
19
+ assert.strictEqual(parseTemperature('0:unavailable'), null)
20
+ assert.strictEqual(parseTemperature('0:notSupported'), null)
21
+ assert.strictEqual(parseTemperature('notSupported'), null)
22
+ })
23
+
24
+ await t.test('should handle null and undefined', () => {
25
+ assert.strictEqual(parseTemperature(null), null)
26
+ assert.strictEqual(parseTemperature(undefined), null)
27
+ })
28
+
29
+ await t.test('should handle empty string', () => {
30
+ assert.strictEqual(parseTemperature(''), null)
31
+ })
32
+
33
+ await t.test('should parse plain numbers as fallback', () => {
34
+ assert.strictEqual(parseTemperature('25'), 25)
35
+ assert.strictEqual(parseTemperature('0'), 0)
36
+ assert.strictEqual(parseTemperature(25), 25)
37
+ assert.strictEqual(parseTemperature(0), 0)
38
+ })
39
+
40
+ await t.test('should handle decimal temperatures', () => {
41
+ assert.strictEqual(parseTemperature('0:19.5'), 19.5)
42
+ assert.strictEqual(parseTemperature('12:20.75:23'), 20.75)
43
+ assert.strictEqual(parseTemperature('19.5'), 19.5)
44
+ })
45
+
46
+ await t.test('should handle negative temperatures', () => {
47
+ assert.strictEqual(parseTemperature('0:-5'), -5)
48
+ assert.strictEqual(parseTemperature('10:-2:15'), -2)
49
+ assert.strictEqual(parseTemperature('-5'), -5)
50
+ })
51
+
52
+ await t.test('should return null for invalid formats', () => {
53
+ assert.strictEqual(parseTemperature('invalid'), null)
54
+ assert.strictEqual(parseTemperature('abc:xyz'), null)
55
+ assert.strictEqual(parseTemperature(':'), null)
56
+ assert.strictEqual(parseTemperature('::'), null)
57
+ })
58
+ })
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @template {Record<PropertyKey, unknown>} T
3
+ * @param {T} obj
4
+ * @returns {(keyof T)[]}
5
+ */
6
+ export function typedKeys<T extends Record<PropertyKey, unknown>>(obj: T): (keyof T)[];
7
+ //# sourceMappingURL=typed-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-keys.d.ts","sourceRoot":"","sources":["typed-keys.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,0BAJ4C,CAAC,SAA/B,MAAM,CAAC,WAAW,EAAE,OAAO,CAAE,OAChC,CAAC,GACC,CAAC,MAAM,CAAC,CAAC,EAAE,CAIvB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @template {Record<PropertyKey, unknown>} T
3
+ * @param {T} obj
4
+ * @returns {(keyof T)[]}
5
+ */
6
+ export function typedKeys (obj) {
7
+ return Object.keys(obj)
8
+ }