From 08d7ff138bec6faa1482ce4640081ba235fa5852 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Sun, 28 Aug 2022 23:57:47 +0800 Subject: [PATCH] fix(login): prompt to retry logging in. Closes #1658 #1591 #1544 1608. --- .../jackhuang/hmcl/game/LauncherHelper.java | 53 +++++++++++++++---- .../hmcl/ui/construct/MessageDialogPane.java | 37 +++++++------ .../jackhuang/hmcl/util/JavaFXPatcher.java | 4 +- .../resources/assets/lang/I18N.properties | 3 ++ .../resources/assets/lang/I18N_zh.properties | 3 ++ .../assets/lang/I18N_zh_CN.properties | 4 +- .../java/org/jackhuang/hmcl/auth/Account.java | 2 +- .../hmcl/auth/NotLoggedInException.java | 21 ++++++++ .../AuthlibInjectorAccount.java | 15 +++--- .../hmcl/auth/microsoft/MicrosoftAccount.java | 4 +- .../hmcl/auth/offline/OfflineAccount.java | 4 +- .../hmcl/auth/yggdrasil/YggdrasilAccount.java | 4 +- javafx.gradle.kts | 2 +- 13 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NotLoggedInException.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index c36cc6487..1ca3ebaca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.game; +import com.jfoenix.controls.JFXButton; import javafx.application.Platform; import javafx.stage.Stage; import org.jackhuang.hmcl.Launcher; @@ -35,6 +36,8 @@ import org.jackhuang.hmcl.mod.ModpackProvider; import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.*; +import org.jackhuang.hmcl.ui.account.ClassicAccountLoginDialog; +import org.jackhuang.hmcl.ui.account.OAuthAccountLoginDialog; import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.*; @@ -62,6 +65,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import static org.jackhuang.hmcl.setting.ConfigHolder.config; +import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.util.Lang.resolveException; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; @@ -171,17 +175,7 @@ public final class LauncherHelper { .thenComposeAsync(() -> { return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null); }) - .thenComposeAsync(Task.supplyAsync(() -> { - try { - return account.logIn(); - } catch (CredentialExpiredException e) { - LOG.log(Level.INFO, "Credential has expired", e); - return DialogController.logIn(account); - } catch (AuthenticationException e) { - LOG.log(Level.WARNING, "Authentication failed, try playing offline", e); - return account.playOffline().orElseThrow(() -> e); - } - }).withStage("launch.state.logging_in")) + .thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in")) .thenComposeAsync(authInfo -> Task.supplyAsync(() -> { LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null); return new HMCLGameLauncher( @@ -618,6 +612,43 @@ public final class LauncherHelper { return future; } + private static Task logIn(Account account) { + return Task.composeAsync(() -> { + try { + return Task.completed(account.logIn()); + } catch (CredentialExpiredException e) { + LOG.log(Level.INFO, "Credential has expired", e); + + return Task.completed(DialogController.logIn(account)); + } catch (AuthenticationException e) { + LOG.log(Level.WARNING, "Authentication failed, try playing offline", e); + + CompletableFuture> future = new CompletableFuture<>(); + runInFX(() -> { + JFXButton loginOfflineButton = new JFXButton(i18n("account.login.offline")); + loginOfflineButton.setOnAction(event -> { + try { + future.complete(Task.completed(account.playOffline())); + } catch (AuthenticationException e2) { + future.completeExceptionally(e2); + } + }); + JFXButton retryButton = new JFXButton(i18n("button.retry")); + retryButton.setOnAction(event -> { + future.complete(logIn(account)); + }); + Controllers.dialog(new MessageDialogPane.Builder(i18n("account.failed.server_disconnected"), i18n("account.failed"), MessageType.ERROR) + .addAction(loginOfflineButton) + .addAction(retryButton) + .addCancel(() -> + future.completeExceptionally(new CancellationException())) + .build()); + }); + return Task.fromCompletableFuture(future).thenComposeAsync(task -> task); + } + }); + } + private static Optional getLog4jPatch(Version version) { Optional log4jVersion = version.getLibraries().stream() .filter(it -> it.is("org.apache.logging.log4j", "log4j-core") diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 4d18d0c2f..97b7a3857 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -123,10 +123,11 @@ public final class MessageDialogPane extends StackPane { public Builder addAction(Node actionNode) { dialog.addButton(actionNode); + actionNode.getStyleClass().add("dialog-accept"); return this; } - public Builder ok(Runnable ok) { + public Builder ok(@Nullable Runnable ok) { JFXButton btnOk = new JFXButton(i18n("button.ok")); btnOk.getStyleClass().add("dialog-accept"); if (ok != null) { @@ -137,7 +138,22 @@ public final class MessageDialogPane extends StackPane { return this; } - public Builder yesOrNo(Runnable yes, Runnable no) { + public Builder addCancel(@Nullable Runnable cancel) { + return addCancel(i18n("button.cancel"), cancel); + } + + public Builder addCancel(String cancelText, @Nullable Runnable cancel) { + JFXButton btnCancel = new JFXButton(cancelText); + btnCancel.getStyleClass().add("dialog-cancel"); + if (cancel != null) { + btnCancel.setOnAction(e -> cancel.run()); + } + dialog.addButton(btnCancel); + dialog.setCancelButton(btnCancel); + return this; + } + + public Builder yesOrNo(@Nullable Runnable yes, @Nullable Runnable no) { JFXButton btnYes = new JFXButton(i18n("button.yes")); btnYes.getStyleClass().add("dialog-accept"); if (yes != null) { @@ -145,27 +161,14 @@ public final class MessageDialogPane extends StackPane { } dialog.addButton(btnYes); - JFXButton btnNo = new JFXButton(i18n("button.no")); - btnNo.getStyleClass().add("dialog-cancel"); - if (no != null) { - btnNo.setOnAction(e -> no.run()); - } - dialog.addButton(btnNo); - dialog.setCancelButton(btnNo); + addCancel(i18n("button.no"), no); return this; } public Builder actionOrCancel(ButtonBase actionButton, Runnable cancel) { dialog.addButton(actionButton); - JFXButton btnCancel = new JFXButton(i18n("button.cancel")); - btnCancel.getStyleClass().add("dialog-cancel"); - if (cancel != null) { - btnCancel.setOnAction(e -> cancel.run()); - } - dialog.addButton(btnCancel); - dialog.setCancelButton(btnCancel); - + addCancel(cancel); return this; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaFXPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaFXPatcher.java index e22d4d679..908bf171e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaFXPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/JavaFXPatcher.java @@ -20,6 +20,8 @@ package org.jackhuang.hmcl.util; import java.nio.file.Path; import java.util.Set; +import static org.jackhuang.hmcl.util.Logging.LOG; + /** * Utility for Adding JavaFX to module path. * @@ -30,6 +32,6 @@ public final class JavaFXPatcher { } public static void patch(Set modules, Path... jarPaths) { - // Nothing to do with Java 8 + LOG.info("No need to patch JavaFX with Java 8"); } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index acf2837a4..1c74a5310 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -72,6 +72,7 @@ account.failed.invalid_password=Invalid password account.failed.invalid_token=Please try to re-login again. account.failed.migration=Your account needs to be migrated to a Microsoft account. If you already did, you should re-login to your migrated Microsoft account instead. account.failed.no_character=There are no characters linked to this account. +account.failed.server_disconnected=Cannot access authentication server. You can log in offline or try to re-login. account.failed.server_response_malformed=Invalid server response, the authentication server may not be working. account.failed.wrong_account=You have logged in to the wrong account. account.hmcl.hint=You need to click on "Login" and complete the process in the opened tab in your browser. @@ -85,6 +86,7 @@ account.injector.server_url=Server URL account.injector.server_name=Server Name account.login=Login account.login.hint=We will not store your password. +account.login.offline=Login offline account.login.refresh=Re-login account.logout=Logout account.register=Register @@ -169,6 +171,7 @@ button.ok=OK button.refresh=Refresh button.remove=Remove button.remove.confirm=Are you sure you want to permanently remove it? This action cannot be undone\! +button.retry=Retry button.save=Save button.save_as=Save As button.select_all=Select All diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index a8aef6508..506b94d10 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -70,6 +70,7 @@ account.failed.invalid_password=密碼無效 account.failed.invalid_token=請嘗試登出並重新輸入密碼登入 account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你已經遷移,你需要使用微軟登錄方式登錄遷移後的微軟帳號。 account.failed.no_character=該帳戶沒有角色 +account.failed.server_disconnected=無法訪問登錄伺服器。是否以離線模式繼續登錄(進入遊戲後無法進入有正版驗證的伺服器)?或嘗試重新登入? account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障 account.failed.wrong_account=登錄了錯誤的帳號 account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入 @@ -83,6 +84,7 @@ account.injector.server_url=伺服器位址 account.injector.server_name=伺服器名稱 account.login=登入 account.login.hint=我們不會保存你的密碼 +account.login.offline=以離線模式登錄 account.login.refresh=重新登錄 account.logout=登出 account.register=註冊 @@ -157,6 +159,7 @@ button.ok=確定 button.refresh=重新整理 button.remove=刪除 button.remove.confirm=您確認要刪除嗎?該操作無法撤銷! +button.retry=重試 button.save=儲存 button.save_as=另存為 button.select_all=全選 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 334d75e7b..1ac3fdb88 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -70,7 +70,7 @@ account.failed.invalid_password=无效的密码 account.failed.invalid_token=请尝试登出并重新输入密码登录 account.failed.migration=你的帐号需要被迁移至微软帐号。如果你已经迁移,你需要使用微软登录方式登录迁移后的微软帐号。 account.failed.no_character=该帐号没有角色 -account.failed.server_disconnected=无法访问登录服务器。离线模式继续登录,或者 +account.failed.server_disconnected=无法访问登录服务器。是否以离线模式继续登录(进入游戏后无法进入有正版验证的服务器)?或尝试重新登录? account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障 account.failed.wrong_account=登录了错误的帐号 account.hmcl.hint=你需要点击“登录”按钮,并在打开的网页中完成登录 @@ -84,6 +84,7 @@ account.injector.server_url=服务器地址 account.injector.server_name=服务器名称 account.login=登录 account.login.hint=我们不会保存你的密码 +account.login.offline=以离线模式登录 account.login.refresh=重新登录 account.logout=登出 account.register=注册 @@ -158,6 +159,7 @@ button.ok=确定 button.refresh=刷新 button.remove=删除 button.remove.confirm=您确定要删除吗?此操作无法撤销! +button.retry=重试 button.save=保存 button.save_as=另存为 button.select_all=全选 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index 26af74f32..d1b7138a9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -64,7 +64,7 @@ public abstract class Account implements Observable { * Play offline. * @return the specific offline player's info. */ - public abstract Optional playOffline() throws AuthenticationException; + public abstract AuthInfo playOffline() throws AuthenticationException; public abstract Map toStorage(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NotLoggedInException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NotLoggedInException.java new file mode 100644 index 000000000..7e246336b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/NotLoggedInException.java @@ -0,0 +1,21 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.auth; + +public class NotLoggedInException extends AuthenticationException { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java index 054dd51fe..b7d25d37f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorAccount.java @@ -17,10 +17,7 @@ */ package org.jackhuang.hmcl.auth.authlibinjector; -import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.auth.AuthenticationException; -import org.jackhuang.hmcl.auth.CharacterSelector; -import org.jackhuang.hmcl.auth.ServerDisconnectException; +import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; @@ -66,15 +63,15 @@ public class AuthlibInjectorAccount extends YggdrasilAccount { } @Override - public Optional playOffline() { - Optional auth = super.playOffline(); + public AuthInfo playOffline() throws AuthenticationException { + AuthInfo auth = super.playOffline(); Optional artifact = downloader.getArtifactInfoImmediately(); Optional prefetchedMeta = server.getMetadataResponse(); - if (auth.isPresent() && artifact.isPresent() && prefetchedMeta.isPresent()) { - return Optional.of(new AuthlibInjectorAuthInfo(auth.get(), artifact.get(), server, prefetchedMeta.get())); + if (artifact.isPresent() && prefetchedMeta.isPresent()) { + return new AuthlibInjectorAuthInfo(auth, artifact.get(), server, prefetchedMeta.get()); } else { - return Optional.empty(); + throw new NotLoggedInException(); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java index 0e114225b..d79673855 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java @@ -117,8 +117,8 @@ public class MicrosoftAccount extends OAuthAccount { } @Override - public Optional playOffline() { - return Optional.of(session.toAuthInfo()); + public AuthInfo playOffline() { + return session.toAuthInfo(); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java index d829bb56c..8cd8b22ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/OfflineAccount.java @@ -180,8 +180,8 @@ public class OfflineAccount extends Account { } @Override - public Optional playOffline() throws AuthenticationException { - return Optional.of(logIn()); + public AuthInfo playOffline() throws AuthenticationException { + return logIn(); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index 1b3c97664..6d79bcd27 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -161,8 +161,8 @@ public class YggdrasilAccount extends ClassicAccount { } @Override - public Optional playOffline() { - return Optional.of(session.toAuthInfo()); + public AuthInfo playOffline() throws AuthenticationException { + return session.toAuthInfo(); } @Override diff --git a/javafx.gradle.kts b/javafx.gradle.kts index 374f49f88..e71944d55 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -74,7 +74,7 @@ if (!jfxInClasspath && JavaVersion.current() >= JavaVersion.VERSION_11) { val classifier = platform.classifier rootProject.subprojects { for (module in jfxModules) { - dependencies.add("compileOnly", "$groupId:javafx-$module:$version:$classifier") + dependencies.add("implementation", "$groupId:javafx-$module:$version:$classifier") dependencies.add("testImplementation", "$groupId:javafx-$module:$version:$classifier") } }