summaryrefslogtreecommitdiff
path: root/indra/newview/llscripteditorws.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llscripteditorws.cpp')
-rw-r--r--indra/newview/llscripteditorws.cpp692
1 files changed, 238 insertions, 454 deletions
diff --git a/indra/newview/llscripteditorws.cpp b/indra/newview/llscripteditorws.cpp
index 503624d15a..9863130aea 100644
--- a/indra/newview/llscripteditorws.cpp
+++ b/indra/newview/llscripteditorws.cpp
@@ -1,9 +1,10 @@
/**
* @file llscripteditorws.cpp
+ * @brief JSON-RPC 2.0 WebSocket server implementation for external script editor integration
*
- * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2025, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -23,164 +24,24 @@
* $/LicenseInfo$
*/
-/*
- * ============================================================================
- * WEBSOCKET MESSAGE FORMAT SPECIFICATION
- * ============================================================================
- *
- * This WebSocket implementation uses a structured JSON message format for
- * communication between the Second Life viewer and external script editors.
- * All messages are encoded as UTF-8 JSON strings.
- *
- * ## BASE MESSAGE STRUCTURE
- *
- * Every WebSocket message follows this mandatory structure:
- *
- * ```json
- * {
- * "command": "message_type_identifier", // REQUIRED: Command/message type
- * "data": { // OPTIONAL: Command-specific payload
- * // ... command-specific fields ...
- * },
- * "timestamp": "2025-01-27T15:30:00Z", // OPTIONAL: ISO8601 timestamp
- * "session_id": "uuid-string", // OPTIONAL: Session identifier
- * "message_id": "unique-id" // OPTIONAL: For request/response correlation
- * }
- * ```
- *
- * ## MESSAGE TYPES
- *
- * ### 1. SERVER-TO-CLIENT MESSAGES
- *
- * #### capabilities
- * Sent immediately when client connects to announce server capabilities.
- * ```json
- * {
- * "command": "capabilities",
- * "data": {
- * "server_name": "Second Life Script Editor WebSocket Server",
- * "server_version": "1.0.0",
- * "protocol_version": "1.0",
- * "viewer_name": "Second Life",
- * "viewer_version": "Second Life Release 6.4.26",
- * "session_id": "generated-uuid",
- * "timestamp": "2025-01-27T15:30:00Z",
- * "capabilities": ["script_editing", "compilation", "metadata", ...],
- * "supported_languages": ["lsl", "luau"],
- * "features": {
- * "live_compilation": true,
- * "debugging": false,
- * "breakpoints": false
- * }
- * }
- * }
- * ```
- *
- * #### error
- * Sent when message processing fails or protocol violations occur.
- * ```json
- * {
- * "command": "error",
- * "data": {
- * "error_code": "PARSE_ERROR|INVALID_MESSAGE|UNKNOWN_MESSAGE_TYPE",
- * "error_message": "Human-readable error description"
- * }
- * }
- * ```
- *
- * ### 2. CLIENT-TO-SERVER MESSAGES
- *
- * #### connect
- * Client handshake message to establish connection and negotiate capabilities.
- * ```json
- * {
- * "command": "connect",
- * "data": {
- * "client_name": "VS Code LSL Extension",
- * "client_version": "1.2.3",
- * "protocol_version": "1.0",
- * "capabilities": ["script_editing", "syntax_highlighting"],
- * "supported_languages": ["lsl", "luau"],
- * "features": {
- * "auto_completion": true,
- * "live_preview": false
- * },
- * "session_id": "optional-client-provided-uuid"
- * }
- * }
- * ```
- *
- * ### 3. BIDIRECTIONAL MESSAGES
- *
- * #### connect_ack
- * Server response to client connect message.
- * ```json
- * {
- * "command": "connect_ack",
- * "data": {
- * "status": "connected|rejected",
- * "message": "Connection established successfully",
- * "session_id": "established-session-uuid",
- * "timestamp": "2025-01-27T15:30:00Z",
- * "mutual_capabilities": ["script_editing", "compilation"],
- * "supported_languages": ["lsl", "luau"],
- * "max_script_size": 65536,
- * "heartbeat_interval": 30,
- * // For rejected connections:
- * "error": "incompatible_protocol|missing_capabilities|connection_failed"
- * }
- * }
- * ```
- *
- * ## FUTURE MESSAGE TYPES (Planned)
- *
- * The following message types are referenced in the protocol design but not
- * yet implemented:
- *
- * - `script_updated`: Editor notifies viewer of script content changes
- * - `save_request`: Editor requests script save to SL servers
- * - `compile_request`: Editor requests script compilation
- * - `script_content`: Viewer sends full script content to editor
- * - `compile_result`: Viewer sends compilation results to editor
- * - `save_result`: Viewer sends save operation results to editor
- * - `metadata`: Script and object metadata exchange
- * - `ping`/`pong`: Connection health check messages
- * - `editor_ready`: Editor initialization complete notification
- *
- * ## PROTOCOL RULES
- *
- * 1. **Encoding**: All messages must be valid UTF-8 JSON
- * 2. **Required Fields**: Every message MUST have a "command" field
- * 3. **Case Sensitivity**: All field names are case-sensitive
- * 4. **Protocol Version**: Currently only "1.0" is supported
- * 5. **Error Handling**: Invalid messages trigger "error" responses
- * 6. **Connection Flow**: Client must send "connect" before other commands
- * 7. **Session Management**: session_id tracks individual editor sessions
- * 8. **Capability Negotiation**: Features limited to mutual capabilities
- *
- * ## ERROR CODES
+/**
+ * This implementation provides JSON-RPC 2.0 WebSocket communication between
+ * the Second Life viewer and external script editors. It uses the standard
+ * JSON-RPC 2.0 protocol without pre-defined script-specific methods,
+ * allowing for flexible integration approaches.
*
- * - `PARSE_ERROR`: Invalid JSON syntax
- * - `PARSE_EXCEPTION`: JSON parsing threw exception
- * - `INVALID_MESSAGE`: Missing required "command" field
- * - `UNKNOWN_MESSAGE_TYPE`: Unrecognized command type
- * - `INCOMPATIBLE_PROTOCOL`: Unsupported protocol version
- * - `MISSING_CAPABILITIES`: Required capabilities not supported
- * - `CONNECTION_FAILED`: General connection establishment failure
+ * ## JSON-RPC Integration
*
- * ## IMPLEMENTATION NOTES
+ * The connection provides a clean JSON-RPC 2.0 interface that can be
+ * extended with script-specific functionality as needed:
*
- * - Messages are parsed using boost::json and converted to LLSD internally
- * - All timestamps use ISO 8601 format in UTC timezone
- * - UUIDs are generated using LLUUID::generateNewID() for session tracking
- * - Maximum script size is currently limited to 65536 bytes
- * - WebSocket server binds to localhost only for security by default
- * - Protocol designed for extensibility with additional message types
+ * ### Server-to-Client (Viewer to Editor):
+ * - `session.handshake`: Welcome message on connection
+ * - `session.disconnect`: Notify editor of disconnection
*
- * ============================================================================
+ * ### Notifications (no response expected):
*/
-
#include "llviewerprecompiledheaders.h"
#include "llscripteditorws.h"
#include "llpreviewscript.h"
@@ -189,82 +50,42 @@
#include "lldate.h"
#include "llerror.h"
#include "lluuid.h"
-#include "llsdjson.h"
-#include <boost/json.hpp>
-
-//------------------------------------------------------------------------
+#include "llversioninfo.h"
-LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string_view name, U16 port, bool local_only):
- LLWebsocketMgr::WSServer(name, port, local_only)
+//========================================================================
+LLScriptEditorWSServer::LLScriptEditorWSServer(const std::string& name, U16 port, bool local_only)
+ : LLJSONRPCServer(name, port, local_only)
{
+ LL_INFOS("ScriptEditorWS") << "Created JSON-RPC script editor server: " << name
+ << " on port " << port << LL_ENDL;
}
-LLWebsocketMgr::WSConnection::ptr_t LLScriptEditorWSServer::connectionFactory(WSServer::ptr_t server, LLWebsocketMgr::connection_h handle)
+LLWebsocketMgr::WSConnection::ptr_t LLScriptEditorWSServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
+ LLWebsocketMgr::connection_h handle)
{
- return std::make_shared<LLScriptEditorWSConnection>(server, handle);
+ auto connection = std::make_shared<LLScriptEditorWSConnection>(server, handle);
+ mActiveConnections.insert(connection);
+
+ // Call setupConnectionMethods to register any global methods
+ setupConnectionMethods(connection);
+
+ return connection;
}
-//------------------------------------------------------------------------
void LLScriptEditorWSServer::onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection)
{
- LL_INFOS("ScriptEditorWS") << "New script editor client connected" << LL_ENDL;
-
- // Build capabilities message to send to the newly connected external editor
- LLSD capabilities_message;
- capabilities_message["command"] = "capabilities";
-
- LLSD& data = capabilities_message["data"];
-
- // Server identification and version information
- data["server_name"] = "Second Life Script Editor WebSocket Server";
- data["server_version"] = "1.0.0";
- data["protocol_version"] = "1.0";
-
- // Viewer information
- data["viewer_name"] = LLTrans::getString("APP_NAME");
- data["viewer_version"] = LLAppViewer::instance()->getSecondLifeTitle();
+ // Call parent class to handle JSON-RPC setup and standard methods
+ LLJSONRPCServer::onConnectionOpened(connection);
- // Session information
- data["session_id"] = LLUUID::generateNewID().asString();
- data["timestamp"] = LLDate::now().asString();
+ LL_INFOS("ScriptEditorWS") << "New script editor client connected via JSON-RPC" << LL_ENDL;
- // Server capabilities - what the viewer/server supports
- LLSD capabilities = LLSD::emptyArray();
- capabilities.append("script_editing"); // Basic script content editing
- capabilities.append("script_synchronization"); // Real-time sync between viewer and editor
- capabilities.append("compilation"); // Compile results with errors/warnings
- capabilities.append("metadata"); // Script and object metadata
- capabilities.append("syntax_highlighting"); // LSL/Luau syntax information
- capabilities.append("error_reporting"); // Detailed error reporting
-
- data["capabilities"] = capabilities;
-
- // Language support information
- LLSD languages = LLSD::emptyArray();
- languages.append("lsl"); // Linden Scripting Language
- languages.append("luau"); // Luau scripting language
- data["supported_languages"] = languages;
-
- // Feature flags
- LLSD features;
- features["live_compilation"] = true;
- features["debugging"] = false; // Not implemented yet
- features["breakpoints"] = false; // Not implemented yet
- data["features"] = features;
-
- // Send the capabilities message to the newly connected client
- if (connection->sendMessage(capabilities_message))
- {
- LL_INFOS("ScriptEditorWS") << "Sent capabilities message to new client" << LL_ENDL;
- }
- else
- {
- LL_WARNS("ScriptEditorWS") << "Failed to send capabilities message to new client" << LL_ENDL;
- }
}
void LLScriptEditorWSServer::onConnectionClosed(const LLWebsocketMgr::WSConnection::ptr_t& connection)
{
+ // Call parent class to handle JSON-RPC cleanup
+ LLJSONRPCServer::onConnectionClosed(connection);
+
LL_INFOS("ScriptEditorWS") << "Script editor client disconnected" << LL_ENDL;
// Remove from active connections
@@ -273,13 +94,12 @@ void LLScriptEditorWSServer::onConnectionClosed(const LLWebsocketMgr::WSConnecti
{
mActiveConnections.erase(script_connection);
- // Remove from any script associations
LL_INFOS("ScriptEditorWS") << "Removed connection from active connections. Total: "
<< mActiveConnections.size() << LL_ENDL;
+ // TODO: When connections reach 0, stop the server aftera a timeout.
}
}
-//------------------------------------------------------------------------
bool LLScriptEditorWSServer::associateEditor(const LLHandle<LLPanel>& editor_handle, const std::string& script_id)
{
if (!editor_handle.isDead())
@@ -305,304 +125,268 @@ LLHandle<LLPanel> LLScriptEditorWSServer::findEditorForScript(const std::string&
return LLHandle<LLPanel>();
}
-//========================================================================
-void LLScriptEditorWSConnection::onOpen()
-{
-
-}
-
-void LLScriptEditorWSConnection::onClose()
+std::shared_ptr<LLScriptEditorWSConnection> LLScriptEditorWSServer::findConnectionForScript(const std::string& script_id)
{
+ // TODO: Implement logic to find connection handling a specific script
+ // This would require tracking which connection is responsible for which script
+ return nullptr;
}
-void LLScriptEditorWSConnection::onMessage(const std::string& message)
+std::set<std::string> LLScriptEditorWSServer::getActiveScripts() const
{
- LL_DEBUGS("ScriptEditorWS") << "Received message: " << message << LL_ENDL;
-
- // Convert JSON string to LLSD
- LLSD parsed_message;
- try
+ std::set<std::string> active_scripts;
+ for (const auto& [script_id, editor_handle] : mScriptEditors)
{
- boost::system::error_code ec;
- boost::json::value json_value = boost::json::parse(message, ec);
-
- if (ec.failed())
+ if (!editor_handle.isDead())
{
- LL_WARNS("ScriptEditorWS") << "Failed to parse JSON message: " << ec.message() << LL_ENDL;
-
- // Send error response back to client
- LLSD error_response;
- error_response["command"] = "error";
- error_response["data"]["error_code"] = "PARSE_ERROR";
- error_response["data"]["error_message"] = "Invalid JSON format: " + std::string(ec.message());
- sendMessage(error_response);
- return;
+ active_scripts.insert(script_id);
}
+ }
+ return active_scripts;
+}
- // Convert boost::json::value to LLSD
- parsed_message = LlsdFromJson(json_value);
+void LLScriptEditorWSServer::broadcastScriptUpdate(const std::string& script_id, const std::string& content, const LLSD& metadata)
+{
+ LL_DEBUGS("ScriptEditorWS") << "Broadcasting script update for script: " << script_id << LL_ENDL;
- LL_DEBUGS("ScriptEditorWS") << "Parsed LLSD message type: " << parsed_message.type()
- << ", has 'type' field: " << parsed_message.has("command") << LL_ENDL;
- }
- catch (const std::exception& e)
- {
- LL_WARNS("ScriptEditorWS") << "Exception parsing JSON message: " << e.what() << LL_ENDL;
+ LLSD params;
+ params["script_id"] = script_id;
+ params["content"] = content;
+ params["timestamp"] = LLDate::now().asString();
- // Send error response back to client
- LLSD error_response;
- error_response["command"] = "error";
- error_response["data"]["error_code"] = "PARSE_EXCEPTION";
- error_response["data"]["error_message"] = "JSON parsing exception: " + std::string(e.what());
- sendMessage(error_response);
- return;
+ if (!metadata.isUndefined())
+ {
+ params["metadata"] = metadata;
}
- // Validate that we have a proper message structure
- if (!parsed_message.has("command"))
- {
- LL_WARNS("ScriptEditorWS") << "Received message without 'type' field" << LL_ENDL;
+ // Send to all connected editors as a notification
+ broadcastNotification("script.update", params);
+}
- LLSD error_response;
- error_response["command"] = "error";
- error_response["data"]["error_code"] = "INVALID_MESSAGE";
- error_response["data"]["error_message"] = "Message must have a 'type' field";
- sendMessage(error_response);
- return;
- }
+void LLScriptEditorWSServer::broadcastCompilationResult(const std::string& script_id, bool success, const LLSD& errors)
+{
+ LL_DEBUGS("ScriptEditorWS") << "Broadcasting compilation result for script: " << script_id
+ << " (success: " << success << ")" << LL_ENDL;
- std::string message_type = parsed_message["command"].asString();
- LL_INFOS("ScriptEditorWS") << "Processing message of type: " << message_type << LL_ENDL;
+ LLSD params;
+ params["script_id"] = script_id;
+ params["success"] = success;
+ params["timestamp"] = LLDate::now().asString();
- // Route message to appropriate handler based on type
- if (message_type == "connect")
+ if (!errors.isUndefined() && errors.isArray())
{
- processConnectMessage(parsed_message);
+ params["errors"] = errors;
}
- else
- {
- LL_WARNS("ScriptEditorWS") << "Received unknown message type: " << message_type << LL_ENDL;
- LLSD error_response;
- error_response["command"] = "error";
- error_response["data"]["error_code"] = "UNKNOWN_MESSAGE_TYPE";
- error_response["data"]["error_message"] = "Unknown message type: " + message_type;
- sendMessage(error_response);
- }
+ // Send to all connected editors as a notification
+ broadcastNotification("compilation.result", params);
}
-void LLScriptEditorWSConnection::processConnectMessage(const LLSD& message)
+void LLScriptEditorWSServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connection)
{
- LL_INFOS("ScriptEditorWS") << "Processing connect message from client" << LL_ENDL;
+ // Call parent class to register global JSON-RPC methods
+ LLJSONRPCServer::setupConnectionMethods(connection);
- // Extract connection information from the message
- LLSD data;
- if (message.has("data"))
+ // Cast to our specific connection type to access script editor functionality
+ auto script_connection = std::dynamic_pointer_cast<LLScriptEditorWSConnection>(connection);
+ if (script_connection)
{
- data = message["data"];
- }
+ LL_INFOS("ScriptEditorWS") << "Setting up script editor connection methods" << LL_ENDL;
- // Parse client information
- std::string client_name;
- std::string client_version;
- std::string protocol_version;
- LLSD client_capabilities;
- LLSD supported_languages;
- LLSD client_features;
+ // Here derived classes could add script-specific method registrations
+ // For now, the base LLScriptEditorWSConnection doesn't register any specific methods
+ // but this provides a hook for future customization
- // Extract client identification
- if (data.has("client_name"))
- {
- client_name = data["client_name"].asString();
- LL_INFOS("ScriptEditorWS") << "Client name: " << client_name << LL_ENDL;
+ // Example of how custom methods could be registered:
+ // script_connection->registerMethod("script.custom", handler);
}
+}
- if (data.has("client_version"))
- {
- client_version = data["client_version"].asString();
- LL_INFOS("ScriptEditorWS") << "Client version: " << client_version << LL_ENDL;
- }
+//========================================================================
+LLScriptEdContainer* LLScriptEditorWSConnection::getEditor() const
+{
+ return mEditorPanel.isDead() ? nullptr : dynamic_cast<LLScriptEdContainer*>(mEditorPanel.get());
+}
- if (data.has("protocol_version"))
- {
- protocol_version = data["protocol_version"].asString();
- LL_INFOS("ScriptEditorWS") << "Protocol version: " << protocol_version << LL_ENDL;
- }
+std::shared_ptr<LLScriptEditorWSServer> LLScriptEditorWSConnection::getServer() const
+{
+ return std::static_pointer_cast<LLScriptEditorWSServer>(mOwningServer.lock());
+}
- // Extract client capabilities
- if (data.has("capabilities"))
- {
- client_capabilities = data["capabilities"];
- mEditorCapabilities = client_capabilities; // Store for later use
- LL_INFOS("ScriptEditorWS") << "Client capabilities count: " << client_capabilities.size() << LL_ENDL;
- }
+void LLScriptEditorWSConnection::onOpen()
+{
+ // Call parent class to set up JSON-RPC infrastructure
+ LLJSONRPCConnection::onOpen();
- // Extract supported languages
- if (data.has("supported_languages"))
- {
- supported_languages = data["supported_languages"];
- LL_INFOS("ScriptEditorWS") << "Supported languages count: " << supported_languages.size() << LL_ENDL;
- }
+ LL_INFOS("ScriptEditorWS") << "Script editor JSON-RPC connection opened" << LL_ENDL;
- // Extract client features
- if (data.has("features"))
- {
- client_features = data["features"];
- LL_INFOS("ScriptEditorWS") << "Client features available" << LL_ENDL;
- }
+ // Generate unique editor session ID
+ mEditorId = LLUUID::generateNewID().asString();
+ mEditorReady = false;
- // Generate or extract editor session ID
- if (data.has("session_id"))
- {
- mEditorId = data["session_id"].asString();
- LL_INFOS("ScriptEditorWS") << "Using client session ID: " << mEditorId << LL_ENDL;
- }
- else
- {
- // Generate a new session ID if client didn't provide one
- mEditorId = LLUUID::generateNewID().asString();
- LL_INFOS("ScriptEditorWS") << "Generated session ID: " << mEditorId << LL_ENDL;
- }
+ LL_INFOS("ScriptEditorWS") << "Initialized editor session: " << mEditorId << LL_ENDL;
- // Validate protocol compatibility
- bool protocol_compatible = true;
- if (!protocol_version.empty())
- {
- // For now, we only support protocol version "1.0"
- if (protocol_version != "1.0")
+ // Build hello data according to the protocol specification
+ LLSD handshake;
+ handshake["server_version"] = "1.0.0";
+ handshake["protocol_version"] = "1.0";
+ handshake["viewer_name"] = LLVersionInfo::instance().getChannel();
+ handshake["viewer_version"] = LLVersionInfo::instance().getVersion();
+
+ // Supported languages array
+ LLSD languages = LLSD::emptyArray();
+ languages.append("lsl");
+ languages.append("luau");
+ handshake["supported_languages"] = languages;
+
+ // Features object
+ LLSD features;
+ features["live_sync"] = true;
+ features["compilation"] = true;
+ features["syntax_highlight"] = true;
+ handshake["features"] = features;
+
+ // Send editor.handshake method call to the client and handle response
+ call("session.handshake", handshake, [this](const LLSD& result, const LLSD& error) {
+ if (error.isUndefined())
{
- protocol_compatible = false;
- LL_WARNS("ScriptEditorWS") << "Unsupported protocol version: " << protocol_version
- << ", expected: 1.0" << LL_ENDL;
+ handleHandshakeResponse(result);
}
- }
-
- // Determine if we have feature compatibility
- bool has_script_editing = false;
- if (client_capabilities.isArray())
- {
- for (LLSD::array_const_iterator it = client_capabilities.beginArray();
- it != client_capabilities.endArray(); ++it)
+ else
{
- if (it->asString() == "script_editing")
- {
- has_script_editing = true;
- break;
- }
+ LL_WARNS("ScriptEditorWS") << "Handshake failed: "
+ << error["message"].asString() << LL_ENDL;
}
- }
-
- // Build response message
- LLSD response;
- response["command"] = "connect_ack";
+ });
- LLSD& response_data = response["data"];
+ LL_INFOS("ScriptEditorWS") << "Sent handshake call to new editor client" << LL_ENDL;
+}
- if (protocol_compatible && has_script_editing)
- {
- // Successful connection
- response_data["status"] = "connected";
- response_data["message"] = "Connection established successfully";
- response_data["session_id"] = mEditorId;
- response_data["timestamp"] = LLDate::now().asString();
+void LLScriptEditorWSConnection::onClose()
+{
+ // Call parent class to clean up JSON-RPC infrastructure
+ LLJSONRPCConnection::onClose();
- // Send back our supported capabilities that match the client's
- LLSD mutual_capabilities = LLSD::emptyArray();
+ LL_INFOS("ScriptEditorWS") << "Script editor JSON-RPC connection closed for session: "
+ << mEditorId << LL_ENDL;
- // Check which capabilities we both support
- if (client_capabilities.isArray())
- {
- // Our server capabilities (from onConnectionOpened)
- std::set<std::string> server_caps = {
- "script_editing", "script_synchronization", "compilation",
- "metadata", "syntax_highlighting", "error_reporting"
- };
+ cleanupConnection();
- for (LLSD::array_const_iterator it = client_capabilities.beginArray();
- it != client_capabilities.endArray(); ++it)
- {
- std::string cap = it->asString();
- if (server_caps.count(cap) > 0)
- {
- mutual_capabilities.append(cap);
- }
- }
- }
+ // Clean up editor-specific state
+ mEditorId.clear();
+ mEditorCapabilities.clear();
+ mScriptId.clear();
+ mEditorReady = false;
- response_data["mutual_capabilities"] = mutual_capabilities;
+ // Clean up handshake response data
+ mClientName.clear();
+ mClientVersion.clear();
+ mProtocolVersion.clear();
+ mScriptName.clear();
+ mScriptLanguage.clear();
+ mLanguages.clear();
+ mFeatures.clear();
+}
- // Send supported languages intersection
- LLSD mutual_languages = LLSD::emptyArray();
- if (supported_languages.isArray())
- {
- std::set<std::string> server_languages = {"lsl", "luau"};
+void LLScriptEditorWSConnection::handleHandshakeResponse(const LLSD& result)
+{
+ LL_INFOS("ScriptEditorWS") << "Processing handshake response from client" << LL_ENDL;
- for (LLSD::array_const_iterator it = supported_languages.beginArray();
- it != supported_languages.endArray(); ++it)
- {
- std::string lang = it->asString();
- if (server_languages.count(lang) > 0)
- {
- mutual_languages.append(lang);
- }
- }
- }
- else
- {
- // Default to all our supported languages if client didn't specify
- mutual_languages.append("lsl");
- mutual_languages.append("luau");
- }
+ // Extract and validate client information
+ mClientName = result["client_name"].asString();
+ mClientVersion = result["client_version"].asString();
+ mProtocolVersion = result["protocol_version"].asString();
- response_data["supported_languages"] = mutual_languages;
+ // Validate protocol compatibility
+ if (mProtocolVersion != "1.0")
+ {
+ LL_WARNS("ScriptEditorWS") << "Protocol version mismatch. Expected: 1.0, Got: "
+ << mProtocolVersion << LL_ENDL;
+ }
- // Connection limits and constraints
- response_data["max_script_size"] = 65536;
- response_data["heartbeat_interval"] = 30;
+ // Store script information if provided
+ mScriptName = result["script_name"].asString();
+ mScriptLanguage = result["script_language"].asString();
+ mScriptId = result["script_id"].asString();
- LL_INFOS("ScriptEditorWS") << "Successfully connected client: " << client_name
- << " v" << client_version
- << " with " << mutual_capabilities.size() << " mutual capabilities" << LL_ENDL;
- }
- else
+ // Store supported languages
+ for (const auto& lang : llsd::inArray( result["languages"]))
{
- // Connection failed
- response_data["status"] = "rejected";
-
- if (!protocol_compatible)
+ if (lang.isString())
{
- response_data["error"] = "incompatible_protocol";
- response_data["message"] = "Unsupported protocol version: " + protocol_version;
+ mLanguages.insert(lang.asString());
}
- else if (!has_script_editing)
- {
- response_data["error"] = "missing_capabilities";
- response_data["message"] = "Client must support 'script_editing' capability";
- }
- else
+ }
+
+ for (const auto& [feature, enabled] : llsd::inMap(result["features"]))
+ {
+ if (enabled.asBoolean())
{
- response_data["error"] = "connection_failed";
- response_data["message"] = "Connection failed for unknown reason";
+ mFeatures.insert(feature);
}
+ }
+
+ connectToEditor(mScriptId);
+ // Mark editor as ready
+ mEditorReady = true;
+
+ LL_INFOS("ScriptEditorWS") << "Handshake completed successfully for session: " << mEditorId << LL_ENDL;
+}
- LL_WARNS("ScriptEditorWS") << "Rejected connection from client: " << client_name
- << " - " << response_data["message"].asString() << LL_ENDL;
+bool LLScriptEditorWSConnection::connectToEditor(const std::string& script_id)
+{
+ LLScriptEditorWSServer::ptr_t server = std::dynamic_pointer_cast<LLScriptEditorWSServer>(mOwningServer.lock());
+ if (!server)
+ {
+ LL_WARNS("ScriptEditorWS") << "Cannot connect to editor - server reference lost" << LL_ENDL;
+ return false;
}
- // Send the response back to the client
- if (sendMessage(response))
+ mEditorPanel = server->findEditorForScript(script_id);
+
+ LLScriptEdContainer* editor_core = getEditor();
+ if (!editor_core)
{
- LL_INFOS("ScriptEditorWS") << "Sent connect acknowledgment to client" << LL_ENDL;
+ LL_INFOS("ScriptEditorWS") << "Could not find editor: " << script_id << LL_ENDL;
+ // TODO: Disconnect the client if no editor found
+ return false;
}
- else
+
+ return true;
+}
+
+void LLScriptEditorWSConnection::cleanupConnection()
+{
+ LL_INFOS("ScriptEditorWS") << "Cleaning up connection for editor session: " << mEditorId << LL_ENDL;
+
+ LLScriptEditorWSServer::ptr_t server = getServer();
+ if (server)
{
- LL_WARNS("ScriptEditorWS") << "Failed to send connect acknowledgment to client" << LL_ENDL;
+ server->dissociateEditor(mScriptId);
}
- // If connection was successful, we could also send any initial state or configuration
- if (response_data["status"].asString() == "connected")
+ LLScriptEdContainer* editor_core = getEditor();
+
+ if (editor_core)
{
- // TODO: Send initial script list, active editors, or other relevant state
- // Example: sendScriptList(), sendActiveEditors(), etc.
+ editor_core->cleanupWebSocket();
+
+ // Notify the editor panel of disconnection
+ //editor_core->onExternalEditorDisconnected();
}
+
+ mEditorPanel = LLHandle<LLPanel>();
+}
+
+
+void LLScriptEditorWSConnection::sendDisconnect(S32 reason, const std::string& message)
+{
+ LL_INFOS("ScriptEditorWS") << "Sending disconnect message to editor (reason: "
+ << reason << ", message: " << message << ")" << LL_ENDL;
+
+ LLSD params;
+ params["reason"] = reason;
+ params["message"] = message;
+
+ notify("session.disconnect", params);
}