Quick update. There will soon be a mechanism to implement OAuth flows within a setupâs configuration UI. Hereâs how it will work once released:
First, use a custom config value. Within node.json
for example use:
{
"title": "Spotify integration",
"name": "spotify",
"type": "custom",
"page": "spotify.html",
"ui_width": 3,
"default": {}
}
Then add the referenced spotify.html
to your package. A minimal example implementation is give below. The result neatly integrate a button into the rest of the configuration interface:
The code that powers the embedded iframe might look as follows:
<html>
<body>
<button id='btn' class='btn btn-default'>Connect to Spotify</button>
<div id='bottom'></div>
<script src='hosted.js'></script>
<script>
function nonce() {
const array = new Uint32Array(32)
window.crypto.getRandomValues(array)
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('')
}
function sha256(plain) {
const encoder = new TextEncoder()
const data = encoder.encode(plain)
return window.crypto.subtle.digest('SHA-256', data)
}
function base64url_encode(str) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
async function pkce_verifier_to_challenge(v) {
return base64url_encode(await sha256(v))
}
let btn = document.getElementById('btn')
function update_btn() {
if (ib.config.refresh_token) {
btn.innerText = "Connected"
} else {
btn.innerText = "Connect to Spotify"
}
}
ib.ready.then(function() {
ib.setDefaultStyle()
update_btn()
btn.addEventListener('click', async () => {
if (ib.config.refresh_token) {
ib.setConfig({})
update_btn()
return
}
const redirect_uri = 'https://info-beamer.com/oauth/callback'
const client_id = '<YOUR CLIENT ID>'
const code_verifier = nonce()
const code_challenge = await pkce_verifier_to_challenge(code_verifier)
const url = "https://accounts.spotify.com/authorize"
+ '?response_type=code'
+ `&client_id=${encodeURIComponent(client_id)}`
+ '&state={STATE}'
+ `&redirect_uri=${encodeURIComponent(redirect_uri)}`
+ '&code_challenge_method=S256'
+ `&code_challenge=${encodeURIComponent(code_challenge)}`
const redir = (new URL(await ib.oauth_redirect(url))).searchParams
const body = new URLSearchParams()
body.append('client_id', client_id)
body.append('grant_type', 'authorization_code')
body.append('code', redir.get('code'))
body.append('redirect_uri', redirect_uri)
body.append('code_verifier', code_verifier)
const token_res = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
body: body,
})
const res = await token_res.json()
ib.setConfig({refresh_token: res.refresh_token})
update_btn()
})
})
</script>
</body>
</html>
The new feature added here is the ib.oauth_redirect
function. When used as await ib.oauth_redirect(url)
like in the code above, it replaces any reference to {STATE}
in the provided url
value with an info-beamer generated oauth state value and opens the url in a new window. When redirected back to https://info-beamer.com/oauth/callback
the complete url is passed back to the caller of ib.oauth_redirect
and can be handled. The above code uses the provided code
value to grab the refresh_token
and stores that in the setupâs configuration.
In the generated config.json
, the result will be
{
....<rest of config.json>....
"spotify": {
"refresh_token": "<the retrieved refresh token>"
}
}