Update public/js/ws.js
Browse files- public/js/ws.js +43 -19
public/js/ws.js
CHANGED
|
@@ -50,13 +50,51 @@ function emit(type, data) {
|
|
| 50 |
listeners.get('*')?.forEach(fn => fn({ type, ...data }));
|
| 51 |
}
|
| 52 |
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) return;
|
| 55 |
|
| 56 |
ws = new WebSocket(WS_URL);
|
| 57 |
|
| 58 |
ws.onopen = () => {
|
| 59 |
reconnectDelay = RECONNECT_DELAY_MS;
|
|
|
|
| 60 |
emit('ws:connected', {});
|
| 61 |
};
|
| 62 |
|
|
@@ -64,8 +102,9 @@ function connect() {
|
|
| 64 |
let msg;
|
| 65 |
try { msg = JSON.parse(event.data); } catch { return; }
|
| 66 |
if (!msg?.type) return;
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
// Resolve pending request callbacks
|
| 69 |
if (msg._reqId && pendingCallbacks.has(msg._reqId)) {
|
| 70 |
const cb = pendingCallbacks.get(msg._reqId);
|
| 71 |
pendingCallbacks.delete(msg._reqId);
|
|
@@ -79,6 +118,7 @@ function connect() {
|
|
| 79 |
};
|
| 80 |
|
| 81 |
ws.onclose = () => {
|
|
|
|
| 82 |
emit('ws:disconnected', {});
|
| 83 |
scheduleReconnect();
|
| 84 |
};
|
|
@@ -88,22 +128,6 @@ function connect() {
|
|
| 88 |
};
|
| 89 |
}
|
| 90 |
|
| 91 |
-
function scheduleReconnect() {
|
| 92 |
-
clearTimeout(reconnectTimer);
|
| 93 |
-
reconnectTimer = setTimeout(() => {
|
| 94 |
-
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT_DELAY);
|
| 95 |
-
connect();
|
| 96 |
-
}, reconnectDelay);
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
export function getReadyState() {
|
| 100 |
-
return ws?.readyState ?? WebSocket.CLOSED;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
export function isConnected() {
|
| 104 |
-
return ws?.readyState === WebSocket.OPEN;
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
// Boot
|
| 108 |
-
|
| 109 |
window.ws = { send, request, on, off, isConnected, getReadyState };
|
|
|
|
| 50 |
listeners.get('*')?.forEach(fn => fn({ type, ...data }));
|
| 51 |
}
|
| 52 |
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
function scheduleReconnect() {
|
| 56 |
+
clearTimeout(reconnectTimer);
|
| 57 |
+
reconnectTimer = setTimeout(() => {
|
| 58 |
+
reconnectDelay = Math.min(reconnectDelay * 1.5, MAX_RECONNECT_DELAY);
|
| 59 |
+
connectWithPing();
|
| 60 |
+
}, reconnectDelay);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
export function getReadyState() {
|
| 64 |
+
return ws?.readyState ?? WebSocket.CLOSED;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export function isConnected() {
|
| 68 |
+
return ws?.readyState === WebSocket.OPEN;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// ββ Ping keepalive ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 72 |
+
// Prevents the WebSocket from being closed by proxies/servers during long
|
| 73 |
+
// operations like image/video generation (which can take 30-60+ seconds).
|
| 74 |
+
let pingInterval = null;
|
| 75 |
+
|
| 76 |
+
function startPing() {
|
| 77 |
+
stopPing();
|
| 78 |
+
pingInterval = setInterval(() => {
|
| 79 |
+
if (ws?.readyState === WebSocket.OPEN) {
|
| 80 |
+
try { ws.send(JSON.stringify({ type: 'ping' })); } catch {}
|
| 81 |
+
}
|
| 82 |
+
}, 20000); // every 20 seconds
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
function stopPing() {
|
| 86 |
+
if (pingInterval) { clearInterval(pingInterval); pingInterval = null; }
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// Patch connect to start/stop ping
|
| 90 |
+
function connectWithPing() {
|
| 91 |
if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) return;
|
| 92 |
|
| 93 |
ws = new WebSocket(WS_URL);
|
| 94 |
|
| 95 |
ws.onopen = () => {
|
| 96 |
reconnectDelay = RECONNECT_DELAY_MS;
|
| 97 |
+
startPing();
|
| 98 |
emit('ws:connected', {});
|
| 99 |
};
|
| 100 |
|
|
|
|
| 102 |
let msg;
|
| 103 |
try { msg = JSON.parse(event.data); } catch { return; }
|
| 104 |
if (!msg?.type) return;
|
| 105 |
+
// Silently swallow pong responses
|
| 106 |
+
if (msg.type === 'pong') return;
|
| 107 |
|
|
|
|
| 108 |
if (msg._reqId && pendingCallbacks.has(msg._reqId)) {
|
| 109 |
const cb = pendingCallbacks.get(msg._reqId);
|
| 110 |
pendingCallbacks.delete(msg._reqId);
|
|
|
|
| 118 |
};
|
| 119 |
|
| 120 |
ws.onclose = () => {
|
| 121 |
+
stopPing();
|
| 122 |
emit('ws:disconnected', {});
|
| 123 |
scheduleReconnect();
|
| 124 |
};
|
|
|
|
| 128 |
};
|
| 129 |
}
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
// Boot
|
| 132 |
+
connectWithPing();
|
| 133 |
window.ws = { send, request, on, off, isConnected, getReadyState };
|