Unable to connect to NodeJS standalone socket.io with ReactJS - node.js

I am looking for the best WS solution for IoT project. I am currently testing my options with Web Sockets. I have tried so far two NPM libraries 'ws' and 'websockets'. They worked great both NodeJS and ReactJS implementation was simple. I am now trying websocket.io. Reading the documentation I struggle to create even a simple working example copying the code directly from the documentation. Since the test code is so simple, I am really confused especially after the positive experience with two previous packages. I am pretty sure I am doing something wrong but I am unable to spot my mistake. I am really thankful for anyone helping to spot what am I not doing right.
NodeJS server instance listening on port 8000 (based on example here: https://socket.io/docs/v4/server-initialization/) :
const io = require("socket.io")();
io.on("connection", socket => {
console.log('blip')
});
io.listen(8000);
React client trying to connect to the NodeJS server from port 2000:
import React from "react";
import { io } from "socket.io-client";
class Io extends React.Component {
state = {
wsConnected: false
}
componentDidMount() {
const socket = io.connect('http://localhost:8000');
socket.on("connect", () => {
console.log('connect',socket.id);
});
}
render() {
const { wsConnected } = this.state;
return (
<div>{wsConnected ? 'on' : 'off'}</div>
)
}
}
export default Io

It seems you have CORS problem when in polling transport mode, So you can use Socket.io standalone server like this when you are using polling:
const io = require("socket.io")(
{
cors: {
origin: '*',
}
}
);
io.on("connection", socket => {
console.log(`${socket.handshake.headers.origin} connected`);
});
io.listen(8000);
But it's better use websocket transport if you need persistent http connection. When you are using websocket transport mode, there's no CORS policy.
websocket transport doesn't support http headers like cookies or etc but it is better to use it when there's energy consumption concerns in client side. Also you can force socket.io server to only supports determined transport modes. See socket.io documentations.

Related

socket.io in useEffect produces Proxy Error (ECONNRESET)

Summary
I've built a prototype for an app that is going to have some sort of chat functionality. As of right now the frontend React just pulls data via useEffect. To implement a more dynamic chat feeling I thought about using socket.io. When instantiating socket.io client-side as suggested (in useEffect) I am getting an ECONNRESET error.
Setup:
Backend: Node.js + express.js (listening on Port 5000)
Frontend: React
Frontend: Proxy for local development in package.json like this "proxy": "http://localhost:5000"
Problem:
When initializing my socket client-side like this:
const Flow = () => {
...
const fetchsocketData = () => {
const socket = io();
console.log("trying socket stuff");
socket.on("FromBackEnd", data => {console.log(data)});
};
useEffect(() => fetchsocketData(),[])
...
return (<div>Yolo</div>)
}
The proxying (as defined in the package.json) works nicely with e.g. Axios-calls, however, with socket.io, I get the following error on the server-side:
Proxy error: Could not proxy request
/socket.io/?EIO=3&transport=polling&t=N5v5GOe&sid=yugXlgWYsoqJRqcxAAAT
from localhost:3000 to http://localhost:5000. See
https://nodejs.org/api/errors.html#errors_common_system_errors for
more information (ECONNRESET).
And the following error on the client-side:
websocket.js:116 WebSocket connection to
'ws://localhost:3000/socket.io/?EIO=3&transport=websocket&sid=QKMDK2qmVGT3eud2AAAA'
failed: Error during WebSocket handshake: Unexpected response code:
400
This seems to be a temporal thing though, as the socket.io-connection is ultimately established and emits the test messages.
To make things a little weirder: If I move the socket instantiation into a user-triggered event:
const manuallyInstantiateSocket = () => {
const socket = io();
console.log("trying socket stuff");
socket.on("FromBackEnd", data => {console.log(data)});
}
And call this on a click of a button all works as expected. No error on the back-end side of things. The front-end error persists though.
What am I missing here?

Websocket request sometimes doesn't work after connection establishment

I have a Node.js script which is supposed to regularly access a SailsJS application via a socket connection. Client and server run on physically different machines on different networks. The SailsJS application is proxied behind nginx. That works in general. However, at random times, the connection is established but the first post request within the websocket connection never reaches its destination.
The code looks basically like this:
var socketIOClient = require('socket.io-client');
var sailsIOClient = require('sails.io.js');
var io = sailsIOClient(socketIOClient);
io.sails.url = 'https://foo.foo:443';
io.sails.rejectUnauthorized = false;
io.socket.on('connect', function() {
console.log("Connected!")
io.socket.post('/someroute', { someOptions: "foo" } ,
function(data) {
console.log(data);
});
});
io.socket.on('disconnect', function(){
console.log("Disconnected!");
});
io.socket.on('connect_error',function () {
console.log("connect_error!");
});
In case of a failure, simply nothing happens after console.log("Connected!"). Nothing appears in nginx's logs (in contrast to successful cases), the callback of io.socket.post never gets executed.
The most important question for me is: At which side is the problem? Client or server?
How can I debug this and narrow down the problem? Could it be a networking issue? Or something wrong the configuration, implementation or with the script itself?

how do i send a message to a specific user in ws library?

I'm exploring different websocket library for self-learning and I found that this library is really amazing ws-node. I'm building a basic 1 on 1 chat in ws-node library
My question is what is the equivalent of socket.io function which is socket.to().emit() in ws? because i want to send a message to a specific user.
Frontend - socket.io
socket.emit("message", { message: "my name is dragon", userID: "123"});
Serverside - socket.io
// listening on Message sent by users
socket.on("message", (data) => {
// Send to a specific user for 1 on 1 chat
socket.to(data.userID).emit(data.message);
});
WS - backend
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
\\ I can't give it a extra parameter so that I can listen on the client side, and how do I send to a specific user?
ws.send(`Hello, you sent -> ${data.message}`);
});
});
Honestly, the best approach is to abstract away the WebSocket using a pub/sub service.
The issue with client<=(server)=>client communication using WebSockets is that client connections are specific to the process (and machine) that "owns" the connection.
The moment your application expands beyond a single process (i.e., due to horizontal scaling requirements), the WebSocket "collection" becomes irrelevant at best. The array / dictionary in which you stored all your WebSocket connections now only stores some of the connections.
To correct approach would be to use a pub/sub approach, perhaps using something similar to Redis.
This allows every User to "subscribe" to a private "channel" (or "subject"). Users can subscribe to more than one "channel" (for example, a global notification channel).
To send a private message, another user "publishes" to that private "channel" - and that's it.
The pub/sub service routes the messages from the "channels" to the correct subscribers - even if they don't share the same process or the same machine.
This allows a client connected to your server in Germany to send a private message to a client connected to your server in Oregon (USA) without anyone being worried about the identity of the server / process that "owns" the connection.
There isn't an equivalent method. socket.io comes with a lot of helpers and functionalities, that will make your life easier, such as rooms, events...
socket.io is a realtime application framework, while ws is just a WebSocket client.
You will need to make your custom wrapper:
const sockets = {};
function to(user, data) {
if(sockets[user] && sockets[user].readyState === WebSocket.OPEN)
sockets[user].send(data);
}
wss.on('connection', (ws) => {
const userId = getUserIdSomehow(ws);
sockets[userId] = ws;
ws.on('message', function incoming(message) {
// Or get user in here
});
ws.on('close', function incoming(message) {
delete sockets[userId];
});
});
And then use it like this:
to('userId', 'some data');
In my opinion, if you seek that functionality, you should use socket.io. Which it's easy to integrate, has a lot of support, and have client libraries for multiple languages.
If your front-end uses socket.io you must use it on the server too.

How to use PM2 Cluster with Socket IO?

I am developing an application that relies completely on Socket.io. As we all know NodeJS by default runs only on one core. Now I would like to scale it across multiple cores. I am finding it difficult to make socketio work with PM2 Cluster Mode. Any sample code would help.
I am using Artillery to test. And when the app runs on single core I get the response while It runs in cluster the response would be NaN
When Ran Without Cluster
PM2 docs say
Be sure your application is stateless meaning that no local data is
stored in the process, for example sessions/websocket connections,
session-memory and related. Use Redis, Mongo or other databases to
share states between processes.
Socket.io is not stateless.
Kubernetes implementation get around the statefull issues by routing based on source IP to a specific instance. This is still not 100% since some sources may present more than one IP address. I know this is not PM2, but gives you an idea of the complexity.
NESTjs SERVER
I use Socket server 2.4.1 so then i get the compatible redis adapter that is 5.4.0
I need to extend nest's adepter class "ioAdapter" that class only works for normal ws connections not our pm2 clusters
import { IoAdapter } from '#nestjs/platform-socket.io';
import * as redisIOAdapter from 'socket.io-redis';
import { config } from './config';
export class RedisIoAdapter extends IoAdapter {
createIOServer(port: number, options?: any): any {
const server = super.createIOServer(port, options);
const redisAdapter = redisIOAdapter({
host: config.server.redisUrl,
port: config.server.redisPort,
});
server.adapter(redisAdapter);
return server;
}
}
That is actually nestjs implementation
Now i need to tell nest im using that implementetion so i go to main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { config } from './config';
import { RedisIoAdapter } from './socket-io.adapter';
import { EventEmitter } from 'events';
async function bootstrap() {
EventEmitter.defaultMaxListeners = 15;
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useWebSocketAdapter(new RedisIoAdapter(app));
await app.listen(config.server.port);
}
bootstrap();
I have a lot of events for this one so i had to up my max event count
now for every gateway you got, you need to use a different connection strategy, so instead of using polling you need to go to websocket directly
...
#WebSocketGateway({ transports: ['websocket'] })
export class AppGateway implements OnGatewayConnection, OnGatewayDisconnect {
...
or if you are using namespaces
...
#WebSocketGateway({ transports: ['websocket'], namespace: 'user' })
export class UsersGateway {
...
last step is to install the redis database on your AWS instance and that is another thing; and also install pm2
nest build
pm2 i -g pm2
pm2 start dist/main.js -i 4
CLIENT
const config: SocketIoConfig = {
url: environment.server.admin_url, //http:localhost:3000
options: {
transports: ['websocket'],
},
};
You can now test your websocket server using FireCamp
Try using this lib:
https://github.com/luoyjx/socket.io-redis-stateless
It makes socket io stateless through redis.
You need to setup Redis with your Node server. Here is how I managed to get cluster mode to work with Socket.io
First install Redis. If you are using Ubuntu, follow this link: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-18-04
Then:
npm i socket.io-redis
Now place Redis in your Node server
const redisAdapter = require('socket.io-redis')
global.io = require('socket.io')(server, { transports: [ 'websocket' ]})
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }))
That was all I had to do to get PM2 cluster mode to work with socket.io in my server.

How does one correctly set up a server based deepstream RPC provider?

I am building a SOA with deepstream and I want to use a deepstream client server to perform API-KEY based look ups that the user should not know. How do I actually set up an RPC client provider? I have looked in the deepstream docs and on google, but there is not a full code example on how to do this. I have created a file like below and run it with node. The output I get is below it:
var deepstream = require('deepstream.io-client-js')
const client = deepstream('localhost:6020').login()
console.log('Starting up')
client.on('error', (error,event,topic) => {
console.log(error, event, topic);
})
client.on('connectionStateChanged', connectionState => {
console.log(connectionState);
})
client.login({username: 'USER', password: 'PASSWORD'}, (success, data) => {
if (success) {
client.rpc.provide('the-rpc', function( data, response ){
response.send(data);
});
} else {
console.log(data);
}
})
--
Starting up
AWAITING_CONNECTION
As you can see it runs the code, but does not actually connect to the deepstream server. I already have the deepstream server running, and a browser client that connects to it, so the config is correct. Please help!
I think your issue is based on the fact your trying to connect node via the webport. Try using port 6021 instead for tcp ( used by the node client ).
const client = deepstream('localhost:6021').login()
You should also only call .login() once, so the line would be:
const client = deepstream('localhost:6021')
We are working on a 2.0 release coming out very soon which will remove tcp entirely and only require a single port to make life easier in terms of deployment and performance.

Resources