diff options
Diffstat (limited to 'indra/newview/llscripteditorws.cpp')
| -rw-r--r-- | indra/newview/llscripteditorws.cpp | 692 |
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); } |
