# (c) 2023-present Eroax # (c) 2023-present Yagich # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) extends Node class_name RPCRenderer ## A WebSocket API server. ## ## A renderer that exposes a subset of the core API to remote clients using WebSocket. const SCOPES_DIR := "res://rpc_renderer/scopes/" @export var default_port := 6907 var _ws: WebSocketServer = WebSocketServer.new() var scopes: Array[RPCScope] var request_schema: Zodot func load_scopes() -> void: var d := DirAccess.open(SCOPES_DIR) d.list_dir_begin() var current := d.get_next() while current != "": if !d.current_is_dir(): var scope = load(SCOPES_DIR.path_join(current)).new() as RPCScope scopes.append(scope) current = d.get_next() for scope in scopes: scope.event.connect( func(event: RPCEvent): send_frame(0, event) ) scope.response.connect( func(response: RPCRequestResponse): send_frame(response.peer_id, response) if response.event_counterpart != null: send_frame(-response.peer_id, response.event_counterpart) ) func build_schema() -> void: var scope_names = scopes.map( func(scope: RPCScope): return scope.name ) var scopes_schema: Array[Zodot] scopes_schema.assign(scope_names.map( func(scope_name: String): return Z.literal(scope_name) )) request_schema = Z.schema({ "request": Z.schema({ "id": Z.string(), "scope": Z.union(scopes_schema), "operation": RPCOperation.schema(), "keep": Z.dictionary().nullable(), }) }) func _ready() -> void: load_scopes() build_schema() add_child(_ws) _ws.client_connected.connect(_on_ws_client_connected) _ws.client_disconnected.connect(_on_ws_client_disconnected) _ws.message_received.connect(_on_ws_message) listen() func listen(port := default_port) -> void: if _ws.listen(port) != OK: pass func stop() -> void: _ws.stop() func send_frame(peer_id: int, frame: RPCFrame) -> void: _ws.send(peer_id, JSON.stringify(frame.to_dict(), "", false)) func _on_ws_message(peer_id: int, message: Variant) -> void: if not message is String: return var json := JSON.new() var err := json.parse(message) if err != OK: send_frame(peer_id, RPCError.new(json.get_error_message())) return var result = request_schema.parse(json.get_data()) if not result.ok(): send_frame(peer_id, RPCError.new(result.error)) return var req := RPCRequest.from_dict(result.data, peer_id) for scope in scopes: if scope.can_handle_request(req): scope.handle_request(req) return func _on_ws_client_connected(peer_id: int) -> void: pass func _on_ws_client_disconnected(peer_id: int) -> void: pass