Node net socket.end event fires twice? - node.js

This event is firing twice. I'm trying to figure out why.
On one client, I have:
import Net from 'net';
import Chalk from 'chalk';
const fallback = [2,5,10,25,50,100,250,500,1000,2000];
class LocalNetworkInterface {
constructor({path}) {
this._sock = new Net.Socket();
this._pending = {};
this._count = 0;
this._retry = 0;
const connect = () => {
this._sock.connect({path});
};
this._sock.on('connect',() => {
this._retry = 0;
console.log(`Connected to ${Chalk.underline(path)}`);
});
this._sock.on('data',buffer => {
let data = JSON.parse(buffer);
this._pending[data.queryId].resolve(data);
delete this._pending[data.queryId];
});
this._sock.on('end', () => {
console.log(`Lost connection to ${Chalk.underline(path)}. Attempting to reconnect...`);
connect();
});
this._sock.on('error', err => {
if(err.code === 'ENOENT') {
let ms = fallback[this._retry];
if(this._retry < fallback.length - 1) ++this._retry;
console.log(`Socket server unavailable. Trying again in ${ms}ms`);
setTimeout(connect, ms);
}
});
connect();
}
// ...
}
And the server:
const sockServer = Net.createServer(c => {
c.on('data', buffer => {
let data = JSON.parse(buffer);
// log('Received',data);
let ql = queryLogger();
runQuery(Object.assign({}, data, {schema})).then(result => {
ql(`${Chalk.magenta('socket')} ${print(data.query).trim()}`);
let response = Object.assign({}, result, {queryId: data.queryId});
c.write(JSON.stringify(response));
});
})
});
sockServer.on('error', serverError => {
if(serverError.code === 'EADDRINUSE') {
let clientSocket = new Net.Socket();
clientSocket.on('error', clientError => {
if(clientError.code === 'ECONNREFUSED') {
FileSystem.unlink(SOCK_FILE, unlinkErr => {
if(unlinkErr) throw unlinkErr;
sockServer.listen(SOCK_FILE, () => {
log(`Sock server improperly shut down. Listening on '${sockServer.address()}'`)
});
});
}
});
clientSocket.connect({path: SOCK_FILE}, () => {
throw new Error(`Server already running`);
});
}
});
['SIGTERM','SIGINT'].forEach(signal => process.on(signal, () => {
console.log(`\rReceived ${Chalk.yellow(signal)}, shutting down ${Chalk.red('❤')}`);
sockServer.close();
process.exit();
}));
sockServer.listen(SOCK_FILE, () => {
log(`Listening on ${Chalk.underline(sockServer.address())}`)
});
When I restart the server, I see "Lost connection" twice on the client. Why?
The documentation says:
Emitted when the other end of the socket sends a FIN packet.
The server isn't sending two "FIN" packets is it? Any way I can verify?

Seeing this in docs in regard to connect...
"...This function is asynchronous. When the 'connect' event is emitted the socket is established. If there is a problem connecting, the 'connect' event will not be emitted, the 'error' event will be emitted with the exception."
The fact that the connect event might simply not be firing simply making it look to you like the end event fired twice? Like #robertklep said, maybe expand that error check for more than specific code.

I think it's because on end, I immediately try to reconnect and then the same event is being caught again. Seems kind of strange that it would do that, but delaying it to the next tick works:
this._sock.on('end', () => {
console.log(`${Chalk.yellow('Lost connection')} to ${Chalk.underline(path)}. Attempting to reconnect...`);
process.nextTick(connect);
});

Related

Socket.io emit event only works once/first time

I'm brand new to socket.io and am trying to create an app similar to slido - users can send in messages and then view all messages being submitted in real time. I'm using node.js, express, socket.io, and redis in the back end. React and socket.io-client in front end.
At the moment, the live messages page/feed only updates (in real time) the first time a message is sent in, after that the emit even appears to stop working and the list of messages will only update when you refresh the page and it pulls the message history from redis.
Does anyone know why this may be happening? I've checked that the versions of socket.io for server and client are the same.
Thank you!
server-side socket setup:
io.on("connect", (socket) => {
initialiseUser(socket);
socket.on("dm", (message) => {
dm(socket, message, io);
});
io.emit("hello", "hello world");
socket.on("disconnecting", () => onDisconnect(socket));
});
// dm logic sits in separate file
module.exports.dm = async (socket, message, io) => {
message.from = socket.user.userid;
const messageString = [message.from, message.content].join(".");
await redisClient.lpush(`prayers:messages`, messageString);
io.emit("dm", message);
};
client-side setup:
const useSocketSetup = (setMessages, messages) => {
const { setUser } = useContext(AccountContext);
useEffect(() => {
socket.connect();
socket.on("hello", (content) => {
console.log("hello world", content);
});
socket.on("messages", (redisMessages) => {
setMessages(redisMessages);
});
socket.on("dm", (message) => {
setMessages((prevMessages) => [message, ...prevMessages]);
console.log("NEW MESSAGE", message);
});
socket.on("connect_error", () => {
console.log("Socket cannot connect");
setUser({ loggedIn: false });
});
return () => {
socket.off("connect_error");
socket.off("messages");
socket.off("dm");
};
}, [setUser, setMessages, messages]);
};
export default useSocketSetup;
The console log sitting inside socket.on("dm".... is only being logged on the first dm event.
This is the form setup for submitting a message:
const { setMessages } = useContext(MessagesContext);
useSocketSetup(setMessages);
return (
<>
<Formik
initialValues={{ message: "" }}
validationSchema={Yup.object({ message: Yup.string().min(1).max(255) })}
onSubmit={(values, actions) => {
const message = { from: null, content: values.message };
socket.emit("dm", message);
setMessages((prevMessages) => [message, ...prevMessages]);
console.log(JSON.stringify(message));
actions.resetForm();
navigate("/prayers");
}}
>
There is then a component accessing the messages from context and mapping through them to display.

How to listen to socketIO private message in React client?

I have a SocketIO instance in an Express app, that listens to a React client requests. A user can send private messages to a specific person. The server receives the private message, and should dispatch it back to both sender & recipient thanks to the io.to(socketId).emit(content) method.
How to listen to this event in React and update the message array? In order to ease the process, I have created a connectedUsers object, whose keys are mongoDB's user._id, and whose values are the unique socketID generated by socketIO. This way, I can easily address message to specific persons in the client. Once sent, the messages are stored in a MongoDB database.
Here is the back-end. The point of interest is io.on("privateMessage")
const connectedUsers = {};
const socketManager = (io) => {
io.on("identifyUser", (user) => {
if (!([user.id] in connectedUsers)) {
connectedUsers[user.id] = io.id;
}
});
io.on("privateMessage", (data) => {
io.to(connectedUsers[data.recipientId]).emit(data.message);
io.to(connectedUsers[data.senderId]).emit(data.message);
});
io.on("disconnect", () => console.log("user disconnected!"));
};
Here is the listening function in React. Everything works but the "privateMessage" part.
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connect", () => {
socket.emit("identifyUser", { id: res.data._id });
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
});
} catch (err) {
throw new Error(err);
}
}
Thanks for your help!
I think you need to put the socket.on("privateMessage") part outside the socket.on("connect") scope.
React must load all events at the beginning.
The backend side must be responsible for the authorization.
For the client there is connection event, not connect.
Subscription to event privateMessage should be outside connection callback.
This code should work. Hope this helps
import io from 'socket.io-client'
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connection", () => {
socket.emit("identifyUser", { id: res.data._id });
});
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
} catch (err) {
throw new Error(err);
}
}

Autoconnect option in socket.io client

I my app I use socket.io. Inside the client code I use
let socket;
const connect = () => {
let error = null;
socket = io({autoConnect: false});
socket.on('connect', () => {
console.log('Connected');
});
socket.on('disconnect', (reason) => {
console.log(`Disconnected: ${error || reason}`);
error = null;
});
socket.on('message', (message) => {
$("#messages").append(message + '<br/>');
let last = document.querySelector('#messages').lastElementChild;
last.scrollIntoView();
});
socket.open();
}
But it looks like that `enter code here
socket = io({autoConnect: false});
So not work well. Every time I open the side from node server, node reports a connection. Is there a problem in my syntax? I assume that the auto connect will avoid this case so the connection is only done when call connect().
I think
socket = io({autoConnect: false, reconnection: false});
is the solution

Unable to override net.Socket.write

I can't seem to override net.Socket.write. The example below is not my real use case, but rather a scaled down, runnable example to demonstrate the issue.
const net = require("net");
class UTF8Socket extends net.Socket {
constructor () {
super();
this.setEncoding("utf8");
}
write(data, cb) {
console.log("Sending...");
super.write(data, "utf8", cb);
}
end(data) {
console.log("Ending socket...");
super.end(data);
}
}
// Setup server
const server = net.createServer(socket => {
socket.setEncoding("utf8");
socket.on("data", (res) => console.log("Server received data:", res));
});
server.listen(8080, '127.0.0.1');
// Create a UTF8Socket and write to server
const socket = new UTF8Socket();
socket.connect(8080, "127.0.0.1", () => {
socket.write("test write\n");
socket.end("test end");
});
Expected output:
Sending...
Ending socket...
Server received data: test write
test end
Actual output:
Ending socket...
Server received data: test write
test end
The overridden end function is called as you can see from the output, but only the original net.Socket.write is ever called. I feel like I'm missing something...
I guess it's because of this code in the implementation of net.Socket.connect():
if (this.write !== Socket.prototype.write)
this.write = Socket.prototype.write;
Possible workaround:
connect() {
let result = super.connect.apply(this, arguments);
this.write = UTF8Socket.prototype.write.bind(this);
return result;
}

Emergency! Error: This socket has been ended by the other party

I revised appium source code, add my code, when i connect to the port that is forwarded to device and send command to port, it comes out:
Error: This socket has been ended by the other party
and my code is like this:
return await new Promise((resolve, reject) => {
try {
this.socketClient = net.connect(this.webSocket);
// Windows: the socket errors out when ADB restarts. Let's catch it to avoid crashing.
this.socketClient.on('error', (err) => {
if (!this.ignoreUnexpectedShutdown) {
//throw new Error(`Android bootstrap socket crashed: ${err}`);
log.debug('//////////////////////////////////')
log.debug(err)
log.debug('//////////////////////////////////')
throw new Error(`Android testbundle socket crashed: ${err}`)
}
});
this.socketClient.once('connect', () => {
log.info("Android bundle socket is now connected");
resolve();
});
} catch (err) {
reject(err);
}
})
after that, I use this.socketClient to send command like this:
async sendCommand(type, extra = {}) {
if (!this.socketClient) {
log.debug('==========socket closed========')
throw new Error('Socket connection closed unexpectedly');
}
return await new B((resolve, reject) => {
let cmd = Object.assign({cmd: type}, extra);
let cmdJson = `${JSON.stringify(cmd)}\n`;
log.debug(`Sending command to android testbundle: ${_.trunc(cmdJson, 1000).trim()}`);
this.socketClient.write(cmdJson);
this.socketClient.setEncoding('utf8');
let streamData = '';
this.socketClient.on('data', (data) => {
try {
streamData = JSON.parse(streamData + data);
// we successfully parsed JSON so we've got all the data,
// remove the socket listener and evaluate
this.socketClient.removeAllListeners('data');
if (streamData.status === 0) {
resolve(streamData.value);
}
log.debug("Received command result from bundle:" + JSON.stringify(streamData));
reject(errorFromCode(streamData.status));
} catch (ign) {
log.debug("Stream still not complete, waiting");
streamData += data;
}
})
})
}
But, I always get the error:
[debug] [bundle] //////////////////////////////////
[debug] [bundle] Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (net.js:291:12)
at ..\../lib/bundle.js:160:31
Anyone can help me...

Resources