feat(dashboard): refine activity telemetry and maps
CI / validate (push) Successful in 5m24s
Release / release (push) Successful in 11m6s

This commit is contained in:
dmg
2026-08-01 20:28:32 -04:00
parent 9116107917
commit ebc7c7df17
32 changed files with 695 additions and 72 deletions
@@ -59,6 +59,43 @@ final class AccountManagerClient {
}
}
boolean reportConnected(UUID minecraftUuid, String username) {
String compactUuid = minecraftUuid.toString().replace("-", "").toLowerCase();
ConnectionRequest payload = new ConnectionRequest(
UUID.randomUUID().toString(),
config.serverId(),
compactUuid,
username,
Instant.now().toString()
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(config.apiUrl() + "/api/velocity/connection"))
.timeout(config.timeout())
.header("Authorization", "Bearer " + config.apiToken())
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(payload)))
.build();
try {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
return response.statusCode() == 204;
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
return false;
} catch (IOException | RuntimeException exception) {
return false;
}
}
private record ConnectionRequest(
String requestId,
String serverId,
String minecraftUuid,
String username,
String occurredAt
) {}
private record AccessRequest(
String requestId,
String serverId,
@@ -5,9 +5,11 @@ import com.velocitypowered.api.event.EventTask;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PostLoginEvent;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.io.IOException;
import java.nio.file.Path;
import net.kyori.adventure.text.Component;
@@ -22,13 +24,15 @@ import org.slf4j.Logger;
public final class MinecraftAccountManagerPlugin {
private final Logger logger;
private final Path dataDirectory;
private final ProxyServer proxyServer;
private volatile AccountManagerClient accountManagerClient;
private volatile String fallbackMessage = "Please register your Minecraft account in Discord before joining.";
@Inject
public MinecraftAccountManagerPlugin(Logger logger, @DataDirectory Path dataDirectory) {
public MinecraftAccountManagerPlugin(Logger logger, @DataDirectory Path dataDirectory, ProxyServer proxyServer) {
this.logger = logger;
this.dataDirectory = dataDirectory;
this.proxyServer = proxyServer;
}
@Subscribe
@@ -66,4 +70,19 @@ public final class MinecraftAccountManagerPlugin {
}
});
}
@Subscribe
public void onPostLogin(PostLoginEvent event) {
proxyServer.getScheduler().buildTask(this, () -> {
AccountManagerClient client = accountManagerClient;
if (client == null) return;
boolean recorded = client.reportConnected(
event.getPlayer().getUniqueId(),
event.getPlayer().getUsername()
);
if (!recorded) {
logger.warn("Could not report confirmed Minecraft connection for {} ({})", event.getPlayer().getUsername(), event.getPlayer().getUniqueId());
}
}).schedule();
}
}
@@ -2,9 +2,15 @@ package games.dmg.accountmanager;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.Test;
class AccountManagerClientTest {
@@ -27,4 +33,55 @@ class AccountManagerClientTest {
assertFalse(decision.allowed());
assertEquals("Register through Discord.", decision.message());
}
@Test
void reportsConfirmedConnectionsToTheAuthenticatedEndpoint() throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
AtomicReference<String> body = new AtomicReference<>();
AtomicReference<String> authorization = new AtomicReference<>();
server.createContext("/api/velocity/connection", exchange -> {
body.set(new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8));
authorization.set(exchange.getRequestHeaders().getFirst("Authorization"));
exchange.sendResponseHeaders(204, -1);
exchange.close();
});
server.start();
try {
PluginConfig config = new PluginConfig(
"http://127.0.0.1:" + server.getAddress().getPort(),
"velocity-test",
"test-token",
Duration.ofSeconds(2),
"Register through Discord."
);
assertTrue(new AccountManagerClient(config).reportConnected(
UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"),
"Notch"
));
assertEquals("Bearer test-token", authorization.get());
assertTrue(body.get().contains("\"minecraftUuid\":\"069a79f444e94726a5befca90e38aaf5\""));
assertTrue(body.get().contains("\"username\":\"Notch\""));
} finally {
server.stop(0);
}
}
@Test
void connectionReportingIsBestEffortWhenTheApiCannotBeReached() {
PluginConfig config = new PluginConfig(
"http://127.0.0.1:1",
"velocity-test",
"test-token",
Duration.ofMillis(100),
"Register through Discord."
);
boolean recorded = new AccountManagerClient(config).reportConnected(
UUID.fromString("069a79f4-44e9-4726-a5be-fca90e38aaf5"),
"Notch"
);
assertFalse(recorded);
}
}