🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

shell-cluster

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shell-cluster - npm Package Compare versions

Comparing version
1.0.13
to
1.0.14
+1
-1
package.json
{
"name": "shell-cluster",
"version": "1.0.13",
"version": "1.0.14",
"description": "Decentralized remote shell access via tunnels — Node.js server with node-pty and xterm-headless",

@@ -5,0 +5,0 @@ "main": "src/index.js",

@@ -166,3 +166,3 @@ # Shell Cluster

- **HTTP** `/sessions` endpoint: list active sessions
- **Dashboard API** (port 9000): `/api/peers`, `/api/refresh-peers`, WebSocket proxy
- **Dashboard API** (port 9000): `/api/peers`, `/api/version`, `/api/refresh-peers`, WebSocket proxy

@@ -201,3 +201,3 @@ ## Why Decentralized?

| Linux | `~/.config/shell-cluster/config.toml` |
| Windows | `%APPDATA%\shell-cluster\config.toml` |
| Windows | `%LOCALAPPDATA%\shell-cluster\config.toml` |

@@ -230,2 +230,10 @@ ```toml

### Running Tests
```bash
npm test # all tests
npm run test:unit # unit tests only
npm run test:e2e # end-to-end tests only
```
## Service Management

@@ -232,0 +240,0 @@

@@ -67,3 +67,10 @@ /**

const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
const data = TOML.parse(raw);
let data;
try {
data = TOML.parse(raw);
} catch (e) {
console.error(`[Config] Failed to parse ${CONFIG_FILE}: ${e.message}`);
console.error('[Config] Using default configuration');
return defaultConfig();
}
const config = defaultConfig();

@@ -70,0 +77,0 @@

@@ -41,3 +41,3 @@ /**

this._host = opts.host || '127.0.0.1';
this._port = opts.port || 9000;
this._port = opts.port !== undefined ? opts.port : 9000;
this._getPeers = opts.getPeers || (() => []);

@@ -77,6 +77,13 @@ this._refreshPeers = opts.refreshPeers || null;

const origin = req.headers.origin || '';
if (origin && (origin.includes('://localhost') || origin.includes('://127.0.0.1'))) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (origin) {
try {
const parsed = new URL(origin);
if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
} catch (e) {
// invalid origin URL — ignore
}
}

@@ -231,3 +238,3 @@ }

console.log(`[DashboardServer] Browser WS closed code=${code} reason="${reason || ''}"`);
if (peerWs && peerWs.readyState === WebSocket.OPEN) {
if (peerWs && (peerWs.readyState === WebSocket.OPEN || peerWs.readyState === WebSocket.CONNECTING)) {
peerWs.close();

@@ -238,3 +245,3 @@ }

browserWs.on('error', () => {
if (peerWs && peerWs.readyState === WebSocket.OPEN) {
if (peerWs && (peerWs.readyState === WebSocket.OPEN || peerWs.readyState === WebSocket.CONNECTING)) {
peerWs.close();

@@ -241,0 +248,0 @@ }

@@ -283,2 +283,7 @@ /**

// Fire exit callbacks BEFORE setting _disposed so onExit won't double-fire
for (const cb of [...sess._exits]) {
try { cb(sessionId); } catch (e) { /* ignore */ }
}
sess._disposed = true;

@@ -285,0 +290,0 @@ try {

@@ -189,2 +189,6 @@ /**

const onExit = (sid) => {
// Flush any pending output before sending shell.closed
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
flushOutput();
if (ws.readyState !== ws.OPEN) return;

@@ -245,3 +249,8 @@ try {

// --- Batched PTY input: reduce write frequency to ConPTY ---
// Also dedup mouse move events — only keep the latest position per batch.
// SGR mouse move: \x1b[<35;X;YM (button=35 means motion, no button pressed)
// SGR mouse drag: \x1b[<32;X;YM \x1b[<33;X;YM \x1b[<34;X;YM
const MOUSE_MOVE_RE = /^\x1b\[<(3[2-5]);(\d+);(\d+)M$/;
let inputBuf = [];
let lastMouseMove = null; // deduplicated: only keep latest mouse move
let inputTimer = null;

@@ -251,5 +260,9 @@

inputTimer = null;
if (inputBuf.length === 0) return;
const combined = Buffer.concat(inputBuf);
const parts = inputBuf;
const tail = lastMouseMove;
inputBuf = [];
lastMouseMove = null;
if (parts.length === 0 && !tail) return;
if (tail) parts.push(tail);
const combined = Buffer.concat(parts);
this._shellManager.write(sessionId, combined);

@@ -261,4 +274,15 @@ };

if (isBinary) {
// Binary frame = PTY input — batch to reduce ConPTY pressure
inputBuf.push(data);
// Binary frame = PTY input — batch + dedup mouse moves
const str = data.toString('utf-8');
if (MOUSE_MOVE_RE.test(str)) {
// Mouse move/drag: replace previous, don't accumulate
lastMouseMove = data;
} else {
// Non-mouse data: flush any pending mouse move first, then queue
if (lastMouseMove) {
inputBuf.push(lastMouseMove);
lastMouseMove = null;
}
inputBuf.push(data);
}
if (!inputTimer) {

@@ -286,4 +310,4 @@ inputTimer = setTimeout(flushInput, 8);

} else {
// Unknown JSON — treat as PTY input
this._shellManager.write(sessionId, Buffer.from(text, 'utf-8'));
// Unknown JSON control type — ignore it (don't write to PTY)
console.warn(`[ShellServer] Unknown control type: ${ctrl.type} session=${sessionId}`);
}

@@ -290,0 +314,0 @@ } catch (e) {