dmclc
Version:
Dolphin Minecraft Launcher Core
199 lines (198 loc) • 6.8 kB
JavaScript
import { got } from "got";
import open from "open";
import { setTimeout as sleep } from "timers/promises";
import copy from "../../utils/copy.js";
const scope = "XboxLive.signin offline_access";
export class MicrosoftAccount {
data;
launcher;
constructor(data, launcher) {
this.data = data;
this.launcher = launcher;
}
getTokens() {
const ret = [];
if (this.data.accessToken)
ret.push(this.data.accessToken);
return ret;
}
async step1_new() {
const device_response = await got.post("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", {
form: {
client_id: this.launcher.clientId,
scope
}
}).json();
if (this.launcher.copy)
this.launcher.copy(device_response.user_code);
else
copy(device_response.user_code);
open(device_response.verification_uri);
let interval = device_response.interval;
const startTime = Date.now() / 1000;
// eslint-disable-next-line no-constant-condition
while (true) {
await sleep(Math.max(interval, 1) * 1000);
const estimatedTime = Date.now() / 1000 - startTime;
if (estimatedTime >= device_response.expires_in) {
await this.launcher.error("accounts.microsoft.timeout");
return null;
}
const tokenResponse = await got.post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", {
form: {
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
code: device_response.device_code,
client_id: this.launcher.clientId
},
throwHttpErrors: false,
retry: {
limit: 5
}
}).json();
if ("error" in tokenResponse) {
switch (tokenResponse.error) {
case "expired_token":
await this.launcher.error("accounts.microsoft.timeout");
return null;
case "authorization_declined":
await this.launcher.error("accounts.microsoft.canceled");
return null;
case "slow_down":
interval += 5;
break;
}
}
else
return tokenResponse;
}
}
async step1_refresh() {
const res = await got.post("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", {
form: {
client_id: this.launcher.clientId,
grant_type: "refresh_token",
refresh_token: this.data.refresh_token
}
}).json();
return res.access_token;
}
async step2_xbl(accessToken) {
const reqBody = {
Properties: {
AuthMethod: "RPS",
SiteName: "user.auth.xboxlive.com",
RpsTicket: "d=" + accessToken
},
RelyingParty: "http://auth.xboxlive.com",
TokenType: "JWT"
};
const res = await got("https://user.auth.xboxlive.com/user/authenticate", {
method: "POST",
json: reqBody
}).json();
return ({
token: res.Token,
uhs: res.DisplayClaims.xui[0].uhs
});
}
async step3_xsts(xblToken) {
const reqBody = {
Properties: {
SandboxId: "RETAIL",
UserTokens: [xblToken]
},
RelyingParty: "rp://api.minecraftservices.com/",
TokenType: "JWT"
};
const res = await got("https://xsts.auth.xboxlive.com/xsts/authorize", {
method: "POST",
json: reqBody
}).json();
return res.Token;
}
async step4_login(xstsToken, uhs) {
const reqBody = {
identityToken: `XBL3.0 x=${uhs};${xstsToken}`
};
const res = await got("https://api.minecraftservices.com/authentication/login_with_xbox", {
method: "POST",
json: reqBody
}).json();
return res.access_token;
}
async step5_check(MCAccessToken) {
const obj = (await got("https://api.minecraftservices.com/entitlements/mcstore", {
headers: {
Authorization: `Bearer ${MCAccessToken}`
}
}).json());
for (const i of obj.items) {
if (i.name === "game_minecraft") {
return true;
}
}
return false;
}
async step6_uuid_name(MCAccessToken) {
return await got("https://api.minecraftservices.com/minecraft/profile", {
headers: {
Authorization: `Bearer ${MCAccessToken}`
}
}).json();
}
async check() {
return await this.refresh();
}
async refresh() {
const at = (await this.step1_refresh());
if (!at)
return false;
return await this.nextSteps(at);
}
getUUID() {
return this.data.uuid;
}
async login() {
await this.launcher.info("accounts.microsoft.message");
const val = await this.step1_new();
if (!val)
return false;
this.data.refresh_token = val.refresh_token;
return await this.nextSteps(val.access_token);
}
async nextSteps(access_token) {
const tu = await this.step2_xbl(access_token);
const xsts = await this.step3_xsts(tu.token);
const MCAccessToken = await this.step4_login(xsts, tu.uhs);
if (!await this.step5_check(MCAccessToken)) {
await this.launcher.error("accounts.microsoft.no_minecraft_in_account");
return false;
}
const un = await this.step6_uuid_name(MCAccessToken);
this.data.accessToken = MCAccessToken;
this.data.name = un.name;
this.data.uuid = un.id;
return true;
}
async prepareLaunch() {
// We don't need to do anything using this method.
return true;
}
async getLaunchJVMArgs() {
return [];
}
async getLaunchGameArgs() {
const map = new Map();
await this.refresh();
const at = this.data.accessToken;
map.set("auth_access_token", at);
map.set("auth_session", at);
map.set("auth_player_name", this.data.name);
map.set("user_type", "mojang");
map.set("user_properties", "{}");
return map;
}
toString() {
return `${this.data.name} (${this.launcher.i18n("accounts.microsoft.name")})`;
}
}