valeriangalliat / spotify-buddylist

Fetch the friend activity Spotify feed.
122 stars 8 forks source link

Proper way to avoid Spotify errors #2

Open carcot opened 2 years ago

carcot commented 2 years ago

Every so often when my program calls buddyList.getFriendActivity(accessToken) I get the error

/.../node_modules/node-fetch/lib/index.js:273
                return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json'));
                                           ^
FetchError: invalid json response body at https://open.spotify.com/get_access_token?reason=transport&productType=web_player reason: Unexpected token u in JSON at position 0
    at /.../node_modules/node-fetch/lib/index.js:273:32
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async getFriendActivity (/.../fa.js:55:29)
    at async main (/.../fa.js:11:30) {
  type: 'invalid-json'
}

and I would like to avoid having this error crash my program. I have tried various workarounds, but I am not a JS expert and was hoping you might be able to help. I thought that following code should work, but it still doesn't work reliably.

async function getFriendActivity () {
    const spDcCookie = 'my cookie is here'

    const { accessToken } = await buddyList.getWebAccessToken(spDcCookie)

    var friendActivity = false
    while (!friendActivity) {
        try {
            friendActivity = await fulfillWithTimeLimit(1000, buddyList.getFriendActivity(accessToken), false)
        } catch {
            friendActivity = false
        }
    }

    return friendActivity
}

where fulfillWithTimeLimit(timeLimit, task, failureValue) is

//  https://medium.com/swlh/set-a-time-limit-on-async-actions-in-javascript-567d7ca018c2
async function fulfillWithTimeLimit(timeLimit, task, failureValue) {
    let timeout
    const timeoutPromise = new Promise((resolve, reject) => {
        timeout = setTimeout(() => {
            resolve(failureValue)
        }, timeLimit)
    })

    const response = await Promise.race([task, timeoutPromise])
    if (timeout) {   //  the code works without this but let's be safe and clean up the timeout
        clearTimeout(timeout)
    }

    return response
}

Do you have any suggestions on how I can make this work reliably?

valeriangalliat commented 2 years ago

Hey! It looks like the exception is happening at the getWebAccessToken for which you don't have a catch block, which is why the function fails :)

You should add a try/catch around this, and probably put another while loop around it if you want to keep retrying when it getWebAccessToken fails too.

I would also recommend that you add some kind of delay (maybe even exponential delay) between each retry to avoid hammering the Spotify API if something goes wrong. Cheers!

carcot commented 2 years ago

Oh, I see now. Thank you!

Does the accessToken need to be fetched again each time before the call to getFriendActivity(accessToken)? (I.e., does it change?)

Thanks for you help.

valeriangalliat commented 2 years ago

If you do const token = await buddyList.getWebAccessToken(spDcCookie) and you console.log(token) you'll see other fields returned by Spotify including the expiry of the token, I think it's something like an hour? So based on that you should be able to reuse the same token during that period =)

carcot commented 2 years ago

I implemented your previous suggestion and so far so good! I'll let you know after I've had more time to test it. Thanks for your help.

iCrowd commented 2 years ago

Hey since I had to set up my windows again i get this error too. Before it worked just fine, I never had any problems.

It looks like this for me but since i have no idea what im doing how can I avoid this error?

const buddyList = require('spotify-buddylist')

async function main () {
  const spDcCookie = 'my cookie here'
  const { accessToken } = await buddyList.getWebAccessToken(spDcCookie)
  const friendActivity = await buddyList.getFriendActivity(accessToken)

  console.log(new Date())
  console.log(JSON.stringify(friendActivity, null, 2))
}

setInterval(main, 60000);

clearInterval(main);
main()
  .catch(err => {
    console.error(err)
    process.exit(1)
  })