fix(mobile): clean up sockets when simulation starts

When the server URL is cleared, close the active WebSocket and cancel pending reconnects so simulation mode does not race with stale network state.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Gujiassh 2026-03-12 21:00:35 +09:00 committed by gujishh
parent 2a05378bd2
commit 59f44dcfe5
2 changed files with 63 additions and 0 deletions

View file

@ -109,6 +109,62 @@ describe('WsService', () => {
expect(ws.getStatus()).toBe('simulated');
ws.disconnect();
});
it('cleans up active sockets and pending reconnects when switching to simulation mode', () => {
const sockets: MockWebSocketInstance[] = [];
const OrigWebSocket = globalThis.WebSocket;
class MockWebSocket {
static OPEN = 1;
static CONNECTING = 0;
static CLOSED = 3;
readyState = MockWebSocket.OPEN;
onopen: (() => void) | null = null;
onclose: ((event: { code: number }) => void) | null = null;
onerror: (() => void) | null = null;
onmessage: (() => void) | null = null;
close = jest.fn((code?: number) => {
this.readyState = MockWebSocket.CLOSED;
this.onclose?.({ code: code ?? 1000 });
});
constructor(_url: string) {
sockets.push(this as unknown as MockWebSocketInstance);
}
}
type MockWebSocketInstance = {
onclose: ((event: { code: number }) => void) | null;
close: jest.Mock;
};
globalThis.WebSocket = MockWebSocket as any;
try {
const ws = createWsService();
ws.connect('http://localhost:3000');
expect(sockets).toHaveLength(1);
ws.connect('');
expect(sockets[0].close).toHaveBeenCalledWith(1000, 'switch to simulation');
expect(ws.getStatus()).toBe('simulated');
ws.disconnect();
const wsWithReconnect = createWsService();
wsWithReconnect.connect('http://localhost:3000');
expect(sockets).toHaveLength(2);
sockets[1].onclose?.({ code: 1006 });
wsWithReconnect.connect('');
jest.advanceTimersByTime(60_000);
expect(sockets).toHaveLength(2);
expect(wsWithReconnect.getStatus()).toBe('simulated');
wsWithReconnect.disconnect();
} finally {
globalThis.WebSocket = OrigWebSocket;
}
});
});
describe('subscribe and unsubscribe', () => {

View file

@ -22,6 +22,13 @@ class WsService {
this.reconnectAttempt = 0;
if (!url) {
this.clearReconnectTimer();
if (this.ws) {
const socket = this.ws;
this.ws = null;
socket.onclose = null;
socket.close(1000, 'switch to simulation');
}
this.handleStatusChange('simulated');
this.startSimulation();
return;