[FEATURE] Offline Mode

This commit is contained in:
Gravita 2021-11-16 23:00:41 +07:00
parent 111639b963
commit eecd61e6ba
17 changed files with 356 additions and 98 deletions

View file

@ -6,6 +6,7 @@
import pro.gravit.launcher.client.events.ClientPreGuiPhase; import pro.gravit.launcher.client.events.ClientPreGuiPhase;
import pro.gravit.launcher.console.GetPublicKeyCommand; import pro.gravit.launcher.console.GetPublicKeyCommand;
import pro.gravit.launcher.console.SignDataCommand; import pro.gravit.launcher.console.SignDataCommand;
import pro.gravit.launcher.events.request.*;
import pro.gravit.launcher.guard.LauncherGuardInterface; import pro.gravit.launcher.guard.LauncherGuardInterface;
import pro.gravit.launcher.guard.LauncherGuardManager; import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.guard.LauncherNoGuard; import pro.gravit.launcher.guard.LauncherNoGuard;
@ -14,14 +15,21 @@
import pro.gravit.launcher.gui.RuntimeProvider; import pro.gravit.launcher.gui.RuntimeProvider;
import pro.gravit.launcher.managers.ClientGsonManager; import pro.gravit.launcher.managers.ClientGsonManager;
import pro.gravit.launcher.managers.ConsoleManager; import pro.gravit.launcher.managers.ConsoleManager;
import pro.gravit.launcher.modules.events.OfflineModeEvent;
import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.profiles.optional.actions.OptionalAction; import pro.gravit.launcher.profiles.optional.actions.OptionalAction;
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException; import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest; import pro.gravit.launcher.request.auth.*;
import pro.gravit.launcher.request.auth.details.AuthLoginOnlyDetails;
import pro.gravit.launcher.request.management.FeaturesRequest;
import pro.gravit.launcher.request.secure.GetSecureLevelInfoRequest;
import pro.gravit.launcher.request.secure.SecurityReportRequest;
import pro.gravit.launcher.request.update.LauncherRequest;
import pro.gravit.launcher.request.websockets.ClientWebSocketService; import pro.gravit.launcher.request.websockets.ClientWebSocketService;
import pro.gravit.launcher.request.websockets.OfflineRequestService;
import pro.gravit.launcher.request.websockets.StdWebSocketService; import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.utils.NativeJVMHalt; import pro.gravit.launcher.utils.NativeJVMHalt;
import pro.gravit.utils.helper.*; import pro.gravit.utils.helper.*;
@ -34,7 +42,9 @@
import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -143,6 +153,35 @@ public static LauncherGuardInterface tryGetStdGuard() {
return null; return null;
} }
public static RequestService initOffline() {
OfflineRequestService service = new OfflineRequestService();
applyBasicOfflineProcessors(service);
OfflineModeEvent event = new OfflineModeEvent(service);
modulesManager.invokeEvent(event);
return event.service;
}
public static void applyBasicOfflineProcessors(OfflineRequestService service) {
service.registerRequestProcessor(LauncherRequest.class, (r) -> new LauncherRequestEvent(false, (String) null));
service.registerRequestProcessor(CheckServerRequest.class, (r) -> {
throw new RequestException("CheckServer disabled in offline mode");
});
service.registerRequestProcessor(GetAvailabilityAuthRequest.class, (r) -> {
List<GetAvailabilityAuthRequestEvent.AuthAvailabilityDetails> details = new ArrayList<>();
details.add(new AuthLoginOnlyDetails());
GetAvailabilityAuthRequestEvent.AuthAvailability authAvailability = new GetAvailabilityAuthRequestEvent.AuthAvailability("offline", "Offline Mode", details);
List<GetAvailabilityAuthRequestEvent.AuthAvailability> list = new ArrayList<>(1);
list.add(authAvailability);
return new GetAvailabilityAuthRequestEvent(list);
});
service.registerRequestProcessor(JoinServerRequest.class, (r) -> new JoinServerRequestEvent(false));
service.registerRequestProcessor(ExitRequest.class, (r) -> new ExitRequestEvent(ExitRequestEvent.ExitReason.CLIENT));
service.registerRequestProcessor(SetProfileRequest.class, (r) -> new SetProfileRequestEvent(null));
service.registerRequestProcessor(FeaturesRequest.class, (r) -> new FeaturesRequestEvent());
service.registerRequestProcessor(GetSecureLevelInfoRequest.class, (r) -> new GetSecureLevelInfoRequestEvent(null, false));
service.registerRequestProcessor(SecurityReportRequest.class, (r) -> new SecurityReportRequestEvent(SecurityReportRequestEvent.ReportAction.NONE));
}
public static LauncherEngine clientInstance() { public static LauncherEngine clientInstance() {
return new LauncherEngine(true); return new LauncherEngine(true);
} }
@ -193,9 +232,19 @@ public void start(String... args) throws Throwable {
if (!Request.isAvailable()) { if (!Request.isAvailable()) {
String address = Launcher.getConfig().address; String address = Launcher.getConfig().address;
LogHelper.debug("Start async connection to %s", address); LogHelper.debug("Start async connection to %s", address);
StdWebSocketService service = StdWebSocketService.initWebSockets(address, true); RequestService service;
try {
service = StdWebSocketService.initWebSockets(address).get();
} catch (Throwable e) {
if(LogHelper.isDebugEnabled()) {
LogHelper.error(e);
}
LogHelper.warning("Launcher in offline mode");
service = initOffline();
}
Request.setRequestService(service); Request.setRequestService(service);
service.reconnectCallback = () -> if(service instanceof StdWebSocketService) {
((StdWebSocketService) service).reconnectCallback = () ->
{ {
LogHelper.debug("WebSocket connect closed. Try reconnect"); LogHelper.debug("WebSocket connect closed. Try reconnect");
try { try {
@ -205,6 +254,7 @@ public void start(String... args) throws Throwable {
throw new RequestException("Connection failed", e); throw new RequestException("Connection failed", e);
} }
}; };
}
service.registerEventHandler(new BasicLauncherEventHandler()); service.registerEventHandler(new BasicLauncherEventHandler());
} }
Objects.requireNonNull(args, "args"); Objects.requireNonNull(args, "args");

View file

@ -4,12 +4,16 @@
import pro.gravit.launcher.api.AuthService; import pro.gravit.launcher.api.AuthService;
import pro.gravit.launcher.api.ClientService; import pro.gravit.launcher.api.ClientService;
import pro.gravit.launcher.client.events.client.*; import pro.gravit.launcher.client.events.client.*;
import pro.gravit.launcher.events.request.ProfileByUUIDRequestEvent;
import pro.gravit.launcher.events.request.ProfileByUsernameRequestEvent;
import pro.gravit.launcher.guard.LauncherGuardManager; import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.hasher.FileNameMatcher; import pro.gravit.launcher.hasher.FileNameMatcher;
import pro.gravit.launcher.hasher.HashedDir; import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry; import pro.gravit.launcher.hasher.HashedEntry;
import pro.gravit.launcher.managers.ClientGsonManager; import pro.gravit.launcher.managers.ClientGsonManager;
import pro.gravit.launcher.managers.ConsoleManager; import pro.gravit.launcher.managers.ConsoleManager;
import pro.gravit.launcher.modules.LauncherModulesManager;
import pro.gravit.launcher.modules.events.OfflineModeEvent;
import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.patches.FMLPatcher; import pro.gravit.launcher.patches.FMLPatcher;
import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.ClientProfile;
@ -19,8 +23,13 @@
import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger; import pro.gravit.launcher.profiles.optional.triggers.OptionalTrigger;
import pro.gravit.launcher.request.Request; import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException; import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest; import pro.gravit.launcher.request.auth.GetAvailabilityAuthRequest;
import pro.gravit.launcher.request.uuid.BatchProfileByUsernameRequest;
import pro.gravit.launcher.request.uuid.ProfileByUUIDRequest;
import pro.gravit.launcher.request.uuid.ProfileByUsernameRequest;
import pro.gravit.launcher.request.websockets.OfflineRequestService;
import pro.gravit.launcher.request.websockets.StdWebSocketService; import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.serialize.HInput; import pro.gravit.launcher.serialize.HInput;
import pro.gravit.launcher.utils.DirWatcher; import pro.gravit.launcher.utils.DirWatcher;
@ -113,12 +122,15 @@ public static void main(String[] args) throws Throwable {
List<URL> classpath = resolveClassPath(clientDir, params.actions, params.profile).map(IOHelper::toURL).collect(Collectors.toList()); List<URL> classpath = resolveClassPath(clientDir, params.actions, params.profile).map(IOHelper::toURL).collect(Collectors.toList());
// Start client with WatchService monitoring // Start client with WatchService monitoring
boolean digest = !profile.isUpdateFastCheck(); boolean digest = !profile.isUpdateFastCheck();
StdWebSocketService service = StdWebSocketService.initWebSockets(Launcher.getConfig().address, false); RequestService service;
if(params.offlineMode) {
service = initOffline(LauncherEngine.modulesManager, params);
} else {
service = StdWebSocketService.initWebSockets(Launcher.getConfig().address).get();
LogHelper.debug("Restore sessions"); LogHelper.debug("Restore sessions");
Request.restore(); Request.restore();
service.registerEventHandler(new BasicLauncherEventHandler()); service.registerEventHandler(new BasicLauncherEventHandler());
service.reconnectCallback = () -> ((StdWebSocketService) service).reconnectCallback = () ->
{ {
LogHelper.debug("WebSocket connect closed. Try reconnect"); LogHelper.debug("WebSocket connect closed. Try reconnect");
try { try {
@ -128,6 +140,7 @@ public static void main(String[] args) throws Throwable {
throw new RequestException("Connection failed", e); throw new RequestException("Connection failed", e);
} }
}; };
}
Request.setRequestService(service); Request.setRequestService(service);
ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig(); ClientProfile.ClassLoaderConfig classLoaderConfig = profile.getClassLoaderConfig();
if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) { if (classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
@ -204,6 +217,30 @@ private static void initGson(ClientModuleManager moduleManager) {
Launcher.gsonManager.initGson(); Launcher.gsonManager.initGson();
} }
public static RequestService initOffline(LauncherModulesManager modulesManager, ClientLauncherProcess.ClientParams params) {
OfflineRequestService service = new OfflineRequestService();
LauncherEngine.applyBasicOfflineProcessors(service);
applyClientOfflineProcessors(service, params);
OfflineModeEvent event = new OfflineModeEvent(service);
modulesManager.invokeEvent(event);
return event.service;
}
public static void applyClientOfflineProcessors(OfflineRequestService service, ClientLauncherProcess.ClientParams params) {
service.registerRequestProcessor(ProfileByUsernameRequest.class, (r) -> {
if(params.playerProfile.username.equals(r.username)) {
return new ProfileByUsernameRequestEvent(params.playerProfile);
}
throw new RequestException("User not found");
});
service.registerRequestProcessor(ProfileByUUIDRequest.class, (r) -> {
if(params.playerProfile.uuid.equals(r.uuid)) {
return new ProfileByUUIDRequestEvent(params.playerProfile);
}
throw new RequestException("User not found");
});
}
public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException { public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException {
//if (matcher != null) //if (matcher != null)
// matcher = matcher.verifyOnly(); // matcher = matcher.verifyOnly();

View file

@ -281,6 +281,8 @@ public static class ClientParams {
public Map<String, String> extendedTokens; public Map<String, String> extendedTokens;
public boolean offlineMode;
public transient HashedDir assetHDir; public transient HashedDir assetHDir;
public transient HashedDir clientHDir; public transient HashedDir clientHDir;

View file

@ -7,7 +7,12 @@
import pro.gravit.launcher.client.ClientModuleManager; import pro.gravit.launcher.client.ClientModuleManager;
import pro.gravit.launcher.managers.ConsoleManager; import pro.gravit.launcher.managers.ConsoleManager;
import pro.gravit.launcher.modules.LauncherModule; import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.modules.events.OfflineModeEvent;
import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.websockets.OfflineRequestService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
@ -22,6 +27,7 @@ public class DebugMain {
public static String webSocketURL = System.getProperty("launcherdebug.websocket", "ws://localhost:9274/api"); public static String webSocketURL = System.getProperty("launcherdebug.websocket", "ws://localhost:9274/api");
public static String projectName = System.getProperty("launcherdebug.projectname", "Minecraft"); public static String projectName = System.getProperty("launcherdebug.projectname", "Minecraft");
public static String unlockKey = System.getProperty("launcherdebug.unlockkey", "0000"); public static String unlockKey = System.getProperty("launcherdebug.unlockkey", "0000");
public static boolean offlineMode = Boolean.getBoolean("launcherdebug.offlinemode");
public static String[] moduleClasses = System.getProperty("launcherdebug.modules", "").split(","); public static String[] moduleClasses = System.getProperty("launcherdebug.modules", "").split(",");
public static String[] moduleFiles = System.getProperty("launcherdebug.modulefiles", "").split(","); public static String[] moduleFiles = System.getProperty("launcherdebug.modulefiles", "").split(",");
public static LauncherConfig.LauncherEnvironment environment = LauncherConfig.LauncherEnvironment.valueOf(System.getProperty("launcherdebug.env", "STD")); public static LauncherConfig.LauncherEnvironment environment = LauncherConfig.LauncherEnvironment.valueOf(System.getProperty("launcherdebug.env", "STD"));
@ -50,6 +56,17 @@ public static void main(String[] args) throws Throwable {
LauncherEngine.initGson(LauncherEngine.modulesManager); LauncherEngine.initGson(LauncherEngine.modulesManager);
ConsoleManager.initConsole(); ConsoleManager.initConsole();
LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase()); LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase());
RequestService service;
if(offlineMode) {
OfflineRequestService offlineRequestService = new OfflineRequestService();
LauncherEngine.applyBasicOfflineProcessors(offlineRequestService);
OfflineModeEvent event = new OfflineModeEvent(offlineRequestService);
LauncherEngine.modulesManager.invokeEvent(event);
service = event.service;
} else {
service = StdWebSocketService.initWebSockets(webSocketURL).get();
}
Request.setRequestService(service);
LogHelper.debug("Initialization LauncherEngine"); LogHelper.debug("Initialization LauncherEngine");
LauncherEngine instance = LauncherEngine.newInstance(false); LauncherEngine instance = LauncherEngine.newInstance(false);
instance.start(args); instance.start(args);

View file

@ -10,6 +10,11 @@ public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey) {
this.verifySecureKey = verifySecureKey; this.verifySecureKey = verifySecureKey;
} }
public GetSecureLevelInfoRequestEvent(byte[] verifySecureKey, boolean enabled) {
this.verifySecureKey = verifySecureKey;
this.enabled = enabled;
}
@Override @Override
public String getType() { public String getType() {
return "getSecureLevelInfo"; return "getSecureLevelInfo";

View file

@ -0,0 +1,12 @@
package pro.gravit.launcher.modules.events;
import pro.gravit.launcher.modules.LauncherModule;
import pro.gravit.launcher.request.RequestService;
public class OfflineModeEvent extends LauncherModule.Event {
public RequestService service;
public OfflineModeEvent(RequestService service) {
this.service = service;
}
}

View file

@ -214,8 +214,9 @@ public void removeOAuthChangeHandler(BiConsumer<String, AuthRequestEvent.OAuthRe
public R request() throws Exception { public R request() throws Exception {
if (!started.compareAndSet(false, true)) if (!started.compareAndSet(false, true))
throw new IllegalStateException("Request already started"); throw new IllegalStateException("Request already started");
if (!isAvailable()) if(!isAvailable()) {
setRequestService(StdWebSocketService.initWebSockets(Launcher.getConfig().address, false)); throw new RequestException("RequestService not initialized");
}
return requestDo(requestService); return requestDo(requestService);
} }

View file

@ -11,15 +11,15 @@ public final class AuthRequest extends Request<AuthRequestEvent> implements WebS
public static final ProviderMap<AuthPasswordInterface> providers = new ProviderMap<>(); public static final ProviderMap<AuthPasswordInterface> providers = new ProviderMap<>();
private static boolean registerProviders = false; private static boolean registerProviders = false;
@LauncherNetworkAPI @LauncherNetworkAPI
private final String login; public final String login;
@LauncherNetworkAPI @LauncherNetworkAPI
private final AuthPasswordInterface password; public final AuthPasswordInterface password;
@LauncherNetworkAPI @LauncherNetworkAPI
private final String auth_id; public final String auth_id;
@LauncherNetworkAPI @LauncherNetworkAPI
private final boolean getSession; public final boolean getSession;
@LauncherNetworkAPI @LauncherNetworkAPI
private final ConnectTypes authType; public final ConnectTypes authType;
public AuthRequest(String login, String password, String auth_id, ConnectTypes authType) { public AuthRequest(String login, String password, String auth_id, ConnectTypes authType) {
this.login = login; this.login = login;

View file

@ -24,6 +24,8 @@
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public abstract class ClientJSONPoint { public abstract class ClientJSONPoint {
@ -94,16 +96,26 @@ public void open() throws Exception {
webSocketClientHandler.handshakeFuture().sync(); webSocketClientHandler.handshakeFuture().sync();
} }
public void openAsync(Runnable onConnect) { public void openAsync(Runnable onConnect, Consumer<Throwable> onFail) {
//System.out.println("WebSocket Client connecting"); //System.out.println("WebSocket Client connecting");
webSocketClientHandler = webSocketClientHandler =
new WebSocketClientHandler( new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker( WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE, 12800000), this); uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE, 12800000), this);
ChannelFuture future = bootstrap.connect(uri.getHost(), port); ChannelFuture future = bootstrap.connect(uri.getHost(), port);
future.addListener((e) -> { future.addListener((l) -> {
if(l.isSuccess()) {
ch = future.channel(); ch = future.channel();
webSocketClientHandler.handshakeFuture().addListener((e1) -> onConnect.run()); webSocketClientHandler.handshakeFuture().addListener((e) -> {
if(e.isSuccess()) {
onConnect.run();
} else {
onFail.accept(webSocketClientHandler.handshakeFuture().cause());
}
});
} else {
onFail.accept(future.cause());
}
}); });
} }

View file

@ -27,6 +27,7 @@
public abstract class ClientWebSocketService extends ClientJSONPoint { public abstract class ClientWebSocketService extends ClientJSONPoint {
public static final ProviderMap<WebSocketEvent> results = new ProviderMap<>(); public static final ProviderMap<WebSocketEvent> results = new ProviderMap<>();
public static final ProviderMap<WebSocketRequest> requests = new ProviderMap<>(); public static final ProviderMap<WebSocketRequest> requests = new ProviderMap<>();
private static boolean resultsRegistered = false;
public final Gson gson; public final Gson gson;
public final Boolean onConnect; public final Boolean onConnect;
public OnCloseCallback onCloseCallback; public OnCloseCallback onCloseCallback;
@ -83,6 +84,7 @@ public void registerRequests() {
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void registerResults() { public void registerResults() {
if(!resultsRegistered) {
results.register("auth", AuthRequestEvent.class); results.register("auth", AuthRequestEvent.class);
results.register("checkServer", CheckServerRequestEvent.class); results.register("checkServer", CheckServerRequestEvent.class);
results.register("joinServer", JoinServerRequestEvent.class); results.register("joinServer", JoinServerRequestEvent.class);
@ -115,6 +117,8 @@ public void registerResults() {
results.register("refreshToken", RefreshTokenRequestEvent.class); results.register("refreshToken", RefreshTokenRequestEvent.class);
results.register("restore", RestoreRequestEvent.class); results.register("restore", RestoreRequestEvent.class);
results.register("additionalData", AdditionalDataRequestEvent.class); results.register("additionalData", AdditionalDataRequestEvent.class);
resultsRegistered = true;
}
} }
public void waitIfNotConnected() { public void waitIfNotConnected() {

View file

@ -0,0 +1,73 @@
package pro.gravit.launcher.request.websockets;
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class OfflineRequestService implements RequestService {
private final HashSet<EventHandler> eventHandlers = new HashSet<>();
private final Map<Class<?>, RequestProcessor<?, ?>> processors = new ConcurrentHashMap<>();
@Override
@SuppressWarnings("unchecked")
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
RequestProcessor<T, Request<T>> processor = (RequestProcessor<T, Request<T>>) processors.get(request.getClass());
CompletableFuture<T> future = new CompletableFuture<>();
if(processor == null) {
future.completeExceptionally(new RequestException(String.format("Offline mode not support '%s'", request.getType())));
return future;
}
if(LogHelper.isDevEnabled()) {
LogHelper.dev("Request %s: %s", request.getType(), Launcher.gsonManager.gson.toJson(request));
}
try {
T event = processor.process(request);
if(LogHelper.isDevEnabled()) {
LogHelper.dev("Response %s: %s", event.getType(), Launcher.gsonManager.gson.toJson(event));
}
future.complete(event);
} catch (Throwable e) {
if(e instanceof RequestException) {
future.completeExceptionally(e);
} else {
future.completeExceptionally(new RequestException(e));
}
}
return future;
}
@Override
public void registerEventHandler(EventHandler handler) {
eventHandlers.add(handler);
}
@Override
public void unregisterEventHandler(EventHandler handler) {
eventHandlers.remove(handler);
}
@Override
public boolean isClosed() {
return false;
}
public<T extends WebSocketEvent, V extends WebSocketRequest> void registerRequestProcessor(Class<V> requestClazz, RequestProcessor<T, V> function) {
processors.put(requestClazz, function);
}
public<T extends WebSocketEvent> void unregisterRequestProcessor(Class<Request<T>> requestClazz) {
processors.remove(requestClazz);
}
public interface RequestProcessor<T extends WebSocketEvent, V extends WebSocketRequest> {
T process(V request) throws RequestException;
}
}

View file

@ -28,7 +28,7 @@ public StdWebSocketService(String address) throws SSLException {
super(address); super(address);
} }
public static StdWebSocketService initWebSockets(String address, boolean async) { public static CompletableFuture<StdWebSocketService> initWebSockets(String address) throws Exception {
StdWebSocketService service; StdWebSocketService service;
try { try {
service = new StdWebSocketService(address); service = new StdWebSocketService(address);
@ -37,16 +37,9 @@ public static StdWebSocketService initWebSockets(String address, boolean async)
} }
service.registerResults(); service.registerResults();
service.registerRequests(); service.registerRequests();
if (!async) { CompletableFuture<StdWebSocketService> future = new CompletableFuture<>();
try {
service.open();
} catch (Exception e) {
LogHelper.error(e);
}
} else {
service.openAsync(() -> { service.openAsync(() -> {
}); future.complete(service);
}
JVMHelper.RUNTIME.addShutdownHook(new Thread(() -> { JVMHelper.RUNTIME.addShutdownHook(new Thread(() -> {
try { try {
//if(service.isOpen()) //if(service.isOpen())
@ -56,9 +49,14 @@ public static StdWebSocketService initWebSockets(String address, boolean async)
LogHelper.error(e); LogHelper.error(e);
} }
})); }));
return service; }, (error) -> {
future.completeExceptionally(error);
});
return future;
} }
@Deprecated @Deprecated
public void registerEventHandler(ClientWebSocketService.EventHandler handler) { public void registerEventHandler(ClientWebSocketService.EventHandler handler) {
legacyEventHandlers.add(handler); legacyEventHandlers.add(handler);

View file

@ -0,0 +1,43 @@
package pro.gravit.launcher.request.websockets;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.RequestService;
import pro.gravit.launcher.request.WebSocketEvent;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class VoidRequestService implements RequestService {
private final Throwable ex;
public VoidRequestService(Throwable ex) {
this.ex = ex;
}
public VoidRequestService() {
this.ex = null;
}
@Override
public <T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
CompletableFuture<T> future = new CompletableFuture<>();
future.completeExceptionally(ex != null ? ex : new RequestException("Connection fail"));
return future;
}
@Override
public void registerEventHandler(EventHandler handler) {
}
@Override
public void unregisterEventHandler(EventHandler handler) {
}
@Override
public boolean isClosed() {
return true;
}
}

View file

@ -77,17 +77,15 @@ else if (frame instanceof BinaryWebSocketFrame) {
// uncomment to print request // uncomment to print request
// logger.info(frame.content().toString()); // logger.info(frame.content().toString());
} }
} }
@Override @Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) { public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
LogHelper.error(cause);
if (!handshakeFuture.isDone()) { if (!handshakeFuture.isDone()) {
handshakeFuture.setFailure(cause); handshakeFuture.setFailure(cause);
} else {
LogHelper.error(cause);
} }
ctx.close(); ctx.close();
} }
} }

View file

@ -113,7 +113,7 @@ public void run(String... args) throws Throwable {
updateLauncherConfig(); updateLauncherConfig();
if (config.env != null) Launcher.applyLauncherEnv(config.env); if (config.env != null) Launcher.applyLauncherEnv(config.env);
else Launcher.applyLauncherEnv(LauncherConfig.LauncherEnvironment.STD); else Launcher.applyLauncherEnv(LauncherConfig.LauncherEnvironment.STD);
StdWebSocketService service = StdWebSocketService.initWebSockets(config.address, false); StdWebSocketService service = StdWebSocketService.initWebSockets(config.address).get();
service.reconnectCallback = () -> service.reconnectCallback = () ->
{ {
LogHelper.debug("WebSocket connect closed. Try reconnect"); LogHelper.debug("WebSocket connect closed. Try reconnect");

View file

@ -61,7 +61,13 @@ public void run() throws Exception {
if(!Request.isAvailable() || Request.getRequestService().isClosed()) { if(!Request.isAvailable() || Request.getRequestService().isClosed()) {
System.out.println("Print launchserver websocket host( ws://host:port/api ):"); System.out.println("Print launchserver websocket host( ws://host:port/api ):");
wrapper.config.address = commands.commandHandler.readLine(); wrapper.config.address = commands.commandHandler.readLine();
StdWebSocketService service = StdWebSocketService.initWebSockets(wrapper.config.address, false); StdWebSocketService service;
try {
service = StdWebSocketService.initWebSockets(wrapper.config.address).get();
} catch (Throwable e) {
LogHelper.error(e);
continue;
}
Request.setRequestService(service); Request.setRequestService(service);
} }
System.out.println("Print server token:"); System.out.println("Print server token:");

View file

@ -5,7 +5,7 @@
id 'org.openjfx.javafxplugin' version '0.0.10' apply false id 'org.openjfx.javafxplugin' version '0.0.10' apply false
} }
group = 'pro.gravit.launcher' group = 'pro.gravit.launcher'
version = '5.2.5' version = '5.2.6-SNAPSHOT'
apply from: 'props.gradle' apply from: 'props.gradle'