class_name AdminClient extends Node ## Client der Match-Server-Admin-API v1 (Doku: docs/admin-api.md im ## scopedvr-match-server-Repo). Knoten in den Baum hängen, dann ## `var res = await client.call_api("GET", "/status")`. ## ## Antwort: {ok: bool (HTTP 2xx), status: int, body: Dictionary, transport_error: String} ## transport_error ist deutsch und leer, wenn der Server geantwortet hat. ## Das Token geht NUR als Header raus und wird nie geloggt oder zurückgegeben. const TIMEOUT_S := 30.0 const MAX_RESPONSE_BYTES := 8 * 1024 * 1024 var host: String = "" var port: int = 24580 var tls: bool = false var token: String = "" ## Server-Eintrag aus dem ServerStore übernehmen. func use(entry: Dictionary) -> void: host = str(entry.get("host", "")) port = int(entry.get("port", 24580)) tls = bool(entry.get("tls", false)) token = str(entry.get("token", "")) static func base_url(p_host: String, p_port: int, p_tls: bool) -> String: var h := p_host if h.contains(":") and not h.begins_with("["): h = "[%s]" % h # IPv6 return "%s://%s:%d/api/v1" % ["https" if p_tls else "http", h, p_port] ## Prüft die Eingabe für „Server hinzufügen“. Leerer Text = ok. static func check_address(p_host: String, p_port: int) -> String: var h := p_host.strip_edges() if h.is_empty(): return "Bitte eine Adresse eingeben (IP oder Name)." if h.contains("/") or h.contains(" ") or h.contains("://"): return "Nur die Adresse eintragen, ohne http:// und ohne Schrägstriche (z. B. 192.168.1.20)." if p_port < 1 or p_port > 65535: return "Der Port muss zwischen 1 und 65535 liegen." return "" func call_api(method: String, path: String, body: Variant = null, query: Dictionary = {}, raw: PackedByteArray = PackedByteArray()) -> Dictionary: var err := check_address(host, port) if err != "": return _transport_fail(err) var url := base_url(host, port, tls) + path if not query.is_empty(): var parts: PackedStringArray = [] for k in query.keys(): parts.append("%s=%s" % [str(k).uri_encode(), str(query[k]).uri_encode()]) url += "?" + "&".join(parts) var headers: PackedStringArray = ["Accept: application/json"] if token != "": headers.append("Authorization: Bearer " + token) var payload := raw if body != null: payload = JSON.stringify(body).to_utf8_buffer() headers.append("Content-Type: application/json") elif not raw.is_empty(): headers.append("Content-Type: application/octet-stream") var http := HTTPRequest.new() http.timeout = TIMEOUT_S http.body_size_limit = MAX_RESPONSE_BYTES http.accept_gzip = false add_child(http) var m := _method(method) var rerr := http.request_raw(url, headers, m, payload) if rerr != OK: http.queue_free() return _transport_fail("Anfrage konnte nicht gestartet werden (Fehler %d)." % rerr) var done: Array = await http.request_completed http.queue_free() var result: int = done[0] if result != HTTPRequest.RESULT_SUCCESS: return _transport_fail(_result_text(result)) var code: int = done[1] var parsed: Variant = JSON.parse_string((done[3] as PackedByteArray).get_string_from_utf8()) var out: Dictionary = parsed if parsed is Dictionary else {} return {"ok": code >= 200 and code < 300, "status": code, "body": out, "transport_error": ""} static func _method(m: String) -> int: match m.to_upper(): "POST": return HTTPClient.METHOD_POST "PUT": return HTTPClient.METHOD_PUT "PATCH": return HTTPClient.METHOD_PATCH "DELETE": return HTTPClient.METHOD_DELETE return HTTPClient.METHOD_GET static func _transport_fail(msg: String) -> Dictionary: return {"ok": false, "status": 0, "body": {}, "transport_error": msg} static func _result_text(result: int) -> String: match result: HTTPRequest.RESULT_CANT_CONNECT, HTTPRequest.RESULT_CONNECTION_ERROR: return "Keine Verbindung zum Server. Läuft er, ist die Admin-API eingeschaltet, und stimmen Adresse und Port?" HTTPRequest.RESULT_CANT_RESOLVE: return "Die Adresse wurde nicht gefunden. Tippfehler?" HTTPRequest.RESULT_TIMEOUT: return "Der Server antwortet nicht (Zeitüberschreitung)." HTTPRequest.RESULT_TLS_HANDSHAKE_ERROR: return "Sichere Verbindung (https) fehlgeschlagen. Gibt der Server wirklich https her?" HTTPRequest.RESULT_BODY_SIZE_LIMIT_EXCEEDED: return "Die Antwort des Servers ist zu groß." return "Verbindungsfehler (Code %d)." % result