fair-twitch
Version:
Fair's Twitch API and Chat bot library
341 lines (340 loc) • 14.3 kB
JavaScript
;
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;