diff --git a/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java index 4887fdcd5..562185836 100644 --- a/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java +++ b/app/src/main/java/io/xpipe/app/beacon/AppBeaconServer.java @@ -1,6 +1,8 @@ package io.xpipe.app.beacon; +import io.xpipe.app.beacon.api.HandshakeExchange; import io.xpipe.app.beacon.mcp.AppMcpServer; +import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppLocalTemp; import io.xpipe.app.core.AppProperties; import io.xpipe.app.issue.ErrorEventFactory; @@ -62,6 +64,7 @@ public class AppBeaconServer { AppProperties.get().queryEffectiveBeaconPort(false).orElseThrow()); INSTANCE.initAuthSecret(); INSTANCE.start(); + INSTANCE.testConnection(); TrackEvent.withInfo("Started http server") .tag("port", INSTANCE.getPort()) .build() @@ -145,6 +148,26 @@ public class AppBeaconServer { } } + private void testConnection() { + var address = "127.0.0.1"; + var client = new BeaconClient(address, port); + var method = BeaconAuthMethod.Local.builder().authFileContent(localAuthSecret).build(); + var info = BeaconClientInformation.Daemon.builder().build(); + + try { + client.performRequest(HandshakeExchange.Request.builder().client(info).auth(method).build(), 5); + } catch (Exception ex) { + if (AppProperties.get().isPrintBeaconMessages()) { + ErrorEventFactory.fromThrowable(ex).description("Failed to connect to beacon server " + address + ":" + port) + .omit().expected().handle(); + } + + var msg = "Unable to connect to local running HTTP server on http://localhost:" + port + + ". Is something on your system interfering with HTTP connections or TCP sockets?"; + ErrorEventFactory.fromMessage(msg).handle(); + } + } + private void start() throws IOException { executor = Executors.newFixedThreadPool(3, r -> { Thread t = Executors.defaultThreadFactory().newThread(r); diff --git a/app/src/main/java/io/xpipe/app/beacon/BeaconClient.java b/app/src/main/java/io/xpipe/app/beacon/BeaconClient.java index b70cd20c5..d2b8c6d1d 100644 --- a/app/src/main/java/io/xpipe/app/beacon/BeaconClient.java +++ b/app/src/main/java/io/xpipe/app/beacon/BeaconClient.java @@ -1,7 +1,10 @@ package io.xpipe.app.beacon; import io.xpipe.app.beacon.api.HandshakeExchange; +import io.xpipe.app.core.AppCache; import io.xpipe.app.core.AppProperties; +import io.xpipe.app.issue.ErrorEventFactory; +import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.util.JacksonMapper; import lombok.SneakyThrows; @@ -13,31 +16,34 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.Optional; public class BeaconClient { + private final String address; private final int port; private String token; - public BeaconClient(int port) { + public BeaconClient(String address, int port) { + this.address = address; this.port = port; } - public static BeaconClient establishConnection(int port, BeaconClientInformation information, Path authFile) - throws Exception { - var client = new BeaconClient(port); + public static BeaconClient establishConnection(int port, BeaconClientInformation information, Path authFile) throws Exception { + var addr = "127.0.0.1"; + var client = new BeaconClient(addr, port); var auth = Files.readString(authFile); HandshakeExchange.Response response = client.performRequest(HandshakeExchange.Request.builder() .client(information) .auth(BeaconAuthMethod.Local.builder().authFileContent(auth).build()) - .build()); + .build(), 5); client.token = response.getSessionToken(); return client; } @SuppressWarnings("unchecked") - public RES performRequest(BeaconInterface prov, String rawNode) + public RES performRequest(BeaconInterface prov, String rawNode, int timeoutSeconds) throws BeaconConnectorException, BeaconClientException, BeaconServerException { var content = rawNode; if (AppProperties.get().isPrintBeaconMessages()) { @@ -46,20 +52,24 @@ public class BeaconClient { } var client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .version(HttpClient.Version.HTTP_1_1) .proxy(HttpClient.Builder.NO_PROXY) .followRedirects(HttpClient.Redirect.NORMAL) .build(); HttpResponse response; try { // Use direct IP to prevent DNS lookups and potential blocks (e.g. portmaster) - var uri = URI.create("http://127.0.0.1:" + port + prov.getPath()); + var uri = URI.create("http://" + address + ":" + port + prov.getPath()); var builder = HttpRequest.newBuilder(); if (token != null) { builder.header("Authorization", "Bearer " + token); } - var httpRequest = builder.uri(uri) - .POST(HttpRequest.BodyPublishers.ofString(content)) - .build(); + var b = builder.uri(uri).POST(HttpRequest.BodyPublishers.ofString(content)); + if (timeoutSeconds != -1) { + b.timeout(Duration.ofSeconds(timeoutSeconds)); + } + var httpRequest = b.build(); response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); } catch (Exception ex) { throw new BeaconConnectorException("Couldn't send request", ex); @@ -107,6 +117,11 @@ public class BeaconClient { public RES performRequest(REQ req) throws BeaconConnectorException, BeaconClientException, BeaconServerException { + return performRequest(req, -1); + } + + public RES performRequest(REQ req, int timeoutSeconds) + throws BeaconConnectorException, BeaconClientException, BeaconServerException { ObjectNode node = JacksonMapper.getDefault().valueToTree(req); var prov = BeaconInterface.byRequest(req); if (prov.isEmpty()) { @@ -114,10 +129,10 @@ public class BeaconClient { } if (AppProperties.get().isPrintBeaconMessages()) { System.out.println( - "Sending request to server of type " + req.getClass().getName()); + "Sending request to server " + address + ":" + port + " of type " + req.getClass().getName()); } - return performRequest(prov.get(), node.toPrettyString()); + return performRequest(prov.get(), node.toPrettyString(), timeoutSeconds); } private Optional parseClientError(HttpResponse response) diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java b/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java index e225532a3..0b1afeb93 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorEventFactory.java @@ -6,10 +6,7 @@ import io.xpipe.app.process.ProcessOutputException; import io.xpipe.app.util.OsType; import java.lang.reflect.InvocationTargetException; -import java.nio.file.AccessDeniedException; -import java.nio.file.FileSystemException; -import java.nio.file.InvalidPathException; -import java.nio.file.NoSuchFileException; +import java.nio.file.*; import java.util.*; public class ErrorEventFactory { @@ -70,6 +67,10 @@ public class ErrorEventFactory { return "No such file: " + nsfe.getMessage(); } + if (t instanceof FileAlreadyExistsException faee) { + return "File already exists: " + faee.getMessage(); + } + if (t instanceof InvalidPathException ipe) { return "Invalid file path: " + ipe.getMessage(); }