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.
- package/README.md +24 -6
- package/bin/auth.js +4 -3
- package/bin/device-model.js +25 -5
- package/bin/device-tui.js +25 -3
- package/bin/devices.js +58 -42
- package/bin/lib/cli-helpers.d.ts.map +1 -1
- package/bin/lib/cli-helpers.js +3 -1
- package/bin/lib/token-helpers.d.ts +4 -2
- package/bin/lib/token-helpers.d.ts.map +1 -1
- package/bin/lib/token-helpers.js +9 -8
- package/bin/refresh-token.js +4 -2
- package/bin/token-info.js +2 -2
- package/lib/api-client.d.ts +11 -10
- package/lib/api-client.d.ts.map +1 -1
- package/lib/api-client.js +12 -14
- package/lib/api-endpoints/auth.test.js +4 -4
- package/lib/api-endpoints/content.test.js +32 -32
- package/lib/api-endpoints/devices.d.ts +4 -4
- package/lib/api-endpoints/devices.js +2 -2
- package/lib/api-endpoints/devices.test.js +45 -45
- package/lib/api-endpoints/endpoint-test-helpers.d.ts +3 -4
- package/lib/api-endpoints/endpoint-test-helpers.d.ts.map +1 -1
- package/lib/api-endpoints/endpoint-test-helpers.js +21 -5
- package/lib/api-endpoints/family-library-groups.d.ts +3 -3
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -1
- package/lib/api-endpoints/family-library-groups.js +3 -3
- package/lib/api-endpoints/family-library-groups.test.js +29 -29
- package/lib/api-endpoints/family.test.js +11 -11
- package/lib/api-endpoints/icons.test.js +14 -14
- package/lib/mqtt/client.d.ts +124 -49
- package/lib/mqtt/client.d.ts.map +1 -1
- package/lib/mqtt/client.js +132 -50
- package/lib/mqtt/factory.d.ts +12 -5
- package/lib/mqtt/factory.d.ts.map +1 -1
- package/lib/mqtt/factory.js +39 -11
- package/lib/mqtt/index.js +2 -1
- package/lib/mqtt/mqtt.test.js +25 -22
- package/lib/token.d.ts +44 -2
- package/lib/token.d.ts.map +1 -1
- package/lib/token.js +142 -2
- package/lib/yoto-device.d.ts +392 -32
- package/lib/yoto-device.d.ts.map +1 -1
- package/lib/yoto-device.js +643 -99
- package/lib/yoto-device.test.js +193 -0
- 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:
|
|
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
|
-
- `
|
|
966
|
-
- `
|
|
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:
|
|
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,
|
|
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 ${
|
|
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')
|
package/bin/device-model.js
CHANGED
|
@@ -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
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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 =
|
|
164
|
-
|
|
165
|
-
|
|
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('
|
|
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('
|
|
320
|
-
|
|
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('
|
|
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,
|
|
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"}
|
package/bin/lib/cli-helpers.js
CHANGED
|
@@ -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}
|
|
35
|
+
* @param {string} envFilePath file path
|
|
36
36
|
* @param {YotoTokenResponse} tokens
|
|
37
37
|
* @param {string} clientId
|
|
38
38
|
*/
|
|
39
|
-
export function saveTokensToEnv(
|
|
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,
|
|
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"}
|
package/bin/lib/token-helpers.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { promises as fs } from 'node:fs'
|
|
6
|
-
import {
|
|
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}
|
|
79
|
+
* @param {string} envFilePath file path
|
|
80
80
|
* @param {YotoTokenResponse} tokens
|
|
81
81
|
* @param {string} clientId
|
|
82
82
|
*/
|
|
83
|
-
export async function saveTokensToEnv (
|
|
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(
|
|
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(
|
|
141
|
+
await fs.writeFile(envFilePath, lines.join('\n'), 'utf8')
|
|
142
|
+
|
|
143
|
+
return { resolvedPath }
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
/**
|
package/bin/refresh-token.js
CHANGED
|
@@ -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 ${
|
|
306
|
+
console.log(`ā
Updated tokens saved to ${resolvedPath}`)
|
|
307
307
|
},
|
|
308
308
|
onRefreshStart: () => {
|
|
309
309
|
console.log('\nš Token refresh triggered during inspection...')
|
package/lib/api-client.d.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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:
|
|
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 {
|
|
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
|
package/lib/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["api-client.js"],"names":[],"mappings":"AAwBA
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["api-client.js"],"names":[],"mappings":"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"}
|