yoto-nodejs-client 0.0.8 → 0.0.9
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/bin/content.js +0 -0
- package/bin/devices.js +0 -0
- package/bin/groups.js +0 -0
- package/bin/icons.js +0 -0
- package/bin/refresh-token.js +0 -0
- package/lib/pkg.d.cts +3 -1
- package/lib/yoto-device.d.ts +60 -0
- package/lib/yoto-device.d.ts.map +1 -1
- package/lib/yoto-device.js +212 -4
- package/lib/yoto-device.test.js +218 -0
- package/package.json +15 -15
package/bin/content.js
CHANGED
|
File without changes
|
package/bin/devices.js
CHANGED
|
File without changes
|
package/bin/groups.js
CHANGED
|
File without changes
|
package/bin/icons.js
CHANGED
|
File without changes
|
package/bin/refresh-token.js
CHANGED
|
File without changes
|
package/lib/pkg.d.cts
CHANGED
|
@@ -7,13 +7,15 @@ export const pkg: {
|
|
|
7
7
|
url: string;
|
|
8
8
|
};
|
|
9
9
|
dependencies: {
|
|
10
|
+
fastq: string;
|
|
10
11
|
"jwt-decode": string;
|
|
11
12
|
mqtt: string;
|
|
13
|
+
"quick-lru": string;
|
|
12
14
|
undici: string;
|
|
13
15
|
};
|
|
14
16
|
devDependencies: {
|
|
15
|
-
"@unblessed/node": string;
|
|
16
17
|
"@types/node": string;
|
|
18
|
+
"@unblessed/node": string;
|
|
17
19
|
"@voxpelli/tsconfig": string;
|
|
18
20
|
argsclopts: string;
|
|
19
21
|
"auto-changelog": string;
|
package/lib/yoto-device.d.ts
CHANGED
|
@@ -116,6 +116,34 @@ export const YotoDeviceModelConfigType: {};
|
|
|
116
116
|
* @property {string} updatedAt - ISO 8601 timestamp of last update
|
|
117
117
|
*/
|
|
118
118
|
export const YotoPlaybackStateType: {};
|
|
119
|
+
/**
|
|
120
|
+
* Cached chapter info for playback enrichment.
|
|
121
|
+
* @typedef {Object} YotoCardCacheChapterInfo
|
|
122
|
+
* @property {string} key
|
|
123
|
+
* @property {string} title
|
|
124
|
+
*/
|
|
125
|
+
/**
|
|
126
|
+
* Cached track info for playback enrichment.
|
|
127
|
+
* @typedef {Object} YotoCardCacheTrackInfo
|
|
128
|
+
* @property {string} key
|
|
129
|
+
* @property {string} title
|
|
130
|
+
* @property {number} duration
|
|
131
|
+
* @property {string} chapterKey
|
|
132
|
+
* @property {string} chapterTitle
|
|
133
|
+
*/
|
|
134
|
+
/**
|
|
135
|
+
* Cached card metadata for playback enrichment.
|
|
136
|
+
* @typedef {Object} YotoCardCacheEntry
|
|
137
|
+
* @property {string} cardId
|
|
138
|
+
* @property {string} title
|
|
139
|
+
* @property {Map<string, YotoCardCacheChapterInfo>} chaptersByKey
|
|
140
|
+
* @property {Map<string, YotoCardCacheTrackInfo>} tracksByKey
|
|
141
|
+
*/
|
|
142
|
+
/**
|
|
143
|
+
* Cached card lookup task.
|
|
144
|
+
* @typedef {Object} YotoCardCacheTask
|
|
145
|
+
* @property {string} cardId
|
|
146
|
+
*/
|
|
119
147
|
/**
|
|
120
148
|
* Complete device client state
|
|
121
149
|
* @typedef {Object} YotoDeviceClientState
|
|
@@ -845,6 +873,38 @@ export type YotoPlaybackState = {
|
|
|
845
873
|
*/
|
|
846
874
|
updatedAt: string;
|
|
847
875
|
};
|
|
876
|
+
/**
|
|
877
|
+
* Cached chapter info for playback enrichment.
|
|
878
|
+
*/
|
|
879
|
+
export type YotoCardCacheChapterInfo = {
|
|
880
|
+
key: string;
|
|
881
|
+
title: string;
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* Cached track info for playback enrichment.
|
|
885
|
+
*/
|
|
886
|
+
export type YotoCardCacheTrackInfo = {
|
|
887
|
+
key: string;
|
|
888
|
+
title: string;
|
|
889
|
+
duration: number;
|
|
890
|
+
chapterKey: string;
|
|
891
|
+
chapterTitle: string;
|
|
892
|
+
};
|
|
893
|
+
/**
|
|
894
|
+
* Cached card metadata for playback enrichment.
|
|
895
|
+
*/
|
|
896
|
+
export type YotoCardCacheEntry = {
|
|
897
|
+
cardId: string;
|
|
898
|
+
title: string;
|
|
899
|
+
chaptersByKey: Map<string, YotoCardCacheChapterInfo>;
|
|
900
|
+
tracksByKey: Map<string, YotoCardCacheTrackInfo>;
|
|
901
|
+
};
|
|
902
|
+
/**
|
|
903
|
+
* Cached card lookup task.
|
|
904
|
+
*/
|
|
905
|
+
export type YotoCardCacheTask = {
|
|
906
|
+
cardId: string;
|
|
907
|
+
};
|
|
848
908
|
/**
|
|
849
909
|
* Complete device client state
|
|
850
910
|
*/
|
package/lib/yoto-device.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yoto-device.d.ts","sourceRoot":"","sources":["yoto-device.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"yoto-device.d.ts","sourceRoot":"","sources":["yoto-device.js"],"names":[],"mappings":"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"}
|
package/lib/yoto-device.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @import { IConnackPacket } from 'mqtt'
|
|
11
|
+
* @import { queueAsPromised } from 'fastq'
|
|
11
12
|
* @import { YotoClient } from './api-client.js'
|
|
13
|
+
* @import { YotoCard } from './api-endpoints/content.js'
|
|
12
14
|
* @import { YotoDevice, YotoDeviceConfig, YotoDeviceShortcuts, YotoDeviceFullStatus, YotoDeviceStatusResponse, YotoDeviceCommand, YotoDeviceCommandResponse } from './api-endpoints/devices.js'
|
|
13
15
|
* @import { YotoMqttClient, YotoMqttStatus, YotoEventsMessage, YotoLegacyStatus, YotoStatusMessage, YotoStatusLegacyMessage, YotoResponseMessage, YotoMqttClientDisconnectMetadata, YotoMqttClientCloseMetadata, PlaybackStatus } from './mqtt/client.js'
|
|
14
16
|
* @import { YotoMqttOptions } from './mqtt/factory.js'
|
|
@@ -16,10 +18,16 @@
|
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import { EventEmitter } from 'events'
|
|
21
|
+
import fastq from 'fastq'
|
|
22
|
+
import QuickLRU from 'quick-lru'
|
|
19
23
|
import { parseTemperature } from './helpers/temperature.js'
|
|
20
24
|
import { detectPowerState } from './helpers/power-state.js'
|
|
21
25
|
import { typedKeys } from './helpers/typed-keys.js'
|
|
22
26
|
|
|
27
|
+
const CARD_CACHE_MAX_SIZE = 2000
|
|
28
|
+
const CARD_CACHE_QUEUE_CONCURRENCY = 1
|
|
29
|
+
const CARD_ID_NONE = 'none'
|
|
30
|
+
|
|
23
31
|
// ============================================================================
|
|
24
32
|
// Type Definitions
|
|
25
33
|
// ============================================================================
|
|
@@ -410,6 +418,38 @@ export const YotoPlaybackStateType = {}
|
|
|
410
418
|
// Internal Types
|
|
411
419
|
// ============================================================================
|
|
412
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Cached chapter info for playback enrichment.
|
|
423
|
+
* @typedef {Object} YotoCardCacheChapterInfo
|
|
424
|
+
* @property {string} key
|
|
425
|
+
* @property {string} title
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Cached track info for playback enrichment.
|
|
430
|
+
* @typedef {Object} YotoCardCacheTrackInfo
|
|
431
|
+
* @property {string} key
|
|
432
|
+
* @property {string} title
|
|
433
|
+
* @property {number} duration
|
|
434
|
+
* @property {string} chapterKey
|
|
435
|
+
* @property {string} chapterTitle
|
|
436
|
+
*/
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Cached card metadata for playback enrichment.
|
|
440
|
+
* @typedef {Object} YotoCardCacheEntry
|
|
441
|
+
* @property {string} cardId
|
|
442
|
+
* @property {string} title
|
|
443
|
+
* @property {Map<string, YotoCardCacheChapterInfo>} chaptersByKey
|
|
444
|
+
* @property {Map<string, YotoCardCacheTrackInfo>} tracksByKey
|
|
445
|
+
*/
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Cached card lookup task.
|
|
449
|
+
* @typedef {Object} YotoCardCacheTask
|
|
450
|
+
* @property {string} cardId
|
|
451
|
+
*/
|
|
452
|
+
|
|
413
453
|
/**
|
|
414
454
|
* Complete device client state
|
|
415
455
|
* @typedef {Object} YotoDeviceClientState
|
|
@@ -567,6 +607,9 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
567
607
|
/** @type {NodeJS.Timeout | null} */ #eventsRequestTimer = null
|
|
568
608
|
/** @type {NodeJS.Timeout | null} */ #backgroundPollTimer = null
|
|
569
609
|
/** @type {number | null} */ #shutdownDetectedAt = null
|
|
610
|
+
/** @type {QuickLRU<string, YotoCardCacheEntry>} */ #cardCache = new QuickLRU({ maxSize: CARD_CACHE_MAX_SIZE })
|
|
611
|
+
/** @type {queueAsPromised<YotoCardCacheTask, YotoCardCacheEntry | null>} */ #cardCacheQueue = fastq.promise(this, this.#fetchCardCacheTask, CARD_CACHE_QUEUE_CONCURRENCY)
|
|
612
|
+
/** @type {Set<string>} */ #cardCacheQueueTasks = new Set()
|
|
570
613
|
|
|
571
614
|
/**
|
|
572
615
|
* Create a Yoto device client
|
|
@@ -3102,6 +3145,69 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
3102
3145
|
}
|
|
3103
3146
|
}
|
|
3104
3147
|
|
|
3148
|
+
/**
|
|
3149
|
+
* Apply cached card metadata to playback state (if available).
|
|
3150
|
+
* @param {string | null} cardId
|
|
3151
|
+
* @param {YotoPlaybackState} playback
|
|
3152
|
+
* @param {Set<keyof YotoPlaybackState>} playbackChangedFields
|
|
3153
|
+
* @returns {boolean}
|
|
3154
|
+
*/
|
|
3155
|
+
#applyCardCacheToPlayback (cardId, playback, playbackChangedFields) {
|
|
3156
|
+
if (!isCacheableCardId(cardId)) {
|
|
3157
|
+
return false
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
const cachedEntry = this.#cardCache.get(cardId)
|
|
3161
|
+
if (cachedEntry) {
|
|
3162
|
+
return applyCardCacheToPlayback(cachedEntry, playback, playbackChangedFields)
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
this.#queueCardCacheFetch(cardId)
|
|
3166
|
+
return false
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
/**
|
|
3170
|
+
* @param {YotoCardCacheTask} task
|
|
3171
|
+
* @returns {Promise<YotoCardCacheEntry | null>}
|
|
3172
|
+
*/
|
|
3173
|
+
async #fetchCardCacheTask (task) {
|
|
3174
|
+
try {
|
|
3175
|
+
const response = await this.#client.getContent({ cardId: task.cardId })
|
|
3176
|
+
const entry = buildCardCacheEntry(response.card)
|
|
3177
|
+
this.#cardCache.set(task.cardId, entry)
|
|
3178
|
+
|
|
3179
|
+
if (this.#state.playback.cardId === task.cardId) {
|
|
3180
|
+
/** @type {Set<keyof YotoPlaybackState>} */
|
|
3181
|
+
const playbackChangedFields = new Set()
|
|
3182
|
+
if (applyCardCacheToPlayback(entry, this.#state.playback, playbackChangedFields)) {
|
|
3183
|
+
this.#state.playback.updatedAt = new Date().toISOString()
|
|
3184
|
+
this.#state.lastUpdate.playback = Date.now()
|
|
3185
|
+
this.emit('playbackUpdate', this.playback, playbackChangedFields)
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
return entry
|
|
3190
|
+
} catch {
|
|
3191
|
+
return null
|
|
3192
|
+
} finally {
|
|
3193
|
+
this.#cardCacheQueueTasks.delete(task.cardId)
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
/**
|
|
3198
|
+
* Fetch card content for playback enrichment and cache the result.
|
|
3199
|
+
* @param {string} cardId
|
|
3200
|
+
* @returns {void}
|
|
3201
|
+
*/
|
|
3202
|
+
#queueCardCacheFetch (cardId) {
|
|
3203
|
+
if (this.#cardCache.has(cardId) || this.#cardCacheQueueTasks.has(cardId)) {
|
|
3204
|
+
return
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
this.#cardCacheQueueTasks.add(cardId)
|
|
3208
|
+
this.#cardCacheQueue.push({ cardId })
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3105
3211
|
/**
|
|
3106
3212
|
* Handle MQTT event message - updates status, config, and playback
|
|
3107
3213
|
* Events are partial updates - only changed fields are included
|
|
@@ -3123,6 +3229,7 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
3123
3229
|
let playbackChanged = false
|
|
3124
3230
|
|
|
3125
3231
|
const { status, config, playback } = this.#state
|
|
3232
|
+
const previousCardId = playback.cardId
|
|
3126
3233
|
/** @type {Set<keyof YotoDeviceStatus>} */
|
|
3127
3234
|
const statusChangedFields = new Set()
|
|
3128
3235
|
/** @type {Set<keyof YotoDeviceModelConfig>} */
|
|
@@ -3204,10 +3311,13 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
3204
3311
|
break
|
|
3205
3312
|
}
|
|
3206
3313
|
case 'cardId': {
|
|
3207
|
-
if (eventsMessage.cardId !== undefined
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3314
|
+
if (eventsMessage.cardId !== undefined) {
|
|
3315
|
+
const normalizedCardId = eventsMessage.cardId === CARD_ID_NONE ? null : eventsMessage.cardId
|
|
3316
|
+
if (playback.cardId !== normalizedCardId) {
|
|
3317
|
+
playback.cardId = normalizedCardId
|
|
3318
|
+
playbackChangedFields.add('cardId')
|
|
3319
|
+
playbackChanged = true
|
|
3320
|
+
}
|
|
3211
3321
|
}
|
|
3212
3322
|
break
|
|
3213
3323
|
}
|
|
@@ -3286,6 +3396,12 @@ export class YotoDeviceModel extends EventEmitter {
|
|
|
3286
3396
|
handleField(key, eventsMessage)
|
|
3287
3397
|
}
|
|
3288
3398
|
|
|
3399
|
+
if (previousCardId !== playback.cardId) {
|
|
3400
|
+
if (this.#applyCardCacheToPlayback(playback.cardId, playback, playbackChangedFields)) {
|
|
3401
|
+
playbackChanged = true
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3289
3405
|
// Update timestamps and emit events for changed categories
|
|
3290
3406
|
if (statusChanged) {
|
|
3291
3407
|
this.#state.lastUpdate.status = Date.now()
|
|
@@ -3332,6 +3448,98 @@ function createEmptyPlaybackState () {
|
|
|
3332
3448
|
}
|
|
3333
3449
|
}
|
|
3334
3450
|
|
|
3451
|
+
/**
|
|
3452
|
+
* @param {string | null} cardId
|
|
3453
|
+
* @returns {cardId is string}
|
|
3454
|
+
*/
|
|
3455
|
+
function isCacheableCardId (cardId) {
|
|
3456
|
+
return typeof cardId === 'string' && cardId.length > 0 && cardId !== CARD_ID_NONE
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
/**
|
|
3460
|
+
* @param {YotoCard} card
|
|
3461
|
+
* @returns {YotoCardCacheEntry}
|
|
3462
|
+
*/
|
|
3463
|
+
function buildCardCacheEntry (card) {
|
|
3464
|
+
const chaptersByKey = new Map()
|
|
3465
|
+
const tracksByKey = new Map()
|
|
3466
|
+
|
|
3467
|
+
for (const chapter of card.content.chapters) {
|
|
3468
|
+
chaptersByKey.set(chapter.key, {
|
|
3469
|
+
key: chapter.key,
|
|
3470
|
+
title: chapter.title
|
|
3471
|
+
})
|
|
3472
|
+
|
|
3473
|
+
for (const track of chapter.tracks) {
|
|
3474
|
+
tracksByKey.set(track.key, {
|
|
3475
|
+
key: track.key,
|
|
3476
|
+
title: track.title,
|
|
3477
|
+
duration: track.duration,
|
|
3478
|
+
chapterKey: chapter.key,
|
|
3479
|
+
chapterTitle: chapter.title
|
|
3480
|
+
})
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
return {
|
|
3485
|
+
cardId: card.cardId,
|
|
3486
|
+
title: card.title,
|
|
3487
|
+
chaptersByKey,
|
|
3488
|
+
tracksByKey
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
/**
|
|
3493
|
+
* @param {YotoCardCacheEntry} entry
|
|
3494
|
+
* @param {YotoPlaybackState} playback
|
|
3495
|
+
* @param {Set<keyof YotoPlaybackState>} changedFields
|
|
3496
|
+
* @returns {boolean}
|
|
3497
|
+
*/
|
|
3498
|
+
function applyCardCacheToPlayback (entry, playback, changedFields) {
|
|
3499
|
+
let changed = false
|
|
3500
|
+
const { trackKey } = playback
|
|
3501
|
+
|
|
3502
|
+
if (trackKey) {
|
|
3503
|
+
const trackInfo = entry.tracksByKey.get(trackKey)
|
|
3504
|
+
if (trackInfo) {
|
|
3505
|
+
if (playback.trackTitle !== trackInfo.title) {
|
|
3506
|
+
playback.trackTitle = trackInfo.title
|
|
3507
|
+
changedFields.add('trackTitle')
|
|
3508
|
+
changed = true
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
if (playback.trackLength !== trackInfo.duration) {
|
|
3512
|
+
playback.trackLength = trackInfo.duration
|
|
3513
|
+
changedFields.add('trackLength')
|
|
3514
|
+
changed = true
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
if (playback.chapterKey !== trackInfo.chapterKey) {
|
|
3518
|
+
playback.chapterKey = trackInfo.chapterKey
|
|
3519
|
+
changedFields.add('chapterKey')
|
|
3520
|
+
changed = true
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
if (playback.chapterKey === trackInfo.chapterKey && playback.chapterTitle !== trackInfo.chapterTitle) {
|
|
3524
|
+
playback.chapterTitle = trackInfo.chapterTitle
|
|
3525
|
+
changedFields.add('chapterTitle')
|
|
3526
|
+
changed = true
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
if (playback.chapterKey) {
|
|
3532
|
+
const chapterInfo = entry.chaptersByKey.get(playback.chapterKey)
|
|
3533
|
+
if (chapterInfo && playback.chapterTitle !== chapterInfo.title) {
|
|
3534
|
+
playback.chapterTitle = chapterInfo.title
|
|
3535
|
+
changedFields.add('chapterTitle')
|
|
3536
|
+
changed = true
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
return changed
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3335
3543
|
/**
|
|
3336
3544
|
* Create an empty device config object
|
|
3337
3545
|
* @returns {YotoDeviceModelConfig}
|
package/lib/yoto-device.test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @import { YotoDevice } from './api-endpoints/devices.js' */
|
|
2
|
+
/** @import { YotoPlaybackState } from './yoto-device.js' */
|
|
2
3
|
|
|
3
4
|
import test from 'node:test'
|
|
4
5
|
import assert from 'node:assert/strict'
|
|
@@ -15,6 +16,122 @@ import {
|
|
|
15
16
|
} from './test-helpers/device-model-test-helpers.js'
|
|
16
17
|
|
|
17
18
|
const envPath = join(import.meta.dirname, '..', '.env')
|
|
19
|
+
const PLAYBACK_SAMPLE_SCAN_LIMIT = 5
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {Object} PlaybackSample
|
|
23
|
+
* @property {string} cardId
|
|
24
|
+
* @property {Map<string, { title: string, duration: number, chapterKey: string, chapterTitle: string }>} tracksByKey
|
|
25
|
+
* @property {string} chapterKey
|
|
26
|
+
* @property {string} chapterTitle
|
|
27
|
+
* @property {string} trackKey
|
|
28
|
+
* @property {string} trackTitle
|
|
29
|
+
* @property {number} trackLength
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {YotoDeviceModel} model
|
|
34
|
+
* @param {(playback: YotoPlaybackState, changedFields: Set<keyof YotoPlaybackState>) => boolean} predicate
|
|
35
|
+
* @param {number} timeoutMs
|
|
36
|
+
* @param {string} label
|
|
37
|
+
* @returns {Promise<[YotoPlaybackState, Set<keyof YotoPlaybackState>]>}
|
|
38
|
+
*/
|
|
39
|
+
function waitForPlaybackUpdateMatching (model, predicate, timeoutMs, label) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const timeout = setTimeout(() => {
|
|
42
|
+
model.off('playbackUpdate', handler)
|
|
43
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`))
|
|
44
|
+
}, timeoutMs)
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {YotoPlaybackState} playback
|
|
48
|
+
* @param {Set<keyof YotoPlaybackState>} changedFields
|
|
49
|
+
*/
|
|
50
|
+
const handler = (playback, changedFields) => {
|
|
51
|
+
let matches = false
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
matches = predicate(playback, changedFields)
|
|
55
|
+
} catch (err) {
|
|
56
|
+
clearTimeout(timeout)
|
|
57
|
+
model.off('playbackUpdate', handler)
|
|
58
|
+
reject(err)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!matches) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clearTimeout(timeout)
|
|
67
|
+
model.off('playbackUpdate', handler)
|
|
68
|
+
resolve([playback, changedFields])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
model.on('playbackUpdate', handler)
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {YotoClient} client
|
|
77
|
+
* @returns {Promise<PlaybackSample | null>}
|
|
78
|
+
*/
|
|
79
|
+
async function findPlaybackSample (client) {
|
|
80
|
+
const response = await client.getUserMyoContent({ showDeleted: false })
|
|
81
|
+
|
|
82
|
+
for (const card of response.cards.slice(0, PLAYBACK_SAMPLE_SCAN_LIMIT)) {
|
|
83
|
+
if (!card.cardId || card.deleted) {
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const contentResponse = await client.getContent({ cardId: card.cardId })
|
|
89
|
+
const chapters = contentResponse.card.content.chapters
|
|
90
|
+
const firstChapter = chapters.find(candidate => candidate.tracks.length > 0)
|
|
91
|
+
|
|
92
|
+
if (!firstChapter) {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const firstTrack = firstChapter.tracks[0]
|
|
97
|
+
|
|
98
|
+
if (!firstTrack || typeof firstTrack.title !== 'string' || !Number.isFinite(firstTrack.duration)) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const tracksByKey = new Map()
|
|
103
|
+
|
|
104
|
+
for (const chapter of chapters) {
|
|
105
|
+
for (const track of chapter.tracks) {
|
|
106
|
+
if (!track || typeof track.title !== 'string' || !Number.isFinite(track.duration)) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
tracksByKey.set(track.key, {
|
|
111
|
+
title: track.title,
|
|
112
|
+
duration: track.duration,
|
|
113
|
+
chapterKey: chapter.key,
|
|
114
|
+
chapterTitle: chapter.title
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
cardId: contentResponse.card.cardId,
|
|
121
|
+
tracksByKey,
|
|
122
|
+
chapterKey: firstChapter.key,
|
|
123
|
+
chapterTitle: firstChapter.title,
|
|
124
|
+
trackKey: firstTrack.key,
|
|
125
|
+
trackTitle: firstTrack.title,
|
|
126
|
+
trackLength: firstTrack.duration
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
18
135
|
|
|
19
136
|
/**
|
|
20
137
|
* @returns {YotoClient}
|
|
@@ -86,3 +203,104 @@ test('YotoDeviceModel - online devices', async (t) => {
|
|
|
86
203
|
await assertDeviceModel(client, onlineV3)
|
|
87
204
|
})
|
|
88
205
|
})
|
|
206
|
+
|
|
207
|
+
test('YotoDeviceModel - playback normalization', async (t) => {
|
|
208
|
+
loadTestTokens()
|
|
209
|
+
const client = createTestClient()
|
|
210
|
+
const response = await client.getDevices()
|
|
211
|
+
|
|
212
|
+
const onlineDevice = response.devices.find(device => device.online)
|
|
213
|
+
if (!onlineDevice) {
|
|
214
|
+
t.skip('No online device found')
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sample = await findPlaybackSample(client)
|
|
219
|
+
if (!sample) {
|
|
220
|
+
t.skip('No MYO content with tracks available')
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const model = new YotoDeviceModel(client, onlineDevice)
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
await waitForModelReady(model)
|
|
228
|
+
assertPlaybackShape(model.playback)
|
|
229
|
+
|
|
230
|
+
const mqttClient = model.mqttClient
|
|
231
|
+
assert.ok(mqttClient, 'MQTT client should be initialized')
|
|
232
|
+
|
|
233
|
+
await t.test('card cache enrichment', async () => {
|
|
234
|
+
if (model.playback.cardId === sample.cardId) {
|
|
235
|
+
const resetPromise = waitForPlaybackUpdateMatching(
|
|
236
|
+
model,
|
|
237
|
+
(playback, changedFields) => playback.cardId === null && changedFields.has('cardId'),
|
|
238
|
+
5000,
|
|
239
|
+
'playback card reset'
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
mqttClient.emit('events', 'test', { cardId: 'none' })
|
|
243
|
+
await resetPromise
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const cardUpdatePromise = waitForPlaybackUpdateMatching(
|
|
247
|
+
model,
|
|
248
|
+
(playback, changedFields) => playback.cardId === sample.cardId && changedFields.has('cardId'),
|
|
249
|
+
5000,
|
|
250
|
+
'playback card update'
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
const cacheUpdatePromise = waitForPlaybackUpdateMatching(
|
|
254
|
+
model,
|
|
255
|
+
(playback, changedFields) => {
|
|
256
|
+
if (playback.cardId !== sample.cardId) {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
changedFields.has('trackTitle') ||
|
|
262
|
+
changedFields.has('trackLength') ||
|
|
263
|
+
changedFields.has('chapterKey') ||
|
|
264
|
+
changedFields.has('chapterTitle')
|
|
265
|
+
)
|
|
266
|
+
},
|
|
267
|
+
15000,
|
|
268
|
+
'playback cache update'
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
mqttClient.emit('events', 'test', {
|
|
272
|
+
cardId: sample.cardId,
|
|
273
|
+
trackKey: sample.trackKey
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const [cardPlayback] = await cardUpdatePromise
|
|
277
|
+
assert.equal(cardPlayback.cardId, sample.cardId)
|
|
278
|
+
|
|
279
|
+
const [cachePlayback] = await cacheUpdatePromise
|
|
280
|
+
assert.ok(cachePlayback.trackKey, 'trackKey should be set after cache update')
|
|
281
|
+
const trackInfo = sample.tracksByKey.get(cachePlayback.trackKey)
|
|
282
|
+
assert.ok(trackInfo, 'trackKey should exist in card content')
|
|
283
|
+
assert.equal(cachePlayback.trackTitle, trackInfo.title)
|
|
284
|
+
assert.equal(cachePlayback.trackLength, trackInfo.duration)
|
|
285
|
+
assert.equal(cachePlayback.chapterKey, trackInfo.chapterKey)
|
|
286
|
+
assert.equal(cachePlayback.chapterTitle, trackInfo.chapterTitle)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
await t.test('cardId none normalization', async () => {
|
|
290
|
+
const noneUpdatePromise = waitForPlaybackUpdateMatching(
|
|
291
|
+
model,
|
|
292
|
+
(playback, changedFields) => playback.cardId === null && changedFields.has('cardId'),
|
|
293
|
+
5000,
|
|
294
|
+
'playback cardId none normalization'
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
mqttClient.emit('events', 'test', { cardId: 'none' })
|
|
298
|
+
|
|
299
|
+
const [nonePlayback, changedFields] = await noneUpdatePromise
|
|
300
|
+
assert.equal(nonePlayback.cardId, null)
|
|
301
|
+
assert.ok(changedFields.has('cardId'), 'cardId should be in changed fields')
|
|
302
|
+
})
|
|
303
|
+
} finally {
|
|
304
|
+
await model.stop()
|
|
305
|
+
}
|
|
306
|
+
})
|
package/package.json
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoto-nodejs-client",
|
|
3
3
|
"description": "(Unofficial) Node.js client for the Yoto API with automatic token refresh, MQTT device communication, and TypeScript support",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/yoto-nodejs-client/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
+
"fastq": "^1.20.1",
|
|
10
11
|
"jwt-decode": "^4.0.0",
|
|
11
12
|
"mqtt": "^5.14.1",
|
|
13
|
+
"quick-lru": "^7.3.0",
|
|
12
14
|
"undici": "^7.16.0"
|
|
13
15
|
},
|
|
14
16
|
"devDependencies": {
|
|
15
|
-
"@unblessed/node": "1.0.0-alpha.23",
|
|
16
17
|
"@types/node": "^25.0.0",
|
|
18
|
+
"@unblessed/node": "1.0.0-alpha.23",
|
|
17
19
|
"@voxpelli/tsconfig": "^16.1.0",
|
|
18
20
|
"argsclopts": "^1.0.5",
|
|
19
21
|
"auto-changelog": "^2.0.0",
|
|
@@ -69,9 +71,17 @@
|
|
|
69
71
|
"type": "git",
|
|
70
72
|
"url": "https://github.com/bcomnes/yoto-nodejs-client.git"
|
|
71
73
|
},
|
|
74
|
+
"funding": {
|
|
75
|
+
"type": "individual",
|
|
76
|
+
"url": "https://github.com/sponsors/bcomnes"
|
|
77
|
+
},
|
|
78
|
+
"c8": {
|
|
79
|
+
"reporter": [
|
|
80
|
+
"lcov",
|
|
81
|
+
"text"
|
|
82
|
+
]
|
|
83
|
+
},
|
|
72
84
|
"scripts": {
|
|
73
|
-
"prepublishOnly": "npm run build && git push --follow-tags && gh-release -y",
|
|
74
|
-
"postpublish": "npm run clean",
|
|
75
85
|
"test": "run-s test:*",
|
|
76
86
|
"test:lint": "eslint",
|
|
77
87
|
"test:tsc": "tsc",
|
|
@@ -85,15 +95,5 @@
|
|
|
85
95
|
"clean:declarations-top": "rm -rf $(find . -maxdepth 1 -type f -name '*.d.ts*' -o -name '*.d.cts*' -o -name '*.d.mts*')",
|
|
86
96
|
"clean:declarations-lib": "rm -rf $(find lib -type f \\( -name '*.d.ts*' -o -name '*.d.cts*' -o -name '*.d.mts*' \\) ! -name '*-types.d.ts')",
|
|
87
97
|
"clean:declarations-bin": "rm -rf $(find bin -type f \\( -name '*.d.ts*' -o -name '*.d.cts*' -o -name '*.d.mts*' \\) ! -name '*-types.d.ts')"
|
|
88
|
-
},
|
|
89
|
-
"funding": {
|
|
90
|
-
"type": "individual",
|
|
91
|
-
"url": "https://github.com/sponsors/bcomnes"
|
|
92
|
-
},
|
|
93
|
-
"c8": {
|
|
94
|
-
"reporter": [
|
|
95
|
-
"lcov",
|
|
96
|
-
"text"
|
|
97
|
-
]
|
|
98
98
|
}
|
|
99
|
-
}
|
|
99
|
+
}
|