From 85f08be29b55c66996d1ab7d9fce9274d519042e Mon Sep 17 00:00:00 2001 From: creeper123123321 Date: Fri, 6 Aug 2021 23:09:45 -0300 Subject: [PATCH] Revert "temp Revert "cleanup, js classes"" This reverts commit 37ca5e22f6a8bb3198fba99f5d8f34e5da589394. --- src/main/resources/web/index.html | 9 +- src/main/resources/web/js/account_manager.js | 410 +++++++++++++------ src/main/resources/web/js/auth_ms.js | 65 --- src/main/resources/web/js/cors_proxy.js | 8 +- src/main/resources/web/js/minecraft_id.js | 19 - src/main/resources/web/js/notification.js | 21 +- src/main/resources/web/js/page.js | 181 +++++--- src/main/resources/web/js/util.js | 14 +- src/main/resources/web/js/websocket.js | 71 ++-- src/main/resources/web/js/worker.js | 50 +-- 10 files changed, 489 insertions(+), 359 deletions(-) delete mode 100644 src/main/resources/web/js/auth_ms.js delete mode 100644 src/main/resources/web/js/minecraft_id.js diff --git a/src/main/resources/web/index.html b/src/main/resources/web/index.html index 37d9554..45e2dd0 100644 --- a/src/main/resources/web/index.html +++ b/src/main/resources/web/index.html @@ -169,13 +169,6 @@ script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*. - - - - - - - - + diff --git a/src/main/resources/web/js/account_manager.js b/src/main/resources/web/js/account_manager.js index 1e7a524..1b8ed39 100644 --- a/src/main/resources/web/js/account_manager.js +++ b/src/main/resources/web/js/account_manager.js @@ -1,35 +1,226 @@ -// Account storage -function storeMcAccount(accessToken, clientToken, name, id, msUser = null) { - let accounts = JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []; - let account = {accessToken: accessToken, clientToken: clientToken, name: name, id: id, msUser: msUser}; - accounts.push(account); - localStorage.setItem("viaaas_mc_accounts", JSON.stringify(accounts)); - refreshAccountList(); - return account; +import {getCorsProxy} from "./cors_proxy.js"; +import {checkFetchSuccess, filterNot, isSuccess} from "./util.js"; +import {addToast, refreshAccountList} from "./page.js"; + +let activeAccounts = []; + +function loadAccounts() { + (JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []).forEach(it => { + if (it.clientToken) { + addActiveAccount(new MojangAccount(it.id, it.name, it.accessToken, it.clientToken)) + } else if (it.msUser) { + addActiveAccount(new MicrosoftAccount(it.id, it.name, it.accessToken, it.msUser)) + } + }) } -function removeMcAccount(id) { - let accounts = getMcAccounts(); - accounts = accounts.filter(it => it.id != id); - localStorage.setItem("viaaas_mc_accounts", JSON.stringify(accounts)); - refreshAccountList(); +$(() => loadAccounts()); + +function saveRefreshAccounts() { + localStorage.setItem("viaaas_mc_accounts", JSON.stringify(getActiveAccounts())) + refreshAccountList() } -function getMcAccounts() { - return JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []; +export function getActiveAccounts() { + return activeAccounts; } -function findAccountByMcName(name) { - return getMcAccounts().reverse().find(it => it.name.toLowerCase() == name.toLowerCase()); - -} -function findAccountByMs(username) { - return getMcAccounts().filter(isNotMojang).find(it => it.msUser == username); +export function getMicrosoftUsers() { + return (myMSALObj.getAllAccounts() || []).map(it => it.username); } -// Mojang account -function loginMc(user, pass) { - var clientToken = uuid.v4(); +export class McAccount { + id; + name; + accessToken; + loggedOut = false; + + constructor(id, username, accessToken) { + this.id = id; + this.name = username; + this.accessToken = accessToken; + } + + logout() { + activeAccounts = filterNot(activeAccounts, this); + saveRefreshAccounts(); + this.loggedOut = true; + } + + checkActive() { + return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", { + method: "post", + body: JSON.stringify({accessToken: this.accessToken}), + headers: {"content-type": "application/json"} + }).then(data => isSuccess(data.status)); + } + + joinGame(hash) { + return this.acquireActiveToken() + .then(() => fetch(getCorsProxy() + "https://sessionserver.mojang.com/session/minecraft/join", { + method: "post", + body: JSON.stringify({ + accessToken: this.accessToken, + selectedProfile: this.id, + serverId: hash + }), + headers: {"content-type": "application/json"} + })).then(checkFetchSuccess("Failed to join session")); + } + + refresh() { + } + + acquireActiveToken() { + return this.checkActive().then(success => { + if (!success) { + return this.refresh(); + } + return this; + }).catch(e => addToast("Failed to refresh token!", e)); + } +} + +export class MojangAccount extends McAccount { + clientToken; + + constructor(id, username, accessToken, clientToken) { + super(id, username, accessToken); + this.clientToken = clientToken; + } + + logout() { + super.logout(); + fetch(getCorsProxy() + "https://authserver.mojang.com/invalidate", { + method: "post", + body: JSON.stringify({ + accessToken: this.accessToken, + clientToken: this.clientToken + }), + headers: {"content-type": "application/json"} + }).then(checkFetchSuccess("not success logout")); + } + + checkActive() { + return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", { + method: "post", + body: JSON.stringify({ + accessToken: this.accessToken, + clientToken: this.clientToken + }), + headers: {"content-type": "application/json"} + }).then(data => isSuccess(data.status)); + } + + refresh() { + super.refresh(); + + console.log("refreshing " + this.id); + return fetch(getCorsProxy() + "https://authserver.mojang.com/refresh", { + method: "post", + body: JSON.stringify({ + accessToken: this.accessToken, + clientToken: this.clientToken + }), + headers: {"content-type": "application/json"}, + }) + .then(checkFetchSuccess("code")) + .then(r => r.json()) + .then(json => { + console.log("refreshed " + json.selectedProfile.id); + this.accessToken = json.accessToken; + this.clientToken = json.clientToken; + this.name = json.selectedProfile.name; + this.id = json.id; + saveRefreshAccounts(); + }); + } +} + +export class MicrosoftAccount extends McAccount { + msUser; + + constructor(id, username, accessToken, msUser) { + super(id, username, accessToken); + this.msUser = msUser; + } + + logout() { + super.logout(); + + let msAccount = myMSALObj.getAccountByUsername(this.msUser); + if (!msAccount) return; + + const logoutRequest = {account: msAccount}; + myMSALObj.logout(logoutRequest); + } + + refresh() { + super.refresh(); + return getTokenPopup(this.msUser, loginRequest) + .then(response => { + // this supports CORS + return fetch("https://user.auth.xboxlive.com/user/authenticate", { + method: "post", + body: JSON.stringify({ + Properties: { + AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", + RpsTicket: "d=" + response.accessToken + }, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT" + }), + headers: {"content-type": "application/json"} + }).then(checkFetchSuccess("xbox response not success")) + .then(r => r.json()); + }).then(json => { + return fetch("https://xsts.auth.xboxlive.com/xsts/authorize", { + method: "post", + body: JSON.stringify({ + Properties: {SandboxId: "RETAIL", UserTokens: [json.Token]}, + RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT" + }), + headers: {"content-type": "application/json"} + }).then(checkFetchSuccess("xsts response not success")) + .then(r => r.json()); + }).then(json => { + return fetch(getCorsProxy() + "https://api.minecraftservices.com/authentication/login_with_xbox", { + method: "post", + body: JSON.stringify({identityToken: "XBL3.0 x=" + json.DisplayClaims.xui[0].uhs + ";" + json.Token}), + headers: {"content-type": "application/json"} + }).then(checkFetchSuccess("mc response not success")) + .then(r => r.json()); + }).then(json => { + return fetch(getCorsProxy() + "https://api.minecraftservices.com/minecraft/profile", { + method: "get", + headers: {"content-type": "application/json", "authorization": "Bearer " + json.access_token} + }).then(profile => { + if (profile.status === 404) return {id: "MHF_Exclamation", name: "[DEMO]", access_token: ""}; + if (!isSuccess(profile.status)) throw "profile response not success"; + return profile.json(); + }).then(jsonProfile => { + this.accessToken = json.access_token; + this.name = jsonProfile.name; + this.id = jsonProfile.id; + saveRefreshAccounts(); + }); + }); + } +} + +export function findAccountByMcName(name) { + return activeAccounts.find(it => it.name.toLowerCase() === name.toLowerCase()); +} + +export function findAccountByMs(username) { + return getActiveAccounts().find(it => it.msUser === username); +} + +function addActiveAccount(acc) { + activeAccounts.push(acc) + saveRefreshAccounts() +} + +export function loginMc(user, pass) { + const clientToken = uuid.v4(); fetch(getCorsProxy() + "https://authserver.mojang.com/authenticate", { method: "post", body: JSON.stringify({ @@ -40,123 +231,74 @@ function loginMc(user, pass) { }), headers: {"content-type": "application/json"} }).then(checkFetchSuccess("code")) - .then(r => r.json()) - .then(data => { - storeMcAccount(data.accessToken, data.clientToken, data.selectedProfile.name, data.selectedProfile.id); - }).catch(e => addToast("Failed to login", e)); + .then(r => r.json()) + .then(data => { + let acc = new MojangAccount(data.selectedProfile.id, data.selectedProfile.name, data.accessToken, data.clientToken); + addActiveAccount(acc); + return acc; + }).catch(e => addToast("Failed to login", e)); $("#form_add_mc input").val(""); } -function logoutMojang(id) { - getMcAccounts().filter(isMojang).filter(it => it.id == id).forEach(it => { - fetch(getCorsProxy() + "https://authserver.mojang.com/invalidate", {method: "post", - body: JSON.stringify({ - accessToken: it.accessToken, - clientToken: it.clientToken - }), - headers: {"content-type": "application/json"} - }) - .then(checkFetchSuccess("not success logout")) - .finally(() => removeMcAccount(id)); - }); +// https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code +const azureClientId = "a370fff9-7648-4dbf-b96e-2b4f8d539ac2"; +const whitelistedOrigin = [ + "https://via-login.geyserconnect.net", + "https://via.re.yt.nom.br", + "https://viaaas.noxt.cf" +]; +const loginRequest = {scopes: ["XboxLive.signin"]}; +let redirectUrl = "https://viaversion.github.io/VIAaaS/src/main/resources/web/"; +if (location.hostname === "localhost" || whitelistedOrigin.includes(location.origin)) { + redirectUrl = location.origin + location.pathname; } -function refreshMojangAccount(it) { - console.log("refreshing " + it.id); - return fetch(getCorsProxy() + "https://authserver.mojang.com/refresh", { - method: "post", - body: JSON.stringify({ - accessToken: it.accessToken, - clientToken: it.clientToken - }), - headers: {"content-type": "application/json"}, - }).then(checkFetchSuccess("code")) - .then(r => r.json()) - .then(json => { - console.log("refreshed " + json.selectedProfile.id); - removeMcAccount(json.selectedProfile.id); - return storeMcAccount(json.accessToken, json.clientToken, json.selectedProfile.name, json.selectedProfile.id); - }); +const msalConfig = { + auth: { + clientId: azureClientId, + authority: "https://login.microsoftonline.com/consumers/", + redirectUri: redirectUrl, + }, + cache: { + cacheLocation: "sessionStorage", + storeAuthStateInCookie: false, + } +}; + +const myMSALObj = new msal.PublicClientApplication(msalConfig); + +export function loginMs() { + myMSALObj.loginRedirect(loginRequest); } -// Generic -function getMcUserToken(account) { - return validateToken(account.accessToken, account.clientToken || undefined).then(data => { - if (!isSuccess(data.status)) { - if (isMojang(account)) { - return refreshMojangAccount(account); - } else { - return refreshTokenMs(account.msUser); - } +$(() => myMSALObj.handleRedirectPromise().then((resp) => { + if (resp) { + let found = findAccountByMs(resp.account.username) + if (!found) { + let accNew = new MicrosoftAccount("", "", "", resp.account.username); + accNew.refresh() + .then(() => addActiveAccount(accNew)) + .catch(e => addToast("Failed to get token", e)); + } else { + found.refresh() + .catch(e => addToast("Failed to refresh token", e)); } - return account; - }).catch(e => addToast("Failed to refresh token!", e)); -} + } +})); -function validateToken(accessToken, clientToken) { - return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", { - method: "post", - body: JSON.stringify({ - accessToken: accessToken, - clientToken: clientToken - }), - headers: {"content-type": "application/json"} +function getTokenPopup(username, request) { + /** + * See here for more info on account retrieval: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md + */ + request.account = myMSALObj.getAccountByUsername(username); + return myMSALObj.acquireTokenSilent(request).catch(error => { + console.warn("silent token acquisition fails."); + if (error instanceof msal.InteractionRequiredAuthError) { + // fallback to interaction when silent call fails + return myMSALObj.acquireTokenPopup(request).catch(error => console.error(error)); + } else { + console.warn(error); + } }); -} - -function joinGame(token, id, hash) { - return fetch(getCorsProxy() + "https://sessionserver.mojang.com/session/minecraft/join", { - method: "post", - body: JSON.stringify({ - accessToken: token, - selectedProfile: id, - serverId: hash - }), - headers: {"content-type": "application/json"} - }); -} - -// Microsoft auth -function refreshTokenMs(username) { - return getTokenPopup(username, loginRequest) - .then(response => { - // this supports CORS - return fetch("https://user.auth.xboxlive.com/user/authenticate", {method: "post", - body: JSON.stringify({Properties: {AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", - RpsTicket: "d=" + response.accessToken}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"}), - headers: {"content-type": "application/json"}}) - .then(checkFetchSuccess("xbox response not success")) - .then(r => r.json()); - }).then(json => { - return fetch("https://xsts.auth.xboxlive.com/xsts/authorize", {method: "post", - body: JSON.stringify({Properties: {SandboxId: "RETAIL", UserTokens: [json.Token]}, - RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"}), - headers: {"content-type": "application/json"}}) - .then(checkFetchSuccess("xsts response not success")) - .then(r => r.json()); - }).then(json => { - return fetch(getCorsProxy() + "https://api.minecraftservices.com/authentication/login_with_xbox", {method: "post", - body: JSON.stringify({identityToken: "XBL3.0 x=" + json.DisplayClaims.xui[0].uhs + ";" + json.Token}), - headers: {"content-type": "application/json"}}) - .then(checkFetchSuccess("mc response not success")) - .then(r => r.json()); - }).then(json => { - return fetch(getCorsProxy() + "https://api.minecraftservices.com/minecraft/profile", { - method: "get", headers: {"content-type": "application/json", "authorization": "Bearer " + json.access_token}}).then(profile => { - if (profile.status == 404) return {id: "MHF_Exclamation", name: "[DEMO]"}; - if (!isSuccess(profile.status)) throw "profile response not success"; - return profile.json(); - }).then(jsonProfile => { - removeMcAccount(jsonProfile.id); - return storeMcAccount(json.access_token, null, jsonProfile.name, jsonProfile.id, username); - }); - }); -} - -function isMojang(it) { - return !!it.clientToken; -} - -function isNotMojang(it) { - return !isMojang(it); -} +} \ No newline at end of file diff --git a/src/main/resources/web/js/auth_ms.js b/src/main/resources/web/js/auth_ms.js deleted file mode 100644 index 0fbdc27..0000000 --- a/src/main/resources/web/js/auth_ms.js +++ /dev/null @@ -1,65 +0,0 @@ -// https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code - -const azureClientId = "a370fff9-7648-4dbf-b96e-2b4f8d539ac2"; -const whitelistedOrigin = ["https://localhost:25543", "https://via-login.geyserconnect.net", "https://via.re.yt.nom.br", "https://viaaas.noxt.cf"]; -let redirectUrl = "https://viaversion.github.io/VIAaaS/src/main/resources/web/"; -if (whitelistedOrigin.includes(location.origin)) { - redirectUrl = location.origin + location.pathname; -} - -const msalConfig = { - auth: { - clientId: azureClientId, - authority: "https://login.microsoftonline.com/consumers/", - redirectUri: redirectUrl, - }, - cache: { - cacheLocation: "sessionStorage", - storeAuthStateInCookie: false, - } -}; - -const myMSALObj = new msal.PublicClientApplication(msalConfig); - -const loginRequest = { - scopes: ["XboxLive.signin"] -}; - -function loginMs() { - myMSALObj.loginRedirect(loginRequest); -} - -$(() => myMSALObj.handleRedirectPromise().then((resp) => { - if (resp) { - refreshTokenMs(resp.account.username).catch(e => addToast("Failed to get token", e)); - refreshAccountList(); - } -})); - -function getTokenPopup(username, request) { - /** - * See here for more info on account retrieval: - * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md - */ - request.account = myMSALObj.getAccountByUsername(username); - return myMSALObj.acquireTokenSilent(request).catch(error => { - console.warn("silent token acquisition fails."); - if (error instanceof msal.InteractionRequiredAuthError) { - // fallback to interaction when silent call fails - return myMSALObj.acquireTokenPopup(request).catch(error => console.error(error)); - } else { - console.warn(error); - } - }); -} - -function logoutMs(username) { - let mcAcc = findAccountByMs(username) || {}; - removeMcAccount(mcAcc.id); - - const logoutRequest = { - account: myMSALObj.getAccountByUsername(username) - }; - - myMSALObj.logout(logoutRequest); -} diff --git a/src/main/resources/web/js/cors_proxy.js b/src/main/resources/web/js/cors_proxy.js index 80610b6..f32552e 100644 --- a/src/main/resources/web/js/cors_proxy.js +++ b/src/main/resources/web/js/cors_proxy.js @@ -1,10 +1,14 @@ +import {refreshCorsStatus} from "./page.js"; + function defaultCors() { return "https://crp123-cors.herokuapp.com/"; } -function getCorsProxy() { + +export function getCorsProxy() { return localStorage.getItem("viaaas_cors_proxy") || defaultCors(); } -function setCorsProxy(url) { + +export function setCorsProxy(url) { localStorage.setItem("viaaas_cors_proxy", url); refreshCorsStatus(); } diff --git a/src/main/resources/web/js/minecraft_id.js b/src/main/resources/web/js/minecraft_id.js deleted file mode 100644 index b63e923..0000000 --- a/src/main/resources/web/js/minecraft_id.js +++ /dev/null @@ -1,19 +0,0 @@ -// Minecraft.id -var mcIdUsername = null; -var mcauth_code = null; -var mcauth_success = null; - -$(() => { - let urlParams = new URLSearchParams(); - window.location.hash.substr(1).split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a))); - mcIdUsername = urlParams.get("username"); - mcauth_code = urlParams.get("mcauth_code"); - mcauth_success = urlParams.get("mcauth_success"); - if (mcauth_success == "false") { - addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg")); - } - if (mcauth_code != null) { - history.replaceState(null, null, "#"); - renderActions(); - } -}); \ No newline at end of file diff --git a/src/main/resources/web/js/notification.js b/src/main/resources/web/js/notification.js index 465ebe4..910ce3b 100644 --- a/src/main/resources/web/js/notification.js +++ b/src/main/resources/web/js/notification.js @@ -1,7 +1,8 @@ // Notification -var notificationCallbacks = {}; +let notificationCallbacks = {}; $(() => { - new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg); + new BroadcastChannel("viaaas-notification") + .addEventListener("message", handleSWMsg); }) function handleSWMsg(event) { @@ -13,8 +14,8 @@ function handleSWMsg(event) { callback(data.action); } -function authNotification(msg, yes, no) { - if (!navigator.serviceWorker || Notification.permission != "granted") { +export function authNotification(msg, yes, no) { + if (!navigator.serviceWorker || Notification.permission !== "granted") { if (confirm(msg)) yes(); else no(); return; } @@ -23,21 +24,21 @@ function authNotification(msg, yes, no) { r.showNotification("Click to allow auth impersionation", { body: msg, tag: tag, - vibrate: [200,10,100,200,100,10,100,10,200], + vibrate: [200, 10, 100, 200, 100, 10, 100, 10, 200], actions: [ {action: "reject", title: "Reject"}, {action: "confirm", title: "Confirm"} ] }); notificationCallbacks[tag] = action => { - if (action == "reject") { + if (action === "reject") { no(); - } else if (!action || action == "confirm") { + } else if (!action || action === "confirm") { yes(); - } else { - return; } }; - setTimeout(() => { delete notificationCallbacks[tag] }, 30 * 1000); + setTimeout(() => { + delete notificationCallbacks[tag] + }, 30 * 1000); }); } diff --git a/src/main/resources/web/js/page.js b/src/main/resources/web/js/page.js index 9e8d7fc..be5cc98 100644 --- a/src/main/resources/web/js/page.js +++ b/src/main/resources/web/js/page.js @@ -1,35 +1,67 @@ -var connectionStatus = document.getElementById("connection_status"); -var corsStatus = document.getElementById("cors_status"); -var listening = document.getElementById("listening"); -var actions = document.getElementById("actions"); -var accounts = document.getElementById("accounts-list"); -var listenVisible = false; -var workers = []; +// Minecraft.id +import {icanhazepoch, icanhazip} from "./util.js"; +import {getCorsProxy, setCorsProxy} from "./cors_proxy.js"; +import { + findAccountByMs, + getActiveAccounts, + getMicrosoftUsers, + loginMc, + loginMs, MicrosoftAccount, + MojangAccount +} from "./account_manager.js"; +import {connect, getWsUrl, removeToken, sendSocket, setWsUrl, unlisten} from "./websocket.js"; + +let mcIdUsername = null; +let mcauth_code = null; +let mcauth_success = null; + +$(() => { + let urlParams = new URLSearchParams(); + window.location.hash.substr(1).split("?") + .map(it => new URLSearchParams(it) + .forEach((a, b) => urlParams.append(b, a))); + mcIdUsername = urlParams.get("username"); + mcauth_code = urlParams.get("mcauth_code"); + mcauth_success = urlParams.get("mcauth_success"); + if (mcauth_success === "false") { + addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg")); + } + if (mcauth_code != null) { + history.replaceState(null, null, "#"); + renderActions(); + } +}); + +let connectionStatus = document.getElementById("connection_status"); +let corsStatus = document.getElementById("cors_status"); +let listening = document.getElementById("listening"); +let actions = document.getElementById("actions"); +let accounts = document.getElementById("accounts-list"); +let cors_proxy_txt = document.getElementById("cors-proxy"); +let ws_url_txt = document.getElementById("ws-url"); +let listenVisible = false; +let workers = []; $(() => workers = new Array(navigator.hardwareConcurrency).fill(null).map(() => new Worker("js/worker.js"))); window.addEventListener('beforeinstallprompt', e => { - e.preventDefault(); + e.preventDefault(); }); // On load $(() => { if (navigator.serviceWorker) { - navigator.serviceWorker.register("sw.js"); - navigator.serviceWorker.ready.then(ready => ready.active.postMessage({ - action: "cache", - urls: performance.getEntriesByType("resource") - .map(it => it.name) - .filter(it => it.endsWith(".js") || it.endsWith(".css") || it.endsWith(".png")) - })); // https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker + navigator.serviceWorker.register("sw.js").then(() => { + swCacheFiles(); + }); } ohNo(); - $("#cors-proxy").val(getCorsProxy()); - $("#ws-url").val(getWsUrl()); + cors_proxy_txt.value = getCorsProxy(); + ws_url_txt.value = getWsUrl(); $("form").on("submit", e => e.preventDefault()); - $("#form_add_mc").on("submit", e => loginMc($("#email").val(), $("#password").val())); - $("#form_add_ms").on("submit", e => loginMs()); - $("#form_ws_url").on("submit", e => setWsUrl($("#ws-url").val())); - $("#form_cors_proxy").on("submit", e => setCorsProxy($("#cors-proxy").val())); + $("#form_add_mc").on("submit", () => loginMc($("#email").val(), $("#password").val())); + $("#form_add_ms").on("submit", () => loginMs()); + $("#form_ws_url").on("submit", () => setWsUrl($("#ws-url").val())); + $("#form_cors_proxy").on("submit", () => setCorsProxy($("#cors-proxy").val())); $(".css_async").attr("disabled", null); workers.forEach(it => it.onmessage = onWorkerMsg); @@ -41,14 +73,31 @@ $(() => { connect(); }); -function refreshCorsStatus() { - corsStatus.innerText = "..."; - icanhazip(true).then(ip => { - return icanhazip(false).then(ip2 => corsStatus.innerText = "OK " + ip + (ip != ip2 ? " (different IP)" : "")); - }).catch(e => corsStatus.innerText = "error: " + e); +function swCacheFiles() { + navigator.serviceWorker.ready.then(ready => ready.active.postMessage({ + action: "cache", + urls: performance.getEntriesByType("resource") + .map(it => it.name) + .filter(it => it.endsWith(".js") || it.endsWith(".css") || it.endsWith(".png")) + })); // https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker } -function addMcAccountToList(id, name, msUser = null) { +export function setWsStatus(txt) { + connectionStatus.innerText = txt; +} + +export function setListenVisible(visible) { + listenVisible = visible; +} + +export function refreshCorsStatus() { + corsStatus.innerText = "..."; + icanhazip(true).then(ip => { + return icanhazip(false).then(ip2 => corsStatus.innerText = "OK " + ip + (ip !== ip2 ? " (different IP)" : "")); + }).catch(e => corsStatus.innerText = "error: " + e); +} + +function addMcAccountToList(account) { let p = document.createElement("li"); p.className = "input-group d-flex"; let shead = document.createElement("span"); @@ -59,19 +108,15 @@ function addMcAccountToList(id, name, msUser = null) { n.className = "form-control"; let remove = document.createElement("a"); remove.className = "btn btn-danger"; - n.innerText = " " + name + " " + (msUser == null ? "" : "(" + msUser + ") "); + n.innerText = " " + account.name + " " + (account instanceof MicrosoftAccount ? "(" + account.msUser + ") " : ""); remove.innerText = "Logout"; remove.href = "javascript:"; remove.onclick = () => { - if (msUser == null) { - logoutMojang(id); - } else { - logoutMs(msUser); - } + account.logout(); }; - head.width = "24"; - head.alt = name + "'s head"; - head.src = "https://crafthead.net/helm/" + id; + head.width = 24; + head.alt = account.name + "'s head"; + head.src = "https://crafthead.net/helm/" + account.id; //(id.length == 36 || id.length == 32) ? "https://crafatar.com/avatars/" + id + "?overlay" : "https://crafthead.net/helm/" + id; p.append(shead); p.append(n); @@ -79,28 +124,35 @@ function addMcAccountToList(id, name, msUser = null) { accounts.appendChild(p); } -function refreshAccountList() { +export function refreshAccountList() { accounts.innerHTML = ""; - getMcAccounts().filter(isMojang).sort((a, b) => a.name.localeCompare(b.name)).forEach(it => addMcAccountToList(it.id, it.name)); - (myMSALObj.getAllAccounts() || []).sort((a, b) => a.username.localeCompare(b.username)).forEach(msAccount => { - let mcAcc = findAccountByMs(msAccount.username) || {id: "MHF_Question", name: "..."}; - addMcAccountToList(mcAcc.id, mcAcc.name, msAccount.username); - }); + getActiveAccounts() + .filter(it => it instanceof MojangAccount) + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach(it => addMcAccountToList(it)); + getMicrosoftUsers() + .sort((a, b) => a.localeCompare(b)) + .forEach(username => { + let mcAcc = findAccountByMs(username); + if (!mcAcc) return; + addMcAccountToList(mcAcc); + }); } -function renderActions() { +export function renderActions() { actions.innerHTML = ""; - if (Notification.permission == "default") { + if (Notification.permission === "default") { actions.innerHTML += '

Enable notifications

'; - $("#notificate").on("click", e => Notification.requestPermission().then(renderActions)); // i'm lazy + $("#notificate").on("click", () => Notification.requestPermission().then(renderActions)); // i'm lazy } if (listenVisible) { if (mcIdUsername != null && mcauth_code != null) { addAction("Listen to " + mcIdUsername, () => { - socket.send(JSON.stringify({ + sendSocket(JSON.stringify({ "action": "minecraft_id_login", "username": mcIdUsername, - "code": mcauth_code})); + "code": mcauth_code + })); mcauth_code = null; renderActions(); }); @@ -111,7 +163,7 @@ function renderActions() { let callbackUrl = new URL(location); callbackUrl.search = ""; callbackUrl.hash = "#username=" + encodeURIComponent(user); - location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user) + "?callback=" + encodeURIComponent(callbackUrl); }); addAction("Listen to frontend offline login in VIAaaS instance", () => { @@ -126,13 +178,13 @@ function renderActions() { function onWorkerMsg(e) { console.log(e); - if (e.data.action == "completed_pow") onCompletedPoW(e); + if (e.data.action === "completed_pow") onCompletedPoW(e); } function onCompletedPoW(e) { addToast("Offline username", "Completed proof of work"); workers.forEach(it => it.postMessage({action: "cancel", id: e.data.id})); - socket.send(e.data.msg); + sendSocket(e.data.msg); } function addAction(text, onClick) { @@ -145,7 +197,7 @@ function addAction(text, onClick) { actions.appendChild(p); } -function addListeningList(user, token) { +export function addListeningList(user, token) { let p = document.createElement("p"); let head = document.createElement("img"); let n = document.createElement("span"); @@ -158,7 +210,7 @@ function addListeningList(user, token) { listening.removeChild(p); unlisten(user); }; - head.width = "24"; + head.width = 24; head.alt = user + "'s head"; head.src = "https://crafthead.net/helm/" + user; p.append(head); @@ -167,7 +219,7 @@ function addListeningList(user, token) { listening.appendChild(p); } -function addToast(title, msg) { +export function addToast(title, msg) { let toast = document.createElement("div"); document.getElementById("toasts").prepend(toast); $(toast) @@ -188,7 +240,7 @@ function addToast(title, msg) { new bootstrap.Toast(toast).show(); } -function resetHtml() { +export function resetHtml() { listening.innerHTML = ""; listenVisible = false; renderActions(); @@ -196,13 +248,16 @@ function resetHtml() { function ohNo() { try { - icanhazepoch().then(sec => { - if (Math.abs(Date.now() / 1000 - sec) > 15) { - addToast("Time isn't synchronized", "Please synchronize your computer time to NTP servers"); - } else { - console.log("time seems synchronized"); - } - }) - new Date().getDay() == 3 && console.log("it's snapshot day 🐸 my dudes"); new Date().getDate() == 1 && new Date().getMonth() == 3 && addToast("WARNING", "Your ViaVersion has expired, please renew it at https://viaversion.com/ for $99"); - } catch (e) { console.log(e); } + icanhazepoch().then(sec => { + if (Math.abs(Date.now() / 1000 - sec) > 15) { + addToast("Time isn't synchronized", "Please synchronize your computer time to NTP servers"); + } else { + console.log("time seems synchronized"); + } + }) + new Date().getDay() === 3 && console.log("it's snapshot day 🐸 my dudes"); + new Date().getDate() === 1 && new Date().getMonth() === 3 && addToast("WARNING", "Your ViaVersion has expired, please renew it at https://viaversion.com/ for $99"); + } catch (e) { + console.log(e); + } } diff --git a/src/main/resources/web/js/util.js b/src/main/resources/web/js/util.js index 873b8c3..b58930c 100644 --- a/src/main/resources/web/js/util.js +++ b/src/main/resources/web/js/util.js @@ -1,24 +1,30 @@ -function isSuccess(status) { +import {getCorsProxy} from "./cors_proxy.js"; + +export function isSuccess(status) { return status >= 200 && status < 300; } -function checkFetchSuccess(msg) { +export function checkFetchSuccess(msg) { return r => { if (!isSuccess(r.status)) throw r.status + " " + msg; return r; }; } -function icanhazip(cors) { +export function icanhazip(cors) { return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com") .then(checkFetchSuccess("code")) .then(r => r.text()) .then(it => it.trim()); } -function icanhazepoch() { +export function icanhazepoch() { return fetch("https://icanhazepoch.com") .then(checkFetchSuccess("code")) .then(r => r.text()) .then(it => parseInt(it.trim())) +} + +export function filterNot(array, item) { + return array.filter(it => it !== item); } \ No newline at end of file diff --git a/src/main/resources/web/js/websocket.js b/src/main/resources/web/js/websocket.js index ead5810..fca033d 100644 --- a/src/main/resources/web/js/websocket.js +++ b/src/main/resources/web/js/websocket.js @@ -1,23 +1,30 @@ -var wsUrl = getWsUrl(); -var socket = null; +import {authNotification} from "./notification.js"; +import {checkFetchSuccess} from "./util.js"; +import {findAccountByMcName} from "./account_manager.js"; +import {addListeningList, addToast, renderActions, resetHtml, setListenVisible, setWsStatus} from "./page.js"; + +let wsUrl = getWsUrl(); +let socket = null; // WS url function defaultWs() { let url = new URL("ws", new URL(location)); url.protocol = "wss"; - return window.location.host == "viaversion.github.io" || !window.location.host ? "wss://localhost:25543/ws" : url.toString(); + return window.location.host.endsWith("github.io") || !window.location.protocol.startsWith("http") + ? "wss://localhost:25543/ws" : url.toString(); } -function getWsUrl() { +export function getWsUrl() { return localStorage.getItem("viaaas_ws_url") || defaultWs(); } -function setWsUrl(url) { + +export function setWsUrl(url) { localStorage.setItem("viaaas_ws_url", url); location.reload(); } // Tokens -function saveToken(token) { +export function saveToken(token) { let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {}; let tokens = getTokens(); tokens.push(token); @@ -25,28 +32,28 @@ function saveToken(token) { localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens)); } -function removeToken(token) { +export function removeToken(token) { let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {}; let tokens = getTokens(); - tokens = tokens.filter(it => it != token); + tokens = tokens.filter(it => it !== token); hTokens[wsUrl] = tokens; localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens)); } -function getTokens() { +export function getTokens() { return (JSON.parse(localStorage.getItem("viaaas_tokens")) || {})[wsUrl] || []; } // Websocket -function listen(token) { +export function listen(token) { socket.send(JSON.stringify({"action": "listen_login_requests", "token": token})); } -function unlisten(id) { +export function unlisten(id) { socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id})); } -function confirmJoin(hash) { +export function confirmJoin(hash) { socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash})); } @@ -56,12 +63,10 @@ function handleJoinRequest(parsed) { + parsed.message.split(/[\r\n]+/).map(it => "> " + it).join('\n'), () => { let account = findAccountByMcName(parsed.user); if (account) { - getMcUserToken(account).then(data => { - return joinGame(data.accessToken, data.id, parsed.session_hash); - }) - .then(checkFetchSuccess("code")) - .finally(() => confirmJoin(parsed.session_hash)) - .catch((e) => addToast("Couldn't contact session server", "Error: " + e)); + account.joinGame(parsed.session_hash) + .then(checkFetchSuccess("code")) + .finally(() => confirmJoin(parsed.session_hash)) + .catch((e) => addToast("Couldn't contact session server", "Error: " + e)); } else { confirmJoin(parsed.session_hash); addToast("Couldn't find account", "Couldn't find " + parsed.user + ", check Accounts tab"); @@ -71,51 +76,51 @@ function handleJoinRequest(parsed) { function onSocketMsg(event) { let parsed = JSON.parse(event.data); - if (parsed.action == "ad_minecraft_id_login") { - listenVisible = true; + if (parsed.action === "ad_minecraft_id_login") { + setListenVisible(true); renderActions(); - } else if (parsed.action == "login_result") { + } else if (parsed.action === "login_result") { if (!parsed.success) { addToast("Couldn't verify Minecraft account", "VIAaaS returned failed response"); } else { listen(parsed.token); saveToken(parsed.token); } - } else if (parsed.action == "listen_login_requests_result") { + } else if (parsed.action === "listen_login_requests_result") { if (parsed.success) { addListeningList(parsed.user, parsed.token); } else { removeToken(parsed.token); } - } else if (parsed.action == "session_hash_request") { + } else if (parsed.action === "session_hash_request") { handleJoinRequest(parsed); } } -function listenStoredTokens() { +export function listenStoredTokens() { getTokens().forEach(listen); } function onConnect() { - connectionStatus.innerText = "connected"; + setWsStatus("connected"); resetHtml(); listenStoredTokens(); } function onWsError(e) { console.log(e); - connectionStatus.innerText = "socket error"; + setWsStatus("socket error"); resetHtml(); } function onDisconnect(evt) { - connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason; + setWsStatus("disconnected with close code " + evt.code + " and reason: " + evt.reason); resetHtml(); setTimeout(connect, 5000); } -function connect() { - connectionStatus.innerText = "connecting..."; +export function connect() { + setWsStatus("connecting..."); socket = new WebSocket(wsUrl); socket.onerror = onWsError; @@ -123,3 +128,11 @@ function connect() { socket.onclose = onDisconnect socket.onmessage = onSocketMsg; } + +export function sendSocket(msg) { + if (!socket) { + console.error("couldn't send msg, socket isn't set"); + return + } + socket.send(msg); +} \ No newline at end of file diff --git a/src/main/resources/web/js/worker.js b/src/main/resources/web/js/worker.js index 995adb6..69779db 100644 --- a/src/main/resources/web/js/worker.js +++ b/src/main/resources/web/js/worker.js @@ -1,43 +1,43 @@ importScripts("https://cdnjs.cloudflare.com/ajax/libs/js-sha512/0.8.0/sha512.min.js"); -var pending = []; +let pending = []; -onmessage = function(e) { - if (e.data.action == "listen_pow") startPoW(e); - if (e.data.action == "cancel") removePending(e.data.id); +onmessage = function (e) { + if (e.data.action === "listen_pow") startPoW(e); + if (e.data.action === "cancel") removePending(e.data.id); } function removePending(id) { - console.log("removing task" + id); - pending = pending.filter(it => it != id); + console.log("removing task" + id); + pending = pending.filter(it => it !== id); } function startPoW(e) { - pending.push(e.data.id); - listenPoW(e); + pending.push(e.data.id); + listenPoW(e); } function listenPoW(e) { - var user = e.data.user; - let msg = null; - var endTime = Date.now() + 1000; - do { - if (!pending.includes(e.data.id)) return; // cancelled + let user = e.data.user; + let msg = null; + let endTime = Date.now() + 1000; + do { + if (!pending.includes(e.data.id)) return; // cancelled - msg = JSON.stringify({ - action: "offline_login", - username: user, - date: Date.now(), - rand: Math.random() - }); + msg = JSON.stringify({ + action: "offline_login", + username: user, + date: Date.now(), + rand: Math.random() + }); - if (Date.now() >= endTime) { - setTimeout(() => listenPoW(e), 0); - return; - } - } while (!sha512(msg).startsWith("00000")); + if (Date.now() >= endTime) { + setTimeout(() => listenPoW(e), 0); + return; + } + } while (!sha512(msg).startsWith("00000")); - postMessage({id: e.data.id, action: "completed_pow", msg: msg}); + postMessage({id: e.data.id, action: "completed_pow", msg: msg}); } /* function sha512(s) {