summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llcorehttp/lljsonrpcws.cpp124
-rw-r--r--indra/llcorehttp/lljsonrpcws.h13
-rw-r--r--indra/newview/llcompilequeue.cpp19
-rw-r--r--indra/newview/llpublishedobjectmgr.cpp26
-rw-r--r--indra/newview/llscripteditorws.cpp227
-rw-r--r--indra/newview/llscripteditorws.h7
6 files changed, 382 insertions, 34 deletions
diff --git a/indra/llcorehttp/lljsonrpcws.cpp b/indra/llcorehttp/lljsonrpcws.cpp
index d6c5b0eb97..5b704e6322 100644
--- a/indra/llcorehttp/lljsonrpcws.cpp
+++ b/indra/llcorehttp/lljsonrpcws.cpp
@@ -207,16 +207,17 @@ void LLJSONRPCConnection::processRequest(const LLSD& request)
// Async handler — launched as a coroutine, response sent by the lambda.
if (is_notification)
{
- LL_WARNS("JSONRPC") << "Async method " << method
- << " called as notification; ignoring" << LL_ENDL;
- return;
+ LL_WARNS("JSONRPC") << "Method " << method
+ << " called as notification; rejecting request" << LL_ENDL;
+ throw InvalidRequest("Method " + method + " cannot be called as a notification");
}
ptr_t conn = std::static_pointer_cast<LLJSONRPCConnection>(getSelfPtr());
if (!conn)
{
- LL_WARNS("JSONRPC") << "Connection expired before async method " << method
- << " could be launched" << LL_ENDL;
- return;
+ LL_WARNS("JSONRPC") << "Connection expired before method " << method
+ << " could be launched; failing request" << LL_ENDL;
+ throw InternalError("Connection expired before method " + method
+ + " could be launched");
}
LLMainThreadTask::dispatch(
[handler, method, id, params, conn]()
@@ -515,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,
@@ -669,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,
@@ -718,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)
{
- result["timestamp"] = params["timestamp"];
+ return methods;
}
- // Add server's current time in milliseconds
- result["server_time"] = static_cast<LLSD::Integer>(
- LLDate::now().secondsSinceEpoch() * 1000.0);
- return result;
+
+ for (const std::string& method : connection->getMethods())
+ {
+ methods.append(method);
+ }
+
+ 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/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp
index ba19135fce..98b9255f66 100644
--- a/indra/newview/llcompilequeue.cpp
+++ b/indra/newview/llcompilequeue.cpp
@@ -356,7 +356,8 @@ bool LLFloaterCompileQueue::processScript(LLHandle<LLFloaterCompileQueue> hfloat
LLCheckedHandle<LLFloaterCompileQueue> floater(hfloater);
// Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle.
// which is caught in objectScriptProcessingQueueCoro
- std::string compile_target = floater->mCompileTarget;
+ const std::string requested_target = floater->mCompileTarget;
+ std::string compile_target = requested_target;
// Initial test to see if we can (or should) attempt to compile the script.
LLInventoryItem *item = dynamic_cast<LLInventoryItem *>(inventory);
@@ -367,6 +368,22 @@ bool LLFloaterCompileQueue::processScript(LLHandle<LLFloaterCompileQueue> hfloat
return true;
}
+ if (requested_target == "auto-luau")
+ {
+ compile_target =
+ item->getInventorySubType() == SST_LUA ? "luau" : "lsl-luau";
+ }
+ else if (requested_target == "auto")
+ {
+ compile_target = item->getRuntime();
+ if (compile_target.empty())
+ {
+ floater->addStringMessage(
+ "Skipping: " + item->getName() + " (no registered VM)");
+ return true;
+ }
+ }
+
if (!item->getPermissions().allowModifyBy(gAgent.getID(), gAgent.getGroupID()) ||
!item->getPermissions().allowCopyBy(gAgent.getID(), gAgent.getGroupID()))
{
diff --git a/indra/newview/llpublishedobjectmgr.cpp b/indra/newview/llpublishedobjectmgr.cpp
index d92bb382de..85a9542dce 100644
--- a/indra/newview/llpublishedobjectmgr.cpp
+++ b/indra/newview/llpublishedobjectmgr.cpp
@@ -88,6 +88,23 @@ namespace
// Never emit an empty prim/object name to downstream tooling.
return obj->getID().asString();
}
+
+ void add_object_permissions(LLSD& object_data, LLViewerObject* object)
+ {
+ LLPermissions* permissions =
+ LLSelectMgr::getInstance()->findObjectPermissions(object);
+ if (!permissions)
+ {
+ return;
+ }
+
+ LLSD permission_entry;
+ permission_entry["owner"] =
+ static_cast<S32>(permissions->getMaskOwner());
+ permission_entry["next_owner"] =
+ static_cast<S32>(permissions->getMaskNextOwner());
+ object_data["permissions"] = permission_entry;
+ }
}
class LLPublishedPrimListener : public LLVOInventoryListener
@@ -729,6 +746,7 @@ LLSD LLPublishedObjectMgr::buildPublishedObjectLLSD(LLViewerObject* root) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_SCRIPTDEV;
LLSD pub;
+ add_object_permissions(pub, root);
pub["object_id"] = root->getID();
pub["object_name"] = get_prim_name(root);
pub["object_description"] = nv_string(root, "Desc");
@@ -737,6 +755,11 @@ LLSD LLPublishedObjectMgr::buildPublishedObjectLLSD(LLViewerObject* root) const
{
pub["region"] = root->getRegion()->getName();
}
+ const PublishedObjectInfo* published_info = getPublished(root->getID());
+ if (published_info)
+ {
+ pub["can_save_back"] = published_info->mCanSaveBackToContents;
+ }
pub["inventory"] = buildPrimInventoryLLSD(root);
LLSD linked_objects = LLSD::emptyArray();
@@ -748,6 +771,7 @@ LLSD LLPublishedObjectMgr::buildPublishedObjectLLSD(LLViewerObject* root) const
link["link_number"] = link_number++;
link["link_name"] = get_prim_name(child);
link["link_description"] = nv_string(child, "Desc");
+ add_object_permissions(link, child);
link["inventory"] = buildPrimInventoryLLSD(child);
linked_objects.append(link);
}
@@ -773,6 +797,7 @@ LLSD LLPublishedObjectMgr::buildObjectListLLSD() const
}
LLSD pub;
+ add_object_permissions(pub, root);
pub["object_id"] = info.mObjectID;
pub["object_name"] = info.mObjectName;
pub["object_description"] = info.mObjectDescription;
@@ -803,6 +828,7 @@ LLSD LLPublishedObjectMgr::buildObjectListLLSD() const
link["link_number"] = prim_info.mLinkNumber;
link["link_name"] = prim_info.mPrimName;
link["link_description"] = prim_info.mPrimDescription;
+ add_object_permissions(link, child);
link["inventory"] = buildPrimInventoryLLSD(child);
linked_objects.append(link);
}
diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp
index f886be819f..72b5b66463 100644
--- a/indra/newview/llscripteditorws.cpp
+++ b/indra/newview/llscripteditorws.cpp
@@ -34,6 +34,7 @@
#include "llagentcamera.h"
#include "llappviewer.h"
#include "llchat.h"
+#include "llcompilequeue.h"
#include "lldate.h"
#include "llerror.h"
#include "lleventcoro.h"
@@ -169,6 +170,14 @@ namespace
return std::string(s);
}
+ LLSD object_id_command_params()
+ {
+ LLSD params(LLSD::emptyMap());
+ params["object_id"]["type"] = "string";
+ params["object_id"]["required"] = true;
+ return params;
+ }
+
}
//========================================================================
@@ -184,7 +193,8 @@ LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string& name, U16 port
LL_INFOS("ScriptEditorWS") << "Created JSON-RPC script editor server: " << name
<< " on port " << port << LL_ENDL;
- registerCommand({ "viewer.teleport", "Teleport agent to an in-world object" },
+ registerCommand({ "viewer.teleport", "Teleport agent to an in-world object",
+ object_id_command_params() },
[](U32, const LLSD& p) -> LLSD
{
LLUUID object_id = p["object_id"].asUUID();
@@ -203,7 +213,9 @@ LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string& name, U16 port
return response;
});
- registerCommand({ "viewer.camera.focus", "Zoom camera to an in-world object (same behavior as context menu Zoom In)" },
+ registerCommand({ "viewer.camera.focus",
+ "Zoom camera to an in-world object (same behavior as context menu Zoom In)",
+ object_id_command_params() },
[](U32, const LLSD& p) -> LLSD
{
LLUUID object_id = p["object_id"].asUUID();
@@ -221,11 +233,36 @@ LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string& name, U16 port
return response;
});
- registerCommand({ "viewer.object.save_back_to_contents", "Save an in-world object back to source object contents" },
+ registerCommand({ "viewer.object.save_back_to_contents",
+ "Save an in-world object back to source object contents",
+ object_id_command_params() },
[this](U32 connection_id, const LLSD& p) -> LLSD
{
return this->handleSaveBackToObjectContents(connection_id, p);
});
+
+ registerCommand({ "viewer.script.reset_all",
+ "Reset all scripts in an in-world object",
+ object_id_command_params() },
+ [this](U32 connection_id, const LLSD& p) -> LLSD
+ {
+ return this->handleObjectScriptResetAll(connection_id, p);
+ });
+
+ registerCommand({ "viewer.script.recompile_all",
+ "Recompile all scripts in an in-world object",
+ LLSDMap("object_id",
+ LLSDMap("type", "string")
+ ("required", true))
+ ("target",
+ LLSDMap("type", "string")
+ ("required", true)
+ ("description",
+ "Compilation target: luau, lsl2, mono, or auto")) },
+ [this](U32 connection_id, const LLSD& p) -> LLSD
+ {
+ return this->handleObjectScriptRecompileAll(connection_id, p);
+ });
}
LLScriptEditorWSServer::ptr_t LLScriptEditorWSServer::getServer()
@@ -801,6 +838,146 @@ LLSD LLScriptEditorWSServer::handleObjectScriptReset(U32 connection_id, const LL
return response;
}
+LLSD LLScriptEditorWSServer::handleObjectScriptResetAll(U32 connection_id, const LLSD& params)
+{
+ LLUUID prim_id = params["object_id"].asUUID();
+ if (prim_id.isNull())
+ {
+ throw LLJSONRPCConnection::InvalidParams("object_id is required");
+ }
+
+ LLViewerObject* prim = gObjectList.findObject(prim_id);
+ if (!prim)
+ {
+ throw LLJSONRPCConnection::InvalidParams("Object not found");
+ }
+
+ LLViewerObject* root = prim->getRootEdit();
+ if (!root || !isObjectPublished(root->getID()))
+ {
+ throw LLJSONRPCConnection::ForbiddenError("Object is not published");
+ }
+
+ if (!prim->flagScripted())
+ {
+ throw LLJSONRPCConnection::InvalidParams(
+ "Prim contains no scripts");
+ }
+
+ if (!prim->permModify())
+ {
+ throw LLJSONRPCConnection::ForbiddenError(
+ "No modify permission on prim");
+ }
+
+ LLUUID queue_id;
+ queue_id.generate();
+
+ LLFloaterScriptQueue* queue =
+ LLFloaterReg::getTypedInstance<LLFloaterScriptQueue>(
+ "reset_queue", LLSD(queue_id));
+ if (!queue)
+ {
+ throw LLJSONRPCConnection::InternalError(
+ "Unable to open reset queue");
+ }
+
+ queue->addObject(prim->getID(), prim->getID().asString());
+ if (!queue->start())
+ {
+ queue->closeFloater();
+ throw LLJSONRPCConnection::InternalError(
+ "Unable to start reset queue");
+ }
+
+ queue->setTitle(LLTrans::getString("ResetQueueTitle"));
+
+ LLSD response;
+ response["success"] = true;
+ response["object_id"] = prim->getID();
+ response["queued"] = true;
+ return response;
+}
+
+LLSD LLScriptEditorWSServer::handleObjectScriptRecompileAll(
+ U32 connection_id, const LLSD& params)
+{
+ LLUUID object_id = params["object_id"].asUUID();
+ if (object_id.isNull())
+ {
+ throw LLJSONRPCConnection::InvalidParams("object_id is required");
+ }
+
+ std::string target = params["target"].asString();
+ if (target != "luau" &&
+ target != "lsl2" &&
+ target != "mono" &&
+ target != "auto")
+ {
+ throw LLJSONRPCConnection::InvalidParams(
+ "target must be 'luau', 'lsl2', 'mono', or 'auto'");
+ }
+
+ LLViewerObject* object = gObjectList.findObject(object_id);
+ if (!object)
+ {
+ throw LLJSONRPCConnection::InvalidParams("Object not found");
+ }
+
+ LLViewerObject* root = object->getRootEdit();
+ if (!root || root->getID() != object_id || !isObjectPublished(root->getID()))
+ {
+ throw LLJSONRPCConnection::ForbiddenError("Object is not published");
+ }
+
+ if (!root->flagScripted())
+ {
+ throw LLJSONRPCConnection::InvalidParams(
+ "Object contains no scripts");
+ }
+
+ if (!root->permModify())
+ {
+ throw LLJSONRPCConnection::ForbiddenError(
+ "No modify permission on object");
+ }
+
+ if (target == "luau")
+ {
+ target = "auto-luau";
+ }
+
+ LLUUID queue_id;
+ queue_id.generate();
+
+ LLFloaterCompileQueue* queue =
+ LLFloaterReg::getTypedInstance<LLFloaterCompileQueue>(
+ "compile_queue", LLSD(queue_id));
+ if (!queue)
+ {
+ throw LLJSONRPCConnection::InternalError(
+ "Unable to open compile queue");
+ }
+
+ queue->setCompileTarget(target);
+ queue->addObject(root->getID(), root->getID().asString());
+ if (!queue->start())
+ {
+ queue->closeFloater();
+ throw LLJSONRPCConnection::InternalError(
+ "Unable to start compile queue");
+ }
+
+ queue->setTitle(LLTrans::getString("CompileQueueTitle"));
+
+ LLSD response;
+ response["success"] = true;
+ response["object_id"] = root->getID();
+ response["target"] = (target == "auto-luau") ? "luau" : target;
+ response["queued"] = true;
+ return response;
+}
+
LLSD LLScriptEditorWSServer::handleObjectModify(U32 connection_id, const LLSD& params)
{
// Step 1: Parameter Validation
@@ -1019,6 +1196,10 @@ LLSD LLScriptEditorWSServer::handleCommandList()
LLSD info;
info["command"] = entry.first.command;
info["description"] = entry.first.description;
+ if (!entry.first.params.isUndefined())
+ {
+ info["params"] = entry.first.params;
+ }
commands.append(info);
}
LLSD response;
@@ -1070,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;
@@ -1456,6 +1664,7 @@ LLSD LLScriptEditorWSServer::handleObjectContentGet(const std::string& method, c
response["prim_id"] = prim_id;
response["item_id"] = item_id;
response["content"] = text_content;
+ response["encoding"] = "utf-8";
return response;
}
@@ -1523,7 +1732,13 @@ LLSD LLScriptEditorWSServer::saveScript(LLViewerObject* prim, LLInventoryItem* i
[&, prim_id, item_id](const std::string& pump_name)
{
auto [on_success, on_failure] = make_asset_upload_callbacks(pump_name);
- bool is_running = params.has("running") ? params["running"].asBoolean() : false;
+ const LLViewerInventoryItem* viewer_item =
+ dynamic_cast<const LLViewerInventoryItem*>(item);
+ bool is_running = viewer_item ? viewer_item->getIsRunning() : false;
+ if (params.has("running"))
+ {
+ is_running = params["running"].asBoolean();
+ }
LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLScriptAssetUpload>(
prim_id, item_id,
compile_target, is_running, LLUUID::null, content,
@@ -2078,6 +2293,10 @@ void LLScriptEditorWSServer::sendRuntimeEvent(
}
LLSD message;
+ if (!script_id.empty())
+ {
+ message["script_id"] = script_id;
+ }
message["object_id"] = event.mRootID;
message["prim_id"] = event.mPrimID;
message["item_id"] = event.mItemID;
diff --git a/indra/newview/llscripteditorws.h b/indra/newview/llscripteditorws.h
index 1dc218853b..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();
@@ -257,6 +261,8 @@ protected:
LLSD handleObjectList() const;
LLSD handleObjectScriptSetRunning(U32 connection_id, const LLSD& params);
LLSD handleObjectScriptReset(U32 connection_id, const LLSD& params);
+ LLSD handleObjectScriptResetAll(U32 connection_id, const LLSD& params);
+ LLSD handleObjectScriptRecompileAll(U32 connection_id, const LLSD& params);
LLSD handleObjectModify(U32 connection_id, const LLSD& params);
LLSD handleObjectItemModify(U32 connection_id, const LLSD& params);
LLSD handleCommandExecute(U32 connection_id, const LLSD& params);
@@ -333,6 +339,7 @@ private:
{
std::string command;
std::string description;
+ LLSD params;
};
enum WSCommandError
{