Complete private authentication, server integrations and activity reporting
Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
This commit is contained in:
commit
a1f19e86c6
74 files changed
+2108
-95
No files matched your search
@@ -0,0 +1,123 @@
|
||||
package net.scopenet.integration;
|
||||
|
||||
import com.google.gson.*;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.*;
|
||||
|
||||
public final class Integration implements AutoCloseable {
|
||||
public static final String UNAVAILABLE = "SCOPENET sign-in is unavailable. Please try again shortly.";
|
||||
public final Activity activity = new Activity();
|
||||
private final PanelClient client;
|
||||
private final JsonObject hello;
|
||||
private final Consumer<String> log;
|
||||
private final BiConsumer<UUID, String> kick;
|
||||
private final ExecutorService syncWorker = Executors.newSingleThreadExecutor(r -> daemon(r, "scopenet-sync"));
|
||||
private final ExecutorService loginWorker = new ThreadPoolExecutor(2, 4, 30, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(64), r -> daemon(r, "scopenet-login"), new ThreadPoolExecutor.AbortPolicy());
|
||||
private final AtomicBoolean syncing = new AtomicBoolean();
|
||||
private volatile boolean closed;
|
||||
private boolean registered;
|
||||
private JsonObject pending;
|
||||
private long nextSync;
|
||||
private long ticks;
|
||||
private long sampleStart = System.nanoTime();
|
||||
|
||||
public Integration(Settings settings, String software, String mcVersion, String version,
|
||||
boolean onlineMode, int maxPlayers, Consumer<String> log, BiConsumer<UUID, String> kick) {
|
||||
if (!onlineMode) throw new IllegalArgumentException("SCOPENET requires online-mode=true and authlib-injector");
|
||||
this.client = new PanelClient(settings);
|
||||
this.log = log;
|
||||
this.kick = kick;
|
||||
hello = new JsonObject();
|
||||
hello.addProperty("software", software);
|
||||
hello.addProperty("mc_version", mcVersion);
|
||||
hello.addProperty("plugin_version", version);
|
||||
hello.addProperty("online_mode", onlineMode);
|
||||
hello.addProperty("max_players", maxPlayers);
|
||||
}
|
||||
|
||||
/** Null means allowed. Errors, missing settings and overload always deny. */
|
||||
public CompletableFuture<String> login(UUID id, String name, String ip) {
|
||||
if (closed) return CompletableFuture.completedFuture(UNAVAILABLE);
|
||||
try {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JsonObject request = new JsonObject();
|
||||
request.addProperty("uuid", id.toString());
|
||||
request.addProperty("name", name);
|
||||
if (ip != null) request.addProperty("ip", ip);
|
||||
try { return PanelClient.verdict(client.post("login", request)); }
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) Thread.currentThread().interrupt();
|
||||
return UNAVAILABLE;
|
||||
}
|
||||
}, loginWorker).completeOnTimeout(UNAVAILABLE, 6, TimeUnit.SECONDS);
|
||||
} catch (RejectedExecutionException e) {
|
||||
return CompletableFuture.completedFuture(UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
/** Called on the server thread. Network and JSON work runs on the worker. */
|
||||
public void tick() {
|
||||
if (closed) return;
|
||||
ticks++;
|
||||
long now = System.nanoTime();
|
||||
if (now < nextSync || !syncing.compareAndSet(false, true)) return;
|
||||
double tps = Math.min(20, ticks * 1_000_000_000.0 / Math.max(1, now - sampleStart));
|
||||
ticks = 0;
|
||||
sampleStart = now;
|
||||
nextSync = now + TimeUnit.SECONDS.toNanos(30);
|
||||
syncWorker.execute(() -> {
|
||||
try { sync(tps); }
|
||||
catch (Exception e) {
|
||||
if (e instanceof InterruptedException) Thread.currentThread().interrupt();
|
||||
log.accept("SCOPENET sync failed; retained batch will be retried (" + e.getClass().getSimpleName() + ")");
|
||||
} finally { syncing.set(false); }
|
||||
});
|
||||
}
|
||||
|
||||
private void sync(double tps) throws Exception {
|
||||
if (!registered) {
|
||||
JsonObject response = client.post("hello", hello);
|
||||
if (!response.has("server_id")) throw new IllegalStateException("Invalid hello response");
|
||||
registered = true;
|
||||
}
|
||||
if (pending == null) pending = activity.drain(tps);
|
||||
JsonObject response = client.post("sync", pending);
|
||||
if (!response.has("ok") || !response.get("ok").getAsBoolean() || !response.has("kick")
|
||||
|| !response.get("kick").isJsonArray()) throw new IllegalStateException("Invalid sync response");
|
||||
pending = null;
|
||||
for (JsonElement element : response.getAsJsonArray("kick")) {
|
||||
try {
|
||||
JsonObject entry = element.getAsJsonObject();
|
||||
String message = entry.has("message") && !entry.get("message").isJsonNull()
|
||||
? entry.get("message").getAsString() : "Your server access was revoked.";
|
||||
kick.accept(UUID.fromString(entry.get("uuid").getAsString()), message);
|
||||
} catch (RuntimeException e) { log.accept("SCOPENET ignored a malformed kick entry"); }
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
closed = true;
|
||||
loginWorker.shutdownNow();
|
||||
activity.leaveAll();
|
||||
Future<?> flush = syncWorker.submit(() -> {
|
||||
try {
|
||||
// A retained retry may contain an earlier online snapshot.
|
||||
sync(20);
|
||||
sync(20);
|
||||
} catch (Exception e) { log.accept("SCOPENET final sync failed; unsent activity may be lost"); }
|
||||
});
|
||||
try { flush.get(12, TimeUnit.SECONDS); }
|
||||
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
||||
catch (ExecutionException | TimeoutException e) { log.accept("SCOPENET final sync did not finish"); }
|
||||
finally { syncWorker.shutdownNow(); }
|
||||
}
|
||||
|
||||
private static Thread daemon(Runnable task, String name) {
|
||||
Thread thread = new Thread(task, name);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user