fix: 메인 채팅 WebSocket에 Wake Lock 적용 — 외부 접속 시 잦은 끊김 대응
사용자가 "어제 집에서는 괜찮았는데 지금 외부에서 연결하니깐 자꾸 끊어지네"라고 보고. 서버는 이미 30초마다 ping을 보내 프록시/OS 유휴 타임아웃(92초 패턴)을 피하는 방어 로직이 있고(server.ts wsPingInterval), 60초 무응답이면 서버가 직접 연결을 끊는다. 메인 채팅 페이지엔 이 ping에 응답할 JS 타이머를 살려두는 장치가 없었다. 음성통화 페이지(voice-call.js)는 정확히 같은 증상(폰 화면 꺼짐 → 탭 백그라운드 스로틀링 → 소켓이 서버 ping에 응답 못 함 → 끊김)을 07-09에 Wake Lock으로 이미 고쳤는데, 메인 채팅 쪽엔 이 조치가 빠져 있었다 — 집에서는 화면이 잘 안 꺼지니 덜 겪고, 외부에서 폰으로 쓸 때 화면이 자주 잠기니 더 자주 겪는 것과도 맞아떨어진다. voice-call.js와 같은 패턴을 그대로 적용: WS가 열릴 때 Wake Lock을 요청하고, visibilitychange로 탭이 다시 보일 때 재요청(Wake Lock은 탭이 숨겨지면 자동 해제되므로), WS가 닫히면 명시적으로 해제. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+31
-1
@@ -2,6 +2,7 @@
|
||||
const _DBG = false; // set true to enable streaming/editor debug logs
|
||||
const API = window.location.origin || 'http://127.0.0.1:18789';
|
||||
let ws = null;
|
||||
let wsWakeLock = null;
|
||||
let selectedJobId = null;
|
||||
let allJobs = [];
|
||||
let pollInterval = null;
|
||||
@@ -349,11 +350,36 @@ function updateAgentMode() {
|
||||
// ---- Chat ----
|
||||
|
||||
// ---- WebSocket ----
|
||||
// Screen Wake Lock keeps this main WS connection alive on mobile. Without it, the OS
|
||||
// screen-timeout (idle reading a reply, thinking before typing) turns the screen off, the
|
||||
// tab gets backgrounded, and the browser throttles/suspends it — the socket misses the
|
||||
// server's 30s pings, gets terminated after ~60s, and the client sees a "connection keeps
|
||||
// dropping" pattern that never shows up on a desktop/always-on connection. Same root cause
|
||||
// as the voice-call page's drop bug (fixed 2026-07-09 with the same pattern, see
|
||||
// voice-call.js) — added here 2026-08-10 after the same symptom showed up on the main chat
|
||||
// while used away from home wifi. A Wake Lock is auto-released whenever the tab loses
|
||||
// visibility, so it has to be re-requested once the tab is visible again.
|
||||
async function acquireWsWakeLock() {
|
||||
if (!('wakeLock' in navigator)) return;
|
||||
try {
|
||||
wsWakeLock = await navigator.wakeLock.request('screen');
|
||||
wsWakeLock.addEventListener('release', () => { wsWakeLock = null; });
|
||||
} catch (e) {
|
||||
console.warn('[app] wsWakeLock request failed:', e.message);
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN && wsWakeLock === null && document.visibilityState === 'visible') {
|
||||
acquireWsWakeLock();
|
||||
}
|
||||
});
|
||||
|
||||
function connectWS() {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const token = getAuthToken();
|
||||
const qs = token ? `?token=${encodeURIComponent(token)}` : '';
|
||||
ws = new WebSocket(`${proto}://${API.replace(/^https?:\/\//, '')}/ws${qs}`);
|
||||
ws.onopen = () => { acquireWsWakeLock(); };
|
||||
ws.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'jobs_update') {
|
||||
@@ -514,7 +540,11 @@ function connectWS() {
|
||||
if (agentId) pauseAgentExecution(agentId, msg.reason);
|
||||
}
|
||||
};
|
||||
ws.onclose = () => setTimeout(connectWS, 2000);
|
||||
ws.onclose = () => {
|
||||
try { wsWakeLock && wsWakeLock.release(); } catch {}
|
||||
wsWakeLock = null;
|
||||
setTimeout(connectWS, 2000);
|
||||
};
|
||||
ws.onerror = () => ws.close();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user