yoto-nodejs-client 0.0.10 → 0.0.11

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/auth.js CHANGED
@@ -108,7 +108,7 @@ async function main () {
108
108
  if (error.message === 'Device code has expired') {
109
109
  console.error('\n❌ Device code has expired. Please run the command again.')
110
110
  process.exit(1)
111
- } else if (error.body?.error === 'expired_token') {
111
+ } else if (error.jsonBody?.error === 'expired_token') {
112
112
  console.error('\n❌ Device code has expired. Please run the command again.')
113
113
  process.exit(1)
114
114
  } else {
@@ -256,19 +256,22 @@ async function main () {
256
256
  // Show only changed fields
257
257
  console.log(`\n${playbackIcon} PLAYBACK UPDATE [${timestamp}]: (${changedFields.size} change${changedFields.size === 1 ? '' : 's'})`)
258
258
  for (const field of changedFields) {
259
+ if (field === 'trackLength' && changedFields.has('position')) {
260
+ continue
261
+ }
262
+
259
263
  let value = playback[field]
260
264
  if (field === 'position' || field === 'trackLength') {
261
- // Show position/trackLength together if either changed
265
+ // Show position/trackLength together if either changed.
262
266
  value = `${playback.position}/${playback.trackLength}s`
263
267
  console.log(` position/trackLength: ${value}`)
264
- // Skip if we already printed this combo
265
- if (field === 'trackLength') continue
266
- } else {
267
- if (field === 'cardDurationSeconds' && typeof value === 'number') {
268
- value = `${value}s`
269
- }
270
- console.log(` ${field}: ${value}`)
268
+ continue
269
+ }
270
+
271
+ if (field === 'cardDurationSeconds' && typeof value === 'number') {
272
+ value = `${value}s`
271
273
  }
274
+ console.log(` ${field}: ${value}`)
272
275
  }
273
276
  } else {
274
277
  console.log(`\n${playbackIcon} PLAYBACK UPDATE [${timestamp}]: (no changes)`)
@@ -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,CAwBtB;AAED;;;GAGG;AACH,sCAFW,GAAG,QAyBb;AAED;;;GAGG;AACH,mCAFW,MAAM,QAKhB;sDA5IoD,YAAY;2BAGtC,yBAAyB"}
1
+ {"version":3,"file":"cli-helpers.d.ts","sourceRoot":"","sources":["cli-helpers.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,oCAFa,gCAAgC,CAoB5C;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;GAKG;AACH,wCAJW;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;CAAE,GACtD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAoB5F;AAED;;;;;;;;GAQG;AACH,sFANG;IAAwB,QAAQ,EAAxB,MAAM;IACU,YAAY,EAA5B,MAAM;IACU,WAAW,EAA3B,MAAM;IACW,UAAU;CACnC,GAAU,UAAU,CAwBtB;AAED;;;GAGG;AACH,sCAFW,GAAG,QA4Bb;AAED;;;GAGG;AACH,mCAFW,MAAM,QAKhB;sDA/IoD,YAAY;2BAGtC,yBAAyB"}
@@ -116,17 +116,20 @@ export function handleCliError (error) {
116
116
  console.error(`Status Code: ${error.statusCode}`)
117
117
  }
118
118
 
119
- if (error.body?.error) {
120
- console.error(`Error: ${error.body.error}`)
119
+ if (error.jsonBody?.error) {
120
+ console.error(`Error: ${error.jsonBody.error}`)
121
121
  }
122
122
 
123
- if (error.body?.error_description) {
124
- console.error(`Description: ${error.body.error_description}`)
123
+ if (error.jsonBody?.error_description) {
124
+ console.error(`Description: ${error.jsonBody.error_description}`)
125
125
  }
126
126
 
127
- if (error.body && typeof error.body === 'object') {
127
+ if (error.jsonBody && typeof error.jsonBody === 'object') {
128
128
  console.error('\nFull error response:')
129
- console.error(JSON.stringify(error.body, null, 2))
129
+ console.error(JSON.stringify(error.jsonBody, null, 2))
130
+ } else if (error.textBody) {
131
+ console.error('\nRaw error response:')
132
+ console.error(error.textBody)
130
133
  }
131
134
 
132
135
  process.exit(1)
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.js"],"names":[],"mappings":"AAWA;;;GAGG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,4JAbG;IAA0B,QAAQ;IACR,KAAK;IACS,YAAY,EAA3C,qBAAqB;IACL,QAAQ,EAAxB,MAAM;IACU,WAAW,EAA3B,MAAM;IACU,KAAK,EAArB,MAAM;IACW,KAAK;IACQ,MAAM;IACnB,MAAM;IACN,aAAa;IACS,mBAAmB;CACnE,GAAS,MAAM,CA+BjB;AAED;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,4KAdG;IAAqC,SAAS,EAArC,kBAAkB;IACD,IAAI;IACJ,WAAW;IACX,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,KAAK;IACL,YAAY;IACZ,UAAU;IACV,QAAQ;IACR,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,iBAAiB,CAAC,CAoErC;AAED;;;;;;;;;GASG;AAEH;;;;;;;;;;GAUG;AACH,4FAPG;IAAyB,QAAQ,EAAxB,MAAM;IACW,KAAK;IACL,QAAQ;IACR,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,sBAAsB,CAAC,CA+B1C;AAED;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,mHAVG;IAAwB,UAAU,EAA1B,MAAM;IACU,QAAQ,EAAxB,MAAM;IACW,QAAQ;IACR,eAAe;IACf,SAAS;IACD,cAAc;;;CAC/C,GAAU,OAAO,CAAC,oBAAoB,CAAC,CAuDzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,8IArCG;IAAwB,UAAU,EAA1B,MAAM;IACU,QAAQ,EAAxB,MAAM;IACW,QAAQ;IACR,eAAe;IACf,SAAS;IACT,SAAS;IACD,cAAc;;;IACU,MAAM,aAA9C,oBAAoB,KAAK,IAAI;CAC9C,GAAU,OAAO,CAAC,iBAAiB,CAAC,CA0EtC;;;;oCA5ZY,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB;;;;kCAKzG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB;;;;2CAK/C,MAAM,GAAG,OAAO;;;;iCAKhB,oBAAoB,GAAG,eAAe,GAAG,oBAAoB,GAAG,OAAO,sBAAsB;;kBAsD5F,MAAM;gBACN,MAAM;gBACN,MAAM;oBACN,MAAM;YACN,MAAM;eACN,MAAM;iBACN,MAAM;;;;;;iBA4FN,MAAM;;;;eACN,MAAM;;;;sBACN,MAAM;;;;gCACN,MAAM;;;;gBACN,MAAM;;;;cACN,MAAM;;;;;;;;;YAgDN,SAAS;;;;cACT,MAAM;;;;;;;;;YAMN,WAAW;;;;cACX,MAAM;;;;;;;;;YAMN,SAAS;;;;YACT,iBAAiB;;;;;mCAKlB,qBAAqB,GAAG,sBAAsB,GAAG,qBAAqB;uCAxPK,gBAAgB"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.js"],"names":[],"mappings":"AAWA;;;GAGG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,4JAbG;IAA0B,QAAQ;IACR,KAAK;IACS,YAAY,EAA3C,qBAAqB;IACL,QAAQ,EAAxB,MAAM;IACU,WAAW,EAA3B,MAAM;IACU,KAAK,EAArB,MAAM;IACW,KAAK;IACQ,MAAM;IACnB,MAAM;IACN,aAAa;IACS,mBAAmB;CACnE,GAAS,MAAM,CA+BjB;AAED;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,4KAdG;IAAqC,SAAS,EAArC,kBAAkB;IACD,IAAI;IACJ,WAAW;IACX,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,KAAK;IACL,YAAY;IACZ,UAAU;IACV,QAAQ;IACR,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,iBAAiB,CAAC,CA8ErC;AAED;;;;;;;;;GASG;AAEH;;;;;;;;;;GAUG;AACH,4FAPG;IAAyB,QAAQ,EAAxB,MAAM;IACW,KAAK;IACL,QAAQ;IACR,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,sBAAsB,CAAC,CA+B1C;AAED;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,mHAVG;IAAwB,UAAU,EAA1B,MAAM;IACU,QAAQ,EAAxB,MAAM;IACW,QAAQ;IACR,eAAe;IACf,SAAS;IACD,cAAc;;;CAC/C,GAAU,OAAO,CAAC,oBAAoB,CAAC,CAuDzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,8IArCG;IAAwB,UAAU,EAA1B,MAAM;IACU,QAAQ,EAAxB,MAAM;IACW,QAAQ;IACR,eAAe;IACf,SAAS;IACT,SAAS;IACD,cAAc;;;IACU,MAAM,aAA9C,oBAAoB,KAAK,IAAI;CAC9C,GAAU,OAAO,CAAC,iBAAiB,CAAC,CA0EtC;;;;oCAtaY,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,GAAG,eAAe,GAAG,gBAAgB,GAAG,qBAAqB;;;;kCAKzG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,gBAAgB;;;;2CAK/C,MAAM,GAAG,OAAO;;;;iCAKhB,oBAAoB,GAAG,eAAe,GAAG,oBAAoB,GAAG,OAAO,sBAAsB;;kBAsD5F,MAAM;gBACN,MAAM;gBACN,MAAM;oBACN,MAAM;YACN,MAAM;eACN,MAAM;iBACN,MAAM;;;;;;iBAsGN,MAAM;;;;eACN,MAAM;;;;sBACN,MAAM;;;;gCACN,MAAM;;;;gBACN,MAAM;;;;cACN,MAAM;;;;;;;;;YAgDN,SAAS;;;;cACT,MAAM;;;;;;;;;YAMN,WAAW;;;;cACX,MAAM;;;;;;;;;YAMN,SAAS;;;;YACT,iBAAiB;;;;;mCAKlB,qBAAqB,GAAG,sBAAsB,GAAG,qBAAqB;uCAlQK,gBAAgB"}
@@ -158,14 +158,24 @@ export async function exchangeToken ({
158
158
  // For device_code grant, always parse JSON first so error details are available
159
159
  // 403 errors with authorization_pending/slow_down are expected during polling
160
160
  if (grantType === DEVICE_CODE_GRANT_TYPE) {
161
- /** @type {any} */
162
- const rawResponse = await response.body.json()
161
+ const textBody = await response.body.text()
162
+ let jsonBody = null
163
+
164
+ try {
165
+ jsonBody = JSON.parse(textBody)
166
+ } catch {
167
+ jsonBody = null
168
+ }
163
169
 
164
170
  if (response.statusCode > 299) {
165
- throw new YotoAPIError(response, rawResponse, { grantType })
171
+ throw new YotoAPIError(response, textBody, jsonBody, { grantType })
172
+ }
173
+
174
+ if (!jsonBody || typeof jsonBody !== 'object') {
175
+ throw new Error('OAuth token response did not contain valid JSON')
166
176
  }
167
177
 
168
- const responseBody = /** @type {YotoTokenResponse} */ (rawResponse)
178
+ const responseBody = /** @type {YotoTokenResponse} */ (jsonBody)
169
179
  return responseBody
170
180
  }
171
181
 
@@ -303,7 +313,7 @@ export async function pollForDeviceToken ({
303
313
  }
304
314
  } catch (err) {
305
315
  const error = /** @type {any} */ (err)
306
- const errorCode = error.body?.error
316
+ const errorCode = error.jsonBody?.error
307
317
 
308
318
  // Handle recoverable polling states
309
319
  if (errorCode === 'authorization_pending') {
@@ -19,7 +19,7 @@ test('exchangeToken - refresh flow', async (t) => {
19
19
  (err) => {
20
20
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
21
21
  assert.ok(err.statusCode, 'Error should have statusCode')
22
- assert.ok(err.body, 'Error should have body')
22
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
23
23
  return true
24
24
  }
25
25
  )
@@ -40,7 +40,7 @@ test('pollForDeviceToken', async (t) => {
40
40
  (err) => {
41
41
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
42
42
  // @ts-expect-error
43
- assert.strictEqual(err.body?.error, 'invalid_grant', 'Should be invalid_grant error')
43
+ assert.strictEqual(err.jsonBody?.error, 'invalid_grant', 'Should be invalid_grant error')
44
44
  assert.ok(err.statusCode, 'Error should have statusCode')
45
45
  return true
46
46
  }
@@ -62,7 +62,7 @@ test('pollForDeviceToken', async (t) => {
62
62
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
63
63
  // Will get invalid_grant for malformed codes
64
64
  // @ts-expect-error
65
- assert.ok(err.body?.error, 'Should have error code')
65
+ assert.ok(err.jsonBody?.error, 'Should have error code')
66
66
  return true
67
67
  }
68
68
  )
@@ -79,7 +79,7 @@ test('getUserMyoContent', async (t) => {
79
79
  (err) => {
80
80
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
81
81
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
82
- assert.ok(err.body, 'Error should have body')
82
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
83
83
  return true
84
84
  }
85
85
  )
@@ -43,7 +43,7 @@ test('getDevices', async (t) => {
43
43
  (err) => {
44
44
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
45
45
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
46
- assert.ok(err.body, 'Error should have body')
46
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
47
47
  return true
48
48
  }
49
49
  )
@@ -185,7 +185,7 @@ test('getDeviceStatus', async (t) => {
185
185
  (err) => {
186
186
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
187
187
  assert.equal(err.statusCode, 404, 'Should return 404 for invalid device ID')
188
- assert.ok(err.body, 'Error should have body')
188
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
189
189
  return true
190
190
  }
191
191
  )
@@ -205,7 +205,7 @@ test('getDeviceStatus', async (t) => {
205
205
  (err) => {
206
206
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
207
207
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
208
- assert.ok(err.body, 'Error should have body')
208
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
209
209
  return true
210
210
  }
211
211
  )
@@ -455,7 +455,7 @@ test('getDeviceConfig', async (t) => {
455
455
  (err) => {
456
456
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
457
457
  assert.ok(err.statusCode >= 400, 'Should return error status code for invalid device ID')
458
- assert.ok(err.body, 'Error should have body')
458
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
459
459
  return true
460
460
  }
461
461
  )
@@ -475,7 +475,7 @@ test('getDeviceConfig', async (t) => {
475
475
  (err) => {
476
476
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
477
477
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
478
- assert.ok(err.body, 'Error should have body')
478
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
479
479
  return true
480
480
  }
481
481
  )
@@ -53,7 +53,7 @@ test('getGroups', async (t) => {
53
53
  (err) => {
54
54
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
55
55
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
56
- assert.ok(err.body, 'Error should have body')
56
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
57
57
  return true
58
58
  }
59
59
  )
@@ -37,7 +37,7 @@ test('getFamilyImages', async (t) => {
37
37
  (err) => {
38
38
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
39
39
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
40
- assert.ok(err.body, 'Error should have body')
40
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
41
41
  return true
42
42
  }
43
43
  )
@@ -164,7 +164,7 @@ test('getAFamilyImage', async (t) => {
164
164
  (err) => {
165
165
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
166
166
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
167
- assert.ok(err.body, 'Error should have body')
167
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
168
168
  return true
169
169
  }
170
170
  )
@@ -46,12 +46,14 @@ export function handleBadResponse(response: Dispatcher.ResponseData, extra?: any
46
46
  export class YotoAPIError extends Error {
47
47
  /**
48
48
  * @param {Dispatcher.ResponseData} response A undici Response
49
- * @param {string | object} body response body
49
+ * @param {string} textBody response body as text
50
+ * @param {unknown | null} jsonBody parsed response body (or null if invalid JSON)
50
51
  * @param {any} [extra] any extra info to attach to the error
51
52
  */
52
- constructor(response: Dispatcher.ResponseData, body: string | object, extra?: any);
53
+ constructor(response: Dispatcher.ResponseData, textBody: string, jsonBody: unknown | null, extra?: any);
53
54
  /** @type { number } */ statusCode: number;
54
- /** @type {string | object } */ body: string | object;
55
+ /** @type {string} */ textBody: string;
56
+ /** @type {unknown | null} */ jsonBody: unknown | null;
55
57
  /** @type {any} */ extra: any;
56
58
  }
57
59
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["helpers.js"],"names":[],"mappings":"AAOA;;;GAGG;AAEH;;;;GAIG;AACH,yCAHG;IAAyB,SAAS;IACD,cAAc;;;CACjD;;;EAWA;AAED;;;;GAIG;AACH,sEAHG;IAAwB,WAAW,EAA1B,MAAM;IACS,SAAS;CACnC;;;;EAMA;AAED;;;;;;;GAOG;AACH,iDAJW,cAAc,mBACd,cAAc,GACZ,MAAM,CAoBlB;AAED;;;GAGG;AACH,4CAHY,uBAAuB,UACvB,GAAG,iBAWd;AAED;IAKE;;;;OAIG;IACH,sBAJY,uBAAuB,QACvB,MAAM,GAAG,MAAM,UACf,GAAG,EAUd;IAjBD,uBAAuB,CAAC,YAAZ,MAAM,CAAgB;IAClC,+BAA+B,CAAC,MAArB,MAAM,GAAG,MAAM,CAAU;IACpC,kBAAkB,CAAC,OAAR,GAAG,CAAU;CAgBzB;;;;6BA9FY,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;gCARxB,QAAQ;6BACX,QAAQ"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["helpers.js"],"names":[],"mappings":"AAOA;;;GAGG;AAEH;;;;GAIG;AACH,yCAHG;IAAyB,SAAS;IACD,cAAc;;;CACjD;;;EAWA;AAED;;;;GAIG;AACH,sEAHG;IAAwB,WAAW,EAA1B,MAAM;IACS,SAAS;CACnC;;;;EAMA;AAED;;;;;;;GAOG;AACH,iDAJW,cAAc,mBACd,cAAc,GACZ,MAAM,CAoBlB;AAED;;;GAGG;AACH,4CAHY,uBAAuB,UACvB,GAAG,iBAed;AAED;IAME;;;;;OAKG;IACH,sBALY,uBAAuB,YACvB,MAAM,YACN,OAAO,GAAG,IAAI,UACd,GAAG,EAWd;IApBD,uBAAuB,CAAC,YAAZ,MAAM,CAAgB;IAClC,qBAAqB,CAAC,UAAX,MAAM,CAAa;IAC9B,6BAA6B,CAAC,UAAnB,OAAO,GAAG,IAAI,CAAa;IACtC,kBAAkB,CAAC,OAAR,GAAG,CAAU;CAkBzB;;;;6BArGY,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;gCARxB,QAAQ;6BACX,QAAQ"}
@@ -73,32 +73,39 @@ export function mergeRequestOptions (baseOptions, requestOptions) {
73
73
  */
74
74
  export async function handleBadResponse (response, extra) {
75
75
  if (response.statusCode > 299) {
76
- const contentTypeHeaders = response.headers['Content-Type']
77
- const contentType = Array.isArray(contentTypeHeaders) ? contentTypeHeaders[0] : contentTypeHeaders
78
- const isJSON = contentType && contentType.match(/json/)
79
- /** @type { any } */
80
- const body = isJSON ? await response.body.json() : await response.body.text()
81
- throw new YotoAPIError(response, body, extra)
76
+ const textBody = await response.body.text()
77
+ let jsonBody = null
78
+
79
+ try {
80
+ jsonBody = JSON.parse(textBody)
81
+ } catch {
82
+ jsonBody = null
83
+ }
84
+
85
+ throw new YotoAPIError(response, textBody, jsonBody, extra)
82
86
  }
83
87
  }
84
88
 
85
89
  export class YotoAPIError extends Error {
86
90
  /** @type { number } */ statusCode
87
- /** @type {string | object } */ body
91
+ /** @type {string} */ textBody
92
+ /** @type {unknown | null} */ jsonBody
88
93
  /** @type {any} */ extra
89
94
 
90
95
  /**
91
96
  * @param {Dispatcher.ResponseData} response A undici Response
92
- * @param {string | object} body response body
97
+ * @param {string} textBody response body as text
98
+ * @param {unknown | null} jsonBody parsed response body (or null if invalid JSON)
93
99
  * @param {any} [extra] any extra info to attach to the error
94
100
  */
95
- constructor (response, body, extra) {
101
+ constructor (response, textBody, jsonBody, extra) {
96
102
  super('Unexpected response status code')
97
103
  this.name = this.constructor.name
98
104
  Error.captureStackTrace(this, this.constructor)
99
105
 
100
106
  this.statusCode = response.statusCode
101
- this.body = body
107
+ this.textBody = textBody
108
+ this.jsonBody = jsonBody
102
109
  this.extra = extra
103
110
  }
104
111
  }
@@ -50,7 +50,7 @@ test('getPublicIcons', async (t) => {
50
50
  (err) => {
51
51
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
52
52
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
53
- assert.ok(err.body, 'Error should have body')
53
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
54
54
  return true
55
55
  }
56
56
  )
@@ -96,7 +96,7 @@ test('getUserIcons', async (t) => {
96
96
  (err) => {
97
97
  assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
98
98
  assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
99
- assert.ok(err.body, 'Error should have body')
99
+ assert.ok(err.jsonBody, 'Error should have jsonBody')
100
100
  return true
101
101
  }
102
102
  )
@@ -1 +1 @@
1
- {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["token.js"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;GAWG;AAEH;IAwBE;;OAEG;IACH,oFAFW,oBAAoB,EAuB9B;IAED;;;;OAIG;IACH,kBAHa,OAAO,CAAC,MAAM,CAAC,CAkB3B;IAyGD;;OAEG;IACH,wBAGC;IAED;;OAEG;IACH,yBAQC;IAED;;;OAGG;IACH,wBAFa,OAAO,CAInB;IAED;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,mBAFa,MAAM,CAIlB;IAiED;;;OAGG;IACH,WAFa,OAAO,CASnB;IAED;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAFa,MAAM,CAKlB;IAED;;;;;OAKG;IACH,WAHa,OAAO,CAAC,mBAAmB,CAAC,CAUxC;;CACF;;;;;cAzXa,MAAM;;;;kBACN,MAAM;;;;iBACN,MAAM;;;;oBACN,qBAAqB;;;;oBACrB,MAAM;;;;;;;oCAQP,CAAC,mBAAmB,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;;;;;cAKjE,MAAM;;;;wBACN,MAAM;;;;yBACN,MAAM;;;;sBACN,MAAM;;;;qBACN,MAAM;;;;sBACN,MAAM;;;;mBACN,MAAM;;;;;uCAKP;IACZ,eAAmB,EAAE,EAAE,CAAC;IACxB,iBAAqB,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,eAAmB,EAAE,CAAC,KAAK,CAAC,CAAC;IAC7B,SAAa,EAAE,CAAC,KAAK,CAAC,CAAA;CACnB;6BA1CyB,aAAa"}
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["token.js"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;GASG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;GAWG;AAEH;IAwBE;;OAEG;IACH,oFAFW,oBAAoB,EAuB9B;IAED;;;;OAIG;IACH,kBAHa,OAAO,CAAC,MAAM,CAAC,CAkB3B;IA2GD;;OAEG;IACH,wBAGC;IAED;;OAEG;IACH,yBAQC;IAED;;;OAGG;IACH,wBAFa,OAAO,CAInB;IAED;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,mBAFa,MAAM,CAIlB;IAiED;;;OAGG;IACH,WAFa,OAAO,CASnB;IAED;;;OAGG;IACH,gBAFa,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAFa,MAAM,CAKlB;IAED;;;;;OAKG;IACH,WAHa,OAAO,CAAC,mBAAmB,CAAC,CAUxC;;CACF;;;;;cA3Xa,MAAM;;;;kBACN,MAAM;;;;iBACN,MAAM;;;;oBACN,qBAAqB;;;;oBACrB,MAAM;;;;;;;oCAQP,CAAC,mBAAmB,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;;;;;cAKjE,MAAM;;;;wBACN,MAAM;;;;yBACN,MAAM;;;;sBACN,MAAM;;;;qBACN,MAAM;;;;sBACN,MAAM;;;;mBACN,MAAM;;;;;uCAKP;IACZ,eAAmB,EAAE,EAAE,CAAC;IACxB,iBAAqB,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC7C,eAAmB,EAAE,CAAC,KAAK,CAAC,CAAC;IAC7B,SAAa,EAAE,CAAC,KAAK,CAAC,CAAA;CACnB;6BA1CyB,aAAa"}
package/lib/token.js CHANGED
@@ -214,12 +214,14 @@ export class RefreshableToken extends EventEmitter {
214
214
  'refresh_token_expired'
215
215
  ]
216
216
 
217
- if (error.body?.error && invalidRefreshErrors.includes(error.body.error)) {
217
+ const errorCode = error.jsonBody?.error
218
+ if (errorCode && invalidRefreshErrors.includes(errorCode)) {
218
219
  // Mark this token as permanently invalid
219
220
  this.#invalid = true
220
221
  this.#clearAutoRefreshTimeout()
221
222
  const statusCode = error.statusCode ? ` (${error.statusCode})` : ''
222
- const invalidError = new Error(`Refresh token is invalid or expired${statusCode}: ${error.body.error}${error.body.error_description ? ` - ${error.body.error_description}` : ''}`)
223
+ const errorDescription = error.jsonBody?.error_description
224
+ const invalidError = new Error(`Refresh token is invalid or expired${statusCode}: ${errorCode}${errorDescription ? ` - ${errorDescription}` : ''}`)
223
225
  this.emit('invalid', invalidError)
224
226
  throw invalidError
225
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"yoto-device.d.ts","sourceRoot":"","sources":["yoto-device.js"],"names":[],"mappings":"AAoTA;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAxBD;;;;GAIG;AACH;;;;;;;;;;EAUC;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,2CAA2C;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,uCAAuC;AAMvC;;;;;GAKG;AAEH;;;;;;;;GAQG;AAEH;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AAEH;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;GAOG;AAEH;;;;;;GAMG;AAEH;;;;;GAKG;AAEH;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH;IA8LE;;;;OAIG;IACH,0BAFU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEY;IAE5C;;;;;OAKG;IACH,6DAAsD;IA5LtD;;;;;OAKG;IACH,oBAJW,UAAU,UACV,UAAU,YACV,sBAAsB,EAiChC;IAgCD;;;OAGG;IACH,cAFa,UAAU,CAE2B;IAElD;;;OAGG;IACH,cAFc,gBAAgB,CAEa;IAE3C;;;OAGG;IACH,cAFa,qBAAqB,CAES;IAE3C;;;OAGG;IACH,iBAFa,mBAAmB,CAEiB;IAEjD;;;OAGG;IACH,gBAFa,iBAAiB,CAEiB;IAE/C;;;OAGG;IACH,mBAFa,OAAO,CAEiC;IAErD;;;OAGG;IACH,eAFa,OAAO,CAEyB;IAE7C;;;OAGG;IACH,qBAFa,OAAO,CAE+B;IAEnD;;;OAGG;IACH,oBAFa,OAAO,CAE6B;IAEjD;;;OAGG;IACH,oBAFa,sBAAsB,CAgClC;IAED;;;OAGG;IACH,kBAFa,wBAAwB,CAYpC;IAqBD;;;;OAIG;IACH,SAHa,OAAO,CAAC,IAAI,CAAC,CAmEzB;IAED;;;OAGG;IACH,QAFa,OAAO,CAAC,IAAI,CAAC,CA0BzB;IAED;;;OAGG;IACH,WAFa,OAAO,CAAC,IAAI,CAAC,CAMzB;IAED;;;OAGG;IACH,kBAFa,cAAc,GAAG,IAAI,CAIjC;IAMD;;;;OAIG;IACH,qBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,qBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,kBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAMzB;IAED;;;;;;OAMG;IACH,cALW,MAAM,KACN,MAAM,KACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,wBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,uBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,UAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;OAIG;IACH,mBAHW,oBAAoB,GAClB,OAAO,CAAC,IAAI,CAAC,CAmBzB;IAED;;;OAGG;IACH,YAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,aAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,cAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;;;;;;OASG;IACH,sBAPG;QAAyB,MAAM;QACI,IAAI;QACd,IAAI;QACJ,IAAI;QACJ,GAAG;KAC5B,GAAU,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,gBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,wBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,4BAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,wBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,oBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,uBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;OAGG;IACH,qBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;;;;OAOG;IACH,wBALG;QAAwB,GAAG,EAAnB,MAAM;QACU,OAAO,EAAvB,MAAM;QACW,QAAQ,EAAzB,OAAO;KACf,GAAU,OAAO,CAAC,IAAI,CAAC,CAIzB;IAMD;;;;OAIG;IACH;;;OAGG;IACH,iBAFa,OAAO,CAAC,qBAAqB,CAAC,CAqB1C;IAED;;;;OAIG;IACH,2BAHW,OAAO,CAAC,qBAAqB,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;OAIG;IACH,qBAHW,iBAAiB,GACf,OAAO,CAAC,yBAAyB,CAAC,CAO9C;;CAwrEF;;;;iCAr0GY,MAAM,GAAG,UAAU,GAAG,QAAQ;;;;sBAiC9B,SAAS,GAAG,OAAO,GAAG,KAAK;;;;0BAoB3B,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU;;;;;;;;YAMxC,MAAM;;;;iBACN,MAAM;;;;eACN,MAAM;;;;gBACN,MAAM;;;;aACN,MAAM;;;;oBACN,OAAO;;;;;;;;;;;;;;kBAkOP,MAAM,GAAG,IAAI;;;;4BACb,MAAM;;;;gBACN,OAAO;;;;cACP,OAAO;;;;YACP,MAAM;;;;eACN,MAAM;;;;wBACN,kBAAkB;;;;aAClB,OAAO;;;;iBACP,WAAW;;;;qBACX,MAAM;;;;kBACN,MAAM;;;;wBACN,MAAM;;;;yBACN,MAAM;;;;4BACN,OAAO;;;;+BACP,OAAO;;;;oBACP,MAAM;;;;wBACN,MAAM,GAAG,MAAM,GAAG,IAAI;;;;+BACtB,MAAM;;;;uBACN,MAAM,GAAG,IAAI;;;;gBACb,IAAI,GAAG,IAAI,GAAG,IAAI;;;;YAClB,MAAM;;;;eACN,MAAM;;;;YACN,MAAM;;;;;;;;;;;;YAWN,MAAM,EAAE;;;;mBACR,MAAM;;;;sBACN,OAAO;;;;yBACP,OAAO;;;;eACP,MAAM;;;;0BACN,MAAM,GAAG,IAAI;;;;8BACb,OAAO;;;;aACP,MAAM;;;;kBACN,MAAM;;;;kBACN,MAAM;;;;kBACN,OAAO;;;;0BACP,MAAM;;;;uBACN,MAAM;;;;6BACN,OAAO;;;;gBACP,EAAE,GAAG,EAAE;;;;YACP,MAAM;;;;cACN,MAAM;;;;oBACN,MAAM;;;;wBACN,MAAM;;;;4BACN,MAAM,GAAG,IAAI;;;;gCACb,OAAO;;;;yBACP,MAAM;;;;eACN,MAAM;;;;oBACN,MAAM;;;;oBACN,MAAM,GAAG,IAAI;;;;2BACb,OAAO;;;;oBACP,OAAO;;;;sBACP,OAAO;;;;qBACP,OAAO;;;;eACP,OAAO;;;;qBACP,OAAO;;;;qBACP,MAAM;;;;kBACN,MAAM;;;;cACN,MAAM;;;;iBACN,MAAM;;;;;;;;;YAON,MAAM,GAAG,IAAI;;;;eACb,MAAM,GAAG,IAAI;;;;cACb,MAAM,GAAG,IAAI;;;;uBACb,MAAM,GAAG,IAAI;;;;gBACb,MAAM,GAAG,IAAI;;;;gBACb,MAAM,GAAG,IAAI;;;;yBACb,MAAM,GAAG,IAAI;;;;YACb,MAAM,GAAG,IAAI;;;;oBACb,cAAc,GAAG,IAAI;;;;gBACrB,MAAM,GAAG,IAAI;;;;cACb,MAAM,GAAG,IAAI;;;;kBACb,MAAM,GAAG,IAAI;;;;gBACb,MAAM,GAAG,IAAI;;;;cACb,MAAM,GAAG,IAAI;;;;iBACb,MAAM,GAAG,IAAI;;;;eACb,OAAO,GAAG,IAAI;;;;sBACd,OAAO,GAAG,IAAI;;;;uBACd,MAAM,GAAG,IAAI;;;;eACb,MAAM;;;;;;SAWN,MAAM;WACN,MAAM;;;;;;SAMN,MAAM;WACN,MAAM;cACN,MAAM;gBACN,MAAM;kBACN,MAAM;;;;;;YAMN,MAAM;eACN,MAAM;cACN,MAAM,GAAG,IAAI;uBACb,MAAM,GAAG,IAAI;gBACb,MAAM,GAAG,IAAI;gBACb,MAAM,GAAG,IAAI;yBACb,MAAM,GAAG,IAAI;mBACb,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC;iBACrC,GAAG,CAAC,MAAM,EAAE,sBAAsB,CAAC;;;;;;YAMnC,MAAM;;;;;;;;;YAMN,UAAU;;;;YACV,qBAAqB;;;;eACrB,mBAAmB;;;;YACnB,gBAAgB;;;;cAChB,iBAAiB;;;;iBACjB,OAAO;;;;aACP,OAAO;;;;gBAElB;QAAqC,MAAM,EAAhC,MAAM,GAAG,IAAI;QACa,MAAM,EAAhC,MAAM,GAAG,IAAI;QACa,QAAQ,EAAlC,MAAM,GAAG,IAAI;QACa,MAAM,EAAhC,MAAM,GAAG,IAAI;KAC1B;;;;;;;;;0BAKa,OAAO;;;;2BACP,OAAO;;;;0BACP,OAAO;;;;eACP,OAAO;;;;;;;;;WAMP,MAAM;;;;UACN,MAAM;;;;eACN,OAAO;;;;;;;;;4BAMP,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,OAAO,CAAE;;;;yBAC5C,MAAM;;;;;;;;;YAMN,SAAS,GAAG,UAAU;;;;aACtB,MAAM,GAAG,IAAI;;;;;;;;;YAMb,UAAU,GAAG,SAAS,GAAG,aAAa;;;;qBACtC,MAAM,GAAG,IAAI;;;;wBACb,MAAM,GAAG,IAAI;;;;aACb,MAAM;;;;;yCAKP,gCAAgC;;;;sCAKhC,cAAc;;;;oCAKd,2BAA2B;;;;;;;;YAM1B,UAAU;;;;YACV,qBAAqB;;;;eACrB,mBAAmB;;;;YACnB,gBAAgB;;;;cAChB,iBAAiB;;;;iBACjB,OAAO;;;;aACP,OAAO;;;;;sCAKR;IACZ,SAAa,EAAE,CAAC,yBAAyB,CAAC,CAAC;IAC3C,SAAa,EAAE,EAAE,CAAC;IAClB,cAAkB,EAAE,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,gBAAgB,CAAC,CAAC,CAAC;IAC5E,cAAkB,EAAE,CAAC,qBAAqB,EAAE,GAAG,CAAC,MAAM,qBAAqB,CAAC,CAAC,CAAC;IAC9E,gBAAoB,EAAE,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,iBAAiB,CAAC,CAAC,CAAC;IACxE,QAAY,EAAE,CAAC,wBAAwB,CAAC,CAAC;IACzC,SAAa,EAAE,CAAC,yBAAyB,CAAC,CAAC;IAC3C,aAAiB,EAAE,CAAC,uBAAuB,CAAC,CAAC;IAC7C,gBAAoB,EAAE,CAAC,0BAA0B,CAAC,CAAC;IACnD,WAAe,EAAE,CAAC,qBAAqB,CAAC,CAAC;IACzC,eAAmB,EAAE,EAAE,CAAC;IACxB,aAAiB,EAAE,EAAE,CAAC;IACtB,SAAa,EAAE,EAAE,CAAC;IAClB,YAAgB,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC9C,YAAgB,EAAE,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC9C,kBAAsB,EAAE,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAC1D,cAAkB,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAClD,aAAiB,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,OAAW,EAAE,CAAC,KAAK,CAAC,CAAA;CACjB;6BAtiByB,QAAQ;gCAN+H,4BAA4B;yCAA5B,4BAA4B;oCACwC,kBAAkB;uCADtF,4BAA4B;+CAA5B,4BAA4B;gCAFjK,iBAAiB;oCAGwL,kBAAkB;qCACtN,mBAAmB;sDADiL,kBAAkB;oCALvN,MAAM;iDAK+L,kBAAkB;uCAAlB,kBAAkB;uCAAlB,kBAAkB;6CAAlB,kBAAkB;yCAAlB,kBAAkB"}
1
+ {"version":3,"file":"yoto-device.d.ts","sourceRoot":"","sources":["yoto-device.js"],"names":[],"mappings":"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"}
@@ -20,6 +20,7 @@
20
20
  import { EventEmitter } from 'events'
21
21
  import fastq from 'fastq'
22
22
  import QuickLRU from 'quick-lru'
23
+ import { YotoAPIError } from './api-endpoints/helpers.js'
23
24
  import { parseTemperature } from './helpers/temperature.js'
24
25
  import { detectPowerState } from './helpers/power-state.js'
25
26
  import { typedKeys } from './helpers/typed-keys.js'
@@ -1467,12 +1468,16 @@ export class YotoDeviceModel extends EventEmitter {
1467
1468
  #updateStatusFromFullStatus (fullStatus) {
1468
1469
  let statusChanged = false
1469
1470
  let configChanged = false
1471
+ let playbackChanged = false
1470
1472
  const wasOnline = this.#deviceOnline
1471
- const { status, config } = this.#state
1473
+ const { status, config, playback } = this.#state
1474
+ const previousPlaybackCardId = playback.cardId
1472
1475
  /** @type {Set<keyof YotoDeviceStatus>} */
1473
1476
  const changedFields = new Set()
1474
1477
  /** @type {Set<keyof YotoDeviceModelConfig>} */
1475
1478
  const configChangedFields = new Set()
1479
+ /** @type {Set<keyof YotoPlaybackState>} */
1480
+ const playbackChangedFields = new Set()
1476
1481
 
1477
1482
  /**
1478
1483
  * Handler function for each status field
@@ -1837,6 +1842,20 @@ export class YotoDeviceModel extends EventEmitter {
1837
1842
  handleField(key, fullStatus)
1838
1843
  }
1839
1844
 
1845
+ if (changedFields.has('activeCardId') && previousPlaybackCardId !== status.activeCardId) {
1846
+ playback.cardId = status.activeCardId
1847
+ playbackChangedFields.add('cardId')
1848
+ playbackChanged = true
1849
+
1850
+ if (clearCardCachePlaybackFields(playback, playbackChangedFields)) {
1851
+ playbackChanged = true
1852
+ }
1853
+
1854
+ if (this.#applyCardCacheToPlayback(playback.cardId, playback, playbackChangedFields)) {
1855
+ playbackChanged = true
1856
+ }
1857
+ }
1858
+
1840
1859
  // Only emit if something actually changed
1841
1860
  if (statusChanged) {
1842
1861
  // Update metadata
@@ -1856,6 +1875,12 @@ export class YotoDeviceModel extends EventEmitter {
1856
1875
  this.emit('configUpdate', this.config, configChangedFields)
1857
1876
  }
1858
1877
 
1878
+ if (playbackChanged) {
1879
+ playback.updatedAt = new Date().toISOString()
1880
+ this.#state.lastUpdate.playback = Date.now()
1881
+ this.emit('playbackUpdate', this.playback, playbackChangedFields)
1882
+ }
1883
+
1859
1884
  // Check if device went offline (transition from online to offline)
1860
1885
  if (wasOnline && !this.#deviceOnline) {
1861
1886
  this.emit('offline', { reason: 'http-status', source: 'http' })
@@ -1870,10 +1895,14 @@ export class YotoDeviceModel extends EventEmitter {
1870
1895
  */
1871
1896
  #updateStatusFromStatusResponse (statusResponse) {
1872
1897
  let statusChanged = false
1898
+ let playbackChanged = false
1873
1899
  const wasOnline = this.#deviceOnline
1874
- const { status } = this.#state
1900
+ const { status, playback } = this.#state
1901
+ const previousPlaybackCardId = playback.cardId
1875
1902
  /** @type {Set<keyof YotoDeviceStatus>} */
1876
1903
  const changedFields = new Set()
1904
+ /** @type {Set<keyof YotoPlaybackState>} */
1905
+ const playbackChangedFields = new Set()
1877
1906
 
1878
1907
  /**
1879
1908
  * Handler function for each status field
@@ -2114,6 +2143,20 @@ export class YotoDeviceModel extends EventEmitter {
2114
2143
  handleField(key, statusResponse)
2115
2144
  }
2116
2145
 
2146
+ if (changedFields.has('activeCardId') && previousPlaybackCardId !== status.activeCardId) {
2147
+ playback.cardId = status.activeCardId
2148
+ playbackChangedFields.add('cardId')
2149
+ playbackChanged = true
2150
+
2151
+ if (clearCardCachePlaybackFields(playback, playbackChangedFields)) {
2152
+ playbackChanged = true
2153
+ }
2154
+
2155
+ if (this.#applyCardCacheToPlayback(playback.cardId, playback, playbackChangedFields)) {
2156
+ playbackChanged = true
2157
+ }
2158
+ }
2159
+
2117
2160
  // Only emit if something actually changed
2118
2161
  if (statusChanged) {
2119
2162
  // Update metadata
@@ -2126,6 +2169,12 @@ export class YotoDeviceModel extends EventEmitter {
2126
2169
  this.emit('statusUpdate', this.status, 'http', changedFields)
2127
2170
  }
2128
2171
 
2172
+ if (playbackChanged) {
2173
+ playback.updatedAt = new Date().toISOString()
2174
+ this.#state.lastUpdate.playback = Date.now()
2175
+ this.emit('playbackUpdate', this.playback, playbackChangedFields)
2176
+ }
2177
+
2129
2178
  // Check if device went offline (transition from online to offline)
2130
2179
  if (wasOnline && !this.#deviceOnline) {
2131
2180
  this.emit('offline', { reason: 'http-status', source: 'http' })
@@ -3198,7 +3247,25 @@ export class YotoDeviceModel extends EventEmitter {
3198
3247
  }
3199
3248
 
3200
3249
  return entry
3201
- } catch {
3250
+ } catch (err) {
3251
+ if (err instanceof YotoAPIError) {
3252
+ const errorBody = /** @type {{ error?: { code?: string, message?: string } } | null} */ (err.jsonBody)
3253
+ const statusCode = err.statusCode
3254
+ const extraCardId = err.extra?.cardId
3255
+ const bodyCode = errorBody?.error?.code
3256
+ const bodyMessage = errorBody?.error?.message
3257
+
3258
+ const ignoreForbiddenCard = statusCode === 403 &&
3259
+ (!extraCardId || extraCardId === task.cardId) &&
3260
+ bodyCode === 'forbidden' &&
3261
+ bodyMessage?.includes('Access to card') &&
3262
+ bodyMessage?.includes('forbidden')
3263
+
3264
+ if (ignoreForbiddenCard) return null
3265
+ }
3266
+
3267
+ const error = err instanceof Error ? err : new Error(String(err))
3268
+ this.emit('error', error)
3202
3269
  return null
3203
3270
  } finally {
3204
3271
  this.#cardCacheQueueTasks.delete(task.cardId)
@@ -3408,6 +3475,9 @@ export class YotoDeviceModel extends EventEmitter {
3408
3475
  }
3409
3476
 
3410
3477
  if (previousCardId !== playback.cardId) {
3478
+ if (clearCardCachePlaybackFields(playback, playbackChangedFields)) {
3479
+ playbackChanged = true
3480
+ }
3411
3481
  if (this.#applyCardCacheToPlayback(playback.cardId, playback, playbackChangedFields)) {
3412
3482
  playbackChanged = true
3413
3483
  }
@@ -3517,6 +3587,77 @@ function buildCardCacheEntry (card) {
3517
3587
  }
3518
3588
  }
3519
3589
 
3590
+ /**
3591
+ * @param {YotoPlaybackState} playback
3592
+ * @param {Set<keyof YotoPlaybackState>} changedFields
3593
+ * @returns {boolean}
3594
+ */
3595
+ function clearCardCachePlaybackFields (playback, changedFields) {
3596
+ let changed = false
3597
+
3598
+ if (!changedFields.has('cardTitle') && playback.cardTitle !== null) {
3599
+ playback.cardTitle = null
3600
+ changedFields.add('cardTitle')
3601
+ changed = true
3602
+ }
3603
+
3604
+ if (!changedFields.has('cardSlug') && playback.cardSlug !== null) {
3605
+ playback.cardSlug = null
3606
+ changedFields.add('cardSlug')
3607
+ changed = true
3608
+ }
3609
+
3610
+ if (!changedFields.has('cardCoverImageUrl') && playback.cardCoverImageUrl !== null) {
3611
+ playback.cardCoverImageUrl = null
3612
+ changedFields.add('cardCoverImageUrl')
3613
+ changed = true
3614
+ }
3615
+
3616
+ if (!changedFields.has('cardAuthor') && playback.cardAuthor !== null) {
3617
+ playback.cardAuthor = null
3618
+ changedFields.add('cardAuthor')
3619
+ changed = true
3620
+ }
3621
+
3622
+ if (!changedFields.has('cardReadBy') && playback.cardReadBy !== null) {
3623
+ playback.cardReadBy = null
3624
+ changedFields.add('cardReadBy')
3625
+ changed = true
3626
+ }
3627
+
3628
+ if (!changedFields.has('cardDurationSeconds') && playback.cardDurationSeconds !== null) {
3629
+ playback.cardDurationSeconds = null
3630
+ changedFields.add('cardDurationSeconds')
3631
+ changed = true
3632
+ }
3633
+
3634
+ if (!changedFields.has('trackTitle') && playback.trackTitle !== null) {
3635
+ playback.trackTitle = null
3636
+ changedFields.add('trackTitle')
3637
+ changed = true
3638
+ }
3639
+
3640
+ if (!changedFields.has('trackLength') && playback.trackLength !== null) {
3641
+ playback.trackLength = null
3642
+ changedFields.add('trackLength')
3643
+ changed = true
3644
+ }
3645
+
3646
+ if (!changedFields.has('chapterKey') && playback.chapterKey !== null) {
3647
+ playback.chapterKey = null
3648
+ changedFields.add('chapterKey')
3649
+ changed = true
3650
+ }
3651
+
3652
+ if (!changedFields.has('chapterTitle') && playback.chapterTitle !== null) {
3653
+ playback.chapterTitle = null
3654
+ changedFields.add('chapterTitle')
3655
+ changed = true
3656
+ }
3657
+
3658
+ return changed
3659
+ }
3660
+
3520
3661
  /**
3521
3662
  * @param {YotoCardCacheEntry} entry
3522
3663
  * @param {YotoPlaybackState} playback
package/package.json CHANGED
@@ -1,7 +1,7 @@
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.10",
4
+ "version": "0.0.11",
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"