yoto-nodejs-client 0.0.5 ā 0.0.7
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 +73 -40
- package/bin/auth.js +4 -3
- package/bin/device-model.js +25 -5
- package/bin/device-tui.js +25 -3
- package/bin/devices.js +25 -9
- 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 +123 -48
- package/lib/mqtt/client.d.ts.map +1 -1
- package/lib/mqtt/client.js +131 -49
- 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/test-helpers/device-model-test-helpers.d.ts +29 -0
- package/lib/test-helpers/device-model-test-helpers.d.ts.map +1 -0
- package/lib/test-helpers/device-model-test-helpers.js +116 -0
- package/lib/token.d.ts +44 -2
- package/lib/token.d.ts.map +1 -1
- package/lib/token.js +142 -2
- package/lib/yoto-account.d.ts +339 -9
- package/lib/yoto-account.d.ts.map +1 -1
- package/lib/yoto-account.js +411 -39
- package/lib/yoto-account.test.js +139 -0
- package/lib/yoto-device.d.ts +418 -30
- package/lib/yoto-device.d.ts.map +1 -1
- package/lib/yoto-device.js +670 -104
- package/lib/yoto-device.test.js +88 -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
|
|
@@ -203,24 +211,21 @@ account.on('started', (metadata) => {
|
|
|
203
211
|
|
|
204
212
|
|
|
205
213
|
|
|
206
|
-
// Listen for device
|
|
207
|
-
account.on('
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
console.log(`${deviceId} battery: ${status.batteryLevelPercentage}%`)
|
|
211
|
-
})
|
|
214
|
+
// Listen for device events across all devices via the unified bus
|
|
215
|
+
account.on('statusUpdate', ({ deviceId, status, source }) => {
|
|
216
|
+
console.log(`${deviceId} battery: ${status.batteryLevelPercentage}% (${source})`)
|
|
217
|
+
})
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
account.on('online', ({ deviceId }) => {
|
|
220
|
+
console.log(`${deviceId} came online`)
|
|
221
|
+
})
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
})
|
|
223
|
+
account.on('offline', ({ deviceId }) => {
|
|
224
|
+
console.log(`${deviceId} went offline`)
|
|
220
225
|
})
|
|
221
226
|
|
|
222
227
|
// Unified error handling
|
|
223
|
-
account.on('error', (error, context) => {
|
|
228
|
+
account.on('error', ({ error, context }) => {
|
|
224
229
|
console.error(`Error in ${context.source}:`, error.message)
|
|
225
230
|
if (context.deviceId) {
|
|
226
231
|
console.error(`Device: ${context.deviceId}`)
|
|
@@ -938,7 +943,7 @@ Create a stateful device client that manages device state primarily from MQTT wi
|
|
|
938
943
|
**State Accessors:**
|
|
939
944
|
- `deviceClient.device` - Device information
|
|
940
945
|
- `deviceClient.status` - Current device status (normalized from HTTP/MQTT)
|
|
941
|
-
- `deviceClient.config` - Device configuration
|
|
946
|
+
- `deviceClient.config` - Device configuration (normalized numbers/booleans; auto fields split into `<field>Auto` + value)
|
|
942
947
|
- `deviceClient.shortcuts` - Button shortcuts
|
|
943
948
|
- `deviceClient.playback` - Current playback state
|
|
944
949
|
- `deviceClient.capabilities` - Hardware capabilities (sensors, nightlight support, etc.)
|
|
@@ -962,8 +967,17 @@ Create a stateful device client that manages device state primarily from MQTT wi
|
|
|
962
967
|
- `playbackUpdate(playback, changedFields)` - Playback state changed, passes (playback, changedFields)
|
|
963
968
|
- `online(metadata)` - Device came online, passes metadata with reason and optional upTime
|
|
964
969
|
- `offline(metadata)` - Device went offline, passes metadata with reason and optional shutDownReason or timeSinceLastSeen
|
|
965
|
-
- `
|
|
966
|
-
- `
|
|
970
|
+
- `mqttConnect(metadata)` - MQTT client connected, passes CONNACK metadata
|
|
971
|
+
- `mqttDisconnect(metadata)` - MQTT disconnect packet received, passes metadata with disconnect packet
|
|
972
|
+
- `mqttClose(metadata)` - MQTT connection closed, passes metadata with close reason
|
|
973
|
+
- `mqttReconnect()` - MQTT client is reconnecting
|
|
974
|
+
- `mqttOffline()` - MQTT client goes offline
|
|
975
|
+
- `mqttEnd()` - MQTT client end is called
|
|
976
|
+
- `mqttStatus(topic, message)` - Raw MQTT status messages (documented status topic)
|
|
977
|
+
- `mqttEvents(topic, message)` - Raw MQTT events messages
|
|
978
|
+
- `mqttStatusLegacy(topic, message)` - Raw legacy MQTT status messages (undocumented status topic)
|
|
979
|
+
- `mqttResponse(topic, message)` - Raw MQTT response messages
|
|
980
|
+
- `mqttUnknown(topic, message)` - Raw MQTT messages that do not match known types
|
|
967
981
|
- `error(error)` - Error occurred, passes error
|
|
968
982
|
|
|
969
983
|
**Static Properties & Methods:**
|
|
@@ -1001,7 +1015,15 @@ console.log('Available colors:', YotoDeviceModel.NIGHTLIGHT_COLORS)
|
|
|
1001
1015
|
console.log('Color name:', YotoDeviceModel.getNightlightColorName('0x643600'))
|
|
1002
1016
|
|
|
1003
1017
|
// Control device
|
|
1004
|
-
await deviceClient.updateConfig({ maxVolumeLimit:
|
|
1018
|
+
await deviceClient.updateConfig({ maxVolumeLimit: 14 })
|
|
1019
|
+
await deviceClient.updateConfig({
|
|
1020
|
+
nightDisplayBrightnessAuto: true,
|
|
1021
|
+
nightDisplayBrightness: null
|
|
1022
|
+
})
|
|
1023
|
+
await deviceClient.updateConfig({
|
|
1024
|
+
nightYotoRadioEnabled: false,
|
|
1025
|
+
nightYotoRadio: null
|
|
1026
|
+
})
|
|
1005
1027
|
|
|
1006
1028
|
await deviceClient.stop()
|
|
1007
1029
|
```
|
|
@@ -1033,11 +1055,27 @@ Create an account manager that automatically discovers and manages all devices f
|
|
|
1033
1055
|
**Events:**
|
|
1034
1056
|
- `started(metadata)` - Account started (metadata: { deviceCount, devices })
|
|
1035
1057
|
- `stopped()` - Account stopped
|
|
1036
|
-
- `deviceAdded(deviceId
|
|
1037
|
-
- `deviceRemoved(deviceId)` - Device was removed
|
|
1038
|
-
- `
|
|
1039
|
-
|
|
1040
|
-
|
|
1058
|
+
- `deviceAdded({ deviceId })` - Device was added
|
|
1059
|
+
- `deviceRemoved({ deviceId })` - Device was removed
|
|
1060
|
+
- `statusUpdate({ deviceId, status, source, changedFields })` - Re-emitted device status update
|
|
1061
|
+
- `configUpdate({ deviceId, config, changedFields })` - Re-emitted config update
|
|
1062
|
+
- `playbackUpdate({ deviceId, playback, changedFields })` - Re-emitted playback update
|
|
1063
|
+
- `online({ deviceId, metadata })` - Re-emitted online event
|
|
1064
|
+
- `offline({ deviceId, metadata })` - Re-emitted offline event
|
|
1065
|
+
- `mqttConnect({ deviceId, metadata })` - Re-emitted MQTT connect
|
|
1066
|
+
- `mqttDisconnect({ deviceId, metadata })` - Re-emitted MQTT disconnect
|
|
1067
|
+
- `mqttClose({ deviceId, metadata })` - Re-emitted MQTT close
|
|
1068
|
+
- `mqttReconnect({ deviceId })` - Re-emitted MQTT reconnect
|
|
1069
|
+
- `mqttOffline({ deviceId })` - Re-emitted MQTT offline
|
|
1070
|
+
- `mqttEnd({ deviceId })` - Re-emitted MQTT end
|
|
1071
|
+
- `mqttStatus({ deviceId, topic, message })` - Re-emitted raw MQTT status
|
|
1072
|
+
- `mqttEvents({ deviceId, topic, message })` - Re-emitted raw MQTT events
|
|
1073
|
+
- `mqttStatusLegacy({ deviceId, topic, message })` - Re-emitted raw MQTT legacy status
|
|
1074
|
+
- `mqttResponse({ deviceId, topic, message })` - Re-emitted raw MQTT response
|
|
1075
|
+
- `mqttUnknown({ deviceId, topic, message })` - Re-emitted raw MQTT unknown message
|
|
1076
|
+
- `error({ error, context })` - Error occurred (context: { source, deviceId, operation })
|
|
1077
|
+
|
|
1078
|
+
**Note:** You can still listen to individual device events by attaching listeners to each `YotoDeviceModel`, but the account now re-emits device and MQTT events with device context for unified handling.
|
|
1041
1079
|
|
|
1042
1080
|
```js
|
|
1043
1081
|
import { YotoAccount } from 'yoto-nodejs-client'
|
|
@@ -1057,26 +1095,21 @@ const account = new YotoAccount({
|
|
|
1057
1095
|
})
|
|
1058
1096
|
|
|
1059
1097
|
// Account-level error handling
|
|
1060
|
-
account.on('error', (error, context) => {
|
|
1098
|
+
account.on('error', ({ error, context }) => {
|
|
1061
1099
|
console.error(`Error in ${context.source}:`, error.message)
|
|
1062
1100
|
})
|
|
1063
1101
|
|
|
1064
|
-
//
|
|
1065
|
-
account.on('
|
|
1066
|
-
console.log(
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
})
|
|
1076
|
-
|
|
1077
|
-
deviceModel.on('offline', (metadata) => {
|
|
1078
|
-
console.log(`${deviceId} went offline (${metadata.reason})`)
|
|
1079
|
-
})
|
|
1102
|
+
// Unified device events across all devices
|
|
1103
|
+
account.on('statusUpdate', ({ deviceId, status, source }) => {
|
|
1104
|
+
console.log(`${deviceId} battery: ${status.batteryLevelPercentage}% (${source})`)
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
account.on('online', ({ deviceId, metadata }) => {
|
|
1108
|
+
console.log(`${deviceId} came online (${metadata.reason})`)
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
account.on('offline', ({ deviceId, metadata }) => {
|
|
1112
|
+
console.log(`${deviceId} went offline (${metadata.reason})`)
|
|
1080
1113
|
})
|
|
1081
1114
|
|
|
1082
1115
|
await account.start()
|
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,10 +159,7 @@ async function main () {
|
|
|
160
159
|
console.log('š Connecting to MQTT...')
|
|
161
160
|
console.log('='.repeat(60))
|
|
162
161
|
|
|
163
|
-
const mqttClient =
|
|
164
|
-
deviceId,
|
|
165
|
-
accessToken
|
|
166
|
-
})
|
|
162
|
+
const mqttClient = await client.createMqttClient({ deviceId })
|
|
167
163
|
|
|
168
164
|
// Setup interactive keyboard input (once, before connecting)
|
|
169
165
|
readline.emitKeypressEvents(process.stdin)
|
|
@@ -200,8 +196,9 @@ async function main () {
|
|
|
200
196
|
})
|
|
201
197
|
|
|
202
198
|
// Setup message handlers
|
|
203
|
-
mqttClient.on('
|
|
199
|
+
mqttClient.on('connect', (connack) => {
|
|
204
200
|
console.log('ā
Connected to MQTT broker')
|
|
201
|
+
console.log(` Session present: ${connack.sessionPresent ? 'yes' : 'no'}`)
|
|
205
202
|
console.log('š” Subscribed to topics:')
|
|
206
203
|
console.log(` ⢠device/${deviceId}/data/events (playback events)`)
|
|
207
204
|
console.log(` ⢠device/${deviceId}/data/status (regular status)`)
|
|
@@ -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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"}
|