diff --git a/apis/webbridge.lua b/apis/webbridge.lua index 4a8ed17..390a29c 100644 --- a/apis/webbridge.lua +++ b/apis/webbridge.lua @@ -232,50 +232,74 @@ function WebBridge.wsConnect(wsUrl, handlers, opts) local receiveTimeout = opts.receiveTimeout or 30 while true do - local ok, ws = pcall(http.websocket, wsUrl) - - if ok and ws then - if handlers.onConnect then - handlers.onConnect(ws) - end - - -- Message receive loop + -- 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 rok, msg = pcall(ws.receive, receiveTimeout) - - if not rok then - -- Connection error + 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 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) - if data then - if data.type == 'pong' then - -- Keepalive response, handled - elseif handlers.onMessage then - handlers.onMessage(ws, data) + if ws then + if handlers.onConnect then + handlers.onConnect(ws) + end + + -- 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 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 + + 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 + + pcall(ws.close) + + if handlers.onDisconnect then + handlers.onDisconnect() + end end - pcall(ws.close) - - if handlers.onDisconnect then - handlers.onDisconnect() - end - else - if handlers.onError then - handlers.onError(ws) -- ws contains the error string on failure - end + os.sleep(reconnectDelay) end - - os.sleep(reconnectDelay) end end