Automating token refresh

Apologies if this has been answered elsewhere, i tried searching but couldn’t find it.

I’ve written a script (with the help of AI) to display current track info for an overlay.

The issue i’m having is it works perfectly for 5 minutes and then console explodes with error about token not being valid and for the life of me i’m unable to get the script to automate this process using the grant flow (not implicit)

Here is an example of the script that deals with that side of things and hopefully someone infinitely smarter than i can see where i’m going wrong…

async function refreshAccessToken(url, params, isNightbot = false) {
    if (isNightbot && isNightbotRefreshing) {
        console.log("Nightbot refresh already in progress, skipping.");
        return false;
    }

    if (isNightbot) isNightbotRefreshing = true;

    try {
        console.log(`Attempting to refresh ${isNightbot ? 'Nightbot' : 'Twitch'} token...`);
        if (isNightbot) {
            console.log("Nightbot refreshToken:", nightbotConfig.refreshToken); // Added logging
        }
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams(params)
        });

        if (!response.ok) {
            const errorData = await response.text();
            console.error(`Token refresh failed with status: ${response.status}`, errorData);
            return false;
        }

        const data = await response.json();

        if (data.access_token) {
            if (isNightbot) {
                nightbotConfig.accessToken = data.access_token;
                if (data.refresh_token) {
                    nightbotConfig.refreshToken = data.refresh_token;
                }
                console.log('Nightbot token refreshed successfully:', data.access_token.substring(0, 10) + '...');
                console.log("Nightbot accessToken updated:", nightbotConfig.accessToken.substring(0, 10) + "...");
            } else {
                twitchConfig.accessToken = data.access_token;
                if (data.refresh_token) {
                    twitchConfig.refreshToken = data.refresh_token;
                }
                console.log('Twitch token refreshed successfully:', data.access_token.substring(0, 10) + '...');
            }
            return true;
        } else {
            console.error(`${isNightbot ? 'Nightbot' : 'Twitch'} token refresh failed:`, data);
            return false;
        }
    } catch (error) {
        console.error(`Error refreshing ${isNightbot ? 'Nightbot' : 'Twitch'} access token:`, error);
        return false;
    } finally {
        if (isNightbot) isNightbotRefreshing = false;
    }
}

async function validateTwitchToken() {
    try {
        const response = await fetch("https://id.twitch.tv/oauth2/validate", {
            headers: { "Authorization": `OAuth ${twitchConfig.accessToken}` }
        });
        if (!response.ok) {
            console.warn("Twitch token expired. Refreshing...");
            await refreshTwitchAccessToken();
        } else {
            console.log("Twitch token is still valid.");
        }
    } catch (error) {
        console.error("Error validating Twitch token:", error);
        await refreshTwitchAccessToken();
    }
}

async function validateNightbotToken() {
    try{
        const response = await fetch("https://api.nightbot.tv/1/me", {
            headers: {"Authorization": `Bearer ${nightbotConfig.accessToken}`}
        });
        if (!response.ok){
            console.warn("Nightbot token Expired. Refreshing...");
            await refreshNightbotAccessToken();
        } else{
            console.log("Nightbot token is still valid");
        }
    } catch(error) {
        console.error("error validating Nightbot token:", error);
        await refreshNightbotAccessToken();
    }
}

async function refreshNightbotAccessToken() {
    return await refreshAccessToken('https://api.nightbot.tv/oauth2/token', {
        grant_type: 'refresh_token',
        refresh_token: nightbotConfig.refreshToken,
        client_id: nightbotConfig.clientId,
        client_secret: nightbotConfig.clientSecret
    }, true);
}

async function refreshTwitchAccessToken() {
    return await refreshAccessToken('https://id.twitch.tv/oauth2/token', {
        client_id: twitchConfig.clientId,
        client_secret: twitchConfig.clientSecret,
        grant_type: 'refresh_token',
        refresh_token: twitchConfig.refreshToken
    });
}

async function loadTrack() {
    try {
        let response = await fetch('https://api.nightbot.tv/1/song_requests/queue', {
            headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
        });

        if (response.status === 401) {
            console.log("Nightbot token expired, refreshing...");
            const refreshed = await refreshNightbotAccessToken();
            if (refreshed) {
                response = await fetch('https://api.nightbot.tv/1/song_requests/queue', {
                    headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
                });
            } else {
                console.error("Nightbot token refresh failed, cannot load track.");
                document.getElementById('currentTrack').textContent = 'Track info not available.';
                return;
            }
        }

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

Hiya, just quickly skimming through the code, not really sure where to look, just a bunch of functions, not where they are called. I see twitch tokens, nightbot tokens. Are you using both? Also dont see the authorization part, the first access token should last for 30 days, are those access/refresh tokens properly stored in your nightbotConfig?

Thank you for replying. Sorry, i was trying to omit anything that would reveal my tokens etc but was way too cautious and missed out important script. Here’s the full code with placeholder in places of tokens/secrets etc for security and for context the purpose of the script is to display the current track info and post track info in chat on song change.

   const nightbotConfig = {
    accessToken: 'Placeholder',
    refreshToken: 'Placeholder',
    clientId: 'Placeholder',
    clientSecret: 'Placeholder'
};

const twitchConfig = {
    clientId: "Placeholder",
    clientSecret: "Placeholder",
    accessToken: "Placeholder",
    refreshToken: "Placeholder"
};

let comfyConnected = false;
let lastTrack = "";
let isNightbotRefreshing = false;

async function refreshAccessToken(url, params, isNightbot = false) {
    if (isNightbot && isNightbotRefreshing) {
        console.log("Nightbot refresh already in progress, skipping.");
        return false;
    }

    if (isNightbot) isNightbotRefreshing = true;

    try {
        console.log(`Attempting to refresh ${isNightbot ? 'Nightbot' : 'Twitch'} token...`);
        if (isNightbot) {
            console.log("Nightbot refreshToken:", nightbotConfig.refreshToken); // Added logging
        }
        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams(params)
        });

        if (!response.ok) {
            const errorData = await response.text();
            console.error(`Token refresh failed with status: ${response.status}`, errorData);
            return false;
        }

        const data = await response.json();

        if (data.access_token) {
            if (isNightbot) {
                nightbotConfig.accessToken = data.access_token;
                if (data.refresh_token) {
                    nightbotConfig.refreshToken = data.refresh_token;
                }
                console.log('Nightbot token refreshed successfully:', data.access_token.substring(0, 10) + '...');
                console.log("Nightbot accessToken updated:", nightbotConfig.accessToken.substring(0, 10) + "...");
            } else {
                twitchConfig.accessToken = data.access_token;
                if (data.refresh_token) {
                    twitchConfig.refreshToken = data.refresh_token;
                }
                console.log('Twitch token refreshed successfully:', data.access_token.substring(0, 10) + '...');
            }
            return true;
        } else {
            console.error(`${isNightbot ? 'Nightbot' : 'Twitch'} token refresh failed:`, data);
            return false;
        }
    } catch (error) {
        console.error(`Error refreshing ${isNightbot ? 'Nightbot' : 'Twitch'} access token:`, error);
        return false;
    } finally {
        if (isNightbot) isNightbotRefreshing = false;
    }
}

async function validateTwitchToken() {
    try {
        const response = await fetch("://id.twitch.tv/oauth2/validate", {
            headers: { "Authorization": `OAuth ${twitchConfig.accessToken}` }
        });
        if (!response.ok) {
            console.warn("Twitch token expired. Refreshing...");
            await refreshTwitchAccessToken();
        } else {
            console.log("Twitch token is still valid.");
        }
    } catch (error) {
        console.error("Error validating Twitch token:", error);
        await refreshTwitchAccessToken();
    }
}

async function validateNightbotToken() {
    try{
        const response = await fetch("://api.nightbot.tv/1/me", {
            headers: {"Authorization": `Bearer ${nightbotConfig.accessToken}`}
        });
        if (!response.ok){
            console.warn("Nightbot token Expired. Refreshing...");
            await refreshNightbotAccessToken();
        } else{
            console.log("Nightbot token is still valid");
        }
    } catch(error) {
        console.error("error validating Nightbot token:", error);
        await refreshNightbotAccessToken();
    }
}

async function refreshNightbotAccessToken() {
    return await refreshAccessToken('://api.nightbot.tv/oauth2/token', {
        grant_type: 'refresh_token',
        refresh_token: nightbotConfig.refreshToken,
        client_id: nightbotConfig.clientId,
        client_secret: nightbotConfig.clientSecret
    }, true);
}

async function refreshTwitchAccessToken() {
    return await refreshAccessToken('://id.twitch.tv/oauth2/token', {
        client_id: twitchConfig.clientId,
        client_secret: twitchConfig.clientSecret,
        grant_type: 'refresh_token',
        refresh_token: twitchConfig.refreshToken
    });
}

async function loadTrack() {
    try {
        let response = await fetch('://api.nightbot.tv/1/song_requests/queue', {
            headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
        });

        if (response.status === 401) {
            console.log("Nightbot token expired, refreshing...");
            const refreshed = await refreshNightbotAccessToken();
            if (refreshed) {
                response = await fetch('://api.nightbot.tv/1/song_requests/queue', {
                    headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
                });
            } else {
                console.error("Nightbot token refresh failed, cannot load track.");
                document.getElementById('currentTrack').textContent = 'Track info not available.';
                return;
            }
        }

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();

        if (data._currentSong && data._currentSong.track) {
            const songInfo = data._currentSong.track;
            const userInfo = data._currentSong.user;
            const artist = songInfo.artist.replace(' - Topic', '');
            const newTrack = `${songInfo.title} by ${artist}`;

            if (newTrack !== lastTrack) {
                lastTrack = newTrack;
                const trackText = `Track: ${songInfo.title}<br>Artist: ${artist}<br>Requested By: ${userInfo.displayName}`;
                document.getElementById('currentTrack').innerHTML = trackText;

                if (comfyConnected) {
                    ComfyJS.Say(`Now Playing: ${songInfo.title} by ${artist}, Requested By: ${userInfo.displayName}`);
                }
            }
        } else {
            document.getElementById('currentTrack').textContent = 'No song playing.';
        }
    } catch (error) {
        console.error('Error loading track:', error);
        document.getElementById('currentTrack').textContent = 'Track info not available.';
    }
}

    (async function init() {
        try {
            await refreshNightbotAccessToken();
            await refreshTwitchAccessToken();
            await validateTwitchToken();
            await validateNightbotToken();

        loadTrack();

            setInterval(loadTrack, 5000);

            setInterval(async () => {
                console.log("Refreshing Twitch token on schedule...");
                await refreshTwitchAccessToken();
                await validateTwitchToken();
            }, 30 * 60 * 1000);

            setInterval(async () => {
              console.log("Refreshing Nightbot token on schedule...");
              await refreshNightbotAccessToken();
              await validateNightbotToken();
            }, 30 * 60 * 1000);

            console.log("Initializing ComfyJS...");
            ComfyJS.Init("RearSilver", twitchConfig.accessToken);
            ComfyJS.onConnected = () => {
                comfyConnected = true;
                console.log("ComfyJS connected successfully.");
            };
        } catch (error) {
            console.error("Initialization failed:", error);
        }
        })();

Console Log:
current-track.html:116

   POST api.nightbot.tv/oauth2/token 400 (Bad Request)

refreshAccessToken @ current-track.html:116
refreshNightbotAccessToken @ current-track.html:193
init @ current-track.html:262
(anonymous) @ current-track.html:292
current-track.html:124 Token refresh failed with status: 400 {“error”:“invalid_grant”,“error_description”:“Invalid grant: refresh token is invalid”}

Hiya, ok so I dont see start authorization part: Nightbot API Reference you manually set the tokens in the nightbotConfig tokens at the start? Once you run the script and it refreshes the token, it will update the nightbotConfig for that run, but when you rerun the script it will still have the old values. I would suggest storing this data in a separate file or database.

Are you using the Twitch API to Post the chat message with your own account? You can skip the whole the Twitch API integration part, and use Nightbot to send a chat message: Nightbot API Reference

About all the intervals to validate tokens, I personally would remove those to. Since you already check in loadTrack function if the token is valid, and if not you refresh it.
If the refreshing fails you might want to stop the script entirely, otherwise it will just keep failing to refresh every 5 secs.

Underneath your code, which is non working probably, but removed some unnecessary parts and added some comments.

// Move to a txt file / database
const nightbotConfig = {
    accessToken: 'Placeholder',
    refreshToken: 'Placeholder',
    clientId: 'Placeholder',
    clientSecret: 'Placeholder'
};

let lastTrack = "";
let isNightbotRefreshing = false;

// Can remove all the if isNightbot logic if you use Nightbot to post the message
async function refreshAccessToken(url, params) {
    if (isNightbotRefreshing) {
        console.log("Nightbot refresh already in progress, skipping.");
        return false;
    }

    isNightbotRefreshing = true;

    try {
        console.log(`Attempting to refresh Nightbot`);
        console.log("Nightbot refreshToken:", nightbotConfig.refreshToken); // Added logging

        const response = await fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams(params)
        });

        if (!response.ok) {
            const errorData = await response.text();
            console.error(`Token refresh failed with status: ${response.status}`, errorData);
            return false;
        }

        const data = await response.json();

        if (data.access_token) {
                // Write to a file or database here to store the new tokens
                nightbotConfig.accessToken = data.access_token;
                if (data.refresh_token) {
                    nightbotConfig.refreshToken = data.refresh_token;
                }
                console.log('Nightbot token refreshed successfully:', data.access_token.substring(0, 10) + '...');
                console.log("Nightbot accessToken updated:", nightbotConfig.accessToken.substring(0, 10) + "...");
            return true;
        } else {
            console.error(`Nightbot token refresh failed:`, data);
            return false;
        }
    } catch (error) {
        console.error(`Error refreshing Nightbot access token:`, error);
        return false;
    } finally {
        isNightbotRefreshing = false;
    }
}

async function refreshNightbotAccessToken() {
    return await refreshAccessToken('://api.nightbot.tv/oauth2/token', {
        grant_type: 'refresh_token',
        refresh_token: nightbotConfig.refreshToken,
        client_id: nightbotConfig.clientId,
        client_secret: nightbotConfig.clientSecret
    }, true);
}

async function loadTrack() {
    try {
        let response = await fetch('://api.nightbot.tv/1/song_requests/queue', {
            headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
        });

        if (response.status === 401) {
            console.log("Nightbot token expired, refreshing...");
            const refreshed = await refreshNightbotAccessToken();
            if (refreshed) {
                response = await fetch('://api.nightbot.tv/1/song_requests/queue', {
                    headers: { 'Authorization': `Bearer ${nightbotConfig.accessToken}` }
                });
            } else {
                // Exit the script, or it will keep failing every 5 secs
                console.error("Nightbot token refresh failed, cannot load track.");
                document.getElementById('currentTrack').textContent = 'Track info not available.';
                return;
            }
        }

        if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();

        if (data._currentSong && data._currentSong.track) {
            const songInfo = data._currentSong.track;
            const userInfo = data._currentSong.user;
            const artist = songInfo.artist.replace(' - Topic', '');
            const newTrack = `${songInfo.title} by ${artist}`;

            if (newTrack !== lastTrack) {
                lastTrack = newTrack;
                const trackText = `Track: ${songInfo.title}<br>Artist: ${artist}<br>Requested By: ${userInfo.displayName}`;
                document.getElementById('currentTrack').innerHTML = trackText;

                // Use Nightbot API Send Channel Message here
                if (comfyConnected) {
                    ComfyJS.Say(`Now Playing: ${songInfo.title} by ${artist}, Requested By: ${userInfo.displayName}`);
                }
            }
        } else {
            document.getElementById('currentTrack').textContent = 'No song playing.';
        }
    } catch (error) {
        console.error('Error loading track:', error);
        document.getElementById('currentTrack').textContent = 'Track info not available.';
    }
}

    (async function init() {
        try {
            loadTrack();
            setInterval(loadTrack, 5000);
        } catch (error) {
            console.error("Initialization failed:", error);
        }
        })();

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.