ccIDE/src/ccRemote.js
damp11113 5200e86f7d update 1.6
support multiple upload to machine and make remote script to one file and support to run on startup
2024-11-24 14:33:49 +07:00

266 lines
10 KiB
JavaScript

const WebSocket = require('ws');
class CCRemote {
constructor(ip = "127.0.0.1", port = 5133) {
this.socket = new WebSocket.Server({
port: port,
host: ip
});
this.clients = new Map(); // Map to track client data (numeric IDs)
this.clientIdCounter = 0; // Counter for numeric client IDs
console.log("Remote server is started");
fireNotify(
"Remote server started, waiting for clients to connect...",
"info"
);
this.socket.on('connection', (ws) => {
const clientId = this.clientIdCounter++;
this.clients.set(clientId, { ws, isAlive: true, name: `Client-${clientId}`, clientInfo: null });
if (this.clients.size >= 0) {
document.getElementById("navbar-button-computer-disconnect").disabled = false;
document.getElementById("navbar-button-computer-run").disabled = false;
}
console.log(`Client ${clientId} connected.`);
fireNotify(`Client ${clientId} connected`, "success");
// Send "info" command to the client immediately upon connection
this.sendCommandToClient(clientId, 'info', true);
ws.on('pong', () => {
this.clients.get(clientId).isAlive = true;
});
ws.on('message', (message) => {
console.log(`Message from Client ${clientId}: ${message}`);
// Handle info response
try {
const data = JSON.parse(message);
if (data.OSVersion && typeof data.OSVersion === 'string' &&
data.Name && typeof data.Name === 'string' &&
typeof data.ID === 'number' &&
typeof data.uptime === 'number' &&
typeof data.Type === 'number') {
// Update clientInfo with the received data
this.clients.get(clientId).clientInfo = data;
console.log(`Client ${clientId} info updated:`, data);
} else {
console.log(`Invalid data format from Client ${clientId}:`, data);
}
} catch (error) {
console.error(`Error parsing message from Client ${clientId}:`, error);
}
});
ws.on('close', () => {
console.log(`Client ${clientId} disconnected.`);
this.clients.delete(clientId);
fireNotify(`Client ${clientId} disconnected`, "warning");
if (this.clients.size === 0) {
document.getElementById("navbar-button-computer-disconnect").disabled = true;
document.getElementById("navbar-button-computer-run").disabled = true;
}
});
ws.on('error', (error) => {
console.error(`Client ${clientId} error:`, error);
fireNotify(`Error with Client ${clientId}: ${error.message}`, "error");
});
});
// Ping clients every 30 seconds to check if they are alive
const interval = setInterval(() => {
for (const [clientId, clientData] of this.clients.entries()) {
if (clientData.isAlive === false) {
console.log(`Client ${clientId} did not respond to ping, terminating connection.`);
fireNotify(`Client ${clientId} did not respond to ping, disconnected`, "warning");
clientData.ws.terminate();
this.clients.delete(clientId);
continue;
}
clientData.isAlive = false;
clientData.ws.ping();
}
}, 30000);
this.socket.on('close', () => {
clearInterval(interval);
console.log('WebSocket server closed.');
fireNotify("WebSocket server closed.", "info");
});
this.socket.on('error', (error) => {
console.error('WebSocket server error:', error);
fireNotify(`WebSocket server error: ${error.message}`, "error");
});
}
// List all connected clients with their IDs, names, and clientInfo
// List all connected clients with their IDs, names, and clientInfo
async listClients() {
const clientList = [];
// Create an array of promises for fetching client info
const infoPromises = [];
// Loop over each client and trigger 'info' command to get the latest information
for (const [clientId, clientData] of this.clients.entries()) {
if (clientData.ws.readyState === WebSocket.OPEN) {
const infoPromise = new Promise((resolve) => {
// Set a timeout to handle cases where no response is received
const timeout = setTimeout(() => {
resolve({ id: clientId, name: clientData.name, info: null });
}, 5000); // Wait 5 seconds for a response
// Listen for the response from the client
const messageHandler = (message) => {
try {
const data = JSON.parse(message);
if (data.OSVersion && typeof data.OSVersion === 'string' &&
data.Name && typeof data.Name === 'string' &&
typeof data.ID === 'number' &&
typeof data.uptime === 'number' &&
typeof data.Type === 'number') {
// Resolve with the valid data
resolve({ id: clientId, name: clientData.name, info: data });
} else {
resolve({ id: clientId, name: clientData.name, info: null });
}
} catch (error) {
resolve({ id: clientId, name: clientData.name, info: null });
}
};
// Send the "info" command to the client
this.sendCommandToClient(clientId, 'info', true);
// Attach the handler for this specific client
clientData.ws.on('message', messageHandler);
});
// Add the promise to the array
infoPromises.push(infoPromise);
}
}
// Wait for all promises to resolve (all client info to be updated)
const resolvedClientInfo = await Promise.all(infoPromises);
// Construct the final list of clients with updated information
for (const clientInfo of resolvedClientInfo) {
clientList.push(clientInfo);
}
return clientList;
}
// Check if any client is connected
isClientConnect() {
return this.clients.size > 0;
}
// Send code to all clients
sendCode(code) {
this.clients.forEach((clientData, clientId) => {
if (clientData.ws.readyState === WebSocket.OPEN) {
clientData.ws.send("sendcode");
setTimeout(() => {
clientData.ws.send(code);
}, 500);
}
});
}
// Send code to a specific client
sendCodeToClient(clientId, code) {
const client = this.clients.get(clientId);
if (!client || client.ws.readyState !== WebSocket.OPEN) {
console.error(`Client ${clientId} is not available or not connected.`);
fireNotify(`Client ${clientId} is not connected.`, "error");
return;
}
client.ws.send("sendcode");
setTimeout(() => {
client.ws.send(code);
}, 500);
}
// Run code on all clients
runCode() {
this.clients.forEach((clientData, clientId) => {
if (clientData.ws.readyState === WebSocket.OPEN) {
clientData.ws.send("runcode");
}
});
}
// Run code on a specific client
runCodeOnClient(clientId) {
const client = this.clients.get(clientId);
if (!client || client.ws.readyState !== WebSocket.OPEN) {
console.error(`Client ${clientId} is not available or not connected.`);
fireNotify(`Client ${clientId} is not connected.`, "error");
return;
}
client.ws.send("runcode");
}
// Disconnect all clients
disconnectAllClients() {
this.clients.forEach((clientData, clientId) => {
if (clientData.ws.readyState === WebSocket.OPEN) {
clientData.ws.send("exit");
clientData.ws.close();
fireNotify(`Client ${clientId} disconnected`, "warning");
}
});
this.clients.clear();
}
// Send a command to a specific client
sendCommandToClient(clientId, command, expectResponse = false) {
const client = this.clients.get(clientId);
if (!client || client.ws.readyState !== WebSocket.OPEN) {
console.error(`Client ${clientId} is not available or not connected.`);
fireNotify(`Client ${clientId} is not connected.`, "error");
return;
}
client.ws.send(command);
if (expectResponse) {
client.ws.on('message', (response) => {
console.log(`Response from Client ${clientId}: ${response}`);
});
}
}
// Send a command to all clients
sendCommandToAll(command, expectResponse = false) {
for (const [clientId, clientData] of this.clients.entries()) {
if (clientData.ws.readyState === WebSocket.OPEN) {
clientData.ws.send(command);
if (expectResponse) {
clientData.ws.on('message', (response) => {
console.log(`Response from Client ${clientId}: ${response}`);
});
}
}
}
}
}
module.exports = { CCRemote };