@xmcl/user
Version:
Minecraft user related functions, including Yggdrasil authenticator, player skin fetcher, and Mojang security API
813 lines (805 loc) • 27.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// offline.ts
import { v4 } from "uuid";
// ../user-offline-uuid/index.browser.ts
var md5 = (s) => {
function md5cycle(x, k) {
let a = x[0];
let b = x[1];
let c = x[2];
let d = x[3];
a = ff(a, b, c, d, k[0], 7, -680876936);
d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819);
b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897);
d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341);
b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416);
d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063);
b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682);
d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290);
b = ff(b, c, d, a, k[15], 22, 1236535329);
a = gg(a, b, c, d, k[1], 5, -165796510);
d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713);
b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691);
d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335);
b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438);
d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961);
b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467);
d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473);
b = gg(b, c, d, a, k[12], 20, -1926607734);
a = hh(a, b, c, d, k[5], 4, -378558);
d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562);
b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060);
d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632);
b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174);
d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979);
b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487);
d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520);
b = hh(b, c, d, a, k[2], 23, -995338651);
a = ii(a, b, c, d, k[0], 6, -198630844);
d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905);
b = ii(b, c, d, a, k[5], 21, -57434055);
a = ii(a, b, c, d, k[12], 6, 1700485571);
d = ii(d, a, b, c, k[3], 10, -1894986606);
c = ii(c, d, a, b, k[10], 15, -1051523);
b = ii(b, c, d, a, k[1], 21, -2054922799);
a = ii(a, b, c, d, k[8], 6, 1873313359);
d = ii(d, a, b, c, k[15], 10, -30611744);
c = ii(c, d, a, b, k[6], 15, -1560198380);
b = ii(b, c, d, a, k[13], 21, 1309151649);
a = ii(a, b, c, d, k[4], 6, -145523070);
d = ii(d, a, b, c, k[11], 10, -1120210379);
c = ii(c, d, a, b, k[2], 15, 718787259);
b = ii(b, c, d, a, k[9], 21, -343485551);
x[0] = add32(a, x[0]);
x[1] = add32(b, x[1]);
x[2] = add32(c, x[2]);
x[3] = add32(d, x[3]);
}
function cmn(q, a, b, x, s2, t) {
a = add32(add32(a, q), add32(x, t));
return add32(a << s2 | a >>> 32 - s2, b);
}
function ff(a, b, c, d, x, s2, t) {
return cmn(b & c | ~b & d, a, b, x, s2, t);
}
function gg(a, b, c, d, x, s2, t) {
return cmn(b & d | c & ~d, a, b, x, s2, t);
}
function hh(a, b, c, d, x, s2, t) {
return cmn(b ^ c ^ d, a, b, x, s2, t);
}
function ii(a, b, c, d, x, s2, t) {
return cmn(c ^ (b | ~d), a, b, x, s2, t);
}
function md51(s2) {
const n = s2.length;
const state = new Int32Array([1732584193, -271733879, -1732584194, 271733878]);
let i;
for (i = 64; i <= s2.length; i += 64) {
md5cycle(state, md5blk(s2.substring(i - 64, i)));
}
s2 = s2.substring(i - 64);
const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (i = 0; i < s2.length; i++) {
tail[i >> 2] |= s2.charCodeAt(i) << (i % 4 << 3);
}
tail[i >> 2] |= 128 << (i % 4 << 3);
if (i > 55) {
md5cycle(state, tail);
for (i = 0; i < 16; i++)
tail[i] = 0;
}
tail[14] = n * 8;
md5cycle(state, tail);
return state;
}
function md5blk(s2) {
const md5blks = [];
let i;
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] = s2.charCodeAt(i) + (s2.charCodeAt(i + 1) << 8) + (s2.charCodeAt(i + 2) << 16) + (s2.charCodeAt(i + 3) << 24);
}
return md5blks;
}
function add32(a, b) {
return a + b & 4294967295;
}
return md51(s);
};
function getOfflineUUID(username) {
const md5Bytes = md5(`OfflinePlayer:${username}`);
md5Bytes[6] &= 15;
md5Bytes[6] |= 48;
md5Bytes[8] &= 63;
md5Bytes[8] |= 128;
const hex_chr = "0123456789abcdef".split("");
function rhex(n) {
let s = "";
let j = 0;
for (; j < 4; j++) {
s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
}
return s;
}
function hex(x) {
const reuslt = new Array(4);
for (let i = 0; i < x.length; i++) {
reuslt[i] = rhex(x[i]);
}
return reuslt.join("");
}
return hex(md5Bytes).replace(/(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})/, "$1-$2-$3-$4-$5");
}
// offline.ts
function newToken() {
return v4().replace(/-/g, "");
}
function offline(username, uuid) {
const id = uuid || getOfflineUUID(username);
const prof = {
id,
name: username
};
return {
accessToken: newToken(),
clientToken: newToken(),
selectedProfile: prof,
availableProfiles: [prof],
user: {
id,
username
}
};
}
// ../undici-shim/index.ts
var fetch2 = globalThis.fetch;
var File2 = globalThis.File;
var FormData2 = globalThis.FormData;
// mojang.ts
var NameAvailability = /* @__PURE__ */ ((NameAvailability2) => {
NameAvailability2["DUPLICATE"] = "DUPLICATE";
NameAvailability2["AVAILABLE"] = "AVAILABLE";
NameAvailability2["NOT_ALLOWED"] = "NOT_ALLOWED";
return NameAvailability2;
})(NameAvailability || {});
var SetNameError = class extends Error {
constructor(message, err) {
super(message);
__publicField(this, "path");
__publicField(this, "errorType");
__publicField(this, "error");
__publicField(this, "details");
__publicField(this, "errorMessage");
__publicField(this, "developerMessage");
this.name = "SetNameError";
this.path = err.path;
this.errorType = err.errorType;
this.error = err.error;
this.details = err.details;
this.errorMessage = err.errorMessage;
this.developerMessage = err.developerMessage;
}
};
var SetSkinError = class extends Error {
constructor(message, err) {
super(message);
__publicField(this, "path");
__publicField(this, "errorType");
__publicField(this, "error");
__publicField(this, "details");
__publicField(this, "errorMessage");
__publicField(this, "developerMessage");
this.name = "SetSkinError";
this.path = err.path;
this.errorType = err.errorType;
this.error = err.error;
this.details = err.details;
this.errorMessage = err.errorMessage;
this.developerMessage = err.developerMessage;
}
};
var MojangError = class extends Error {
constructor(err) {
super(err.errorMessage);
__publicField(this, "path");
__publicField(this, "errorMessage");
__publicField(this, "developerMessage");
this.path = err.path;
this.errorMessage = err.errorMessage;
this.developerMessage = err.developerMessage;
Object.assign(this, err);
}
};
var UnauthorizedError = class extends MojangError {
constructor(err) {
super(err);
__publicField(this, "name", "UnauthorizedError");
}
};
var ProfileNotFoundError = class extends MojangError {
constructor(err) {
super(err);
__publicField(this, "name", "ProfileNotFoundError");
}
};
var MojangClient = class {
constructor(options) {
__publicField(this, "fetch");
this.fetch = options?.fetch || fetch2;
}
async setName(name, token, signal) {
const resp = await this.fetch(`https://api.minecraftservices.com/minecraft/profile/name/${name}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
switch (resp.status) {
case 200:
return await resp.json();
case 400:
throw new SetNameError("Name is unavailable (Either taken or has not become available)", await resp.json());
case 403:
throw new SetNameError("Name is unavailable (Either taken or has not become available)", await resp.json());
case 401:
throw new SetNameError("Unauthorized (Bearer token expired or is not correct)", await resp.json());
case 429:
throw new SetNameError("Too many requests sent", await resp.json());
case 500:
throw new SetNameError("Timed out (API lagged out and could not respond)", await resp.json());
}
throw new SetNameError("Unknown error", await resp.json());
}
async getNameChangeInformation(token) {
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile/namechange", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
}
});
return await resp.json();
}
async checkNameAvailability(name, token, signal) {
const resp = await this.fetch(`https://api.minecraftservices.com/minecraft/profile/name/${name}/available`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
const result = await resp.json();
return result.status;
}
async getProfile(token, signal) {
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
if (resp.headers.get("content-type")?.toLocaleLowerCase() !== "application/json") {
throw new Error(await resp.text());
}
const json = await resp.json();
if (resp.ok) {
return json;
} else if (json.error === "NOT_FOUND") {
throw new ProfileNotFoundError(json);
} else if (resp.status === 401) {
throw new UnauthorizedError(json);
}
throw Object.assign(new Error("Unknown Error"), json);
}
async setSkin(fileName, skin, variant, token, signal) {
const body = typeof skin === "string" ? JSON.stringify({ url: skin, variant }) : getSkinFormData(skin, fileName, variant);
const headers = {
Authorization: `Bearer ${token}`
};
if (typeof body === "string") {
headers["Content-Type"] = "application/json";
}
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile/skins", {
method: "POST",
headers,
body,
signal
});
const profileResponse = await resp.json();
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
if ("error" in profileResponse || "errorMessage" in profileResponse) {
throw new SetSkinError(`Fail to set skin ${profileResponse.errorMessage}`, profileResponse);
}
return profileResponse;
}
async resetSkin(token, signal) {
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile/skins/active", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
}
async hideCape(token, signal) {
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile/capes/active", {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
}
async showCape(capeId, token, signal) {
const resp = await this.fetch("https://api.minecraftservices.com/minecraft/profile/capes/active", {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ capeId }),
signal
});
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
if (resp.status === 400) {
throw new Error();
}
const profile = await resp.json();
return profile;
}
async verifySecurityLocation(token, signal) {
const resp = await this.fetch("https://api.mojang.com/user/security/location", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
},
signal
});
if (resp.status === 204) {
return true;
}
return false;
}
async getSecurityChallenges(token) {
const resp = await this.fetch("https://api.mojang.com/user/security/challenges", {
method: "GET",
headers: {
Authorization: `Bearer ${token}`
}
});
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
return await resp.json();
}
async submitSecurityChallenges(answers, token) {
const resp = await this.fetch("https://api.mojang.com/user/security/location", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify(answers)
});
if (resp.status === 204) {
return;
}
if (resp.status === 401) {
throw new UnauthorizedError(await resp.json());
}
throw new Error();
}
/**
* Return the owner ship list of the player with those token.
*/
async checkGameOwnership(token, signal) {
const mcResponse = await this.fetch("https://api.minecraftservices.com/entitlements/mcstore", {
headers: {
Authorization: `Bearer ${token}`
},
signal
});
if (mcResponse.status === 401) {
throw new UnauthorizedError(await mcResponse.text());
}
if (!mcResponse.ok || mcResponse.headers.get("content-type")?.toLocaleLowerCase() !== "application/json") {
throw new Error(await mcResponse.text());
}
const result = await mcResponse.json();
return result;
}
};
function getSkinFormData(buf, fileName, variant) {
const form = new FormData2();
form.append("variant", variant);
const file = new File([buf], fileName, { type: "image/png" });
form.append("file", file);
return form;
}
// yggdrasil.ts
var YggdrasilError = class extends Error {
constructor(statusCode, message, o) {
super(message);
this.statusCode = statusCode;
__publicField(this, "error");
__publicField(this, "errorMessage");
__publicField(this, "cause");
this.name = "YggdrasilError";
this.error = o?.error;
this.errorMessage = o?.errorMessage;
this.cause = o?.cause;
}
};
var YggdrasilClient = class {
/**
* Create client for official-like api endpoint
* @param api The official-like api endpoint
*/
constructor(api, options) {
this.api = api;
__publicField(this, "headers");
__publicField(this, "fetch");
__publicField(this, "FormData");
__publicField(this, "File");
this.headers = options?.headers ?? {};
this.fetch = options?.fetch || fetch;
this.FormData = options?.FormData || FormData;
this.File = options?.File || File;
}
async validate(accessToken, clientToken, signal) {
const response = await this.fetch(this.api + "/validate", {
method: "POST",
body: JSON.stringify({ accessToken, clientToken }),
headers: {
...this.headers,
"content-type": "application/json; charset=utf-8"
},
signal
});
return response.ok;
}
async invalidate(accessToken, clientToken, signal) {
return await this.fetch(this.api + "/invalidate", {
method: "POST",
body: JSON.stringify({ accessToken, clientToken }),
headers: {
...this.headers,
"content-type": "application/json; charset=utf-8"
},
signal
}).then((s) => s.ok);
}
async login({ username, password, clientToken, requestUser }, signal) {
const response = await this.fetch(this.api + "/authenticate", {
method: "POST",
body: JSON.stringify({
agent: { name: "Minecraft", version: 1 },
requestUser: typeof requestUser === "boolean" ? requestUser : false,
username,
password,
clientToken
}),
headers: {
...this.headers,
"content-type": "application/json; charset=utf-8"
},
signal
});
if (response.status >= 400) {
const body = await response.text();
throw new YggdrasilError(response.status, response.status + ":" + body, response.headers.get("content-type")?.startsWith("application/json") ? JSON.parse(body) : void 0);
}
const authentication = await response.json();
return authentication;
}
async refresh({ accessToken, requestUser, clientToken }, signal) {
const response = await this.fetch(this.api + "/refresh", {
method: "POST",
body: JSON.stringify({
accessToken,
clientToken,
requestUser: typeof requestUser === "boolean" ? requestUser : false
}),
headers: {
...this.headers,
"content-type": "application/json; charset=utf-8"
},
signal
});
if (response.status >= 400) {
const body = await response.text();
throw new YggdrasilError(response.status, response.status + ":" + body, response.headers.get("content-type")?.startsWith("application/json") ? JSON.parse(body) : void 0);
}
const authentication = await response.json();
return authentication;
}
};
function isTextureSlim(o) {
return o.metadata ? o.metadata.model === "slim" : false;
}
function getTextureType(o) {
return isTextureSlim(o) ? "slim" : "steve";
}
var YggdrasilThirdPartyClient = class extends YggdrasilClient {
/**
* Create thirdparty (authlib-injector) style client
* @param api The api url following https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83
* @param clientToken
* @param dispatcher
*/
constructor(api, options) {
super(api + "/authserver", options);
__publicField(this, "profileApi");
__publicField(this, "textureApi");
this.profileApi = api + "/sessionserver/session/minecraft/profile/${uuid}";
this.textureApi = api + "/api/user/profile/${uuid}/${type}";
}
async lookup(uuid, unsigned = true, signal) {
const url = new URL(this.profileApi.replace("${uuid}", uuid));
url.searchParams.append("unsigned", unsigned ? "true" : "false");
const response = await this.fetch(url.toString(), {
method: "GET",
headers: this.headers,
signal
});
if (response.status !== 200) {
const body = await response.text();
throw new YggdrasilError(response.status, response.status + ":" + body, response.headers.get("content-type")?.startsWith("application/json") ? JSON.parse(body) : void 0);
}
const o = await response.json();
if (o.properties && o.properties instanceof Array) {
const properties = o.properties;
const to = {};
for (const prop of properties) {
to[prop.name] = prop.value;
}
o.properties = to;
}
return o;
}
async setTexture(options, signal) {
const url = new URL(this.textureApi.replace("${uuid}", options.uuid).replace("${type}", options.type));
const requestOptions = {
headers: {
...this.headers,
Authorization: `Bearer ${options.accessToken}`
},
signal
};
if (!options.texture) {
requestOptions.method = "DELETE";
} else if ("data" in options.texture) {
requestOptions.method = "PUT";
const form = new this.FormData();
form.append("model", options.texture.metadata?.model || "steve");
form.append("file", new this.File([options.texture.data], "Steve.png", { type: "image/png" }));
requestOptions.body = form;
} else if ("url" in options.texture) {
requestOptions.method = "POST";
url.searchParams.append("model", options.texture.metadata?.model || "");
url.searchParams.append("url", options.texture.url);
} else {
throw new TypeError("Illegal Option Format!");
}
const response = await this.fetch(url.toString(), requestOptions);
if (response.status === 401) {
if (response.headers.get("content-type") === "application/json") {
const body = await response.json();
throw new YggdrasilError(response.status, response.status.toString(), {
error: body.error ?? "Unauthorized",
errorMessage: body.errorMessage ?? "Unauthorized"
});
} else {
const body = await response.text();
throw new YggdrasilError(response.status, response.status + ":" + body, {
error: "Unauthorized",
errorMessage: "Unauthorized: " + body
});
}
}
if (response.status >= 400) {
const body = await response.text();
throw new YggdrasilError(response.status, response.status + ":" + body, {
error: "SetSkinFailed",
errorMessage: "Fail to set skin " + body
});
}
}
};
// microsoft.ts
var MicrosoftAuthenticator = class {
constructor(options) {
__publicField(this, "fetch");
this.fetch = options.fetch || fetch;
}
/**
* Authenticate with xbox live by ms oauth access token
* @param oauthAccessToken The oauth access token
*/
async authenticateXboxLive(oauthAccessToken, signal) {
const xblResponse = await this.fetch("https://user.auth.xboxlive.com/user/authenticate", {
method: "POST",
body: JSON.stringify({
Properties: {
AuthMethod: "RPS",
SiteName: "user.auth.xboxlive.com",
RpsTicket: `d=${oauthAccessToken}`
},
RelyingParty: "http://auth.xboxlive.com",
TokenType: "JWT"
}),
headers: {
"Content-Type": "application/json"
},
signal
});
if (xblResponse.status !== 200) {
throw new Error(`Failed to authenticate with xbox live, status code: ${xblResponse.status}: ${await xblResponse.text()}}`);
}
const result = await xblResponse.json();
return result;
}
/**
* Authorize the xbox live. It will get the xsts token in response.
* @param xblResponseToken The {@link XBoxResponse.Token}
*/
async authorizeXboxLive(xblResponseToken, relyingParty = "rp://api.minecraftservices.com/", signal) {
const xstsResponse = await this.fetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
Properties: {
SandboxId: "RETAIL",
UserTokens: [xblResponseToken]
},
RelyingParty: relyingParty,
TokenType: "JWT"
}),
signal
});
if (xstsResponse.status !== 200) {
const errText = await xstsResponse.text();
let errObj = {};
try {
errObj = JSON.parse(errText);
} catch (e) {
}
throw Object.assign(new Error(`Failed to authorize with xbox live, status code: ${xstsResponse.status}: ${errText}}`), errObj);
}
const result = await xstsResponse.json();
return result;
}
/**
* Get xbox user profile, including **username** and **avatar**.
*
* You can find the parameters from the {@link XBoxResponse}.
*
* @param xuid The `xuid` in a {@link XBoxResponse.DisplayClaims}
* @param uhs The `uhs` in a {@link XBoxResponse.DisplayClaims}
* @param xstsToken The {@link XBoxResponse.Token}
* @returns The user game profile.
*/
async getXboxGameProfile(xuid, uhs, xstsToken, signal) {
const url = new URL(`https://profile.xboxlive.com/users/xuid(${xuid})/profile/settings`);
url.searchParams.append("settings", ["PublicGamerpic", "Gamertag"].join(","));
const response = await this.fetch(url.toString(), {
headers: {
"x-xbl-contract-version": "2",
"content-type": "application/json",
Authorization: `XBL3.0 x=${uhs};${xstsToken}`
},
signal
});
if (response.status !== 200) {
throw new Error(`Failed to get xbox game profile, status code: ${response.status}: ${await response.text()}}`);
}
const result = await response.json();
return result;
}
/**
* Acquire both Minecraft and xbox token and xbox game profile.
* You can use the xbox token to login Minecraft by {@link loginMinecraftWithXBox}.
*
* This method is the composition of calling
* - {@link authenticateXboxLive}
* - {@link authorizeXboxLive} to `rp://api.minecraftservices.com/`
* - {@link authorizeXboxLive} to `http://xboxlive.com`
* - {@link getXboxGameProfile}
*
* You can call them individually if you want a more detailed control.
*
* @param oauthAccessToken The microsoft access token
* @param signal The abort signal
* @returns The object contain xstsResponse (minecraft xbox token) and xbox game profile
*/
async acquireXBoxToken(oauthAccessToken, signal) {
const xblResponse = await this.authenticateXboxLive(oauthAccessToken, signal);
const minecraftXstsResponse = await this.authorizeXboxLive(xblResponse.Token, "rp://api.minecraftservices.com/", signal);
const xstsResponse = await this.authorizeXboxLive(xblResponse.Token, "http://xboxlive.com", signal);
return { minecraftXstsResponse, liveXstsResponse: xstsResponse };
}
/**
* This will return the response with Minecraft access token!
*
* This access token allows us to launch the game, but, we haven't actually checked if the account owns the game. Everything until here works with a normal Microsoft account!
*
* @param uhs uhs from {@link XBoxResponse} of {@link acquireXBoxToken}
* @param xstsToken You need to get this token from {@link acquireXBoxToken}
*/
async loginMinecraftWithXBox(uhs, xstsToken, signal) {
const mcResponse = await this.fetch("https://api.minecraftservices.com/authentication/login_with_xbox", {
method: "POST",
body: JSON.stringify({
identityToken: `XBL3.0 x=${uhs};${xstsToken}`
}),
headers: {
"content-type": "application/json"
},
signal
});
if (mcResponse.status !== 200) {
throw new Error(`Failed to login minecraft with xbox, status code: ${mcResponse.status}: ${await mcResponse.text()}}`);
}
const result = await mcResponse.json();
return result;
}
};
export {
MicrosoftAuthenticator,
MojangClient,
MojangError,
NameAvailability,
ProfileNotFoundError,
SetNameError,
SetSkinError,
UnauthorizedError,
YggdrasilClient,
YggdrasilError,
YggdrasilThirdPartyClient,
getOfflineUUID,
getTextureType,
isTextureSlim,
newToken,
offline
};
//# sourceMappingURL=index.browser.mjs.map