yoto-nodejs-client 0.0.4 → 0.0.6

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 (45) hide show
  1. package/README.md +24 -6
  2. package/bin/auth.js +4 -3
  3. package/bin/device-model.js +25 -5
  4. package/bin/device-tui.js +25 -3
  5. package/bin/devices.js +58 -42
  6. package/bin/lib/cli-helpers.d.ts.map +1 -1
  7. package/bin/lib/cli-helpers.js +3 -1
  8. package/bin/lib/token-helpers.d.ts +4 -2
  9. package/bin/lib/token-helpers.d.ts.map +1 -1
  10. package/bin/lib/token-helpers.js +9 -8
  11. package/bin/refresh-token.js +4 -2
  12. package/bin/token-info.js +2 -2
  13. package/lib/api-client.d.ts +11 -10
  14. package/lib/api-client.d.ts.map +1 -1
  15. package/lib/api-client.js +12 -14
  16. package/lib/api-endpoints/auth.test.js +4 -4
  17. package/lib/api-endpoints/content.test.js +32 -32
  18. package/lib/api-endpoints/devices.d.ts +4 -4
  19. package/lib/api-endpoints/devices.js +2 -2
  20. package/lib/api-endpoints/devices.test.js +45 -45
  21. package/lib/api-endpoints/endpoint-test-helpers.d.ts +3 -4
  22. package/lib/api-endpoints/endpoint-test-helpers.d.ts.map +1 -1
  23. package/lib/api-endpoints/endpoint-test-helpers.js +21 -5
  24. package/lib/api-endpoints/family-library-groups.d.ts +3 -3
  25. package/lib/api-endpoints/family-library-groups.d.ts.map +1 -1
  26. package/lib/api-endpoints/family-library-groups.js +3 -3
  27. package/lib/api-endpoints/family-library-groups.test.js +29 -29
  28. package/lib/api-endpoints/family.test.js +11 -11
  29. package/lib/api-endpoints/icons.test.js +14 -14
  30. package/lib/mqtt/client.d.ts +124 -49
  31. package/lib/mqtt/client.d.ts.map +1 -1
  32. package/lib/mqtt/client.js +132 -50
  33. package/lib/mqtt/factory.d.ts +12 -5
  34. package/lib/mqtt/factory.d.ts.map +1 -1
  35. package/lib/mqtt/factory.js +39 -11
  36. package/lib/mqtt/index.js +2 -1
  37. package/lib/mqtt/mqtt.test.js +25 -22
  38. package/lib/token.d.ts +44 -2
  39. package/lib/token.d.ts.map +1 -1
  40. package/lib/token.js +142 -2
  41. package/lib/yoto-device.d.ts +392 -32
  42. package/lib/yoto-device.d.ts.map +1 -1
  43. package/lib/yoto-device.js +643 -99
  44. package/lib/yoto-device.test.js +193 -0
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -169,7 +169,15 @@ console.log('Current playback:', deviceClient.playback)
169
169
  console.log('Device capabilities:', deviceClient.capabilities)
170
170
 
171
171
  // Control the device
172
- await deviceClient.updateConfig({ maxVolumeLimit: '14' })
172
+ await deviceClient.updateConfig({ maxVolumeLimit: 14 })
173
+ await deviceClient.updateConfig({
174
+ dayDisplayBrightnessAuto: false,
175
+ dayDisplayBrightness: 80
176
+ })
177
+ await deviceClient.updateConfig({
178
+ nightYotoRadioEnabled: true,
179
+ nightYotoRadio: 'favourites'
180
+ })
173
181
  await deviceClient.sendCommand({ volume: 50 })
174
182
 
175
183
  // Stop when done
@@ -938,7 +946,7 @@ Create a stateful device client that manages device state primarily from MQTT wi
938
946
  **State Accessors:**
939
947
  - `deviceClient.device` - Device information
940
948
  - `deviceClient.status` - Current device status (normalized from HTTP/MQTT)
941
- - `deviceClient.config` - Device configuration
949
+ - `deviceClient.config` - Device configuration (normalized numbers/booleans; auto fields split into `<field>Auto` + value)
942
950
  - `deviceClient.shortcuts` - Button shortcuts
943
951
  - `deviceClient.playback` - Current playback state
944
952
  - `deviceClient.capabilities` - Hardware capabilities (sensors, nightlight support, etc.)
@@ -962,8 +970,10 @@ Create a stateful device client that manages device state primarily from MQTT wi
962
970
  - `playbackUpdate(playback, changedFields)` - Playback state changed, passes (playback, changedFields)
963
971
  - `online(metadata)` - Device came online, passes metadata with reason and optional upTime
964
972
  - `offline(metadata)` - Device went offline, passes metadata with reason and optional shutDownReason or timeSinceLastSeen
965
- - `mqttConnected()` - MQTT client connected
966
- - `mqttDisconnected()` - MQTT client disconnected
973
+ - `mqttConnect()` - MQTT client connected
974
+ - `mqttDisconnect(metadata)` - MQTT disconnect packet received, passes metadata with disconnect packet
975
+ - `mqttClose(metadata)` - MQTT connection closed, passes metadata with close reason
976
+ - `mqttReconnect()` - MQTT client is reconnecting
967
977
  - `error(error)` - Error occurred, passes error
968
978
 
969
979
  **Static Properties & Methods:**
@@ -1001,7 +1011,15 @@ console.log('Available colors:', YotoDeviceModel.NIGHTLIGHT_COLORS)
1001
1011
  console.log('Color name:', YotoDeviceModel.getNightlightColorName('0x643600'))
1002
1012
 
1003
1013
  // Control device
1004
- await deviceClient.updateConfig({ maxVolumeLimit: '14' })
1014
+ await deviceClient.updateConfig({ maxVolumeLimit: 14 })
1015
+ await deviceClient.updateConfig({
1016
+ nightDisplayBrightnessAuto: true,
1017
+ nightDisplayBrightness: null
1018
+ })
1019
+ await deviceClient.updateConfig({
1020
+ nightYotoRadioEnabled: false,
1021
+ nightYotoRadio: null
1022
+ })
1005
1023
 
1006
1024
  await deviceClient.stop()
1007
1025
  ```
@@ -1037,7 +1055,7 @@ Create an account manager that automatically discovers and manages all devices f
1037
1055
  - `deviceRemoved(deviceId)` - Device was removed
1038
1056
  - `error(error, context)` - Error occurred (context: { source, deviceId, operation })
1039
1057
 
1040
- **Note:** To listen to individual device events (statusUpdate, configUpdate, playbackUpdate, online, offline, mqttConnected, mqttDisconnected, etc.), access the device models directly via `account.devices` or `account.getDevice(deviceId)` and attach listeners to them.
1058
+ **Note:** To listen to individual device events (statusUpdate, configUpdate, playbackUpdate, online, offline, mqttConnect, mqttDisconnect, mqttClose, mqttReconnect, etc.), access the device models directly via `account.devices` or `account.getDevice(deviceId)` and attach listeners to them.
1041
1059
 
1042
1060
  ```js
1043
1061
  import { YotoAccount } from 'yoto-nodejs-client'
package/bin/auth.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { printHelpText } from 'argsclopts'
8
8
  import { parseArgs } from 'node:util'
9
+ import { join } from 'node:path'
9
10
  import { YotoClient } from '../index.js'
10
11
  import { pkg } from '../lib/pkg.cjs'
11
12
  import { DEFAULT_CLIENT_ID } from '../lib/api-endpoints/constants.js'
@@ -41,7 +42,7 @@ if (args.values['help']) {
41
42
 
42
43
  const clientId = String(args.values['client-id'] || process.env['YOTO_CLIENT_ID'] || DEFAULT_CLIENT_ID)
43
44
 
44
- const outputFile = String(args.values['output'] || '.env')
45
+ const outputFile = String(args.values['output'] || join(process.cwd(), '.env'))
45
46
 
46
47
  async function main () {
47
48
  printHeader('Yoto Device Flow Authentication')
@@ -93,9 +94,9 @@ async function main () {
93
94
  // Success! Save tokens to .env file
94
95
  console.log('\nāœ… Authorization successful!\n')
95
96
 
96
- await saveTokensToEnv(outputFile, tokens, clientId)
97
+ const { resolvedPath } = await saveTokensToEnv(outputFile, tokens, clientId)
97
98
 
98
- console.log(`✨ Tokens saved to ${outputFile}`)
99
+ console.log(`✨ Tokens saved to ${resolvedPath}`)
99
100
  console.log('\nYou can now use these environment variables:')
100
101
  console.log(' - YOTO_ACCESS_TOKEN')
101
102
  console.log(' - YOTO_REFRESH_TOKEN')
@@ -191,8 +191,8 @@ async function main () {
191
191
  console.log(` Bluetooth Enabled: ${config.bluetoothEnabled}`)
192
192
  console.log(` BT Headphones Enabled: ${config.btHeadphonesEnabled}`)
193
193
  console.log(` Clock Face: ${config.clockFace}`)
194
- console.log(` Day Display Brightness: ${config.dayDisplayBrightness}`)
195
- console.log(` Night Display Brightness: ${config.nightDisplayBrightness}`)
194
+ console.log(` šŸ’” Day Display Brightness: ${config.dayDisplayBrightness}`)
195
+ console.log(` šŸ’” Night Display Brightness: ${config.nightDisplayBrightness}`)
196
196
  console.log(` Shutdown Timeout: ${config.shutdownTimeout}`)
197
197
  console.log(` Volume Level: ${config.volumeLevel}`)
198
198
  console.log(` Ambient Colour: ${config.ambientColour}`)
@@ -202,7 +202,9 @@ async function main () {
202
202
  // Show only changed fields
203
203
  console.log(`\nāš™ļø CONFIG UPDATE [${timestamp}]: (${changedFields.size} change${changedFields.size === 1 ? '' : 's'})`)
204
204
  for (const field of changedFields) {
205
- console.log(` ${field}: ${config[field]}`)
205
+ // Highlight brightness changes
206
+ const prefix = (field === 'dayDisplayBrightness' || field === 'nightDisplayBrightness') ? 'šŸ’” ' : ''
207
+ console.log(` ${prefix}${field}: ${config[field]}`)
206
208
  }
207
209
  } else {
208
210
  console.log(`\nāš™ļø CONFIG UPDATE [${timestamp}]: (no changes)`)
@@ -284,14 +286,28 @@ async function main () {
284
286
  }
285
287
  })
286
288
 
287
- deviceModel.on('mqttConnected', () => {
289
+ deviceModel.on('mqttConnect', () => {
288
290
  const timestamp = new Date().toISOString()
289
291
  console.log(`\nšŸ”Œ MQTT CONNECTED [${timestamp}]`)
290
292
  })
291
293
 
292
- deviceModel.on('mqttDisconnected', () => {
294
+ deviceModel.on('mqttDisconnect', (metadata) => {
293
295
  const timestamp = new Date().toISOString()
294
296
  console.log(`\nšŸ”Œ MQTT DISCONNECTED [${timestamp}]`)
297
+ const reasonCode = metadata.packet.reasonCode ?? 'unknown'
298
+ console.log(` Reason Code: ${reasonCode}`)
299
+ console.log(' Packet:', metadata.packet)
300
+ })
301
+
302
+ deviceModel.on('mqttClose', (metadata) => {
303
+ const timestamp = new Date().toISOString()
304
+ console.log(`\nšŸ”Œ MQTT CLOSED [${timestamp}]`)
305
+ console.log(` Reason: ${metadata.reason}`)
306
+ })
307
+
308
+ deviceModel.on('mqttReconnect', () => {
309
+ const timestamp = new Date().toISOString()
310
+ console.log(`\nšŸ”Œ MQTT RECONNECTING [${timestamp}]`)
295
311
  })
296
312
 
297
313
  deviceModel.on('error', (error) => {
@@ -329,6 +345,10 @@ async function main () {
329
345
  console.log(` Initialized: ${deviceModel.initialized}`)
330
346
  console.log(` Running: ${deviceModel.running}`)
331
347
 
348
+ console.log('\nšŸ’” Current Brightness Config:')
349
+ console.log(` Day Display Brightness: ${deviceModel.config.dayDisplayBrightness}`)
350
+ console.log(` Night Display Brightness: ${deviceModel.config.nightDisplayBrightness}`)
351
+
332
352
  console.log('\nšŸ”§ Capabilities:')
333
353
  console.log(` Temperature Sensor: ${deviceModel.capabilities.hasTemperatureSensor}`)
334
354
  console.log(` Ambient Light Sensor: ${deviceModel.capabilities.hasAmbientLightSensor}`)
package/bin/device-tui.js CHANGED
@@ -725,12 +725,14 @@ async function main () {
725
725
  refreshToken,
726
726
  accessToken,
727
727
  onTokenRefresh: async (tokens) => {
728
- await saveTokensToEnv(envFile, {
728
+ const { resolvedPath } = await saveTokensToEnv(envFile, {
729
729
  access_token: tokens.updatedAccessToken,
730
730
  refresh_token: tokens.updatedRefreshToken,
731
731
  token_type: 'Bearer',
732
732
  expires_in: tokens.updatedExpiresAt - Math.floor(Date.now() / 1000)
733
733
  }, tokens.clientId)
734
+
735
+ console.log(`Auth token refreshed: ${resolvedPath}`)
734
736
  }
735
737
  },
736
738
  deviceOptions: {
@@ -988,7 +990,7 @@ async function main () {
988
990
  screen.render()
989
991
  })
990
992
 
991
- model.on('mqttConnected', () => {
993
+ model.on('mqttConnect', () => {
992
994
  updateDeviceCard(card, index === selectedIndex)
993
995
 
994
996
  if (currentView === 'detail' && detailView.model === model) {
@@ -998,7 +1000,7 @@ async function main () {
998
1000
  screen.render()
999
1001
  })
1000
1002
 
1001
- model.on('mqttDisconnected', () => {
1003
+ model.on('mqttDisconnect', () => {
1002
1004
  updateDeviceCard(card, index === selectedIndex)
1003
1005
 
1004
1006
  if (currentView === 'detail' && detailView.model === model) {
@@ -1008,6 +1010,26 @@ async function main () {
1008
1010
  screen.render()
1009
1011
  })
1010
1012
 
1013
+ model.on('mqttClose', () => {
1014
+ updateDeviceCard(card, index === selectedIndex)
1015
+
1016
+ if (currentView === 'detail' && detailView.model === model) {
1017
+ logToDetail(detailView, 'MQTT closed', 'mqtt')
1018
+ updateDetailView(detailView)
1019
+ }
1020
+ screen.render()
1021
+ })
1022
+
1023
+ model.on('mqttReconnect', () => {
1024
+ updateDeviceCard(card, index === selectedIndex)
1025
+
1026
+ if (currentView === 'detail' && detailView.model === model) {
1027
+ logToDetail(detailView, 'MQTT reconnecting', 'mqtt')
1028
+ updateDetailView(detailView)
1029
+ }
1030
+ screen.render()
1031
+ })
1032
+
1011
1033
  model.on('error', (error) => {
1012
1034
  if (currentView === 'detail' && detailView.model === model) {
1013
1035
  logToDetail(detailView, `Error: ${error.message}`, 'error')
package/bin/devices.js CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  handleCliError,
16
16
  printHeader
17
17
  } from './lib/cli-helpers.js'
18
- import { createYotoMqttClient } from '../lib/mqtt/index.js'
19
18
  import { parseTemperature } from '../lib/helpers/temperature.js'
20
19
 
21
20
  /** @type {ArgscloptsParseArgsOptionsConfig} */
@@ -160,14 +159,46 @@ async function main () {
160
159
  console.log('šŸ”Œ Connecting to MQTT...')
161
160
  console.log('='.repeat(60))
162
161
 
163
- const mqttClient = createYotoMqttClient({
164
- deviceId,
165
- accessToken
162
+ const mqttClient = await client.createMqttClient({ deviceId })
163
+
164
+ // Setup interactive keyboard input (once, before connecting)
165
+ readline.emitKeypressEvents(process.stdin)
166
+ if (process.stdin.isTTY) {
167
+ process.stdin.setRawMode(true)
168
+ }
169
+
170
+ process.stdin.on('keypress', (_str, key) => {
171
+ if (key.ctrl && key.name === 'c') {
172
+ cleanup()
173
+ return
174
+ }
175
+
176
+ if (key.name === 's') {
177
+ const requestId = `interactive-${Date.now()}`
178
+ console.log(`\nāŒØļø Requesting status [${requestId}]...`)
179
+ mqttClient.requestStatus(requestId).catch(err => {
180
+ console.error('āš ļø Failed to request status:', err.message)
181
+ })
182
+ } else if (key.name === 'e') {
183
+ const requestId = `interactive-${Date.now()}`
184
+ console.log(`\nāŒØļø Requesting events [${requestId}]...`)
185
+ mqttClient.requestEvents(requestId).catch(err => {
186
+ console.error('āš ļø Failed to request events:', err.message)
187
+ })
188
+ } else if (key.name === 'return') {
189
+ const timestamp = new Date().toISOString()
190
+ console.log(`\n${'─'.repeat(60)}`)
191
+ console.log(`šŸ“ MARKER [${timestamp}]`)
192
+ console.log(`${'─'.repeat(60)}\n`)
193
+ } else if (key.name === 'q') {
194
+ cleanup()
195
+ }
166
196
  })
167
197
 
168
198
  // Setup message handlers
169
- mqttClient.on('connected', () => {
199
+ mqttClient.on('connect', (connack) => {
170
200
  console.log('āœ… Connected to MQTT broker')
201
+ console.log(` Session present: ${connack.sessionPresent ? 'yes' : 'no'}`)
171
202
  console.log('šŸ“” Subscribed to topics:')
172
203
  console.log(` • device/${deviceId}/data/events (playback events)`)
173
204
  console.log(` • device/${deviceId}/data/status (regular status)`)
@@ -211,40 +242,6 @@ async function main () {
211
242
  }
212
243
 
213
244
  console.log() // Add blank line for spacing
214
-
215
- // Setup interactive keyboard input
216
- readline.emitKeypressEvents(process.stdin)
217
- if (process.stdin.isTTY) {
218
- process.stdin.setRawMode(true)
219
- }
220
-
221
- process.stdin.on('keypress', (_str, key) => {
222
- if (key.ctrl && key.name === 'c') {
223
- cleanup()
224
- return
225
- }
226
-
227
- if (key.name === 's') {
228
- const requestId = `interactive-${Date.now()}`
229
- console.log(`\nāŒØļø Requesting status [${requestId}]...`)
230
- mqttClient.requestStatus(requestId).catch(err => {
231
- console.error('āš ļø Failed to request status:', err.message)
232
- })
233
- } else if (key.name === 'e') {
234
- const requestId = `interactive-${Date.now()}`
235
- console.log(`\nāŒØļø Requesting events [${requestId}]...`)
236
- mqttClient.requestEvents(requestId).catch(err => {
237
- console.error('āš ļø Failed to request events:', err.message)
238
- })
239
- } else if (key.name === 'return') {
240
- const timestamp = new Date().toISOString()
241
- console.log(`\n${'─'.repeat(60)}`)
242
- console.log(`šŸ“ MARKER [${timestamp}]`)
243
- console.log(`${'─'.repeat(60)}\n`)
244
- } else if (key.name === 'q') {
245
- cleanup()
246
- }
247
- })
248
245
  })
249
246
 
250
247
  mqttClient.on('events', (topic, payload) => {
@@ -316,18 +313,37 @@ async function main () {
316
313
  console.dir(payload, { depth: null, colors: true })
317
314
  })
318
315
 
319
- mqttClient.on('disconnected', () => {
320
- console.log('\nāŒ Disconnected from MQTT broker')
316
+ mqttClient.on('disconnect', (metadata) => {
317
+ const timestamp = new Date().toISOString()
318
+ console.log(`\nāŒ MQTT DISCONNECTED [${timestamp}]`)
319
+ const reasonCode = metadata.packet.reasonCode ?? 'unknown'
320
+ console.log(` Reason Code: ${reasonCode}`)
321
+ })
322
+
323
+ mqttClient.on('close', (metadata) => {
324
+ const timestamp = new Date().toISOString()
325
+ console.log(`\nāŒ MQTT CLOSED [${timestamp}]`)
326
+ console.log(` Reason: ${metadata.reason}`)
321
327
  })
322
328
 
323
- mqttClient.on('reconnecting', () => {
329
+ mqttClient.on('reconnect', () => {
324
330
  console.log('\nšŸ”„ Reconnecting to MQTT broker...')
325
331
  })
326
332
 
333
+ mqttClient.on('offline', () => {
334
+ const timestamp = new Date().toISOString()
335
+ console.log(`\nšŸ““ MQTT OFFLINE [${timestamp}]`)
336
+ })
337
+
327
338
  mqttClient.on('error', (error) => {
328
339
  console.error('\nāš ļø MQTT Error:', error.message)
329
340
  })
330
341
 
342
+ mqttClient.on('end', () => {
343
+ const timestamp = new Date().toISOString()
344
+ console.log(`\nšŸ›‘ MQTT END [${timestamp}]`)
345
+ })
346
+
331
347
  // Handle graceful shutdown
332
348
  const cleanup = async () => {
333
349
  console.log('\n\nšŸ›‘ Shutting down...')
@@ -1 +1 @@
1
- {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["cli-helpers.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,oCAFa,gCAAgC,CAoB5C;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;GAKG;AACH,wCAJW;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;CAAE,GACtD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoB5F;AAED;;;;;;;;GAQG;AACH,sFANG;IAAwB,QAAQ,EAAxB,MAAM;IACU,YAAY,EAA5B,MAAM;IACU,WAAW,EAA3B,MAAM;IACW,UAAU;CACnC,GAAU,UAAU,CAsBtB;AAED;;;GAGG;AACH,sCAFW,GAAG,QAyBb;AAED;;;GAGG;AACH,mCAFW,MAAM,QAKhB;sDA1IoD,YAAY;2BAGtC,yBAAyB"}
1
+ {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["cli-helpers.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,oCAFa,gCAAgC,CAoB5C;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;GAKG;AACH,wCAJW;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;CAAE,GACtD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoB5F;AAED;;;;;;;;GAQG;AACH,sFANG;IAAwB,QAAQ,EAAxB,MAAM;IACU,YAAY,EAA5B,MAAM;IACU,WAAW,EAA3B,MAAM;IACW,UAAU;CACnC,GAAU,UAAU,CAwBtB;AAED;;;GAGG;AACH,sCAFW,GAAG,QAyBb;AAED;;;GAGG;AACH,mCAFW,MAAM,QAKhB;sDA5IoD,YAAY;2BAGtC,yBAAyB"}
@@ -86,12 +86,14 @@ export function createYotoClient ({ clientId, refreshToken, accessToken, outputF
86
86
  accessToken,
87
87
  onTokenRefresh: async (tokens) => {
88
88
  // Save tokens if they refresh during operation
89
- await saveTokensToEnv(outputFile, {
89
+ const { resolvedPath } = await saveTokensToEnv(outputFile, {
90
90
  access_token: tokens.updatedAccessToken,
91
91
  refresh_token: tokens.updatedRefreshToken,
92
92
  token_type: 'Bearer',
93
93
  expires_in: tokens.updatedExpiresAt - Math.floor(Date.now() / 1000)
94
94
  }, tokens.clientId)
95
+
96
+ console.log(`Auth token refreshed: ${resolvedPath}`)
95
97
  },
96
98
  onRefreshStart: () => {
97
99
  console.log('\nšŸ”„ Token refresh triggered...')
@@ -32,11 +32,13 @@ export function checkTokenExpiration(token: string, bufferSeconds?: number): {
32
32
  export function decodeJwt(token: string): any;
33
33
  /**
34
34
  * Save tokens to .env file
35
- * @param {string} filename
35
+ * @param {string} envFilePath file path
36
36
  * @param {YotoTokenResponse} tokens
37
37
  * @param {string} clientId
38
38
  */
39
- export function saveTokensToEnv(filename: string, tokens: YotoTokenResponse, clientId: string): Promise<void>;
39
+ export function saveTokensToEnv(envFilePath: string, tokens: YotoTokenResponse, clientId: string): Promise<{
40
+ resolvedPath: string;
41
+ }>;
40
42
  /**
41
43
  * Sleep for a specified number of milliseconds
42
44
  * @param {number} ms
@@ -1 +1 @@
1
- {"version":3,"file":"token-helpers.d.ts","sourceRoot":"","sources":["token-helpers.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,2CAHW,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;GAKG;AACH,qCAJW,MAAM,kBACN,MAAM,GACJ;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAmB/C;AAED;;;;;GAKG;AACH,4CAJW,MAAM,kBACN,MAAM,GACJ;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAa/C;AAED;;;;GAIG;AACH,iCAHW,MAAM,GACJ,GAAG,CASf;AAED;;;;;GAKG;AACH,0CAJW,MAAM,UACN,iBAAiB,YACjB,MAAM,iBA8DhB;AAED;;;GAGG;AACH,0BAFW,MAAM,gBAIhB;uCArJmC,iCAAiC"}
1
+ {"version":3,"file":"token-helpers.d.ts","sourceRoot":"","sources":["token-helpers.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,2CAHW,MAAM,GACJ,MAAM,CAKlB;AAED;;;;;GAKG;AACH,qCAJW,MAAM,kBACN,MAAM,GACJ;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAmB/C;AAED;;;;;GAKG;AACH,4CAJW,MAAM,kBACN,MAAM,GACJ;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAa/C;AAED;;;;GAIG;AACH,iCAHW,MAAM,GACJ,GAAG,CASf;AAED;;;;;GAKG;AACH,6CAJW,MAAM,UACN,iBAAiB,YACjB,MAAM;;GA+DhB;AAED;;;GAGG;AACH,0BAFW,MAAM,gBAIhB;uCAtJmC,iCAAiC"}
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { promises as fs } from 'node:fs'
6
- import { join } from 'node:path'
6
+ import { resolve } from 'node:path'
7
7
  import { jwtDecode } from 'jwt-decode'
8
8
 
9
9
  /**
@@ -76,19 +76,18 @@ export function decodeJwt (token) {
76
76
 
77
77
  /**
78
78
  * Save tokens to .env file
79
- * @param {string} filename
79
+ * @param {string} envFilePath file path
80
80
  * @param {YotoTokenResponse} tokens
81
81
  * @param {string} clientId
82
82
  */
83
- export async function saveTokensToEnv (filename, tokens, clientId) {
84
- const cwd = process.cwd()
85
- const filePath = join(cwd, filename)
86
-
83
+ export async function saveTokensToEnv (envFilePath, tokens, clientId) {
87
84
  let existingContent = ''
88
85
 
86
+ const resolvedPath = resolve(envFilePath)
87
+
89
88
  // Read existing .env file if it exists
90
89
  try {
91
- existingContent = await fs.readFile(filePath, 'utf8')
90
+ existingContent = await fs.readFile(envFilePath, 'utf8')
92
91
  } catch (err) {
93
92
  // File doesn't exist, that's okay
94
93
  }
@@ -139,7 +138,9 @@ export async function saveTokensToEnv (filename, tokens, clientId) {
139
138
  lines.push(`YOTO_CLIENT_ID=${clientId}`)
140
139
 
141
140
  // Write back to file
142
- await fs.writeFile(filePath, lines.join('\n'), 'utf8')
141
+ await fs.writeFile(envFilePath, lines.join('\n'), 'utf8')
142
+
143
+ return { resolvedPath }
143
144
  }
144
145
 
145
146
  /**
@@ -16,6 +16,7 @@ import {
16
16
  handleCliError
17
17
  } from './lib/cli-helpers.js'
18
18
  import { DEFAULT_CLIENT_ID } from '../lib/api-endpoints/constants.js'
19
+ import { join } from 'node:path'
19
20
 
20
21
  /** @type {ArgscloptsParseArgsOptionsConfig} */
21
22
  const options = {
@@ -51,7 +52,7 @@ if (args.values['help']) {
51
52
  }
52
53
 
53
54
  // Load .env file if specified or use default
54
- const outputFile = String(args.values['output'] || '.env')
55
+ const outputFile = String(args.values['output'] || join(process.cwd(), '.env'))
55
56
  loadEnvFile(args.values['env-file'] ? String(args.values['env-file']) : outputFile)
56
57
 
57
58
  const clientId = String(args.values['client-id'] || process.env['YOTO_CLIENT_ID'] || DEFAULT_CLIENT_ID)
@@ -90,12 +91,13 @@ async function main () {
90
91
  onTokenRefresh: async (tokens) => {
91
92
  // This will be called after successful refresh
92
93
  // Convert RefreshSuccessEvent to YotoTokenResponse format
93
- await saveTokensToEnv(outputFile, {
94
+ const { resolvedPath } = await saveTokensToEnv(outputFile, {
94
95
  access_token: tokens.updatedAccessToken,
95
96
  refresh_token: tokens.updatedRefreshToken,
96
97
  token_type: 'Bearer',
97
98
  expires_in: tokens.updatedExpiresAt - Math.floor(Date.now() / 1000)
98
99
  }, tokens.clientId)
100
+ console.log(`Token Refreshed: ${resolvedPath}`)
99
101
  },
100
102
  onRefreshStart: () => {
101
103
  console.log('\nšŸ”„ Refreshing tokens...')
package/bin/token-info.js CHANGED
@@ -297,13 +297,13 @@ try {
297
297
  onTokenRefresh: async (tokens) => {
298
298
  // Save tokens if they refresh during inspection
299
299
  console.log('\nāš ļø Token was refreshed during inspection! Saving...')
300
- await saveTokensToEnv(outputFile, {
300
+ const { resolvedPath } = await saveTokensToEnv(outputFile, {
301
301
  access_token: tokens.updatedAccessToken,
302
302
  refresh_token: tokens.updatedRefreshToken,
303
303
  token_type: 'Bearer',
304
304
  expires_in: tokens.updatedExpiresAt - Math.floor(Date.now() / 1000)
305
305
  }, tokens.clientId)
306
- console.log(`āœ… Updated tokens saved to ${outputFile}`)
306
+ console.log(`āœ… Updated tokens saved to ${resolvedPath}`)
307
307
  },
308
308
  onRefreshStart: () => {
309
309
  console.log('\nšŸ”„ Token refresh triggered during inspection...')
@@ -3,11 +3,12 @@
3
3
  * @property {string} clientId - OAuth client ID
4
4
  * @property {string} refreshToken - OAuth refresh token
5
5
  * @property {string} accessToken - Initial OAuth access token (JWT)
6
- * @property {(refreshedTokenData: RefreshSuccessEvent) => void | Promise<void>} onTokenRefresh - **REQUIRED** Callback invoked when tokens are refreshed. You MUST persist these tokens (to file, database, etc.) as the refresh can happen at any time during API calls. The refresh token may be rotated by the auth server. **DO NOT STUB THIS CALLBACK** - always implement proper persistence logic.
6
+ * @property {OnTokenRefreshHandler} onTokenRefresh - **REQUIRED** Callback invoked when tokens are refreshed. You MUST persist these tokens (to file, database, etc.) as the refresh can happen at any time during API calls. The refresh token may be rotated by the auth server. **DO NOT STUB THIS CALLBACK** - always implement proper persistence logic.
7
7
  * @property {number} [bufferSeconds=30] - Seconds before expiration to consider token expired
8
8
  * @property {() => void | Promise<void>} [onRefreshStart] - Optional callback invoked when token refresh starts. Defaults to console.log.
9
9
  * @property {(error: Error) => void | Promise<void>} [onRefreshError] - Optional callback invoked when token refresh fails with a transient error. Defaults to console.warn.
10
10
  * @property {(error: Error) => void | Promise<void>} [onInvalid] - Optional callback invoked when refresh token is permanently invalid. Defaults to console.error.
11
+ * @property {string} [mqttSessionId] - Stable unique client ID suffix used for MQTT connections (defaults to a random UUID per YotoClient instance)
11
12
  * @property {string} [userAgent] - Optional user agent string to identify your application
12
13
  * @property {RequestOptions} [defaultRequestOptions] - Default undici request options for all requests (dispatcher, timeouts, etc.)
13
14
  */
@@ -509,15 +510,10 @@ export class YotoClient {
509
510
  }): Promise<Media.YotoUploadCoverImageResponse>;
510
511
  /**
511
512
  * Create an MQTT client for a device
512
- * @param {Object} params
513
- * @param {string} params.deviceId - Device ID to connect to
514
- * @param {MqttClientOptions} [params.mqttOptions] - MQTT.js client options (excluding deviceId and accessToken which are provided automatically)
513
+ * @param {Omit<YotoMqttOptions, 'token'>} YotoMqttOptions
515
514
  * @returns {Promise<YotoMqttClient>}
516
515
  */
517
- createMqttClient({ deviceId, mqttOptions }: {
518
- deviceId: string;
519
- mqttOptions?: Partial<import("mqtt").IClientOptions> | undefined;
520
- }): Promise<YotoMqttClient>;
516
+ createMqttClient({ deviceId, mqttOptions }: Omit<YotoMqttOptions, "token">): Promise<YotoMqttClient>;
521
517
  #private;
522
518
  }
523
519
  export type YotoClientConstructorOptions = {
@@ -536,7 +532,7 @@ export type YotoClientConstructorOptions = {
536
532
  /**
537
533
  * - **REQUIRED** Callback invoked when tokens are refreshed. You MUST persist these tokens (to file, database, etc.) as the refresh can happen at any time during API calls. The refresh token may be rotated by the auth server. **DO NOT STUB THIS CALLBACK** - always implement proper persistence logic.
538
534
  */
539
- onTokenRefresh: (refreshedTokenData: RefreshSuccessEvent) => void | Promise<void>;
535
+ onTokenRefresh: OnTokenRefreshHandler;
540
536
  /**
541
537
  * - Seconds before expiration to consider token expired
542
538
  */
@@ -553,6 +549,10 @@ export type YotoClientConstructorOptions = {
553
549
  * - Optional callback invoked when refresh token is permanently invalid. Defaults to console.error.
554
550
  */
555
551
  onInvalid?: (error: Error) => void | Promise<void>;
552
+ /**
553
+ * - Stable unique client ID suffix used for MQTT connections (defaults to a random UUID per YotoClient instance)
554
+ */
555
+ mqttSessionId?: string;
556
556
  /**
557
557
  * - Optional user agent string to identify your application
558
558
  */
@@ -569,8 +569,9 @@ import * as FamilyLibraryGroups from './api-endpoints/family-library-groups.js';
569
569
  import * as Family from './api-endpoints/family.js';
570
570
  import * as Icons from './api-endpoints/icons.js';
571
571
  import * as Media from './api-endpoints/media.js';
572
+ import type { YotoMqttOptions } from './mqtt/factory.js';
572
573
  import type { YotoMqttClient } from './mqtt/client.js';
573
574
  import * as Auth from './api-endpoints/auth.js';
574
- import type { RefreshSuccessEvent } from './token.js';
575
+ import type { OnTokenRefreshHandler } from './token.js';
575
576
  import type { RequestOptions } from './api-endpoints/helpers.js';
576
577
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["api-client.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["api-client.js"],"names":[],"mappings":"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"}