expressJS: How to push socketIO on get call? - javascript

This is how I set up a simple expressJS server with a socket.IO connection.
My application is reading some sensor data every 10 seconds, which gets pushed to every client. This is no problem for me.
But also one client can call /set-status with some parameter to change some status data. In that case the new status data should be send to every client. That's why a simple request/response attempt is not working.
What do I have to do to push the socketIO connection after /set-status has been called?
const express = require('express')
const http = require('http')
const socketIo = require('socket.io')
const app = express()
const server = http.createServer(app)
const io = socketIo(server)
io.on('connection', socket => {
getStatus(socket)
getData(socket)
setInterval(
() => getData(socket),
10000
)
})
app.get('/set-status', (req, res) => {
// Change some data and push new data to every client
// How to get access to socket?
res.send(200, 'new-data')
})
const getStatus = async socket => {
const res = { initial: 'data' }
socket.emit('exampleStatus', res)
}
const getData = async socket => {
// read some sensor data, which is refreshed every 10 sec.
// this is working as expected
const res = { some: 'sensor data' }
socket.emit('sensorData', res)
}
server.listen(port, () => {
if (process.env.NODE_ENV !== 'production') {
console.log(`Listening on port ${port}`)
}
})

If client sockets are listening for exampleStatus events, you can emit an exampleStatus event from inside of your get callback. It would look like this: io.emit('exampleStatus', data). On your client sockets, you can write a listener which looks like socket.on('exampleStatus, data => // do something with data).

Related

Why my socketio is not connecting with my socketio-client?

i am working on a chatapp project that needs a real time chatting so i have used socketio in my server side which is written in nodejs and than used socketio-client in my main chatapp react-native project.
But now a problem is coming my socket is not initializing. I'm not able to connect my server with my main app. I am using socketio and socketio client my both the socket version are same 4.5.1 but it's not even connecting. I have tried to use old version of socket but its also not working and I have also tried to change my localhost port to 4000 but it's also not working.
My server code:
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
const port = process.env.PORT || 3000;
require('./src/config/database')
const user_routes = require('./src/user/users.routes');
app.use(bodyParser.urlencoded({extended: true}))
app.use(express.json())
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
app.use('/User', user_routes)
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('send_message',(data)=>{
console.log("received message in server side",data)
io.emit('received_message',data)
})
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(port, () => {
console.log( `Server running at http://localhost:${port}/`);
});
My app socketservice file code:
import io from 'socket.io-client';
const SOCKET_URL = 'http://localhost:3000'
class WSService {
initializeSocket = async () => {
try {
this.socket = io(SOCKET_URL, {
transports: ['websocket']
})
console.log("initializing socket", this.socket)
this.socket.on('connect', (data) => {
console.log("=== socket connected ====")
})
this.socket.on('disconnect', (data) => {
console.log("=== socket disconnected ====")
})
this.socket.on('error', (data) => {
console.log("socekt error", data)
})
} catch (error) {
console.log("scoket is not inialized", error)
}
}
emit(event, data = {}) {
this.socket.emit(event, data)
}
on(event, cb) {
this.socket.on(event, cb)
}
removeListener(listenerName) {
this.socket.removeListener(listenerName)
}
}
const socketServcies = new WSService()
export default socketServcies
Where I have marked it should be connected = true but it's false in the dev console I have done console log so check that it's connecting or not and I can see that it's not connecting. How to make it connect?
There is no error in my app or server I have checked many times and my server is also running when I am running my app.
Answering my own question
The problem was i was using android emulator and android in an emulator can't connect to localhost you need to use the proxy ip so when i add http://10.0.2.2:3000 in const SOCKET_URL = 'http://10.0.2.2:3000' than its working fine
credit goes to gorbypark who told me this in discord
I'm assuming that your front and back runs in localhost. The documentation says that if the front-end is in the same domain as the back-end, you don't need to use the URL. Since you have the options parameter declared, you can use the default argument window.location in first place:
class WSService {
initializeSocket = async () => {
try {
this.socket = io(window.location, {
transports: ['websocket']
})
console.log("initializing socket", this.socket)
this.socket.on('connect', (data) => {
console.log("=== socket connected ====")
})
this.socket.on('disconnect', (data) => {
console.log("=== socket disconnected ====")
})
this.socket.on('error', (data) => {
console.log("socekt error", data)
})
} catch (error) {
console.log("scoket is not inialized", error)
}
}
emit(event, data = {}) {
this.socket.emit(event, data)
}
on(event, cb) {
this.socket.on(event, cb)
}
removeListener(listenerName) {
this.socket.removeListener(listenerName)
}
}
Don't specify the host/port for socket-io to connect to. It can figure it out on its own.
Per documentation, it tries to connect to window.location if no URL is specified as an argument.
So instead of
this.socket = io(SOCKET_URL, {
transports: ['websocket']
})
Just do
this.socket = io()
I am not sure it works with other arguments. You could try like this
this.socket = io(undefined, {
transports: ['websocket']
})

Express CORS is not working with socket.io

I've used cors for my express server, but I can't figure out why it's not working. Can anyone please help me with this issue?
Access to XMLHttpRequest at
'https://tic-tac-toe-server.now.sh/socket.io/?EIO=3&transport=polling&t=N6Z2b4X'
from origin 'http://localhost:8080' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested
resource.
Client:
import io from 'socket.io-client';
const socket = io('https://tic-tac-toe-server.now.sh')
Here is my index.js
const express = require('express');
const socketio = require('socket.io');
const http = require('http');
const cors = require('cors');
const router = require('./router');
const { addUser, removeUser, getUsers } = require('./users');
const { getMatch, addPlayer, destroyMatch } = require('./players');
const PORT = process.env.PORT || 5000;
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(router);
app.use(cors());
io.on('connection', function (socket) {
const id = socket.id;
let user_room = '';
/**
* User Joins to the global room
*/
socket.on('join', function ({ name, room, playing = false }) {
addUser({ id, name, room, playing }); // add user to users array
user_room = room;
socket.join(user_room);
socket.join(id);
socket.emit('user_joined', getUsers());
socket.broadcast.emit('user_joined', getUsers()); // emit event with modified users array
});
/**
* Match Started
*/
socket.on('player_joined', user => {
const match = getMatch();
addPlayer(user.match, user);
if(match.hasOwnProperty(user.match) && match[user.match].length === 2){
socket.emit('player_joined', match[user.match]);
socket.broadcast.to(user.match).emit('player_joined', match[user.match]);
}
});
socket.on('move', (data) => {
socket.emit('move', data);
socket.broadcast.to(data.match).emit('move', data);
});
socket.on('emote', (data) => {
socket.emit('emote_from', data);
socket.broadcast.to(data.match).emit('emote_to', data);
});
/**
* On user challenge
*/
socket.on('challenge', (socketId) => {
io.to(socketId).emit('accept', id);
});
socket.on('rejected', (socketId) => {
io.to(socketId).emit('rejected', id);
});
socket.on('accepted', data => {
io.to(data.opponent.id).emit('accepted', data);
socket.emit('accepted', data);
});
socket.on('player_left_match', match => {
socket.broadcast.to(match).emit('player_left_match');
});
socket.on('destroy_match', match => {
destroyMatch(match);
});
/**
* User Disconnect function
*/
socket.on('disconnect', () => {
socket.leave(user_room);
socket.leave(id);
removeUser(id); // remove user form users array
socket.emit('user_left', getUsers());
socket.broadcast.emit('user_left', getUsers()); // emit event with modified users
})
});
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
You can tell socket.io to only use the webSocket transport which is not subject to CORS by changing this:
const socket = io('https://tic-tac-toe-server.now.sh')
to this:
const socket = io('https://tic-tac-toe-server.now.sh', {transports: ['websocket']});
Some background. In its default configuration, socket.io starts every connection with multiple plain http requests. These plain http requests require server-side CORS support if the connection is cross-origin. But, socket.io can be configured to go straight to the webSocket transport (which is what is eventually ends up using anyway) and webSocket connections are not subject to CORS limitations.
The socket.io design to start with http polling was largely there because in the early days of webSocket support, not every browser supported it and not every server infrastructure supported it. But now-a-days, it is pretty widely supported.
So, telling socket.io to start with the webSocket transport from the beginning avoids many potential CORS issues.
We are now chasing a different issue and the error showing in the console at https://tic-tac-toe-vue.now.sh/ is coming from this code in webSocket.js.
try {
this.ws =
this.usingBrowserWebSocket && !this.isReactNative
? protocols
? new WebSocketImpl(uri, protocols)
: new WebSocketImpl(uri)
: new WebSocketImpl(uri, protocols, opts);
} catch (err) {
return this.emit('error', err);
}
It looks like something React related since there's a reference to isReactNative, but since your code is packaged and minimized, it's not very easy to do any debugging from here.

socket.io with socket.io-redis: get all socket object in a room

I want to retrieve server side all socket connected in a room.
I have found the method clients that if chained after the method in return all the sockets connected in a room:
import * as express from 'express';
import * as SocketIO from 'socket.io';
import * as redisSocket from 'socket.io-redis';
import * as sharedsession from 'express-socket.io-session';
const app = express();
const redisOption = {port: 6379, host: 'XXX'};
// use the RedisStore as express session
const session = session({
name: 'SESSIONID',
store: new RedisStore(redisOption),
secret: 'Ashg8hV77bxFeyLVec',
resave: false,
saveUninitialized: false
});
app.use(session);
// start express server
const httpServer = app.listen(80);
// attach the express app to socket.io server
const socketServer = SocketIO(httpServer, { path: '/api/socket.io', origins: '*:*' });
// set redis as socket adpter for multi instance/nodes
socketServer.adapter(redisSocket(redisOption));
// share the express session with socket.io
socketServer.use(sharedsession(session, {autoSave: true}));
// get all client in a room
socketServer.in(room_id).clients((err, clients) => {
for (let i = 0, e = clients.length; i < e; i++) {
const client = clients[i];
console.log(client); // print the socket id
// HOW RETRIVE THE SOCKET OBJECT???
}
});
But i need to retrive all sockets session/handshake.
There is a way for retrive all sockets session/handshake?
SIDE NOTE: the socket server is multi instance/nodes with socket.io-redis
socket.io: 2.3.0
socket.io-redis: 5.2.0
I am not sure below code will work, but I think by using customHook provided by socket.io-redis, We can get redis based multi-node's socket.handshake.session.
I hope the code below helps.
// get all client in a room
socketServer.in(room_id).clients((err, clients) => {
for (let i = 0, e = clients.length; i < e; i++) {
const client = clients[i];
console.log(client); // print the socket id
// HOW RETRIVE THE SOCKET OBJECT???
}
});
// set root namespace
const rootNamespace = socketServer.of('/');
// define customHook
rootNamespace.adapter.customHook = (request, cb) => {
// every socket.io server execute below, when customRequest requested
const type = request.type;
if(type === 'getHandShakeSession'){
// get all socket objects on local socket.io server
const sockets = rootNamespace.connected;
const socketIDs = Object.keys(sockets);
// get all socket.handshak.session array on local socket.io server
const sessions = socketIDs.map(socketID => sockets[socketID].handshake.session);
cb(sessions)
}
cb()
}
// request customRequest
rootNamespace.adapter.customRequest({type:'getHandShakeSession'},(err,replies) => {
//replies are array which element was pushed by cb(element) on individual socket.io server
//remove empty reply
const filtered = replies.filter(reply => reply !== undefined)
// filtered seems like [[session1,session2,...],[sesssion3,session4,...],..]
console.log(filtered)
} )
Object.keys(io.sockets.sockets); it gives you all the connected sockets in the room.

How can I emit to a single client with socket.io (rather than everyone)

Combing through the documentation but no luck. I'm trying to emit to a single client/user rather than everyone.
I read several other questions about this, most have no answer or point to older solutions. Any help would be appreciated.
The following works but emits to everyone on the site rather than the individual user...
SERVER:
//Socket.io
const http = require('http').Server(app);
const io = require('socket.io')(http);
app.post('/login', (req, res) => {
const email = cryptonize.encrypt(req.body.email);
const password = cryptonize.encrypt(req.body.password);
io.emit('login success', email, password);
});
CLIENT:
const socket = io();
socket.on('login success', (user, token, auth) => {
console.log(`user:${user}, password:${password});
});
I've tried "socket.emit" as mentioned in the socket.io cheat sheet, but it's coming back as undefined on the server. Probably something really simple I'm missing, just not seeing it.
I don't think that is the intended use of socket.io.
In your case, a simple res.end(...) will do (at least based on what you showed us).
app.post('/login', (req, res) => {
const email = cryptonize.encrypt(req.body.email);
const password = cryptonize.encrypt(req.body.password);
res.end(/* data */);
});
Read the docs about res.end().
If you really need to emit to a single socket, you need more work:
Use socket.io's rooms or namespace.
Get target socket's id.
Emit using the socket id.
Here's an example using default namespace:
Server
const IO = require('socket.io');
const io = IO(server);
// Default namespace is '/'
const namespacedIO = io.of('/');
io.on('connection', socket => {
socket.on('send', data => {
const targetSocket = namespacedIO.connected[data.socketID];
targetSocket.emit('received', data.value);
});
});
Client
const socket = io();
submit.addEventListener('click', function(){
socket.emit('send', {
socketID: socket.id, // IMPORTANT: to get the source socket ID
value: input.value
});
})
socket.on('received', function(data){
console.log(`Data "${data}" is received at server.'`);
});
Here's what I ended up doing, for anyone else trying to figure this out.
SERVER:
//Socket.io
const http = require('http').Server(app);
const io = require('socket.io')(http);
app.post('/login', (req, res) => {
const email = cryptonize.encrypt(req.body.email);
const password = cryptonize.encrypt(req.body.password);
const socketid = req.query.socket;
io.sockets.connected[socketid].emit('login success', email, password);
});
CLIENT:
const socket = io();
let socketid;
socket.on('connect', () => socketid = socket.io.engine.id);
CLIENT cont..
Then I just added a "socketid" query to my posts as they're generated.
//XHR Setup
const xhr = new XMLHttpRequest();
let response, status, readyState;
xhr.onreadystatechange = () => {
if (xhr.status === 200 && xhr.readyState === 4) response = xhr.response;
};
//XHR POST
const post = ({ url, callback, data }) => {
xhr.open('POST', `${url}&socketid=${socketid}`, true), xhr.setRequestHeader('Content-type', 'application/json'), xhr.send(data);
if (callback) callback();
console.log(`${url}&socketid=${socketid}`);
}

Socket.io only successfully connects if window opens before server starts

I've been trying to resolve a really strange Socket.io bug.
If I open the page on the client while the server is running, it will fail to connect with the message:
universalModuleDefinition:3 WebSocket connection to
'ws://localhost:4000/socket.io/?EIO=3&transport=websocket&sid=f6LwPIDZubiPKE-TAAAA'
failed: Connection closed before receiving a handshake response
If I then restart the server, while leaving the page open, it connects without issue.
app.js
const app = express();
const server = require('http').Server(app);
require('./socket')(server);
// More code here
server.listen(app.get('port'))
socket.js
const io = require('socket.io');
const jackrabbit = require(`jackrabbit`);
const rabbit = jackrabbit(process.env.RABBIT_URI);
const exchange = rabbit.default();
function Socket (app) {
this.io = io(app);
this.io.on('connection', socket => {
socket.emit('sync');
socket.on('room', room => {
socket.join(room);
});
})
this.queue = exchange.queue({ name: 'worker.socket' });
this.queue.consume(this.onMessage.bind(this), { noAck: true });
}
Socket.prototype.onMessage = function (message) {
this.io.to(message.report).emit('photo', message.photo);
}
module.exports = function (app) {
return new Socket(app);
}
client
var socket = io.connect();
socket.on('connect', function () {
// This gets triggered every time (after the error above)
console.log('Connected');
// This is never logged by the server
socket.emit('room', value); // value set by template engine
});
socket.on('sync', function(){
// will not execute first time I connect, but if I restart
// the server, it runs no problem
alert('Synced with server');
})
socket.on('photo', function(data) {
// also will not be run the first time, but works if the
// server is restarted when the page is open
})
Edit:
I've tried rewriting it to
Initialise socket.io within app.js, then pass it to the socket controller
Run server.listen before requiring socket.js
Initialising the client after a timeout
Setting the transport method on the client strictly to websocket
None of these methods have worked
Found the solution to my problem (actually not an issue with any of the code I posted). I was using the compression middleware for Express, which appears to break socket.io. Solution was to add the following:
app.use((req, res, next) => {
// Disable compression for socket.io
if (req.originalUrl.indexOf('socket.io') > -1) {
return next();
}
compression()(req, res, next);
});

Categories