225 lines
6.8 KiB
JavaScript
Executable File
225 lines
6.8 KiB
JavaScript
Executable File
// WebService.js - robust WebSocket wrapper (drop-in replacement)
|
|
(function () {
|
|
const startTime = Date.now();
|
|
console.log("Meine Document-Location:", document.location);
|
|
|
|
const socketUrl =
|
|
(location.protocol === "https:" ? "wss://" : "ws://") +
|
|
window.location.host +
|
|
"/echo";
|
|
|
|
console.log("WebSocket URL:", socketUrl);
|
|
|
|
// Expose global socket object immediately so other scripts never see "socket is not defined"
|
|
window.socket = {
|
|
// internal
|
|
_ws: null,
|
|
_queue: [],
|
|
_retry: 0,
|
|
_pingIntervalId: null,
|
|
_lastPingRequest: null,
|
|
_connectedOnce: false,
|
|
|
|
// user hooks (can be assigned by UI code)
|
|
onMessage: null,
|
|
onOpen: null,
|
|
onClose: null,
|
|
|
|
// ------ API: send / sendCommand (legacy & recommended) ------
|
|
// raw send (backwards compatible)
|
|
send(msg) {
|
|
this._enqueueOrSend(msg);
|
|
},
|
|
|
|
// preferred semantic method for commands
|
|
sendCommand(cmd) {
|
|
// you can add validation/normalization here
|
|
this._enqueueOrSend(cmd);
|
|
},
|
|
|
|
// convenience robot command
|
|
moveZ(value, feed = 100) {
|
|
// positive value moves up; negative moves down depending on your G-code semantics
|
|
this.sendCommand(`G91 G1 Z${value} F${feed}`);
|
|
},
|
|
|
|
// manual control to connect/disconnect
|
|
connect() {
|
|
_connect(this);
|
|
},
|
|
|
|
disconnect() {
|
|
_disconnect(this);
|
|
},
|
|
|
|
isOpen() {
|
|
return this._ws && this._ws.readyState === WebSocket.OPEN;
|
|
},
|
|
|
|
// internal helper: enqueue if not open, otherwise send immediately
|
|
_enqueueOrSend(msg) {
|
|
try {
|
|
if (this._ws && this._ws.readyState === WebSocket.OPEN) {
|
|
this._ws.send(msg);
|
|
console.log("WS SEND:", msg);
|
|
} else {
|
|
// queue to be flushed on open
|
|
console.warn("WS not ready → queueing:", msg);
|
|
this._queue.push(msg);
|
|
}
|
|
} catch (e) {
|
|
console.error("WS send failed:", e, " (queued )");
|
|
this._queue.push(msg);
|
|
}
|
|
}
|
|
};
|
|
|
|
// ---------- internal functions ----------
|
|
function _flushQueue(sock) {
|
|
while (sock._queue.length) {
|
|
const m = sock._queue.shift();
|
|
try {
|
|
if (sock._ws && sock._ws.readyState === WebSocket.OPEN) {
|
|
sock._ws.send(m);
|
|
console.log("Flushed queued msg:", m);
|
|
} else {
|
|
// push back and stop flushing
|
|
sock._queue.unshift(m);
|
|
break;
|
|
}
|
|
} catch (e) {
|
|
console.warn("Failed flushing queued msg, requeueing", e);
|
|
sock._queue.unshift(m);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function _startPing(sock) {
|
|
if (sock._pingIntervalId) return;
|
|
sock._pingIntervalId = setInterval(() => {
|
|
if (sock._ws && sock._ws.readyState === WebSocket.OPEN) {
|
|
sock._lastPingRequest = Date.now();
|
|
sock._enqueueOrSend("Ping");
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
function _stopPing(sock) {
|
|
if (sock._pingIntervalId) {
|
|
clearInterval(sock._pingIntervalId);
|
|
sock._pingIntervalId = null;
|
|
}
|
|
}
|
|
|
|
function _connect(sock) {
|
|
// prevent duplicate connects
|
|
if (sock._ws && (sock._ws.readyState === WebSocket.CONNECTING || sock._ws.readyState === WebSocket.OPEN)) {
|
|
console.log("WebSocket already connecting/open - skipping connect()");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log("WebSocket: connecting to", socketUrl);
|
|
const ws = new WebSocket(socketUrl);
|
|
sock._ws = ws;
|
|
|
|
ws.onopen = () => {
|
|
console.log("Connected to WS Server");
|
|
sock._connectedOnce = true;
|
|
sock._retry = 0; // reset retries
|
|
_flushQueue(sock);
|
|
_startPing(sock);
|
|
|
|
if (typeof sock.onOpen === "function") {
|
|
try { sock.onOpen(); } catch (e) { console.error("onOpen handler error", e); }
|
|
}
|
|
};
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = event.data ? event.data.toString() : "";
|
|
// built-in handling (keeps backward compatible behaviour)
|
|
if (data === "Ping") {
|
|
if (sock._lastPingRequest) {
|
|
console.log("Ping:", Date.now() - sock._lastPingRequest, "ms");
|
|
} else {
|
|
console.log("Ping (no timestamp)");
|
|
}
|
|
} else if (data.includes("position")) {
|
|
console.log("Position:", data);
|
|
} else if (data.includes("XYZ__FShow__XYZ")) {
|
|
const content = data.split("XYZ__FShow__XYZ")[1] || "";
|
|
console.log("FShow content received, length", content.length);
|
|
const el = document.querySelector("textarea#GCodeWindow.editor-look");
|
|
if (el) el.value = content;
|
|
} else {
|
|
console.log("DATA:", Date.now() - startTime, data);
|
|
}
|
|
|
|
// call user onMessage hook if present
|
|
if (typeof sock.onMessage === "function") {
|
|
try { sock.onMessage(data); } catch (e) { console.error("onMessage handler error", e); }
|
|
}
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
const reason = event.reason || event.code || "closed";
|
|
console.log(event.wasClean ? "Disconnected" : "Connection break: " + reason);
|
|
_stopPing(sock);
|
|
|
|
if (typeof sock.onClose === "function") {
|
|
try { sock.onClose(event); } catch (e) { console.error("onClose handler error", e); }
|
|
}
|
|
|
|
// exponential backoff with small jitter
|
|
sock._retry = (sock._retry || 0) + 1;
|
|
const base = 1000;
|
|
const delay = Math.min(30000, base * Math.pow(2, sock._retry - 1)) + Math.floor(Math.random() * 300);
|
|
console.log("Reconnect in", delay, "ms (attempt", sock._retry, ")");
|
|
setTimeout(() => _connect(sock), delay);
|
|
};
|
|
|
|
ws.onerror = (err) => {
|
|
// some browsers call onerror right before onclose; log only
|
|
console.error("WebSocket error:", err && (err.message || err));
|
|
};
|
|
} catch (e) {
|
|
console.error("WebSocket create failed", e);
|
|
// schedule reconnect
|
|
sock._retry = (sock._retry || 0) + 1;
|
|
const delay = Math.min(30000, 1000 * Math.pow(2, sock._retry - 1)) + Math.floor(Math.random() * 300);
|
|
setTimeout(() => _connect(sock), delay);
|
|
}
|
|
}
|
|
|
|
function _disconnect(sock) {
|
|
try {
|
|
_stopPing(sock);
|
|
if (sock._ws) {
|
|
try { sock._ws.close(); } catch (e) { /* ignore */ }
|
|
sock._ws = null;
|
|
}
|
|
} catch (e) {
|
|
console.error("Error during disconnect", e);
|
|
}
|
|
}
|
|
|
|
// Kick off initial connection
|
|
window.socket.connect();
|
|
|
|
// Expose some helpful debug tools in the console
|
|
window.__socket_debug = {
|
|
getQueue: () => window.socket._queue.slice(),
|
|
getState: () => (window.socket._ws ? window.socket._ws.readyState : null),
|
|
flush: () => _flushQueue(window.socket),
|
|
reconnect: () => {
|
|
_disconnect(window.socket);
|
|
window.socket._retry = 0;
|
|
window.socket.connect();
|
|
}
|
|
};
|
|
|
|
// Optional: if you want a simple rate limiter later, you can add code here.
|
|
// Keep GamePad traffic in mind — don't flood the queue on reconnect.
|
|
})();
|