import { NodeCG } from "nodecg-types/types/server" ; import { Result , emptySuccess , success , error , ServiceBundle , Logger } from "nodecg-io-core" ; import SpotifyWebApi = require ( "spotify-web-api-node" ) ; import open = require ( "open" ) ; interface SpotifyServiceConfig { clientId : string ; clientSecret : string ; scopes : Array < string > ; refreshToken ? : string ; httpsRedirect ? : boolean ; } export type SpotifyServiceClient = SpotifyWebApi ; const callbackEndpoint = "/nodecg-io-spotify/spotifycallback" ; const defaultState = "defaultState" ; const refreshInterval = 1800000 ; module . exports = ( nodecg ) => { new SpotifyService ( nodecg , "spotify" , __dirname , "../spotify-schema.json" ) . register ( ) ; } ; class SpotifyService extends ServiceBundle < SpotifyServiceConfig , SpotifyServiceClient > { async validateConfig ( config ) { if ( config . scopes === undefined || config . scopes . length === 0 ) { return error ( "Scopes are empty. Please specify at least one scope!" ) ; } else { return emptySuccess ( ) ; } } async createClient ( config , logger ) { logger . info ( "Spotify service connecting..." ) ; const spotifyApi = new SpotifyWebApi ( { clientId : config . clientId , clientSecret : config . clientSecret , redirectUri : `${ config . httpsRedirect ? "https" : "http" }://${ this . nodecg . config . baseURL }${ callbackEndpoint }` } ) ; if ( config . refreshToken ) { try { spotifyApi . setRefreshToken ( config . refreshToken ) ; const refreshData = await spotifyApi . refreshAccessToken ( ) ; spotifyApi . setAccessToken ( refreshData . body [ "access_token" ] ) ; logger . info ( "Successfully authenticated using saved refresh token." ) ; } catch ( e ) { logger . warn ( `Couldn't re-use refresh token ("${ e }"). Creating a new one...` ) ; config . refreshToken = undefined ; return await this . createClient ( config , logger ) ; } } else { const promise = this . mountCallBackURL ( spotifyApi , logger ) ; const authorizeURL = spotifyApi . createAuthorizeURL ( config . scopes , defaultState ) ; await open ( authorizeURL ) ; await promise ; config . refreshToken = spotifyApi . getRefreshToken ( ) ; } logger . info ( "Successfully connected to Spotify." ) ; this . startTokenRefreshing ( spotifyApi , logger ) ; return success ( spotifyApi ) ; } private mountCallBackURL ( spotifyApi , logger ) { return new Promise ( ( resolve ) => { const router = this . nodecg . Router ( ) ; router . get ( callbackEndpoint , ( req , res ) => { const authCode = req . query . code ?. toString ( ) ?? "" ; spotifyApi ?. authorizationCodeGrant ( authCode ) . then ( ( data ) => { spotifyApi . setAccessToken ( data . body [ "access_token" ] ) ; spotifyApi . setRefreshToken ( data . body [ "refresh_token" ] ) ; resolve ( undefined ) ; } , ( err ) => logger . error ( "Spotify login error!" , err ) ) ; const callbackWebsite = "<html><head><script>window.close();</script></head><body>Spotify connection successful! You may close this window now.</body></html>" ; res . send ( callbackWebsite ) ; } ) ; this . nodecg . mount ( router ) ; } ) ; } private startTokenRefreshing ( spotifyApi , logger ) { const interval = setInterval ( ( ) => { if ( spotifyApi . getAccessToken ( ) === undefined ) { clearInterval ( interval ) ; return ; } spotifyApi . refreshAccessToken ( ) . then ( ( data ) => { logger . info ( "The Spotify access token has been refreshed." ) ; spotifyApi . setAccessToken ( data . body [ "access_token" ] ) ; } , ( error ) => { logger . warn ( "Could not spotify refresh access token" , error ) ; } ) ; } , refreshInterval ) ; } stopClient ( client ) { client . resetAccessToken ( ) ; } }