Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build-data/paper.at
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public net.minecraft.server.Services USERID_CACHE_FILE
public net.minecraft.server.dedicated.DedicatedServerProperties$WorldDimensionData
public net.minecraft.server.dedicated.Settings getStringRaw(Ljava/lang/String;)Ljava/lang/String;
public net.minecraft.server.dedicated.Settings properties
public net.minecraft.server.jsonrpc.ManagementServer forEachConnection(Ljava/util/function/Consumer;)V
public net.minecraft.server.level.ChunkHolder oldTicketLevel
public net.minecraft.server.level.ChunkHolder playerProvider
public net.minecraft.server.level.ChunkLevel ENTITY_TICKING_LEVEL
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.papermc.paper.jsonrpc;

/**
* Information about a connected management client.
*/
public interface ClientInfo {

/**
* Gets the unique connection ID for this client.
*
* @return The connection ID
*/
int getConnectionId();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.papermc.paper.jsonrpc;

import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;

/**
* Service for sending custom notifications through the Management API (JSON-RPC over WebSocket).
* <p>
* Plugins can use this service to register custom notification types and broadcast data
* to connected management clients.
* </p>
*
* <p>Example usage:</p>
* <pre>{@code
* ManagementNotificationService service = Bukkit.getServicesManager().load(ManagementNotificationService.class);
*
* // Register a notification type
* NotificationHandle<MyData> handle = service.registerNotification(
* Key.key("myplugin", "custom_event"),
* "My custom event notification",
* MyData.class
* );
*
* // Send notification
* MyData data = new MyData("example", 123);
* handle.send(data);
* }</pre>
*/
@NullMarked
public interface ManagementNotificationService {

/**
* Registers a parameterless notification type.
* <p>
* The notification will be registered with the name: {@code notification/plugin/<namespace>/<name>}
* </p>
*
* @param key The key for the notification (namespace typically your plugin name)
* @param description A description of what this notification represents
* @return A handle to send this notification type
* @throws IllegalArgumentException if the notification is already registered
* @throws IllegalStateException if the management server is not available
*/
NotificationHandle<Void> registerNotification(
Key key,
String description
);

/**
* Registers a notification type with a data parameter.
* <p>
* The notification will be registered with the name: {@code notification/plugin/<namespace>/<name>}
* </p>
* <p>
* The data class must have appropriate JSON serialization support. Fields should be
* primitive types, Strings, or other JSON-serializable objects.
* </p>
*
* @param key The key for the notification (namespace typically your plugin name)
* @param description A description of what this notification represents
* @param dataClass The class type of the data to be sent with this notification
* @param <T> The type of data
* @return A handle to send this notification type
* @throws IllegalArgumentException if the notification is already registered or dataClass is not serializable
* @throws IllegalStateException if the management server is not available
*/
<T> NotificationHandle<T> registerNotification(
Key key,
String description,
Class<T> dataClass
);

/**
* Checks if a notification with the given key is registered.
*
* @param key The key of the notification
* @return true if the notification is registered
*/
boolean isNotificationRegistered(Key key);

/**
* Gets a handle to a previously registered notification.
*
* @param key The key of the notification
* @param <T> The type of data for this notification
* @return The notification handle, or null if not registered
*/
@Nullable
<T> NotificationHandle<T> getNotificationHandle(Key key);

/**
* Checks if the management server is currently running and accepting connections.
*
* @return true if the management server is available
*/
boolean isManagementServerAvailable();

/**
* Gets the number of currently connected management clients.
*
* @return The number of connected clients
*/
int getConnectedClientCount();

// ===== RPC Method Registration (Request-Response) =====

/**
* Registers a parameterless RPC method that clients can call.
* <p>
* The method will be registered with the name: {@code plugin/<namespace>/<name>}
* </p>
* <p>
* Clients can call this method and receive a response:
* <pre>{@code
* Request: {"jsonrpc":"2.0","method":"minecraft:plugin/myplugin/get_data","id":"123"}
* Response: {"jsonrpc":"2.0","result":{...},"id":"123"}
* }</pre>
*
* @param key The key for the method (namespace typically your plugin name)
* @param description A description of what this method does
* @param resultClass The class type of the result
* @param handler The handler function that processes the request
* @param <Result> The type of result
* @return A handle to the registered method
* @throws IllegalArgumentException if the method is already registered or resultClass is not serializable
* @throws IllegalStateException if the management server is not available
*/
<Result> MethodHandle<Void, Result> registerMethod(
Key key,
String description,
Class<Result> resultClass,
MethodHandler.Parameterless<Result> handler
);

/**
* Registers an RPC method with parameters that clients can call.
* <p>
* The method will be registered with the name: {@code plugin/<namespace>/<name>}
* </p>
* <p>
* Clients can call this method with parameters and receive a response:
* <pre>{@code
* Request: {"jsonrpc":"2.0","method":"minecraft:plugin/myplugin/process","params":[{...}],"id":"123"}
* Response: {"jsonrpc":"2.0","result":{...},"id":"123"}
* }</pre>
*
* @param key The key for the method (namespace typically your plugin name)
* @param description A description of what this method does
* @param paramsClass The class type of the parameters
* @param resultClass The class type of the result
* @param handler The handler function that processes the request
* @param <Params> The type of parameters
* @param <Result> The type of result
* @return A handle to the registered method
* @throws IllegalArgumentException if the method is already registered or classes are not serializable
* @throws IllegalStateException if the management server is not available
*/
<Params, Result> MethodHandle<Params, Result> registerMethod(
Key key,
String description,
Class<Params> paramsClass,
Class<Result> resultClass,
MethodHandler.WithParams<Params, Result> handler
);

/**
* Checks if a method with the given key is registered.
*
* @param key The key of the method
* @return true if the method is registered
*/
boolean isMethodRegistered(Key key);

/**
* Gets a handle to a previously registered method.
*
* @param key The key of the method
* @param <Params> The type of parameters
* @param <Result> The type of result
* @return The method handle, or null if not registered
*/
@Nullable
<Params, Result> MethodHandle<Params, Result> getMethodHandle(Key key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.papermc.paper.jsonrpc;

import net.kyori.adventure.key.Key;
import org.jspecify.annotations.NullMarked;

/**
* A handle to a registered RPC method that can be called by management clients.
* <p>
* Unlike notifications (one-way), methods are request-response:
* clients send a request with an ID and receive a response.
* </p>
*
* @param <Params> The type of parameters this method accepts (Void for parameterless)
* @param <Result> The type of result this method returns
*/
@NullMarked
public interface MethodHandle<Params, Result> {

/**
* Gets the namespace of this method.
*
* @return The namespace
*/
String getNamespace();

/**
* Gets the name of this method.
*
* @return The name
*/
String getName();

/**
* Gets the key of this method.
*
* @return The key
*/
Key key();

/**
* Gets the full method identifier in the format: {@code plugin/<namespace>/<name>}
*
* @return The full identifier
*/
String getFullName();

/**
* Gets the description of this method.
*
* @return The description
*/
String getDescription();

/**
* Checks if this method requires parameters.
*
* @return true if parameters are required
*/
boolean requiresParameters();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.papermc.paper.jsonrpc;

import org.bukkit.Server;
import org.jspecify.annotations.NullMarked;

/**
* Functional interfaces for RPC method handlers.
*/
@NullMarked
public final class MethodHandler {

private MethodHandler() {
// Utility class
}

/**
* Handler for parameterless RPC methods.
*
* @param <Result> The result type
*/
@FunctionalInterface
public interface Parameterless<Result> {
/**
* Handles the RPC method call.
*
* @param server The Bukkit server instance
* @param clientInfo Information about the calling client
* @return The result to send back to the client
*/
Result handle(Server server, ClientInfo clientInfo);
}

/**
* Handler for RPC methods with parameters.
*
* @param <Params> The parameter type
* @param <Result> The result type
*/
@FunctionalInterface
public interface WithParams<Params, Result> {
/**
* Handles the RPC method call.
*
* @param server The Bukkit server instance
* @param params The parameters from the client
* @param clientInfo Information about the calling client
* @return The result to send back to the client
*/
Result handle(Server server, Params params, ClientInfo clientInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.papermc.paper.jsonrpc;

import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NullMarked;

/**
* A handle to a registered notification type that can be used to send notifications
* to connected management clients.
*
* @param <T> The type of data this notification sends (Void for parameterless notifications)
*/
@NullMarked
public interface NotificationHandle<T> {

/**
* Gets the namespace of this notification.
*
* @return The namespace
*/
String getNamespace();

/**
* Gets the name of this notification.
*
* @return The name
*/
String getName();

/**
* Gets the key of this notification.
*
* @return The key
*/
Key key();

/**
* Gets the full notification identifier in the format: {@code notification/plugin/<namespace>/<name>}
*
* @return The full identifier
*/
String getFullName();

/**
* Gets the description of this notification.
*
* @return The description
*/
String getDescription();

/**
* Sends this notification to all connected management clients.
* <p>
* For parameterless notifications, pass null as the data parameter.
* </p>
*
* @param data The data to send, or null for parameterless notifications
* @throws IllegalArgumentException if data is required but null, or if data is the wrong type
* @throws IllegalStateException if the management server is not available
*/
void send(@Nullable T data);

/**
* Checks if this notification requires data.
*
* @return true if data is required when sending
*/
boolean requiresData();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package io.papermc.paper.jsonrpc;
Loading