class TwitchAuth { constructor(clientId, scopes) { this.clientId = clientId; this.scopes = scopes; this.access_token = null; this.refresh_token = null; } async startAuthFlow() { // Step 1: Request Device Code const codeData = await this.requestDeviceCode(this.clientId); this.displayUserCode(codeData.verificationUri); return codeData; } async continueAuthFlow(codeData) { // After obtaining Device Code // Step 2: Poll for Token const tokenData = await this.pollForToken( codeData.deviceCode, this.clientId ); if (!tokenData) { location.reload(); } const tokenToSave = { client_id: this.clientId, scopes: this.scopes, access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, }; await this.saveToken(tokenToSave); } async requestDeviceCode(clientId) { const now = Date.now(); const url = "https://id.twitch.tv/oauth2/device"; const body = new URLSearchParams({ client_id: clientId, scopes: this.scopes, }); const res = await fetch(url, { method: "POST", body }); const json = await res.json(); if ("message" in json) { throw new Error(`[${res.status}] ${json.message}`); } return { deviceCode: json.device_code, userCode: json.user_code, verificationUri: json.verification_uri, expiresIn: json.expires_in * 1000, createdAt: now, interval: json.interval * 1000, }; } displayUserCode(verificationUri) { const container = document.getElementById("auth-container"); container.innerHTML = `

Copy Link to browser:
${verificationUri}

`; } async pollForToken(deviceCode, clientId) { const endpoint = "https://id.twitch.tv/oauth2/token"; const body = new FormData(); body.append("client_id", clientId); body.append( "grant_type", "urn:ietf:params:oauth:grant-type:device_code" ); body.append("device_code", deviceCode); const response = await fetch(endpoint, { method: "POST", body, }); const data = await response.json(); if (data.access_token) { return data; } else { console.error("Error:", data); } } async saveToken(tokenData) { localStorage.setItem("twitch_token", JSON.stringify(tokenData)); } getToken() { return JSON.parse(localStorage.getItem("twitch_token")) || null; } setupAuthModal() { // modal overlay const modalOverlay = document.createElement("div"); modalOverlay.id = "modal-overlay"; modalOverlay.style.fontFamily = "Arial, sans-serif"; modalOverlay.style.position = "fixed"; modalOverlay.style.top = "0"; modalOverlay.style.left = "0"; modalOverlay.style.width = "100%"; modalOverlay.style.height = "100%"; modalOverlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; modalOverlay.style.display = "flex"; modalOverlay.style.justifyContent = "center"; modalOverlay.style.alignItems = "center"; modalOverlay.style.zIndex = "1000"; modalOverlay.innerHTML = ` `; // Append modal overlay to body document.body.appendChild(modalOverlay); // Add event listener to the authorization button document.getElementById("authorized").addEventListener("click", () => { document.body.removeChild(modalOverlay); }); } displayNoClientId() { // modal overlay const modalOverlay = document.createElement("div"); modalOverlay.id = "modal-overlay"; modalOverlay.style.position = "fixed"; modalOverlay.style.top = "0"; modalOverlay.style.left = "0"; modalOverlay.style.width = "100%"; modalOverlay.style.height = "100%"; modalOverlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; modalOverlay.style.display = "flex"; modalOverlay.style.justifyContent = "center"; modalOverlay.style.alignItems = "center"; modalOverlay.style.zIndex = "1000"; modalOverlay.style.fontFamily = "Arial, sans-serif"; modalOverlay.innerHTML = ` `; // Append modal overlay to body document.body.appendChild(modalOverlay); // Add event listener to the refresh button document .getElementById("refresh-button") .addEventListener("click", () => { location.reload(); }); } async refreshToken(data) { const now = Date.now(); const url = "https://id.twitch.tv/oauth2/token"; const body = new URLSearchParams({ client_id: data.client_id, grant_type: "refresh_token", refresh_token: data.refresh_token, }); const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body, }); const json = await res.json(); if (!res.ok) { throw new Error( `[${res.status}] ${json.message || "Failed to refresh token"}` ); } return { ...json, createdAt: now, }; } async init() { return new Promise(async (resolve, reject) => { let token = await this.getToken(); if ( token && this.clientId && token.client_id == this.clientId && token.scopes == this.scopes ) { try { let token_refresh = await this.refreshToken(token); const tokenToSave = { client_id: this.clientId, scopes: this.scopes, access_token: token_refresh.access_token, refresh_token: token_refresh.refresh_token, }; this.access_token = token_refresh.access_token; this.refresh_token = token_refresh.refresh_token; await this.saveToken(tokenToSave); resolve(tokenToSave); } catch (error) { console.error("Error refreshing token:", error); reject(error); } } else if (this.clientId == null || this.clientId == "") { this.displayNoClientId(); reject(new Error("Client ID is missing")); } else { this.setupAuthModal(); var codeData = await this.startAuthFlow(); document .getElementById("authorized") .addEventListener("click", async (e) => { try { await this.continueAuthFlow(codeData); location.reload(); resolve(); } catch (error) { reject(error); } }); } }); } } export default TwitchAuth;