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 = `
Authorization Required
Step 1:
Copy Link to browser:
Step 2:
Click below AFTER authorizing
`;
// 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 = `
Missing Client ID
Step 2:
Enter information for the app.
Redirect URL: http://localhost
Category: Chat Bot
Client Type: Public
Step 3:
Obtain Client ID from the registered application
Step 4:
Enter Client ID in index.js file
`;
// 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;