feat(webbridge): implement asynchronous WebSocket connection handling

This commit is contained in:
MayaTheShy
2026-03-29 00:35:23 -04:00
parent 63e110f2ee
commit caf82ba81d

View File

@@ -232,50 +232,74 @@ function WebBridge.wsConnect(wsUrl, handlers, opts)
local receiveTimeout = opts.receiveTimeout or 30 local receiveTimeout = opts.receiveTimeout or 30
while true do while true do
local ok, ws = pcall(http.websocket, wsUrl) -- Use async API to avoid unfiltered os.pullEvent() in http.websocket()
local aok, aerr = http.websocketAsync(wsUrl)
if ok and ws then if not aok then
if handlers.onConnect then if handlers.onError then handlers.onError(aerr) end
handlers.onConnect(ws) os.sleep(reconnectDelay)
end else
-- Wait for websocket_success or websocket_failure (filtered)
-- Message receive loop local ws = nil
while true do while true do
local rok, msg = pcall(ws.receive, receiveTimeout) local event, url, param = os.pullEvent()
if event == 'websocket_success' and url == wsUrl then
if not rok then ws = param
-- Connection error break
elseif event == 'websocket_failure' and url == wsUrl then
if handlers.onError then handlers.onError(param) end
break break
end end
end
if not msg then if ws then
-- Timeout — send keepalive ping if handlers.onConnect then
local pok = pcall(ws.send, textutils.serialiseJSON({ type = 'ping' })) handlers.onConnect(ws)
if not pok then break end end
else
local data = textutils.unserialiseJSON(msg) -- Event-based message loop — uses os.pullEvent() with awareness
if data then -- of websocket_message, websocket_closed, and timer events.
if data.type == 'pong' then -- Unlike ws.receive(), this loop does NOT swallow unrelated events
-- Keepalive response, handled -- because CC:Tweaked's parallel scheduler delivers each event to
elseif handlers.onMessage then -- all coroutines whose filter matches (or filter is nil).
handlers.onMessage(ws, data) local keepaliveTimer = os.startTimer(receiveTimeout)
while true do
local event, p1, p2, p3 = os.pullEvent()
if event == 'websocket_message' and p1 == wsUrl then
-- Reset keepalive timer on each message
os.cancelTimer(keepaliveTimer)
keepaliveTimer = os.startTimer(receiveTimeout)
local data = textutils.unserialiseJSON(p2)
if data then
if data.type == 'pong' then
-- Keepalive response, handled
elseif handlers.onMessage then
handlers.onMessage(ws, data)
end
end end
elseif event == 'websocket_closed' and p1 == wsUrl then
break
elseif event == 'timer' and p1 == keepaliveTimer then
-- No message received within timeout — send keepalive ping
local pok = pcall(ws.send, textutils.serialiseJSON({ type = 'ping' }))
if not pok then break end
keepaliveTimer = os.startTimer(receiveTimeout)
end end
end end
pcall(ws.close)
if handlers.onDisconnect then
handlers.onDisconnect()
end
end end
pcall(ws.close) os.sleep(reconnectDelay)
if handlers.onDisconnect then
handlers.onDisconnect()
end
else
if handlers.onError then
handlers.onError(ws) -- ws contains the error string on failure
end
end end
os.sleep(reconnectDelay)
end end
end end