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; package org.jackhuang.hmcl.game;
import com.jfoenix.controls.JFXButton;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.Launcher;
@ -35,6 +36,8 @@ import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.*; 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.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.*;
@ -62,6 +65,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level; import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; 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.Lang.resolveException;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
@ -171,17 +175,7 @@ public final class LauncherHelper {
.thenComposeAsync(() -> { .thenComposeAsync(() -> {
return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null); return gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null);
}) })
.thenComposeAsync(Task.supplyAsync(() -> { .thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
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(authInfo -> Task.supplyAsync(() -> { .thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null); LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, scriptFile != null);
return new HMCLGameLauncher( return new HMCLGameLauncher(
@ -618,6 +612,43 @@ public final class LauncherHelper {
return future; 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) { private static Optional<String> getLog4jPatch(Version version) {
Optional<String> log4jVersion = version.getLibraries().stream() Optional<String> log4jVersion = version.getLibraries().stream()
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core") .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) { public Builder addAction(Node actionNode) {
dialog.addButton(actionNode); dialog.addButton(actionNode);
actionNode.getStyleClass().add("dialog-accept");
return this; return this;
} }
public Builder ok(Runnable ok) { public Builder ok(@Nullable Runnable ok) {
JFXButton btnOk = new JFXButton(i18n("button.ok")); JFXButton btnOk = new JFXButton(i18n("button.ok"));
btnOk.getStyleClass().add("dialog-accept"); btnOk.getStyleClass().add("dialog-accept");
if (ok != null) { if (ok != null) {
@ -137,7 +138,22 @@ public final class MessageDialogPane extends StackPane {
return this; 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")); JFXButton btnYes = new JFXButton(i18n("button.yes"));
btnYes.getStyleClass().add("dialog-accept"); btnYes.getStyleClass().add("dialog-accept");
if (yes != null) { if (yes != null) {
@ -145,27 +161,14 @@ public final class MessageDialogPane extends StackPane {
} }
dialog.addButton(btnYes); dialog.addButton(btnYes);
JFXButton btnNo = new JFXButton(i18n("button.no")); addCancel(i18n("button.no"), no);
btnNo.getStyleClass().add("dialog-cancel");
if (no != null) {
btnNo.setOnAction(e -> no.run());
}
dialog.addButton(btnNo);
dialog.setCancelButton(btnNo);
return this; return this;
} }
public Builder actionOrCancel(ButtonBase actionButton, Runnable cancel) { public Builder actionOrCancel(ButtonBase actionButton, Runnable cancel) {
dialog.addButton(actionButton); dialog.addButton(actionButton);
JFXButton btnCancel = new JFXButton(i18n("button.cancel")); addCancel(cancel);
btnCancel.getStyleClass().add("dialog-cancel");
if (cancel != null) {
btnCancel.setOnAction(e -> cancel.run());
}
dialog.addButton(btnCancel);
dialog.setCancelButton(btnCancel);
return this; return this;
} }

View File

@ -20,6 +20,8 @@ package org.jackhuang.hmcl.util;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Set; import java.util.Set;
import static org.jackhuang.hmcl.util.Logging.LOG;
/** /**
* Utility for Adding JavaFX to module path. * Utility for Adding JavaFX to module path.
* *
@ -30,6 +32,6 @@ public final class JavaFXPatcher {
} }
public static void patch(Set<String> modules, Path... jarPaths) { 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.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.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.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.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.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. 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.injector.server_name=Server Name
account.login=Login account.login=Login
account.login.hint=We will not store your password. account.login.hint=We will not store your password.
account.login.offline=Login offline
account.login.refresh=Re-login account.login.refresh=Re-login
account.logout=Logout account.logout=Logout
account.register=Register account.register=Register
@ -169,6 +171,7 @@ button.ok=OK
button.refresh=Refresh button.refresh=Refresh
button.remove=Remove button.remove=Remove
button.remove.confirm=Are you sure you want to permanently remove it? This action cannot be undone\! 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=Save
button.save_as=Save As button.save_as=Save As
button.select_all=Select All button.select_all=Select All

View File

@ -70,6 +70,7 @@ account.failed.invalid_password=密碼無效
account.failed.invalid_token=請嘗試登出並重新輸入密碼登入 account.failed.invalid_token=請嘗試登出並重新輸入密碼登入
account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你已經遷移,你需要使用微軟登錄方式登錄遷移後的微軟帳號。 account.failed.migration=你的帳號需要被遷移至微軟帳號。如果你已經遷移,你需要使用微軟登錄方式登錄遷移後的微軟帳號。
account.failed.no_character=該帳戶沒有角色 account.failed.no_character=該帳戶沒有角色
account.failed.server_disconnected=無法訪問登錄伺服器。是否以離線模式繼續登錄(進入遊戲後無法進入有正版驗證的伺服器)?或嘗試重新登入?
account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障 account.failed.server_response_malformed=無法解析認證伺服器回應,可能是伺服器故障
account.failed.wrong_account=登錄了錯誤的帳號 account.failed.wrong_account=登錄了錯誤的帳號
account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入 account.hmcl.hint=你需要點擊“登入”按鈕,並在打開的網頁中完成登入
@ -83,6 +84,7 @@ account.injector.server_url=伺服器位址
account.injector.server_name=伺服器名稱 account.injector.server_name=伺服器名稱
account.login=登入 account.login=登入
account.login.hint=我們不會保存你的密碼 account.login.hint=我們不會保存你的密碼
account.login.offline=以離線模式登錄
account.login.refresh=重新登錄 account.login.refresh=重新登錄
account.logout=登出 account.logout=登出
account.register=註冊 account.register=註冊
@ -157,6 +159,7 @@ button.ok=確定
button.refresh=重新整理 button.refresh=重新整理
button.remove=刪除 button.remove=刪除
button.remove.confirm=您確認要刪除嗎?該操作無法撤銷! button.remove.confirm=您確認要刪除嗎?該操作無法撤銷!
button.retry=重試
button.save=儲存 button.save=儲存
button.save_as=另存為 button.save_as=另存為
button.select_all=全選 button.select_all=全選

View File

@ -70,7 +70,7 @@ account.failed.invalid_password=无效的密码
account.failed.invalid_token=请尝试登出并重新输入密码登录 account.failed.invalid_token=请尝试登出并重新输入密码登录
account.failed.migration=你的帐号需要被迁移至微软帐号。如果你已经迁移,你需要使用微软登录方式登录迁移后的微软帐号。 account.failed.migration=你的帐号需要被迁移至微软帐号。如果你已经迁移,你需要使用微软登录方式登录迁移后的微软帐号。
account.failed.no_character=该帐号没有角色 account.failed.no_character=该帐号没有角色
account.failed.server_disconnected=无法访问登录服务器。离线模式继续登录,或者 account.failed.server_disconnected=无法访问登录服务器。是否以离线模式继续登录(进入游戏后无法进入有正版验证的服务器)?或尝试重新登录?
account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障 account.failed.server_response_malformed=无法解析认证服务器响应,可能是服务器故障
account.failed.wrong_account=登录了错误的帐号 account.failed.wrong_account=登录了错误的帐号
account.hmcl.hint=你需要点击“登录”按钮,并在打开的网页中完成登录 account.hmcl.hint=你需要点击“登录”按钮,并在打开的网页中完成登录
@ -84,6 +84,7 @@ account.injector.server_url=服务器地址
account.injector.server_name=服务器名称 account.injector.server_name=服务器名称
account.login=登录 account.login=登录
account.login.hint=我们不会保存你的密码 account.login.hint=我们不会保存你的密码
account.login.offline=以离线模式登录
account.login.refresh=重新登录 account.login.refresh=重新登录
account.logout=登出 account.logout=登出
account.register=注册 account.register=注册
@ -158,6 +159,7 @@ button.ok=确定
button.refresh=刷新 button.refresh=刷新
button.remove=删除 button.remove=删除
button.remove.confirm=您确定要删除吗?此操作无法撤销! button.remove.confirm=您确定要删除吗?此操作无法撤销!
button.retry=重试
button.save=保存 button.save=保存
button.save_as=另存为 button.save_as=另存为
button.select_all=全选 button.select_all=全选

View File

@ -64,7 +64,7 @@ public abstract class Account implements Observable {
* Play offline. * Play offline.
* @return the specific offline player's info. * @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(); 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; package org.jackhuang.hmcl.auth.authlibinjector;
import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType; import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
@ -66,15 +63,15 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
} }
@Override @Override
public Optional<AuthInfo> playOffline() { public AuthInfo playOffline() throws AuthenticationException {
Optional<AuthInfo> auth = super.playOffline(); AuthInfo auth = super.playOffline();
Optional<AuthlibInjectorArtifactInfo> artifact = downloader.getArtifactInfoImmediately(); Optional<AuthlibInjectorArtifactInfo> artifact = downloader.getArtifactInfoImmediately();
Optional<String> prefetchedMeta = server.getMetadataResponse(); Optional<String> prefetchedMeta = server.getMetadataResponse();
if (auth.isPresent() && artifact.isPresent() && prefetchedMeta.isPresent()) { if (artifact.isPresent() && prefetchedMeta.isPresent()) {
return Optional.of(new AuthlibInjectorAuthInfo(auth.get(), artifact.get(), server, prefetchedMeta.get())); return new AuthlibInjectorAuthInfo(auth, artifact.get(), server, prefetchedMeta.get());
} else { } else {
return Optional.empty(); throw new NotLoggedInException();
} }
} }

View File

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

View File

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

View File

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

View File

@ -74,7 +74,7 @@ if (!jfxInClasspath && JavaVersion.current() >= JavaVersion.VERSION_11) {
val classifier = platform.classifier val classifier = platform.classifier
rootProject.subprojects { rootProject.subprojects {
for (module in jfxModules) { 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") dependencies.add("testImplementation", "$groupId:javafx-$module:$version:$classifier")
} }
} }