import * as config from "./config"; import * as queries from "./queries"; import { log, LogLevel } from "./logging" import fetch, { Response as FetchResponse } from "node-fetch"; import pg from "pg"; import db from "./db"; export interface TokenPair { access_token: string; refresh_token: string; } // Refresh the API token. Returns true on success and false on failure. async function refreshApiToken(tokens: TokenPair): Promise { log(LogLevel.DEBUG,`Call: twitch.refreshApiToken(${JSON.stringify(tokens,null,2)})`); return fetch("https://id.twitch.tv/oauth2/token", { method: 'POST', body: new URLSearchParams({ client_id: config.twitchClientId, client_secret: config.twitchSecret, grant_type: "refresh_token", refresh_token: tokens.refresh_token }) }).then(async (res: FetchResponse) => { if (res.status == 200) { log(LogLevel.INFO,"twitch.refreshApiToken: Refresh returned success."); var data = await (res.json() as Promise); log(LogLevel.DEBUG, "Returned data:") log(LogLevel.DEBUG, data) tokens.access_token = data.access_token; tokens.refresh_token = data.refresh_token; return true; } else { log(LogLevel.ERROR,"twitch.refreshApiToken: Refresh returned failure. Response object:"); log(LogLevel.ERROR,JSON.stringify(await res.json(),null,2)); return false; } }) } // Send an API request. On success, return the specified data. On failure, // attempt to refresh the API token and retry export async function apiRequest(tokens: TokenPair, endpoint: string): Promise { log(LogLevel.DEBUG,`Call: twitch.apiRequest(${JSON.stringify(tokens,null,2)},${endpoint})`); var headers = { "Authorization": "Bearer " + tokens.access_token, "Client-ID": config.twitchClientId }; return fetch("https://api.twitch.tv/helix" + endpoint, { headers: headers }) .then(async (res: FetchResponse) => { if (res.status == 200) { return res.json(); } else { log(LogLevel.WARNING,"twitch.apiRequest: Failed API request (pre-refresh):"); log(LogLevel.WARNING,"Request URL: https://api.twitch.tv/helix" + endpoint); log(LogLevel.WARNING,"Headers:"); log(LogLevel.WARNING,JSON.stringify(headers,null,2)); log(LogLevel.WARNING,"Response:"); log(LogLevel.WARNING,JSON.stringify(await res.json(),null,2)); log(LogLevel.WARNING,"Attempting refresh"); if (await refreshApiToken(tokens)) { headers = { "Authorization": "Bearer " + tokens.access_token, "Client-ID": config.twitchClientId }; return fetch("https://api.twitch.tv/helix" + endpoint, { headers: headers }) .then(async (res: FetchResponse) => { if (res.status == 200) { log(LogLevel.WARNING,"twitch.apiRequest: API call succeeded after token refresh.") return res.json(); } else { log(LogLevel.ERROR,"Failed API request:"); log(LogLevel.ERROR,"Request URL: https://api.twitch.tv/helix" + endpoint); log(LogLevel.ERROR,"Headers:"); log(LogLevel.ERROR,JSON.stringify(headers,null,2)); log(LogLevel.ERROR,"Response:"); log(LogLevel.ERROR,JSON.stringify(await res.json(),null,2)); return false; } }) } else { return false; } } }) } export async function streamerApiRequest(endpoint: string) { log(LogLevel.DEBUG,`Call: twitch.streamerApiRequest(${endpoint})`); var streamer = await db.query(queries.getStreamerIdToken).then((result: pg.QueryResult) => result.rows[0]); var tokenpair = streamer.tokenpair; var originaltoken = tokenpair.access_token; log(LogLevel.DEBUG,"Original token: " + tokenpair.access_token); var response = await apiRequest(tokenpair,endpoint); log(LogLevel.DEBUG,"New token: " + tokenpair.access_token); if (tokenpair.access_token != originaltoken) await db.query(Object.assign(queries.updateStreamer,{ values: [streamer.userid,tokenpair] })) return response; } // Check if API token is valid. Checks Twitch's OAuth validation endpoint. If // success, return true. If failure, return the result of attempting to refresh // the API token. export async function isApiTokenValid(tokens: TokenPair) { log(LogLevel.DEBUG,`Call: twitch.isApiTokenValid(${JSON.stringify(tokens,null,2)})`); return fetch("https://id.twitch.tv/oauth2/validate", { headers: {'Authorization': `OAuth ${tokens.access_token}`} }).then((res: FetchResponse) => { if (res.status == 200) { return true; } else { return refreshApiToken(tokens); } }) }