Added a new Couldnt connect to server error message

Re wrote the URL parsing logic again, but due to increased complexity put it into a utility class
Fixed desktop version opening up a lobby even when it did not connect to a server
Wired up the new error message to mobile, and both error messages to Desktop
This commit is contained in:
matthias8422
2025-04-24 21:09:09 -07:00
committed by Chris H
parent f69fd12ebd
commit d74d2da755
13 changed files with 93 additions and 22 deletions

View File

@@ -3,6 +3,7 @@ package forge.screens.home.online;
import java.net.BindException; import java.net.BindException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
@@ -16,12 +17,14 @@ import forge.gui.error.BugReporter;
import forge.gui.framework.EDocID; import forge.gui.framework.EDocID;
import forge.gui.framework.ICDoc; import forge.gui.framework.ICDoc;
import forge.gui.util.SOptionPane; import forge.gui.util.SOptionPane;
import forge.localinstance.properties.ForgeConstants;
import forge.menus.IMenuProvider; import forge.menus.IMenuProvider;
import forge.menus.MenuUtil; import forge.menus.MenuUtil;
import forge.screens.home.CHomeUI; import forge.screens.home.CHomeUI;
import forge.screens.home.CLobby; import forge.screens.home.CLobby;
import forge.screens.home.VLobby; import forge.screens.home.VLobby;
import forge.screens.home.sanctioned.ConstructedGameMenu; import forge.screens.home.sanctioned.ConstructedGameMenu;
import forge.toolbox.FOptionPane;
import forge.util.Localizer; import forge.util.Localizer;
public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider {
@@ -39,7 +42,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider {
if (url == null) { return; } if (url == null) { return; }
FThreads.invokeInBackgroundThread(() -> { FThreads.invokeInBackgroundThread(() -> {
if (url.length() > 0) { if (!url.isEmpty()) {
join(url); join(url);
} }
else { else {
@@ -83,16 +86,23 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider {
}); });
final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE);
if(Objects.equals(result.getMessage(), ForgeConstants.CLOSE_CONN_COMMAND)) {
SwingUtilities.invokeLater(() -> { FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("UnableConnectToServer", url));
SOverlayUtils.hideOverlay(); SOverlayUtils.hideOverlay();
if (result instanceof ChatMessage) { } else if (Objects.equals(result.getMessage(), ForgeConstants.INVALID_HOST_COMMAND)) {
FNetOverlay.SINGLETON_INSTANCE.show(result); FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblDetectedInvalidHostAddress", url));
if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) { SOverlayUtils.hideOverlay();
VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate(); } else {
SwingUtilities.invokeLater(() -> {
SOverlayUtils.hideOverlay();
if (result instanceof ChatMessage) {
FNetOverlay.SINGLETON_INSTANCE.show(result);
if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) {
VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate();
}
} }
} });
}); }
} }
@Override @Override

View File

@@ -108,6 +108,9 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby {
if (joinServer) { if (joinServer) {
result[0] = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); result[0] = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface);
if (result[0].getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception if (result[0].getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception
closeConn(Forge.getLocalizer().getMessage("UnableConnectToServer", url));
return;
} else if (result[0].getMessage() == ForgeConstants.INVALID_HOST_COMMAND) {
closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url)); closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url));
return; return;
} }

View File

@@ -281,6 +281,7 @@ OKresetWorkshopLayout=Workshop-Layout wurde zurückgesetzt.
AresetMatchScreenLayout=Dies wird das Spielfeldlayout zurücksetzen.\nFalls du vorher noch das derzeitige Layout speichern möchtest, nutze bitte Forge->Layout->File->Save Current Layout in einem laufenden Spiel.\n\nSpielfeldlayout zurücksetzen? AresetMatchScreenLayout=Dies wird das Spielfeldlayout zurücksetzen.\nFalls du vorher noch das derzeitige Layout speichern möchtest, nutze bitte Forge->Layout->File->Save Current Layout in einem laufenden Spiel.\n\nSpielfeldlayout zurücksetzen?
TresetMatchScreenLayout=Spielfeldlayout zurücksetzen TresetMatchScreenLayout=Spielfeldlayout zurücksetzen
OKresetMatchScreenLayout=Spielfeldlayout wurde zurückgesetzt. OKresetMatchScreenLayout=Spielfeldlayout wurde zurückgesetzt.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Gültige Spielformate lblSanctionedFormats=Gültige Spielformate
lblOnlineMultiplayer=Mehrspieler (online) lblOnlineMultiplayer=Mehrspieler (online)

View File

@@ -283,6 +283,7 @@ OKresetWorkshopLayout=Workshop layout has been reset.
AresetMatchScreenLayout=This will reset the layout of the Match screen.\n If you want to save the current layout first, please use the Dock tab -> Save Layout option in the Match screen.\n\n Reset layout? AresetMatchScreenLayout=This will reset the layout of the Match screen.\n If you want to save the current layout first, please use the Dock tab -> Save Layout option in the Match screen.\n\n Reset layout?
TresetMatchScreenLayout=Reset Match Screen Layout TresetMatchScreenLayout=Reset Match Screen Layout
OKresetMatchScreenLayout=Match Screen layout has been reset. OKresetMatchScreenLayout=Match Screen layout has been reset.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Sanctioned Formats lblSanctionedFormats=Sanctioned Formats
lblOnlineMultiplayer=Online Multiplayer lblOnlineMultiplayer=Online Multiplayer

View File

@@ -283,6 +283,7 @@ OKresetWorkshopLayout=El diseño del taller se ha restablecido.
AresetMatchScreenLayout=Esto restablecerá el diseño de la pantalla de juego.\n Si desea guardar primero el diseño actual, use la pestaña Dock -> Guardar opción de diseño en la pantalla de Juego\n \n ¿Restablecer diseño? AresetMatchScreenLayout=Esto restablecerá el diseño de la pantalla de juego.\n Si desea guardar primero el diseño actual, use la pestaña Dock -> Guardar opción de diseño en la pantalla de Juego\n \n ¿Restablecer diseño?
TresetMatchScreenLayout=Restablecer diseño de pantalla de juego TresetMatchScreenLayout=Restablecer diseño de pantalla de juego
OKresetMatchScreenLayout=El diseño de la pantalla de juego se ha restablecido. OKresetMatchScreenLayout=El diseño de la pantalla de juego se ha restablecido.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Formatos oficiales lblSanctionedFormats=Formatos oficiales
lblOnlineMultiplayer=Multijugador online lblOnlineMultiplayer=Multijugador online

View File

@@ -281,6 +281,7 @@ OKresetWorkshopLayout=La disposition de l'atelier a été réinitialisée.
AresetMatchScreenLayout=Ceci réinitialisera la mise en page de l'écran Match.\n Si vous souhaitez d'abord enregistrer la mise en page actuelle, veuillez utiliser l'onglet Dock -> Enregistrer la mise en page dans l'écran Match.\n\n Réinitialiser la mise en page ? AresetMatchScreenLayout=Ceci réinitialisera la mise en page de l'écran Match.\n Si vous souhaitez d'abord enregistrer la mise en page actuelle, veuillez utiliser l'onglet Dock -> Enregistrer la mise en page dans l'écran Match.\n\n Réinitialiser la mise en page ?
TresetMatchScreenLayout=Réinitialiser la disposition de l'écran de correspondance TresetMatchScreenLayout=Réinitialiser la disposition de l'écran de correspondance
OKresetMatchScreenLayout=La disposition de l'écran de correspondance a été réinitialisée. OKresetMatchScreenLayout=La disposition de l'écran de correspondance a été réinitialisée.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Formats autorisés lblSanctionedFormats=Formats autorisés
lblOnlineMultiplayer=Multijoueur en ligne lblOnlineMultiplayer=Multijoueur en ligne

View File

@@ -280,6 +280,7 @@ OKresetWorkshopLayout=Il layout dell'officina è stato ripristinato.
AresetMatchScreenLayout=Ciò ripristinerà il layout della schermata degli incontri. \n Se si desidera prima salvare il layout corrente, utilizzare la scheda Comandi -> opzione Salva layout della schermata di gioco. \n \n Ripristinare layout? AresetMatchScreenLayout=Ciò ripristinerà il layout della schermata degli incontri. \n Se si desidera prima salvare il layout corrente, utilizzare la scheda Comandi -> opzione Salva layout della schermata di gioco. \n \n Ripristinare layout?
TresetMatchScreenLayout=Ripristina il layout della schermata degli incontri TresetMatchScreenLayout=Ripristina il layout della schermata degli incontri
OKresetMatchScreenLayout=Il layout della schermata degli incontri è stato ripristinato. OKresetMatchScreenLayout=Il layout della schermata degli incontri è stato ripristinato.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Formati sanzionati lblSanctionedFormats=Formati sanzionati
lblOnlineMultiplayer=Multigiocatore online lblOnlineMultiplayer=Multigiocatore online

View File

@@ -281,6 +281,7 @@ OKresetWorkshopLayout=ワークショップレイアウトのリセットされ
AresetMatchScreenLayout=これにより、試合画面のレイアウトがリセットされます。\n 現在のレイアウトを最初に保存する場合は、[Dock]タブ-> [レイアウトの保存]オプションを[一致]画面で使用してください。\n\n レイアウトをリセットしますか? AresetMatchScreenLayout=これにより、試合画面のレイアウトがリセットされます。\n 現在のレイアウトを最初に保存する場合は、[Dock]タブ-> [レイアウトの保存]オプションを[一致]画面で使用してください。\n\n レイアウトをリセットしますか?
TresetMatchScreenLayout=試合画面レイアウトのリセット TresetMatchScreenLayout=試合画面レイアウトのリセット
OKresetMatchScreenLayout=試合画面のレイアウトがリセットされました。 OKresetMatchScreenLayout=試合画面のレイアウトがリセットされました。
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=フォーマット lblSanctionedFormats=フォーマット
lblOnlineMultiplayer=オンラインマルチプレイ lblOnlineMultiplayer=オンラインマルチプレイ

View File

@@ -293,6 +293,7 @@ AresetMatchScreenLayout=Isso irá redefinir o layout da tela da Partida.\n\
Redefinir o layout? Redefinir o layout?
TresetMatchScreenLayout=Redefinir Layout da Partida TresetMatchScreenLayout=Redefinir Layout da Partida
OKresetMatchScreenLayout=O layout da Tela da Partida foi redefinido. OKresetMatchScreenLayout=O layout da Tela da Partida foi redefinido.
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=Formatos Sancionados lblSanctionedFormats=Formatos Sancionados
lblOnlineMultiplayer=Multijogador Online lblOnlineMultiplayer=Multijogador Online

View File

@@ -283,6 +283,7 @@ OKresetWorkshopLayout=游戏界面已重置
AresetMatchScreenLayout=这将重置匹配屏幕的布局。\n如果要保存当前的布局请使用“匹配”屏幕中的“保存布局”选项。\n\n重置布局 AresetMatchScreenLayout=这将重置匹配屏幕的布局。\n如果要保存当前的布局请使用“匹配”屏幕中的“保存布局”选项。\n\n重置布局
TresetMatchScreenLayout=重置匹配布局 TresetMatchScreenLayout=重置匹配布局
OKresetMatchScreenLayout=匹配布局已重置。 OKresetMatchScreenLayout=匹配布局已重置。
UnableConnectToServer=Unable to connect to server with host {0}.
#EMenuGroup.java #EMenuGroup.java
lblSanctionedFormats=单人游戏 lblSanctionedFormats=单人游戏
lblOnlineMultiplayer=多人联机 lblOnlineMultiplayer=多人联机

View File

@@ -21,9 +21,10 @@ import forge.localinstance.properties.ForgePreferences.FPref;
import forge.model.FModel; import forge.model.FModel;
import forge.player.GamePlayerUtil; import forge.player.GamePlayerUtil;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.URLValidator;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.net.URI; import static forge.util.URLValidator.parseURL;
public class NetConnectUtil { public class NetConnectUtil {
private NetConnectUtil() { } private NetConnectUtil() { }
@@ -125,21 +126,17 @@ public class NetConnectUtil {
public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) {
final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); final IGuiGame gui = GuiBase.getInterface().getNewGuiGame();
String hostname; String hostname;
int port = ForgeConstants.DEFAULT_SERVER_CONNECTION_PORT; int port;
try { URLValidator.HostPort hostPort = parseURL(url);
// Check if the URL already has a protocol if(hostPort == null) {
String formattedUrl = url.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*") ? url : "http://" + url; return new ChatMessage(null, ForgeConstants.INVALID_HOST_COMMAND);
// Parse the URI
URI uri = new URI(formattedUrl);
hostname = uri.getHost();
if (uri.getPort() != -1) { // If a port is specified in the URL
port = uri.getPort();
}
} catch (Exception ex) {
hostname = url; // Fallback to original URL if parsing fails
} }
hostname = hostPort.host();
port = hostPort.port();
if(port == -1) port = ForgeConstants.DEFAULT_SERVER_CONNECTION_PORT;
final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui, hostname, port); final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui, hostname, port);
onlineLobby.setClient(client); onlineLobby.setClient(client);

View File

@@ -274,6 +274,7 @@ public final class ForgeConstants {
public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences"; public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences";
public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences"; public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences";
public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<"; public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<";
public static final String INVALID_HOST_COMMAND = "<<_TSOH_DILAVNI_<<";
// data that has defaults in the program dir but overrides/additions in the user dir // data that has defaults in the program dir but overrides/additions in the user dir
private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR; private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR;

View File

@@ -0,0 +1,52 @@
package forge.util;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Pattern;
public class URLValidator {
private static final Pattern DOMAIN_NAME_PATTERN = Pattern.compile("^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\\.[A-Za-z]{2,})+$");
public static HostPort parseIP(String ip) {
String[] parts = ip.split("\\.");
if (parts.length != 4) return null;
for (String part : parts) {
try {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) return null;
} catch (NumberFormatException e) {
return null;
}
}
return new HostPort(ip, null); // No port for raw IP
}
public static HostPort parseURL(String url) {
try {
//prepend http:// if a protocol is missing so URI parsing does not fail
String formattedUrl = url.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*") ? url : "http://" + url;
URI uri = new URI(formattedUrl);
String host = uri.getHost();
int port = uri.getPort(); // Returns -1 if port is not specified
if (host == null) return null;
HostPort hostPort;
if (parseIP(host) != null) {
hostPort = new HostPort(host, port == -1 ? null : port);
} else if (DOMAIN_NAME_PATTERN.matcher(host).matches()) {
hostPort = new HostPort(host, port == -1 ? null : port);
} else {
return null;
}
return hostPort;
} catch (URISyntaxException e) {
return null;
}
}
//This is fine, Records were introduced in Java 16, its essentially a DTO class with implicit getters and constructors
public record HostPort(String host, Integer port) {
}
}