Run shell(bash, zsh, sh...) as a child process in node.js - node.js

I'm making an online shell to access my computer from another computer or device. I can run any program, sending and receiving stdio data using socket.io. I run the programs using execFile() from child_process. The problem is that, when I try to run bash, sh, zsh, csh or any other shell it just doesn't work, no error message or anything. How can I run a shell as a child process in node.js(prefer using execFile, but it doesn't matter)? This is the code I'm using:
Server:
let fs = require("fs");
let socket = require("socket.io");
let express = require("express");
let stringio = require("#rauschma/stringio");
let childprocess = require("child_process");
let app = express();
app.use(express.static("public"));
let server = app.listen(3000);
console.log("Server running!");
let io = socket(server);
io.sockets.on("connection", connectionListener);
function connectionListener(socket) {
console.log("New client!");
let child = childprocess.execFile("bash", ["test.sh"], {
stdio: [process.stdin, process.stdout, process.stderr]
});
socket.on("stdin-packet", sendtochild);
function sendtochild(packet) {
child.stdin.write(packet.rawtext);
}
child.stdout.on('data', function(data) {
let packet = {
rawtext: data.toString()
};
socket.emit("stdout-packet", packet);
console.log("Packet sended!");
});
}
Client:
let socket = io.connect("localhost:3000");
function TerminalScreen(width, height) {
this.width = width;
this.height = height;
this.chars = "";
this.print = function(text) {
let isansi = false;
for (let i = 0; i < text.length; i++) {
if (text.charCodeAt(i) == 27) {
isansi = true;
} else if (text.charAt(i) == 'm' || text.charAt(i) == 'K' || text.charAt(i) == 'H' || text.charAt(i) == 'f' || text.charAt(i) == 'J') {
if (isansi) {
isansi = false;
} else {
this.chars += text.charAt(i);
}
} else if (!isansi) {
this.chars += text.charAt(i);
}
}
this.chars = this.chars.replace("\n", "<br>");
}
this.clear = function() {
this.chars = "";
}
this.update = function() {
document.getElementById("terminal-div").innerHTML = this.chars;
}
}
let screen = new TerminalScreen();
socket.on("stdout-packet", packetReceived);
function packetReceived(packet) {
screen.print(packet.rawtext);
screen.update();
}
document.onkeypress = function (e) {
e = e || window.event;
let packet = {
rawtext: String.fromCharCode(e.keyCode)
};
socket.emit("stdin-packet", packet);
};

Most likely, because bash detects it's not running in a pseudo terminal (pty), it assumes it is running non-interactively. You can either create a full pseudo terminal (which things like expect or ssh will do), or you may be able to force it with the -i flag.

Using the node-pty package works for me.

Related

Messages are large, You may want to consider smaller messages. node-ipc and swift (OSX)

I have a node app that is trying to communicate with a swift application using a unix socket.
I am broadcasting a Message ever 3 seconds using the following code with node-ipc:
const ipc = require('node-ipc').default;
// Run IPC server for testing
const first = "FIRST";
const second = "SECOND";
const ipcSocketPath = `/tmp/${ipcAppId}`;
async function setupCoreCommunicationIpc() {
await new Promise((resolve) => {
ipc.serve(ipcSocketPath, () => {
ipc.server.on('ping', (data, socket) => {
ipc.server.emit(socket, 'pong');
});
return resolve(true);
});
ipc.server.start();
});
}
function sendMessage(message, payload = {}) {
ipc.server.broadcast(message, { ...payload, "valueOne": first, "valueTwo": seconds });
}
(async () => {
try {
await setupCoreCommunicationIpc()
} catch (e) {
// Deal with the fact the chain failed
}
// `text` is not available here
})();
setInterval(async () => {
await sendMessage("first:message", {core_id: ipcAppId, app_id: ipcAppId, message_id: 5})
}, 3000);
The code on swift is a bit more complicated. But I am able to connect to the unix socket and receive the message. The Problem is the sending of the AckMessage. I tried different approaches for sending the message but it would not work. Here is the code in swift:
func startSocketStack(valueOne: String, valueTwo: String){
let MTU = 65536
let path = "/tmp/\(valueTwo)"
print("starting socket at: %\(path)%")
let client = socket(AF_UNIX, SOCK_STREAM, 0)
var address = sockaddr_un()
//address.sun_family = UInt8(AF_UNIX)
address.sun_family = sa_family_t(AF_UNIX)
//address.sun_len = UInt8(MemoryLayout<sockaddr_un>.size)
address.sun_len = UInt8(MemoryLayout<UInt8>.size + MemoryLayout<sa_family_t>.size + path.utf8.count + 1)
strlcpy(&address.sun_path.0, path, MemoryLayout.size(ofValue: address.sun_path))
var adressUnwrap = address
withUnsafePointer(to: &adressUnwrap) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
let connection = connect(client, $0, socklen_t(address.sun_len))
if (connection != 0) {
print("startSocket: could not connect to socket at \(path)")
}
}
}
var buffer = UnsafeMutableRawPointer.allocate(byteCount: MTU,alignment: MemoryLayout<CChar>.size)
while(true) {
let readResult = read(client, &buffer, MTU)
if (readResult == 0) {
break; // end of file
} else if (readResult == -1) {
print("Error reading form client\(client) - \(errno)")
break; // error
} else {
let strResult = withUnsafePointer(to: &buffer) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: readResult)) {
String(cString: $0)
}
}
print("Received form client(\(client)): §\(strResult)§")
print("§\(strResult)§")
let trimmedString = strResult.components(separatedBy: .whitespacesAndNewlines).joined()
print("§\(trimmedString)§")
struct Message: Decodable {
let type: String
let data: MessageData
struct MessageData: Decodable {
var valueOne: String
var valueTwo: String
var message_id: String
}
}
struct AckMessage: Encodable {
let type: String
let data: Bool
}
do {
let jsonMessage = trimmedString.components(separatedBy: "}")[0] + "}" + "}"
let jsonData = jsonMessage.trimmingCharacters(in: CharacterSet.newlines).data(using: .utf8)!
let receivedMessage: Message = try JSONDecoder().decode(Message.self, from: jsonData)
let messageId = receivedMessage.data.message_id
let ackMessage = AckMessage(type: messageId, data: true)
let jsonAckMessage = try JSONEncoder().encode(ackMessage)
let delimiter = #"\f"#.bytes
let jsonAckMessageWithDelimiter = jsonAckMessage + delimiter
print("jsonAckMessageWithDelimiter")
print(jsonAckMessageWithDelimiter)
// first attempt
do {
try jsonAckMessageWithDelimiter.withUnsafeBytes() { [unowned self] (buffer: UnsafeRawBufferPointer) throws -> Int in
let buffer = buffer.baseAddress!
print(buffer)
let bufSize = jsonAckMessageWithDelimiter.count
print(bufSize)
var sent = 0
var sendFlags: Int32 = 0
while sent < bufSize {
var s = 0
s = send(client, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags)
if s <= 0 {
if errno == EAGAIN{
// We have written out as much as we can...
return sent
}
}
sent += s
}
return sent
}
} catch {
print(error)
}
// second attempt
jsonAckMessageWithDelimiter.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
write(client, pointer, jsonAckMessageWithDelimiter.count)
}
} catch {
print("Error on received message \(error)")
}
}
}
}
In my node application I can not receive the AckMessage because I get the following error:
Messages are large, You may want to consider smaller messages.
Now from my experience from Windows and Linux I know that \f is the delimiter that should indicate the end of the message. I am sending it at the end of my json but it is somehow not recognized.
What could be the problem? Is the delimiter wrong? Is the buffersize a Problem?
When logging what I send it says that it has sent all 61 bytes as expected.
For anyone having this problem using "\u{000C}" instead of "\f" is the solution.

overwrite previous lines on copy paste in REPL with ANSI escapes codes

I have Node.js REPL with syntax highlighting for Scheme language using ANSI escape codes. The problem is that when I copy-paste the code I need to overwrite previous text with new text that is highlighted. I'm using F ANSI escape to move the cursor up clear each line (K code) and write color text on top.
Here is my code:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
var prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
var prev_line;
boostrap(interp).then(function() {
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 0) {
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
var result = run(code, interp, dynamic);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: boostrap filed');
log_error(e.message || e);
console.error('Internal Error: boostrap filed');
});
}
this is my main function, I also have hacked on top of readline that highlight text while the user is typing but it's cleared after you press enter that's why I need to update previous lines on each enter (copy/paste also works the same as enter key because it generates line event).
The only relevant code is this part:
rl.on('line', function(line) {
code += line + '\n';
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length - 1;
const format = `\x1b[${num}F${stdout}`;
process.stdout.write(format);
}
try {
if (balanced_parenthesis(code)) {
// ...
// when Scheme code is not finished else is executed.
} else {
multiline = true;
// write spaces for next line according to indent rules
var ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
}
});
The code works fine unless original lines wrap into multiple lines, and it happens very often because you can't detect copy paste and the code is inserting spaces (auto-indent feature when user type code and press enter) so if the original pasted command already have indentation they are doubled.
I'm testing with this expression:
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(define foo (lambda ()
(let ()
(display "x"))))))))))))))))
The problematic code is on GitHub https://github.com/jcubic/lips/blob/devel/bin/lips.js
The problem is how to clear what was already written in the terminal with ANSI escape codes.
EDIT:
This is my latest code that shows the same error when copy-pasting:
function run_repl(err, rl) {
const dynamic = options.d || options.dynamic;
var code = '';
var multiline = false;
var resolve;
// we use promise loop to fix issue when copy paste list of S-Expression
let prev_eval = Promise.resolve();
if (process.stdin.isTTY) {
rl.prompt();
}
let prev_line;
bootstrap(interp).then(function() {
rl.on('line', function(line) {
code += line;
const lines = code.split('\n');
const cols = process.stdout.columns;
// fix formatting for previous lines that was echo
// ReadLine will not handle those
if (terminal && lines.length > 2) {
var count = 0;
// correction when line wrapps in original line
// that will be overwritten
lines.map(line => {
if (line.length > cols) {
count += Math.ceil(line.length / cols) - 1;
}
});
var f = new Formatter(code);
code = f.format();
const stdout = scheme(code).split('\n').map((line, i) => {
var prefix;
if (i === 0) {
prefix = unify_prompt(prompt, continuePrompt);
} else {
prefix = unify_prompt(continuePrompt, prompt);
}
return '\x1b[K' + prefix + line;
}).join('\n');
let num = lines.length + count;
const format = `\x1b[${num}F${stdout}\n`;
process.stdout.write(format);
}
code += '\n';
try {
if (balanced_parenthesis(code)) {
// we need to clear the prompt because resume
// is adding the prompt that was present when pause was called
// https://github.com/nodejs/node/issues/11699
rl.setPrompt('');
rl.pause();
prev_eval = prev_eval.then(function() {
const result = run(code, interp, dynamic, null, options.t || options.trace);
code = '';
return result;
}).then(function(result) {
if (process.stdin.isTTY) {
print(result);
if (newline) {
// readline doesn't work with not ended lines
// it ignore those, so we end them ourselves
process.stdout.write("\n");
newline = false;
}
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
}).catch(function() {
if (process.stdin.isTTY) {
if (multiline) {
multiline = false;
}
}
rl.setPrompt(prompt);
rl.prompt();
rl.resume();
});
} else {
multiline = true;
const ind = indent(code, 2, prompt.length - continuePrompt.length);
rl.setPrompt(continuePrompt);
rl.prompt();
const spaces = new Array(ind + 1).join(' ');
if (terminal) {
rl.write(spaces);
}
}
} catch (e) {
console.error(e.message);
console.error(e.stack);
code = '';
rl.setPrompt(prompt);
rl.prompt();
}
});
}).catch(function(e) {
log_error('Internal Error: bootstrap filed');
log_error(e.message || e);
console.error('Internal Error: bootstrap filed');
});
}
if you want to see the working code look at the LIPS git repo.

making node wait for db call to get completed

I just started writing node.js code.
I'm writing a code that extracts data from a pdf file, cleans it up and stores it in a database (using couchdb and accessing that using nano library).
The problem is that the calls are being made asynchronously... so the database get calls (i make some get calls to get a few affiliation files during the clean up) get completed only after the program runs resulting in variables being undefined. is there any way around this?
I've reproduced my code below
const fs = require('fs');
const os = require('os');
var couchDB = require('couch-db').CouchDB;
var pdf_table_extractor = require('pdf-table-extractor');
const filename = "PQ-PRI-0005-1806-01-0000_quoteSlipForLIBVIDGI1.pdf"
var nano = require('nano')('https://couchadmin:difficulttoguessmypassword#dbdev.perilwise.com');
var server = new couchDB('https://db.url.com');
server.auth("admin","admin");
var db = nano.db.use('pwfb');
var temp = [];
//New callView function
async function callView(){
try{
const doc = await view('liabilitymdm','pi');
for (var i =0; i<doc.rows.length;i++){
tmp.push(doc.rows[i]);
};
return doc;
} catch(e){
console.log(e);
};
};
function suc(result){
let ttmp = [];
console.log(result);
var pageTables = result.pageTables;
var firstPageTables = pageTables[0].tables;
ttmp = callView();
//this console log shows Promise { <pending> }
console.log(ttmp)
for (var k = 0; k < firstPageTables.length; k++) {
var temp = firstPageTables[k];
if (temp.length > 0) {
dump.push(temp);
}
}
// console.log(dump);
var insurer = filename.substr(37,8);
read_quote_slip(insurer,dump);
}
var read_quote_slip = (insurer,data) => {
console.log("read_quote_slip correctly entered");
var finOut = {};
if (insurer === "LIBVIDGI"){
finOut.insurer = insurer;
finOut.policyType = data[2][0].replace(/Quotation for/g,"");
finOut.natureOfWork = data[13][3];
let dedpos = indexGetter(data, "Deductible")[0];
finOut.deductible = data[dedpos+1][0];
let cov = indexGetter(data, "Coverage Territory and Jurisdiction")[0];
finOut.coverageTerritory = data[cov+1][0].replace(/Territory/g,"");
finOut.coverageJurisdiction = data[cov+2][0].replace(/Jurisdiction/g,"");
let ext = indexGetter(data,"Extensions")[0];
finOut.coverage = data[ext+1][0].split(/\r?\n/);
let majexc = indexGetter(data,"Major Exclusions")[0];
finOut.exclusions = data[majexc+1][0].split(/\r?\n/);
let prdtl = indexGetter(data,"Description")[0];
let prm = premiumcompute(data,prdtl,dedpos);
finOut.premium = prm;
finCleaned = libvidgi_cleaned(finOut);
// console.log(finCleaned);
}
}
var indexGetter = (words,toFind) => {
var finindex = [];
for (var i = 0; i < words.length; i++){
for (var j = 0; j < words[i].length; j++){
if(words[i][j].indexOf(toFind) >=0 ){
finindex.push(i);
}
}
}
return finindex;
}
var premiumcompute = (data, from, to) => {
let finprem = [];
let numbop = to - from - 2;
let incr = 0;
for (var i = from+2; i < to; i++){
let pr = {};
pr.option = incr+1;
pr.sumInsured = data[i][2].replace(/ /g,"");
pr.premium = data[i][data[i].length - 1].replace(/ /g,"");
finprem.push(pr);
incr +=1;
}
return finprem;
}
var libvidgi_cleaned = (finOut) => {
return finOut;
}
var fal = (result) => {
console.log(result);
console.log("there was an error");
}
var readPDFFile = function(filename){
//Decide which insurer from the filename
// console.log(filename);
console.log(filename.substr(37,8)+"Printed on line 38");
insurer = filename.substr(37,8)
pdf_table_extractor(filename, (result) => {suc(result)} , fal);
}
var libvidgi_data_extract = (data) => {
console.log(data);
let arr = data.pageTables.tables;
for (var i = 0; i <= arr.length; i++ ){
console.log(arr[i]);
}
}
readPDFFile(filename);
This answer assumes you are using Node.js > v7.6
Since db.view accepts a callback, and you wish to wait for it to finish, one solution will be to promisify it - meaning to turn it into a promise which can be awaited. You can use a library like Bluebird or you can even use Node's builtin promisify util. Then you can rewrite callViews:
const {promisify} = require('util');
const view = promisify(db.view);
async function callView() {
try {
const doc = await view('liabilitymdm', 'pi');
// the async operation is now guaranteed to be done
// (if there is an error it will be caught by the catch clause)
for (var i = 0; i < doc.rows.length; i++) {
temp.push(doc.rows[i]);
}
console.log(temp);
} catch (e) {
}
}
If you are not using Node.js > v7.6 (and cannot use async\await you can still utilize promises, by using their then method:
const {promisify} = require('util');
const view = promisify(db.view);
function callView() {
view('liabilitymdm', 'pi')
.then(doc => {
for (var i = 0; i < doc.rows.length; i++) {
temp.push(doc.rows[i]);
}
console.log(temp);
return temp;
})
.then(temp => {
console.log(temp);
})
.catch(e => {});
}
Notice how the first then is returning something which is used in a later then.
To make Node run asynchronously, you can use the keywords async and await.
They work like this:
async function doSomething () {
const formattedData = formatData();
const result = await db.postToDatabase(formattedData);
// the below will not happen until the above line is finished
doSomethingElse(result);
}
It's pretty simple in Node to get functions to execute asynchronously. Just put the async keyword at the beginning of the function definition and then put await in front of anything that you want to block execution until completed.

How to setsockopt under node.js?

Is there a setsockopt/getsockopt-like socket options manipulation functionality in node.js?
I'm expanding on the comment left by socketpair which shows getsockopt. You can accomplish this by using ffi and ref. I've reformatted this to allow it to be easily manipulated.
I edited my comment because I had to make some changes to make the code work on both Linux and Win32. I had to create a node library for Windows to get the socket handle and pass it to setsockopt. Be aware that Linux and Windows may have different values for socket options
Edit: Here's a cleaned up piece of of production code I'm using:
var net = require("net");
var ffi = require("ffi");
var ref = require("ref");
var getSocketHandleAddress;
var SOL_SOCKET = 0x1;
var SO_OOBINLINE = 0xA;
var _setsockopt;
if (process.platform == "win32") {
SOL_SOCKET = 0xffff;
SO_OOBINLINE = 0x0100;
}
var setSocketOption = function (handle, level, option, value) {
if (!_setsockopt) {
var library;
var paramTypes;
if (process.platform === "win32") {
library = "ws2_32.dll";
paramTypes = [
ref.types.int,
ref.types.int,
ref.types.int,
ref.refType(ref.types.void),
ref.types.int
];
} else {
paramTypes = [
ref.types.int,
ref.types.int,
ref.types.int,
ref.refType(ref.types.void),
ref.refType(ref.types.int)
];
}
var lib = new ffi.DynamicLibrary(library);
_setsockopt = ffi.ForeignFunction(
lib.get("setsockopt"),
ref.types.int,
paramTypes);
}
var refType;
var length;
if (typeof value === "boolean") {
refType = ref.types.bool;
} else {
refType = ref.types.int;
}
if (process.platform !== "win32") {
return _setsockopt(
handle.fd,
level,
option,
ref.alloc(refType, value),
ref.alloc(ref.types.int, refType.size)
);
}
if (!getSocketHandleAddress) {
getSocketHandleAddress = require("getsockethandleaddress");
}
return _setsockopt(
getSocketHandleAddress.getAddress(handle),
level,
option,
ref.alloc(refType, value),
refType.size
);
};
var tcpserver = net.createServer(function (socket) {
var ret = setSocketOption(socket._handle, SOL_SOCKET, SO_OOBINLINE, true);
if (ret !== 0) {
console.error("OOB Inline socket option failed: " + ret);
}
});
This is my getsockopt:
var ffi = require('ffi');
var net = require('net');
var StructType = require('ref-struct');
var ref = require('ref');
var current = ffi.Library(null, {
'getsockopt': [ 'int', [ 'int', 'int', 'int', 'pointer', 'pointer']],
'ntohs': ['uint16', ['uint16']],
// const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
});
var SOL_IP = 0;
var SO_ORIGINAL_DST = 80;
var AF_INET = 2;
var sockaddr_in = StructType([
['int16', 'sin_family'],
['uint16', 'sin_port'],
['uint32', 'sin_addr'],
['uint32', 'trash1'],
['uint32', 'trash2'],
]);
function get_original_dst(client) {
var dst = new sockaddr_in;
var dstlen = ref.alloc(ref.types.int, sockaddr_in.size);
var r = current.getsockopt(client._handle.fd, SOL_IP, SO_ORIGINAL_DST, dst.ref(), dstlen);
if (r === -1)
throw new Error("getsockopt(SO_ORIGINAL_DST) error");
if (dst.sin_family !== AF_INET)
throw new Error("getsockopt(SO_ORIGINAL_DST) returns unknown family: " + dst.sin_family );
// TODO: inet_ntop. inet_ntoa is _UNSAFE_
var ipaddr = dst.ref(); ipaddr = ipaddr[4] + "." + ipaddr[5] + "." + ipaddr[6] + "." + ipaddr[7];
return [ipaddr, current.ntohs(dst.sin_port)];
}
module.exports.get_original_dst = get_original_dst;
Kind of late but here is a pacakge on npm https://www.npmjs.com/package/net-keepalive
Provides high-level access to socket options like TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT
var Net = require('net')
, NetKeepAlive = require('net-keepalive')
;
// Create a TCP Server
var srv = Net.createServer(function(s){>
console.log('Connected %j', s.address())
// Doesn't matter what it does
s.pipe(s)
});
// Start on some port
srv.listen(1337, function(){
console.log('Listening on %j', srv.address())
});
// Connect to that server
var s = Net.createConnection({port:1337}, function(){
console.log('Connected to %j', s.address())
//IMPORTANT: KeepAlive must be enabled for this to work
s.setKeepAlive(true, 1000)
// Set TCP_KEEPINTVL for this specific socket
NetKeepAlive.setKeepAliveInterval(s, 1000)
// and TCP_KEEPCNT
NetKeepAlive.setKeepAliveProbes(s, 1)
});

Handling byte streams in node.js

For education purposes I am creating a little chat with node.js using TCP.
I am using the windows console to connect with my node server but when I am typing all the characters are streamed one by one. They don't arive as strings. How can I manage to handle those streams so my users don't can write complete words.
My Code:
var net = require("net");
Array.prototype.remove = function(e) {
for (var i = 0; i < this.length; i++) {
if (e == this[i]) { return this.splice(i, 1); }
}
};
function Chatter(stream) {
this.name = null;
this.stream = stream;
}
var chatters = [];
var server = net.createServer(function(stream) {
var chatter = new Chatter(stream);
chatters.push(chatter);
stream.setTimeout(0);
stream.setEncoding("utf8");
stream.addListener("connect", function(){
stream.write("Hallo, wer bist du?:\n");
});
stream.addListener("data", function (data) {
if(chatter.name == null) {
chatter.name = data.match(/\S+/);
stream.write("....................\n");
chatters.forEach(function(c){
if (c != chatter) {
c.stream.write(chatter.name + " ist dem Chat beigetreten!\n");
}
});
return;
}
var command = data.match(/^\/(.*)/);
if (command) {
if (command[1] == 'users') {
chatters.forEach(function(c) {
stream.write("- " + c.name + "\n");
});
}
else if (command[1] == 'quit') {
stream.end();
}
}
chatters.forEach(function(c) {
if(c != chatter) {
c.stream.write(chatter.name + ": " + data);
}
});
});
stream.addListener("end", function(){
chatters.remove(chatter);
chatters.forEach(function(c) {
c.stream.write(chatter.name + " hat den Chat verlassen.\n");
});
stream.end();
});
});
server.listen(8000);
For the record that code is from this site
ADDITION:
setEncoding('utf8') is supposed to change the emiting of data, but it doesn't work for me :-(
The solution to your problem is to store all received characters in a buffer and when an END_OF_NICK character is encountered (say, \n), use the buffer as the name.
var buffer = ""; // stores received characters
stream.addListener("data", function (data) {
if(chatter.name == null) { // still receiving characters for the name
buffer += data; // append received characters to the buffer
if (buffer.indexOf('\n') == -1) return; // if there's no END_OF_NICK character, keep waiting for it
chatter.name = buffer.match(/\S+/); // use the name in the buffer
// ...
}

Resources