UNPKG

dmclc

Version:

Dolphin Minecraft Launcher Core

199 lines (198 loc) 6.8 kB
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")})`; } }