fix(login): prompt to retry logging in. Closes #1658 #1591 #1544 1608.

This commit is contained in:
huanghongxun 2022-08-28 23:57:47 +08:00
parent 0c172e5d08
commit 08d7ff138b
13 changed files with 109 additions and 47 deletions

View File

@ -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<AuthInfo> 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<Task<AuthInfo>> 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<String> getLog4jPatch(Version version) {
Optional<String> log4jVersion = version.getLibraries().stream()
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core")

View File

@ -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;
}

View File

@ -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<String> modules, Path... jarPaths) {
// Nothing to do with Java 8
LOG.info("No need to patch JavaFX with Java 8");
}
}

View File

@ -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

View File

@ -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=全選

View File

@ -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=全选

View File

@ -64,7 +64,7 @@ public abstract class Account implements Observable {
* Play offline.
* @return the specific offline player's info.
*/
public abstract Optional<AuthInfo> playOffline() throws AuthenticationException;
public abstract AuthInfo playOffline() throws AuthenticationException;
public abstract Map<Object, Object> toStorage();

View File

@ -0,0 +1,21 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.auth;
public class NotLoggedInException extends AuthenticationException {
}

View File

@ -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<AuthInfo> playOffline() {
Optional<AuthInfo> auth = super.playOffline();
public AuthInfo playOffline() throws AuthenticationException {
AuthInfo auth = super.playOffline();
Optional<AuthlibInjectorArtifactInfo> artifact = downloader.getArtifactInfoImmediately();
Optional<String> 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();
}
}

View File

@ -117,8 +117,8 @@ public class MicrosoftAccount extends OAuthAccount {
}
@Override
public Optional<AuthInfo> playOffline() {
return Optional.of(session.toAuthInfo());
public AuthInfo playOffline() {
return session.toAuthInfo();
}
@Override

View File

@ -180,8 +180,8 @@ public class OfflineAccount extends Account {
}
@Override
public Optional<AuthInfo> playOffline() throws AuthenticationException {
return Optional.of(logIn());
public AuthInfo playOffline() throws AuthenticationException {
return logIn();
}
@Override

View File

@ -161,8 +161,8 @@ public class YggdrasilAccount extends ClassicAccount {
}
@Override
public Optional<AuthInfo> playOffline() {
return Optional.of(session.toAuthInfo());
public AuthInfo playOffline() throws AuthenticationException {
return session.toAuthInfo();
}
@Override

View File

@ -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")
}
}