feat(webbridge): implement asynchronous WebSocket connection handling
This commit is contained in:
@@ -232,28 +232,46 @@ function WebBridge.wsConnect(wsUrl, handlers, opts)
|
||||
local receiveTimeout = opts.receiveTimeout or 30
|
||||
|
||||
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 not aok then
|
||||
if handlers.onError then handlers.onError(aerr) end
|
||||
os.sleep(reconnectDelay)
|
||||
else
|
||||
-- Wait for websocket_success or websocket_failure (filtered)
|
||||
local ws = nil
|
||||
while true do
|
||||
local event, url, param = os.pullEvent()
|
||||
if event == 'websocket_success' and url == wsUrl then
|
||||
ws = param
|
||||
break
|
||||
elseif event == 'websocket_failure' and url == wsUrl then
|
||||
if handlers.onError then handlers.onError(param) end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if ok and ws then
|
||||
if ws then
|
||||
if handlers.onConnect then
|
||||
handlers.onConnect(ws)
|
||||
end
|
||||
|
||||
-- Message receive loop
|
||||
-- Event-based message loop — uses os.pullEvent() with awareness
|
||||
-- of websocket_message, websocket_closed, and timer events.
|
||||
-- Unlike ws.receive(), this loop does NOT swallow unrelated events
|
||||
-- because CC:Tweaked's parallel scheduler delivers each event to
|
||||
-- all coroutines whose filter matches (or filter is nil).
|
||||
local keepaliveTimer = os.startTimer(receiveTimeout)
|
||||
|
||||
while true do
|
||||
local rok, msg = pcall(ws.receive, receiveTimeout)
|
||||
local event, p1, p2, p3 = os.pullEvent()
|
||||
|
||||
if not rok then
|
||||
-- Connection error
|
||||
break
|
||||
end
|
||||
if event == 'websocket_message' and p1 == wsUrl then
|
||||
-- Reset keepalive timer on each message
|
||||
os.cancelTimer(keepaliveTimer)
|
||||
keepaliveTimer = os.startTimer(receiveTimeout)
|
||||
|
||||
if not msg then
|
||||
-- Timeout — send keepalive ping
|
||||
local pok = pcall(ws.send, textutils.serialiseJSON({ type = 'ping' }))
|
||||
if not pok then break end
|
||||
else
|
||||
local data = textutils.unserialiseJSON(msg)
|
||||
local data = textutils.unserialiseJSON(p2)
|
||||
if data then
|
||||
if data.type == 'pong' then
|
||||
-- Keepalive response, handled
|
||||
@@ -261,6 +279,15 @@ function WebBridge.wsConnect(wsUrl, handlers, opts)
|
||||
handlers.onMessage(ws, data)
|
||||
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
|
||||
|
||||
@@ -269,15 +296,12 @@ function WebBridge.wsConnect(wsUrl, handlers, opts)
|
||||
if handlers.onDisconnect then
|
||||
handlers.onDisconnect()
|
||||
end
|
||||
else
|
||||
if handlers.onError then
|
||||
handlers.onError(ws) -- ws contains the error string on failure
|
||||
end
|
||||
end
|
||||
|
||||
os.sleep(reconnectDelay)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
-- HTTP poll→process→ack cycle (WebSocket fallback transport)
|
||||
|
||||
Reference in New Issue
Block a user