Wrapper für WSS

This commit is contained in:
ChK
2026-02-05 18:50:24 +01:00
parent c077bf57c6
commit a634998555
4 changed files with 259 additions and 113 deletions

View File

@@ -1,75 +1,224 @@
var startTime = Date.now() ;
console.log("Meine Document-Location: " + document.location);
var lastPingRequest;
// WebService.js - robust WebSocket wrapper (drop-in replacement)
(function () {
const startTime = Date.now();
console.log("Meine Document-Location:", document.location);
// Use explicit '/echo' path and a small wrapper to guard sends from other modules
var socketUrl = (location.protocol === 'https:' ? 'wss://' : 'ws://') + window.location.host + '/echo';
const socketUrl =
(location.protocol === "https:" ? "wss://" : "ws://") +
window.location.host +
"/echo";
console.log("WebSocket URL: " + socketUrl);
console.log("WebSocket URL:", socketUrl);
// socket wrapper keeps the same global identifier but guards sends when underlying ws is not open
var socket = {
// Expose global socket object immediately so other scripts never see "socket is not defined"
window.socket = {
// internal
_ws: null,
send: function(msg) {
_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('Sent message with WebSocket.OPEN:', msg);
this._ws.send(msg);
console.log("WS SEND:", msg);
} else {
console.warn('Socket not open, dropping message:', msg);
// 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);
}
}
};
};
let pingIntervalId = null;
let reconnectDelay = 1000;
function connect() {
const ws = new WebSocket(socketUrl);
socket._ws = ws;
ws.onopen = () => {
console.log('Connected to WS Server');
reconnectDelay = 1000;
if (pingIntervalId) clearInterval(pingIntervalId);
pingIntervalId = setInterval(() => {
if (socket._ws && socket._ws.readyState === WebSocket.OPEN) {
lastPingRequest = Date.now();
socket.send('Ping');
}
}, 5000);
};
ws.onclose = (event) => {
console.log((event.wasClean) ? 'Disconnected' : 'Connection break: ' + (event.reason || event.code));
if (pingIntervalId) { clearInterval(pingIntervalId); pingIntervalId = null; }
setTimeout(() => {
console.log('Reconnecting...');
connect();
}, reconnectDelay);
reconnectDelay = Math.min(30000, reconnectDelay * 2);
};
ws.onmessage = (event) => {
if(event.data == 'Ping'){
console.log('Ping: ' + (Date.now() - lastPingRequest).toString() + ' ms');
// ---------- 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;
}
else if(event.data.toString().includes('position')){
console.log('Position: ' + event.data);
} 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); }
}
else if(event.data.toString().includes('XYZ__FShow__XYZ')){
const content = event.data.toString().split('XYZ__FShow__XYZ')[1];
console.log('File Content: ' + content);
const el = document.querySelector('textarea#GCodeWindow.editor-look');
if (el) el.value = content;
};
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);
}
else{
console.log('DATA SinceStartup: ' + (Date.now() - startTime).toString() +': ', event.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.onerror = (err) => console.error('WebSocket error:', err || err.message);
}
ws.onclose = (event) => {
const reason = event.reason || event.code || "closed";
console.log(event.wasClean ? "Disconnected" : "Connection break: " + reason);
_stopPing(sock);
connect();
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.
})();