mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2024-11-21 11:55:15 +01:00
fixed sw cache, better ui?
This commit is contained in:
parent
171c00a148
commit
c25745cdc1
@ -71,7 +71,7 @@ dependencies {
|
||||
implementation("io.netty:netty-tcnative-boringssl-static:2.0.43.Final")
|
||||
implementation("io.netty.incubator:netty-incubator-transport-native-io_uring:0.0.8.Final:linux-x86_64")
|
||||
|
||||
implementation("com.google.guava:guava:30.1.1-jre")
|
||||
implementation("com.google.guava:guava:31.0-jre")
|
||||
implementation("com.velocitypowered:velocity-native:3.0.1")
|
||||
implementation("net.coobird:thumbnailator:0.4.14")
|
||||
implementation("org.powernukkit.fastutil:fastutil-lite:8.1.1")
|
||||
|
@ -39,22 +39,26 @@ class WebDashboardServer {
|
||||
val clients = ConcurrentHashMap<WebSocketSession, WebClient>()
|
||||
val jwtAlgorithm = Algorithm.HMAC256(VIAaaSConfig.jwtSecret)
|
||||
|
||||
fun generateToken(account: UUID): String {
|
||||
fun generateToken(account: UUID, username: String): String {
|
||||
return JWT.create()
|
||||
.withIssuedAt(Date())
|
||||
.withExpiresAt(Date.from(Instant.now().plus(Duration.ofDays(30))))
|
||||
.withSubject(account.toString())
|
||||
.withClaim("name", username)
|
||||
.withAudience("viaaas_listen")
|
||||
.withIssuer("viaaas")
|
||||
.sign(jwtAlgorithm)
|
||||
}
|
||||
|
||||
fun checkToken(token: String): UUID? {
|
||||
data class UserInfo(val id: UUID, val name: String?)
|
||||
|
||||
fun parseToken(token: String): UserInfo? {
|
||||
return try {
|
||||
val verified = JWT.require(jwtAlgorithm)
|
||||
.withAnyOfAudience("viaaas_listen")
|
||||
.build()
|
||||
.verify(token)
|
||||
UUID.fromString(verified.subject)
|
||||
UserInfo(UUID.fromString(verified.subject), verified.getClaim("name").asString())
|
||||
} catch (e: JWTVerificationException) {
|
||||
null
|
||||
}
|
||||
|
@ -19,95 +19,108 @@ class WebLogin : WebState {
|
||||
webClient.ws.flush()
|
||||
}
|
||||
|
||||
private fun loginSuccessJson(username: String, uuid: UUID, token: String): JsonObject {
|
||||
return JsonObject().also {
|
||||
it.addProperty("action", "login_result")
|
||||
it.addProperty("success", true)
|
||||
it.addProperty("username", username)
|
||||
it.addProperty("uuid", uuid.toString())
|
||||
it.addProperty("token", token)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginNotSuccess(): JsonObject {
|
||||
return JsonObject().also {
|
||||
it.addProperty("action", "login_result")
|
||||
it.addProperty("success", false)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleOfflineLogin(webClient: WebClient, msg: String, obj: JsonObject) {
|
||||
if (!sha512Hex(msg.toByteArray(Charsets.UTF_8)).startsWith("00000")) throw StacklessException("PoW failed")
|
||||
if ((obj.getAsJsonPrimitive("date").asLong - System.currentTimeMillis())
|
||||
.absoluteValue > Duration.ofSeconds(20).toMillis()
|
||||
) {
|
||||
throw StacklessException("Invalid PoW date")
|
||||
}
|
||||
val username = obj["username"].asString.trim()
|
||||
val uuid = generateOfflinePlayerUuid(username)
|
||||
|
||||
val token = webClient.server.generateToken(uuid, username)
|
||||
webClient.ws.send(loginSuccessJson(username, uuid, token).toString())
|
||||
|
||||
webLogger.info("Token gen: ${webClient.id}: offline $username $uuid")
|
||||
}
|
||||
|
||||
private suspend fun handleMcIdLogin(webClient: WebClient, msg: String, obj: JsonObject) {
|
||||
val username = obj["username"].asString
|
||||
val code = obj["code"].asString
|
||||
|
||||
val check = AspirinServer.httpClient.submitForm<JsonObject>(
|
||||
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
|
||||
formParameters = parametersOf("code", code),
|
||||
)
|
||||
|
||||
if (check.getAsJsonPrimitive("valid").asBoolean) {
|
||||
val mcIdUser = check["username"].asString
|
||||
val uuid = check["uuid"]?.asString?.let { parseUndashedId(it.replace("-", "")) }
|
||||
?: webClient.server.usernameIdCache[mcIdUser].await()
|
||||
?: throw StacklessException("Failed to get UUID from minecraft.id")
|
||||
|
||||
val token = webClient.server.generateToken(uuid, mcIdUser)
|
||||
webClient.ws.send(loginSuccessJson(mcIdUser, uuid, token).toString())
|
||||
|
||||
webLogger.info("Token gen: ${webClient.id}: $mcIdUser $uuid")
|
||||
} else {
|
||||
webClient.ws.send(loginNotSuccess().toString())
|
||||
webLogger.info("Token gen fail: ${webClient.id}: $username")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleListenLogins(webClient: WebClient, msg: String, obj: JsonObject) {
|
||||
val token = obj.getAsJsonPrimitive("token").asString
|
||||
val user = webClient.server.parseToken(token)
|
||||
val response = JsonObject().also {
|
||||
it.addProperty("action", "listen_login_requests_result")
|
||||
it.addProperty("token", token)
|
||||
}
|
||||
if (user != null && webClient.listenId(user.id)) {
|
||||
response.addProperty("success", true)
|
||||
response.addProperty("user", user.id.toString())
|
||||
response.addProperty("username", user.name)
|
||||
webLogger.info("Listen: ${webClient.id}: $user")
|
||||
} else {
|
||||
response.addProperty("success", false)
|
||||
webLogger.info("Listen fail: ${webClient.id}")
|
||||
}
|
||||
webClient.ws.send(response.toString())
|
||||
}
|
||||
|
||||
private suspend fun handleUnlisten(webClient: WebClient, msg: String, obj: JsonObject) {
|
||||
val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString)
|
||||
webLogger.info("Unlisten: ${webClient.id}: $uuid")
|
||||
val response = JsonObject().also {
|
||||
it.addProperty("action", "unlisten_login_requests_result")
|
||||
it.addProperty("uuid", uuid.toString())
|
||||
it.addProperty("success", webClient.unlistenId(uuid))
|
||||
}
|
||||
webClient.ws.send(response.toString())
|
||||
}
|
||||
|
||||
private suspend fun handleSessionResponse(webClient: WebClient, msg: String, obj: JsonObject) {
|
||||
val hash = obj["session_hash"].asString
|
||||
webClient.server.sessionHashCallbacks.getIfPresent(hash)?.complete(Unit)
|
||||
}
|
||||
|
||||
override suspend fun onMessage(webClient: WebClient, msg: String) {
|
||||
val obj = JsonParser.parseString(msg) as JsonObject
|
||||
|
||||
when (obj.getAsJsonPrimitive("action").asString) {
|
||||
"offline_login" -> {
|
||||
if (!sha512Hex(msg.toByteArray(Charsets.UTF_8)).startsWith("00000")) throw StacklessException("PoW failed")
|
||||
if ((obj.getAsJsonPrimitive("date").asLong - System.currentTimeMillis())
|
||||
.absoluteValue > Duration.ofSeconds(20).toMillis()
|
||||
) {
|
||||
throw StacklessException("Invalid PoW date")
|
||||
}
|
||||
val username = obj["username"].asString.trim()
|
||||
val uuid = generateOfflinePlayerUuid(username)
|
||||
|
||||
val token = webClient.server.generateToken(uuid)
|
||||
webClient.ws.send(JsonObject().also {
|
||||
it.addProperty("action", "login_result")
|
||||
it.addProperty("success", true)
|
||||
it.addProperty("username", username)
|
||||
it.addProperty("uuid", uuid.toString())
|
||||
it.addProperty("token", token)
|
||||
}.toString())
|
||||
|
||||
webLogger.info("Token gen: ${webClient.id}: offline $username $uuid")
|
||||
}
|
||||
"minecraft_id_login" -> {
|
||||
val username = obj["username"].asString
|
||||
val code = obj["code"].asString
|
||||
|
||||
val check = AspirinServer.httpClient.submitForm<JsonObject>(
|
||||
"https://api.minecraft.id/gateway/verify/${URLEncoder.encode(username, Charsets.UTF_8)}",
|
||||
formParameters = parametersOf("code", code),
|
||||
)
|
||||
|
||||
if (check.getAsJsonPrimitive("valid").asBoolean) {
|
||||
val mcIdUser = check["username"].asString
|
||||
val uuid = check["uuid"]?.asString?.let { parseUndashedId(it.replace("-", "")) }
|
||||
?: webClient.server.usernameIdCache[mcIdUser].await()
|
||||
?: throw StacklessException("Failed to get UUID from minecraft.id")
|
||||
|
||||
val token = webClient.server.generateToken(uuid)
|
||||
webClient.ws.send(JsonObject().also {
|
||||
it.addProperty("action", "login_result")
|
||||
it.addProperty("success", true)
|
||||
it.addProperty("username", mcIdUser)
|
||||
it.addProperty("uuid", uuid.toString())
|
||||
it.addProperty("token", token)
|
||||
}.toString())
|
||||
|
||||
webLogger.info("Token gen: ${webClient.id}: $mcIdUser $uuid")
|
||||
} else {
|
||||
webClient.ws.send(JsonObject().also {
|
||||
it.addProperty("action", "login_result")
|
||||
it.addProperty("success", false)
|
||||
}.toString())
|
||||
webLogger.info("Token gen fail: ${webClient.id}: $username")
|
||||
}
|
||||
}
|
||||
"listen_login_requests" -> {
|
||||
val token = obj.getAsJsonPrimitive("token").asString
|
||||
val user = webClient.server.checkToken(token)
|
||||
val response = JsonObject().also {
|
||||
it.addProperty("action", "listen_login_requests_result")
|
||||
it.addProperty("token", token)
|
||||
}
|
||||
if (user != null && webClient.listenId(user)) {
|
||||
response.addProperty("success", true)
|
||||
response.addProperty("user", user.toString())
|
||||
webLogger.info("Listen: ${webClient.id}: $user")
|
||||
} else {
|
||||
response.addProperty("success", false)
|
||||
webLogger.info("Listen fail: ${webClient.id}")
|
||||
}
|
||||
webClient.ws.send(response.toString())
|
||||
}
|
||||
"unlisten_login_requests" -> {
|
||||
val uuid = UUID.fromString(obj.getAsJsonPrimitive("uuid").asString)
|
||||
webLogger.info("Unlisten: ${webClient.id}: $uuid")
|
||||
val response = JsonObject().also {
|
||||
it.addProperty("action", "unlisten_login_requests_result")
|
||||
it.addProperty("uuid", uuid.toString())
|
||||
it.addProperty("success", webClient.unlistenId(uuid))
|
||||
}
|
||||
webClient.ws.send(response.toString())
|
||||
}
|
||||
"session_hash_response" -> {
|
||||
val hash = obj["session_hash"].asString
|
||||
webClient.server.sessionHashCallbacks.getIfPresent(hash)?.complete(Unit)
|
||||
}
|
||||
"offline_login" -> handleOfflineLogin(webClient, msg, obj)
|
||||
"minecraft_id_login" -> handleMcIdLogin(webClient, msg, obj)
|
||||
"listen_login_requests" -> handleListenLogins(webClient, msg, obj)
|
||||
"unlisten_login_requests" -> handleUnlisten(webClient, msg, obj)
|
||||
"session_hash_response" -> handleSessionResponse(webClient, msg, obj)
|
||||
else -> throw StacklessException("invalid action!")
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*.
|
||||
<meta content="#0468a1" name="theme-color">
|
||||
|
||||
<!-- https://www.srihash.org/ -->
|
||||
<link class="async-css" rel="preload" as="stylesheet"
|
||||
<link class="async-css" rel="preload" as="style"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.1/css/bootstrap.min.css"
|
||||
integrity="sha512-6KY5s6UI5J7SVYuZB4S/CZMyPylqyyNZco376NM2Z8Sb8OxEdp02e1jkKk/wZxIEmjQ6DRCEBhni+gpr9c4tvA=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
||||
@ -48,8 +48,6 @@ script-src 'self' https://*.cloudflare.com/ https://alcdn.msauth.net/ https://*.
|
||||
crossorigin="anonymous"></script>
|
||||
<script defer src="js/config.js"></script>
|
||||
<script defer src="js/page.js"></script>
|
||||
<link rel="preload" href="js/worker.js" as="worker">
|
||||
<link rel="preload" href="sw.js" as="serviceworker">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-2">
|
||||
|
@ -39,7 +39,8 @@ $(() => {
|
||||
});
|
||||
$(() => {
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register("sw.js").then(() => swCacheFiles());
|
||||
navigator.serviceWorker.register("sw.js")
|
||||
.then(() => setTimeout(() => swRefreshFiles(), 1000));
|
||||
}
|
||||
})
|
||||
|
||||
@ -48,7 +49,6 @@ window.addEventListener('beforeinstallprompt', e => e.preventDefault());
|
||||
$(() => {
|
||||
$(".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();
|
||||
@ -70,11 +70,12 @@ $(() => {
|
||||
connect();
|
||||
})
|
||||
|
||||
function swCacheFiles() {
|
||||
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)
|
||||
})); // https://stackoverflow.com/questions/46830493/is-there-any-way-to-cache-all-files-of-defined-folder-path-in-service-worker
|
||||
}));
|
||||
}
|
||||
|
||||
function setWsStatus(txt) {
|
||||
@ -96,7 +97,7 @@ function addMcAccountToList(account) {
|
||||
let line = $(`<li class='input-group d-flex'>
|
||||
<span class='input-group-text'><img loading="lazy" width=24 class='mc-head'/></span>
|
||||
<span class='form-control mc-user'></span>
|
||||
<a class='btn btn-danger mc-remove' href='javascript:'>Logout</a>
|
||||
<button type="button" class='btn btn-danger mc-remove'>Logout</button>
|
||||
</li>`);
|
||||
let txt = account.name;
|
||||
if (account instanceof MicrosoftAccount) txt += " (" + account.msUser + ")";
|
||||
@ -126,8 +127,7 @@ function refreshAccountList() {
|
||||
function renderActions() {
|
||||
actions.innerHTML = "";
|
||||
if (Notification.permission === "default") {
|
||||
actions.innerHTML += '<p><a href="javascript:" id="notificate">Enable notifications</a></p>';
|
||||
$("#notificate").on("click", () => Notification.requestPermission().then(renderActions)); // i'm lazy
|
||||
addAction("Enable notifications", () => Notification.requestPermission().then(renderActions));
|
||||
}
|
||||
if (listenVisible) {
|
||||
if (mcIdUsername != null && mcauth_code != null) {
|
||||
@ -141,7 +141,7 @@ function renderActions() {
|
||||
renderActions();
|
||||
});
|
||||
}
|
||||
addAction("Listen to frontend premium login in VIAaaS instance", () => {
|
||||
addAction("Listen to frontend premium login", () => {
|
||||
let user = prompt("Premium username (case-sensitive): ", "");
|
||||
if (!user) return;
|
||||
let callbackUrl = new URL(location);
|
||||
@ -150,7 +150,7 @@ function renderActions() {
|
||||
location.href = "https://api.minecraft.id/gateway/start/" + encodeURIComponent(user)
|
||||
+ "?callback=" + encodeURIComponent(callbackUrl);
|
||||
});
|
||||
addAction("Listen to frontend offline login in VIAaaS instance", () => {
|
||||
addAction("Listen to frontend offline login", () => {
|
||||
let user = prompt("Offline username (case-sensitive):", "");
|
||||
if (!user) return;
|
||||
let taskId = Math.random();
|
||||
@ -171,21 +171,17 @@ function onCompletedPoW(e) {
|
||||
}
|
||||
|
||||
function addAction(text, onClick) {
|
||||
let p = document.createElement("p");
|
||||
let link = document.createElement("a");
|
||||
p.appendChild(link);
|
||||
link.innerText = text;
|
||||
link.href = "javascript:";
|
||||
link.onclick = onClick;
|
||||
actions.appendChild(p);
|
||||
let line = $("<button type='button' class='btn btn-primary'></button>")
|
||||
line.text(text).on("click", onClick);
|
||||
$(actions).append(line);
|
||||
}
|
||||
|
||||
function addListeningList(user, token) {
|
||||
let line = $("<p><img loading='lazy' width=24 class='head'/> <span class='username'></span> <a href='javascript:'>Unlisten</a></p>");
|
||||
line.find(".username").text(user);
|
||||
line.find("a").on("click", () => {
|
||||
function addListeningList(user, username, token) {
|
||||
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 || user);
|
||||
line.find(".btn").on("click", () => {
|
||||
removeToken(token);
|
||||
listening.removeChild(p);
|
||||
line.remove();
|
||||
unlisten(user);
|
||||
});
|
||||
let head = line.find(".head");
|
||||
@ -719,7 +715,7 @@ function onSocketMsg(event) {
|
||||
}
|
||||
} else if (parsed.action === "listen_login_requests_result") {
|
||||
if (parsed.success) {
|
||||
addListeningList(parsed.user, parsed.token);
|
||||
addListeningList(parsed.user, parsed.username, parsed.token);
|
||||
} else {
|
||||
removeToken(parsed.token);
|
||||
}
|
||||
|
@ -37,10 +37,4 @@ function listenPoW(e) {
|
||||
} while (!sha512(msg).startsWith("00000"));
|
||||
|
||||
postMessage({id: e.data.id, action: "completed_pow", msg: msg});
|
||||
}
|
||||
|
||||
/* function sha512(s) {
|
||||
const shaObj = new jsSHA("SHA-512", "TEXT", { encoding: "UTF8" });
|
||||
shaObj.update(s);
|
||||
return shaObj.getHash("HEX");
|
||||
} */
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}],
|
||||
"start_url": "./index.html",
|
||||
"start_url": "./",
|
||||
"background_color": "#3367D6",
|
||||
"display": "standalone",
|
||||
"theme_color": "#0468a1",
|
||||
|
@ -1,49 +1,56 @@
|
||||
// https://stackoverflow.com/questions/42127148/service-worker-communicate-to-clients
|
||||
let viac = new BroadcastChannel("viaaas-notification");
|
||||
self.addEventListener("notificationclick", event => {
|
||||
event.preventDefault();
|
||||
event.notification.close();
|
||||
viac.postMessage({tag: event.notification.tag, action: event.action});
|
||||
event.preventDefault();
|
||||
event.notification.close();
|
||||
viac.postMessage({tag: event.notification.tag, action: event.action});
|
||||
});
|
||||
|
||||
// stolen from https://github.com/mozilla/serviceworker-cookbook/blob/master/strategy-network-or-cache/service-worker.js (MIT license)
|
||||
|
||||
var CACHE = "network-or-cache";
|
||||
let cacheId = "viaaas";
|
||||
|
||||
self.addEventListener("install", evt => {
|
||||
evt.waitUntil(cache(["./index.html"]));
|
||||
self.addEventListener("install", () => {
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", evt => {
|
||||
return; // todo fix
|
||||
if (!shouldCache(evt.request.url)
|
||||
|| evt.request.method != "GET") return;
|
||||
evt.respondWith(
|
||||
fromCache(evt.request).catch(() => fromNetwork(evt.request))
|
||||
);
|
||||
if (!shouldCache(evt.request.url)
|
||||
|| evt.request.method !== "GET") return;
|
||||
evt.respondWith(
|
||||
fromCache(evt.request).catch(() => fromNetwork(evt.request))
|
||||
);
|
||||
});
|
||||
|
||||
addEventListener("message", e => {
|
||||
if (e.data.action == "cache") {
|
||||
e.waitUntil(cache(e.data.urls));
|
||||
}
|
||||
if (e.data.action === "cache") {
|
||||
e.waitUntil(cache(e.data.urls));
|
||||
}
|
||||
});
|
||||
|
||||
function shouldCache(it) {
|
||||
return it.endsWith(".js") || it.endsWith(".css") || it.endsWith(".png") || it.endsWith(".html")
|
||||
return [".js", ".css", ".png", ".html", ".webp", "manifest.json"].findIndex(end => it.endsWith(end)) !== -1
|
||||
|| it === new URL("./", self.location).toString()
|
||||
}
|
||||
|
||||
function cache(urls) {
|
||||
return; // todo fix
|
||||
return caches.open(CACHE).then(cache => cache.addAll(urls.filter(shouldCache)));
|
||||
let filtered = Array.from(new Set(urls.filter(shouldCache)));
|
||||
return caches.open(cacheId).then(cache => cache.addAll(filtered));
|
||||
}
|
||||
|
||||
function fromNetwork(request) {
|
||||
return fetch(request);
|
||||
return fetch(request)
|
||||
.then(response => {
|
||||
if (!shouldCache(response.url)) return response;
|
||||
|
||||
// Let's cache it when loading for the first time
|
||||
return caches.open(cacheId)
|
||||
.then(it => it.add(request))
|
||||
.then(() => response);
|
||||
});
|
||||
}
|
||||
|
||||
function fromCache(request) {
|
||||
return caches.open(CACHE)
|
||||
.then(cache => cache.match(request))
|
||||
.then(matching => matching || Promise.reject("no-match"));
|
||||
return caches.open(cacheId)
|
||||
.then(cache => cache.match(request))
|
||||
.then(matching => matching || Promise.reject("no-match"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user