class_name LuaNetworkUtils extends RefCounted static func setup_network_api(vm: LuauVM): vm.lua_pushcallable(_lua_fetch_handler, "fetch") vm.lua_setglobal("fetch") static func _lua_fetch_handler(vm: LuauVM) -> int: var url: String = vm.luaL_checkstring(1) var options: Dictionary = {} if vm.lua_gettop() >= 2 and vm.lua_istable(2): options = vm.lua_todictionary(2) # Default options var method = options.get("method", "GET").to_upper() var headers = options.get("headers", {}) var body = options.get("body", "") # Set request options var headers_array: PackedStringArray = [] for header_name in headers: headers_array.append(str(header_name) + ": " + str(headers[header_name])) var response_data = make_http_request(url, method, headers_array, body) # Create response object with actual data vm.lua_newtable() # Add response properties vm.lua_pushinteger(response_data.status) vm.lua_setfield(-2, "status") vm.lua_pushstring(response_data.status_text) vm.lua_setfield(-2, "statusText") # Convert response headers to table vm.lua_newtable() for header_name in response_data.headers: vm.lua_pushstring(response_data.headers[header_name]) vm.lua_setfield(-2, header_name.to_lower()) vm.lua_setfield(-2, "headers") # Store response body vm.lua_pushstring(response_data.body) vm.lua_setfield(-2, "_response_body") # Add response methods vm.lua_pushcallable(_response_text_handler, "response.text") vm.lua_setfield(-2, "text") vm.lua_pushcallable(_response_json_handler, "response.json") vm.lua_setfield(-2, "json") vm.lua_pushcallable(_response_ok_handler, "response.ok") vm.lua_setfield(-2, "ok") return 1 static func _response_text_handler(vm: LuauVM) -> int: vm.luaL_checktype(1, vm.LUA_TTABLE) vm.lua_getfield(1, "_response_body") var response_text = vm.lua_tostring(-1) vm.lua_pop(1) vm.lua_pushstring(response_text) return 1 static func _response_json_handler(vm: LuauVM) -> int: vm.luaL_checktype(1, vm.LUA_TTABLE) vm.lua_getfield(1, "_response_body") var response_text = vm.lua_tostring(-1) vm.lua_pop(1) var json = JSON.new() var parse_result = json.parse(response_text) if parse_result == OK: vm.lua_pushvariant(json.data) else: vm.luaL_error("Invalid JSON in response") return 1 static func _response_ok_handler(vm: LuauVM) -> int: vm.luaL_checktype(1, vm.LUA_TTABLE) vm.lua_getfield(1, "status") var status = vm.lua_tointeger(-1) vm.lua_pop(1) vm.lua_pushboolean(status >= 200 and status < 300) return 1 static func make_http_request(url: String, method: String, headers: PackedStringArray, body: String) -> Dictionary: var http_client = HTTPClient.new() var response_data = { "status": 0, "status_text": "Network Error", "headers": {}, "body": "" } # Parse URL var url_parts = url.split("://") if url_parts.size() < 2: response_data.status = 400 response_data.status_text = "Bad Request - Invalid URL" return response_data var protocol = url_parts[0].to_lower() var use_ssl = (protocol == "https" or protocol == "gurt+ssl") var remaining_url = url_parts[1] var host_path = remaining_url.split("/", false, 1) var host = host_path[0] var path = "/" + (host_path[1] if host_path.size() > 1 else "") # Extract port if specified var port = 80 if use_ssl: port = 443 var host_parts = host.split(":") if host_parts.size() > 1: host = host_parts[0] port = host_parts[1].to_int() # Connect to host var error: Error if use_ssl: var tls_options = TLSOptions.client() error = http_client.connect_to_host(host, port, tls_options) else: error = http_client.connect_to_host(host, port) if error != OK: response_data.status = 0 response_data.status_text = "Connection Failed" return response_data # Wait for connection var timeout_count = 0 while http_client.get_status() == HTTPClient.STATUS_CONNECTING or http_client.get_status() == HTTPClient.STATUS_RESOLVING: http_client.poll() OS.delay_msec(10) timeout_count += 1 if timeout_count > 300: # 3 second timeout response_data.status = 0 response_data.status_text = "Connection Timeout" return response_data if http_client.get_status() != HTTPClient.STATUS_CONNECTED: response_data.status = 0 response_data.status_text = "Connection Failed" return response_data # Convert method string to HTTPClient.Method enum var http_method: HTTPClient.Method match method: "GET": http_method = HTTPClient.METHOD_GET "POST": http_method = HTTPClient.METHOD_POST "PUT": http_method = HTTPClient.METHOD_PUT "DELETE": http_method = HTTPClient.METHOD_DELETE "HEAD": http_method = HTTPClient.METHOD_HEAD "OPTIONS": http_method = HTTPClient.METHOD_OPTIONS "PATCH": http_method = HTTPClient.METHOD_PATCH _: http_method = HTTPClient.METHOD_GET # Make request error = http_client.request(http_method, path, headers, body) if error != OK: response_data.status = 0 response_data.status_text = "Request Failed" return response_data # Wait for response timeout_count = 0 while http_client.get_status() == HTTPClient.STATUS_REQUESTING: http_client.poll() OS.delay_msec(10) timeout_count += 1 if timeout_count > 1000: # 10 second timeout response_data.status = 0 response_data.status_text = "Request Timeout" return response_data if http_client.get_status() != HTTPClient.STATUS_BODY and http_client.get_status() != HTTPClient.STATUS_CONNECTED: response_data.status = 0 response_data.status_text = "Request Failed" return response_data # Get response if http_client.has_response(): response_data.status = http_client.get_response_code() # Get status text match response_data.status: 200: response_data.status_text = "OK" 201: response_data.status_text = "Created" 204: response_data.status_text = "No Content" 400: response_data.status_text = "Bad Request" 401: response_data.status_text = "Unauthorized" 403: response_data.status_text = "Forbidden" 404: response_data.status_text = "Not Found" 500: response_data.status_text = "Internal Server Error" _: response_data.status_text = "Unknown" # Get response headers var response_headers = http_client.get_response_headers_as_dictionary() response_data.headers = response_headers # Get response body var body_bytes = PackedByteArray() timeout_count = 0 while http_client.get_status() == HTTPClient.STATUS_BODY: http_client.poll() var chunk = http_client.read_response_body_chunk() if chunk.size() > 0: body_bytes.append_array(chunk) timeout_count = 0 else: OS.delay_msec(10) timeout_count += 1 if timeout_count > 1000: # 10 second timeout for body break response_data.body = body_bytes.get_string_from_utf8() http_client.close() return response_data