mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-21 11:55:15 +01:00
typescript, fix duplicate online mode listening
This commit is contained in:
parent
adfa957e66
commit
9b7821b704
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
/build
|
/build
|
||||||
/config
|
/config
|
||||||
/logs
|
/logs
|
||||||
|
node_modules/
|
||||||
|
180
package-lock.json
generated
Normal file
180
package-lock.json
generated
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
{
|
||||||
|
"name": "viaaas-web",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "viaaas-web",
|
||||||
|
"devDependencies": {
|
||||||
|
"@azure/msal-browser": "^2.27.0",
|
||||||
|
"@types/bootstrap": "^5.1.12",
|
||||||
|
"@types/jquery": "^3.5.14",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"jquery": "^3.6.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/msal-browser": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-PyATq2WvK+x32waRqqikym8wvn939iO9UhpFqhLwitNrfLa3PHUgJuuI9oLSQOS3/UzjYb8aqN+XzchU3n/ZuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/msal-common": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/msal-common": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-WyfqE5mY/rggjqvq0Q5DxLnA33KSb0vfsUjxa95rycFknI03L5GPYI4HTU9D+g0PL5TtsQGnV3xzAGq9BFCVJQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||||
|
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/bootstrap": {
|
||||||
|
"version": "5.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.12.tgz",
|
||||||
|
"integrity": "sha512-pSS5BGEgepwzdbsBGswBWFmgrnYpp7c4UfuYe1FJWwkrcjm/JVwfG4gBkOYtd92Otd3RdJK0ByBWMkBROfLEPw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/jquery": {
|
||||||
|
"version": "3.5.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
|
||||||
|
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/sizzle": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/sizzle": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/bootstrap": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@popperjs/core": "^2.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jquery": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": {
|
||||||
|
"version": "2.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.27.0.tgz",
|
||||||
|
"integrity": "sha512-PyATq2WvK+x32waRqqikym8wvn939iO9UhpFqhLwitNrfLa3PHUgJuuI9oLSQOS3/UzjYb8aqN+XzchU3n/ZuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@azure/msal-common": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@azure/msal-common": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-WyfqE5mY/rggjqvq0Q5DxLnA33KSb0vfsUjxa95rycFknI03L5GPYI4HTU9D+g0PL5TtsQGnV3xzAGq9BFCVJQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@popperjs/core": {
|
||||||
|
"version": "2.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||||
|
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/bootstrap": {
|
||||||
|
"version": "5.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.1.12.tgz",
|
||||||
|
"integrity": "sha512-pSS5BGEgepwzdbsBGswBWFmgrnYpp7c4UfuYe1FJWwkrcjm/JVwfG4gBkOYtd92Otd3RdJK0ByBWMkBROfLEPw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@popperjs/core": "^2.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/jquery": {
|
||||||
|
"version": "3.5.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.14.tgz",
|
||||||
|
"integrity": "sha512-X1gtMRMbziVQkErhTQmSe2jFwwENA/Zr+PprCkF63vFq+Yt5PZ4AlKqgmeNlwgn7dhsXEK888eIW2520EpC+xg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/sizzle": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/sizzle": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "8.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||||
|
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"bootstrap": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"jquery": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"description": "Please use Gradle, NPM is mainly used for auto completing on IDE and update checking.",
|
||||||
|
"name": "viaaas-web",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@azure/msal-browser": "^2.27.0",
|
||||||
|
"@types/bootstrap": "^5.1.12",
|
||||||
|
"@types/jquery": "^3.5.14",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"jquery": "^3.6.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "./gradlew run"
|
||||||
|
}
|
||||||
|
}
|
@ -17,7 +17,7 @@
|
|||||||
style-src 'self' https://cdnjs.cloudflare.com/;
|
style-src 'self' https://cdnjs.cloudflare.com/;
|
||||||
img-src 'self' data: https://crafthead.net/ https://crafatar.com/;
|
img-src 'self' data: https://crafthead.net/ https://crafatar.com/;
|
||||||
connect-src 'self' http://localhost:*/ https: wss:;
|
connect-src 'self' http://localhost:*/ https: wss:;
|
||||||
script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*.cloudflareinsights.com/ 'unsafe-hashes' 'sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=';
|
script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*.cloudflareinsights.com/;
|
||||||
frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
||||||
http-equiv="Content-Security-Policy">
|
http-equiv="Content-Security-Policy">
|
||||||
<meta content="no-referrer" name="referrer">
|
<meta content="no-referrer" name="referrer">
|
||||||
@ -26,12 +26,13 @@ frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
|||||||
|
|
||||||
<!-- https://www.srihash.org/ -->
|
<!-- https://www.srihash.org/ -->
|
||||||
<link class="async-css" rel="preload" as="style"
|
<link class="async-css" rel="preload" as="style"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.1/css/bootstrap.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css"
|
||||||
integrity="sha512-6KY5s6UI5J7SVYuZB4S/CZMyPylqyyNZco376NM2Z8Sb8OxEdp02e1jkKk/wZxIEmjQ6DRCEBhni+gpr9c4tvA=="
|
integrity="sha512-GQGU0fMMi238uA+a/bdWJfpUGKUkBdgfFdgBm72SUQ6BeyWjoY/ton0tEjH+OSH9iP4Dfh+7HM0I9f5eR0L/4w=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.1/js/bootstrap.min.js"
|
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.min.js"
|
||||||
integrity="sha512-ewfXo9Gq53e1q1+WDTjaHAGZ8UvCWq0eXONhwDuIoaH8xz2r96uoAYaQCm1oQhnBfRXrvJztNXFsTloJfgbL5Q=="
|
integrity="sha512-OvBgP9A2JBgiRad/mM36mkzXSXaJE9BEIENnVEmeZdITvwT09xnxLtT4twkCa8m/loMbPHsvPl0T8lRGVBwjlQ=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
<!-- Safari workaround -->
|
||||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/HTML5Notification/3.0.0/Notification.min.js"
|
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/HTML5Notification/3.0.0/Notification.min.js"
|
||||||
integrity="sha512-gx0m7Qoum1Bb0KrP6AEZSt0e+o2xMEyStAz2TNGXGqR4HSVgPveWFQdtE06FRvJmmp8HdkJklOLYiV3aZN6tQg=="
|
integrity="sha512-gx0m7Qoum1Bb0KrP6AEZSt0e+o2xMEyStAz2TNGXGqR4HSVgPveWFQdtE06FRvJmmp8HdkJklOLYiV3aZN6tQg=="
|
||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
@ -41,8 +42,8 @@ frame-src 'self' https://login.microsoftonline.com/ https://login.live.com/"
|
|||||||
<script defer crossorigin="anonymous"
|
<script defer crossorigin="anonymous"
|
||||||
integrity="sha512-UNM1njAgOFUa74Z0bADwAq8gbTcqZC8Ej4xPSzpnh0l6KMevwvkBvbldF9uR++qKeJ+MOZHRjV1HZjoRvjDfNQ=="
|
integrity="sha512-UNM1njAgOFUa74Z0bADwAq8gbTcqZC8Ej4xPSzpnh0l6KMevwvkBvbldF9uR++qKeJ+MOZHRjV1HZjoRvjDfNQ=="
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>
|
src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.3.2/uuid.min.js"></script>
|
||||||
<script defer src="https://alcdn.msauth.net/browser/2.19.0/js/msal-browser.min.js"
|
<script defer src="https://alcdn.msauth.net/browser/2.27.0/js/msal-browser.min.js"
|
||||||
integrity="sha384-VK+6hHt27itNFksZdEeXofJXdAhmlizHbC/a1TUUJm/Yq6gOuAjXKkiiCaGbFsnd"
|
integrity="sha384-IlUQkOwOI6mWk8GNIWu8hpPE1sasxSg3gGjZo0dncq6IhHsTlH51mp5mhFYS5po1"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
<script defer src="js/config.js"></script>
|
<script defer src="js/config.js"></script>
|
||||||
<script defer src="js/page.js"></script>
|
<script defer src="js/page.js"></script>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Minecraft.id
|
"use strict";
|
||||||
let urlParams = new URLSearchParams();
|
let urlParams = new URLSearchParams();
|
||||||
window.location.hash.substring(1).split("?")
|
window.location.hash.substring(1).split("?")
|
||||||
.map(it => new URLSearchParams(it)
|
.map(it => new URLSearchParams(it)
|
||||||
@ -6,7 +6,6 @@ window.location.hash.substring(1).split("?")
|
|||||||
let mcIdUsername = urlParams.get("username");
|
let mcIdUsername = urlParams.get("username");
|
||||||
let mcauth_code = urlParams.get("mcauth_code");
|
let mcauth_code = urlParams.get("mcauth_code");
|
||||||
let mcauth_success = urlParams.get("mcauth_success");
|
let mcauth_success = urlParams.get("mcauth_success");
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
if (mcauth_success === "false") {
|
if (mcauth_success === "false") {
|
||||||
addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg"));
|
addToast("Couldn't authenticate with Minecraft.ID", urlParams.get("mcauth_msg"));
|
||||||
@ -15,8 +14,6 @@ $(() => {
|
|||||||
history.replaceState(null, null, "#");
|
history.replaceState(null, null, "#");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Page
|
|
||||||
let connectionStatus = document.getElementById("connection_status");
|
let connectionStatus = document.getElementById("connection_status");
|
||||||
let corsStatus = document.getElementById("cors_status");
|
let corsStatus = document.getElementById("cors_status");
|
||||||
let listening = document.getElementById("listening");
|
let listening = document.getElementById("listening");
|
||||||
@ -24,13 +21,12 @@ let accounts = document.getElementById("accounts-list");
|
|||||||
let cors_proxy_txt = document.getElementById("cors-proxy");
|
let cors_proxy_txt = document.getElementById("cors-proxy");
|
||||||
let ws_url_txt = document.getElementById("ws-url");
|
let ws_url_txt = document.getElementById("ws-url");
|
||||||
let listenVisible = false;
|
let listenVisible = false;
|
||||||
// + deltaTime means that the clock is ahead
|
|
||||||
let deltaTime = 0;
|
let deltaTime = 0;
|
||||||
let workers = [];
|
let workers = [];
|
||||||
$(() => {
|
$(() => {
|
||||||
workers = new Array(navigator.hardwareConcurrency)
|
workers = new Array(navigator.hardwareConcurrency)
|
||||||
.fill(null)
|
.fill(null)
|
||||||
.map(() => new Worker("js/worker.js"))
|
.map(() => new Worker("js/worker.js"));
|
||||||
workers.forEach(it => it.onmessage = onWorkerMsg);
|
workers.forEach(it => it.onmessage = onWorkerMsg);
|
||||||
});
|
});
|
||||||
$(() => {
|
$(() => {
|
||||||
@ -38,17 +34,13 @@ $(() => {
|
|||||||
navigator.serviceWorker.register("sw.js")
|
navigator.serviceWorker.register("sw.js")
|
||||||
.then(() => setTimeout(() => swRefreshFiles(), 1000));
|
.then(() => setTimeout(() => swRefreshFiles(), 1000));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
|
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
$(".async-css").attr("rel", "stylesheet");
|
$(".async-css").attr("rel", "stylesheet");
|
||||||
$("form").on("submit", e => e.preventDefault());
|
$("form").on("submit", e => e.preventDefault());
|
||||||
|
$("a[href='javascript:']").on("click", e => e.preventDefault());
|
||||||
cors_proxy_txt.value = getCorsProxy();
|
cors_proxy_txt.value = getCorsProxy();
|
||||||
ws_url_txt.value = getWsUrl();
|
ws_url_txt.value = getWsUrl();
|
||||||
|
|
||||||
$("#form_add_mc").on("submit", () => loginMc($("#mc_email").val(), $("#mc_password").val()));
|
$("#form_add_mc").on("submit", () => loginMc($("#mc_email").val(), $("#mc_password").val()));
|
||||||
$("#form_add_ms").on("submit", () => loginMs());
|
$("#form_add_ms").on("submit", () => loginMs());
|
||||||
$("#form_ws_url").on("submit", () => setWsUrl($("#ws-url").val()));
|
$("#form_ws_url").on("submit", () => setWsUrl($("#ws-url").val()));
|
||||||
@ -57,46 +49,40 @@ $(() => {
|
|||||||
$("#form_send_token").on("submit", () => submittedSendToken());
|
$("#form_send_token").on("submit", () => submittedSendToken());
|
||||||
$("#en_notific").on("click", () => Notification.requestPermission().then(renderActions));
|
$("#en_notific").on("click", () => Notification.requestPermission().then(renderActions));
|
||||||
$("#listen_continue").on("click", () => clickedListenContinue());
|
$("#listen_continue").on("click", () => clickedListenContinue());
|
||||||
|
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
|
||||||
ohNo();
|
ohNo();
|
||||||
|
|
||||||
refreshAccountList();
|
refreshAccountList();
|
||||||
setInterval(refreshCorsStatus, 10 * 60 * 1000); // Heroku auto sleeps in 30 min
|
setInterval(refreshCorsStatus, 10 * 60 * 1000);
|
||||||
refreshCorsStatus();
|
refreshCorsStatus();
|
||||||
resetHtml();
|
resetHtml();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
connect();
|
connect();
|
||||||
})
|
});
|
||||||
|
|
||||||
function swRefreshFiles() {
|
function swRefreshFiles() {
|
||||||
// https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker
|
|
||||||
navigator.serviceWorker.ready.then(ready => ready.active.postMessage({
|
navigator.serviceWorker.ready.then(ready => ready.active.postMessage({
|
||||||
action: "cache",
|
action: "cache",
|
||||||
urls: performance.getEntriesByType("resource").map(it => it.name)
|
urls: performance.getEntriesByType("resource").map(it => it.name)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWsStatus(txt) {
|
function setWsStatus(txt) {
|
||||||
connectionStatus.innerText = txt;
|
connectionStatus.innerText = txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshCorsStatus() {
|
function refreshCorsStatus() {
|
||||||
corsStatus.innerText = "...";
|
corsStatus.innerText = "...";
|
||||||
icanhazip(true).then(ip => {
|
getIpAddress(true).then(ip => {
|
||||||
return icanhazip(false).then(ip2 => corsStatus.innerText = "OK " + ip + (ip !== ip2 ? " (different IP)" : ""));
|
return getIpAddress(false).then(ip2 => corsStatus.innerText = "OK " + ip + (ip !== ip2 ? " (different IP)" : ""));
|
||||||
}).catch(e => corsStatus.innerText = "error: " + e);
|
}).catch(e => corsStatus.innerText = "error: " + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMcAccountToList(account) {
|
function addMcAccountToList(account) {
|
||||||
let line = $(`<li class='input-group d-flex'>
|
let line = $(`<li class='input-group d-flex'>
|
||||||
<span class='input-group-text'><img loading="lazy" width=24 class='mc-head'/></span>
|
<span class='input-group-text'><img alt="?" src="?" loading="lazy" width=24 class='mc-head'/></span>
|
||||||
<span class='form-control mc-user'></span>
|
<span class='form-control mc-user'></span>
|
||||||
<button type="button" class='btn btn-danger mc-remove'>Logout</button>
|
<button type="button" class='btn btn-danger mc-remove'>Logout</button>
|
||||||
</li>`);
|
</li>`);
|
||||||
let txt = account.name;
|
let txt = account.name;
|
||||||
if (account instanceof MicrosoftAccount) txt += " (" + account.msUser + ")";
|
if (account instanceof MicrosoftAccount)
|
||||||
|
txt += " (" + account.msUser + ")";
|
||||||
line.find(".mc-user").text(txt);
|
line.find(".mc-user").text(txt);
|
||||||
line.find(".mc-remove").on("click", () => account.logout());
|
line.find(".mc-remove").on("click", () => account.logout());
|
||||||
let head = line.find(".mc-head");
|
let head = line.find(".mc-head");
|
||||||
@ -104,14 +90,12 @@ function addMcAccountToList(account) {
|
|||||||
head.attr("src", "https://crafthead.net/helm/" + account.id);
|
head.attr("src", "https://crafthead.net/helm/" + account.id);
|
||||||
$(accounts).append(line);
|
$(accounts).append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUsernameList(username) {
|
function addUsernameList(username) {
|
||||||
let line = $("<option class='mc_username'></option>");
|
let line = $("<option class='mc_username'></option>");
|
||||||
line.text(username);
|
line.text(username);
|
||||||
$("#send_token_user").append(line);
|
$("#send_token_user").append(line);
|
||||||
$("#backend_user_list").append(line.clone());
|
$("#backend_user_list").append(line.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshAccountList() {
|
function refreshAccountList() {
|
||||||
accounts.innerHTML = "";
|
accounts.innerHTML = "";
|
||||||
$("#send_token_user .mc_username").remove();
|
$("#send_token_user .mc_username").remove();
|
||||||
@ -119,41 +103,39 @@ function refreshAccountList() {
|
|||||||
getActiveAccounts()
|
getActiveAccounts()
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.forEach(it => {
|
.forEach(it => {
|
||||||
addMcAccountToList(it)
|
addMcAccountToList(it);
|
||||||
addUsernameList(it.name)
|
addUsernameList(it.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#mcIdUsername").text(mcIdUsername);
|
$("#mcIdUsername").text(mcIdUsername);
|
||||||
|
|
||||||
function submittedListen() {
|
function submittedListen() {
|
||||||
let user = $("#listen_username").val();
|
let user = $("#listen_username").val();
|
||||||
if (!user) return;
|
if (!user)
|
||||||
|
return;
|
||||||
if ($("#listen_online")[0].checked) {
|
if ($("#listen_online")[0].checked) {
|
||||||
let callbackUrl = new URL(location);
|
let callbackUrl = new URL(location.href);
|
||||||
callbackUrl.search = "";
|
callbackUrl.search = "";
|
||||||
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
||||||
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||||
+ "?callback=" + encodeURIComponent(callbackUrl.toString());
|
+ "?callback=" + encodeURIComponent(callbackUrl.toString());
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
let taskId = Math.random();
|
let taskId = Math.random();
|
||||||
workers.forEach(it => it.postMessage({ action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime }));
|
workers.forEach(it => it.postMessage({ action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime }));
|
||||||
addToast("Offline username", "Please wait a minute...");
|
addToast("Offline username", "Please wait a minute...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function submittedSendToken() {
|
function submittedSendToken() {
|
||||||
findAccountByMcName($("#send_token_user").val())
|
let account = findAccountByMcName($("#send_token_user").val());
|
||||||
.acquireActiveToken()
|
account.acquireActiveToken()
|
||||||
.then(acc => {
|
.then(() => {
|
||||||
sendSocket(JSON.stringify({
|
sendSocket(JSON.stringify({
|
||||||
"action": "save_access_token",
|
"action": "save_access_token",
|
||||||
"mc_access_token": acc.accessToken
|
"mc_access_token": account.accessToken
|
||||||
}))
|
}));
|
||||||
})
|
})
|
||||||
.catch(e => addToast("Failed to send access token", e));
|
.catch(e => addToast("Failed to send access token", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickedListenContinue() {
|
function clickedListenContinue() {
|
||||||
sendSocket(JSON.stringify({
|
sendSocket(JSON.stringify({
|
||||||
"action": "minecraft_id_login",
|
"action": "minecraft_id_login",
|
||||||
@ -163,13 +145,11 @@ function clickedListenContinue() {
|
|||||||
mcauth_code = null;
|
mcauth_code = null;
|
||||||
renderActions();
|
renderActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderActions() {
|
function renderActions() {
|
||||||
$("#en_notific").hide();
|
$("#en_notific").hide();
|
||||||
$("#listen_continue").hide();
|
$("#listen_continue").hide();
|
||||||
$("#listen_open").hide();
|
$("#listen_open").hide();
|
||||||
$("#send_token_open").hide();
|
$("#send_token_open").hide();
|
||||||
|
|
||||||
if (Notification.permission === "default") {
|
if (Notification.permission === "default") {
|
||||||
$("#en_notific").show();
|
$("#en_notific").show();
|
||||||
}
|
}
|
||||||
@ -181,31 +161,28 @@ function renderActions() {
|
|||||||
$("#send_token_open").show();
|
$("#send_token_open").show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWorkerMsg(e) {
|
function onWorkerMsg(e) {
|
||||||
if (e.data.action === "completed_pow") onCompletedPoW(e);
|
if (e.data.action === "completed_pow")
|
||||||
|
onCompletedPoW(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCompletedPoW(e) {
|
function onCompletedPoW(e) {
|
||||||
addToast("Offline username", "Completed proof of work");
|
addToast("Offline username", "Completed proof of work");
|
||||||
workers.forEach(it => it.postMessage({ action: "cancel", id: e.data.id }));
|
workers.forEach(it => it.postMessage({ action: "cancel", id: e.data.id }));
|
||||||
sendSocket(e.data.msg);
|
sendSocket(e.data.msg);
|
||||||
}
|
}
|
||||||
|
function addListeningList(userId, username, token) {
|
||||||
function addListeningList(user, username, token) {
|
let line = $("<p><img alt='?' src='?' loading='lazy' width=24 class='head'/> <span class='username'></span> <button class='btn btn-danger' type='button'>Unlisten</button></p>");
|
||||||
let line = $("<p><img loading='lazy' width=24 class='head'/> <span class='username'></span> <button class='btn btn-danger' type='button'>Unlisten</button></p>");
|
line.find(".username").text(username || userId);
|
||||||
line.find(".username").text(username || user);
|
|
||||||
line.find(".btn").on("click", () => {
|
line.find(".btn").on("click", () => {
|
||||||
removeToken(token);
|
removeToken(token);
|
||||||
line.remove();
|
line.remove();
|
||||||
unlisten(user);
|
unlisten(userId);
|
||||||
});
|
});
|
||||||
let head = line.find(".head");
|
let head = line.find(".head");
|
||||||
head.attr("alt", user + "'s head");
|
head.attr("alt", userId + "'s head");
|
||||||
head.attr("src", "https://crafthead.net/helm/" + user);
|
head.attr("src", "https://crafthead.net/helm/" + userId);
|
||||||
$(listening).append(line);
|
$(listening).append(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addToast(title, msg, yes = null, no = null) {
|
function addToast(title, msg, yes = null, no = null) {
|
||||||
let toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
let toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
<div class="toast-header">
|
<div class="toast-header">
|
||||||
@ -218,10 +195,8 @@ function addToast(title, msg, yes = null, no = null) {
|
|||||||
</div>
|
</div>
|
||||||
</div>`);
|
</div>`);
|
||||||
toast.find(".toast_title_msg").text(title);
|
toast.find(".toast_title_msg").text(title);
|
||||||
|
|
||||||
let tBody = toast.find(".toast-body");
|
let tBody = toast.find(".toast-body");
|
||||||
tBody.find(".txt").text(msg);
|
tBody.find(".txt").text(msg);
|
||||||
|
|
||||||
let btns = $(tBody).find(".btns");
|
let btns = $(tBody).find(".btns");
|
||||||
let hasButtons = false;
|
let hasButtons = false;
|
||||||
if (yes != null) {
|
if (yes != null) {
|
||||||
@ -239,17 +214,14 @@ function addToast(title, msg, yes = null, no = null) {
|
|||||||
if (!hasButtons) {
|
if (!hasButtons) {
|
||||||
btns.addClass("d-none");
|
btns.addClass("d-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#toasts").prepend(toast);
|
$("#toasts").prepend(toast);
|
||||||
new bootstrap.Toast(toast[0]).show();
|
new bootstrap.Toast(toast[0]).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetHtml() {
|
function resetHtml() {
|
||||||
listening.innerHTML = "";
|
listening.innerHTML = "";
|
||||||
listenVisible = false;
|
listenVisible = false;
|
||||||
renderActions();
|
renderActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function ohNo() {
|
function ohNo() {
|
||||||
try {
|
try {
|
||||||
icanhazepoch().then(sec => {
|
icanhazepoch().then(sec => {
|
||||||
@ -258,59 +230,56 @@ function ohNo() {
|
|||||||
addToast("Time isn't synchronized", "Please synchronize your computer time to NTP servers");
|
addToast("Time isn't synchronized", "Please synchronize your computer time to NTP servers");
|
||||||
deltaTime = calcDelta;
|
deltaTime = calcDelta;
|
||||||
console.log("applying delta time " + deltaTime);
|
console.log("applying delta time " + deltaTime);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
console.log("time seems synchronized");
|
console.log("time seems synchronized");
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
try {
|
try {
|
||||||
new BroadcastChannel("test");
|
new BroadcastChannel("test");
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
addToast("Unsupported browser", "This browser doesn't support required APIs");
|
addToast("Unsupported browser", "This browser doesn't support required APIs");
|
||||||
}
|
}
|
||||||
new Date().getDay() === 3 && console.log("it's snapshot day 🐸 my dudes");
|
new Date().getDay() === 3 && console.log("it's snapshot day 🐸 my dudes");
|
||||||
new Date().getDate() === 1 && new Date().getMonth() === 3 && addToast("LICENSE EXPIRED", "Your ViaVersion has expired, please renew it at https://viaversion.com/ for only $99");
|
new Date().getDate() === 1 && new Date().getMonth() === 3 && addToast("LICENSE EXPIRED", "Your ViaVersion has expired, please renew it at https://viaversion.com/ for only $99");
|
||||||
} catch (e) {
|
}
|
||||||
|
catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util
|
|
||||||
function checkFetchSuccess(msg) {
|
function checkFetchSuccess(msg) {
|
||||||
return r => {
|
return (r) => {
|
||||||
if (!r.ok) throw r.status + " " + msg;
|
if (!r.ok)
|
||||||
|
throw r.status + " " + msg;
|
||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
async function getIpAddress(cors) {
|
||||||
function icanhazip(cors) {
|
|
||||||
return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com")
|
return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com")
|
||||||
.then(checkFetchSuccess("code"))
|
.then(checkFetchSuccess("code"))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(it => it.trim());
|
.then(it => it.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function icanhazepoch() {
|
function icanhazepoch() {
|
||||||
return fetch("https://icanhazepoch.com")
|
return fetch("https://icanhazepoch.com")
|
||||||
.then(checkFetchSuccess("code"))
|
.then(checkFetchSuccess("code"))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(it => parseInt(it.trim()))
|
.then(it => parseInt(it.trim()));
|
||||||
}
|
}
|
||||||
|
let notificationCallbacks = new Map();
|
||||||
// Notification
|
|
||||||
let notificationCallbacks = {};
|
|
||||||
$(() => {
|
$(() => {
|
||||||
new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg);
|
new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg);
|
||||||
})
|
});
|
||||||
|
|
||||||
function handleSWMsg(event) {
|
function handleSWMsg(event) {
|
||||||
console.log("sw msg: " + event);
|
console.log("sw msg: " + event);
|
||||||
let data = event.data;
|
let data = event.data;
|
||||||
let callback = notificationCallbacks[data.tag];
|
let callback = notificationCallbacks.get(data.tag);
|
||||||
delete notificationCallbacks[data.tag];
|
notificationCallbacks.delete(data.tag);
|
||||||
if (callback == null) return;
|
if (callback == null)
|
||||||
|
return;
|
||||||
callback(data.action);
|
callback(data.action);
|
||||||
}
|
}
|
||||||
|
|
||||||
function authNotification(msg, yes, no) {
|
function authNotification(msg, yes, no) {
|
||||||
if (!navigator.serviceWorker || Notification.permission !== "granted") {
|
if (!navigator.serviceWorker || Notification.permission !== "granted") {
|
||||||
addToast("Allow auth impersonation?", msg, yes, no);
|
addToast("Allow auth impersonation?", msg, yes, no);
|
||||||
@ -318,7 +287,7 @@ function authNotification(msg, yes, no) {
|
|||||||
}
|
}
|
||||||
let tag = uuid.v4();
|
let tag = uuid.v4();
|
||||||
navigator.serviceWorker.ready.then(r => {
|
navigator.serviceWorker.ready.then(r => {
|
||||||
r.showNotification("Click to allow auth impersionation", {
|
r.showNotification("Click to allow auth impersonation", {
|
||||||
body: msg,
|
body: msg,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
vibrate: [200, 10, 100, 200, 100, 10, 100, 10, 200],
|
vibrate: [200, 10, 100, 200, 100, 10, 100, 10, 200],
|
||||||
@ -327,57 +296,48 @@ function authNotification(msg, yes, no) {
|
|||||||
{ action: "confirm", title: "Confirm" }
|
{ action: "confirm", title: "Confirm" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
notificationCallbacks[tag] = action => {
|
notificationCallbacks.set(tag, action => {
|
||||||
if (action === "reject") {
|
if (action === "reject") {
|
||||||
no();
|
no();
|
||||||
} else if (!action || action === "confirm") {
|
}
|
||||||
|
else if (!action || action === "confirm") {
|
||||||
yes();
|
yes();
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
delete notificationCallbacks[tag]
|
notificationCallbacks.delete(tag);
|
||||||
}, 30 * 1000);
|
}, 30 * 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cors proxy
|
|
||||||
function defaultCors() {
|
function defaultCors() {
|
||||||
return "https://crp123-cors.herokuapp.com/";
|
return "https://crp123-cors.herokuapp.com/";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCorsProxy() {
|
function getCorsProxy() {
|
||||||
return localStorage.getItem("viaaas_cors_proxy") || defaultCors();
|
return localStorage.getItem("viaaas_cors_proxy") || defaultCors();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCorsProxy(url) {
|
function setCorsProxy(url) {
|
||||||
localStorage.setItem("viaaas_cors_proxy", url);
|
localStorage.setItem("viaaas_cors_proxy", url);
|
||||||
refreshCorsStatus();
|
refreshCorsStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account manager
|
|
||||||
let activeAccounts = [];
|
let activeAccounts = [];
|
||||||
|
|
||||||
function loadAccounts() {
|
function loadAccounts() {
|
||||||
(JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []).forEach(it => {
|
(JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []).forEach((it) => {
|
||||||
if (it.clientToken) {
|
if (it.clientToken) {
|
||||||
addActiveAccount(new MojangAccount(it.id, it.name, it.accessToken, it.clientToken))
|
addActiveAccount(new MojangAccount(it.id, it.name, it.accessToken, it.clientToken));
|
||||||
} else if (it.msUser && myMSALObj.getAccountByUsername(it.msUser)) {
|
|
||||||
addActiveAccount(new MicrosoftAccount(it.id, it.name, it.accessToken, it.msUser))
|
|
||||||
}
|
}
|
||||||
})
|
else if (it.msUser && myMSALObj.getAccountByUsername(it.msUser)) {
|
||||||
|
addActiveAccount(new MicrosoftAccount(it.id, it.name, it.accessToken, it.msUser));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(() => loadAccounts());
|
$(() => loadAccounts());
|
||||||
|
|
||||||
function saveRefreshAccounts() {
|
function saveRefreshAccounts() {
|
||||||
localStorage.setItem("viaaas_mc_accounts", JSON.stringify(getActiveAccounts()))
|
localStorage.setItem("viaaas_mc_accounts", JSON.stringify(getActiveAccounts()));
|
||||||
refreshAccountList()
|
refreshAccountList();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveAccounts() {
|
function getActiveAccounts() {
|
||||||
return activeAccounts;
|
return activeAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
class McAccount {
|
class McAccount {
|
||||||
constructor(id, username, accessToken) {
|
constructor(id, username, accessToken) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -385,14 +345,12 @@ class McAccount {
|
|||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.loggedOut = false;
|
this.loggedOut = false;
|
||||||
}
|
}
|
||||||
|
async logout() {
|
||||||
logout() {
|
|
||||||
activeAccounts = activeAccounts.filter(it => it !== this);
|
activeAccounts = activeAccounts.filter(it => it !== this);
|
||||||
saveRefreshAccounts();
|
saveRefreshAccounts();
|
||||||
this.loggedOut = true;
|
this.loggedOut = true;
|
||||||
}
|
}
|
||||||
|
async checkActive() {
|
||||||
checkActive() {
|
|
||||||
return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", {
|
return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -402,9 +360,8 @@ class McAccount {
|
|||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
}).then(data => data.ok);
|
}).then(data => data.ok);
|
||||||
}
|
}
|
||||||
|
async joinGame(hash) {
|
||||||
joinGame(hash) {
|
await this.acquireActiveToken()
|
||||||
return this.acquireActiveToken()
|
|
||||||
.then(() => fetch(getCorsProxy() + "https://sessionserver.mojang.com/session/minecraft/join", {
|
.then(() => fetch(getCorsProxy() + "https://sessionserver.mojang.com/session/minecraft/join", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -413,31 +370,31 @@ class McAccount {
|
|||||||
serverId: hash
|
serverId: hash
|
||||||
}),
|
}),
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
})).then(checkFetchSuccess("Failed to join session"));
|
}))
|
||||||
|
.then(checkFetchSuccess("Failed to join session"));
|
||||||
}
|
}
|
||||||
|
async refresh() {
|
||||||
refresh() {
|
|
||||||
}
|
}
|
||||||
|
async acquireActiveToken() {
|
||||||
acquireActiveToken() {
|
return this.checkActive()
|
||||||
return this.checkActive().then(success => {
|
.then(success => {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return this.refresh().then(() => this);
|
return this.refresh().then(() => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return this;
|
return Promise.resolve();
|
||||||
}).catch(e => addToast("Failed to refresh token!", e));
|
})
|
||||||
|
.catch(e => addToast("Failed to refresh token!", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MojangAccount extends McAccount {
|
class MojangAccount extends McAccount {
|
||||||
constructor(id, username, accessToken, clientToken) {
|
constructor(id, username, accessToken, clientToken) {
|
||||||
super(id, username, accessToken);
|
super(id, username, accessToken);
|
||||||
this.clientToken = clientToken;
|
this.clientToken = clientToken;
|
||||||
}
|
}
|
||||||
|
async logout() {
|
||||||
logout() {
|
await super.logout();
|
||||||
super.logout();
|
await fetch(getCorsProxy() + "https://authserver.mojang.com/invalidate", {
|
||||||
fetch(getCorsProxy() + "https://authserver.mojang.com/invalidate", {
|
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accessToken: this.accessToken,
|
accessToken: this.accessToken,
|
||||||
@ -446,12 +403,9 @@ class MojangAccount extends McAccount {
|
|||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
}).then(checkFetchSuccess("not success logout"));
|
}).then(checkFetchSuccess("not success logout"));
|
||||||
}
|
}
|
||||||
|
async refresh() {
|
||||||
refresh() {
|
|
||||||
super.refresh();
|
|
||||||
|
|
||||||
console.log("refreshing " + this.id);
|
console.log("refreshing " + this.id);
|
||||||
return fetch(getCorsProxy() + "https://authserver.mojang.com/refresh", {
|
let jsonResp = await fetch(getCorsProxy() + "https://authserver.mojang.com/refresh", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accessToken: this.accessToken,
|
accessToken: this.accessToken,
|
||||||
@ -459,68 +413,67 @@ class MojangAccount extends McAccount {
|
|||||||
}),
|
}),
|
||||||
headers: { "content-type": "application/json" },
|
headers: { "content-type": "application/json" },
|
||||||
})
|
})
|
||||||
.then(r => {
|
.then(async (r) => {
|
||||||
if (r.status === 403) {
|
if (r.status === 403) {
|
||||||
this.logout();
|
try {
|
||||||
|
await this.logout();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
throw "403, token expired?";
|
throw "403, token expired?";
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
})
|
})
|
||||||
.then(checkFetchSuccess("code"))
|
.then(checkFetchSuccess("code"))
|
||||||
.then(r => r.json())
|
.then(r => r.json());
|
||||||
.then(json => {
|
console.log("refreshed " + jsonResp.selectedProfile.id);
|
||||||
console.log("refreshed " + json.selectedProfile.id);
|
this.accessToken = jsonResp.accessToken;
|
||||||
this.accessToken = json.accessToken;
|
this.clientToken = jsonResp.clientToken;
|
||||||
this.clientToken = json.clientToken;
|
this.name = jsonResp.selectedProfile.name;
|
||||||
this.name = json.selectedProfile.name;
|
this.id = jsonResp.selectedProfile.id;
|
||||||
this.id = json.selectedProfile.id;
|
|
||||||
saveRefreshAccounts();
|
saveRefreshAccounts();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MicrosoftAccount extends McAccount {
|
class MicrosoftAccount extends McAccount {
|
||||||
constructor(id, username, accessToken, msUser) {
|
constructor(id, username, accessToken, msUser) {
|
||||||
super(id, username, accessToken);
|
super(id, username, accessToken);
|
||||||
this.msUser = msUser;
|
this.msUser = msUser;
|
||||||
}
|
}
|
||||||
|
async logout() {
|
||||||
logout() {
|
await super.logout();
|
||||||
super.logout();
|
|
||||||
|
|
||||||
let msAccount = myMSALObj.getAccountByUsername(this.msUser);
|
let msAccount = myMSALObj.getAccountByUsername(this.msUser);
|
||||||
if (!msAccount) return;
|
if (!msAccount)
|
||||||
|
return;
|
||||||
const logoutRequest = { account: msAccount };
|
const logoutRequest = { account: msAccount };
|
||||||
myMSALObj.logoutPopup(logoutRequest);
|
await myMSALObj.logoutPopup(logoutRequest);
|
||||||
}
|
}
|
||||||
|
async refresh() {
|
||||||
refresh() {
|
let msTokenResp = await getTokenPopup(this.msUser, getLoginRequest());
|
||||||
super.refresh();
|
let xboxJson = await fetch("https://user.auth.xboxlive.com/user/authenticate", {
|
||||||
return getTokenPopup(this.msUser, getLoginRequest())
|
|
||||||
.then(response => fetch("https://user.auth.xboxlive.com/user/authenticate", {
|
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
Properties: {
|
Properties: {
|
||||||
AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com",
|
AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com",
|
||||||
RpsTicket: "d=" + response.accessToken
|
RpsTicket: "d=" + msTokenResp.accessToken
|
||||||
}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"
|
}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"
|
||||||
}),
|
}),
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
})
|
})
|
||||||
.then(checkFetchSuccess("xbox response not success"))
|
.then(checkFetchSuccess("xbox response not success"))
|
||||||
.then(r => r.json()))
|
.then(r => r.json());
|
||||||
.then(json => fetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
|
let xstsJson = await fetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
Properties: {SandboxId: "RETAIL", UserTokens: [json.Token]},
|
Properties: { SandboxId: "RETAIL", UserTokens: [xboxJson.Token] },
|
||||||
RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"
|
RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"
|
||||||
}),
|
}),
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(resp => {
|
||||||
if (data.status !== 401) return data;
|
if (resp.status !== 401)
|
||||||
return data.json().then(errorData => {
|
return resp;
|
||||||
|
return resp.json().then(errorData => {
|
||||||
let error = errorData.XErr;
|
let error = errorData.XErr;
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case 2148916233:
|
case 2148916233:
|
||||||
@ -534,53 +487,47 @@ class MicrosoftAccount extends McAccount {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(checkFetchSuccess("xsts response not success"))
|
.then(checkFetchSuccess("xsts response not success"))
|
||||||
.then(r => r.json()))
|
.then(r => r.json());
|
||||||
.then(json => fetch(getCorsProxy() + "https://api.minecraftservices.com/authentication/login_with_xbox", {
|
let mcJson = await fetch(getCorsProxy() + "https://api.minecraftservices.com/authentication/login_with_xbox", {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: JSON.stringify({identityToken: "XBL3.0 x=" + json.DisplayClaims.xui[0].uhs + ";" + json.Token}),
|
body: JSON.stringify({ identityToken: "XBL3.0 x=" + xstsJson.DisplayClaims.xui[0].uhs + ";" + xstsJson.Token }),
|
||||||
headers: { "content-type": "application/json" }
|
headers: { "content-type": "application/json" }
|
||||||
})
|
})
|
||||||
.then(checkFetchSuccess("mc response not success"))
|
.then(checkFetchSuccess("mc response not success"))
|
||||||
.then(r => r.json()))
|
.then(r => r.json());
|
||||||
.then(json => fetch(getCorsProxy() + "https://api.minecraftservices.com/minecraft/profile", {
|
let jsonProfile = await fetch(getCorsProxy() + "https://api.minecraftservices.com/minecraft/profile", {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: {"content-type": "application/json", "authorization": "Bearer " + json.access_token}
|
headers: { "content-type": "application/json", "authorization": "Bearer " + mcJson.access_token }
|
||||||
})
|
})
|
||||||
.then(profile => {
|
.then(profile => {
|
||||||
if (profile.status === 404) throw "Minecraft profile not found";
|
if (profile.status === 404)
|
||||||
if (!profile.ok) throw "profile response not success " + profile.status;
|
throw "Minecraft profile not found";
|
||||||
|
if (!profile.ok)
|
||||||
|
throw "profile response not success " + profile.status;
|
||||||
return profile.json();
|
return profile.json();
|
||||||
})
|
});
|
||||||
.then(jsonProfile => {
|
this.accessToken = mcJson.access_token;
|
||||||
this.accessToken = json.access_token;
|
|
||||||
this.name = jsonProfile.name;
|
this.name = jsonProfile.name;
|
||||||
this.id = jsonProfile.id;
|
this.id = jsonProfile.id;
|
||||||
saveRefreshAccounts();
|
saveRefreshAccounts();
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
async checkActive() {
|
||||||
checkActive() {
|
|
||||||
return fetch(getCorsProxy() + "https://api.minecraftservices.com/entitlements/mcstore", {
|
return fetch(getCorsProxy() + "https://api.minecraftservices.com/entitlements/mcstore", {
|
||||||
method: "get",
|
method: "get",
|
||||||
headers: { "authorization": "Bearer " + this.accessToken }
|
headers: { "authorization": "Bearer " + this.accessToken }
|
||||||
}).then(data => data.ok);
|
}).then(data => data.ok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAccountByMcName(name) {
|
function findAccountByMcName(name) {
|
||||||
return activeAccounts.find(it => it.name.toLowerCase() === name.toLowerCase());
|
return activeAccounts.find(it => it.name.toLowerCase() === name.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAccountByMs(username) {
|
function findAccountByMs(username) {
|
||||||
return getActiveAccounts().find(it => it.msUser === username);
|
return getActiveAccounts().find(it => it.msUser === username);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addActiveAccount(acc) {
|
function addActiveAccount(acc) {
|
||||||
activeAccounts.push(acc)
|
activeAccounts.push(acc);
|
||||||
saveRefreshAccounts()
|
saveRefreshAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loginMc(user, pass) {
|
function loginMc(user, pass) {
|
||||||
const clientToken = uuid.v4();
|
const clientToken = uuid.v4();
|
||||||
fetch(getCorsProxy() + "https://authserver.mojang.com/authenticate", {
|
fetch(getCorsProxy() + "https://authserver.mojang.com/authenticate", {
|
||||||
@ -601,16 +548,13 @@ function loginMc(user, pass) {
|
|||||||
}).catch(e => addToast("Failed to login", e));
|
}).catch(e => addToast("Failed to login", e));
|
||||||
$("#form_add_mc input").val("");
|
$("#form_add_mc input").val("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLoginRequest() {
|
function getLoginRequest() {
|
||||||
return { scopes: ["XboxLive.signin"] };
|
return { scopes: ["XboxLive.signin"] };
|
||||||
}
|
}
|
||||||
|
|
||||||
let redirectUrl = "https://viaversion.github.io/VIAaaS/src/main/resources/web/";
|
let redirectUrl = "https://viaversion.github.io/VIAaaS/src/main/resources/web/";
|
||||||
if (location.hostname === "localhost" || whitelistedOrigin.includes(location.origin)) {
|
if (location.hostname === "localhost" || whitelistedOrigin.includes(location.origin)) {
|
||||||
redirectUrl = location.origin + location.pathname;
|
redirectUrl = location.origin + location.pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
const msalConfig = {
|
const msalConfig = {
|
||||||
auth: {
|
auth: {
|
||||||
clientId: azureClientId,
|
clientId: azureClientId,
|
||||||
@ -622,65 +566,56 @@ const msalConfig = {
|
|||||||
storeAuthStateInCookie: false,
|
storeAuthStateInCookie: false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const myMSALObj = new msal.PublicClientApplication(msalConfig);
|
const myMSALObj = new msal.PublicClientApplication(msalConfig);
|
||||||
|
|
||||||
function loginMs() {
|
function loginMs() {
|
||||||
let req = getLoginRequest();
|
let req = getLoginRequest();
|
||||||
req.prompt = "select_account";
|
req["prompt"] = "select_account";
|
||||||
myMSALObj.loginRedirect(req);
|
myMSALObj.loginRedirect(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(() => myMSALObj.handleRedirectPromise().then((resp) => {
|
$(() => myMSALObj.handleRedirectPromise().then((resp) => {
|
||||||
if (resp) {
|
if (resp) {
|
||||||
let found = findAccountByMs(resp.account.username)
|
let found = findAccountByMs(resp.account.username);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
let accNew = new MicrosoftAccount("", "", "", resp.account.username);
|
let accNew = new MicrosoftAccount("", "", "", resp.account.username);
|
||||||
accNew.refresh()
|
accNew.refresh()
|
||||||
.then(() => addActiveAccount(accNew))
|
.then(() => addActiveAccount(accNew))
|
||||||
.catch(e => addToast("Failed to get token", e));
|
.catch(e => addToast("Failed to get token", e));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
found.refresh()
|
found.refresh()
|
||||||
.catch(e => addToast("Failed to refresh token", e));
|
.catch(e => addToast("Failed to refresh token", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function getTokenPopup(username, request) {
|
function getTokenPopup(username, request) {
|
||||||
request.account = myMSALObj.getAccountByUsername(username);
|
request.account = myMSALObj.getAccountByUsername(username);
|
||||||
request.loginHint = username;
|
request.loginHint = username;
|
||||||
return myMSALObj.acquireTokenSilent(request).catch(error => {
|
return myMSALObj.acquireTokenSilent(request)
|
||||||
|
.catch((e) => {
|
||||||
console.warn("silent token acquisition fails.");
|
console.warn("silent token acquisition fails.");
|
||||||
if (error instanceof msal.InteractionRequiredAuthError) {
|
if (error instanceof msal.InteractionRequiredAuthError) {
|
||||||
// fallback to interaction when silent call fails
|
return myMSALObj.acquireTokenPopup(request).catch((error) => console.error(error));
|
||||||
return myMSALObj.acquireTokenPopup(request).catch(error => console.error(error));
|
}
|
||||||
} else {
|
else {
|
||||||
console.warn(error);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Websocket
|
|
||||||
let wsUrl = getWsUrl();
|
let wsUrl = getWsUrl();
|
||||||
let socket = null;
|
let socket = null;
|
||||||
|
|
||||||
function defaultWs() {
|
function defaultWs() {
|
||||||
let url = new URL("ws", new URL(location));
|
let url = new URL("ws", location.href);
|
||||||
url.protocol = "wss";
|
url.protocol = "wss";
|
||||||
return window.location.host.endsWith("github.io") || !window.location.protocol.startsWith("http")
|
return window.location.host.endsWith("github.io") || !window.location.protocol.startsWith("http")
|
||||||
? "wss://localhost:25543/ws" : url.toString();
|
? "wss://localhost:25543/ws" : url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWsUrl() {
|
function getWsUrl() {
|
||||||
return localStorage.getItem("viaaas_ws_url") || defaultWs();
|
return localStorage.getItem("viaaas_ws_url") || defaultWs();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWsUrl(url) {
|
function setWsUrl(url) {
|
||||||
localStorage.setItem("viaaas_ws_url", url);
|
localStorage.setItem("viaaas_ws_url", url);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokens
|
|
||||||
function saveToken(token) {
|
function saveToken(token) {
|
||||||
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
||||||
let tokens = getTokens();
|
let tokens = getTokens();
|
||||||
@ -688,7 +623,6 @@ function saveToken(token) {
|
|||||||
hTokens[wsUrl] = tokens;
|
hTokens[wsUrl] = tokens;
|
||||||
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeToken(token) {
|
function removeToken(token) {
|
||||||
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
||||||
let tokens = getTokens();
|
let tokens = getTokens();
|
||||||
@ -696,42 +630,35 @@ function removeToken(token) {
|
|||||||
hTokens[wsUrl] = tokens;
|
hTokens[wsUrl] = tokens;
|
||||||
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTokens() {
|
function getTokens() {
|
||||||
return (JSON.parse(localStorage.getItem("viaaas_tokens")) || {})[wsUrl] || [];
|
return (JSON.parse(localStorage.getItem("viaaas_tokens")) || {})[wsUrl] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Websocket
|
|
||||||
function listen(token) {
|
function listen(token) {
|
||||||
socket.send(JSON.stringify({ "action": "listen_login_requests", "token": token }));
|
socket.send(JSON.stringify({ "action": "listen_login_requests", "token": token }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function unlisten(id) {
|
function unlisten(id) {
|
||||||
socket.send(JSON.stringify({ "action": "unlisten_login_requests", "uuid": id }));
|
socket.send(JSON.stringify({ "action": "unlisten_login_requests", "uuid": id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmJoin(hash) {
|
function confirmJoin(hash) {
|
||||||
socket.send(JSON.stringify({ action: "session_hash_response", session_hash: hash }));
|
socket.send(JSON.stringify({ action: "session_hash_response", session_hash: hash }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleJoinRequest(parsed) {
|
function handleJoinRequest(parsed) {
|
||||||
authNotification("Allow auth impersonation from VIAaaS instance?\nAccount: "
|
authNotification("Allow auth impersonation from VIAaaS instance?\nAccount: "
|
||||||
+ parsed.user + "\nServer Message: \n"
|
+ parsed.user + "\nServer Message: \n"
|
||||||
+ parsed.message.split(/[\r\n]+/).map(it => "> " + it).join('\n'), () => {
|
+ parsed.message.split(/[\r\n]+/).map((it) => "> " + it).join('\n'), () => {
|
||||||
let account = findAccountByMcName(parsed.user);
|
let account = findAccountByMcName(parsed.user);
|
||||||
if (account) {
|
if (account) {
|
||||||
account.joinGame(parsed.session_hash)
|
account.joinGame(parsed.session_hash)
|
||||||
.then(checkFetchSuccess("code"))
|
|
||||||
.finally(() => confirmJoin(parsed.session_hash))
|
.finally(() => confirmJoin(parsed.session_hash))
|
||||||
.catch((e) => addToast("Couldn't contact session server", "Error: " + e));
|
.catch((e) => addToast("Couldn't contact session server", "Error: " + e));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
confirmJoin(parsed.session_hash);
|
confirmJoin(parsed.session_hash);
|
||||||
addToast("Couldn't find account", "Couldn't find " + parsed.user + ", check Accounts tab");
|
addToast("Couldn't find account", "Couldn't find " + parsed.user + ", check Accounts tab");
|
||||||
}
|
}
|
||||||
}, () => confirmJoin(parsed.session_hash));
|
}, () => confirmJoin(parsed.session_hash));
|
||||||
}
|
}
|
||||||
|
function onWsMsg(event) {
|
||||||
function onSocketMsg(event) {
|
|
||||||
let parsed = JSON.parse(event.data);
|
let parsed = JSON.parse(event.data);
|
||||||
switch (parsed.action) {
|
switch (parsed.action) {
|
||||||
case "ad_login_methods":
|
case "ad_login_methods":
|
||||||
@ -741,7 +668,8 @@ function onSocketMsg(event) {
|
|||||||
case "login_result":
|
case "login_result":
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
addToast("Couldn't verify Minecraft account", "VIAaaS returned failed response");
|
addToast("Couldn't verify Minecraft account", "VIAaaS returned failed response");
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
listen(parsed.token);
|
listen(parsed.token);
|
||||||
saveToken(parsed.token);
|
saveToken(parsed.token);
|
||||||
}
|
}
|
||||||
@ -749,7 +677,8 @@ function onSocketMsg(event) {
|
|||||||
case "listen_login_requests_result":
|
case "listen_login_requests_result":
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
addListeningList(parsed.user, parsed.username, parsed.token);
|
addListeningList(parsed.user, parsed.username, parsed.token);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
removeToken(parsed.token);
|
removeToken(parsed.token);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -761,7 +690,6 @@ function onSocketMsg(event) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleParametersRequest(parsed) {
|
function handleParametersRequest(parsed) {
|
||||||
let url = new URL("https://" + $("#connect_address").val());
|
let url = new URL("https://" + $("#connect_address").val());
|
||||||
socket.send(JSON.stringify({
|
socket.send(JSON.stringify({
|
||||||
@ -769,48 +697,41 @@ function handleParametersRequest(parsed) {
|
|||||||
callback: parsed["callback"],
|
callback: parsed["callback"],
|
||||||
version: $("#connect_version").val(),
|
version: $("#connect_version").val(),
|
||||||
host: url.hostname,
|
host: url.hostname,
|
||||||
port: parseInt(url.port || 25565),
|
port: parseInt(url.port) || 25565,
|
||||||
frontOnline: $("#connect_online").val(),
|
frontOnline: $("#connect_online").val(),
|
||||||
backName: $("#connect_user").val() || undefined
|
backName: $("#connect_user").val() || undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function listenStoredTokens() {
|
function listenStoredTokens() {
|
||||||
getTokens().forEach(listen);
|
getTokens().forEach(listen);
|
||||||
}
|
}
|
||||||
|
function onWsConnect() {
|
||||||
function onConnect() {
|
|
||||||
setWsStatus("connected");
|
setWsStatus("connected");
|
||||||
resetHtml();
|
resetHtml();
|
||||||
listenStoredTokens();
|
listenStoredTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWsError(e) {
|
function onWsError(e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
setWsStatus("socket error");
|
setWsStatus("socket error");
|
||||||
resetHtml();
|
resetHtml();
|
||||||
}
|
}
|
||||||
|
function onWsClose(evt) {
|
||||||
function onDisconnect(evt) {
|
|
||||||
setWsStatus("disconnected with close code " + evt.code + " and reason: " + evt.reason);
|
setWsStatus("disconnected with close code " + evt.code + " and reason: " + evt.reason);
|
||||||
resetHtml();
|
resetHtml();
|
||||||
setTimeout(connect, 5000);
|
setTimeout(connect, 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
setWsStatus("connecting...");
|
setWsStatus("connecting...");
|
||||||
socket = new WebSocket(wsUrl);
|
socket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
socket.onerror = onWsError;
|
socket.onerror = onWsError;
|
||||||
socket.onopen = onConnect;
|
socket.onopen = onWsConnect;
|
||||||
socket.onclose = onDisconnect
|
socket.onclose = onWsClose;
|
||||||
socket.onmessage = onSocketMsg;
|
socket.onmessage = onWsMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendSocket(msg) {
|
function sendSocket(msg) {
|
||||||
if (!socket) {
|
if (!socket) {
|
||||||
console.error("couldn't send msg, socket isn't set");
|
console.error("couldn't send msg, socket isn't set");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
socket.send(msg);
|
socket.send(msg);
|
||||||
}
|
}
|
||||||
|
836
src/main/resources/web/js/page.ts
Normal file
836
src/main/resources/web/js/page.ts
Normal file
@ -0,0 +1,836 @@
|
|||||||
|
/// <reference path='config.js' />
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
// Minecraft.id
|
||||||
|
let urlParams = new URLSearchParams();
|
||||||
|
window.location.hash.substring(1).split("?")
|
||||||
|
.map(it => new URLSearchParams(it)
|
||||||
|
.forEach((a, b) => urlParams.append(b, a)));
|
||||||
|
let mcIdUsername = urlParams.get("username");
|
||||||
|
let mcauth_code = urlParams.get("mcauth_code");
|
||||||
|
let 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, "#");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Page
|
||||||
|
let connectionStatus = document.getElementById("connection_status");
|
||||||
|
let corsStatus = document.getElementById("cors_status");
|
||||||
|
let listening = document.getElementById("listening");
|
||||||
|
let accounts = document.getElementById("accounts-list");
|
||||||
|
let cors_proxy_txt = document.getElementById("cors-proxy") as HTMLInputElement;
|
||||||
|
let ws_url_txt = document.getElementById("ws-url") as HTMLInputElement;
|
||||||
|
let listenVisible = false;
|
||||||
|
// + deltaTime means that the clock is ahead
|
||||||
|
let deltaTime = 0;
|
||||||
|
let workers: Array<Worker> = [];
|
||||||
|
$(() => {
|
||||||
|
workers = new Array(navigator.hardwareConcurrency)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => new Worker("js/worker.js"))
|
||||||
|
workers.forEach(it => it.onmessage = onWorkerMsg);
|
||||||
|
});
|
||||||
|
$(() => {
|
||||||
|
if (navigator.serviceWorker) {
|
||||||
|
navigator.serviceWorker.register("sw.js")
|
||||||
|
.then(() => setTimeout(() => swRefreshFiles(), 1000));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
$(".async-css").attr("rel", "stylesheet");
|
||||||
|
$("form").on("submit", e => e.preventDefault());
|
||||||
|
$("a[href='javascript:']").on("click", e => e.preventDefault());
|
||||||
|
|
||||||
|
cors_proxy_txt.value = getCorsProxy();
|
||||||
|
ws_url_txt.value = getWsUrl();
|
||||||
|
|
||||||
|
$("#form_add_mc").on("submit", () => loginMc($("#mc_email").val() as string, $("#mc_password").val() as string));
|
||||||
|
$("#form_add_ms").on("submit", () => loginMs());
|
||||||
|
$("#form_ws_url").on("submit", () => setWsUrl($("#ws-url").val() as string));
|
||||||
|
$("#form_cors_proxy").on("submit", () => setCorsProxy($("#cors-proxy").val() as string));
|
||||||
|
$("#form_listen").on("submit", () => submittedListen());
|
||||||
|
$("#form_send_token").on("submit", () => submittedSendToken());
|
||||||
|
$("#en_notific").on("click", () => Notification.requestPermission().then(renderActions));
|
||||||
|
$("#listen_continue").on("click", () => clickedListenContinue());
|
||||||
|
window.addEventListener('beforeinstallprompt', e => e.preventDefault());
|
||||||
|
|
||||||
|
ohNo();
|
||||||
|
|
||||||
|
refreshAccountList();
|
||||||
|
setInterval(refreshCorsStatus, 10 * 60 * 1000); // Heroku auto sleeps in 30 min
|
||||||
|
refreshCorsStatus();
|
||||||
|
resetHtml();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(() => {
|
||||||
|
connect();
|
||||||
|
})
|
||||||
|
|
||||||
|
function swRefreshFiles() {
|
||||||
|
// https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker
|
||||||
|
navigator.serviceWorker.ready.then(ready => ready.active.postMessage({
|
||||||
|
action: "cache",
|
||||||
|
urls: performance.getEntriesByType("resource").map(it => it.name)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWsStatus(txt: string) {
|
||||||
|
connectionStatus.innerText = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshCorsStatus() {
|
||||||
|
corsStatus.innerText = "...";
|
||||||
|
getIpAddress(true).then(ip => {
|
||||||
|
return getIpAddress(false).then(ip2 => corsStatus.innerText = "OK " + ip + (ip !== ip2 ? " (different IP)" : ""));
|
||||||
|
}).catch(e => corsStatus.innerText = "error: " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addMcAccountToList(account: McAccount) {
|
||||||
|
let line = $(`<li class='input-group d-flex'>
|
||||||
|
<span class='input-group-text'><img alt="?" src="?" loading="lazy" width=24 class='mc-head'/></span>
|
||||||
|
<span class='form-control mc-user'></span>
|
||||||
|
<button type="button" class='btn btn-danger mc-remove'>Logout</button>
|
||||||
|
</li>`);
|
||||||
|
let txt = account.name;
|
||||||
|
if (account instanceof MicrosoftAccount) txt += " (" + account.msUser + ")";
|
||||||
|
line.find(".mc-user").text(txt);
|
||||||
|
line.find(".mc-remove").on("click", () => account.logout());
|
||||||
|
let head = line.find(".mc-head");
|
||||||
|
head.attr("alt", account.name + "'s head");
|
||||||
|
head.attr("src", "https://crafthead.net/helm/" + account.id);
|
||||||
|
$(accounts).append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUsernameList(username: string) {
|
||||||
|
let line = $("<option class='mc_username'></option>");
|
||||||
|
line.text(username);
|
||||||
|
$("#send_token_user").append(line);
|
||||||
|
$("#backend_user_list").append(line.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAccountList() {
|
||||||
|
accounts.innerHTML = "";
|
||||||
|
$("#send_token_user .mc_username").remove();
|
||||||
|
$("#backend_user_list .mc_username").remove();
|
||||||
|
getActiveAccounts()
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
.forEach(it => {
|
||||||
|
addMcAccountToList(it)
|
||||||
|
addUsernameList(it.name)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#mcIdUsername").text(mcIdUsername);
|
||||||
|
|
||||||
|
function submittedListen() {
|
||||||
|
let user = $("#listen_username").val() as string;
|
||||||
|
if (!user) return;
|
||||||
|
if (($("#listen_online")[0] as HTMLInputElement).checked) {
|
||||||
|
let callbackUrl = new URL(location.href);
|
||||||
|
callbackUrl.search = "";
|
||||||
|
callbackUrl.hash = "#username=" + encodeURIComponent(user);
|
||||||
|
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||||
|
+ "?callback=" + encodeURIComponent(callbackUrl.toString());
|
||||||
|
} else {
|
||||||
|
let taskId = Math.random();
|
||||||
|
workers.forEach(it => it.postMessage({action: "listen_pow", user: user, id: taskId, deltaTime: deltaTime}));
|
||||||
|
addToast("Offline username", "Please wait a minute...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submittedSendToken() {
|
||||||
|
let account = findAccountByMcName($("#send_token_user").val() as string)
|
||||||
|
account.acquireActiveToken()
|
||||||
|
.then(() => {
|
||||||
|
sendSocket(JSON.stringify({
|
||||||
|
"action": "save_access_token",
|
||||||
|
"mc_access_token": account.accessToken
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch(e => addToast("Failed to send access token", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickedListenContinue() {
|
||||||
|
sendSocket(JSON.stringify({
|
||||||
|
"action": "minecraft_id_login",
|
||||||
|
"username": mcIdUsername,
|
||||||
|
"code": mcauth_code
|
||||||
|
}));
|
||||||
|
mcauth_code = null;
|
||||||
|
renderActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderActions() {
|
||||||
|
$("#en_notific").hide();
|
||||||
|
$("#listen_continue").hide();
|
||||||
|
$("#listen_open").hide();
|
||||||
|
$("#send_token_open").hide();
|
||||||
|
|
||||||
|
if (Notification.permission === "default") {
|
||||||
|
$("#en_notific").show();
|
||||||
|
}
|
||||||
|
if (listenVisible) {
|
||||||
|
if (mcIdUsername != null && mcauth_code != null) {
|
||||||
|
$("#listen_continue").show();
|
||||||
|
}
|
||||||
|
$("#listen_open").show();
|
||||||
|
$("#send_token_open").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWorkerMsg(e: MessageEvent) {
|
||||||
|
if (e.data.action === "completed_pow") onCompletedPoW(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCompletedPoW(e: MessageEvent) {
|
||||||
|
addToast("Offline username", "Completed proof of work");
|
||||||
|
workers.forEach(it => it.postMessage({action: "cancel", id: e.data.id}));
|
||||||
|
sendSocket(e.data.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addListeningList(userId: string, username: string, token: string) {
|
||||||
|
let line = $("<p><img alt='?' src='?' loading='lazy' width=24 class='head'/> <span class='username'></span> <button class='btn btn-danger' type='button'>Unlisten</button></p>");
|
||||||
|
line.find(".username").text(username || userId);
|
||||||
|
line.find(".btn").on("click", () => {
|
||||||
|
removeToken(token);
|
||||||
|
line.remove();
|
||||||
|
unlisten(userId);
|
||||||
|
});
|
||||||
|
let head = line.find(".head");
|
||||||
|
head.attr("alt", userId + "'s head");
|
||||||
|
head.attr("src", "https://crafthead.net/helm/" + userId);
|
||||||
|
$(listening).append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToast(title: string, msg: string, yes: () => void = null, no: () => void = null) {
|
||||||
|
let toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto toast_title_msg"></strong>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
<pre class="txt"></pre>
|
||||||
|
<div class="btns mt-2 pt-2 border-top"></div>
|
||||||
|
</div>
|
||||||
|
</div>`);
|
||||||
|
toast.find(".toast_title_msg").text(title);
|
||||||
|
|
||||||
|
let tBody = toast.find(".toast-body");
|
||||||
|
tBody.find(".txt").text(msg);
|
||||||
|
|
||||||
|
let btns = $(tBody).find(".btns");
|
||||||
|
let hasButtons = false;
|
||||||
|
if (yes != null) {
|
||||||
|
hasButtons = true;
|
||||||
|
let btn = $("<button type='button' data-bs-dismiss='toast' class='btn btn-primary btn-sm'>Yes</button>");
|
||||||
|
btn.on("click", yes);
|
||||||
|
btns.append(btn);
|
||||||
|
}
|
||||||
|
if (no != null) {
|
||||||
|
hasButtons = true;
|
||||||
|
let btn = $("<button type='button' data-bs-dismiss='toast' class='btn btn-secondary btn-sm'>No</button>");
|
||||||
|
btn.on("click", no);
|
||||||
|
btns.append(btn);
|
||||||
|
}
|
||||||
|
if (!hasButtons) {
|
||||||
|
btns.addClass("d-none");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#toasts").prepend(toast);
|
||||||
|
// @ts-ignore
|
||||||
|
new bootstrap.Toast(toast[0]).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHtml() {
|
||||||
|
listening.innerHTML = "";
|
||||||
|
listenVisible = false;
|
||||||
|
renderActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
function ohNo() {
|
||||||
|
try {
|
||||||
|
icanhazepoch().then(sec => {
|
||||||
|
const calcDelta = Date.now() - sec * 1000;
|
||||||
|
if (Math.abs(calcDelta) > 10000) {
|
||||||
|
addToast("Time isn't synchronized", "Please synchronize your computer time to NTP servers");
|
||||||
|
deltaTime = calcDelta;
|
||||||
|
console.log("applying delta time " + deltaTime);
|
||||||
|
} else {
|
||||||
|
console.log("time seems synchronized");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
new BroadcastChannel("test");
|
||||||
|
} catch (e) {
|
||||||
|
addToast("Unsupported browser", "This browser doesn't support required APIs");
|
||||||
|
}
|
||||||
|
new Date().getDay() === 3 && console.log("it's snapshot day 🐸 my dudes");
|
||||||
|
new Date().getDate() === 1 && new Date().getMonth() === 3 && addToast("LICENSE EXPIRED", "Your ViaVersion has expired, please renew it at https://viaversion.com/ for only $99");
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Util
|
||||||
|
function checkFetchSuccess(msg: String): (a: Response) => Response {
|
||||||
|
return (r: Response): Response => {
|
||||||
|
if (!r.ok) throw r.status + " " + msg;
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getIpAddress(cors: boolean): Promise<String> {
|
||||||
|
return fetch((cors ? getCorsProxy() : "") + "https://ipv4.icanhazip.com")
|
||||||
|
.then(checkFetchSuccess("code"))
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(it => it.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function icanhazepoch() {
|
||||||
|
return fetch("https://icanhazepoch.com")
|
||||||
|
.then(checkFetchSuccess("code"))
|
||||||
|
.then(r => r.text())
|
||||||
|
.then(it => parseInt(it.trim()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
let notificationCallbacks: Map<string, (action: string) => void> = new Map();
|
||||||
|
$(() => {
|
||||||
|
new BroadcastChannel("viaaas-notification").addEventListener("message", handleSWMsg);
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleSWMsg(event: MessageEvent) {
|
||||||
|
console.log("sw msg: " + event);
|
||||||
|
let data = event.data;
|
||||||
|
let callback = notificationCallbacks.get(data.tag as string);
|
||||||
|
notificationCallbacks.delete(data.tag as string);
|
||||||
|
if (callback == null) return;
|
||||||
|
callback(data.action);
|
||||||
|
}
|
||||||
|
|
||||||
|
function authNotification(msg: string, yes: () => void, no: () => void) {
|
||||||
|
if (!navigator.serviceWorker || Notification.permission !== "granted") {
|
||||||
|
addToast("Allow auth impersonation?", msg, yes, no);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
let tag = uuid.v4();
|
||||||
|
navigator.serviceWorker.ready.then(r => {
|
||||||
|
r.showNotification("Click to allow auth impersonation", {
|
||||||
|
body: msg,
|
||||||
|
tag: tag,
|
||||||
|
vibrate: [200, 10, 100, 200, 100, 10, 100, 10, 200],
|
||||||
|
actions: [
|
||||||
|
{action: "reject", title: "Reject"},
|
||||||
|
{action: "confirm", title: "Confirm"}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
notificationCallbacks.set(tag, action => {
|
||||||
|
if (action === "reject") {
|
||||||
|
no();
|
||||||
|
} else if (!action || action === "confirm") {
|
||||||
|
yes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
notificationCallbacks.delete(tag);
|
||||||
|
}, 30 * 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cors proxy
|
||||||
|
function defaultCors() {
|
||||||
|
return "https://crp123-cors.herokuapp.com/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCorsProxy() {
|
||||||
|
return localStorage.getItem("viaaas_cors_proxy") || defaultCors();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCorsProxy(url: string) {
|
||||||
|
localStorage.setItem("viaaas_cors_proxy", url);
|
||||||
|
refreshCorsStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account manager
|
||||||
|
let activeAccounts: Array<McAccount> = [];
|
||||||
|
|
||||||
|
function loadAccounts() {
|
||||||
|
(JSON.parse(localStorage.getItem("viaaas_mc_accounts")) || []).forEach((it: any) => {
|
||||||
|
if (it.clientToken) {
|
||||||
|
addActiveAccount(new MojangAccount(it.id, it.name, it.accessToken, it.clientToken))
|
||||||
|
} else if (it.msUser && myMSALObj.getAccountByUsername(it.msUser)) {
|
||||||
|
addActiveAccount(new MicrosoftAccount(it.id, it.name, it.accessToken, it.msUser))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(() => loadAccounts());
|
||||||
|
|
||||||
|
function saveRefreshAccounts() {
|
||||||
|
localStorage.setItem("viaaas_mc_accounts", JSON.stringify(getActiveAccounts()))
|
||||||
|
refreshAccountList()
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveAccounts() {
|
||||||
|
return activeAccounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
class McAccount {
|
||||||
|
public id: string;
|
||||||
|
public name: string;
|
||||||
|
public accessToken: string;
|
||||||
|
public loggedOut: boolean;
|
||||||
|
|
||||||
|
constructor(id: string, username: string, accessToken: string) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = username;
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
this.loggedOut = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout(): Promise<void> {
|
||||||
|
activeAccounts = activeAccounts.filter(it => it !== this);
|
||||||
|
saveRefreshAccounts();
|
||||||
|
this.loggedOut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkActive(): Promise<boolean> {
|
||||||
|
return fetch(getCorsProxy() + "https://authserver.mojang.com/validate", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({
|
||||||
|
accessToken: this.accessToken,
|
||||||
|
clientToken: (this as any).clientToken || undefined
|
||||||
|
}),
|
||||||
|
headers: {"content-type": "application/json"}
|
||||||
|
}).then(data => data.ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
async joinGame(hash: string): Promise<void> {
|
||||||
|
await 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh(): Promise<void> {
|
||||||
|
}
|
||||||
|
|
||||||
|
async acquireActiveToken(): Promise<void> {
|
||||||
|
return this.checkActive()
|
||||||
|
.then(success => {
|
||||||
|
if (!success) {
|
||||||
|
return this.refresh().then(() => {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch(e => addToast("Failed to refresh token!", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MojangAccount extends McAccount {
|
||||||
|
public clientToken: string;
|
||||||
|
|
||||||
|
constructor(id: string, username: string, accessToken: string, clientToken: string) {
|
||||||
|
super(id, username, accessToken);
|
||||||
|
this.clientToken = clientToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async logout() {
|
||||||
|
await super.logout();
|
||||||
|
await 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
override async refresh() {
|
||||||
|
console.log("refreshing " + this.id);
|
||||||
|
let jsonResp = await fetch(getCorsProxy() + "https://authserver.mojang.com/refresh", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({
|
||||||
|
accessToken: this.accessToken,
|
||||||
|
clientToken: this.clientToken
|
||||||
|
}),
|
||||||
|
headers: {"content-type": "application/json"},
|
||||||
|
})
|
||||||
|
.then(async r => {
|
||||||
|
if (r.status === 403) {
|
||||||
|
try {
|
||||||
|
await this.logout();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
throw "403, token expired?";
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
})
|
||||||
|
.then(checkFetchSuccess("code"))
|
||||||
|
.then(r => r.json());
|
||||||
|
|
||||||
|
console.log("refreshed " + jsonResp.selectedProfile.id);
|
||||||
|
this.accessToken = jsonResp.accessToken;
|
||||||
|
this.clientToken = jsonResp.clientToken;
|
||||||
|
this.name = jsonResp.selectedProfile.name;
|
||||||
|
this.id = jsonResp.selectedProfile.id;
|
||||||
|
saveRefreshAccounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MicrosoftAccount extends McAccount {
|
||||||
|
public msUser: string;
|
||||||
|
|
||||||
|
constructor(id: string, username: string, accessToken: string, msUser: string) {
|
||||||
|
super(id, username, accessToken);
|
||||||
|
this.msUser = msUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async logout() {
|
||||||
|
await super.logout();
|
||||||
|
|
||||||
|
let msAccount = myMSALObj.getAccountByUsername(this.msUser);
|
||||||
|
if (!msAccount) return;
|
||||||
|
|
||||||
|
const logoutRequest = {account: msAccount};
|
||||||
|
await myMSALObj.logoutPopup(logoutRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async refresh(): Promise<void> {
|
||||||
|
let msTokenResp = await getTokenPopup(this.msUser, getLoginRequest());
|
||||||
|
// noinspection HttpUrlsUsage
|
||||||
|
let xboxJson = await fetch("https://user.auth.xboxlive.com/user/authenticate", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({
|
||||||
|
Properties: {
|
||||||
|
AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com",
|
||||||
|
RpsTicket: "d=" + msTokenResp.accessToken
|
||||||
|
}, RelyingParty: "http://auth.xboxlive.com", TokenType: "JWT"
|
||||||
|
}),
|
||||||
|
headers: {"content-type": "application/json"}
|
||||||
|
})
|
||||||
|
.then(checkFetchSuccess("xbox response not success"))
|
||||||
|
.then(r => r.json());
|
||||||
|
let xstsJson = await fetch("https://xsts.auth.xboxlive.com/xsts/authorize", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({
|
||||||
|
Properties: {SandboxId: "RETAIL", UserTokens: [xboxJson.Token]},
|
||||||
|
RelyingParty: "rp://api.minecraftservices.com/", TokenType: "JWT"
|
||||||
|
}),
|
||||||
|
headers: {"content-type": "application/json"}
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.status !== 401) return resp;
|
||||||
|
return resp.json().then(errorData => {
|
||||||
|
let error = errorData.XErr;
|
||||||
|
switch (error) {
|
||||||
|
case 2148916233:
|
||||||
|
throw "Xbox account not found";
|
||||||
|
case 2148916235:
|
||||||
|
throw "Xbox Live not available in this country";
|
||||||
|
case 2148916238:
|
||||||
|
throw "Account is underage, add it to a family";
|
||||||
|
}
|
||||||
|
throw "xsts error code " + error;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(checkFetchSuccess("xsts response not success"))
|
||||||
|
.then(r => r.json());
|
||||||
|
let mcJson = await fetch(getCorsProxy() + "https://api.minecraftservices.com/authentication/login_with_xbox", {
|
||||||
|
method: "post",
|
||||||
|
body: JSON.stringify({identityToken: "XBL3.0 x=" + xstsJson.DisplayClaims.xui[0].uhs + ";" + xstsJson.Token}),
|
||||||
|
headers: {"content-type": "application/json"}
|
||||||
|
})
|
||||||
|
.then(checkFetchSuccess("mc response not success"))
|
||||||
|
.then(r => r.json());
|
||||||
|
let jsonProfile = await fetch(getCorsProxy() + "https://api.minecraftservices.com/minecraft/profile", {
|
||||||
|
method: "get",
|
||||||
|
headers: {"content-type": "application/json", "authorization": "Bearer " + mcJson.access_token}
|
||||||
|
})
|
||||||
|
.then(profile => {
|
||||||
|
if (profile.status === 404) throw "Minecraft profile not found";
|
||||||
|
if (!profile.ok) throw "profile response not success " + profile.status;
|
||||||
|
return profile.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.accessToken = mcJson.access_token;
|
||||||
|
this.name = jsonProfile.name;
|
||||||
|
this.id = jsonProfile.id;
|
||||||
|
saveRefreshAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
override async checkActive() {
|
||||||
|
return fetch(getCorsProxy() + "https://api.minecraftservices.com/entitlements/mcstore", {
|
||||||
|
method: "get",
|
||||||
|
headers: {"authorization": "Bearer " + this.accessToken}
|
||||||
|
}).then(data => data.ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAccountByMcName(name: string) {
|
||||||
|
return activeAccounts.find(it => it.name.toLowerCase() === name.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
function findAccountByMs(username: string) {
|
||||||
|
return getActiveAccounts().find(it => (it as any).msUser === username);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addActiveAccount(acc: McAccount) {
|
||||||
|
activeAccounts.push(acc)
|
||||||
|
saveRefreshAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loginMc(user: string, pass: string) {
|
||||||
|
// @ts-ignore
|
||||||
|
const 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 => {
|
||||||
|
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 getLoginRequest() {
|
||||||
|
return {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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msalConfig = {
|
||||||
|
auth: {
|
||||||
|
clientId: azureClientId,
|
||||||
|
authority: "https://login.microsoftonline.com/consumers/",
|
||||||
|
redirectUri: redirectUrl,
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
cacheLocation: "localStorage",
|
||||||
|
storeAuthStateInCookie: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const myMSALObj = new msal.PublicClientApplication(msalConfig);
|
||||||
|
|
||||||
|
function loginMs() {
|
||||||
|
let req = getLoginRequest();
|
||||||
|
(req as any)["prompt"] = "select_account";
|
||||||
|
myMSALObj.loginRedirect(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(() => myMSALObj.handleRedirectPromise().then((resp: any) => {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
function getTokenPopup(username: string, request: any) {
|
||||||
|
request.account = myMSALObj.getAccountByUsername(username);
|
||||||
|
request.loginHint = username;
|
||||||
|
return myMSALObj.acquireTokenSilent(request)
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.warn("silent token acquisition fails.");
|
||||||
|
// @ts-ignore
|
||||||
|
if (error instanceof msal.InteractionRequiredAuthError) {
|
||||||
|
// fallback to interaction when silent call fails
|
||||||
|
return myMSALObj.acquireTokenPopup(request).catch((error: any) => console.error(error));
|
||||||
|
} else {
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Websocket
|
||||||
|
let wsUrl = getWsUrl();
|
||||||
|
let socket: WebSocket | null = null;
|
||||||
|
|
||||||
|
function defaultWs() {
|
||||||
|
let url = new URL("ws", location.href);
|
||||||
|
url.protocol = "wss";
|
||||||
|
return window.location.host.endsWith("github.io") || !window.location.protocol.startsWith("http")
|
||||||
|
? "wss://localhost:25543/ws" : url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWsUrl() {
|
||||||
|
return localStorage.getItem("viaaas_ws_url") || defaultWs();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setWsUrl(url: string) {
|
||||||
|
localStorage.setItem("viaaas_ws_url", url);
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens
|
||||||
|
function saveToken(token: string) {
|
||||||
|
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
||||||
|
let tokens = getTokens();
|
||||||
|
tokens.push(token);
|
||||||
|
hTokens[wsUrl] = tokens;
|
||||||
|
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeToken(token: string) {
|
||||||
|
let hTokens = JSON.parse(localStorage.getItem("viaaas_tokens")) || {};
|
||||||
|
let tokens = getTokens();
|
||||||
|
tokens = tokens.filter(it => it !== token);
|
||||||
|
hTokens[wsUrl] = tokens;
|
||||||
|
localStorage.setItem("viaaas_tokens", JSON.stringify(hTokens));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokens(): Array<String> {
|
||||||
|
return (JSON.parse(localStorage.getItem("viaaas_tokens")) || {})[wsUrl] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Websocket
|
||||||
|
function listen(token: String) {
|
||||||
|
socket.send(JSON.stringify({"action": "listen_login_requests", "token": token}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlisten(id: String) {
|
||||||
|
socket.send(JSON.stringify({"action": "unlisten_login_requests", "uuid": id}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmJoin(hash: String) {
|
||||||
|
socket.send(JSON.stringify({action: "session_hash_response", session_hash: hash}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleJoinRequest(parsed: any) {
|
||||||
|
authNotification("Allow auth impersonation from VIAaaS instance?\nAccount: "
|
||||||
|
+ parsed.user + "\nServer Message: \n"
|
||||||
|
+ parsed.message.split(/[\r\n]+/).map((it: string) => "> " + it).join('\n'), () => {
|
||||||
|
let account = findAccountByMcName(parsed.user);
|
||||||
|
if (account) {
|
||||||
|
account.joinGame(parsed.session_hash)
|
||||||
|
.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");
|
||||||
|
}
|
||||||
|
}, () => confirmJoin(parsed.session_hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWsMsg(event: MessageEvent) {
|
||||||
|
let parsed = JSON.parse(event.data);
|
||||||
|
switch (parsed.action) {
|
||||||
|
case "ad_login_methods":
|
||||||
|
listenVisible = true;
|
||||||
|
renderActions();
|
||||||
|
break;
|
||||||
|
case "login_result":
|
||||||
|
if (!parsed.success) {
|
||||||
|
addToast("Couldn't verify Minecraft account", "VIAaaS returned failed response");
|
||||||
|
} else {
|
||||||
|
listen(parsed.token);
|
||||||
|
saveToken(parsed.token);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "listen_login_requests_result":
|
||||||
|
if (parsed.success) {
|
||||||
|
addListeningList(parsed.user, parsed.username, parsed.token);
|
||||||
|
} else {
|
||||||
|
removeToken(parsed.token);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "session_hash_request":
|
||||||
|
handleJoinRequest(parsed);
|
||||||
|
break;
|
||||||
|
case "parameters_request":
|
||||||
|
handleParametersRequest(parsed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleParametersRequest(parsed: any) {
|
||||||
|
let url = new URL("https://" + $("#connect_address").val());
|
||||||
|
socket.send(JSON.stringify({
|
||||||
|
action: "parameters_response",
|
||||||
|
callback: parsed["callback"],
|
||||||
|
version: $("#connect_version").val(),
|
||||||
|
host: url.hostname,
|
||||||
|
port: parseInt(url.port) || 25565,
|
||||||
|
frontOnline: $("#connect_online").val(),
|
||||||
|
backName: $("#connect_user").val() || undefined
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function listenStoredTokens() {
|
||||||
|
getTokens().forEach(listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWsConnect() {
|
||||||
|
setWsStatus("connected");
|
||||||
|
resetHtml();
|
||||||
|
listenStoredTokens();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWsError(e: any) {
|
||||||
|
console.log(e);
|
||||||
|
setWsStatus("socket error");
|
||||||
|
resetHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWsClose(evt: CloseEvent) {
|
||||||
|
setWsStatus("disconnected with close code " + evt.code + " and reason: " + evt.reason);
|
||||||
|
resetHtml();
|
||||||
|
setTimeout(connect, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
setWsStatus("connecting...");
|
||||||
|
socket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
socket.onerror = onWsError;
|
||||||
|
socket.onopen = onWsConnect;
|
||||||
|
socket.onclose = onWsClose;
|
||||||
|
socket.onmessage = onWsMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSocket(msg: string) {
|
||||||
|
if (!socket) {
|
||||||
|
console.error("couldn't send msg, socket isn't set");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socket.send(msg);
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
|
"use strict";
|
||||||
importScripts("https://cdnjs.cloudflare.com/ajax/libs/js-sha512/0.8.0/sha512.min.js");
|
importScripts("https://cdnjs.cloudflare.com/ajax/libs/js-sha512/0.8.0/sha512.min.js");
|
||||||
|
|
||||||
let pending = [];
|
let pending = [];
|
||||||
|
|
||||||
onmessage = function (e) {
|
self.addEventListener("message", e => {
|
||||||
if (e.data.action === "listen_pow") startPoW(e);
|
if (e.data.action === "listen_pow") startPoW(e);
|
||||||
if (e.data.action === "cancel") removePending(e.data.id);
|
if (e.data.action === "cancel") removePending(e.data.id);
|
||||||
}
|
});
|
||||||
|
|
||||||
function removePending(id) {
|
function removePending(id) {
|
||||||
pending = pending.filter(it => it !== id);
|
pending = pending.filter(it => it !== id);
|
||||||
@ -16,12 +17,16 @@ function startPoW(e) {
|
|||||||
listenPoW(e);
|
listenPoW(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPending(id) {
|
||||||
|
return pending.includes(id);
|
||||||
|
}
|
||||||
|
|
||||||
function listenPoW(e) {
|
function listenPoW(e) {
|
||||||
let user = e.data.user;
|
let user = e.data.user;
|
||||||
let msg = null;
|
let msg = null;
|
||||||
let endTime = Date.now() + 1000;
|
let endTime = Date.now() + 1000;
|
||||||
do {
|
do {
|
||||||
if (!pending.includes(e.data.id)) return; // cancelled
|
if (!isPending(e.data.id)) return; // cancelled
|
||||||
|
|
||||||
msg = JSON.stringify({
|
msg = JSON.stringify({
|
||||||
action: "offline_login",
|
action: "offline_login",
|
||||||
@ -31,10 +36,13 @@ function listenPoW(e) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (Date.now() >= endTime) {
|
if (Date.now() >= endTime) {
|
||||||
setTimeout(() => listenPoW(e), 0);
|
setTimeout(() => listenPoW(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while (!sha512(msg).startsWith("00000"));
|
} while (!sha512(msg).startsWith("00000"));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isPending(e.data.id)) return;
|
||||||
postMessage({id: e.data.id, action: "completed_pow", msg: msg});
|
postMessage({id: e.data.id, action: "completed_pow", msg: msg});
|
||||||
|
})
|
||||||
}
|
}
|
19
src/main/resources/web/tsconfig.json
Normal file
19
src/main/resources/web/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2018",
|
||||||
|
"module": "ES2015",
|
||||||
|
"lib": [
|
||||||
|
"ES2018",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"removeComments": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"js/**.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user