Refactor AuthlibInjectorServer
This commit is contained in:
@@ -38,7 +38,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
|||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -235,16 +234,7 @@ public final class Accounts {
|
|||||||
.filter(server -> url.equals(server.getUrl()))
|
.filter(server -> url.equals(server.getUrl()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseGet(() -> {
|
.orElseGet(() -> {
|
||||||
// this usually happens when migrating data from an older version
|
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||||
AuthlibInjectorServer server;
|
|
||||||
try {
|
|
||||||
server = AuthlibInjectorServer.fetchServerInfo(url);
|
|
||||||
LOG.info("Migrated authlib injector server " + server);
|
|
||||||
} catch (IOException e) {
|
|
||||||
server = new AuthlibInjectorServer(url, url);
|
|
||||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
config().getAuthlibInjectorServers().add(server);
|
config().getAuthlibInjectorServers().add(server);
|
||||||
return server;
|
return server;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public final class Config implements Cloneable, Observable {
|
|||||||
private IntegerProperty logLines = new SimpleIntegerProperty(100);
|
private IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||||
|
|
||||||
@SerializedName("authlibInjectorServers")
|
@SerializedName("authlibInjectorServers")
|
||||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server });
|
||||||
|
|
||||||
@SerializedName("updateChannel")
|
@SerializedName("updateChannel")
|
||||||
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
||||||
|
|||||||
@@ -27,14 +27,12 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
|||||||
import org.jackhuang.hmcl.game.Arguments;
|
import org.jackhuang.hmcl.game.Arguments;
|
||||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||||
private AuthlibInjectorServer server;
|
private AuthlibInjectorServer server;
|
||||||
@@ -58,9 +56,9 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
||||||
CompletableFuture<byte[]> prefetchedMetaTask = CompletableFuture.supplyAsync(() -> {
|
CompletableFuture<String> prefetchedMetaTask = CompletableFuture.supplyAsync(() -> {
|
||||||
try (InputStream in = new URL(server.getUrl()).openStream()) {
|
try {
|
||||||
return readFullyWithoutClosing(in);
|
return server.fetchMetadataResponse();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new CompletionException(new ServerDisconnectException(e));
|
throw new CompletionException(new ServerDisconnectException(e));
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
});
|
});
|
||||||
|
|
||||||
AuthInfo auth = loginAction.get();
|
AuthInfo auth = loginAction.get();
|
||||||
byte[] prefetchedMeta;
|
String prefetchedMeta;
|
||||||
AuthlibInjectorArtifactInfo artifact;
|
AuthlibInjectorArtifactInfo artifact;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -95,7 +93,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
return auth.withArguments(new Arguments().addJVMArguments(
|
return auth.withArguments(new Arguments().addJVMArguments(
|
||||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||||
"-Dauthlibinjector.side=client",
|
"-Dauthlibinjector.side=client",
|
||||||
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta)));
|
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -20,25 +20,40 @@ package org.jackhuang.hmcl.auth.authlibinjector;
|
|||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyAsByteArray;
|
||||||
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing;
|
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.lang.reflect.Type;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.annotations.JsonAdapter;
|
||||||
|
|
||||||
public class AuthlibInjectorServer {
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.InvalidationListener;
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
|
||||||
|
@JsonAdapter(AuthlibInjectorServer.Deserializer.class)
|
||||||
|
public class AuthlibInjectorServer implements Observable {
|
||||||
|
|
||||||
private static final int MAX_REDIRECTS = 5;
|
private static final int MAX_REDIRECTS = 5;
|
||||||
|
private static final Gson GSON = new GsonBuilder().create();
|
||||||
|
|
||||||
public static AuthlibInjectorServer locateServer(String url) throws IOException {
|
public static AuthlibInjectorServer locateServer(String url) throws IOException {
|
||||||
url = parseInputUrl(url);
|
url = parseInputUrl(url);
|
||||||
@@ -60,8 +75,11 @@ public class AuthlibInjectorServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return parseResponse(url, readFullyWithoutClosing(conn.getInputStream()));
|
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||||
|
server.refreshMetadata(readFullyWithoutClosing(conn.getInputStream()));
|
||||||
|
return server;
|
||||||
} finally {
|
} finally {
|
||||||
conn.disconnect();
|
conn.disconnect();
|
||||||
}
|
}
|
||||||
@@ -107,45 +125,82 @@ public class AuthlibInjectorServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
|
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
|
||||||
try (InputStream in = new URL(url).openStream()) {
|
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||||
return parseResponse(url, readFullyWithoutClosing(in));
|
server.refreshMetadata();
|
||||||
}
|
return server;
|
||||||
}
|
|
||||||
|
|
||||||
private static AuthlibInjectorServer parseResponse(String url, byte[] rawResponse) throws IOException {
|
|
||||||
try {
|
|
||||||
JsonObject response = JsonUtils.fromNonNullJson(new String(rawResponse, UTF_8), JsonObject.class);
|
|
||||||
String name = extractServerName(response).orElse(url);
|
|
||||||
return new AuthlibInjectorServer(url, name);
|
|
||||||
} catch (JsonParseException e) {
|
|
||||||
throw new IOException("Malformed response", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<String> extractServerName(JsonObject response){
|
|
||||||
return tryCast(response.get("meta"), JsonObject.class)
|
|
||||||
.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String url;
|
private String url;
|
||||||
private String name;
|
@Nullable
|
||||||
|
private String metadataResponse;
|
||||||
|
private long metadataTimestamp;
|
||||||
|
|
||||||
public AuthlibInjectorServer(String url, String name) {
|
@Nullable
|
||||||
|
private transient String name;
|
||||||
|
|
||||||
|
private transient boolean metadataRefreshed;
|
||||||
|
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||||
|
|
||||||
|
public AuthlibInjectorServer(String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public Optional<String> getMetadataResponse() {
|
||||||
return name;
|
return Optional.ofNullable(metadataResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public long getMetadataTimestamp() {
|
||||||
public String toString() {
|
return metadataTimestamp;
|
||||||
return url + " (" + name + ")";
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return Optional.ofNullable(name)
|
||||||
|
.orElse(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fetchMetadataResponse() throws IOException {
|
||||||
|
if (metadataResponse == null || !metadataRefreshed) {
|
||||||
|
refreshMetadata();
|
||||||
|
}
|
||||||
|
return getMetadataResponse().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshMetadata() throws IOException {
|
||||||
|
refreshMetadata(readFullyAsByteArray(new URL(url).openStream()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshMetadata(byte[] rawResponse) throws IOException {
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
setMetadataResponse(new String(rawResponse, UTF_8), timestamp);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new IOException("Malformed response", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataRefreshed = true;
|
||||||
|
LOG.info("authlib-injector server metadata refreshed: " + url);
|
||||||
|
Platform.runLater(helper::invalidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMetadataResponse(String metadataResponse, long metadataTimestamp) throws JsonParseException {
|
||||||
|
JsonObject response = GSON.fromJson(metadataResponse, JsonObject.class);
|
||||||
|
if (response == null) {
|
||||||
|
throw new JsonParseException("Metadata response is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
this.metadataResponse = metadataResponse;
|
||||||
|
this.metadataTimestamp = metadataTimestamp;
|
||||||
|
|
||||||
|
Optional<JsonObject> metaObject = tryCast(response.get("meta"), JsonObject.class);
|
||||||
|
|
||||||
|
this.name = metaObject.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString))
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -162,4 +217,36 @@ public class AuthlibInjectorServer {
|
|||||||
AuthlibInjectorServer another = (AuthlibInjectorServer) obj;
|
AuthlibInjectorServer another = (AuthlibInjectorServer) obj;
|
||||||
return this.url.equals(another.url);
|
return this.url.equals(another.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(InvalidationListener listener) {
|
||||||
|
helper.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(InvalidationListener listener) {
|
||||||
|
helper.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Deserializer implements JsonDeserializer<AuthlibInjectorServer> {
|
||||||
|
@Override
|
||||||
|
public AuthlibInjectorServer deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException {
|
||||||
|
JsonObject jsonObj = json.getAsJsonObject();
|
||||||
|
AuthlibInjectorServer instance = new AuthlibInjectorServer(jsonObj.get("url").getAsString());
|
||||||
|
|
||||||
|
if (jsonObj.has("name")) {
|
||||||
|
instance.name = jsonObj.get("name").getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObj.has("metadataResponse")) {
|
||||||
|
try {
|
||||||
|
instance.setMetadataResponse(jsonObj.get("metadataResponse").getAsString(), jsonObj.get("metadataTimestamp").getAsLong());
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
LOG.log(Level.WARNING, "Ignoring malformed metadata response cache: " + jsonObj.get("metadataResponse"), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user