1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
|
/**
* @file llscripteditorws.h
* @brief WebSocket server and connection classes for external script editor integration
*
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
* Second Life Viewer Source Code
* 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
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#pragma once
#include "lljsonrpcws.h"
#include "llsd.h"
#include "lluuid.h"
#include "llhandle.h"
#include "lltimer.h"
#include <memory>
#include <string>
#include <map>
#include <set>
// Forward declarations
class LLLiveLSLEditor;
class LLScriptEdContainer;
class LLScriptEditorWSServer;
class LLChat;
class LLPanel;
class LLScriptEditorWSConnection : public LLJSONRPCConnection, public std::enable_shared_from_this<LLScriptEditorWSConnection>
{
public:
using ptr_t = std::shared_ptr<LLScriptEditorWSConnection>;
using wptr_t = std::weak_ptr<LLScriptEditorWSConnection>;
enum DisconnectReason
{
REASON_NORMAL = 0,
REASON_EDITOR_CLOSED = 1,
REASON_PROTOCOL_ERROR = 2,
REASON_TIMEOUT = 3,
REASON_INTERNAL_ERROR = 4
};
LLScriptEditorWSConnection(const LLWebsocketMgr::WSServer::ptr_t server, const LLWebsocketMgr::connection_h& handle) :
LLJSONRPCConnection(server, handle)
{
mConnectionID = sNextConnectionID++;
}
~LLScriptEditorWSConnection() override = default;
U32 getConnectionID() const { return mConnectionID; }
// Connection lifecycle overrides
void onOpen() override;
void onClose() override;
void sendDisconnect(S32 reason = 0, const std::string& message = "Goodbye");
private:
using string_set_t = std::set<std::string>;
/**
* @brief Handle the handshake response from the client
* @param result The response data from the client containing client information
*/
void handleHandshakeResponse(const LLSD& result);
std::string generateChallenge();
LLScriptEdContainer* getEditor() const;
std::shared_ptr<LLScriptEditorWSServer> getServer() const;
U32 mConnectionID{ 0 }; ///< Unique identifier for this connection
// Client handshake response data
std::string mClientName; ///< Name of the external editor client
std::string mClientVersion; ///< Version of the external editor client
std::string mProtocolVersion; ///< JSON-RPC protocol version supported by client
std::string mScriptName; ///< Name of the script being edited
std::string mScriptLanguage; ///< Programming language of the script (lsl, luau, etc.)
string_set_t mLanguages; ///< Set of supported scripting languages
string_set_t mFeatures; ///< Active client features (live_sync, compilation, etc.)
LLUUID mChallenge;
std::string mChallengeFile; ///< Temporary file used for challenge-response verification
static U32 sNextConnectionID;
};
/**
* @class LLScriptEditorWSServer
* @brief JSON-RPC 2.0 WebSocket server for external script editor integration
*
* This server extends the JSON-RPC server to provide specialized functionality
* for external script editor integration. It manages WebSocket connections from
* external script editors and provides a structured JSON-RPC 2.0 interface
* between the Second Life viewer's script editing functionality and external
* development tools.
*
* ## Architecture
*
* The server acts as a JSON-RPC communication hub between:
* - LLLiveLSLEditor instances (in-world script editing)
* - External script editors (VS Code, Atom, Sublime Text, etc.)
* - Script compilation and save services
*
* ## Usage
*
* @code
* // Create and start the JSON-RPC server
* auto server = std::make_shared<LLScriptEditorWSServer>("script_editor_server", 9020);
* LLWebsocketMgr::getInstance()->addServer(server);
* LLWebsocketMgr::getInstance()->startServer("script_editor_server");
*
* // Associate with an LSL editor
* server->associateEditor(editor_handle, script_id);
* @endcode
*
* ## Security Considerations
*
* - Server binds to localhost only by default for security
* - JSON-RPC 2.0 structured protocol with validation
* - Rate limiting handled by base JSON-RPC server
* - Error handling with standardized JSON-RPC error codes
*/
class LLScriptEditorWSServer : public LLJSONRPCServer
{
public:
enum SubscriptionError_t
{
SUBSCRIPTION_SUCCESS = 0,
SUBSCRIPTION_INVALID_EDITOR,
SUBSCRIPTION_INVALID_SUBSCRIPTION,
SUBSCRIPTION_ALREADY_SUBSCRIBED,
SUBSCRIPTION_INTERNAL_ERROR
};
static constexpr char const* DEFAULT_SERVER_NAME = "script_editor_server";
static constexpr U16 DEFAULT_SERVER_PORT = 9020;
using ptr_t = std::shared_ptr<LLScriptEditorWSServer>;
using wptr_t = std::weak_ptr<LLScriptEditorWSServer>;
LLScriptEditorWSServer(const std::string& name, U16 port, bool local_only = true);
virtual ~LLScriptEditorWSServer() = default;
static LLScriptEditorWSServer::ptr_t getServer();
void onStarted() override;
void onStopped() override;
void onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection) override;
void onConnectionClosed(const LLWebsocketMgr::WSConnection::ptr_t& connection) override;
bool subscribeScriptEditor(const LLUUID& object_id, const LLUUID& item_id, std::string_view script_name,
const LLHandle<LLPanel>& editor_handle, const std::string &script_id);
void unsubscribeEditor(const std::string &script_id);
void notifyScript(const std::string& script_id, const std::string& method, const LLSD& message) const;
void sendUnsubscribeScriptEditor(const std::string& script_id);
void sendCompileResults(const std::string& script_id, const LLSD& results) const;
LLHandle<LLPanel> findEditorForScript(const std::string& script_id) const;
void forwardChatToIDE(const LLChat& chat_msg) const;
std::set<std::string> getActiveScripts() const;
protected:
LLWebsocketMgr::WSConnection::ptr_t connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
LLWebsocketMgr::connection_h handle) override;
void setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) override;
void broadcastLanguageChange();
LLSD handleLanguageIdRequest() const;
LLSD handleSyntaxRequest(const LLSD ¶ms) const;
LLSD handleScriptSubscribe(U32 connection_id, const LLSD& params);
LLSD handleScriptUnsubscribe(U32 connection_id, const LLSD& params);
LLSD handleFileWatcherFileListRequest() const;
private:
struct EditorSubscription
{
EditorSubscription(const LLUUID &object_id, const LLUUID &item_id, std::string_view script_name, LLHandle<LLPanel> editor_handle):
mObjectID(object_id),
mItemID(item_id),
mScriptName(script_name),
mEditorHandle(editor_handle)
{}
U32 mConnectionID{ 0 };
LLUUID mObjectID;
LLUUID mItemID;
std::string mScriptName;
LLScriptEditorWSConnection::wptr_t mConnection;
LLHandle<LLPanel> mEditorHandle;
};
using subscriptions_t = std::unordered_map<std::string, EditorSubscription>;
SubscriptionError_t updateScriptSubscription(const std::string &script_id, U32 connection_id);
void unsubscribeConnection(U32 connection_id);
subscriptions_t mSubscriptions;
std::map<U32, LLScriptEditorWSConnection::wptr_t> mActiveConnections;
boost::signals2::connection mLanguageChangeSignal;
LLUUID mLastSyntaxId;
LLTimer mCleanupTimer;
static constexpr F32 CLEANUP_INTERVAL = 60.0f; // seconds
static constexpr F32 CONNECTION_TIMEOUT = 300.0f; // 5 minutes
};
|