UNPKG

fair-twitch

Version:

Fair's Twitch API and Chat bot library

341 lines (340 loc) 14.3 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var request_1 = __importDefault(require("request")); var ExpandedEventEmitter_1 = __importDefault(require("./ExpandedEventEmitter")); var TwitchAPI = /** @class */ (function (_super) { __extends(TwitchAPI, _super); function TwitchAPI(options) { var _this = _super.call(this) || this; if (typeof options === 'string') { options = { clientID: options }; } _this.options = { clientID: null, clientSecret: null, redirectURL: null, refreshToken: null, accessToken: null, accessTokenExpireTS: null, autoRefreshToken: true, validateToken: true, apiURL: 'https://api.twitch.tv/', authURL: 'https://id.twitch.tv/' }; if (options) { for (var key in options) { if (_this.options.hasOwnProperty(key)) { _this.options[key] = options[key]; } } } if (_this.options.validateToken && (_this.options.refreshToken !== null || _this.options.accessToken !== null)) { _this.validateAccessToken(); } return _this; } TwitchAPI.prototype._getAuthHeaders = function (options, callback, recursive) { var _this = this; var _a = this.options, clientID = _a.clientID, clientSecret = _a.clientSecret, refreshToken = _a.refreshToken, accessToken = _a.accessToken, autoRefreshToken = _a.autoRefreshToken, accessTokenExpireTS = _a.accessTokenExpireTS; if (options.clientID !== undefined) clientID = options.clientID; if (options.accessToken !== undefined) accessToken = options.accessToken; var headers = {}; if (clientID !== null) { headers['Client-ID'] = clientID; } // We need to find out if we are using Twitch API v5 or new Twitch API // since it defines how the Authorization header should be set and accept header if (options.url.startsWith('/')) options.url = options.url.substring(1); var oldAPI = options.url.startsWith('kraken'); if (oldAPI) { // We expect it to be Twitch API v5 this.emit('debug', 'Using Twitch API v5 Authorization'); headers['Accept'] = 'application/vnd.twitchtv.v5+json'; } else { // We expect it to be new Twitch API this.emit('debug', 'Using new Twitch API Authorization'); } if (accessToken !== null) { // Check if access token is expired (5 seconds before) if (autoRefreshToken && accessTokenExpireTS !== null && accessTokenExpireTS - 5000 < Date.now()) { this.emit('debug', 'Access token is expired, getting new'); this.refreshAccessToken(function (err) { if (err) return callback(err); _this._getAuthHeaders(options, callback, true); }); return; } if (options.accessTokenPrefix !== undefined) { headers['Authorization'] = options.accessTokenPrefix + " " + accessToken; } else { if (oldAPI) { headers['Authorization'] = 'OAuth ' + accessToken; } else { headers['Authorization'] = 'Bearer ' + accessToken; } } } else if (autoRefreshToken && refreshToken !== null && options.accessToken === undefined) { // We don't have an accessToken, but we do have a refresh token. // And the accessToken is not custom for this request. // So we use the refresh token to generate a new access token. if (!recursive && // If it's a recursive call, don't try to refresh token again this.options.clientID !== null && clientSecret !== null // And we have nessecary data to refresh token ) { this.emit('debug', 'Getting new access token from refresh token'); this.refreshAccessToken(function (err) { if (err) return callback(err); _this._getAuthHeaders(options, callback, true); }); return; // We don't want to keep on going here } } callback(null, headers); }; TwitchAPI.prototype._jsonTwitchBody = function (res, body, callback) { if (!callback) return; var data; try { data = JSON.parse(body); } catch (e) { callback({ error: 'Invalid JSON', message: 'Body could not be parsed as JSON', body: body }, null, res, body); } if (Math.floor(res.statusCode / 100) !== 2) { callback(data, null, res, body); } else { callback(null, data, res, body); } }; /** * Will refresh the internal access token. * Requires clientID, clientSecret and refreshToken in constructor options. * @param callback Callback for when the access token has been refreshed */ TwitchAPI.prototype.refreshAccessToken = function (callback) { var _this = this; var _a = this.options, clientID = _a.clientID, clientSecret = _a.clientSecret, refreshToken = _a.refreshToken, authURL = _a.authURL; if (clientID === null) return callback(new Error('Missing clientID')); if (clientSecret === null) return callback(new Error('Missing clientSecret')); if (refreshToken === null) return callback(new Error('Missing refreshToken')); var url = authURL + "oauth2/token?grant_type=refresh_token&refresh_token=" + refreshToken + "&client_id=" + clientID + "&client_secret=" + clientSecret; request_1.default({ method: 'POST', url: url }, function (err, res, body) { if (err) { if (callback) callback(err); else _this.emit('error', { error: 'Error refreshing access token', message: err }); return; } _this._jsonTwitchBody(res, body, function (err, data) { if (err) { if (callback) callback(err); else _this.emit('error', { error: 'Error refreshing access token', message: err }); return; } if (data.access_token) { _this.options.accessToken = data.access_token; _this.options.accessTokenExpireTS = Date.now() + (data.expires_in * 1000); if (callback) callback(null); _this.emit('tokenrefresh', data); } else { err = { error: 'Invalid refresh token', message: 'Could not get new access token from given refresh token' }; if (callback) callback(err); else _this.emit('error', err); } }); }); }; TwitchAPI.prototype.request = function (options, callback, recursive) { var _this = this; this._getAuthHeaders(options, function (err, headers) { if (err) return callback(err); _this.emit('debug', 'Starting request', { options: options, headers: headers }); request_1.default({ method: options.method, baseUrl: options.baseURL || _this.options.apiURL, url: options.url, headers: headers }, function (err, res, body) { if (err) { if (callback) callback(err, null, res, body); return; } _this._jsonTwitchBody(res, body, function (err, data, res, body) { if (_this.options.autoRefreshToken && !recursive && _this.options.accessToken !== null && err && err.status === 401 && err.message === 'authentication failed') { _this.emit('debug', 'Unauthorized request. Clearing accessToken and trying again.'); _this.options.accessToken = null; _this.request(options, callback, true); return; } if (callback) callback(err, data, res, body); }); }); }); }; /** * Runs an oauth validate access token from Twitch * @param callback The callback for the Twitch data sent back */ TwitchAPI.prototype.validateAccessToken = function (callback) { var _this = this; this.request({ method: 'GET', url: 'oauth2/validate', baseURL: this.options.authURL, accessTokenPrefix: 'OAuth' }, function (err, data, res, body) { if (err) { if (callback) callback(err); else _this.emit('error', { error: 'Error validating access token', message: err }); return; } _this.tokenData = data; if (callback) callback(null, data); else _this.emit('tokenvalidate', data); }); }; TwitchAPI.prototype.get = function (urlOrOptions, callback) { var opts; opts = { method: 'GET' }; if (typeof urlOrOptions !== 'string') opts = Object.assign(opts, urlOrOptions); else opts.url = urlOrOptions; this.request(opts, callback); }; TwitchAPI.prototype.post = function (urlOrOptions, callback) { var opts; opts = { method: 'POST' }; if (typeof urlOrOptions !== 'string') opts = Object.assign(opts, urlOrOptions); else opts.url = urlOrOptions; this.request(opts, callback); }; TwitchAPI.prototype.delete = function (urlOrOptions, callback) { var opts; opts = { method: 'DELETE' }; if (typeof urlOrOptions !== 'string') opts = Object.assign(opts, urlOrOptions); else opts.url = urlOrOptions; this.request(opts, callback); }; TwitchAPI.prototype.put = function (urlOrOptions, callback) { var opts; opts = { method: 'PUT' }; if (typeof urlOrOptions !== 'string') opts = Object.assign(opts, urlOrOptions); else opts.url = urlOrOptions; this.request(opts, callback); }; /** * Get OAuth Authorization Code Flow url that clients need to login with. * Remember to add state query to the url. * Requires clientID and redirectURL in construction parameters * When the user has logged in, they will be redirected to: * https://<your registered redirect URI>/?code=<authorization code> * You can then use that code to get a refresh token with: * getRefreshToken(<authorization code>, callback..) * @param scopes List of scopes to be in authorization url */ TwitchAPI.prototype.getAuthenticationURL = function () { var scopes = []; for (var _i = 0; _i < arguments.length; _i++) { scopes[_i] = arguments[_i]; } var _a = this.options, clientID = _a.clientID, redirectURL = _a.redirectURL, authURL = _a.authURL; if (clientID === null) throw new Error('Missing clientID'); if (redirectURL === null) throw new Error('Missing redirectURL'); return authURL + "oauth2/authorize?client_id=" + clientID + "&redirect_uri=" + encodeURIComponent(redirectURL) + "&response_type=code&scope=" + encodeURIComponent(scopes.join(' ')); }; /** * Get a refresh token from an authorization process. * Use getAuthenticationURL() to get the url the user needs to login at. * @param authCode The authorization code gotten from redirect URL query * @param callback Callback for the information Twitch sends back */ TwitchAPI.prototype.getRefreshToken = function (authCode, callback) { var _a = this.options, clientID = _a.clientID, clientSecret = _a.clientSecret, redirectURL = _a.redirectURL, authURL = _a.authURL; this.request({ method: 'POST', url: "oauth2/token?client_id=" + clientID + "&client_secret=" + clientSecret + "&code=" + authCode + "&grant_type=authorization_code&redirect_uri=" + encodeURIComponent(redirectURL), baseURL: authURL }, function (err, data, res, body) { if (err) return callback(err); callback(null, data, res, body); }); }; return TwitchAPI; }(ExpandedEventEmitter_1.default)); module.exports = TwitchAPI;