From a59fd886c429c428373faed25d462f82f8e66f43 Mon Sep 17 00:00:00 2001 From: Sukikui Date: Tue, 26 Aug 2025 21:29:48 +0200 Subject: [PATCH 1/2] feat: add Access-Control-Allow-Origin --- README.md | 4 +- .../PlayerCoordsAPIClient.java | 237 ++++++++++-------- 2 files changed, 128 insertions(+), 113 deletions(-) diff --git a/README.md b/README.md index da7789e..294650c 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,8 @@ print(f"Player {data['username']} (UUID: {data['uuid']}) at X: {data['x']}, Y: { ### JavaScript ```javascript fetch("http://localhost:25565/api/coords") - .then(response => response.json()) - .then(data => console.log(`Player ${data.username} (UUID: ${data.uuid}) at X: ${data.x}, Y: ${data.y}, Z: ${data.z}`)); + .then(response => response.json()) + .then(data => console.log(`Player ${data.username} (UUID: ${data.uuid}) at X: ${data.x}, Y: ${data.y}, Z: ${data.z}`)); ```
diff --git a/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java b/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java index fbfb241..7bec943 100644 --- a/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java +++ b/src/client/java/fr/sukikui/playercoordsapi/PlayerCoordsAPIClient.java @@ -15,115 +15,130 @@ import java.util.concurrent.Executors; public class PlayerCoordsAPIClient implements ClientModInitializer { - private HttpServer server; - private boolean serverStarted = false; - // Hardcoded port value - no longer in config - private static final int PORT = 25565; - - @Override - public void onInitializeClient() { - // Start server on init if enabled - if (PlayerCoordsAPI.getConfig().enabled) { - startServer(); - } - - // Register tick event to constantly check config status - ClientTickEvents.END_CLIENT_TICK.register(client -> { - boolean configEnabled = PlayerCoordsAPI.getConfig().enabled; - - // If enabled and server not started, start server - if (configEnabled && !serverStarted) { - startServer(); - } - - // If disabled and server is running, stop server - if (!configEnabled && serverStarted) { - stopServer(); - } - }); - - PlayerCoordsAPI.LOGGER.info("Registered config monitor"); - } - - private void startServer() { - if (serverStarted) return; - - try { - PlayerCoordsAPI.LOGGER.info("Starting PlayerCoordsAPI HTTP server on port " + PORT); - server = HttpServer.create(new InetSocketAddress(PORT), 0); - server.createContext("/api/coords", this::handleCoordsRequest); - server.setExecutor(Executors.newSingleThreadExecutor()); - server.start(); - serverStarted = true; - PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server started successfully"); - } catch (IOException e) { - PlayerCoordsAPI.LOGGER.error("Failed to start PlayerCoordsAPI HTTP server", e); - } - } - - private void stopServer() { - if (server != null) { - PlayerCoordsAPI.LOGGER.info("Stopping PlayerCoordsAPI HTTP server"); - - // Create a separate thread to stop the server to prevent blocking - final HttpServer serverToStop = server; // Create a final reference for the thread - Thread stopThread = new Thread(() -> { - serverToStop.stop(0); // Stop with no delay - PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server stopped successfully"); - }); - stopThread.setDaemon(true); - stopThread.start(); - - // Set variables immediately so we know the server is being stopped - server = null; - serverStarted = false; - } - } - - private void handleCoordsRequest(HttpExchange exchange) throws IOException { - // Check if the client is allowed to access (only localhost) - String remoteAddress = exchange.getRemoteAddress().getAddress().getHostAddress(); - if (!remoteAddress.equals("127.0.0.1") && !remoteAddress.equals("0:0:0:0:0:0:0:1")) { - sendResponse(exchange, 403, "{\"error\": \"Access denied\"}"); - return; - } - - // Get player coordinates - MinecraftClient client = MinecraftClient.getInstance(); - PlayerEntity player = client.player; - - String responseText; - if (player != null) { - double x = player.getX(); - double y = player.getY(); - double z = player.getZ(); - String world = player.getWorld().getRegistryKey().getValue().toString(); - - // Get biome information - RegistryEntry biomeEntry = player.getWorld().getBiome(player.getBlockPos()); - String biome = biomeEntry.getKey().orElseThrow().getValue().toString(); - - // Get player UUID and username - String uuid = player.getUuid().toString(); - String username = player.getName().getString(); - - // Format as JSON - responseText = String.format( - "{\"x\": %.2f, \"y\": %.2f, \"z\": %.2f, \"world\": \"%s\", \"biome\": \"%s\", \"uuid\": \"%s\", \"username\": \"%s\"}", - x, y, z, world, biome, uuid, username - ); - sendResponse(exchange, 200, responseText); - } else { - sendResponse(exchange, 404, "{\"error\": \"Player not in world\"}"); - } - } - - private void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException { - exchange.getResponseHeaders().set("Content-Type", "application/json"); - exchange.sendResponseHeaders(statusCode, response.length()); - - try (OutputStream os = exchange.getResponseBody()) { - os.write(response.getBytes()); - } - } + private HttpServer server; + private boolean serverStarted = false; + // Hardcoded port value - no longer in config + private static final int PORT = 25565; + + @Override + public void onInitializeClient() { + // Start server on init if enabled + if (PlayerCoordsAPI.getConfig().enabled) { + startServer(); + } + + // Register tick event to constantly check config status + ClientTickEvents.END_CLIENT_TICK.register(client -> { + boolean configEnabled = PlayerCoordsAPI.getConfig().enabled; + + // If enabled and server not started, start server + if (configEnabled && !serverStarted) { + startServer(); + } + + // If disabled and server is running, stop server + if (!configEnabled && serverStarted) { + stopServer(); + } + }); + + PlayerCoordsAPI.LOGGER.info("Registered config monitor"); + } + + private void startServer() { + if (serverStarted) return; + + try { + PlayerCoordsAPI.LOGGER.info("Starting PlayerCoordsAPI HTTP server on port " + PORT); + server = HttpServer.create(new InetSocketAddress(PORT), 0); + server.createContext("/api/coords", this::handleCoordsRequest); + server.setExecutor(Executors.newSingleThreadExecutor()); + server.start(); + serverStarted = true; + PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server started successfully"); + } catch (IOException e) { + PlayerCoordsAPI.LOGGER.error("Failed to start PlayerCoordsAPI HTTP server", e); + } + } + + private void stopServer() { + if (server != null) { + PlayerCoordsAPI.LOGGER.info("Stopping PlayerCoordsAPI HTTP server"); + + // Create a separate thread to stop the server to prevent blocking + final HttpServer serverToStop = server; // Create a final reference for the thread + Thread stopThread = new Thread(() -> { + serverToStop.stop(0); // Stop with no delay + PlayerCoordsAPI.LOGGER.info("PlayerCoordsAPI HTTP server stopped successfully"); + }); + stopThread.setDaemon(true); + stopThread.start(); + + // Set variables immediately so we know the server is being stopped + server = null; + serverStarted = false; + } + } + + private void handleCoordsRequest(HttpExchange exchange) throws IOException { + // Handle CORS preflight request + if (exchange.getRequestMethod().equalsIgnoreCase("OPTIONS")) { + sendResponse(exchange, 204, null); + return; + } + + // Check if the client is allowed to access (only localhost) + String remoteAddress = exchange.getRemoteAddress().getAddress().getHostAddress(); + if (!remoteAddress.equals("127.0.0.1") && !remoteAddress.equals("0:0:0:0:0:0:0:1")) { + sendResponse(exchange, 403, "{\"error\": \"Access denied\"}"); + return; + } + + // Get player coordinates + MinecraftClient client = MinecraftClient.getInstance(); + PlayerEntity player = client.player; + + String responseText; + if (player != null) { + double x = player.getX(); + double y = player.getY(); + double z = player.getZ(); + String world = player.getWorld().getRegistryKey().getValue().toString(); + + // Get biome information + RegistryEntry biomeEntry = player.getWorld().getBiome(player.getBlockPos()); + String biome = biomeEntry.getKey().orElseThrow().getValue().toString(); + + // Get player UUID and username + String uuid = player.getUuid().toString(); + String username = player.getName().getString(); + + // Format as JSON + responseText = String.format( + "{\"x\": %.2f, \"y\": %.2f, \"z\": %.2f, \"world\": \"%s\", \"biome\": \"%s\", \"uuid\": \"%s\", \"username\": \"%s\"}", + x, y, z, world, biome, uuid, username + ); + sendResponse(exchange, 200, responseText); + } else { + sendResponse(exchange, 404, "{\"error\": \"Player not in world\"}"); + } + } + + private void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException { + // Add CORS headers + exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); + exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "GET, OPTIONS"); + exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + // Set content type if response is not null + if (response != null) { + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(statusCode, response.length()); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + } else { + exchange.sendResponseHeaders(statusCode, -1); // No response body + } + } } \ No newline at end of file From 1cc025363f27c16d290711985d1e8857a1fa3fc8 Mon Sep 17 00:00:00 2001 From: Sukikui Date: Tue, 26 Aug 2025 21:34:52 +0200 Subject: [PATCH 2/2] docs: add CORS specifications in the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 294650c..a2c41b9 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ For security reasons, the API server: - Only accepts connections from localhost `127.0.0.1` - Runs on port `25565` by default - Provides read-only access to player position data +- Allows requests from any origin (CORS `Access-Control-Allow-Origin: *`) for easy integration with web applications ## 🛠️ Examples