async-xbox-live-api
Version:
Async library to enable you to interact with the xbox live api
185 lines (184 loc) • 9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateTokenStillValid = exports.authorize = exports.authenticate = exports.fetchInitialAccessToken = exports.fetchPreAuthData = void 0;
const axios_1 = __importDefault(require("axios"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const querystring_1 = __importDefault(require("querystring"));
const url_1 = __importDefault(require("url"));
const cache_1 = require("../cache");
const util_1 = require("../util");
const HOST = 'login.live.com';
const ONE_HOUR = 3600;
const fetchPreAuthData = () => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
// cache solution to store the tokens in cache
const cacheUrlPost = yield cache_1.xlaCache.get(cache_1.CacheKeys.URL_POST);
const cachePpftRe = yield cache_1.xlaCache.get(cache_1.CacheKeys.PPFT_RE);
const cacheCookies = yield cache_1.xlaCache.get(cache_1.CacheKeys.COOKIES);
if (cacheUrlPost.isCached && cachePpftRe.isCached && cacheCookies.isCached) {
const urlPost = cacheUrlPost.value;
const ppftRe = cachePpftRe.value;
const cookies = cacheCookies.value;
return { url_post: urlPost, ppft_re: ppftRe, cookies };
}
const postValues = {
client_id: '0000000048093EE3',
redirect_uri: `https://${HOST}/oauth20_desktop.srf`,
response_type: 'token',
display: 'touch',
scope: 'service::user.auth.xboxlive.com::MBI_SSL',
locale: 'en'
};
const postValuesQueryParams = unescape(querystring_1.default.stringify(postValues));
const options = { headers: { Host: HOST } };
const xruReq = yield (0, node_fetch_1.default)(`https://${HOST}/oauth20_authorize.srf?${postValuesQueryParams}`, options);
const { headers } = xruReq;
const payload = yield xruReq.text();
const { urlPost, ppftRe } = (0, util_1.extractUrlPostAndPpftRe)(payload);
const cookies = (_b = (_a = headers.get('set-cookie')) === null || _a === void 0 ? void 0 : _a.split(', ')) !== null && _b !== void 0 ? _b : [];
const stringifiedCookies = (0, util_1.parseCookies)(cookies);
yield Promise.all([
cache_1.xlaCache.set(cache_1.CacheKeys.URL_POST, urlPost),
cache_1.xlaCache.set(cache_1.CacheKeys.PPFT_RE, ppftRe),
cache_1.xlaCache.set(cache_1.CacheKeys.COOKIES, stringifiedCookies)
]);
return { url_post: urlPost, ppft_re: ppftRe, cookies: stringifiedCookies };
});
exports.fetchPreAuthData = fetchPreAuthData;
const fetchInitialAccessToken = (options) => __awaiter(void 0, void 0, void 0, function* () {
var _c, _d;
const cacheAccessToken = yield cache_1.xlaCache.get(cache_1.CacheKeys.ACCESS_TOKEN);
const cacheCookies = yield cache_1.xlaCache.get(cache_1.CacheKeys.COOKIES);
if (cacheAccessToken.isCached && cacheCookies.isCached) {
const accessToken = cacheAccessToken.value;
const cookies = cacheCookies.value;
return { cookies, accessToken };
}
else {
const { ppft_re: ppftRe, url_post: urlPost, cookies } = options;
const postValues = (0, util_1.generatePostValues)(ppftRe);
// eslint-disable-next-line n/no-deprecated-api
const { path } = url_1.default.parse(urlPost);
if (!path) {
throw new Error('No path found on query params');
}
// TODO: figure out how to make this request work with axios
const accessTokenResponse = yield (0, node_fetch_1.default)(`https://${HOST}${path}`, {
method: 'POST',
body: querystring_1.default.stringify(postValues),
headers: {
Cookie: cookies,
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if ([302, 200].includes(accessTokenResponse.status)) {
const accessToken = (_d = (_c = accessTokenResponse.url) === null || _c === void 0 ? void 0 : _c.match(/access_token=(.+?)&/)) === null || _d === void 0 ? void 0 : _d[1];
if (!accessToken) {
throw new Error('Could not get find location header');
}
yield cache_1.xlaCache.set(cache_1.CacheKeys.ACCESS_TOKEN, accessToken);
return { cookies, accessToken };
}
else {
throw new Error('Could not get access token');
}
}
});
exports.fetchInitialAccessToken = fetchInitialAccessToken;
const authenticate = (options) => __awaiter(void 0, void 0, void 0, function* () {
const cacheToken = yield cache_1.xlaCache.get(cache_1.CacheKeys.TOKEN);
const cacheUhs = yield cache_1.xlaCache.get(cache_1.CacheKeys.UHS);
const cacheNotAfter = yield cache_1.xlaCache.get(cache_1.CacheKeys.NOT_AFTER);
const cacheCookies = yield cache_1.xlaCache.get(cache_1.CacheKeys.COOKIES);
if (cacheToken.isCached && cacheUhs.isCached && cacheNotAfter.isCached)
return {
token: cacheToken.value,
uhs: cacheUhs.value,
notAfter: cacheNotAfter.value,
cookies: cacheCookies.value
};
else {
const { cookies, accessToken } = options;
const payload = {
RelyingParty: 'http://auth.xboxlive.com',
TokenType: 'JWT',
Properties: {
AuthMethod: 'RPS',
SiteName: 'user.auth.xboxlive.com',
RpsTicket: accessToken
}
};
const requestOptions = {
headers: {
Cookie: cookies
}
};
const { data } = yield axios_1.default.post('https://user.auth.xboxlive.com/user/authenticate', payload, requestOptions);
const notAfter = data.NotAfter;
const token = data.Token;
const userHash = data.DisplayClaims.xui[0].uhs;
yield Promise.all([
cache_1.xlaCache.set('notAfter', notAfter),
cache_1.xlaCache.set('token', token),
cache_1.xlaCache.set('uhs', userHash)
]);
return { token, uhs: userHash, notAfter, cookies };
}
});
exports.authenticate = authenticate;
const authorize = (options) => __awaiter(void 0, void 0, void 0, function* () {
const cacheAuthorizationHeader = yield cache_1.xlaCache.get(cache_1.CacheKeys.AUTHORIZATION_HEADER);
const cacheCookies = yield cache_1.xlaCache.get(cache_1.CacheKeys.COOKIES);
if (cacheAuthorizationHeader.isCached && cacheCookies.isCached) {
return {
cookies: cacheCookies.value,
authorizationHeader: cacheAuthorizationHeader.value
};
}
else {
let { token, uhs, notAfter, cookies } = options;
const payload = {
RelyingParty: 'http://xboxlive.com',
TokenType: 'JWT',
Properties: { UserTokens: [token], SandboxId: 'RETAIL' }
};
const requestOptions = { headers: { Cookie: cookies } };
const { data } = yield axios_1.default.post('https://xsts.auth.xboxlive.com/xsts/authorize', payload, requestOptions);
uhs = data.DisplayClaims.xui[0].uhs;
notAfter = data.NotAfter;
token = data.Token;
const authorizationHeader = `XBL3.0 x=${uhs};${token}`;
yield Promise.all([
cache_1.xlaCache.set(cache_1.CacheKeys.NOT_AFTER, notAfter),
cache_1.xlaCache.set(cache_1.CacheKeys.AUTHORIZATION_HEADER, authorizationHeader)
]);
return { cookies, authorizationHeader };
}
});
exports.authorize = authorize;
const validateTokenStillValid = () => __awaiter(void 0, void 0, void 0, function* () {
const cacheNotAfter = yield cache_1.xlaCache.get(cache_1.CacheKeys.NOT_AFTER);
if (cacheNotAfter.isCached) {
const notAfterInSeconds = (0, util_1.convertToTimestamp)(cacheNotAfter.value);
const currentTimeInSeconds = Math.floor(Date.now() / 1000);
const isTokenAboutToExpire = notAfterInSeconds - currentTimeInSeconds < ONE_HOUR;
if (isTokenAboutToExpire) {
// restart the authentication proccess
console.log('Refreshing Xbox tokens');
yield cache_1.xlaCache.clear();
}
}
});
exports.validateTokenStillValid = validateTokenStillValid;