@xboxreplay/xboxlive-auth
Advanced tools
Comparing version 3.3.3 to 4.0.0-beta.0
@@ -1,17 +0,10 @@ | ||
export declare const USER_AGENT: string; | ||
declare const _default: { | ||
request: { | ||
baseHeaders: { | ||
'Accept-encoding': string; | ||
'Accept-Language': string; | ||
'User-Agent': string; | ||
}; | ||
defaultLanguage: string; | ||
defaultUserAgent: string; | ||
}; | ||
gitHubLinks: { | ||
github: { | ||
createIssue: string; | ||
seeUserTokenIssue: string; | ||
twoFactorAuthenticationError: string; | ||
unauthorizedActivityError: string; | ||
}; | ||
}; | ||
export default _default; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.USER_AGENT = [ | ||
'Mozilla/5.0 (XboxReplay; XboxLiveAuth/3.0)', | ||
'AppleWebKit/537.36 (KHTML, like Gecko)', | ||
'Chrome/71.0.3578.98 Safari/537.36' | ||
].join(' '); | ||
exports.default = { | ||
request: { | ||
baseHeaders: { | ||
'Accept-encoding': 'gzip', | ||
'Accept-Language': 'en-US', | ||
'User-Agent': exports.USER_AGENT | ||
} | ||
defaultLanguage: 'en-US', | ||
defaultUserAgent: `XboxReplay; XboxLiveAuth/4.0` | ||
}, | ||
gitHubLinks: { | ||
createIssue: 'https://bit.ly/xr-xbl-auth-create-issue', | ||
seeUserTokenIssue: 'https://bit.ly/xr-xbl-auth-user-token-issue', | ||
twoFactorAuthenticationError: 'https://bit.ly/xr-xbl-auth-err-2fa', | ||
unauthorizedActivityError: 'https://bit.ly/xr-xbl-auth-err-activity' | ||
github: { | ||
createIssue: 'https://github.com/XboxReplay/xboxlive-auth/issues' | ||
} | ||
}; |
@@ -0,16 +1,17 @@ | ||
export declare const defaultClientId = "000000004C12AE6F"; | ||
export declare const defaultScope = "service::user.auth.xboxlive.com::MBI_SSL"; | ||
export declare const defaultRedirectUri = "https://login.live.com/oauth20_desktop.srf"; | ||
export declare const defaultResponseType = "token"; | ||
declare const _default: { | ||
uris: { | ||
urls: { | ||
authorize: string; | ||
token: string; | ||
}; | ||
queries: { | ||
authorize: { | ||
client_id: string; | ||
redirect_uri: string; | ||
scope: string; | ||
display: string; | ||
response_type: string; | ||
locale: string; | ||
}; | ||
client: { | ||
id: string; | ||
redirectUri: string; | ||
scope: string; | ||
responseType: string; | ||
}; | ||
}; | ||
export default _default; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const CLIENT_IDS = { | ||
MY_XBOX_LIVE: '0000000048093EE3', | ||
XBOX_APP: '000000004C12AE6F' | ||
}; | ||
exports.defaultResponseType = exports.defaultRedirectUri = exports.defaultScope = exports.defaultClientId = void 0; | ||
exports.defaultClientId = '000000004C12AE6F'; | ||
exports.defaultScope = 'service::user.auth.xboxlive.com::MBI_SSL'; | ||
exports.defaultRedirectUri = 'https://login.live.com/oauth20_desktop.srf'; | ||
exports.defaultResponseType = 'token'; | ||
exports.default = { | ||
uris: { | ||
authorize: 'https://login.live.com/oauth20_authorize.srf' | ||
urls: { | ||
authorize: 'https://login.live.com/oauth20_authorize.srf', | ||
token: 'https://login.live.com/oauth20_token.srf' | ||
}, | ||
queries: { | ||
authorize: { | ||
client_id: CLIENT_IDS.XBOX_APP, | ||
redirect_uri: 'https://login.live.com/oauth20_desktop.srf', | ||
scope: 'service::user.auth.xboxlive.com::MBI_SSL', | ||
display: 'touch', | ||
response_type: 'token', | ||
locale: 'en' | ||
} | ||
client: { | ||
id: exports.defaultClientId, | ||
redirectUri: exports.defaultRedirectUri, | ||
scope: exports.defaultScope, | ||
responseType: exports.defaultResponseType | ||
} | ||
}; |
@@ -1,3 +0,5 @@ | ||
import { PreAuthResponse, Credentials, LogUserResponse } from '../..'; | ||
export declare const preAuth: () => Promise<PreAuthResponse>; | ||
export declare const logUser: (preAuthResponse: PreAuthResponse, credentials: Credentials) => Promise<LogUserResponse>; | ||
import { LiveAuthResponse, LiveCredentials, LivePreAuthResponse } from '../..'; | ||
export declare const getAuthorizeUrl: (clientId?: string, scope?: string, responseType?: 'token' | 'code', redirectUri?: string) => string; | ||
export declare const refreshAccessToken: (refreshToken: string, clientId?: string, scope?: string, clientSecret?: string | undefined) => Promise<LiveAuthResponse>; | ||
export declare const preAuth: () => Promise<LivePreAuthResponse>; | ||
export declare const authenticate: (credentials: LiveCredentials) => Promise<LiveAuthResponse>; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
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) { | ||
@@ -6,84 +34,121 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const errors_1 = __importDefault(require("@xboxreplay/errors")); | ||
exports.authenticate = exports.preAuth = exports.refreshAccessToken = exports.getAuthorizeUrl = void 0; | ||
const axios_1 = __importDefault(require("axios")); | ||
const config_1 = __importDefault(require("./config")); | ||
const config_2 = __importDefault(require("../../config")); | ||
const querystring_1 = require("querystring"); | ||
const _getMatchForIndex = (entry, regex, index = 0) => { | ||
const utils_1 = require("../../utils"); | ||
const XRError_1 = __importDefault(require("../../classes/XRError")); | ||
const config_1 = __importDefault(require("../../config")); | ||
const config_2 = __importStar(require("./config")); | ||
const getMatchForIndex = (entry, regex, index = 0) => { | ||
const match = entry.match(regex); | ||
return (match === null || match === void 0 ? void 0 : match[index]) || void 0; | ||
}; | ||
const _requiresIdentityConfirmation = (body) => { | ||
const m1 = _getMatchForIndex(body, /id=\"fmHF\" action=\"(.*?)\"/, 1); | ||
const m2 = _getMatchForIndex(m1 || '', /identity\/confirm/, 0); | ||
return m2 !== null; | ||
}; | ||
exports.preAuth = () => axios_1.default | ||
.get(`${config_1.default.uris.authorize}?${querystring_1.stringify(Object.assign({}, config_1.default.queries.authorize))}`, { headers: config_2.default.request.baseHeaders }) | ||
.then(response => { | ||
if (response.status !== 200) { | ||
throw errors_1.default.internal('Pre-authentication failed.'); | ||
const getAuthorizeUrl = (clientId = config_2.defaultClientId, scope = config_2.defaultScope, responseType = config_2.defaultResponseType, redirectUri = config_2.defaultRedirectUri) => `${config_2.default.urls.authorize}?${querystring_1.stringify({ | ||
client_id: clientId, | ||
redirectUri: redirectUri, | ||
response_type: responseType, | ||
scope: scope | ||
})}`; | ||
exports.getAuthorizeUrl = getAuthorizeUrl; | ||
const refreshAccessToken = (refreshToken, clientId = config_2.defaultClientId, scope = config_2.defaultScope, clientSecret = void 0) => __awaiter(void 0, void 0, void 0, function* () { | ||
const payload = { | ||
client_id: clientId, | ||
scope: scope || config_2.defaultScope, | ||
grant_type: 'refresh_token', | ||
refresh_token: refreshToken | ||
}; | ||
if (clientSecret !== void 0) { | ||
payload.client_secret = clientSecret; | ||
} | ||
const body = (response.data || ''); | ||
const cookie = (response.headers['set-cookie'] || []) | ||
.map((c) => c.split(';')[0]) | ||
.join('; '); | ||
const matches = { | ||
PPFT: _getMatchForIndex(body, /sFTTag:'.*value=\"(.*)\"\/>'/, 1), | ||
urlPost: _getMatchForIndex(body, /urlPost:'(.+?(?=\'))/, 1) | ||
}; | ||
if (matches.PPFT === void 0) | ||
throw errors_1.default.internal(`Could not match "PPFT" parameter, please fill an issue on ${config_2.default.gitHubLinks.createIssue}`); | ||
else if (matches.urlPost === void 0) | ||
throw errors_1.default.internal(`Could not match "urlPost" parameter, please fill an issue on ${config_2.default.gitHubLinks.createIssue}`); | ||
return { | ||
cookie, | ||
matches: { | ||
PPFT: matches.PPFT, | ||
urlPost: matches.urlPost | ||
const response = yield axios_1.default({ | ||
url: config_2.default.urls.token, | ||
method: 'POST', | ||
headers: utils_1.getBaseHeaders({ | ||
Accept: 'application/json', | ||
'Content-Type': 'application/x-www-form-urlencoded' | ||
}), | ||
data: querystring_1.stringify(payload) | ||
}) | ||
.then(res => res.data) | ||
.catch((err) => { | ||
var _a; | ||
throw new XRError_1.default(err.message, { | ||
statusCode: (_a = err.response) === null || _a === void 0 ? void 0 : _a.status | ||
}); | ||
}); | ||
return response; | ||
}); | ||
exports.refreshAccessToken = refreshAccessToken; | ||
const preAuth = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const response = yield axios_1.default({ | ||
url: exports.getAuthorizeUrl(), | ||
method: 'GET', | ||
headers: utils_1.getBaseHeaders() | ||
}) | ||
.then(res => { | ||
const body = (res.data || ''); | ||
const cookie = (res.headers['set-cookie'] || []) | ||
.map((c) => c.split(';')[0]) | ||
.join('; '); | ||
const matches = { | ||
PPFT: getMatchForIndex(body, /sFTTag:'.*value=\"(.*)\"\/>'/, 1), | ||
urlPost: getMatchForIndex(body, /urlPost:'(.+?(?=\'))/, 1) | ||
}; | ||
if (matches.PPFT !== void 0 && matches.urlPost !== void 0) { | ||
return { | ||
cookie, | ||
matches: matches | ||
}; | ||
} | ||
}; | ||
}) | ||
.catch(err => { | ||
if (!!err.__XboxReplay__) | ||
throw err; | ||
else | ||
throw errors_1.default.internal(err.message); | ||
throw XRError_1.default.internal(`Could not match required "preAuth" parameters, please fill an issue on ${config_1.default.github.createIssue}`); | ||
}) | ||
.catch(err => { | ||
if (err.__XboxReplay__ === true) | ||
throw err; | ||
throw XRError_1.default.internal(err.message); | ||
}); | ||
return response; | ||
}); | ||
exports.logUser = (preAuthResponse, credentials) => axios_1.default | ||
.post(preAuthResponse.matches.urlPost, querystring_1.stringify({ | ||
login: credentials.email, | ||
loginfmt: credentials.email, | ||
passwd: credentials.password, | ||
PPFT: preAuthResponse.matches.PPFT | ||
}), { | ||
maxRedirects: 1, | ||
headers: Object.assign(Object.assign({}, config_2.default.request.baseHeaders), { 'Content-Type': 'application/x-www-form-urlencoded', Cookie: preAuthResponse.cookie }) | ||
}) | ||
.then(response => { | ||
var _a; | ||
if (response.status !== 200) { | ||
throw errors_1.default.internal(`Authentication failed.`); | ||
} | ||
const body = (response.data || ''); | ||
const { responseUrl = '' } = ((_a = response.request) === null || _a === void 0 ? void 0 : _a.res) || {}; | ||
const hash = responseUrl.split('#')[1]; | ||
if (responseUrl === preAuthResponse.matches.urlPost) { | ||
throw errors_1.default.unauthorized('Invalid credentials.'); | ||
} | ||
if (hash === void 0) { | ||
const errorMessage = _requiresIdentityConfirmation(body) === true | ||
? `Activity confirmation required, please refer to ${config_2.default.gitHubLinks.unauthorizedActivityError}` | ||
: `Invalid credentials or 2FA enabled, please refer to ${config_2.default.gitHubLinks.twoFactorAuthenticationError}`; | ||
throw errors_1.default.unauthorized(errorMessage); | ||
} | ||
const parseHash = querystring_1.parse(hash); | ||
parseHash.expires_in = Number(parseHash.expires_in); | ||
return parseHash; | ||
}) | ||
.catch(err => { | ||
if (!!err.__XboxReplay__) | ||
throw err; | ||
else | ||
throw errors_1.default.internal(err.message); | ||
exports.preAuth = preAuth; | ||
const authenticate = (credentials) => __awaiter(void 0, void 0, void 0, function* () { | ||
const preAuthResponse = yield exports.preAuth(); | ||
const response = yield axios_1.default({ | ||
url: preAuthResponse.matches.urlPost, | ||
method: 'POST', | ||
headers: utils_1.getBaseHeaders({ | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
Cookie: preAuthResponse.cookie | ||
}), | ||
data: querystring_1.stringify({ | ||
login: credentials.email, | ||
loginfmt: credentials.email, | ||
passwd: credentials.password, | ||
PPFT: preAuthResponse.matches.PPFT | ||
}), | ||
maxRedirects: 0, | ||
validateStatus: status => status === 302 || status === 200 | ||
}) | ||
.then(res => { | ||
if (res.status === 200) { | ||
throw XRError_1.default.unauthorized(`Invalid credentials or 2FA enabled`); | ||
} | ||
const { location = '' } = res.headers || {}; | ||
const hash = location.split('#')[1]; | ||
const output = {}; | ||
for (const part of new URLSearchParams(hash)) { | ||
if (part[0] === 'expires_in') { | ||
output[part[0]] = Number(part[1]); | ||
} | ||
else | ||
output[part[0]] = part[1]; | ||
} | ||
return output; | ||
}) | ||
.catch(err => { | ||
if (err.__XboxReplay__ === true) | ||
throw err; | ||
throw XRError_1.default.internal(err.message); | ||
}); | ||
return response; | ||
}); | ||
exports.authenticate = authenticate; |
@@ -0,8 +1,9 @@ | ||
export declare const defaultXSTSRelyingParty = "http://xboxlive.com"; | ||
declare const _default: { | ||
uris: { | ||
urls: { | ||
userAuthenticate: string; | ||
deviceAuthenticate: string; | ||
XSTSAuthorize: string; | ||
}; | ||
defaultRelyingParty: string; | ||
}; | ||
export default _default; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultXSTSRelyingParty = void 0; | ||
exports.defaultXSTSRelyingParty = 'http://xboxlive.com'; | ||
exports.default = { | ||
uris: { | ||
urls: { | ||
userAuthenticate: 'https://user.auth.xboxlive.com/user/authenticate', | ||
deviceAuthenticate: 'https://device.auth.xboxlive.com/device/authenticate', | ||
XSTSAuthorize: 'https://xsts.auth.xboxlive.com/xsts/authorize' | ||
}, | ||
defaultRelyingParty: 'http://xboxlive.com' | ||
} | ||
}; |
@@ -1,4 +0,5 @@ | ||
import { ExchangeRpsTicketResponse, AuthenticateResponse, ExchangeResponse, TokensExchangeProperties, TokensExchangeOptions } from '../..'; | ||
export declare const exchangeRpsTicketForUserToken: (RpsTicket: string) => Promise<ExchangeRpsTicketResponse>; | ||
export declare const exchangeTokensForXSTSIdentity: <T extends ExchangeResponse>({ userToken, deviceToken, titleToken }: TokensExchangeProperties, { XSTSRelyingParty, optionalDisplayClaims, raw }?: TokensExchangeOptions) => Promise<T | AuthenticateResponse>; | ||
export declare const exchangeUserTokenForXSTSIdentity: <T extends ExchangeResponse>(userToken: string, options: TokensExchangeOptions) => Promise<AuthenticateResponse | T>; | ||
import { XBLDummyDeviceTokenResponse, XBLExchangeRpsTicketResponse, XBLExchangeTokensOptions, XBLExchangeTokensResponse, XBLTokens } from '../..'; | ||
export declare const exchangeRpsTicketForUserToken: (rpsTicket: string, preamble?: 'd' | 't', additionalHeaders?: Record<string, string>) => Promise<XBLExchangeRpsTicketResponse>; | ||
export declare const exchangeTokensForXSTSToken: (tokens: XBLTokens, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
export declare const exchangeTokenForXSTSToken: (userToken: string, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
export declare const EXPERIMENTAL_createDummyWin32DeviceToken: () => Promise<XBLDummyDeviceTokenResponse>; |
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
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) { | ||
@@ -6,77 +34,101 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const errors_1 = __importDefault(require("@xboxreplay/errors")); | ||
exports.EXPERIMENTAL_createDummyWin32DeviceToken = exports.exchangeTokenForXSTSToken = exports.exchangeTokensForXSTSToken = exports.exchangeRpsTicketForUserToken = void 0; | ||
const axios_1 = __importDefault(require("axios")); | ||
const config_1 = __importDefault(require("./config")); | ||
const config_1 = __importStar(require("./config")); | ||
const utils_1 = require("../../utils"); | ||
const XRError_1 = __importDefault(require("../../classes/XRError")); | ||
const config_2 = __importDefault(require("../../config")); | ||
exports.exchangeRpsTicketForUserToken = (RpsTicket) => axios_1.default | ||
.post(config_1.default.uris.userAuthenticate, { | ||
RelyingParty: 'http://auth.xboxlive.com', | ||
TokenType: 'JWT', | ||
Properties: { | ||
AuthMethod: 'RPS', | ||
SiteName: 'user.auth.xboxlive.com', | ||
RpsTicket | ||
const XBLContractVersion = 0; | ||
const XBLAdditionalHeaders = { | ||
Accept: 'application/json', | ||
'X-Xbl-Contract-Version': String(XBLContractVersion) | ||
}; | ||
const exchangeRpsTicketForUserToken = (rpsTicket, preamble = 't', additionalHeaders = {}) => __awaiter(void 0, void 0, void 0, function* () { | ||
const match = rpsTicket.match(/^([t|d]=)/g); | ||
if (match === null) { | ||
rpsTicket = `${preamble}=${rpsTicket}`; | ||
} | ||
}, { | ||
headers: Object.assign(Object.assign({}, config_2.default.request.baseHeaders), { Accept: 'application/json', 'x-xbl-contract-version': 0 }) | ||
}) | ||
.then(response => { | ||
if (response.status !== 200) | ||
throw errors_1.default.internal('Could not exchange specified "RpsTicket"'); | ||
else | ||
return response.data; | ||
}) | ||
.catch(err => { | ||
if (!!err.__XboxReplay__) | ||
throw err; | ||
else | ||
throw errors_1.default.internal(err.message); | ||
const response = yield axios_1.default({ | ||
url: config_1.default.urls.userAuthenticate, | ||
method: 'POST', | ||
headers: utils_1.getBaseHeaders(Object.assign(Object.assign({}, XBLAdditionalHeaders), additionalHeaders)), | ||
data: { | ||
RelyingParty: 'http://auth.xboxlive.com', | ||
TokenType: 'JWT', | ||
Properties: { | ||
AuthMethod: 'RPS', | ||
SiteName: 'user.auth.xboxlive.com', | ||
RpsTicket: rpsTicket | ||
} | ||
} | ||
}) | ||
.then(res => res.data) | ||
.catch(_ => { | ||
throw XRError_1.default.badRequest('Could not exchange specified "RpsTicket"'); | ||
}); | ||
return response; | ||
}); | ||
exports.exchangeTokensForXSTSIdentity = ({ userToken, deviceToken, titleToken }, { XSTSRelyingParty, optionalDisplayClaims, raw } = {}) => axios_1.default | ||
.post(config_1.default.uris.XSTSAuthorize, { | ||
RelyingParty: XSTSRelyingParty || config_1.default.defaultRelyingParty, | ||
TokenType: 'JWT', | ||
Properties: { | ||
UserTokens: [userToken], | ||
DeviceToken: deviceToken, | ||
TitleToken: titleToken, | ||
OptionalDisplayClaims: optionalDisplayClaims, | ||
SandboxId: 'RETAIL' | ||
} | ||
}, { | ||
headers: Object.assign(Object.assign({}, config_2.default.request.baseHeaders), { Accept: 'application/json', 'x-xbl-contract-version': 1 }) | ||
}) | ||
.then(response => { | ||
if (response.status !== 200) { | ||
throw errors_1.default.internal('Could not exchange specified "userToken"'); | ||
} | ||
if (raw !== true) { | ||
const body = response.data; | ||
return { | ||
userXUID: body.DisplayClaims.xui[0].xid || null, | ||
userHash: body.DisplayClaims.xui[0].uhs, | ||
XSTSToken: body.Token, | ||
expiresOn: body.NotAfter | ||
}; | ||
} | ||
else | ||
return response.data; | ||
}) | ||
.catch(err => { | ||
var _a; | ||
if (!!err.__XboxReplay__) | ||
throw err; | ||
else if (((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 400) { | ||
const isDefaultRelyingParty = XSTSRelyingParty === config_1.default.defaultRelyingParty; | ||
const computedErrorMessage = [ | ||
'Could not exchange "userToken", please', | ||
`refer to ${config_2.default.gitHubLinks.seeUserTokenIssue}` | ||
]; | ||
if (isDefaultRelyingParty === false) | ||
computedErrorMessage.splice(1, 0, 'double check the specified "XSTSRelyingParty" or'); | ||
throw errors_1.default.internal(computedErrorMessage.join(' ')); | ||
} | ||
else | ||
throw errors_1.default.internal(err.message); | ||
exports.exchangeRpsTicketForUserToken = exchangeRpsTicketForUserToken; | ||
const exchangeTokensForXSTSToken = (tokens, options = {}, additionalHeaders = {}) => __awaiter(void 0, void 0, void 0, function* () { | ||
const response = yield axios_1.default({ | ||
url: config_1.default.urls.XSTSAuthorize, | ||
method: 'POST', | ||
headers: utils_1.getBaseHeaders(Object.assign(Object.assign({}, XBLAdditionalHeaders), additionalHeaders)), | ||
data: { | ||
RelyingParty: options.XSTSRelyingParty || config_1.defaultXSTSRelyingParty, | ||
TokenType: 'JWT', | ||
Properties: { | ||
UserTokens: tokens.userTokens, | ||
DeviceToken: tokens.deviceToken, | ||
TitleToken: tokens.titleToken, | ||
OptionalDisplayClaims: options.optionalDisplayClaims, | ||
SandboxId: options.sandboxId || 'RETAIL' | ||
} | ||
} | ||
}) | ||
.then(res => res.data) | ||
.catch((err) => { | ||
var _a; | ||
throw new XRError_1.default('Could not exchange specified tokens, please double check used parameters or make sure to use the "EXPERIMENTAL_createDummyWin32DeviceToken" method to handle "Child" and "Teen" accounts', { statusCode: (_a = err.response) === null || _a === void 0 ? void 0 : _a.status }); | ||
}); | ||
return response; | ||
}); | ||
exports.exchangeUserTokenForXSTSIdentity = (userToken, options) => exports.exchangeTokensForXSTSIdentity({ userToken }, options); | ||
exports.exchangeTokensForXSTSToken = exchangeTokensForXSTSToken; | ||
const exchangeTokenForXSTSToken = (userToken, options = {}, additionalHeaders = {}) => exports.exchangeTokensForXSTSToken({ userTokens: [userToken] }, options, additionalHeaders); | ||
exports.exchangeTokenForXSTSToken = exchangeTokenForXSTSToken; | ||
const EXPERIMENTAL_createDummyWin32DeviceToken = () => __awaiter(void 0, void 0, void 0, function* () { | ||
const trustedParty = 'https://xboxreplay.net/'; | ||
const serviceDeviceId = '21354D2F-352F-472F-5842-5265706C6179'; | ||
const serviceSignature = 'AAAAAQHW6oD31MwA6MAjn67vdCppWCbrMovubA85xejO06rtOAEdZ0tMTZFnu7xbI6lZDNvIWfuMaIPJSUcpvxjKqSFJl1oaWzQGBw=='; | ||
const serviceProofKey = { | ||
crv: 'P-256', | ||
alg: 'ES256', | ||
use: 'sig', | ||
kty: 'EC', | ||
x: 'b8Zc6GPFeu41DqiWPJxRa_jqUTSiMA537emKVHt8UO8', | ||
y: 'CXAuTEHet72GjgSDfDg6psBrwE1waxBsNEIGrRZV_90' | ||
}; | ||
const response = yield axios_1.default({ | ||
url: config_1.default.urls.deviceAuthenticate, | ||
method: 'POST', | ||
headers: utils_1.getBaseHeaders(Object.assign(Object.assign({}, XBLAdditionalHeaders), { Signature: serviceSignature })), | ||
data: { | ||
RelyingParty: 'http://auth.xboxlive.com', | ||
TokenType: 'JWT', | ||
Properties: { | ||
AuthMethod: 'ProofOfPossession', | ||
TrustedParty: trustedParty, | ||
Id: `{${serviceDeviceId}}`, | ||
DeviceType: 'Win32', | ||
Version: '10.0.18363', | ||
ProofKey: serviceProofKey | ||
} | ||
} | ||
}) | ||
.then(res => res.data) | ||
.catch(_ => { | ||
throw XRError_1.default.badRequest(`Could not create a valid device token, please fill an issue on ${config_2.default.github.createIssue}`); | ||
}); | ||
return response; | ||
}); | ||
exports.EXPERIMENTAL_createDummyWin32DeviceToken = EXPERIMENTAL_createDummyWin32DeviceToken; |
@@ -1,55 +0,136 @@ | ||
import { preAuth, logUser } from './core/live'; | ||
import { exchangeRpsTicketForUserToken, exchangeUserTokenForXSTSIdentity, exchangeTokensForXSTSIdentity } from './core/xboxlive'; | ||
export declare type Credentials = { | ||
export declare type LiveCredentials = { | ||
email: string; | ||
password: string; | ||
}; | ||
export declare type TokensExchangeProperties = { | ||
userToken: string; | ||
deviceToken?: string; | ||
titleToken?: string; | ||
export declare type LiveAuthResponse = { | ||
token_type: 'bearer'; | ||
expires_in: number; | ||
access_token: string; | ||
refresh_token?: string; | ||
scope: string; | ||
user_id: string; | ||
}; | ||
export declare type TokensExchangeOptions = { | ||
XSTSRelyingParty?: string; | ||
optionalDisplayClaims?: string[]; | ||
raw?: boolean; | ||
export declare type LivePreAuthMatchedParameters = { | ||
PPFT: string; | ||
urlPost: string; | ||
}; | ||
export declare type AuthenticateOptions = { | ||
XSTSRelyingParty?: string; | ||
export declare type LivePreAuthResponse = { | ||
cookie: string; | ||
matches: LivePreAuthMatchedParameters; | ||
}; | ||
export declare type PreAuthResponse = { | ||
cookie: string; | ||
matches: { | ||
PPFT: string; | ||
urlPost: string; | ||
export declare type XBLExchangeRpsTicketResponse = { | ||
IssueInstant: string; | ||
NotAfter: string; | ||
Token: string; | ||
DisplayClaims: { | ||
xui: Array<{ | ||
uhs: string; | ||
}>; | ||
}; | ||
}; | ||
export declare type LogUserResponse = { | ||
access_token: string; | ||
token_type: string; | ||
expires_in: number; | ||
scope: string; | ||
refresh_token: string; | ||
user_id: string; | ||
export declare type XBLExchangeTokensOptions = { | ||
XSTSRelyingParty?: string; | ||
optionalDisplayClaims?: string[]; | ||
sandboxId?: string; | ||
}; | ||
export declare type ExchangeResponse = { | ||
export declare type XBLExchangeTokensResponse = { | ||
IssueInstant: string; | ||
NotAfter: string; | ||
Token: string; | ||
DisplayClaims: object; | ||
}; | ||
export declare type ExchangeRpsTicketResponse = ExchangeResponse & { | ||
DisplayClaims: { | ||
xui: [{ | ||
xui: Array<Record<string, string> & { | ||
xid?: string; | ||
uhs: string; | ||
}]; | ||
}>; | ||
}; | ||
}; | ||
export declare type AuthenticateResponse = { | ||
userXUID: string | null; | ||
userHash: string; | ||
XSTSToken: string; | ||
expiresOn: string; | ||
export declare type XBLDummyDeviceTokenResponse = { | ||
IssueInstant: string; | ||
NotAfter: string; | ||
Token: string; | ||
DisplayClaims: { | ||
xdi: { | ||
did: 'F50CDD8781FF4476'; | ||
dcs: string; | ||
}; | ||
}; | ||
}; | ||
export declare const authenticate: (email: string, password: string, options?: AuthenticateOptions) => Promise<AuthenticateResponse>; | ||
export { preAuth, logUser, exchangeRpsTicketForUserToken, exchangeUserTokenForXSTSIdentity, exchangeTokensForXSTSIdentity }; | ||
export declare type XBLTokens = { | ||
userTokens: string[]; | ||
deviceToken?: string; | ||
titleToken?: string; | ||
}; | ||
export declare type AuthenticateOptions = XBLExchangeTokensOptions & { | ||
deviceToken?: string; | ||
titleToken?: string; | ||
raw?: boolean; | ||
}; | ||
export declare const authenticate: (email: string, password: string, options?: AuthenticateOptions) => Promise<{ | ||
xuid: string | null; | ||
user_hash: string; | ||
xsts_token: string; | ||
display_claims: Record<string, string> & { | ||
xid?: string | undefined; | ||
uhs: string; | ||
}; | ||
expires_on: string; | ||
'login.live.com'?: undefined; | ||
'user.auth.xboxlive.com'?: undefined; | ||
'xsts.auth.xboxlive.com'?: undefined; | ||
} | { | ||
'login.live.com': LiveAuthResponse; | ||
'user.auth.xboxlive.com': XBLExchangeRpsTicketResponse; | ||
'xsts.auth.xboxlive.com': XBLExchangeTokensResponse; | ||
xuid?: undefined; | ||
user_hash?: undefined; | ||
xsts_token?: undefined; | ||
display_claims?: undefined; | ||
expires_on?: undefined; | ||
}>; | ||
export declare const live: { | ||
getAuthorizeUrl: (clientId?: string, scope?: string, responseType?: "code" | "token", redirectUri?: string) => string; | ||
authenticate: (credentials: LiveCredentials) => Promise<LiveAuthResponse>; | ||
refreshAccessToken: (refreshToken: string, clientId?: string, scope?: string, clientSecret?: string | undefined) => Promise<LiveAuthResponse>; | ||
}; | ||
export declare const xbl: { | ||
EXPERIMENTAL_createDummyWin32DeviceToken: () => Promise<XBLDummyDeviceTokenResponse>; | ||
exchangeRpsTicketForUserToken: (rpsTicket: string, preamble?: "d" | "t", additionalHeaders?: Record<string, string>) => Promise<XBLExchangeRpsTicketResponse>; | ||
exchangeTokensForXSTSToken: (tokens: XBLTokens, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
exchangeTokenForXSTSToken: (userToken: string, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
}; | ||
declare const _default: { | ||
xbl: { | ||
EXPERIMENTAL_createDummyWin32DeviceToken: () => Promise<XBLDummyDeviceTokenResponse>; | ||
exchangeRpsTicketForUserToken: (rpsTicket: string, preamble?: "d" | "t", additionalHeaders?: Record<string, string>) => Promise<XBLExchangeRpsTicketResponse>; | ||
exchangeTokensForXSTSToken: (tokens: XBLTokens, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
exchangeTokenForXSTSToken: (userToken: string, options?: XBLExchangeTokensOptions, additionalHeaders?: Record<string, string>) => Promise<XBLExchangeTokensResponse>; | ||
}; | ||
live: { | ||
getAuthorizeUrl: (clientId?: string, scope?: string, responseType?: "code" | "token", redirectUri?: string) => string; | ||
authenticate: (credentials: LiveCredentials) => Promise<LiveAuthResponse>; | ||
refreshAccessToken: (refreshToken: string, clientId?: string, scope?: string, clientSecret?: string | undefined) => Promise<LiveAuthResponse>; | ||
}; | ||
authenticate: (email: string, password: string, options?: AuthenticateOptions) => Promise<{ | ||
xuid: string | null; | ||
user_hash: string; | ||
xsts_token: string; | ||
display_claims: Record<string, string> & { | ||
xid?: string | undefined; | ||
uhs: string; | ||
}; | ||
expires_on: string; | ||
'login.live.com'?: undefined; | ||
'user.auth.xboxlive.com'?: undefined; | ||
'xsts.auth.xboxlive.com'?: undefined; | ||
} | { | ||
'login.live.com': LiveAuthResponse; | ||
'user.auth.xboxlive.com': XBLExchangeRpsTicketResponse; | ||
'xsts.auth.xboxlive.com': XBLExchangeTokensResponse; | ||
xuid?: undefined; | ||
user_hash?: undefined; | ||
xsts_token?: undefined; | ||
display_claims?: undefined; | ||
expires_on?: undefined; | ||
}>; | ||
}; | ||
export default _default; |
@@ -12,14 +12,46 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.xbl = exports.live = exports.authenticate = void 0; | ||
const live_1 = require("./core/live"); | ||
exports.preAuth = live_1.preAuth; | ||
exports.logUser = live_1.logUser; | ||
const xboxlive_1 = require("./core/xboxlive"); | ||
exports.exchangeRpsTicketForUserToken = xboxlive_1.exchangeRpsTicketForUserToken; | ||
exports.exchangeUserTokenForXSTSIdentity = xboxlive_1.exchangeUserTokenForXSTSIdentity; | ||
exports.exchangeTokensForXSTSIdentity = xboxlive_1.exchangeTokensForXSTSIdentity; | ||
exports.authenticate = (email, password, options = {}) => __awaiter(void 0, void 0, void 0, function* () { | ||
const preAuthResponse = yield live_1.preAuth(); | ||
const logUserResponse = yield live_1.logUser(preAuthResponse, { email, password }); | ||
const exchangeRpsTicketForUserTokenResponse = yield xboxlive_1.exchangeRpsTicketForUserToken(logUserResponse.access_token); | ||
return xboxlive_1.exchangeUserTokenForXSTSIdentity(exchangeRpsTicketForUserTokenResponse.Token, { XSTSRelyingParty: options.XSTSRelyingParty, raw: false }); | ||
const authenticate = (email, password, options = {}) => __awaiter(void 0, void 0, void 0, function* () { | ||
const credentials = { email, password }; | ||
const liveAuthResponse = yield live_1.authenticate(credentials); | ||
const { access_token: RpsTicket } = liveAuthResponse; | ||
const userTokenResponse = yield xboxlive_1.exchangeRpsTicketForUserToken(RpsTicket); | ||
const XSTSResponse = yield xboxlive_1.exchangeTokensForXSTSToken({ | ||
userTokens: [userTokenResponse.Token], | ||
deviceToken: options.deviceToken, | ||
titleToken: options.titleToken | ||
}, { | ||
XSTSRelyingParty: options.XSTSRelyingParty, | ||
optionalDisplayClaims: options.optionalDisplayClaims, | ||
sandboxId: options.sandboxId | ||
}); | ||
if (options.raw !== true) { | ||
return { | ||
xuid: XSTSResponse.DisplayClaims.xui[0].xid || null, | ||
user_hash: XSTSResponse.DisplayClaims.xui[0].uhs, | ||
xsts_token: XSTSResponse.Token, | ||
display_claims: XSTSResponse.DisplayClaims.xui[0], | ||
expires_on: XSTSResponse.NotAfter | ||
}; | ||
} | ||
return { | ||
'login.live.com': liveAuthResponse, | ||
'user.auth.xboxlive.com': userTokenResponse, | ||
'xsts.auth.xboxlive.com': XSTSResponse | ||
}; | ||
}); | ||
exports.authenticate = authenticate; | ||
exports.live = { | ||
getAuthorizeUrl: live_1.getAuthorizeUrl, | ||
authenticate: live_1.authenticate, | ||
refreshAccessToken: live_1.refreshAccessToken | ||
}; | ||
exports.xbl = { | ||
EXPERIMENTAL_createDummyWin32DeviceToken: xboxlive_1.EXPERIMENTAL_createDummyWin32DeviceToken, | ||
exchangeRpsTicketForUserToken: xboxlive_1.exchangeRpsTicketForUserToken, | ||
exchangeTokensForXSTSToken: xboxlive_1.exchangeTokensForXSTSToken, | ||
exchangeTokenForXSTSToken: xboxlive_1.exchangeTokenForXSTSToken | ||
}; | ||
exports.default = { xbl: exports.xbl, live: exports.live, authenticate: exports.authenticate }; |
{ | ||
"name": "@xboxreplay/xboxlive-auth", | ||
"description": "Simple Xbox Live authentication module.", | ||
"version": "3.3.3", | ||
"description": "A light Xbox Live authentication module", | ||
"version": "4.0.0-beta.0", | ||
"keywords": [ | ||
"xboxreplay", | ||
"xboxlive", | ||
"xbox", | ||
"live", | ||
"auth" | ||
@@ -22,16 +24,17 @@ ], | ||
"scripts": { | ||
"build": "npm run clean && ./node_modules/.bin/tsc && rm -rf ./dist/__tests__", | ||
"build": "npm run clean && ./node_modules/.bin/tsc", | ||
"clean": "rm -rf ./dist", | ||
"prepublishOnly": "npm run test && npm run build", | ||
"test": "ts-node ./__tests__/e2e.test.js" | ||
"touch-test-env": "touch .env.test", | ||
"test": "npm run touch-test-env && ts-node ./__tests__/e2e.test.js" | ||
}, | ||
"dependencies": { | ||
"@xboxreplay/errors": "^0.1.0", | ||
"axios": "^0.21.1" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^13.11.1", | ||
"ts-node": "^8.8.2", | ||
"typescript": "^3.8.3" | ||
"@types/node": "^14.14.20", | ||
"dotenv": "^8.2.0", | ||
"ts-node": "^9.1.1", | ||
"typescript": "^4.1.3" | ||
} | ||
} |
100
README.md
@@ -1,44 +0,62 @@ | ||
# Xbox Live - Auth | ||
# @xboxreplay/xboxlive-auth | ||
Simple Xbox Live authentication module. | ||
A light but advanced Xbox Live authentication module with [OAuth2.0](docs/02-Custom_Azure_Application.md) and [Electron](examples/electron-app) support. | ||
### Warning | ||
This module **MUST** be used server side only to prevent [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues and credentials leak (See issue: https://github.com/XboxReplay/xboxlive-auth/issues/8). | ||
## Installation | ||
### Installation | ||
```shell | ||
$ npm install @xboxreplay/xboxlive-auth | ||
$ npm install @xboxreplay/xboxlive-auth@4.0.0-beta.0 | ||
``` | ||
### Usage example | ||
## Usage Example | ||
```javascript | ||
import XboxLiveAuth from '@xboxreplay/xboxlive-auth'; | ||
import xboxliveAuth from '@xboxreplay/xboxlive-auth'; | ||
XboxLiveAuth.authenticate('xbl-account@your-domain.com', '*********') | ||
.then(console.info) | ||
.catch(console.error); | ||
xboxliveAuth | ||
.authenticate('name@domain.com', '*********') | ||
.then(console.info) | ||
.catch(console.error); | ||
``` | ||
**Sample response:** | ||
``` | ||
##### Sample Response | ||
```javascript | ||
{ | ||
"userXUID": "2584878536129841", // May be null | ||
"userHash": "3218841136841218711", | ||
"XSTSToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiWGJveFJlcGxheS5uZXQifQ.c2UraxPmZ4STYozrjFEW8SBqU0WjnIV0h-jjnfsKtrA", | ||
"expiresOn": "2020-04-13T05:43:32.6275675Z" | ||
"xuid": "2584878536129841", // May be null based on the specified "RelyingParty" | ||
"user_hash": "3218841136841218711", | ||
"xsts_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", | ||
"display_claims": { | ||
"gtg": "Zeny IC", | ||
"xid": "2584878536129841", | ||
"uhs": "3218841136841218711" | ||
"agg": "Adult", | ||
"usr" "234", | ||
"utr": "190", | ||
"prv": "185 186 187 188 191 192 ..." | ||
}, | ||
"expires_on": "2021-04-13T05:43:32.6275675Z" | ||
} | ||
``` | ||
### Parameters | ||
## Documentation | ||
- email {string} | ||
- password {string} | ||
- options {Object?} | ||
- XSTSRelyingParty {string?} - Default: http://xboxlive.com | ||
- [Basic authentication](docs/01-Authenticate.md) | ||
- [Use a custom Azure Application (OAuth2.0)](docs/02-Custom_Azure_Application.md) | ||
- [Experimental methods, such as "deviceToken" generation](docs/03-Experimental.md) | ||
- [What's a RelyingParty and how to use it](docs/04-RelyingParty.md) | ||
- [Available methods in this library](docs/05-Methods.md) | ||
- [Known issues and possible workarounds](docs/06-Known_Issues.md) | ||
- [How to deal with unauthorized "AgeGroup" authentication](docs/07-Detect_Unauthorized_AgeGroup.md) | ||
### How to interact with the Xbox Live API? | ||
## Available Examples | ||
The best way to interact with the API is to use our [XboxLive-API](https://github.com/XboxReplay/xboxlive-api) module. That said, a cURL example is available below. | ||
- [Electron App](examples/electron-app) | ||
**Sample call:** | ||
## How to interact with the Xbox Live API? | ||
The best way to interact with the API is to use our [@xboxreplay/xboxlive-auth](https://github.com/XboxReplay/xboxlive-api) module. That said, a cURL example is available below. | ||
##### Example | ||
```shell | ||
@@ -50,32 +68,12 @@ $ curl 'https://profile.xboxlive.com/users/gt(Major%20Nelson)/profile/settings?settings=Gamerscore' \ | ||
**Sample response:** | ||
## What about 2FA (Two-factor authentication)? | ||
```javascript | ||
{ | ||
"profileUsers": [ | ||
{ | ||
"id": "2584878536129841", | ||
"hostId": "2584878536129841", | ||
"settings": [ | ||
{ | ||
"id": "Gamerscore", | ||
"value": "911540" | ||
} | ||
], | ||
"isSponsoredUser": false | ||
} | ||
] | ||
} | ||
``` | ||
2FA is not supported by this module which may cause authentication issues. Please disable it for the used account or create a dummy one with Xbox LIVE capabalities. Of course, a Gold account is not required. Please note that Electron applications are not impacted by this issue. | ||
### What's a "XSTSRelyingParty"? | ||
## Known Issues | ||
The "XSTSRelyingParty" is a domain configured by Microsoft and / or its partners to create a XSTS token which is intended to be used for a targeted service. For instance, if you use `http://beam.pro/` you will be able to interact with the private **Mixer.com** API. A partial list can be found here: https://title.mgt.xboxlive.com/titles/default/endpoints?type=1. | ||
Please refer to the [dedicated documention](docs/06-Known_Issues.md). | ||
### What about 2FA (Two-factor authentication)? | ||
## Licence | ||
2FA is not supported by this module which may cause authentication issues. Please disable it for the used account or create a dummy one with Xbox LIVE capabalities. Of course, a Gold account is not required. | ||
### I'm unable to connect even with valid credentials and no 2FA | ||
Take a look at https://account.live.com/activity or try to sign in to https://account.xbox.com/Profile from your browser. Recent activities (from unknown location, as a production server) may be blocked. | ||
MIT |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
49390
1
27
691
4
2
79
- Removed@xboxreplay/errors@^0.1.0
- Removed@xboxreplay/errors@0.1.0(transitive)