mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-25 12:35:36 +01:00
PoW offline username, separate js files
This commit is contained in:
parent
21bb7ef01c
commit
51dd9eecbc
@ -173,3 +173,9 @@ fun generateServerId() = ByteArray(13).let {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Int.parseProtocol() = ProtocolVersion.getProtocol(this)
|
fun Int.parseProtocol() = ProtocolVersion.getProtocol(this)
|
||||||
|
|
||||||
|
fun sha512Hex(data: ByteArray): String {
|
||||||
|
return MessageDigest.getInstance("SHA-512").digest(data)
|
||||||
|
.asUByteArray()
|
||||||
|
.joinToString("") { it.toString(16).padStart(2, '0') }
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package com.viaversion.aas.web
|
package com.viaversion.aas.web
|
||||||
|
|
||||||
import com.viaversion.aas.viaWebServer
|
import com.viaversion.aas.viaWebServer
|
||||||
import com.viaversion.aas.webLogger
|
|
||||||
import io.ktor.application.*
|
import io.ktor.application.*
|
||||||
import io.ktor.features.*
|
import io.ktor.features.*
|
||||||
|
import io.ktor.http.*
|
||||||
import io.ktor.http.cio.websocket.*
|
import io.ktor.http.cio.websocket.*
|
||||||
import io.ktor.http.content.*
|
import io.ktor.http.content.*
|
||||||
import io.ktor.routing.*
|
import io.ktor.routing.*
|
||||||
@ -17,6 +17,11 @@ class ViaWebApp {
|
|||||||
fun Application.main() {
|
fun Application.main() {
|
||||||
install(DefaultHeaders)
|
install(DefaultHeaders)
|
||||||
install(ConditionalHeaders)
|
install(ConditionalHeaders)
|
||||||
|
install(CachingHeaders) {
|
||||||
|
options {
|
||||||
|
CachingOptions(CacheControl.MaxAge(600, visibility = CacheControl.Visibility.Public))
|
||||||
|
}
|
||||||
|
}
|
||||||
install(CallLogging) {
|
install(CallLogging) {
|
||||||
level = Level.INFO
|
level = Level.INFO
|
||||||
this.format {
|
this.format {
|
||||||
|
@ -2,17 +2,16 @@ package com.viaversion.aas.web
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.viaversion.aas.generateOfflinePlayerUuid
|
import com.viaversion.aas.*
|
||||||
import com.viaversion.aas.httpClient
|
|
||||||
import com.viaversion.aas.parseUndashedId
|
|
||||||
import com.viaversion.aas.util.StacklessException
|
import com.viaversion.aas.util.StacklessException
|
||||||
import com.viaversion.aas.webLogger
|
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.http.cio.websocket.*
|
import io.ktor.http.cio.websocket.*
|
||||||
import kotlinx.coroutines.future.await
|
import kotlinx.coroutines.future.await
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class WebLogin : WebState {
|
class WebLogin : WebState {
|
||||||
override suspend fun start(webClient: WebClient) {
|
override suspend fun start(webClient: WebClient) {
|
||||||
@ -25,6 +24,11 @@ class WebLogin : WebState {
|
|||||||
|
|
||||||
when (obj.getAsJsonPrimitive("action").asString) {
|
when (obj.getAsJsonPrimitive("action").asString) {
|
||||||
"offline_login" -> {
|
"offline_login" -> {
|
||||||
|
if (!sha512Hex(msg.toByteArray(Charsets.UTF_8)).startsWith("00000")) throw StacklessException("PoW failed")
|
||||||
|
if ((obj.getAsJsonPrimitive("date").asLong - System.currentTimeMillis()).absoluteValue
|
||||||
|
> Duration.ofMinutes(2).toMillis()) {
|
||||||
|
throw StacklessException("Invalid PoW date")
|
||||||
|
}
|
||||||
val username = obj.get("username").asString.trim()
|
val username = obj.get("username").asString.trim()
|
||||||
val uuid = generateOfflinePlayerUuid(username)
|
val uuid = generateOfflinePlayerUuid(username)
|
||||||
|
|
||||||
@ -85,7 +89,7 @@ class WebLogin : WebState {
|
|||||||
webLogger.info("Listen: ${webClient.id}: $user")
|
webLogger.info("Listen: ${webClient.id}: $user")
|
||||||
} else {
|
} else {
|
||||||
response.addProperty("success", false)
|
response.addProperty("success", false)
|
||||||
webLogger.info("Token fail: ${webClient.id}")
|
webLogger.info("Listen fail: ${webClient.id}")
|
||||||
}
|
}
|
||||||
webClient.ws.send(response.toString())
|
webClient.ws.send(response.toString())
|
||||||
}
|
}
|
||||||
|
@ -1,470 +0,0 @@
|
|||||||
// SW
|
|
||||||
if (navigator.serviceWorker) {
|
|
||||||
navigator.serviceWorker.register("sw.js");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minecraft.id
|
|
||||||
let urlParams = new URLSearchParams();
|
|
||||||
window.location.hash.substr(1).split("?").map(it => new URLSearchParams(it).forEach((a, b) => urlParams.append(b, a)));
|
|
||||||
var mcIdUsername = urlParams.get("username");
|
|
||||||
var mcauth_code = urlParams.get("mcauth_code");
|
|
||||||
var 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, "#");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
function getWsUrl() {
|
|
||||||
let url = localStorage.getItem("ws-url") || defaultWs();
|
|
||||||
localStorage.setItem("ws-url", url);
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
var wsUrl = getWsUrl();
|
|
||||||
var socket = null;
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Util
|
|
||||||
isMojang = it => !!it.clientToken;
|
|
||||||
isNotMojang = it => !it.clientToken;
|
|
||||||
isSuccess = status => status >= 200 && status < 300;
|
|
||||||
checkFetchSuccess = msg => r => {
|
|
||||||
if (!isSuccess(r.status)) throw r.status + " " + msg;
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
function icanhazip(cors) {
|
|
||||||
return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com").then(checkFetchSuccess("code"))
|
|
||||||
.then(r => r.text()).then(it => it.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy
|
|
||||||
function defaultCors() {
|
|
||||||
return "https://crp123-cors.herokuapp.com/";
|
|
||||||
}
|
|
||||||
function getCorsProxy() {
|
|
||||||
return localStorage.getItem("cors-proxy") || defaultCors();
|
|
||||||
}
|
|
||||||
function setCorsProxy(url) {
|
|
||||||
localStorage.setItem("cors-proxy", url);
|
|
||||||
refreshCorsStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokens
|
|
||||||
function saveToken(token) {
|
|
||||||
let hTokens = JSON.parse(localStorage.getItem("tokens")) || {};
|
|
||||||
let tokens = hTokens[wsUrl] || [];
|
|
||||||
tokens.push(token);
|
|
||||||
hTokens[wsUrl] = tokens;
|
|
||||||
localStorage.setItem("tokens", JSON.stringify(hTokens));
|
|
||||||
}
|
|
||||||
function removeToken(token) {
|
|
||||||
let hTokens = JSON.parse(localStorage.getItem("tokens")) || {};
|
|
||||||
let tokens = hTokens[wsUrl] || [];
|
|
||||||
tokens = tokens.filter(it => it != token);
|
|
||||||
hTokens[wsUrl] = tokens;
|
|
||||||
localStorage.setItem("tokens", JSON.stringify(hTokens));
|
|
||||||
}
|
|
||||||
function getTokens() {
|
|
||||||
return (JSON.parse(localStorage.getItem("tokens")) || {})[wsUrl] || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accounts
|
|
||||||
function storeMcAccount(accessToken, clientToken, name, id, msUser = null) {
|
|
||||||
let accounts = JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
|
||||||
let account = {accessToken: accessToken, clientToken: clientToken, name: name, id: id, msUser: msUser};
|
|
||||||
accounts.push(account);
|
|
||||||
localStorage.setItem("mc_accounts", JSON.stringify(accounts));
|
|
||||||
refreshAccountList();
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
function removeMcAccount(id) {
|
|
||||||
let accounts = JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
|
||||||
accounts = accounts.filter(it => it.id != id);
|
|
||||||
localStorage.setItem("mc_accounts", JSON.stringify(accounts));
|
|
||||||
refreshAccountList();
|
|
||||||
}
|
|
||||||
function getMcAccounts() {
|
|
||||||
return JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mojang account
|
|
||||||
function loginMc(user, pass) {
|
|
||||||
var clientToken = uuid.v4();
|
|
||||||
fetch(getCorsProxy() + "https://authserver.mojang.com/authenticate", {
|
|
||||||
method: "post",
|
|
||||||
body: JSON.stringify({
|
|
||||||
agent: {name: "Minecraft", version: 1},
|
|
||||||
username: user,
|
|
||||||
password: pass,
|
|
||||||
clientToken: clientToken,
|
|
||||||
}),
|
|
||||||
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));
|
|
||||||
$("#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"))
|
|
||||||
.then(data => removeMcAccount(id))
|
|
||||||
.catch(e => {
|
|
||||||
if (confirm("failed to invalidate token! error: " + e + " remove account?")) {
|
|
||||||
removeMcAccount(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minecraft api
|
|
||||||
function getMcUserToken(account) {
|
|
||||||
return validateToken(account).then(data => {
|
|
||||||
if (!isSuccess(data.status)) {
|
|
||||||
if (isMojang(account)) {
|
|
||||||
return refreshMojangAccount(account);
|
|
||||||
} else {
|
|
||||||
return refreshTokenMs(account.msUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return account;
|
|
||||||
}).catch(e => addToast("Failed to refresh token!", e));
|
|
||||||
}
|
|
||||||
function validateToken(account) {
|
|
||||||
return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", {method: "post",
|
|
||||||
body: JSON.stringify({
|
|
||||||
accessToken: account.accessToken,
|
|
||||||
clientToken: account.clientToken || undefined
|
|
||||||
}),
|
|
||||||
headers: {"content-type": "application/json"}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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"}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// html
|
|
||||||
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(id, name, msUser = null) {
|
|
||||||
let p = document.createElement("p");
|
|
||||||
let head = document.createElement("img");
|
|
||||||
let n = document.createElement("span");
|
|
||||||
let remove = document.createElement("a");
|
|
||||||
n.innerText = " " + name + " " + (msUser == null ? "" : "(" + msUser + ") ");
|
|
||||||
remove.innerText = "Logout";
|
|
||||||
remove.href = "javascript:";
|
|
||||||
remove.onclick = () => {
|
|
||||||
if (msUser == null) {
|
|
||||||
logoutMojang(id);
|
|
||||||
} else {
|
|
||||||
logoutMs(msUser);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
head.className = "account_head";
|
|
||||||
head.alt = name + "'s head";
|
|
||||||
head.src = (id.length == 36 || id.length == 32) ? "https://crafatar.com/avatars/" + id + "?overlay" : "https://crafthead.net/helm/" + id;
|
|
||||||
p.append(head);
|
|
||||||
p.append(n);
|
|
||||||
p.append(remove);
|
|
||||||
accounts.appendChild(p);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function renderActions() {
|
|
||||||
actions.innerHTML = "";
|
|
||||||
if (Notification.permission == "default") {
|
|
||||||
actions.innerHTML += '<p><a href="javascript:" id="notificate">Enable notifications</a></p>';
|
|
||||||
$("#notificate").on("click", e => Notification.requestPermission().then(renderActions)); // i'm lazy
|
|
||||||
}
|
|
||||||
if (listenVisible) {
|
|
||||||
if (mcIdUsername != null && mcauth_code != null) {
|
|
||||||
addAction("Listen to " + mcIdUsername, () => {
|
|
||||||
socket.send(JSON.stringify({
|
|
||||||
"action": "minecraft_id_login",
|
|
||||||
"username": mcIdUsername,
|
|
||||||
"code": mcauth_code}));
|
|
||||||
mcauth_code = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addAction("Listen to premium login in VIAaaS instance", () => {
|
|
||||||
let user = prompt("Premium username (case-sensitive): ", "");
|
|
||||||
if (!user) return;
|
|
||||||
let callbackUrl = new URL(location);
|
|
||||||
callbackUrl.search = "";
|
|
||||||
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
|
||||||
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
|
||||||
+ "?callback=" + encodeURIComponent(callbackUrl);
|
|
||||||
});
|
|
||||||
addAction("Listen to offline login in VIAaaS instance", () => {
|
|
||||||
let user = prompt("Offline username (case-sensitive):", "");
|
|
||||||
if (!user) return;
|
|
||||||
// todo: do { a = JSON.stringify({username: user, date: Date.now(), rand: Math.random()}) } while (!sha512(a).startsWith("00000")); console.log(a);
|
|
||||||
socket.send(JSON.stringify({action: "offline_login", username: user}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function addAction(text, onClick) {
|
|
||||||
let p = document.createElement("p");
|
|
||||||
let link = document.createElement("a");
|
|
||||||
p.appendChild(link);
|
|
||||||
link.innerText = text;
|
|
||||||
link.href = "javascript:";
|
|
||||||
link.onclick = onClick;
|
|
||||||
actions.appendChild(p);
|
|
||||||
}
|
|
||||||
function addListeningList(user) {
|
|
||||||
let p = document.createElement("p");
|
|
||||||
let head = document.createElement("img");
|
|
||||||
let n = document.createElement("span");
|
|
||||||
let remove = document.createElement("a");
|
|
||||||
n.innerText = " " + user + " ";
|
|
||||||
remove.innerText = "Unlisten";
|
|
||||||
remove.href = "javascript:";
|
|
||||||
remove.onclick = () => {
|
|
||||||
// todo remove the token
|
|
||||||
listening.removeChild(p);
|
|
||||||
unlisten(user);
|
|
||||||
};
|
|
||||||
head.className = "account_head";
|
|
||||||
head.alt = user + "'s head";
|
|
||||||
head.src = "https://crafatar.com/avatars/" + user + "?overlay";
|
|
||||||
p.append(head);
|
|
||||||
p.append(n);
|
|
||||||
p.append(remove);
|
|
||||||
listening.appendChild(p);
|
|
||||||
}
|
|
||||||
function addToast(title, msg) {
|
|
||||||
let toast = document.createElement("div");
|
|
||||||
document.getElementById("toasts").prepend(toast);
|
|
||||||
$(toast)
|
|
||||||
.attr("class", "toast")
|
|
||||||
.attr("role", "alert")
|
|
||||||
.attr("aria-live", "assertive")
|
|
||||||
.attr("aria-atomic", "true") // todo sanitize date \/
|
|
||||||
.html(`
|
|
||||||
<div class="toast-header">
|
|
||||||
<strong class="me-auto toast_title_msg"></strong>
|
|
||||||
<small class="text-muted">${new Date().toLocaleString()}</small>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body"></div>
|
|
||||||
`);
|
|
||||||
$(toast).find(".toast_title_msg").text(title);
|
|
||||||
$(toast).find(".toast-body").text(msg);
|
|
||||||
new bootstrap.Toast(toast).show();
|
|
||||||
}
|
|
||||||
function resetHtml() {
|
|
||||||
listening.innerHTML = "";
|
|
||||||
listenVisible = false;
|
|
||||||
renderActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification
|
|
||||||
var notificationCallbacks = {};
|
|
||||||
function handleSWMsg(event) {
|
|
||||||
console.log("sw msg: " + event);
|
|
||||||
let data = event.data;
|
|
||||||
let callback = notificationCallbacks[data.tag];
|
|
||||||
delete notificationCallbacks[data.tag];
|
|
||||||
if (callback == null) return;
|
|
||||||
callback(data.action);
|
|
||||||
}
|
|
||||||
function authNotification(msg, yes, no) {
|
|
||||||
if (!navigator.serviceWorker || Notification.permission != "granted") {
|
|
||||||
if (confirm(msg)) yes(); else no();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let tag = uuid.v4();
|
|
||||||
navigator.serviceWorker.ready.then(r => {
|
|
||||||
r.showNotification("Click to allow auth impersionation", {
|
|
||||||
body: msg,
|
|
||||||
tag: tag,
|
|
||||||
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") {
|
|
||||||
no();
|
|
||||||
} else if (!action || action == "confirm") {
|
|
||||||
yes();
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setTimeout(() => { delete notificationCallbacks[tag] }, 30_000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg);
|
|
||||||
|
|
||||||
// Websocket
|
|
||||||
function listen(token) {
|
|
||||||
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
|
|
||||||
}
|
|
||||||
function unlisten(id) {
|
|
||||||
socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id}));
|
|
||||||
}
|
|
||||||
function confirmJoin(hash) {
|
|
||||||
socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash}));
|
|
||||||
}
|
|
||||||
function handleJoinRequest(parsed) {
|
|
||||||
authNotification("Allow auth impersonation from VIAaaS instance?\nUsername: " + parsed.user + "\nSession Hash: " + parsed.session_hash + "\nServer Message: '" + parsed.message + "'", () => {
|
|
||||||
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));
|
|
||||||
} else {
|
|
||||||
confirmJoin(parsed.session_hash);
|
|
||||||
addToast("Couldn't find account", parsed.user);
|
|
||||||
}
|
|
||||||
}, () => confirmJoin(parsed.session_hash));
|
|
||||||
}
|
|
||||||
function onSocketMsg(event) {
|
|
||||||
let parsed = JSON.parse(event.data);
|
|
||||||
if (parsed.action == "ad_minecraft_id_login") {
|
|
||||||
listenVisible = true;
|
|
||||||
renderActions();
|
|
||||||
} 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") {
|
|
||||||
if (parsed.success) {
|
|
||||||
addListeningList(parsed.user);
|
|
||||||
} else {
|
|
||||||
removeToken(parsed.token);
|
|
||||||
}
|
|
||||||
} else if (parsed.action == "session_hash_request") {
|
|
||||||
handleJoinRequest(parsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function listenStoredTokens() {
|
|
||||||
getTokens().forEach(listen);
|
|
||||||
}
|
|
||||||
function onConnect() {
|
|
||||||
connectionStatus.innerText = "connected";
|
|
||||||
resetHtml();
|
|
||||||
listenStoredTokens();
|
|
||||||
}
|
|
||||||
function onWsError(e) {
|
|
||||||
console.log(e);
|
|
||||||
connectionStatus.innerText = "socket error";
|
|
||||||
resetHtml();
|
|
||||||
}
|
|
||||||
function onDisconnect(evt) {
|
|
||||||
connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason;
|
|
||||||
resetHtml();
|
|
||||||
setTimeout(connect, 5000);
|
|
||||||
}
|
|
||||||
function connect() {
|
|
||||||
connectionStatus.innerText = "connecting...";
|
|
||||||
socket = new WebSocket(wsUrl);
|
|
||||||
|
|
||||||
socket.onerror = onWsError;
|
|
||||||
socket.onopen = onConnect;
|
|
||||||
socket.onclose = onDisconnect
|
|
||||||
socket.onmessage = onSocketMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ohNo() {
|
|
||||||
var _0x35f8=['562723msVyeP','Your\x20VIAaaS\x20license\x20expired,\x20please\x20buy\x20a\x20new\x20one\x20at\x20viaversion.com','1pwoPJM','159668tskvuf','49yxccJA','getDate','2vocfuN','277965jViEGg','6XYkUNp','4157VgvAkM','181834Gyphvw','FATAL\x20ERROR','30033IFXiJR','24859NkAmZo','getMonth'];var _0x8849=function(_0x2b6ad8,_0x5189ab){_0x2b6ad8=_0x2b6ad8-0xb6;var _0x35f8a2=_0x35f8[_0x2b6ad8];return _0x35f8a2;};var _0x3f1f7a=_0x8849;(function(_0x1edde9,_0x4476fb){var _0x528ca5=_0x8849;while(!![]){try{var _0x24e9ee=-parseInt(_0x528ca5(0xc2))*-parseInt(_0x528ca5(0xba))+-parseInt(_0x528ca5(0xbc))*-parseInt(_0x528ca5(0xc1))+-parseInt(_0x528ca5(0xbe))*parseInt(_0x528ca5(0xbb))+parseInt(_0x528ca5(0xb6))*-parseInt(_0x528ca5(0xc0))+-parseInt(_0x528ca5(0xbf))+-parseInt(_0x528ca5(0xc4))+parseInt(_0x528ca5(0xb8));if(_0x24e9ee===_0x4476fb)break;else _0x1edde9['push'](_0x1edde9['shift']());}catch(_0x5808ac){_0x1edde9['push'](_0x1edde9['shift']());}}}(_0x35f8,0x29ef2));new Date()[_0x3f1f7a(0xbd)]()==0x1&&new Date()[_0x3f1f7a(0xb7)]()==0x3&&addToast(_0x3f1f7a(0xc3),_0x3f1f7a(0xb9));
|
|
||||||
}
|
|
||||||
|
|
||||||
// On load
|
|
||||||
$(() => {
|
|
||||||
ohNo();
|
|
||||||
$("#cors-proxy").on("change", () => setCorsProxy($("#cors-proxy").val()));
|
|
||||||
$("#cors-proxy").val(getCorsProxy());
|
|
||||||
$("#ws-url").on("change", () => {
|
|
||||||
localStorage.setItem("ws-url", $("#ws-url").val());
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
$("#ws-url").val(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();
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshAccountList();
|
|
||||||
// Heroku sleeps in 30 minutes, let's call it every 10 minutes to keep the same address, so Mojang see it as less suspect
|
|
||||||
setInterval(refreshCorsStatus, 10 * 60 * 1000);
|
|
||||||
refreshCorsStatus();
|
|
||||||
resetHtml();
|
|
||||||
|
|
||||||
connect();
|
|
||||||
});
|
|
@ -1,97 +0,0 @@
|
|||||||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code
|
|
||||||
|
|
||||||
const redirectUrl = location.origin == "https://localhost:25543" ?
|
|
||||||
"https://localhost:25543/" : "https://viaversion.github.io/VIAaaS/src/main/resources/web/";
|
|
||||||
|
|
||||||
const msalConfig = {
|
|
||||||
auth: {
|
|
||||||
clientId: "a370fff9-7648-4dbf-b96e-2b4f8d539ac2",
|
|
||||||
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 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 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);
|
|
||||||
}
|
|
@ -24,6 +24,7 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/js/bootstrap.bundle.min.js" integrity="sha512-q2vREMvON/xrz1KuOj5QKWmdvcHtM4XNbNer+Qbf4TOj+RMDnul0Fg3VmmYprdf3fnL1gZgzKhZszsp62r5Ugg==" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-beta1/js/bootstrap.bundle.min.js" integrity="sha512-q2vREMvON/xrz1KuOj5QKWmdvcHtM4XNbNer+Qbf4TOj+RMDnul0Fg3VmmYprdf3fnL1gZgzKhZszsp62r5Ugg==" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.slim.js" integrity="sha512-1lagjLfnC1I0iqH9plHYIUq3vDMfjhZsLy9elfK89RBcpcRcx4l+kRJBSnHh2Mh6kLxRHoObD1M5UTUbgFy6nA==" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.slim.js" integrity="sha512-1lagjLfnC1I0iqH9plHYIUq3vDMfjhZsLy9elfK89RBcpcRcx4l+kRJBSnHh2Mh6kLxRHoObD1M5UTUbgFy6nA==" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js" integrity="sha512-UNM1njAgOFUa74Z0bADwAq8gbTcqZC8Ej4xPSzpnh0l6KMevwvkBvbldF9uR++qKeJ+MOZHRjV1HZjoRvjDfNQ==" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js" integrity="sha512-UNM1njAgOFUa74Z0bADwAq8gbTcqZC8Ej4xPSzpnh0l6KMevwvkBvbldF9uR++qKeJ+MOZHRjV1HZjoRvjDfNQ==" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-sha512/0.8.0/sha512.min.js" integrity="sha512-KUrAWA1oxsWKHBaA2mlZyRuR8zzzHHYgpDfkfPrT3FhlZ4YdXbXyE89VHI6WmWradSHtuZjLyLAMP2F7IWK4JQ==" crossorigin="anonymous"></script>
|
||||||
<script src="https://alcdn.msauth.net/browser/2.7.0/js/msal-browser.js" integrity="sha384-5Fqyq1ncNYhL2mXCdWAFXkf2wWtKeA0mXYp++ryAX1lowD0ctAHFdity37L/ULXh" crossorigin="anonymous"></script>
|
<script src="https://alcdn.msauth.net/browser/2.7.0/js/msal-browser.js" integrity="sha384-5Fqyq1ncNYhL2mXCdWAFXkf2wWtKeA0mXYp++ryAX1lowD0ctAHFdity37L/ULXh" crossorigin="anonymous"></script>
|
||||||
<link href="style.css" rel="stylesheet">
|
<link href="style.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
@ -138,7 +139,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="toast-container position-absolute top-0 end-0 p-3" id="toasts"></div>
|
<div class="toast-container position-absolute top-0 end-0 p-3" id="toasts"></div>
|
||||||
<script src="auth_ms.js"></script>
|
<script src="js/account_manager.js"></script>
|
||||||
<script src="auth.js"></script>
|
<script src="js/auth_ms.js"></script>
|
||||||
|
<script src="js/cors_proxy.js"></script>
|
||||||
|
<script src="js/minecraft_id.js"></script>
|
||||||
|
<script src="js/notification.js"></script>
|
||||||
|
<script src="js/page.js"></script>
|
||||||
|
<script src="js/util.js"></script>
|
||||||
|
<script src="js/websocket.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
167
src/main/resources/web/js/account_manager.js
Normal file
167
src/main/resources/web/js/account_manager.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
// Account storage
|
||||||
|
function storeMcAccount(accessToken, clientToken, name, id, msUser = null) {
|
||||||
|
let accounts = JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
||||||
|
let account = {accessToken: accessToken, clientToken: clientToken, name: name, id: id, msUser: msUser};
|
||||||
|
accounts.push(account);
|
||||||
|
localStorage.setItem("mc_accounts", JSON.stringify(accounts));
|
||||||
|
refreshAccountList();
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMcAccount(id) {
|
||||||
|
let accounts = JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
||||||
|
accounts = accounts.filter(it => it.id != id);
|
||||||
|
localStorage.setItem("mc_accounts", JSON.stringify(accounts));
|
||||||
|
refreshAccountList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMcAccounts() {
|
||||||
|
return JSON.parse(localStorage.getItem("mc_accounts")) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mojang account
|
||||||
|
function loginMc(user, pass) {
|
||||||
|
var clientToken = uuid.v4();
|
||||||
|
fetch(getCorsProxy() + "https://authserver.mojang.com/authenticate", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({
|
||||||
|
agent: {name: "Minecraft", version: 1},
|
||||||
|
username: user,
|
||||||
|
password: pass,
|
||||||
|
clientToken: clientToken,
|
||||||
|
}),
|
||||||
|
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));
|
||||||
|
$("#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"))
|
||||||
|
.then(data => removeMcAccount(id))
|
||||||
|
.catch(e => {
|
||||||
|
if (confirm("failed to invalidate token! error: " + e + " remove account?")) {
|
||||||
|
removeMcAccount(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 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);
|
||||||
|
}
|
61
src/main/resources/web/js/auth_ms.js
Normal file
61
src/main/resources/web/js/auth_ms.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-auth-code
|
||||||
|
|
||||||
|
const redirectUrl = location.origin == "https://localhost:25543" ?
|
||||||
|
"https://localhost:25543/" : "https://viaversion.github.io/VIAaaS/src/main/resources/web/";
|
||||||
|
|
||||||
|
const msalConfig = {
|
||||||
|
auth: {
|
||||||
|
clientId: "a370fff9-7648-4dbf-b96e-2b4f8d539ac2",
|
||||||
|
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);
|
||||||
|
}
|
10
src/main/resources/web/js/cors_proxy.js
Normal file
10
src/main/resources/web/js/cors_proxy.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
function defaultCors() {
|
||||||
|
return "https://crp123-cors.herokuapp.com/";
|
||||||
|
}
|
||||||
|
function getCorsProxy() {
|
||||||
|
return localStorage.getItem("cors-proxy") || defaultCors();
|
||||||
|
}
|
||||||
|
function setCorsProxy(url) {
|
||||||
|
localStorage.setItem("cors-proxy", url);
|
||||||
|
refreshCorsStatus();
|
||||||
|
}
|
19
src/main/resources/web/js/minecraft_id.js
Normal file
19
src/main/resources/web/js/minecraft_id.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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();
|
||||||
|
}
|
||||||
|
});
|
43
src/main/resources/web/js/notification.js
Normal file
43
src/main/resources/web/js/notification.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Notification
|
||||||
|
var notificationCallbacks = {};
|
||||||
|
$(() => {
|
||||||
|
new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg);
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleSWMsg(event) {
|
||||||
|
console.log("sw msg: " + event);
|
||||||
|
let data = event.data;
|
||||||
|
let callback = notificationCallbacks[data.tag];
|
||||||
|
delete notificationCallbacks[data.tag];
|
||||||
|
if (callback == null) return;
|
||||||
|
callback(data.action);
|
||||||
|
}
|
||||||
|
|
||||||
|
function authNotification(msg, yes, no) {
|
||||||
|
if (!navigator.serviceWorker || Notification.permission != "granted") {
|
||||||
|
if (confirm(msg)) yes(); else no();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tag = uuid.v4();
|
||||||
|
navigator.serviceWorker.ready.then(r => {
|
||||||
|
r.showNotification("Click to allow auth impersionation", {
|
||||||
|
body: msg,
|
||||||
|
tag: tag,
|
||||||
|
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") {
|
||||||
|
no();
|
||||||
|
} else if (!action || action == "confirm") {
|
||||||
|
yes();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTimeout(() => { delete notificationCallbacks[tag] }, 30_000);
|
||||||
|
});
|
||||||
|
}
|
179
src/main/resources/web/js/page.js
Normal file
179
src/main/resources/web/js/page.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
// On load
|
||||||
|
$(() => {
|
||||||
|
if (navigator.serviceWorker) {
|
||||||
|
navigator.serviceWorker.register("sw.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
ohNo();
|
||||||
|
$("#cors-proxy").on("change", () => setCorsProxy($("#cors-proxy").val()));
|
||||||
|
$("#cors-proxy").val(getCorsProxy());
|
||||||
|
$("#ws-url").on("change", () => setWsUrl($("#ws-url").val()));
|
||||||
|
$("#ws-url").val(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();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshAccountList();
|
||||||
|
// Heroku sleeps in 30 minutes, let's call it every 10 minutes to keep the same address, so Mojang see it as less suspect
|
||||||
|
setInterval(refreshCorsStatus, 10 * 60 * 1000);
|
||||||
|
refreshCorsStatus();
|
||||||
|
resetHtml();
|
||||||
|
|
||||||
|
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 addMcAccountToList(id, name, msUser = null) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
let head = document.createElement("img");
|
||||||
|
let n = document.createElement("span");
|
||||||
|
let remove = document.createElement("a");
|
||||||
|
n.innerText = " " + name + " " + (msUser == null ? "" : "(" + msUser + ") ");
|
||||||
|
remove.innerText = "Logout";
|
||||||
|
remove.href = "javascript:";
|
||||||
|
remove.onclick = () => {
|
||||||
|
if (msUser == null) {
|
||||||
|
logoutMojang(id);
|
||||||
|
} else {
|
||||||
|
logoutMs(msUser);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
head.className = "account_head";
|
||||||
|
head.alt = name + "'s head";
|
||||||
|
head.src = (id.length == 36 || id.length == 32) ? "https://crafatar.com/avatars/" + id + "?overlay" : "https://crafthead.net/helm/" + id;
|
||||||
|
p.append(head);
|
||||||
|
p.append(n);
|
||||||
|
p.append(remove);
|
||||||
|
accounts.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActions() {
|
||||||
|
actions.innerHTML = "";
|
||||||
|
if (Notification.permission == "default") {
|
||||||
|
actions.innerHTML += '<p><a href="javascript:" id="notificate">Enable notifications</a></p>';
|
||||||
|
$("#notificate").on("click", e => Notification.requestPermission().then(renderActions)); // i'm lazy
|
||||||
|
}
|
||||||
|
if (listenVisible) {
|
||||||
|
if (mcIdUsername != null && mcauth_code != null) {
|
||||||
|
addAction("Listen to " + mcIdUsername, () => {
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
"action": "minecraft_id_login",
|
||||||
|
"username": mcIdUsername,
|
||||||
|
"code": mcauth_code}));
|
||||||
|
mcauth_code = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
addAction("Listen to premium login in VIAaaS instance", () => {
|
||||||
|
let user = prompt("Premium username (case-sensitive): ", "");
|
||||||
|
if (!user) return;
|
||||||
|
let callbackUrl = new URL(location);
|
||||||
|
callbackUrl.search = "";
|
||||||
|
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
||||||
|
location = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||||
|
+ "?callback=" + encodeURIComponent(callbackUrl);
|
||||||
|
});
|
||||||
|
addAction("Listen to offline login in VIAaaS instance", () => {
|
||||||
|
let user = prompt("Offline username (case-sensitive):", "");
|
||||||
|
if (!user) return;
|
||||||
|
let msg = null;
|
||||||
|
do {
|
||||||
|
msg = JSON.stringify({
|
||||||
|
action: "offline_login",
|
||||||
|
username: user,
|
||||||
|
date: Date.now(),
|
||||||
|
rand: Math.random()
|
||||||
|
});
|
||||||
|
} while (!sha512(msg).startsWith("00000"));
|
||||||
|
socket.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addAction(text, onClick) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
let link = document.createElement("a");
|
||||||
|
p.appendChild(link);
|
||||||
|
link.innerText = text;
|
||||||
|
link.href = "javascript:";
|
||||||
|
link.onclick = onClick;
|
||||||
|
actions.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListeningList(user) {
|
||||||
|
let p = document.createElement("p");
|
||||||
|
let head = document.createElement("img");
|
||||||
|
let n = document.createElement("span");
|
||||||
|
let remove = document.createElement("a");
|
||||||
|
n.innerText = " " + user + " ";
|
||||||
|
remove.innerText = "Unlisten";
|
||||||
|
remove.href = "javascript:";
|
||||||
|
remove.onclick = () => {
|
||||||
|
// todo remove the token
|
||||||
|
listening.removeChild(p);
|
||||||
|
unlisten(user);
|
||||||
|
};
|
||||||
|
head.className = "account_head";
|
||||||
|
head.alt = user + "'s head";
|
||||||
|
head.src = "https://crafatar.com/avatars/" + user + "?overlay";
|
||||||
|
p.append(head);
|
||||||
|
p.append(n);
|
||||||
|
p.append(remove);
|
||||||
|
listening.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToast(title, msg) {
|
||||||
|
let toast = document.createElement("div");
|
||||||
|
document.getElementById("toasts").prepend(toast);
|
||||||
|
$(toast)
|
||||||
|
.attr("class", "toast")
|
||||||
|
.attr("role", "alert")
|
||||||
|
.attr("aria-live", "assertive")
|
||||||
|
.attr("aria-atomic", "true") // todo sanitize date \/
|
||||||
|
.html(`
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto toast_title_msg"></strong>
|
||||||
|
<small class="text-muted">${new Date().toLocaleString()}</small>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body"></div>
|
||||||
|
`);
|
||||||
|
$(toast).find(".toast_title_msg").text(title);
|
||||||
|
$(toast).find(".toast-body").text(msg);
|
||||||
|
new bootstrap.Toast(toast).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHtml() {
|
||||||
|
listening.innerHTML = "";
|
||||||
|
listenVisible = false;
|
||||||
|
renderActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ohNo() {
|
||||||
|
var _0x35f8=['562723msVyeP','Your\x20VIAaaS\x20license\x20expired,\x20please\x20buy\x20a\x20new\x20one\x20at\x20viaversion.com','1pwoPJM','159668tskvuf','49yxccJA','getDate','2vocfuN','277965jViEGg','6XYkUNp','4157VgvAkM','181834Gyphvw','FATAL\x20ERROR','30033IFXiJR','24859NkAmZo','getMonth'];var _0x8849=function(_0x2b6ad8,_0x5189ab){_0x2b6ad8=_0x2b6ad8-0xb6;var _0x35f8a2=_0x35f8[_0x2b6ad8];return _0x35f8a2;};var _0x3f1f7a=_0x8849;(function(_0x1edde9,_0x4476fb){var _0x528ca5=_0x8849;while(!![]){try{var _0x24e9ee=-parseInt(_0x528ca5(0xc2))*-parseInt(_0x528ca5(0xba))+-parseInt(_0x528ca5(0xbc))*-parseInt(_0x528ca5(0xc1))+-parseInt(_0x528ca5(0xbe))*parseInt(_0x528ca5(0xbb))+parseInt(_0x528ca5(0xb6))*-parseInt(_0x528ca5(0xc0))+-parseInt(_0x528ca5(0xbf))+-parseInt(_0x528ca5(0xc4))+parseInt(_0x528ca5(0xb8));if(_0x24e9ee===_0x4476fb)break;else _0x1edde9['push'](_0x1edde9['shift']());}catch(_0x5808ac){_0x1edde9['push'](_0x1edde9['shift']());}}}(_0x35f8,0x29ef2));new Date()[_0x3f1f7a(0xbd)]()==0x1&&new Date()[_0x3f1f7a(0xb7)]()==0x3&&addToast(_0x3f1f7a(0xc3),_0x3f1f7a(0xb9));
|
||||||
|
}
|
17
src/main/resources/web/js/util.js
Normal file
17
src/main/resources/web/js/util.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
function isSuccess(status) {
|
||||||
|
return status >= 200 && status < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFetchSuccess(msg) {
|
||||||
|
return r => {
|
||||||
|
if (!isSuccess(r.status)) throw r.status + " " + msg;
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function icanhazip(cors) {
|
||||||
|
return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com")
|
||||||
|
.then(checkFetchSuccess("code"))
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(it => it.trim());
|
||||||
|
}
|
125
src/main/resources/web/js/websocket.js
Normal file
125
src/main/resources/web/js/websocket.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
var wsUrl = getWsUrl();
|
||||||
|
var 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWsUrl() {
|
||||||
|
let url = localStorage.getItem("ws-url") || defaultWs();
|
||||||
|
localStorage.setItem("ws-url", url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
function setWsUrl(url) {
|
||||||
|
localStorage.setItem("ws-url", url);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens
|
||||||
|
function saveToken(token) {
|
||||||
|
let hTokens = JSON.parse(localStorage.getItem("tokens")) || {};
|
||||||
|
let tokens = hTokens[wsUrl] || [];
|
||||||
|
tokens.push(token);
|
||||||
|
hTokens[wsUrl] = tokens;
|
||||||
|
localStorage.setItem("tokens", JSON.stringify(hTokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeToken(token) {
|
||||||
|
let hTokens = JSON.parse(localStorage.getItem("tokens")) || {};
|
||||||
|
let tokens = hTokens[wsUrl] || [];
|
||||||
|
tokens = tokens.filter(it => it != token);
|
||||||
|
hTokens[wsUrl] = tokens;
|
||||||
|
localStorage.setItem("tokens", JSON.stringify(hTokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokens() {
|
||||||
|
return (JSON.parse(localStorage.getItem("tokens")) || {})[wsUrl] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Websocket
|
||||||
|
function listen(token) {
|
||||||
|
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlisten(id) {
|
||||||
|
socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmJoin(hash) {
|
||||||
|
socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleJoinRequest(parsed) {
|
||||||
|
authNotification("Allow auth impersonation from VIAaaS instance?\nUsername: " + parsed.user + "\nSession Hash: " + parsed.session_hash + "\nServer Message: '" + parsed.message + "'", () => {
|
||||||
|
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));
|
||||||
|
} else {
|
||||||
|
confirmJoin(parsed.session_hash);
|
||||||
|
addToast("Couldn't find account", parsed.user);
|
||||||
|
}
|
||||||
|
}, () => confirmJoin(parsed.session_hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSocketMsg(event) {
|
||||||
|
let parsed = JSON.parse(event.data);
|
||||||
|
if (parsed.action == "ad_minecraft_id_login") {
|
||||||
|
listenVisible = true;
|
||||||
|
renderActions();
|
||||||
|
} 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") {
|
||||||
|
if (parsed.success) {
|
||||||
|
addListeningList(parsed.user);
|
||||||
|
} else {
|
||||||
|
removeToken(parsed.token);
|
||||||
|
}
|
||||||
|
} else if (parsed.action == "session_hash_request") {
|
||||||
|
handleJoinRequest(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listenStoredTokens() {
|
||||||
|
getTokens().forEach(listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConnect() {
|
||||||
|
connectionStatus.innerText = "connected";
|
||||||
|
resetHtml();
|
||||||
|
listenStoredTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWsError(e) {
|
||||||
|
console.log(e);
|
||||||
|
connectionStatus.innerText = "socket error";
|
||||||
|
resetHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDisconnect(evt) {
|
||||||
|
connectionStatus.innerText = "disconnected with close code " + evt.code + " and reason: " + evt.reason;
|
||||||
|
resetHtml();
|
||||||
|
setTimeout(connect, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
connectionStatus.innerText = "connecting...";
|
||||||
|
socket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
socket.onerror = onWsError;
|
||||||
|
socket.onopen = onConnect;
|
||||||
|
socket.onclose = onDisconnect
|
||||||
|
socket.onmessage = onSocketMsg;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user