fix: harden service websocket reconnect flows
Stabilize the service console and callback-host websocket paths so idle disconnects and mid-task client drops no longer wedge task execution or spam repeated commands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -896,36 +896,66 @@ window.sgclawOnEval = sgclawOnEval;
|
||||
window.callBackJsToCpp = callBackJsToCpp;
|
||||
|
||||
document.getElementById('wi').textContent = SGCLAW_BROWSER_WS_URL;
|
||||
_log('Connecting to browser WebSocket\u2026');
|
||||
|
||||
const sgclawSocket = new WebSocket(SGCLAW_BROWSER_WS_URL);
|
||||
sgclawSocket.addEventListener('open', async () => {{
|
||||
document.getElementById('sd').classList.add('on');
|
||||
document.getElementById('stx').textContent = 'Connected';
|
||||
_log('<span class="ok">\u2713</span> WebSocket connected');
|
||||
_task('Connected to browser');
|
||||
sgclawSocket.send(JSON.stringify({{ type: 'register', role: 'web' }}));
|
||||
await sgclawReady();
|
||||
_log('<span class="ok">\u2713</span> Ready signal sent');
|
||||
_task('Ready \u2014 waiting for commands');
|
||||
}});
|
||||
let sgclawSocket = null;
|
||||
let sgclawReconnectTimer = null;
|
||||
let sgclawDeferredCommandLogged = false;
|
||||
|
||||
sgclawSocket.addEventListener('close', () => {{
|
||||
document.getElementById('sd').classList.remove('on');
|
||||
document.getElementById('stx').textContent = 'Disconnected';
|
||||
_log('<span class="er">\u2717</span> WebSocket disconnected');
|
||||
_task('Disconnected');
|
||||
}});
|
||||
|
||||
sgclawSocket.addEventListener('message', (event) => {{
|
||||
console.debug('sgclaw helper received browser frame', event.data);
|
||||
try {{
|
||||
var data = String(event.data || '');
|
||||
if (data.indexOf('@_@') !== -1) {{
|
||||
sgclawEmitCallback('callBackJsToCpp', {{ raw: data }});
|
||||
function connectSocket() {{
|
||||
if (sgclawSocket && (sgclawSocket.readyState === WebSocket.OPEN || sgclawSocket.readyState === WebSocket.CONNECTING)) {{
|
||||
return;
|
||||
}}
|
||||
_log('Connecting to browser WebSocket\u2026');
|
||||
document.getElementById('stx').textContent = 'Connecting…';
|
||||
_task('Connecting to browser');
|
||||
const socket = new WebSocket(SGCLAW_BROWSER_WS_URL);
|
||||
sgclawSocket = socket;
|
||||
socket.addEventListener('open', async () => {{
|
||||
if (sgclawSocket !== socket) {{
|
||||
return;
|
||||
}}
|
||||
}} catch (_e) {{}}
|
||||
}});
|
||||
if (sgclawReconnectTimer) {{
|
||||
clearTimeout(sgclawReconnectTimer);
|
||||
sgclawReconnectTimer = null;
|
||||
}}
|
||||
sgclawDeferredCommandLogged = false;
|
||||
document.getElementById('sd').classList.add('on');
|
||||
document.getElementById('stx').textContent = 'Connected';
|
||||
_log('<span class="ok">\u2713</span> WebSocket connected');
|
||||
_task('Connected to browser');
|
||||
socket.send(JSON.stringify({{ type: 'register', role: 'web' }}));
|
||||
await sgclawReady();
|
||||
_log('<span class="ok">\u2713</span> Ready signal sent');
|
||||
_task('Ready \u2014 waiting for commands');
|
||||
}});
|
||||
|
||||
socket.addEventListener('close', () => {{
|
||||
if (sgclawSocket !== socket) {{
|
||||
return;
|
||||
}}
|
||||
sgclawSocket = null;
|
||||
document.getElementById('sd').classList.remove('on');
|
||||
document.getElementById('stx').textContent = 'Disconnected';
|
||||
_log('<span class="er">\u2717</span> WebSocket disconnected');
|
||||
_task('Disconnected — reconnecting');
|
||||
if (!sgclawReconnectTimer) {{
|
||||
sgclawReconnectTimer = setTimeout(connectSocket, 1000);
|
||||
}}
|
||||
}});
|
||||
|
||||
socket.addEventListener('message', (event) => {{
|
||||
if (sgclawSocket !== socket) {{
|
||||
return;
|
||||
}}
|
||||
console.debug('sgclaw helper received browser frame', event.data);
|
||||
try {{
|
||||
var data = String(event.data || '');
|
||||
if (data.indexOf('@_@') !== -1) {{
|
||||
sgclawEmitCallback('callBackJsToCpp', {{ raw: data }});
|
||||
}}
|
||||
}} catch (_e) {{}}
|
||||
}});
|
||||
}}
|
||||
|
||||
async function sgclawPollCommands() {{
|
||||
try {{
|
||||
@@ -936,22 +966,29 @@ async function sgclawPollCommands() {{
|
||||
const envelope = await response.json();
|
||||
const command = envelope && envelope.command;
|
||||
if (!command || !command.action) {{
|
||||
sgclawDeferredCommandLogged = false;
|
||||
return;
|
||||
}}
|
||||
if (!sgclawSocket || sgclawSocket.readyState !== WebSocket.OPEN) {{
|
||||
if (!sgclawDeferredCommandLogged) {{
|
||||
_log('<span class="er">!</span> Browser connection lost — command deferred');
|
||||
sgclawDeferredCommandLogged = true;
|
||||
}}
|
||||
return;
|
||||
}}
|
||||
sgclawDeferredCommandLogged = false;
|
||||
_nc++;
|
||||
const args = Array.isArray(command.args) ? command.args : [];
|
||||
_lastCmd=Date.now();_setIdle(false);
|
||||
_log('<span class="a">\u2192</span> execute <span class="a">'+command.action+'</span>'+(args.length>1?' <span class="u">'+String(args[1]||'').substring(0,50)+'</span>':''));
|
||||
_task('Executing: '+command.action);
|
||||
if (sgclawSocket.readyState !== WebSocket.OPEN) {{
|
||||
return;
|
||||
}}
|
||||
sgclawSocket.send(JSON.stringify([window.location.href || SGCLAW_HELPER_URL, command.action, ...args]));
|
||||
await sgclawPostJson(SGCLAW_COMMAND_ACK_ENDPOINT, {{ type: 'command_ack' }});
|
||||
}} catch (_error) {{
|
||||
}}
|
||||
}}
|
||||
|
||||
connectSocket();
|
||||
setInterval(sgclawPollCommands, 250);
|
||||
_log('sgClaw Runtime Console initialized');
|
||||
</script>
|
||||
@@ -1110,6 +1147,11 @@ mod tests {
|
||||
assert!(html.contains("ws://127.0.0.1:12345"));
|
||||
assert!(html.contains(r#"JSON.stringify({ type: 'register', role: 'web' })"#));
|
||||
assert!(html.contains("sgclawReady"));
|
||||
assert!(html.contains("connectSocket()"));
|
||||
assert!(html.contains("setTimeout(connectSocket, 1000)"));
|
||||
assert!(html.contains("if (!sgclawSocket || sgclawSocket.readyState !== WebSocket.OPEN)"));
|
||||
assert!(html.contains("Browser connection lost — command deferred"));
|
||||
assert!(html.contains("sgclawSocket = null;"));
|
||||
assert!(html.contains("sgclawOnLoaded"));
|
||||
assert!(html.contains("sgclawOnClickProbe"));
|
||||
assert!(html.contains("sgclawOnClick"));
|
||||
|
||||
Reference in New Issue
Block a user