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
@@ -1,12 +1,73 @@
1
+ /**
2
+ * Create a configured MQTT client for a Yoto device
3
+ * @param {YotoMqttOptions} options - MQTT connection options
4
+ * @returns {YotoMqttClient} Configured Yoto MQTT client
5
+ * @throws {Error} If required options are missing
6
+ *
7
+ * @example
8
+ * ```javascript
9
+ * import { createYotoMqttClient } from 'yoto-nodejs-client/lib/mqtt'
10
+ *
11
+ * const client = createYotoMqttClient({
12
+ * deviceId: 'abc123',
13
+ * accessToken: 'eyJhbGc...'
14
+ * })
15
+ *
16
+ * client.on('events', (message) => {
17
+ * console.log('Playing:', message.trackTitle)
18
+ * })
19
+ *
20
+ * await client.connect()
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```javascript
25
+ * // Disable automatic reconnection
26
+ * const client = createYotoMqttClient({
27
+ * deviceId: 'abc123',
28
+ * accessToken: 'token',
29
+ * mqttOptions: {
30
+ * reconnectPeriod: 0, // Disable auto-reconnect (default is 5000ms)
31
+ * connectTimeout: 30000 // 30 second connection timeout
32
+ * }
33
+ * })
34
+ * ```
35
+ */
1
36
  export function createYotoMqttClient(options: YotoMqttOptions): YotoMqttClient;
37
+ /**
38
+ * MQTT.js client options that can be passed to createYotoMqttClient
39
+ */
40
+ export type MqttClientOptions = Partial<mqtt.IClientOptions>;
2
41
  export type YotoMqttOptions = {
42
+ /**
43
+ * - Device ID to connect to
44
+ */
3
45
  deviceId: string;
46
+ /**
47
+ * - JWT access token for authentication
48
+ */
4
49
  accessToken: string;
50
+ /**
51
+ * - Prefix for MQTT client ID (default: 'DASH')
52
+ */
5
53
  clientIdPrefix?: string;
54
+ /**
55
+ * - MQTT broker URL
56
+ */
6
57
  brokerUrl?: string;
7
- keepalive?: number;
58
+ /**
59
+ * - MQTT broker port
60
+ */
8
61
  port?: number;
62
+ /**
63
+ * - Auto-subscribe to device topics on connect
64
+ */
9
65
  autoSubscribe?: boolean;
66
+ /**
67
+ * - Additional MQTT.js client options (defaults: reconnectPeriod=5000, keepalive=300; cannot override: clientId, username, password, protocol, ALPNProtocols)
68
+ */
69
+ mqttOptions?: MqttClientOptions;
10
70
  };
11
71
  import { YotoMqttClient } from './client.js';
72
+ import mqtt from 'mqtt';
12
73
  //# sourceMappingURL=factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["factory.js"],"names":[],"mappings":"AA2DA,8CApBW,eAAe,GACb,cAAc,CAkE1B;;cAzFa,MAAM;iBACN,MAAM;qBACN,MAAM;gBACN,MAAM;gBACN,MAAM;WACN,MAAM;oBACN,OAAO;;+BAIU,aAAa"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["factory.js"],"names":[],"mappings":"AA0CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,8CAjCW,eAAe,GACb,cAAc,CAmF1B;;;;gCA/GY,OAAO,CAAC,mBAAc,CAAC;;;;;cAKtB,MAAM;;;;iBACN,MAAM;;;;qBACN,MAAM;;;;gBACN,MAAM;;;;WACN,MAAM;;;;oBACN,OAAO;;;;kBACP,iBAAiB;;+BAIA,aAAa;iBAD3B,MAAM"}
@@ -13,15 +13,20 @@
13
13
  // MQTT Factory: Create properly configured MQTT clients for Yoto devices
14
14
  // ============================================================================
15
15
 
16
+ /**
17
+ * MQTT.js client options that can be passed to createYotoMqttClient
18
+ * @typedef {Partial<IClientOptions>} MqttClientOptions
19
+ */
20
+
16
21
  /**
17
22
  * @typedef {Object} YotoMqttOptions
18
23
  * @property {string} deviceId - Device ID to connect to
19
24
  * @property {string} accessToken - JWT access token for authentication
20
25
  * @property {string} [clientIdPrefix='DASH'] - Prefix for MQTT client ID (default: 'DASH')
21
26
  * @property {string} [brokerUrl='wss://aqrphjqbp3u2z-ats.iot.eu-west-2.amazonaws.com'] - MQTT broker URL
22
- * @property {number} [keepalive=300] - Keepalive interval in seconds
23
27
  * @property {number} [port=443] - MQTT broker port
24
28
  * @property {boolean} [autoSubscribe=true] - Auto-subscribe to device topics on connect
29
+ * @property {MqttClientOptions} [mqttOptions] - Additional MQTT.js client options (defaults: reconnectPeriod=5000, keepalive=300; cannot override: clientId, username, password, protocol, ALPNProtocols)
25
30
  */
26
31
 
27
32
  import mqtt from 'mqtt'
@@ -56,6 +61,19 @@ import {
56
61
  *
57
62
  * await client.connect()
58
63
  * ```
64
+ *
65
+ * @example
66
+ * ```javascript
67
+ * // Disable automatic reconnection
68
+ * const client = createYotoMqttClient({
69
+ * deviceId: 'abc123',
70
+ * accessToken: 'token',
71
+ * mqttOptions: {
72
+ * reconnectPeriod: 0, // Disable auto-reconnect (default is 5000ms)
73
+ * connectTimeout: 30000 // 30 second connection timeout
74
+ * }
75
+ * })
76
+ * ```
59
77
  */
60
78
  export function createYotoMqttClient (options) {
61
79
  // Validate required options
@@ -73,9 +91,9 @@ export function createYotoMqttClient (options) {
73
91
  accessToken,
74
92
  clientIdPrefix = 'DASH',
75
93
  brokerUrl = MQTT_BROKER_URL,
76
- keepalive = MQTT_KEEPALIVE,
77
94
  port = MQTT_PORT,
78
- autoSubscribe = true
95
+ autoSubscribe = true,
96
+ mqttOptions: additionalMqttOptions = {}
79
97
  } = options
80
98
 
81
99
  // Build MQTT client ID
@@ -85,15 +103,19 @@ export function createYotoMqttClient (options) {
85
103
  const username = `${deviceId}?x-amz-customauthorizer-name=${MQTT_AUTH_NAME}`
86
104
 
87
105
  // Create MQTT connection options
106
+ // Merge additional options first, then override with required Yoto settings
88
107
  /** @type {IClientOptions} */
89
108
  const mqttOptions = {
109
+ // Defaults (can be overridden by additionalMqttOptions)
110
+ reconnectPeriod: 5000, // Default: auto-reconnect after 5 seconds
111
+ keepalive: MQTT_KEEPALIVE, // Default: 300 seconds (5 minutes)
112
+ ...additionalMqttOptions, // Allow overriding defaults
113
+ // Required Yoto-specific settings (cannot be overridden)
90
114
  clientId,
91
115
  username,
92
116
  password: accessToken,
93
- keepalive,
94
117
  port,
95
118
  protocol: MQTT_PROTOCOL,
96
- reconnectPeriod: 0, // Disable auto-reconnect, handle manually
97
119
  ALPNProtocols: MQTT_ALPN_PROTOCOLS
98
120
  }
99
121
 
@@ -1,13 +1,13 @@
1
1
  import test from 'node:test'
2
2
  import assert from 'node:assert'
3
3
  import { getDevices } from '../api-endpoints/devices.js'
4
- import { loadTestTokens, logResponse } from '../api-endpoints/test-helpers.js'
4
+ import { loadTestTokens, logResponse } from '../api-endpoints/endpoint-test-helpers.js'
5
5
  import { createYotoMqttClient } from './index.js'
6
6
 
7
7
  const { accessToken } = loadTestTokens()
8
8
 
9
9
  test('MQTT client', async (t) => {
10
- await t.test('should connect, request status/events, and receive response and events messages', async () => {
10
+ await t.test('should connect, request status/events, and receive response and events messages (status-legacy is optional)', async () => {
11
11
  // Get an online device
12
12
  const response = await getDevices({ accessToken })
13
13
  assert.ok(response.devices.length > 0, 'Should have at least one device')
@@ -28,23 +28,26 @@ test('MQTT client', async (t) => {
28
28
  let eventsResponseReceived = false
29
29
  let eventsMessageReceived = false
30
30
  let statusMessageReceived = false
31
+ let statusLegacyMessageReceived = false
31
32
  /** @type {Error[]} */
32
33
  const errors = []
33
34
 
34
35
  // Setup message handlers
35
- mqttClient.on('events', (message) => {
36
- logResponse('MQTT events message', message)
36
+ mqttClient.on('events', (topic, payload) => {
37
+ logResponse('MQTT events message', { topic, payload })
37
38
 
38
39
  try {
39
40
  // Validate events message structure
40
- assert.ok(message, 'Events message should exist')
41
+ assert.ok(payload, 'Events message should exist')
42
+ assert.ok(typeof topic === 'string', 'Topic should be a string')
43
+ assert.ok(topic.includes('/data/events'), 'Topic should contain /data/events')
41
44
 
42
45
  // Events messages are partial - not all fields are always present
43
- if (message.playbackStatus !== undefined) {
44
- assert.ok(typeof message.playbackStatus === 'string', 'playbackStatus should be string')
46
+ if (payload.playbackStatus !== undefined) {
47
+ assert.ok(typeof payload.playbackStatus === 'string', 'playbackStatus should be string')
45
48
  }
46
- if (message.cardId !== undefined) {
47
- assert.ok(typeof message.cardId === 'string', 'cardId should be string')
49
+ if (payload.cardId !== undefined) {
50
+ assert.ok(typeof payload.cardId === 'string', 'cardId should be string')
48
51
  }
49
52
 
50
53
  eventsMessageReceived = true
@@ -53,18 +56,20 @@ test('MQTT client', async (t) => {
53
56
  }
54
57
  })
55
58
 
56
- mqttClient.on('status', (message) => {
57
- logResponse('MQTT status message', message)
59
+ mqttClient.on('status', (topic, payload) => {
60
+ logResponse('MQTT status message', { topic, payload })
58
61
 
59
62
  try {
60
63
  // Validate status message structure
61
- assert.ok(message, 'Status message should exist')
62
- assert.ok(message.status, 'Status message should have status object')
63
- assert.ok(typeof message.status.statusVersion === 'number', 'Should have statusVersion number')
64
- assert.ok(typeof message.status.fwVersion === 'string', 'Should have fwVersion string')
65
- assert.ok(typeof message.status.productType === 'string', 'Should have productType string')
66
- assert.ok(typeof message.status.batteryLevel === 'number', 'Should have batteryLevel number')
67
- assert.ok(typeof message.status.volume === 'number', 'Should have volume number')
64
+ assert.ok(payload, 'Status message should exist')
65
+ assert.ok(typeof topic === 'string', 'Topic should be a string')
66
+ assert.ok(topic.includes('/data/status'), 'Topic should contain /data/status')
67
+ assert.ok(payload.status, 'Status message should have status object')
68
+ assert.ok(typeof payload.status.statusVersion === 'number', 'Should have statusVersion number')
69
+ assert.ok(typeof payload.status.fwVersion === 'string', 'Should have fwVersion string')
70
+ assert.ok(typeof payload.status.productType === 'string', 'Should have productType string')
71
+ assert.ok(typeof payload.status.batteryLevel === 'number', 'Should have batteryLevel number')
72
+ assert.ok(typeof payload.status.volume === 'number', 'Should have volume number')
68
73
 
69
74
  statusMessageReceived = true
70
75
  } catch (err) {
@@ -72,28 +77,71 @@ test('MQTT client', async (t) => {
72
77
  }
73
78
  })
74
79
 
75
- mqttClient.on('response', (message) => {
76
- logResponse('MQTT response message', message)
80
+ mqttClient.on('status-legacy', (topic, payload) => {
81
+ logResponse('MQTT status-legacy message', { topic, payload })
82
+
83
+ try {
84
+ // Validate legacy status message structure
85
+ assert.ok(payload, 'Legacy status message should exist')
86
+ assert.ok(typeof topic === 'string', 'Topic should be a string')
87
+ assert.ok(!topic.includes('/data/'), 'Legacy topic should NOT contain /data/')
88
+ assert.ok(payload.status, 'Legacy status message should have status object')
89
+
90
+ // Validate lifecycle fields unique to legacy format
91
+ assert.ok(typeof payload.status.statusVersion === 'number', 'Should have statusVersion number')
92
+ assert.ok(typeof payload.status.fwVersion === 'string', 'Should have fwVersion string')
93
+
94
+ // shutDown field is the key field for lifecycle events
95
+ if (payload.status.shutDown !== undefined) {
96
+ assert.ok(typeof payload.status.shutDown === 'string', 'shutDown should be string')
97
+ }
98
+
99
+ // upTime and utcTime are key for startup detection
100
+ if (payload.status.upTime !== undefined) {
101
+ assert.ok(typeof payload.status.upTime === 'number', 'upTime should be number')
102
+ }
103
+ if (payload.status.utcTime !== undefined) {
104
+ assert.ok(typeof payload.status.utcTime === 'number', 'utcTime should be number')
105
+ }
106
+
107
+ // Hardware diagnostic fields unique to legacy
108
+ if (payload.status.battery !== undefined) {
109
+ assert.ok(typeof payload.status.battery === 'number', 'battery should be number')
110
+ }
111
+ if (payload.status.wifiStrength !== undefined) {
112
+ assert.ok(typeof payload.status.wifiStrength === 'number', 'wifiStrength should be number')
113
+ }
114
+
115
+ statusLegacyMessageReceived = true
116
+ } catch (err) {
117
+ errors.push(/** @type {Error} */ (err))
118
+ }
119
+ })
120
+
121
+ mqttClient.on('response', (topic, payload) => {
122
+ logResponse('MQTT response message', { topic, payload })
77
123
 
78
124
  try {
79
125
  // Validate response message structure
80
- assert.ok(message, 'Response message should exist')
81
- assert.ok(message.status, 'Response should have status object')
82
- assert.ok(typeof message.status.req_body === 'string', 'Response should have req_body string')
126
+ assert.ok(payload, 'Response message should exist')
127
+ assert.ok(typeof topic === 'string', 'Topic should be a string')
128
+ assert.ok(topic.includes('/response'), 'Topic should contain /response')
129
+ assert.ok(payload.status, 'Response should have status object')
130
+ assert.ok(typeof payload.status.req_body === 'string', 'Response should have req_body string')
83
131
 
84
132
  // Check if status request was acknowledged (field name is 'status/request')
85
- if (message.status['status/request']) {
133
+ if (payload.status['status/request']) {
86
134
  assert.ok(
87
- message.status['status/request'] === 'OK' || message.status['status/request'] === 'FAIL',
135
+ payload.status['status/request'] === 'OK' || payload.status['status/request'] === 'FAIL',
88
136
  'Status request field should be OK or FAIL'
89
137
  )
90
138
  statusResponseReceived = true
91
139
  }
92
140
 
93
141
  // Check if events request was acknowledged
94
- if (message.status.events) {
142
+ if (payload.status.events) {
95
143
  assert.ok(
96
- message.status.events === 'OK' || message.status.events === 'FAIL',
144
+ payload.status.events === 'OK' || payload.status.events === 'FAIL',
97
145
  'Events request field should be OK or FAIL'
98
146
  )
99
147
  eventsResponseReceived = true
@@ -125,7 +173,7 @@ test('MQTT client', async (t) => {
125
173
  // Wait for messages (with timeout)
126
174
  await new Promise((resolve, reject) => {
127
175
  timeoutId = setTimeout(() => {
128
- reject(new Error(`Timeout waiting for messages. statusResponse=${statusResponseReceived}, eventsResponse=${eventsResponseReceived}, eventsMessage=${eventsMessageReceived}, statusMessage=${statusMessageReceived}`))
176
+ reject(new Error(`Timeout waiting for messages. statusResponse=${statusResponseReceived}, eventsResponse=${eventsResponseReceived}, eventsMessage=${eventsMessageReceived}, statusMessage=${statusMessageReceived}, statusLegacy=${statusLegacyMessageReceived} (optional)`))
129
177
  }, 5000) // 5 second timeout
130
178
 
131
179
  checkIntervalId = setInterval(() => {
@@ -134,6 +182,8 @@ test('MQTT client', async (t) => {
134
182
  clearInterval(checkIntervalId)
135
183
  reject(errors[0])
136
184
  }
185
+ // Note: status-legacy is NOT required - it doesn't respond to requestStatus()
186
+ // It only emits on real-time lifecycle events (shutdown/startup) or 5-minute periodic updates
137
187
  if (statusResponseReceived && eventsResponseReceived && eventsMessageReceived && statusMessageReceived) {
138
188
  clearTimeout(timeoutId)
139
189
  clearInterval(checkIntervalId)
@@ -147,6 +197,13 @@ test('MQTT client', async (t) => {
147
197
  assert.ok(eventsResponseReceived, 'Should have received events request response')
148
198
  assert.ok(eventsMessageReceived, 'Should have received events data message')
149
199
  assert.ok(statusMessageReceived, 'Should have received status data message')
200
+
201
+ // Note: status-legacy message is optional in this test because it does NOT respond to requestStatus()
202
+ // It only emits on real-time lifecycle events (shutdown/startup) or 5-minute periodic updates
203
+ // If we happened to catch one during the test window, validate it was received correctly
204
+ if (statusLegacyMessageReceived) {
205
+ console.log('✓ Received optional status-legacy message with lifecycle events')
206
+ }
150
207
  } catch (err) {
151
208
  // Clean up timers on error
152
209
  if (timeoutId) clearTimeout(timeoutId)
@@ -1,34 +1,219 @@
1
+ /**
2
+ * Get the events topics for a device
3
+ *
4
+ * NOTE: Old undocumented format device/{id}/events exists but returns different low-level
5
+ * hardware data. Do not use. Only the documented /data/ path is supported.
6
+ *
7
+ * @param {string} deviceId - Device ID
8
+ * @returns {string[]} Events topics
9
+ */
1
10
  export function getEventsTopic(deviceId: string): string[];
11
+ /**
12
+ * Get the status topics for a device
13
+ *
14
+ * Subscribes to BOTH documented and legacy status topics:
15
+ * - device/{id}/data/status: Documented format - responds to requestStatus(), auto-publishes every 5 min
16
+ * - device/{id}/status: Legacy format - does NOT respond to requests, emits on lifecycle events + every 5 min
17
+ *
18
+ * The legacy topic is the ONLY source for:
19
+ * - shutDown field: 'nA' = device running, any other value = device shutting down/shut down
20
+ * - Startup detection: shutDown='nA' + low upTime values + utcTime: 0 after power on
21
+ * - Full hardware diagnostics: battery voltage, memory stats, temperatures
22
+ *
23
+ * Key behavior differences:
24
+ * - Documented topic: Responds immediately to requestStatus() command
25
+ * - Legacy topic: Emits real-time on shutdown/startup events, plus periodic updates every 5 minutes
26
+ * - Legacy topic does NOT respond to requestStatus() - it's passive/event-driven only
27
+ *
28
+ * Both topics are necessary - the documented /data/ path does not include lifecycle events.
29
+ *
30
+ * @param {string} deviceId - Device ID
31
+ * @returns {string[]} Status topics (both documented and legacy)
32
+ */
2
33
  export function getStatusTopic(deviceId: string): string[];
34
+ /**
35
+ * Get the response topic for a device (subscribe)
36
+ * @param {string} deviceId - Device ID
37
+ * @returns {string} Response topic
38
+ */
3
39
  export function getResponseTopic(deviceId: string): string;
40
+ /**
41
+ * Get all subscription topics for a device
42
+ *
43
+ * Subscribes to documented formats PLUS legacy status topic:
44
+ * - device/{id}/data/events - Event messages (documented)
45
+ * - device/{id}/data/status - Status messages (documented)
46
+ * - device/{id}/status - Legacy status (REQUIRED for shutdown/startup lifecycle events)
47
+ * - device/{id}/response - Command responses (documented)
48
+ *
49
+ * The legacy status topic is necessary because the documented /data/ topics do not
50
+ * include device lifecycle events (shutdown, startup) which are critical for proper
51
+ * device state management.
52
+ *
53
+ * @param {string} deviceId - Device ID
54
+ * @returns {string[]} Array of topics to subscribe to
55
+ */
4
56
  export function getSubscriptionTopics(deviceId: string): string[];
57
+ /**
58
+ * Get a command topic for a device (publish)
59
+ * @param {string} deviceId - Device ID
60
+ * @param {string} resource - Command resource (e.g., 'volume', 'ambients', 'card')
61
+ * @param {string} [action] - Command action (e.g., 'set', 'start', 'stop')
62
+ * @returns {string} Command topic
63
+ */
5
64
  export function getCommandTopic(deviceId: string, resource: string, action?: string): string;
65
+ /**
66
+ * Parse a topic string to extract device ID and message type
67
+ *
68
+ * Distinguishes between documented and legacy status topics:
69
+ * - device/{id}/data/status -> messageType: 'status' (documented)
70
+ * - device/{id}/status -> messageType: 'status-legacy' (for lifecycle events)
71
+ *
72
+ * @param {string} topic - Full MQTT topic string
73
+ * @returns {{ deviceId: string, messageType: YotoMqttTopicType | 'unknown' }} Parsed topic info
74
+ */
6
75
  export function parseTopic(topic: string): {
7
76
  deviceId: string;
8
77
  messageType: YotoMqttTopicType | "unknown";
9
78
  };
79
+ /**
80
+ * Get the events request command topic
81
+ * @param {string} deviceId - Device ID
82
+ * @returns {string} Events request topic
83
+ */
10
84
  export function getEventsRequestTopic(deviceId: string): string;
85
+ /**
86
+ * Get the status request command topic
87
+ * @param {string} deviceId - Device ID
88
+ * @returns {string} Status request topic
89
+ */
11
90
  export function getStatusRequestTopic(deviceId: string): string;
91
+ /**
92
+ * Get the volume set command topic
93
+ * @param {string} deviceId - Device ID
94
+ * @returns {string} Volume set topic
95
+ */
12
96
  export function getVolumeSetTopic(deviceId: string): string;
97
+ /**
98
+ * Get the ambients set command topic
99
+ * @param {string} deviceId - Device ID
100
+ * @returns {string} Ambients set topic
101
+ */
13
102
  export function getAmbientsSetTopic(deviceId: string): string;
103
+ /**
104
+ * Get the sleep timer set command topic
105
+ * @param {string} deviceId - Device ID
106
+ * @returns {string} Sleep timer set topic
107
+ */
14
108
  export function getSleepTimerSetTopic(deviceId: string): string;
109
+ /**
110
+ * Get the reboot command topic
111
+ * @param {string} deviceId - Device ID
112
+ * @returns {string} Reboot topic
113
+ */
15
114
  export function getRebootTopic(deviceId: string): string;
115
+ /**
116
+ * Get the card start command topic
117
+ * @param {string} deviceId - Device ID
118
+ * @returns {string} Card start topic
119
+ */
16
120
  export function getCardStartTopic(deviceId: string): string;
121
+ /**
122
+ * Get the card stop command topic
123
+ * @param {string} deviceId - Device ID
124
+ * @returns {string} Card stop topic
125
+ */
17
126
  export function getCardStopTopic(deviceId: string): string;
127
+ /**
128
+ * Get the card pause command topic
129
+ * @param {string} deviceId - Device ID
130
+ * @returns {string} Card pause topic
131
+ */
18
132
  export function getCardPauseTopic(deviceId: string): string;
133
+ /**
134
+ * Get the card resume command topic
135
+ * @param {string} deviceId - Device ID
136
+ * @returns {string} Card resume topic
137
+ */
19
138
  export function getCardResumeTopic(deviceId: string): string;
139
+ /**
140
+ * Get the bluetooth on command topic
141
+ * @param {string} deviceId - Device ID
142
+ * @returns {string} Bluetooth on topic
143
+ */
20
144
  export function getBluetoothOnTopic(deviceId: string): string;
145
+ /**
146
+ * Get the bluetooth off command topic
147
+ * @param {string} deviceId - Device ID
148
+ * @returns {string} Bluetooth off topic
149
+ */
21
150
  export function getBluetoothOffTopic(deviceId: string): string;
151
+ /**
152
+ * Get the bluetooth delete bonds command topic
153
+ * @param {string} deviceId - Device ID
154
+ * @returns {string} Bluetooth delete bonds topic
155
+ */
22
156
  export function getBluetoothDeleteBondsTopic(deviceId: string): string;
157
+ /**
158
+ * Get the bluetooth connect command topic
159
+ * @param {string} deviceId - Device ID
160
+ * @returns {string} Bluetooth connect topic
161
+ */
23
162
  export function getBluetoothConnectTopic(deviceId: string): string;
163
+ /**
164
+ * Get the bluetooth disconnect command topic
165
+ * @param {string} deviceId - Device ID
166
+ * @returns {string} Bluetooth disconnect topic
167
+ */
24
168
  export function getBluetoothDisconnectTopic(deviceId: string): string;
169
+ /**
170
+ * Get the bluetooth state command topic
171
+ * @param {string} deviceId - Device ID
172
+ * @returns {string} Bluetooth state topic
173
+ */
25
174
  export function getBluetoothStateTopic(deviceId: string): string;
175
+ /**
176
+ * Get the display preview command topic
177
+ * @param {string} deviceId - Device ID
178
+ * @returns {string} Display preview topic
179
+ */
26
180
  export function getDisplayPreviewTopic(deviceId: string): string;
181
+ /**
182
+ * MQTT Topics for Yoto Players
183
+ *
184
+ * Topic builders and constants for Yoto MQTT communication
185
+ * @see https://yoto.dev/players-mqtt/
186
+ */
187
+ /**
188
+ * MQTT topic type for subscriptions
189
+ * @typedef {'events' | 'status' | 'status-legacy' | 'response'} YotoMqttTopicType
190
+ */
191
+ /**
192
+ * MQTT broker URL for Yoto devices
193
+ */
27
194
  export const MQTT_BROKER_URL: "wss://aqrphjqbp3u2z-ats.iot.eu-west-2.amazonaws.com";
195
+ /**
196
+ * MQTT authorizer name for Yoto authentication
197
+ */
28
198
  export const MQTT_AUTH_NAME: "PublicJWTAuthorizer";
199
+ /**
200
+ * MQTT connection port
201
+ */
29
202
  export const MQTT_PORT: 443;
203
+ /**
204
+ * MQTT protocol
205
+ */
30
206
  export const MQTT_PROTOCOL: "wss";
207
+ /**
208
+ * MQTT keepalive interval in seconds
209
+ */
31
210
  export const MQTT_KEEPALIVE: 300;
211
+ /**
212
+ * ALPN protocols for AWS IoT
213
+ */
32
214
  export const MQTT_ALPN_PROTOCOLS: string[];
33
- export type YotoMqttTopicType = "events" | "status" | "response";
215
+ /**
216
+ * MQTT topic type for subscriptions
217
+ */
218
+ export type YotoMqttTopicType = "events" | "status" | "status-legacy" | "response";
34
219
  //# sourceMappingURL=topics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"topics.d.ts","sourceRoot":"","sources":["topics.js"],"names":[],"mappings":"AAoDA,yCAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;AAQD,yCAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;AAOD,2CAHW,MAAM,GACJ,MAAM,CAIlB;AAcD,gDAHW,MAAM,GACJ,MAAM,EAAE,CAQpB;AASD,0CALW,MAAM,YACN,MAAM,WACN,MAAM,GACJ,MAAM,CAKlB;AAOD,kCAHW,MAAM,GACJ;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAAE,CAuB5E;AASD,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,yCAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,2CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,6CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,+CAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,uDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,sDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,iDAHW,MAAM,GACJ,MAAM,CAIlB;AAOD,iDAHW,MAAM,GACJ,MAAM,CAIlB;AAnRD,8BAA+B,qDAAqD,CAAA;AAKpF,6BAA8B,qBAAqB,CAAA;AAKnD,wBAAyB,GAAG,CAAA;AAK5B,4BAA6B,KAAK,CAAA;AAKlC,6BAA8B,GAAG,CAAA;AAKjC,2CAAqD;gCA/BxC,QAAQ,GAAG,QAAQ,GAAG,UAAU"}
1
+ {"version":3,"file":"topics.d.ts","sourceRoot":"","sources":["topics.js"],"names":[],"mappings":"AA8CA;;;;;;;;GAQG;AACH,yCAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,yCAHW,MAAM,GACJ,MAAM,EAAE,CAOpB;AAED;;;;GAIG;AACH,2CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,gDAHW,MAAM,GACJ,MAAM,EAAE,CAQpB;AAED;;;;;;GAMG;AACH,0CALW,MAAM,YACN,MAAM,WACN,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;;;;;GASG;AACH,kCAHW,MAAM,GACJ;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,iBAAiB,GAAG,SAAS,CAAA;CAAE,CA6B5E;AAID;;;;GAIG;AACH,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,gDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,yCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,2CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,4CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,6CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,8CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,+CAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,uDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,sDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,iDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,iDAHW,MAAM,GACJ,MAAM,CAIlB;AAxUD;;;;;GAKG;AAMH;;;GAGG;AAEH;;GAEG;AACH,8BAA+B,qDAAqD,CAAA;AAEpF;;GAEG;AACH,6BAA8B,qBAAqB,CAAA;AAEnD;;GAEG;AACH,wBAAyB,GAAG,CAAA;AAE5B;;GAEG;AACH,4BAA6B,KAAK,CAAA;AAElC;;GAEG;AACH,6BAA8B,GAAG,CAAA;AAEjC;;GAEG;AACH,2CAAqD;;;;gCA/BxC,QAAQ,GAAG,QAAQ,GAAG,eAAe,GAAG,UAAU"}