package vip.mate.tool.browser;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Diagnoses why the browser tool might fail to launch on this host.
*
*
Runs a dry inventory — detecting system browsers, Playwright cache, Node runtime,
* required shared libraries on Linux, container / root context — without actually
* launching a session. Produces a structured report with actionable next steps.
*/
@Slf4j
@Service
public class BrowserDiagnosticsService {
private static final boolean IS_WINDOWS = System.getProperty("os.name", "")
.toLowerCase(Locale.ROOT).contains("win");
private static final boolean IS_LINUX = System.getProperty("os.name", "")
.toLowerCase(Locale.ROOT).contains("linux");
/** Shared libraries Chromium needs on Linux. Missing any is a hard block. */
private static final List REQUIRED_LINUX_LIBS = List.of(
"libnss3", "libgbm", "libasound", "libxkbcommon", "libx11", "libxcomposite",
"libxdamage", "libxrandr", "libxfixes", "libatk", "libcups", "libpango"
);
private final BrowserProperties props;
public BrowserDiagnosticsService(BrowserProperties props) {
this.props = props;
}
public Report run() {
List findings = new ArrayList<>();
findings.add(inspectEnvironment());
findings.add(inspectConfiguredCdp());
findings.add(inspectConfiguredPath());
findings.add(inspectEnvPath());
findings.add(inspectSystemBrowsers());
findings.add(inspectPlaywrightCache());
if (IS_LINUX) {
findings.add(inspectLinuxLibs());
}
String overall = deriveOverall(findings);
List advice = deriveAdvice(findings);
return new Report(overall, findings, advice);
}
// ==================== Individual probes ====================
private Finding inspectEnvironment() {
Map data = new LinkedHashMap<>();
data.put("os", System.getProperty("os.name"));
data.put("arch", System.getProperty("os.arch"));
data.put("user", System.getProperty("user.name"));
data.put("container", BrowserLauncher.isRunningInContainer());
data.put("root", BrowserLauncher.isRunningAsRoot());
return new Finding("environment", Status.INFO, "Runtime environment", data, null);
}
private Finding inspectConfiguredCdp() {
String url = props.getCdpUrl();
if (url == null || url.isBlank()) {
return new Finding("config.cdp-url", Status.INFO, "mateclaw.browser.cdp-url not set", Map.of(), null);
}
Map data = new LinkedHashMap<>();
data.put("url", url);
try {
String resp = HttpUtil.get(stripTrailing(url) + "/json/version", 2000);
if (resp != null && resp.contains("webSocketDebuggerUrl")) {
data.put("reachable", true);
return new Finding("config.cdp-url", Status.OK,
"CDP endpoint reachable", data, null);
}
data.put("reachable", false);
data.put("response", resp);
return new Finding("config.cdp-url", Status.ERROR,
"CDP endpoint did not return a valid /json/version payload", data,
"Ensure Chrome was started with --remote-debugging-port=" + port(url) + " and /json/version is reachable.");
} catch (Exception e) {
data.put("error", e.getMessage());
return new Finding("config.cdp-url", Status.ERROR,
"CDP endpoint unreachable: " + e.getMessage(), data,
"Start Chrome with --remote-debugging-port or clear mateclaw.browser.cdp-url.");
}
}
private Finding inspectConfiguredPath() {
String path = props.getChromePath();
if (path == null || path.isBlank()) {
return new Finding("config.chrome-path", Status.INFO, "mateclaw.browser.chrome-path not set", Map.of(), null);
}
Path p = Path.of(path);
if (!Files.exists(p)) {
return new Finding("config.chrome-path", Status.ERROR,
"Configured chrome-path does not exist: " + path, Map.of("path", path),
"Install Chrome at that path, or clear mateclaw.browser.chrome-path.");
}
if (!Files.isExecutable(p)) {
return new Finding("config.chrome-path", Status.ERROR,
"Configured chrome-path is not executable: " + path, Map.of("path", path),
"chmod +x the binary, or point to the real chrome executable.");
}
return new Finding("config.chrome-path", Status.OK, "Configured chrome-path is valid",
Map.of("path", path), null);
}
private Finding inspectEnvPath() {
String env = System.getenv("CHROME_PATH");
if (env == null || env.isBlank()) {
return new Finding("env.CHROME_PATH", Status.INFO, "CHROME_PATH not set", Map.of(), null);
}
Path p = Path.of(env);
if (!Files.exists(p)) {
return new Finding("env.CHROME_PATH", Status.WARN,
"CHROME_PATH points to a missing file: " + env, Map.of("path", env),
"Fix CHROME_PATH or unset it to let auto-detection run.");
}
return new Finding("env.CHROME_PATH", Status.OK, "CHROME_PATH resolves to a real file",
Map.of("path", env), null);
}
private Finding inspectSystemBrowsers() {
List