yoto-nodejs-client 0.0.1
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/LICENSE +21 -0
- package/README.md +736 -0
- package/bin/auth.d.ts +3 -0
- package/bin/auth.d.ts.map +1 -0
- package/bin/auth.js +130 -0
- package/bin/content.d.ts +3 -0
- package/bin/content.d.ts.map +1 -0
- package/bin/content.js +117 -0
- package/bin/devices.d.ts +3 -0
- package/bin/devices.d.ts.map +1 -0
- package/bin/devices.js +239 -0
- package/bin/groups.d.ts +3 -0
- package/bin/groups.d.ts.map +1 -0
- package/bin/groups.js +80 -0
- package/bin/icons.d.ts +3 -0
- package/bin/icons.d.ts.map +1 -0
- package/bin/icons.js +100 -0
- package/bin/lib/cli-helpers.d.ts +21 -0
- package/bin/lib/cli-helpers.d.ts.map +1 -0
- package/bin/lib/cli-helpers.js +140 -0
- package/bin/lib/token-helpers.d.ts +14 -0
- package/bin/lib/token-helpers.d.ts.map +1 -0
- package/bin/lib/token-helpers.js +151 -0
- package/bin/refresh-token.d.ts +3 -0
- package/bin/refresh-token.d.ts.map +1 -0
- package/bin/refresh-token.js +168 -0
- package/bin/token-info.d.ts +3 -0
- package/bin/token-info.d.ts.map +1 -0
- package/bin/token-info.js +351 -0
- package/index.d.ts +218 -0
- package/index.d.ts.map +1 -0
- package/index.js +689 -0
- package/lib/api-endpoints/auth.d.ts +56 -0
- package/lib/api-endpoints/auth.d.ts.map +1 -0
- package/lib/api-endpoints/auth.js +209 -0
- package/lib/api-endpoints/auth.test.js +27 -0
- package/lib/api-endpoints/constants.d.ts +6 -0
- package/lib/api-endpoints/constants.d.ts.map +1 -0
- package/lib/api-endpoints/constants.js +31 -0
- package/lib/api-endpoints/content.d.ts +275 -0
- package/lib/api-endpoints/content.d.ts.map +1 -0
- package/lib/api-endpoints/content.js +518 -0
- package/lib/api-endpoints/content.test.js +250 -0
- package/lib/api-endpoints/devices.d.ts +202 -0
- package/lib/api-endpoints/devices.d.ts.map +1 -0
- package/lib/api-endpoints/devices.js +404 -0
- package/lib/api-endpoints/devices.test.js +483 -0
- package/lib/api-endpoints/family-library-groups.d.ts +75 -0
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
- package/lib/api-endpoints/family-library-groups.js +247 -0
- package/lib/api-endpoints/family-library-groups.test.js +272 -0
- package/lib/api-endpoints/family.d.ts +39 -0
- package/lib/api-endpoints/family.d.ts.map +1 -0
- package/lib/api-endpoints/family.js +166 -0
- package/lib/api-endpoints/family.test.js +184 -0
- package/lib/api-endpoints/helpers.d.ts +29 -0
- package/lib/api-endpoints/helpers.d.ts.map +1 -0
- package/lib/api-endpoints/helpers.js +104 -0
- package/lib/api-endpoints/icons.d.ts +62 -0
- package/lib/api-endpoints/icons.d.ts.map +1 -0
- package/lib/api-endpoints/icons.js +201 -0
- package/lib/api-endpoints/icons.test.js +118 -0
- package/lib/api-endpoints/media.d.ts +37 -0
- package/lib/api-endpoints/media.d.ts.map +1 -0
- package/lib/api-endpoints/media.js +155 -0
- package/lib/api-endpoints/test-helpers.d.ts +7 -0
- package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
- package/lib/api-endpoints/test-helpers.js +64 -0
- package/lib/mqtt/client.d.ts +124 -0
- package/lib/mqtt/client.d.ts.map +1 -0
- package/lib/mqtt/client.js +558 -0
- package/lib/mqtt/commands.d.ts +69 -0
- package/lib/mqtt/commands.d.ts.map +1 -0
- package/lib/mqtt/commands.js +238 -0
- package/lib/mqtt/factory.d.ts +12 -0
- package/lib/mqtt/factory.d.ts.map +1 -0
- package/lib/mqtt/factory.js +107 -0
- package/lib/mqtt/index.d.ts +5 -0
- package/lib/mqtt/index.d.ts.map +1 -0
- package/lib/mqtt/index.js +81 -0
- package/lib/mqtt/mqtt.test.js +168 -0
- package/lib/mqtt/topics.d.ts +34 -0
- package/lib/mqtt/topics.d.ts.map +1 -0
- package/lib/mqtt/topics.js +295 -0
- package/lib/pkg.cjs +3 -0
- package/lib/pkg.d.cts +70 -0
- package/lib/pkg.d.cts.map +1 -0
- package/lib/token.d.ts +29 -0
- package/lib/token.d.ts.map +1 -0
- package/lib/token.js +240 -0
- package/package.json +91 -0
- package/yoto.png +0 -0
package/bin/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.js"],"names":[],"mappings":""}
|
package/bin/auth.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import {ArgscloptsParseArgsOptionsConfig} from 'argsclopts'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { printHelpText } from 'argsclopts'
|
|
8
|
+
import { parseArgs } from 'node:util'
|
|
9
|
+
import { YotoClient } from '../index.js'
|
|
10
|
+
import { pkg } from '../lib/pkg.cjs'
|
|
11
|
+
import { DEFAULT_CLIENT_ID } from '../lib/api-endpoints/constants.js'
|
|
12
|
+
import { sleep, saveTokensToEnv } from './lib/token-helpers.js'
|
|
13
|
+
import {
|
|
14
|
+
getCommonOptions,
|
|
15
|
+
handleCliError,
|
|
16
|
+
printHeader
|
|
17
|
+
} from './lib/cli-helpers.js'
|
|
18
|
+
|
|
19
|
+
/** @type {ArgscloptsParseArgsOptionsConfig} */
|
|
20
|
+
const options = {
|
|
21
|
+
...getCommonOptions(),
|
|
22
|
+
output: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
short: 'o',
|
|
25
|
+
default: '.env',
|
|
26
|
+
help: 'Output file for tokens (default: .env)'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const args = parseArgs({ options, strict: false })
|
|
31
|
+
|
|
32
|
+
if (args.values['help']) {
|
|
33
|
+
await printHelpText({
|
|
34
|
+
options,
|
|
35
|
+
name: 'yoto-auth',
|
|
36
|
+
version: pkg.version,
|
|
37
|
+
exampleFn: ({ name }) => ` Yoto device flow authentication helper\n\n Example: ${name} --client-id your-client-id\n`
|
|
38
|
+
})
|
|
39
|
+
process.exit(0)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const clientId = String(args.values['client-id'] || process.env['YOTO_CLIENT_ID'] || DEFAULT_CLIENT_ID)
|
|
43
|
+
|
|
44
|
+
const outputFile = String(args.values['output'] || '.env')
|
|
45
|
+
|
|
46
|
+
async function main () {
|
|
47
|
+
printHeader('Yoto Device Flow Authentication')
|
|
48
|
+
console.log()
|
|
49
|
+
|
|
50
|
+
// Step 1: Request device code
|
|
51
|
+
console.log('Requesting device code...\n')
|
|
52
|
+
|
|
53
|
+
const deviceAuth = await YotoClient.requestDeviceCode({ clientId })
|
|
54
|
+
|
|
55
|
+
// Step 2: Display instructions to user
|
|
56
|
+
console.log('┌────────────────────────────────────────────────────┐')
|
|
57
|
+
console.log('│ Please complete authentication in your browser: │')
|
|
58
|
+
console.log('└────────────────────────────────────────────────────┘\n')
|
|
59
|
+
|
|
60
|
+
if (deviceAuth.verification_uri_complete) {
|
|
61
|
+
console.log(' Visit this URL (code included):')
|
|
62
|
+
console.log(` ${deviceAuth.verification_uri_complete}\n`)
|
|
63
|
+
console.log(' Or manually enter:')
|
|
64
|
+
console.log(` URL: ${deviceAuth.verification_uri}`)
|
|
65
|
+
console.log(` Code: ${deviceAuth.user_code}\n`)
|
|
66
|
+
} else {
|
|
67
|
+
console.log(` URL: ${deviceAuth.verification_uri}`)
|
|
68
|
+
console.log(` Code: ${deviceAuth.user_code}\n`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const expiresInMinutes = Math.round(deviceAuth.expires_in / 60)
|
|
72
|
+
console.log(` Code expires in: ${expiresInMinutes} minute(s)`)
|
|
73
|
+
console.log(` Polling every: ${deviceAuth.interval} second(s)\n`)
|
|
74
|
+
|
|
75
|
+
// Step 3: Poll for authorization
|
|
76
|
+
console.log('Waiting for authorization...')
|
|
77
|
+
|
|
78
|
+
let pollInterval = deviceAuth.interval * 1000
|
|
79
|
+
const startTime = Date.now()
|
|
80
|
+
const expiresAt = startTime + (deviceAuth.expires_in * 1000)
|
|
81
|
+
|
|
82
|
+
while (true) {
|
|
83
|
+
// Check if we've exceeded the expiration time
|
|
84
|
+
if (Date.now() >= expiresAt) {
|
|
85
|
+
console.error('\n❌ Device code has expired. Please run the command again.')
|
|
86
|
+
process.exit(1)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const tokens = await YotoClient.exchangeToken({
|
|
91
|
+
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
92
|
+
deviceCode: deviceAuth.device_code,
|
|
93
|
+
clientId
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Success! Save tokens to .env file
|
|
97
|
+
console.log('\n✅ Authorization successful!\n')
|
|
98
|
+
|
|
99
|
+
await saveTokensToEnv(outputFile, tokens, clientId)
|
|
100
|
+
|
|
101
|
+
console.log(`✨ Tokens saved to ${outputFile}`)
|
|
102
|
+
console.log('\nYou can now use these environment variables:')
|
|
103
|
+
console.log(' - YOTO_ACCESS_TOKEN')
|
|
104
|
+
console.log(' - YOTO_REFRESH_TOKEN')
|
|
105
|
+
console.log(' - YOTO_CLIENT_ID')
|
|
106
|
+
|
|
107
|
+
process.exit(0)
|
|
108
|
+
} catch (err) {
|
|
109
|
+
const error = /** @type {any} */ (err)
|
|
110
|
+
if (error.body?.error === 'authorization_pending') {
|
|
111
|
+
process.stdout.write('.')
|
|
112
|
+
await sleep(pollInterval)
|
|
113
|
+
continue
|
|
114
|
+
} else if (error.body?.error === 'slow_down') {
|
|
115
|
+
pollInterval += 5000
|
|
116
|
+
console.log(`\n⚠️ Slowing down polling to ${pollInterval / 1000}s...`)
|
|
117
|
+
await sleep(pollInterval)
|
|
118
|
+
continue
|
|
119
|
+
} else if (error.body?.error === 'expired_token') {
|
|
120
|
+
console.error('\n❌ Device code has expired. Please run the command again.')
|
|
121
|
+
process.exit(1)
|
|
122
|
+
} else {
|
|
123
|
+
handleCliError(error)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Run the main function
|
|
130
|
+
await main().catch(handleCliError)
|
package/bin/content.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["content.js"],"names":[],"mappings":""}
|
package/bin/content.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { printHelpText } from 'argsclopts'
|
|
8
|
+
import { parseArgs } from 'node:util'
|
|
9
|
+
import { pkg } from '../lib/pkg.cjs'
|
|
10
|
+
import {
|
|
11
|
+
getCommonOptions,
|
|
12
|
+
loadTokensFromEnv,
|
|
13
|
+
createYotoClient,
|
|
14
|
+
handleCliError,
|
|
15
|
+
printHeader
|
|
16
|
+
} from './lib/cli-helpers.js'
|
|
17
|
+
|
|
18
|
+
/** @type {ArgscloptsParseArgsOptionsConfig} */
|
|
19
|
+
const options = {
|
|
20
|
+
...getCommonOptions(),
|
|
21
|
+
'card-id': {
|
|
22
|
+
type: 'string',
|
|
23
|
+
short: 'd',
|
|
24
|
+
help: 'Specific card/content ID to fetch'
|
|
25
|
+
},
|
|
26
|
+
'show-deleted': {
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
help: 'Include deleted content (for MYO content list)'
|
|
29
|
+
},
|
|
30
|
+
timezone: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
short: 't',
|
|
33
|
+
help: 'Timezone identifier (e.g., "Pacific/Auckland")'
|
|
34
|
+
},
|
|
35
|
+
'signing-type': {
|
|
36
|
+
type: 'string',
|
|
37
|
+
short: 's',
|
|
38
|
+
help: 'Type of URL signing: "full" or "pre"'
|
|
39
|
+
},
|
|
40
|
+
playable: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
short: 'p',
|
|
43
|
+
help: 'Return playable signed URLs'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const args = parseArgs({ options, strict: false })
|
|
48
|
+
|
|
49
|
+
if (args.values['help']) {
|
|
50
|
+
await printHelpText({
|
|
51
|
+
options,
|
|
52
|
+
name: 'yoto-content',
|
|
53
|
+
version: pkg.version,
|
|
54
|
+
exampleFn: ({ name }) => ` Yoto content information helper\n\n Examples:\n ${name} # List all MYO content\n ${name} --show-deleted # List all MYO content including deleted\n ${name} --card-id abc123 # Get specific content\n ${name} --card-id abc123 --playable # Get content with playable URLs\n ${name} --card-id abc123 --timezone "Pacific/Auckland" --signing-type full --playable\n`
|
|
55
|
+
})
|
|
56
|
+
process.exit(0)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Load tokens from environment
|
|
60
|
+
const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
|
|
61
|
+
|
|
62
|
+
const cardId = args.values['card-id'] ? String(args.values['card-id']) : null
|
|
63
|
+
const showDeleted = Boolean(args.values['show-deleted'])
|
|
64
|
+
const timezone = args.values['timezone'] ? String(args.values['timezone']) : undefined
|
|
65
|
+
const signingType = args.values['signing-type'] ? /** @type {'full' | 'pre'} */ (String(args.values['signing-type'])) : undefined
|
|
66
|
+
const playable = Boolean(args.values['playable'])
|
|
67
|
+
|
|
68
|
+
// Validate signing-type if provided
|
|
69
|
+
if (signingType && signingType !== 'full' && signingType !== 'pre') {
|
|
70
|
+
console.error('❌ Invalid --signing-type value')
|
|
71
|
+
console.error('Valid values: "full" or "pre"')
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main () {
|
|
76
|
+
printHeader('Yoto Content')
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Create client
|
|
80
|
+
const client = createYotoClient({
|
|
81
|
+
clientId,
|
|
82
|
+
refreshToken,
|
|
83
|
+
accessToken,
|
|
84
|
+
outputFile: envFile
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (cardId) {
|
|
88
|
+
// Get specific content
|
|
89
|
+
console.log(`\nFetching content: ${cardId}\n`)
|
|
90
|
+
const content = await client.getContent({
|
|
91
|
+
cardId,
|
|
92
|
+
...(timezone && { timezone }),
|
|
93
|
+
...(signingType && { signingType }),
|
|
94
|
+
...(playable && { playable })
|
|
95
|
+
})
|
|
96
|
+
console.dir(content, { depth: null, colors: true })
|
|
97
|
+
} else {
|
|
98
|
+
// Get all MYO content
|
|
99
|
+
console.log(`\nFetching all MYO content${showDeleted ? ' (including deleted)' : ''}...\n`)
|
|
100
|
+
const myoContent = await client.getUserMyoContent({
|
|
101
|
+
...(showDeleted && { showDeleted })
|
|
102
|
+
})
|
|
103
|
+
console.dir(myoContent, { depth: null, colors: true })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
process.exit(0)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
handleCliError(error)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Run the main function
|
|
113
|
+
await main().catch(err => {
|
|
114
|
+
console.error('\n❌ Unexpected error:')
|
|
115
|
+
console.error(err)
|
|
116
|
+
process.exit(1)
|
|
117
|
+
})
|
package/bin/devices.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["devices.js"],"names":[],"mappings":""}
|
package/bin/devices.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { printHelpText } from 'argsclopts'
|
|
8
|
+
import { parseArgs } from 'node:util'
|
|
9
|
+
import { pkg } from '../lib/pkg.cjs'
|
|
10
|
+
import {
|
|
11
|
+
getCommonOptions,
|
|
12
|
+
loadTokensFromEnv,
|
|
13
|
+
createYotoClient,
|
|
14
|
+
handleCliError,
|
|
15
|
+
printHeader
|
|
16
|
+
} from './lib/cli-helpers.js'
|
|
17
|
+
import { createYotoMqttClient } from '../lib/mqtt/index.js'
|
|
18
|
+
|
|
19
|
+
/** @type {ArgscloptsParseArgsOptionsConfig} */
|
|
20
|
+
const options = {
|
|
21
|
+
...getCommonOptions(),
|
|
22
|
+
'device-id': {
|
|
23
|
+
type: 'string',
|
|
24
|
+
short: 'd',
|
|
25
|
+
help: 'Specific device ID to fetch'
|
|
26
|
+
},
|
|
27
|
+
status: {
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
short: 's',
|
|
30
|
+
help: 'Get device status (requires --device-id)'
|
|
31
|
+
},
|
|
32
|
+
mqtt: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
short: 'm',
|
|
35
|
+
help: 'Connect to MQTT and listen for device messages (requires --device-id)'
|
|
36
|
+
},
|
|
37
|
+
'mqtt-stop-after-seconds': {
|
|
38
|
+
type: 'string',
|
|
39
|
+
help: 'Auto-disconnect MQTT after N seconds (requires --mqtt)'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const args = parseArgs({ options, strict: false })
|
|
44
|
+
|
|
45
|
+
if (args.values['help']) {
|
|
46
|
+
await printHelpText({
|
|
47
|
+
options,
|
|
48
|
+
name: 'yoto-devices',
|
|
49
|
+
version: pkg.version,
|
|
50
|
+
exampleFn: ({ name }) => ` Yoto devices information helper\n\n Examples:\n ${name} # List all devices\n ${name} --device-id abc123 # Get specific device\n ${name} --device-id abc123 --status # Get device status\n ${name} --device-id abc123 --mqtt # Listen to MQTT messages\n ${name} --device-id abc123 --mqtt --mqtt-stop-after-seconds 10 # Sample MQTT for 10s\n`
|
|
51
|
+
})
|
|
52
|
+
process.exit(0)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Load tokens from environment
|
|
56
|
+
const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
|
|
57
|
+
|
|
58
|
+
const deviceId = args.values['device-id'] ? String(args.values['device-id']) : null
|
|
59
|
+
const getStatus = Boolean(args.values['status'])
|
|
60
|
+
const useMqtt = Boolean(args.values['mqtt'])
|
|
61
|
+
const mqttStopAfter = args.values['mqtt-stop-after-seconds'] ? Number(args.values['mqtt-stop-after-seconds']) : null
|
|
62
|
+
|
|
63
|
+
if (getStatus && !deviceId) {
|
|
64
|
+
console.error('❌ --status flag requires --device-id')
|
|
65
|
+
console.error('Usage: yoto-devices --device-id abc123 --status')
|
|
66
|
+
process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (useMqtt && !deviceId) {
|
|
70
|
+
console.error('❌ --mqtt flag requires --device-id')
|
|
71
|
+
console.error('Usage: yoto-devices --device-id abc123 --mqtt')
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (mqttStopAfter && !useMqtt) {
|
|
76
|
+
console.error('❌ --mqtt-stop-after-seconds requires --mqtt')
|
|
77
|
+
console.error('Usage: yoto-devices --device-id abc123 --mqtt --mqtt-stop-after-seconds 10')
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function main () {
|
|
82
|
+
printHeader('Yoto Devices')
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Create client
|
|
86
|
+
const client = createYotoClient({
|
|
87
|
+
clientId,
|
|
88
|
+
refreshToken,
|
|
89
|
+
accessToken,
|
|
90
|
+
outputFile: envFile
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (deviceId) {
|
|
94
|
+
if (getStatus) {
|
|
95
|
+
// Get device status
|
|
96
|
+
console.log(`\nFetching status for device: ${deviceId}\n`)
|
|
97
|
+
const status = await client.getDeviceStatus({ deviceId })
|
|
98
|
+
console.dir(status, { depth: null, colors: true })
|
|
99
|
+
} else {
|
|
100
|
+
// Get specific device from list
|
|
101
|
+
console.log(`\nFetching device: ${deviceId}\n`)
|
|
102
|
+
const devicesResponse = await client.getDevices()
|
|
103
|
+
const device = devicesResponse.devices.find(d => d.deviceId === deviceId)
|
|
104
|
+
|
|
105
|
+
if (device) {
|
|
106
|
+
console.log('Device:')
|
|
107
|
+
console.dir(device, { depth: null, colors: true })
|
|
108
|
+
|
|
109
|
+
// Get device config
|
|
110
|
+
console.log('\nDevice Config:')
|
|
111
|
+
const config = await client.getDeviceConfig({ deviceId })
|
|
112
|
+
console.dir(config, { depth: null, colors: true })
|
|
113
|
+
|
|
114
|
+
// Get device shortcuts
|
|
115
|
+
console.log('\nDevice Shortcuts:')
|
|
116
|
+
const shortcuts = await client.getDeviceConfig({ deviceId })
|
|
117
|
+
if (shortcuts.device?.shortcuts) {
|
|
118
|
+
console.dir(shortcuts.device.shortcuts, { depth: null, colors: true })
|
|
119
|
+
} else {
|
|
120
|
+
console.log('No shortcuts available')
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
console.error(`❌ Device with ID '${deviceId}' not found`)
|
|
124
|
+
process.exit(1)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Get all devices
|
|
129
|
+
console.log('\nFetching all devices...\n')
|
|
130
|
+
const devicesResponse = await client.getDevices()
|
|
131
|
+
console.dir(devicesResponse, { depth: null, colors: true })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// MQTT listening mode
|
|
135
|
+
if (useMqtt && deviceId) {
|
|
136
|
+
console.log('\n' + '='.repeat(60))
|
|
137
|
+
console.log('🔌 Connecting to MQTT...')
|
|
138
|
+
console.log('='.repeat(60))
|
|
139
|
+
|
|
140
|
+
const mqttClient = createYotoMqttClient({
|
|
141
|
+
deviceId,
|
|
142
|
+
accessToken
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Setup message handlers
|
|
146
|
+
mqttClient.on('connected', () => {
|
|
147
|
+
console.log('✅ Connected to MQTT broker')
|
|
148
|
+
console.log(`📡 Subscribed to device/${deviceId}/*`)
|
|
149
|
+
console.log('\n👂 Listening for messages... (Ctrl+C to stop)\n')
|
|
150
|
+
|
|
151
|
+
if (mqttStopAfter) {
|
|
152
|
+
console.log(`⏱️ Will auto-disconnect after ${mqttStopAfter} seconds\n`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Request initial status and events after a delay to ensure subscriptions are ready
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
mqttClient.requestStatus().catch(err => {
|
|
158
|
+
console.error('⚠️ Failed to request status:', err.message)
|
|
159
|
+
})
|
|
160
|
+
mqttClient.requestEvents().catch(err => {
|
|
161
|
+
console.error('⚠️ Failed to request events:', err.message)
|
|
162
|
+
})
|
|
163
|
+
}, 1000)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
mqttClient.on('events', (message) => {
|
|
167
|
+
const timestamp = new Date().toISOString()
|
|
168
|
+
console.log(`\n📨 EVENTS MESSAGE [${timestamp}]:`)
|
|
169
|
+
console.log('─'.repeat(60))
|
|
170
|
+
console.dir(message, { depth: null, colors: true })
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
mqttClient.on('status', (message) => {
|
|
174
|
+
const timestamp = new Date().toISOString()
|
|
175
|
+
console.log(`\n📊 STATUS MESSAGE [${timestamp}]:`)
|
|
176
|
+
console.log('─'.repeat(60))
|
|
177
|
+
console.dir(message, { depth: null, colors: true })
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
mqttClient.on('response', (message) => {
|
|
181
|
+
const timestamp = new Date().toISOString()
|
|
182
|
+
console.log(`\n✉️ RESPONSE MESSAGE [${timestamp}]:`)
|
|
183
|
+
console.log('─'.repeat(60))
|
|
184
|
+
console.dir(message, { depth: null, colors: true })
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
mqttClient.on('disconnected', () => {
|
|
188
|
+
console.log('\n❌ Disconnected from MQTT broker')
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
mqttClient.on('reconnecting', () => {
|
|
192
|
+
console.log('\n🔄 Reconnecting to MQTT broker...')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
mqttClient.on('error', (error) => {
|
|
196
|
+
console.error('\n⚠️ MQTT Error:', error.message)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// Handle graceful shutdown
|
|
200
|
+
const cleanup = async () => {
|
|
201
|
+
console.log('\n\n🛑 Shutting down...')
|
|
202
|
+
try {
|
|
203
|
+
await mqttClient.disconnect()
|
|
204
|
+
console.log('✅ Disconnected cleanly')
|
|
205
|
+
process.exit(0)
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error('❌ Error during disconnect:', err)
|
|
208
|
+
process.exit(1)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
process.on('SIGINT', cleanup)
|
|
213
|
+
process.on('SIGTERM', cleanup)
|
|
214
|
+
|
|
215
|
+
// Connect to MQTT
|
|
216
|
+
await mqttClient.connect()
|
|
217
|
+
|
|
218
|
+
// Auto-disconnect timer if specified
|
|
219
|
+
if (mqttStopAfter) {
|
|
220
|
+
setTimeout(async () => {
|
|
221
|
+
console.log(`\n⏰ ${mqttStopAfter} seconds elapsed, disconnecting...`)
|
|
222
|
+
await cleanup()
|
|
223
|
+
}, mqttStopAfter * 1000)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Keep process alive (unless auto-disconnect is set)
|
|
227
|
+
if (!mqttStopAfter) {
|
|
228
|
+
await new Promise(() => {}) // Keep alive indefinitely
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
process.exit(0)
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
handleCliError(error)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Run the main function
|
|
239
|
+
await main().catch(handleCliError)
|
package/bin/groups.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"groups.d.ts","sourceRoot":"","sources":["groups.js"],"names":[],"mappings":""}
|
package/bin/groups.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { ArgscloptsParseArgsOptionsConfig } from 'argsclopts'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { printHelpText } from 'argsclopts'
|
|
8
|
+
import { parseArgs } from 'node:util'
|
|
9
|
+
import { pkg } from '../lib/pkg.cjs'
|
|
10
|
+
import {
|
|
11
|
+
getCommonOptions,
|
|
12
|
+
loadTokensFromEnv,
|
|
13
|
+
createYotoClient,
|
|
14
|
+
handleCliError,
|
|
15
|
+
printHeader
|
|
16
|
+
} from './lib/cli-helpers.js'
|
|
17
|
+
|
|
18
|
+
/** @type {ArgscloptsParseArgsOptionsConfig} */
|
|
19
|
+
const options = {
|
|
20
|
+
...getCommonOptions(),
|
|
21
|
+
'group-id': {
|
|
22
|
+
type: 'string',
|
|
23
|
+
short: 'g',
|
|
24
|
+
help: 'Specific group ID to fetch'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const args = parseArgs({ options, strict: false })
|
|
29
|
+
|
|
30
|
+
if (args.values['help']) {
|
|
31
|
+
await printHelpText({
|
|
32
|
+
options,
|
|
33
|
+
name: 'yoto-groups',
|
|
34
|
+
version: pkg.version,
|
|
35
|
+
exampleFn: ({ name }) => ` Yoto family library groups information helper\n\n Examples:\n ${name} # List all family library groups\n ${name} --group-id abc123 # Get specific group\n`
|
|
36
|
+
})
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Load tokens from environment
|
|
41
|
+
const { clientId, refreshToken, accessToken, envFile } = loadTokensFromEnv(args)
|
|
42
|
+
|
|
43
|
+
const groupId = args.values['group-id'] ? String(args.values['group-id']) : null
|
|
44
|
+
|
|
45
|
+
async function main () {
|
|
46
|
+
printHeader('Yoto Family Library Groups')
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Create client
|
|
50
|
+
const client = createYotoClient({
|
|
51
|
+
clientId,
|
|
52
|
+
refreshToken,
|
|
53
|
+
accessToken,
|
|
54
|
+
outputFile: envFile
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (groupId) {
|
|
58
|
+
// Get specific group
|
|
59
|
+
console.log(`\nFetching group: ${groupId}\n`)
|
|
60
|
+
const group = await client.getGroup({ groupId })
|
|
61
|
+
console.dir(group, { depth: null, colors: true })
|
|
62
|
+
} else {
|
|
63
|
+
// Get all groups
|
|
64
|
+
console.log('\nFetching all family library groups...\n')
|
|
65
|
+
const groups = await client.getGroups()
|
|
66
|
+
console.dir(groups, { depth: null, colors: true })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
process.exit(0)
|
|
70
|
+
} catch (error) {
|
|
71
|
+
handleCliError(error)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Run the main function
|
|
76
|
+
await main().catch(err => {
|
|
77
|
+
console.error('\n❌ Unexpected error:')
|
|
78
|
+
console.error(err)
|
|
79
|
+
process.exit(1)
|
|
80
|
+
})
|
package/bin/icons.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["icons.js"],"names":[],"mappings":""}
|