UNPKG

async-xbox-live-api

Version:

Async library to enable you to interact with the xbox live api

185 lines (184 loc) 9 kB
"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;