summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRider Linden <rider@lindenlab.com>2026-08-27 12:24:19 -0700
committerRider Linden <rider@lindenlab.com>2026-08-27 12:24:19 -0700
commite6e85510e59ea68e75b565de49522ce3327c056d (patch)
tree59309cb4c8455b1cd986bde35c786631427475f8
parente86a0501bc655548aaadeb1d6475fa1652787098 (diff)
Issue #6188: standardize and implement missing `system.*` methods.
-rw-r--r--doc/external-editor-json-rpc.md146
-rw-r--r--indra/llcorehttp/lljsonrpcws.cpp111
-rw-r--r--indra/llcorehttp/lljsonrpcws.h13
-rw-r--r--indra/newview/llscripteditorws.cpp27
-rw-r--r--indra/newview/llscripteditorws.h4
5 files changed, 265 insertions, 36 deletions
diff --git a/doc/external-editor-json-rpc.md b/doc/external-editor-json-rpc.md
index a09bde2b22..570388ac7c 100644
--- a/doc/external-editor-json-rpc.md
+++ b/doc/external-editor-json-rpc.md
@@ -17,7 +17,10 @@ This document describes all the message interfaces defined for WebSocket communi
- [SessionHandshakeResponse](#sessionhandshakeresponse)
- [Session OK](#session-ok)
- [SessionDisconnect](#sessiondisconnect)
- - [SessionPing](#sessionping)
+ - [SystemPing](#systemping)
+ - [SystemVersion](#systemversion)
+ - [SystemStatus](#systemstatus)
+ - [SystemListMethods](#systemlistmethods)
- [Language and Syntax Interfaces](#language-and-syntax-interfaces)
- [SyntaxChange](#syntaxchange)
- [Language Syntax ID Request](#language-syntax-id-request)
@@ -165,8 +168,14 @@ WebSocket connects → session.handshake → session.ok
| `session.handshake` (response) | Extension → Viewer | Response | `SessionHandshakeResponse` |
| `session.ok` | Viewer → Extension | Notification | _(no interface)_ |
| `session.disconnect` | Bidirectional | Notification | `SessionDisconnect` |
-| `session.ping` | Bidirectional | Call | `SessionPing` |
-| `session.ping` (response) | Bidirectional | Response | `SessionPingResponse` |
+| `system.ping` | Bidirectional | Call | `SystemPing` |
+| `system.ping` (response) | Bidirectional | Response | `SystemPingResponse` |
+| `system.getVersion` | Bidirectional | Call | _(no parameters)_ |
+| `system.getVersion` (response) | Bidirectional | Response | `SystemVersionResponse` |
+| `system.status` | Bidirectional | Call | _(no parameters)_ |
+| `system.status` (response) | Bidirectional | Response | `SystemStatusResponse` |
+| `system.listMethods` | Bidirectional | Call | _(no parameters)_ |
+| `system.listMethods` (response) | Bidirectional | Response | `SystemListMethodsResponse`|
| `script.subscribe` | Extension → Viewer | Call | `ScriptSubscribe` |
| `script.subscribe` (response) | Viewer → Extension | Response | `ScriptSubscribeResponse` |
| `script.unsubscribe` | Viewer → Extension | Notification | `ScriptUnsubscribe` |
@@ -373,18 +382,19 @@ interface SessionDisconnect {
- `4`: Internal server error
- `message`: Human-readable description of the disconnect reason
-### SessionPing
+### SystemPing
-**JSON-RPC Method:** `session.ping` (call, bidirectional)
+**JSON-RPC Method:** `system.ping` (call, bidirectional)
-Heartbeat call used to verify the connection is alive and measure latency. Either side can initiate a ping; the recipient responds with the original timestamp plus its own server time.
+Ping call used to verify that the connection is alive and measure latency. Either side can
+initiate a ping.
-In practice the extension initiates and the viewer only answers — the viewer never sends
-`session.ping` itself. The extension pings every 30 seconds and tears the connection down after
-two consecutive failures.
+The generic JSON-RPC server responds with the simple result `"pong"`. The editor server extends
+that response with the original timestamp and its current server time. The extension uses the
+extended response for its periodic connection-health check.
```typescript
-interface SessionPing {
+interface SystemPing {
timestamp: number;
}
```
@@ -396,7 +406,8 @@ interface SessionPing {
**Response:**
```typescript
-interface SessionPingResponse {
+interface SystemPingResponse {
+ pong: string;
timestamp: number;
server_time: number;
}
@@ -404,15 +415,19 @@ interface SessionPingResponse {
**Response Fields:**
-- `timestamp`: The original timestamp from the request. Echoed back only when the request supplied one.
+- `pong`: Acknowledgement that the ping was received.
+- `timestamp`: The original timestamp from the request, echoed by the editor server.
- `server_time`: Unix timestamp in milliseconds when the response was generated
+The extension sends a `system.ping` request every 30 seconds and tears the connection down
+after two consecutive failures.
+
**Example Request:**
```json
{
"jsonrpc": "2.0",
- "method": "session.ping",
+ "method": "system.ping",
"id": 42,
"params": {
"timestamp": 1721145600000
@@ -420,6 +435,50 @@ interface SessionPingResponse {
}
```
+### SystemVersion
+
+**JSON-RPC Method:** `system.getVersion` (call, bidirectional)
+
+Requests the identity and version of the peer. The response uses the same field names in both
+directions:
+
+```typescript
+interface SystemVersionResponse {
+ client_name: string;
+ client_version: string;
+}
+```
+
+The viewer returns its viewer channel as `client_name` and its full viewer version as
+`client_version`. The extension returns its package name as `client_name` and its package version
+as `client_version`.
+
+**Example viewer response:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 43,
+ "result": {
+ "client_name": "Second Life",
+ "client_version": "7.1.0.123456"
+ }
+}
+```
+
+**Example extension response:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 43,
+ "result": {
+ "client_name": "sl-vscode-plugin",
+ "client_version": "1.0.6"
+ }
+}
+```
+
**Example Response:**
```json
@@ -427,12 +486,73 @@ interface SessionPingResponse {
"jsonrpc": "2.0",
"id": 42,
"result": {
+ "pong": "pong",
"timestamp": 1721145600000,
"server_time": 1721145600015
}
}
```
+### SystemStatus
+
+**JSON-RPC Method:** `system.status` (call, bidirectional)
+
+Requests the current status of the peer. The default response is:
+
+```typescript
+interface SystemStatusResponse {
+ status: "OK";
+}
+```
+
+Both the viewer and the extension currently return `status: "OK"`. The response may be extended
+with additional status information in the future.
+
+**Example response:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 44,
+ "result": {
+ "status": "OK"
+ }
+}
+```
+
+### SystemListMethods
+
+**JSON-RPC Method:** `system.listMethods` (call, bidirectional)
+
+Requests the names of the methods available on the receiving peer. The response contains an
+alphabetically ordered list of unique method names. Sync versus async dispatch is an internal
+implementation detail and is not exposed by this interface.
+
+```typescript
+type SystemListMethodsResponse = string[];
+```
+
+The viewer returns all methods registered on the connection, including methods registered by the
+base JSON-RPC server and the editor server. The extension returns its built-in system methods
+together with dynamically registered handlers.
+
+**Example response:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 45,
+ "result": [
+ "command.execute",
+ "command.list",
+ "system.getVersion",
+ "system.listMethods",
+ "system.ping",
+ "system.status"
+ ]
+}
+```
+
## Language and Syntax Interfaces
### SyntaxChange
diff --git a/indra/llcorehttp/lljsonrpcws.cpp b/indra/llcorehttp/lljsonrpcws.cpp
index df76f6a2bf..5b704e6322 100644
--- a/indra/llcorehttp/lljsonrpcws.cpp
+++ b/indra/llcorehttp/lljsonrpcws.cpp
@@ -516,6 +516,24 @@ void LLJSONRPCConnection::unregisterMethod(const std::string& method)
LL_DEBUGS("JSONRPC") << "Unregistered method: " << method << LL_ENDL;
}
+std::set<std::string> LLJSONRPCConnection::getMethods() const
+{
+ LLMutexLock lock(&mMutex);
+ std::set<std::string> methods;
+
+ for (const auto& [method, handler] : mMethodHandlers)
+ {
+ methods.insert(method);
+ }
+
+ for (const auto& [method, handler] : mAsyncMethodHandlers)
+ {
+ methods.insert(method);
+ }
+
+ return methods;
+}
+
LLSD LLJSONRPCConnection::makeEnvelope(const LLSD& id,
const std::string& method,
const LLSD& params,
@@ -670,23 +688,11 @@ LLJSONRPCServer::LLJSONRPCServer(const std::string& name, U16 port, bool local_o
<< " on port " << port << LL_ENDL;
// Register standard JSON-RPC methods
- registerGlobalMethod("system.listMethods", [this](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD {
- LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL;
- return getMethodList();
- });
-
registerGlobalMethod("system.getStats", [this](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD {
LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL;
return getServerStats();
});
- registerGlobalMethod("system.ping", [](const std::string& method, const LLSD& id, const LLSD& params) -> LLSD {
- LL_DEBUGS("JSONRPC") << "System method " << method << " called" << LL_ENDL;
- LLSD result;
- result["pong"] = LLDate::now().asString();
- result["params"] = params;
- return result;
- });
}
LLWebsocketMgr::WSConnection::ptr_t LLJSONRPCServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
@@ -719,21 +725,80 @@ void LLJSONRPCServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connecti
connection->registerMethod(method, handler);
}
- // Register session.ping handler for connection health monitoring
- connection->registerMethod("session.ping",
- [](const std::string&, const LLSD&, const LLSD& params) -> LLSD
+ std::weak_ptr<LLJSONRPCConnection> weak_connection = connection;
+ connection->registerMethod(
+ "system.listMethods",
+ [weak_connection](
+ const std::string&,
+ const LLSD&,
+ const LLSD&) -> LLSD
{
- LLSD result;
- // Echo back the original timestamp
- if (params.has("timestamp"))
+ LLSD methods(LLSD::emptyArray());
+ auto connection = weak_connection.lock();
+ if (!connection)
+ {
+ return methods;
+ }
+
+ for (const std::string& method : connection->getMethods())
{
- result["timestamp"] = params["timestamp"];
+ methods.append(method);
}
- // Add server's current time in milliseconds
- result["server_time"] = static_cast<LLSD::Integer>(
- LLDate::now().secondsSinceEpoch() * 1000.0);
- return result;
+
+ return methods;
+ });
+
+ connection->registerMethod(
+ "system.ping",
+ [this, weak_connection](
+ const std::string& method,
+ const LLSD& id,
+ const LLSD& params) -> LLSD
+ {
+ LL_DEBUGS("JSONRPC") << "System method " << method
+ << " called" << LL_ENDL;
+ return handlePing(weak_connection.lock(), params);
+ });
+
+ connection->registerMethod(
+ "system.getVersion",
+ [this, weak_connection](
+ const std::string& method,
+ const LLSD& id,
+ const LLSD& params) -> LLSD
+ {
+ LL_DEBUGS("JSONRPC") << "System method " << method
+ << " called" << LL_ENDL;
+ return handleGetVersion(weak_connection.lock(), params);
});
+
+ connection->registerMethod(
+ "system.status",
+ [this, weak_connection](
+ const std::string& method,
+ const LLSD& id,
+ const LLSD& params) -> LLSD
+ {
+ LL_DEBUGS("JSONRPC") << "System method " << method
+ << " called" << LL_ENDL;
+ return handleStatus(weak_connection.lock(), params);
+ });
+}
+
+LLSD LLJSONRPCServer::handlePing(
+ const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const
+{
+ return LLSD("pong");
+}
+
+LLSD LLJSONRPCServer::handleStatus(
+ const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const
+{
+ LLSD result;
+ result["status"] = "OK";
+ return result;
}
void LLJSONRPCServer::registerGlobalMethod(const std::string& method, MethodHandler handler)
diff --git a/indra/llcorehttp/lljsonrpcws.h b/indra/llcorehttp/lljsonrpcws.h
index 7cb26b1fb2..aa4a96be4d 100644
--- a/indra/llcorehttp/lljsonrpcws.h
+++ b/indra/llcorehttp/lljsonrpcws.h
@@ -275,6 +275,12 @@ public:
void unregisterMethod(const std::string& method);
/**
+ * @brief Get the names of all registered methods
+ * @return Ordered set containing sync and async method names
+ */
+ virtual std::set<std::string> getMethods() const;
+
+ /**
* @brief Make an asynchronous JSON-RPC call
* @param method The method name to call
* @param params The parameters to pass
@@ -495,6 +501,13 @@ protected:
LLWebsocketMgr::WSConnection::ptr_t connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
LLWebsocketMgr::connection_h handle) override;
+ virtual LLSD handlePing(const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const;
+ virtual LLSD handleGetVersion(const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const = 0;
+ virtual LLSD handleStatus(const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const;
+
/**
* @brief Apply global method handlers to a new connection
* @param connection The connection to configure
diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp
index 729f335db4..41efa7e016 100644
--- a/indra/newview/llscripteditorws.cpp
+++ b/indra/newview/llscripteditorws.cpp
@@ -1251,6 +1251,33 @@ void LLScriptEditorWSServer::broadcastLanguageChange()
}
}
+LLSD LLScriptEditorWSServer::handlePing(
+ const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const
+{
+ LLSD result;
+ result["pong"] = "pong";
+
+ if (params.has("timestamp"))
+ {
+ result["timestamp"] = params["timestamp"];
+ }
+
+ result["server_time"] = static_cast<LLSD::Integer>(
+ LLDate::now().secondsSinceEpoch() * 1000.0);
+ return result;
+}
+
+LLSD LLScriptEditorWSServer::handleGetVersion(
+ const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const
+{
+ LLSD result;
+ result["client_name"] = LLVersionInfo::instance().getChannel();
+ result["client_version"] = LLVersionInfo::instance().getVersion();
+ return result;
+}
+
LLSD LLScriptEditorWSServer::handleLanguageIdRequest() const
{
LLSD response;
diff --git a/indra/newview/llscripteditorws.h b/indra/newview/llscripteditorws.h
index e569d4ff7d..6eb8385210 100644
--- a/indra/newview/llscripteditorws.h
+++ b/indra/newview/llscripteditorws.h
@@ -236,6 +236,10 @@ protected:
LLWebsocketMgr::connection_h handle) override;
void setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) override;
+ LLSD handlePing(const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const override;
+ LLSD handleGetVersion(const LLJSONRPCConnection::ptr_t& connection,
+ const LLSD& params) const override;
void broadcastLanguageChange();