Socket.emit doesn't trigger for all clients - node.js

I am new to react with socket.io, and I am stuck on the retrieving data permanently on frontend. What I'm trying to do is to create a little game and first step is to have a lobby page where multiple users can join.
Here is the part where I have problems because, if example, if I create a game with user Test
Lobby is now just with user Test
Tab1:
| --- Test --- |
But if I open a new tab and join to the current tab with the user Test1, my new tab is
Tab2:
| --- Test Test1 --- |
The problem that I ecounter is that the first tab only have the first user joined in the game
Tab1:
| --- Test --- |
The event didn't trigger for this tab. The same scenario happends for the next tabs and so on.
Node.js code
socket.on("getUsers", async (data) => {
if (!data.gameCode) {
universalError("GameCode doesn't exist");
return;
}
const existentGame = await pubClient.get(data.gameCode);
if (!existentGame) {
universalError("The Current Game Does Not Exist");
return;
}
currentGame = JSON.parse(existentGame);
socket.emit("gameUsers", {
gameUsers: currentGame,
});
});
React code
const equals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
function() {
const socket = useContext(SocketContext);
const [host, setHost] = useState("");
const [users, setUsers] = useState([]);
useEffect(() => {
if (!currentGameCode) return;
socket.emit("getUsers", { gameCode: currentGameCode });
}, []);
socket.on("gameUsers", (data) => {
setHost(data.gameUsers.host);
console.log(users, data.gameUsers.users);
if (!equals(users, data.gameUsers.users)) {
setUsers(data.gameUsers.users);
}
});
return(
...
)
}
I tried to put the socket.on before useEffect function, also insert the socket.on inside a useEffect with multiple dependency but I had no success with that. From what I saw, the user enter in socket.on("gameUsers") just once per tab and that is when he join the game.. The call on the backend is made everytime a new user join to the game, but my socket.on doesn't trigger in that case. Any ideas of what I'm doing wrong? Anticipated thanks!

Ok, I figured it out, the problem was not on the client side but on server side.. So I edited the title from
Socket.io doesn't trigger event for previous tabs - React
to
Socket.emit doesn't trigger for all clients.
I was trying to emit the event only to the particular socket which triggered the event.
socket.emit < - was triggered JUST by - > socket.on at which was connected to.
SOLUTON: Use io.emit to send to all client
io.on("connection", (socket) => { ... });

Related

Passing data from server to html with Electron/Nodejs

I'm using preload and renderer js to pass data from html to server. I have a main window and I open another window (add window). I take data from add window and pass it to server. I receive the data on server, but I don't know how to send callback with data from server to main window html.
In preload I have:
contextBridge.exposeInMainWorld(
'windowControls',
{
add: (data)=> ipcRenderer.send('item:add',data),
received:(data)=> ipcRenderer.send('item:received',data)
In rendererAddwindow:
var input = document.getElementById("inputItem").value;
windowControls.add(input)
In app.js:
// Catch item:add
ipcMain.on('item:add',(e,item)=>{
console.log('item',item); // Here I can read item
mainWindow.webContents.on('did-finish-load',()=>{
mainWindow.webContents.send('item:received',item)
});
addWindow.close();
})
What should I write in rendererMain to get data as a callback in main window? The main renderer is executed at first run and not when callback is triggered (if I triggered callback with these lines at all).
The did-finish-load event is not what you are looking for. This event is fired once the webpage is loaded, it is emited only once if you stay on the same page.
You have 2 solutions to answer a message received in the main process.
Invoke the message instead of sending it
You should refer to the documentation to learn about invoking the message.
Here is the example from the documentation :
// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})
// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})
Here is what it should look like in your example :
// Renderer process
ipcRenderer.invoke('item:add', item) // This sends the item to main process and wait for the answer
.then((data) => { // Callback triggered once the result comes back
console.log(data) // Do what you want with the data
})
// Main process
ipcMain.handle('item:add', async (event, item) => {
console.log(item)
return item // Or return whatever you want
})
Send a new message
This is not the best solution since it can become very complexe as the app grows. But you can send a new message from main to renderer :
// app.js file
ipcMain.on('item:add',(e,item)=>{
console.log({item})
if(yourWindow) { // It can throw an error if yourWindow is null or defined
yourWindow.webContents.send('item:received',item)
}
})
In app.js (when data is received from add window input):
// Catch item:add
ipcMain.on('item:add',(e,item)=>{
console.log('item',item); // Here I can read item
mainWindow.send('itemreceived',item)
addWindow.close();
})
In preload.js (outside contextBridge.exposeInMainWorld()):
const { contextBridge, ipcRenderer } = require('electron')
// Set up context bridge between the renderer process and the main process
contextBridge.exposeInMainWorld(
'windowControls',
{
close: () => ipcRenderer.send('windowControls:close'),
maximize: () => ipcRenderer.send('windowControls:maximize'),
minimize: () => ipcRenderer.send('windowControls:minimize'),
add: (data)=> ipcRenderer.send('item:add',data),
}
)
ipcRenderer.on('itemreceived',(event,message)=>{
console.log('item received message',message);
}
Similar example is here: https://gist.github.com/malept/3a8fcdc000fbd803d9a3d2b9f6944612

Remove Socket.IO emitter on disconnect from EventEmitter attatched event array of functions

I have EventEmitter and socket.io working in tandem and they work as I am expecting them to work. I have an object, that contains data, that data can randomly change at any point in time, when the data changes, it fires an EventEmitter.emit with the updated data.
In another file I have an EventEmitter.on which captures that data and passes it on to socket.emit to be sent to the front end, something like this:
Within the object containing the data:
UpdateIfDataIsNew(newData, oldData) {
if (newData !== oldData) { //simplified, not exactly like this
eventBus.emit('dataEvent', newData)
}
}
Where eventBus is a global EventEmitter object.
And in my file handling the sending of the data to the front end, the code is roughly:
ioServer.on("connection", (socket) => {
eventBus.on("dataEvent", (data) => {
socket.emit("frontEndDataEvent", data);
});
})
The way that this is working, whenever a new user connects, a new socket is created, and a new socket.emit is added to the list of functions to be fired when "dataEvent" is triggered, so that all connected users receive the updated data. The problem is that I don't know how to remove a particular socket.emit from the eventBus array of functions when a particular user disconnects, because when I don't do that, all functions stay in the event array, and all of them fire when an event is triggered, althought these users are no longer on the website, eventually the array gets incredibly big, even a simple refresh from a single user, keeps adding more and more functions to the array.
I know that I can see if a user has disconnected with socket.on("disconnect", ???) but then what?
http://prntscr.com/tou4pw the functions are the same, just with a different socket, how do I find the one that must be removed, when someone disconnects?
Instead of just adding a function to the eventEmitter, can I add say an object that contains an ID: and the function, so that I can then quickly identify the one that I must remove?
I solved it by creating my own Event Emitter class (externding EventEmitter), that way I have control over the array of functions for a given event and am also able to assign an ID.
const EventEmitter = require("events");
class RobotData extends EventEmitter {
constructor() {
super();
this.emitters = [];
}
add(socket, dataType) {
this.emitters.push({
socket,
dataType,
trigger: (eventName, newData) => {
socket.emit(`${eventName}`, newData);
},
});
}
remove(ID) {
this.emitters = this.emitters.filter((emitter) => emitter.socket.id !== ID);
}
}
const robotData = new RobotData();
module.exports = { robotData };
And then in the disconnect event:
socket.on("disconnect", () => {
robotData.remove(socket.id);
});

Enter twice in socket.on event

I'm new to Angular and Node.js.
My code executes a socket.emit to a server side function and receives a response in the socket.on event ('listartists').
Unfortunately, he enters twice in this event and I do not understand why.
On the server side (app.js) I put logs and the function is called correctly once, but then it goes through the socket.on twice ('listartists').
Thank you all
var appAng = angular.module('appAng', []).controller('myCtrl', function($scope) {
socket.on('listartists', function(msg) {
// !!! enter twice this part of the code
var ClientMessage = JSON.parse(msg);
if (ClientMessage.ClientID == GetClientUniqueId()) {
var artistslist = JSON.parse(ClientMessage.Message);
$scope.$apply(function() {
var artistslist=JSON.parse(ClientMessage.Message);
$scope.artists = artistslist;
});
}
});
});
i modified my code as :
socket.on('listartists', function(msg){
var ClientMessage=JSON.parse(msg);
if (ClientMessage.ClientID== GetClientUniqueId())
{
console.log("Arriva Lista Artisti " + ClientMessage.ClientID);
var artistslist =JSON.parse(ClientMessage.Message);
$scope.artists = artistslist;
$scope.$apply( function(){});
}
});
Anyway if i remove $scope.$apply( function(){}); the socket.on event is called only once,
otherwise it passes twice from there.
I think the problem is the management of the SCOPE some idea?

SocketIO dont fire first client events

I am building a chat app. The issue is that, socket.io does not emitthe keypress event on the first client, when fired.
I built this with Ionic 3 & Angular 5
Here's the "bug"
My view.html
<p *ngIf="typing == true">... is typing</p>
<ion-item no-lines>
<ion-input
type="text"
placeholder="Message"
[(ngModel)]="message"
(keypress)="userIsTyping()">
</ion-input>
</ion-item>
My view.ts
userIsTyping() {
this.socket.emit('typing');
this.socket.on("updateTyping", (data) => {
console.log('typingInSocket = ' + data.isTyping);
this.typing = data.isTyping;
});
console.log('typing = ' + this.typing);
My server.js
io.on('connection', (socket) => {
socket.on('typing', () => {
socket.broadcast.emit('updateTyping', {
isTyping: true
});
setTimeout(() => {
socket.broadcast.emit('updateTyping', {
isTyping: false
});
}, 2000);
});
Do you have any solution ?
Thanks you guys !
Okay, so the issue is how you're going about defining your socket listener. You're setting the event listener for this.socket.on("updateTyping") inside the block of code that only runs once you start typing. So every time you start typing, you create a new duplicate event listener. However, because it's only defined when you start typing, the "is typing..." won't show up until you've typed at least one character, at which point it'll start listening.
The answer is basically to move that socket listener outside of the userIsTyping() function.
this.socket.on("updateTyping", (data) => {
console.log('typingInSocket = ' + data.isTyping);
this.typing = data.isTyping;
});
userIsTyping() {
this.socket.emit('typing');
}

Cannot broadcast message to specific room? TypeError: Cannot call method 'in' of undefined

I am pretty new to socket.io. I try to send a message between client and server by using socket.io. I want the server to broadcast the message to a specific room but I am not success to do so. The error is that " Missing error handler on socket. TypeError: Cannot call method 'in' of undefined". I try to simplify my code so to just input room ID like in the following code, room of 123 but I still got the same error.
Server side's code:
//For checking the username and display registered user
client.on("join",function(name){
clients.push(client);
console.log("Join Event Successfully");
});
//Create Room on server side
client.on("createRoom", function(name) {
console.log(name);
client.join(name);
console.log("Room Create Successfully");
console.log("Room "+ name);
console.log("Room create successfully");
});
//Join room event
client.on("joinRoom",function(room_id){
client.join(room_id);
console.log("Room Join Successfully");
});
//Socket uses to sync video in real time
//On playing event from one client and need to brocast inside the room
client.on("onPlay",function(data){
io.to('123').emit("onPlay_server",data);
console.log("onPlay event trigger");
});
//Socket uses to sync video in real time
//On Pause event from one client and need to brocast inside the room
client.on("onPuase",function(data){
io.to('123').emit("onPuase_server",data);
console.log("onPause event trigger");
});
// Event trigger when client disconnect from the sever
client.on("disconnect", function() {
console.log("There is someone left the room"+ toString(client.id));
});
Client's side code:
socketIO.on("onPause_server", function(currentTime){
console.log("onPause_server");
if(player_state === 'play'){
console.log("onPause_server");
player.pauseVideo();
Player.seektTo(currentTime);
player_state = 'pause'
}
Player.seektTo(currentTime);
});
//Get message from sever side on play message
socketIO.on("onPlay_server", function(currentTime){
console.log("onPlay_server event trigger");
if(player_state === 'pause'){
console.log("onPlay_server event trigger");
player.seekTo(currentTime);
Player.playVideo();
Player_state = "play";
}
player.seekTo(currentTime);
});
function onPlayerStateChange(event) {
//console.log(player.getCurrentTime());
if (event.data == YT.PlayerState.PAUSED) {
player.pauseVideo();
var currentTime = player.getCurrentTime();
player.seekTo(currentTime);
socketIO.emit("onPause",currentTime);
player_state = "paused";
}
else if (event.data == YT.PlayerState.PLAYING) {
player.playVideo();
var currentTime = player.getCurrentTime();
socketIO.emit("onPlay",currentTime);
player_state = "play";
}
}
The code is running fine until when client tries to send the message "onPlay" or "onPause" to the server side, the server immediately yields the error of " Missing error handler on socket. TypeError: Cannot call method 'in' of undefined"

Resources