Skip to content

Networking & WebSockets

Vulpis features a fully asynchronous, non-blocking networking stack. This ensures that making HTTP requests or listening to real-time WebSocket streams will never freeze or stutter your user interface.

The engine provides three layers of networking APIs:

  1. HTTP Client: For standard REST requests (GET, POST, etc.).
  2. WebSocket Wrapper (ws.lua): A high-level, developer-friendly Lua module for real-time data.
  3. Native WebSockets: The raw C++ bindings powering the wrapper.

1. HTTP Client (vulpis.fetch)

The vulpis.fetch API allows you to make asynchronous HTTP requests. It supports all standard HTTP methods, custom headers, and timeouts.

Because the request runs on a background C++ thread, you must provide a callback function to handle the response once it completes.

Function Signature

lua
vulpis.fetch(url, [options], callback)

Parameters

ParameterTypeRequiredDescription
urlstringYesThe destination URL (must include http:// or https://).
optionstableNoConfiguration table (method, headers, body, timeout).
callbackfunctionYesFunction called when the request finishes or fails.

The options Table

FieldTypeDefaultDescription
methodstring"GET""GET", "POST", "PUT", "DELETE", or "PATCH".
timeoutinteger10000Max time to wait in milliseconds (default is 10 seconds).
bodystring""The payload for POST/PUT requests (usually a JSON string).
headerstable{}Key-value pairs for HTTP headers.

The Response Object

The callback receives a single res table containing:

  • res.status (integer): The HTTP status code (e.g., 200 for OK, 404 for Not Found).
  • res.body (string): The raw string response from the server.
  • res.error (string): An error message if the request failed at the network level (e.g., "Connection Refused").

Example: Fetching & Posting Data

This example demonstrates how to fetch data and how to send a JSON payload.

lua
local el = require("utils.core.elements")
local json = require("utils.core.json")

function App()
	local apiStatus = useState("api_status", "Idle")
	local apiData = useState("api_data", "")

	return el.VBox({
		style = { padding = 20, gap = 15, BGColor = "#1e1e1e" },
		children = {
			el.Text("Network Status: " .. apiStatus, { color = "#4CAF50" }, { allowSelection = true }),
			el.Text(apiData, { color = "#A1A1AA" }),

			el.HBox({
				style = { gap = 10 },
				children = {
					-- Example 1: Standard GET Request
					el.Button({
						text = "GET Request",
						onClick = function()
							setState("api_status", "Fetching...")
							vulpis.fetch("https://jsonplaceholder.typicode.com/todos/1", function(res)
								if res.status == 200 then
									setState("api_status", "Success")
									setState("api_data", res.body)
								else
									setState("api_status", "Error: " .. res.error)
								end
							end)
						end,
					}),

					-- Example 2: POST Request with JSON
					el.Button({
						text = "POST Request",
						style = { BGColor = "#2196F3" },
						onClick = function()
							setState("api_status", "Sending...")

							local payload = json.encode({ title = "Vulpis Post", userId = 1 })

							vulpis.fetch("https://jsonplaceholder.typicode.com/posts", {
								method = "POST",
								headers = { ["Content-Type"] = "application/json" },
								body = payload,
							}, function(res)
								setState("api_status", "POST returned Status: " .. tostring(res.status))
								setState("api_data", res.body)
							end)
						end,
					}),
				},
			}),
		},
	})
end

2. WebSocket Wrapper (ws.lua)

For real-time, bi-directional communication (like chat apps or live game servers), you should use the utils.core.ws wrapper. It provides an object-oriented interface over the raw C++ bindings and automatically encodes Lua tables into JSON when sending messages.

Setup

lua
local ws = require("utils.core.ws")

ws.connect(url, onEvent)

Creates a new WebSocket connection.

  • url: The WebSocket endpoint (e.g., "wss://echo.websocket.events").
  • onEvent: A callback function that triggers whenever the connection opens, receives a message, encounters an error, or closes.

Returns: A Connection object.

The Connection Object

  • conn:send(message): Sends data to the server. If message is a Lua table, it is automatically converted to a JSON string.
  • conn:close(): Safely terminates the connection.

The Event Object

The onEvent callback receives a table with the following structure:

  • event.type: A string representing the event state ("open", "message", "error", "close").
  • event.data: The payload or error message string.

Example: Live Chat / Echo Client

lua
local el = require("utils.core.elements")
local ws = require("utils.core.ws")

local myConnection = nil

function App()
	local serverStatus = useState("ws_status", "Disconnected")

	return el.VBox({
		style = { gap = 20, padding = 20 },
		children = {

			el.Text("Server says: " .. serverStatus),

			el.Button({
				text = "1. Connect",
				onClick = function()
					setState("ws_status", "Connecting...")

					-- Open the connection
					myConnection = ws.connect("wss://echo.websocket.org", function(event)
						-- The server will trigger this function whenever something happens
						if event.type == "open" then
							setState("ws_status", "Connected! Ready to send.")
						elseif event.type == "message" then
							-- We got a reply from the server!
							setState("ws_status", "Message received: " .. event.data)
						end
					end)
				end,
			}),

			el.Button({
				text = "2. Send 'Hello'",
				onClick = function()
					-- Make sure we are actually connected first!
					if myConnection then
						myConnection:send("Hello from Vulpis!")
						setState("ws_status", "Message sent, waiting for reply...")
					else
						setState("ws_status", "You need to connect first!")
					end
				end,
			}),
		},
	})
end

3. Native WebSockets (C++ Bindings)

If you are building your own custom networking architecture and want to bypass the ws.lua wrapper, you can interface directly with the native C++ endpoints exposed in the vulpis namespace.

These functions manage connections using raw integer IDs.

FunctionDescription
vulpis.wsConnect(url, callback)Opens a connection and returns an integer ID. The callback receives a table with type and data.
vulpis.wsSend(id, string_msg)Sends a raw string to the specified connection ID. Returns true on success.
vulpis.wsClose(id)Terminates the specified connection ID.

Example: Raw Binding Usage

lua
-- 1. Connect and store the raw ID
local connectionId = vulpis.wsConnect("wss://[example.com/socket](https://example.com/socket)", function(ev)
    if ev.type == "message" then
        print("Raw payload received: " .. ev.data)
    end
end)

-- 2. Send a raw string (You must handle JSON encoding manually)
local success = vulpis.wsSend(connectionId, '{"auth": "token_123"}')

-- 3. Close the socket
vulpis.wsClose(connectionId)