Initial commit: Socktop WebTerm with k3s deployment
- Multi-architecture Docker image (ARM64 + AMD64) - Kubernetes manifests for 3-replica deployment - Traefik ingress configuration - NGINX Proxy Manager integration - ConfigMap-based configuration - Automated build and deployment scripts - Session monitoring tools
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Terminado WebSocket Addon for xterm.js 5.x
|
||||
*
|
||||
* This addon handles the Terminado protocol for xterm.js, allowing
|
||||
* bidirectional communication over WebSocket with a backend PTY process.
|
||||
*
|
||||
* The Terminado protocol uses JSON arrays:
|
||||
* - ["stdin", data] - send input to the terminal
|
||||
* - ["stdout", data] - receive output from the terminal
|
||||
* - ["set_size", rows, cols] - notify backend of terminal size changes
|
||||
*/
|
||||
|
||||
class TerminadoAddon {
|
||||
constructor() {
|
||||
this._disposables = [];
|
||||
this._socket = null;
|
||||
this._bidirectional = true;
|
||||
this._buffered = true;
|
||||
this._attachSocketBuffer = '';
|
||||
this._flushTimeout = null;
|
||||
}
|
||||
|
||||
activate(terminal) {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
this._disposables.length = 0;
|
||||
|
||||
if (this._flushTimeout) {
|
||||
clearTimeout(this._flushTimeout);
|
||||
this._flushTimeout = null;
|
||||
}
|
||||
|
||||
if (this._socket) {
|
||||
this.detach();
|
||||
}
|
||||
}
|
||||
|
||||
attach(socket, bidirectional = true, buffered = true) {
|
||||
if (this._socket) {
|
||||
this.detach();
|
||||
}
|
||||
|
||||
this._socket = socket;
|
||||
this._bidirectional = bidirectional !== false;
|
||||
this._buffered = buffered !== false;
|
||||
|
||||
// Handle incoming messages from the websocket
|
||||
this._messageHandler = (ev) => {
|
||||
try {
|
||||
const data = JSON.parse(ev.data);
|
||||
if (Array.isArray(data) && data[0] === 'stdout') {
|
||||
const output = data[1];
|
||||
if (this._buffered) {
|
||||
this._pushToBuffer(output);
|
||||
} else {
|
||||
this._terminal.write(output);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error handling terminado message:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle terminal input (user typing)
|
||||
if (this._bidirectional) {
|
||||
const dataDisposable = this._terminal.onData((data) => {
|
||||
this._sendData(data);
|
||||
});
|
||||
this._disposables.push(dataDisposable);
|
||||
}
|
||||
|
||||
// Handle terminal resize events
|
||||
const resizeDisposable = this._terminal.onResize((size) => {
|
||||
this._setSize(size);
|
||||
});
|
||||
this._disposables.push(resizeDisposable);
|
||||
|
||||
// Handle socket close/error
|
||||
this._closeHandler = () => this.detach();
|
||||
this._errorHandler = () => this.detach();
|
||||
|
||||
socket.addEventListener('message', this._messageHandler);
|
||||
socket.addEventListener('close', this._closeHandler);
|
||||
socket.addEventListener('error', this._errorHandler);
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (!this._socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._messageHandler) {
|
||||
this._socket.removeEventListener('message', this._messageHandler);
|
||||
this._messageHandler = null;
|
||||
}
|
||||
|
||||
if (this._closeHandler) {
|
||||
this._socket.removeEventListener('close', this._closeHandler);
|
||||
this._closeHandler = null;
|
||||
}
|
||||
|
||||
if (this._errorHandler) {
|
||||
this._socket.removeEventListener('error', this._errorHandler);
|
||||
this._errorHandler = null;
|
||||
}
|
||||
|
||||
this._disposables.forEach(d => d.dispose());
|
||||
this._disposables.length = 0;
|
||||
|
||||
this._socket = null;
|
||||
}
|
||||
|
||||
_sendData(data) {
|
||||
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this._socket.send(JSON.stringify(['stdin', data]));
|
||||
} catch (err) {
|
||||
console.error('Error sending data to terminal:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setSize(size) {
|
||||
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this._socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
} catch (err) {
|
||||
console.error('Error sending terminal size:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pushToBuffer(data) {
|
||||
if (this._attachSocketBuffer) {
|
||||
this._attachSocketBuffer += data;
|
||||
} else {
|
||||
this._attachSocketBuffer = data;
|
||||
if (this._flushTimeout) {
|
||||
clearTimeout(this._flushTimeout);
|
||||
}
|
||||
this._flushTimeout = setTimeout(() => this._flushBuffer(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
_flushBuffer() {
|
||||
if (this._attachSocketBuffer && this._terminal) {
|
||||
this._terminal.write(this._attachSocketBuffer);
|
||||
this._attachSocketBuffer = '';
|
||||
}
|
||||
this._flushTimeout = null;
|
||||
}
|
||||
|
||||
// Public method to manually send size
|
||||
sendSize(rows, cols) {
|
||||
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this._socket.send(JSON.stringify(['set_size', rows, cols]));
|
||||
} catch (err) {
|
||||
console.error('Error sending manual terminal size:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public method to manually send command
|
||||
sendCommand(command) {
|
||||
if (this._socket && this._socket.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this._socket.send(JSON.stringify(['stdin', command]));
|
||||
} catch (err) {
|
||||
console.error('Error sending command:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in browsers
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = TerminadoAddon;
|
||||
}
|
||||
Reference in New Issue
Block a user