Skip to content

Commit 30f4cb7

Browse files
committed
Add WebSocket API implementation and update dependencies in pyproject.toml
1 parent 9b135df commit 30f4cb7

3 files changed

Lines changed: 320 additions & 4 deletions

File tree

architecture.drawio.png

16.3 KB
Loading

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ dependencies = [
1111
"click==8.1.8",
1212
"Flask==3.1.1",
1313
"gamuLogger>=3.2.4",
14-
"itsdangerous==2.2.0",
14+
"itsdangerous>=2.2.0",
1515
"Jinja2==3.1.6",
16-
"MarkupSafe==3.0.2",
17-
"typing_extensions==4.13.2",
18-
"urllib3==2.5.0",
16+
"MarkupSafe>=3.0.2",
17+
"typing_extensions>=4.13.2",
18+
"urllib3>=2.5.0",
1919
"Werkzeug==3.1.3",
2020
"dnspython==2.7.0",
2121
"eventlet==0.40.3",

src/websocket_api.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import socketio
2+
from typing import Any
3+
from datetime import datetime
4+
from gamuLogger import Logger
5+
6+
from version import Version
7+
8+
from modular_server_manager import BaseInterface
9+
10+
Logger.set_module("User Interface.WebSocket API")
11+
12+
class WebSocketAPI(BaseInterface):
13+
def __init__(self, *args, **kwargs):
14+
Logger.trace("Initializing WebSocketServer")
15+
BaseInterface.__init__(self,
16+
*args,
17+
**kwargs
18+
)
19+
self.__sio = socketio.Server(cors_allowed_origins='*')
20+
self.__config_routes()
21+
22+
def _get_sio(self):
23+
"""
24+
Get the SocketIO server instance.
25+
26+
:return: The SocketIO server instance.
27+
"""
28+
return self.__sio
29+
30+
def __config_routes(self):
31+
@self.__sio.event
32+
def connect(sid, environ):
33+
Logger.info(f"Client connected: {sid}")
34+
35+
@self.__sio.event
36+
def disconnect(sid):
37+
Logger.info(f"Client disconnected: {sid}")
38+
39+
self.__config_routes_user()
40+
self.__config_routes_server()
41+
42+
@staticmethod
43+
def normalize_types(data : dict[str, Any]) -> dict[str, int | float | str | bool | None]:
44+
normalized_data : dict[str, Any] = {}
45+
for key, value in data.items():
46+
if isinstance(value, (int, float, str, bool)) or value is None:
47+
normalized_data[key] = value
48+
elif isinstance(value, datetime):
49+
normalized_data[key] = value.isoformat()
50+
else:
51+
normalized_data[key] = str(value)
52+
return normalized_data
53+
54+
def default_callback(self, event_name: str, **kwargs):
55+
Logger.debug(f"Transferring event '{event_name}' to WebSocket clients with data: {kwargs}")
56+
self.__sio.emit(event_name, self.normalize_types(kwargs))
57+
58+
59+
def __config_routes_user(self):
60+
@self.__sio.event
61+
def user_login_request(sid, data : dict):
62+
username = data.get("username")
63+
password = data.get("password")
64+
remember = data.get("remember", False)
65+
66+
try:
67+
if not isinstance(username, str) or not isinstance(password, str):
68+
Logger.warning(f"Invalid login attempt data from {sid}: {data}")
69+
raise ValueError("Invalid username or password format")
70+
71+
if not isinstance(remember, bool):
72+
remember = False
73+
74+
Logger.debug(f"User login attempt: {username}, remember: {remember}")
75+
token = self.login(username, password, remember)
76+
except ValueError as e:
77+
Logger.info(f"User login failed for {username}: {str(e)}")
78+
self.__sio.emit("user_login_response", {"success": False, "error": str(e)}, to=sid)
79+
return
80+
else:
81+
Logger.info(f"User login successful for {username}")
82+
self.__sio.emit("user_login_response", {"success": True, "token": token.token}, to=sid)
83+
84+
@self.__sio.event
85+
def user_register_request(sid, data : dict):
86+
username = data.get("username")
87+
password = data.get("password")
88+
remember = data.get("remember", False)
89+
90+
try:
91+
if not isinstance(username, str) or not isinstance(password, str):
92+
Logger.warning(f"Invalid registration attempt data from {sid}: {data}")
93+
raise ValueError("Invalid username or password format")
94+
95+
if not isinstance(remember, bool):
96+
remember = False
97+
98+
Logger.debug(f"User registration attempt: {username}, remember: {remember}")
99+
token = self.register(username, password, remember)
100+
except ValueError as e:
101+
Logger.info(f"User registration failed for {username}: {str(e)}")
102+
self.__sio.emit("user_register_response", {"success": False, "error": str(e)}, to=sid)
103+
return
104+
else:
105+
Logger.info(f"User registration successful for {username}")
106+
self.__sio.emit("user_register_response", {"success": True, "token": token.token}, to=sid)
107+
108+
@self.__sio.event
109+
def user_logout_request(sid, data : dict):
110+
token_str = data.get("token")
111+
112+
try:
113+
if not isinstance(token_str, str):
114+
Logger.warning(f"Invalid logout attempt data from {sid}: {data}")
115+
raise ValueError("Invalid token format")
116+
117+
Logger.debug(f"User logout attempt with token: {token_str}")
118+
self.logout(token_str)
119+
except ValueError as e:
120+
Logger.info(f"User logout failed for token {token_str}: {str(e)}")
121+
self.__sio.emit("user_logout_response", {"success": False, "error": str(e)}, to=sid)
122+
return
123+
else:
124+
Logger.info(f"User logout successful for token {token_str}")
125+
self.__sio.emit("user_logout_response", {"success": True}, to=sid)
126+
127+
@self.__sio.event
128+
def user_delete_request(sid, data : dict):
129+
token_str = data.get("token")
130+
131+
try:
132+
if not isinstance(token_str, str):
133+
Logger.warning(f"Invalid delete user attempt data from {sid}: {data}")
134+
raise ValueError("Invalid token format")
135+
136+
Logger.debug(f"User delete attempt with token: {token_str}")
137+
self.delete_user(token_str)
138+
except ValueError as e:
139+
Logger.info(f"User delete failed for token {token_str}: {str(e)}")
140+
self.__sio.emit("user_delete_response", {"success": False, "error": str(e)}, to=sid)
141+
return
142+
else:
143+
Logger.info(f"User delete successful for token {token_str}")
144+
self.__sio.emit("user_delete_response", {"success": True}, to=sid)
145+
146+
@self.__sio.event
147+
def user_info_request(sid, data : dict):
148+
token_str = data.get("token")
149+
150+
try:
151+
if not isinstance(token_str, str):
152+
Logger.warning(f"Invalid user info request data from {sid}: {data}")
153+
raise ValueError("Invalid token format")
154+
155+
Logger.debug(f"User info request with token: {token_str}")
156+
user = self.get_user_info(token_str)
157+
except ValueError as e:
158+
Logger.info(f"User info request failed for token {token_str}: {str(e)}")
159+
self.__sio.emit("user_info_response", {"success": False, "error": str(e)}, to=sid)
160+
return
161+
else:
162+
Logger.info(f"User info request successful for token {token_str}")
163+
data = {
164+
"success": True,
165+
'username': user.username,
166+
'registered_at': user.registered_at.isoformat(),
167+
'last_login': user.last_login.isoformat(),
168+
'access_level': user.access_level.value
169+
}
170+
self.__sio.emit("user_info_response", data, to=sid)
171+
172+
@self.__sio.event
173+
def user_update_password_request(sid, data : dict):
174+
token_str = data.get("token")
175+
new_password = data.get("password")
176+
177+
try:
178+
if not isinstance(token_str, str) or not isinstance(new_password, str):
179+
Logger.warning(f"Invalid update password attempt data from {sid}: {data}")
180+
raise ValueError("Invalid token or password format")
181+
182+
Logger.debug(f"User update password attempt with token: {token_str}")
183+
self.update_password(token_str, new_password)
184+
except ValueError as e:
185+
Logger.info(f"User update password failed for token {token_str}: {str(e)}")
186+
self.__sio.emit("user_update_password_response", {"success": False, "error": str(e)}, to=sid)
187+
return
188+
else:
189+
Logger.info(f"User update password successful for token {token_str}")
190+
self.__sio.emit("user_update_password_response", {"success": True}, to=sid)
191+
192+
193+
def __config_routes_server(self):
194+
@self.__sio.event
195+
def get_versions_minecraft_request(sid, data : dict):
196+
token_str = data.get("token")
197+
198+
try:
199+
if not isinstance(token_str, str):
200+
Logger.warning(f"Invalid list MC versions request data from {sid}: {data}")
201+
raise ValueError("Invalid token format")
202+
203+
Logger.debug(f"List MC versions request with token: {token_str}")
204+
versions = self.list_mc_versions(token_str)
205+
except ValueError as e:
206+
Logger.info(f"List MC versions request failed for token {token_str}: {str(e)}")
207+
self.__sio.emit("get_versions_minecraft_response", {"success": False, "error": str(e)}, to=sid)
208+
return
209+
else:
210+
Logger.info(f"List MC versions request successful for token {token_str}")
211+
version_strs = [str(v) for v in versions]
212+
self.__sio.emit("get_versions_minecraft_response", {"success": True, "versions": version_strs}, to=sid)
213+
214+
@self.__sio.event
215+
def get_versions_forge_request(sid, data : dict):
216+
token_str = data.get("token")
217+
mc_version_str = data.get("mc_version")
218+
219+
try:
220+
if not isinstance(token_str, str) or not isinstance(mc_version_str, str):
221+
Logger.warning(f"Invalid list Forge versions request data from {sid}: {data}")
222+
raise ValueError("Invalid token or MC version format")
223+
224+
Logger.debug(f"List Forge versions request with token: {token_str}, MC version: {mc_version_str}")
225+
versions = self.list_forge_versions(token_str, Version.from_string(mc_version_str))
226+
except ValueError as e:
227+
Logger.info(f"List Forge versions request failed for token {token_str}: {str(e)}")
228+
self.__sio.emit("get_versions_forge_response", {"success": False, "error": str(e)}, to=sid)
229+
return
230+
else:
231+
Logger.info(f"List Forge versions request successful for token {token_str}")
232+
version_strs = [str(v) for v in versions]
233+
self.__sio.emit("get_versions_forge_response", {"success": True, "versions": version_strs}, to=sid)
234+
235+
@self.__sio.event
236+
def server_list_request(sid, data : dict):
237+
token_str = data.get("token")
238+
239+
try:
240+
if not isinstance(token_str, str):
241+
Logger.warning(f"Invalid list servers request data from {sid}: {data}")
242+
raise ValueError("Invalid token format")
243+
244+
Logger.debug(f"List servers request with token: {token_str}")
245+
servers = self.list_servers(token_str)
246+
except ValueError as e:
247+
Logger.info(f"List servers request failed for token {token_str}: {str(e)}")
248+
self.__sio.emit("servers_list_response", {"success": False, "error": str(e)}, to=sid)
249+
return
250+
else:
251+
Logger.info(f"List servers request successful for token {token_str}")
252+
server_data = [self.normalize_types(server) for server in servers]
253+
self.__sio.emit("servers_list_response", {"success": True, "servers": server_data}, to=sid)
254+
255+
@self.__sio.event
256+
def server_info_request(sid, data : dict):
257+
token_str = data.get("token")
258+
server_name = data.get("server_name")
259+
260+
try:
261+
if not isinstance(token_str, str) or not isinstance(server_name, str):
262+
Logger.warning(f"Invalid get server info request data from {sid}: {data}")
263+
raise ValueError("Invalid token or server name format")
264+
265+
Logger.debug(f"Get server info request with token: {token_str}, server name: {server_name}")
266+
server_info = self.get_server_info(token_str, server_name)
267+
except ValueError as e:
268+
Logger.info(f"Get server info request failed for token {token_str}: {str(e)}")
269+
self.__sio.emit("server_info_response", {"success": False, "error": str(e)}, to=sid)
270+
return
271+
else:
272+
Logger.info(f"Get server info request successful for token {token_str}")
273+
self.__sio.emit("server_info_response", {"success": True, "server_info": self.normalize_types(server_info)}, to=sid)
274+
275+
@self.__sio.event
276+
def server_create_request(sid, data : dict):
277+
token_str = data.get("token")
278+
name = data.get("name")
279+
_type = data.get("type")
280+
path = data.get("path")
281+
autostart = data.get("autostart", False)
282+
mc_version = data.get("mc_version")
283+
modloader_version = data.get("modloader_version", None)
284+
ram = data.get("ram")
285+
286+
try:
287+
if not isinstance(token_str, str) or not isinstance(name, str) or not isinstance(_type, str) or not isinstance(path, str) or not isinstance(mc_version, str):
288+
Logger.warning(f"Invalid create server request data from {sid}: {data}")
289+
raise ValueError("Invalid format for one or more parameters")
290+
291+
if not isinstance(autostart, bool):
292+
autostart = False
293+
294+
if modloader_version is not None and not isinstance(modloader_version, str):
295+
modloader_version = None
296+
297+
if not isinstance(ram, int):
298+
raise ValueError("Invalid RAM format")
299+
300+
Logger.debug(f"Create server request with token: {token_str}, name: {name}, type: {_type}, path: {path}, autostart: {autostart}, mc_version: {mc_version}, modloader_version: {modloader_version}, ram: {ram}")
301+
server_info = self.create_server(
302+
token_str,
303+
name,
304+
_type,
305+
path,
306+
autostart,
307+
Version.from_string(mc_version),
308+
Version.from_string(modloader_version) if modloader_version else None,
309+
ram
310+
)
311+
except ValueError as e:
312+
Logger.warning(f"Create server request failed for token {token_str}: {str(e)}")
313+
314+
315+
316+

0 commit comments

Comments
 (0)