TL;DR - How to prevent client from receiving its own messages?
So I'm playing with socket.io after my experience with apollo and graphql.
My simple server looks like this:
io.on('connection', (socket) => {
console.log('New connection established.');
socket.on('disconnect', () => {
console.log('User disconnected.');
});
// Projects:
socket.on('join project', (data) => {
console.log(`User (${data.user.email}) join project with ID ${data.project.id}`);
socket.join(data.project.id);
});
socket.on('leave project', (data) => {
socket.leave(data.project.id);
});
socket.on('change field', (data) => {
console.log('Field was changed:', data);
const { project } = data;
socket.to(project.id).broadcast.emit('field changed', data);
});
});
I'm emitting something like this inside my application:
socket.emit('change field', {
project: {
id: 1,
},
value: 'Hello world',
usersEmail: 'example#email.com',
fieldName: 'description',
});
socket.on('field changed', (data) => {
// if (data.usersEmail === 'example#email.com') return; // This would stop from receiving own messages
console.log('CLIENT: field was changed!', data);
});
What I thought would happen is (due to the broadcast flag that I set up in the on('change field', ...)):
Clients A emits the message
Clients other than A receive the message
What is happening is a log inside other clients and client A itself, saying that the field was changed. Am I missing something?
I had the exact same problem. Couldn't (or didn't try hard enough) to find a setting for it, so instead just added this to my clients on page load:
document.windowid = Math.round(Math.random() * 1000000000000);
Then, add this to the message you emit in your client:
windowid: document.windowid
Then, when you accept data on the client, only do the action when windowid is not the same:
if (message.windowid != document.windowid)
It's not great and socket.io should take care of this issue, but this is the solution I used in my app :)
Related
for a while now I am stuck, trying to listen to the event where the agent sends a reply to the ticket. I have tried listening to ticket.comments.changed and ticket.conversation.changed but have not been successful.
I can't use the ticket.submit.[done|fail|always] or ticket.save because I don't have a way of knowing if it is the event I want or is being called with another event.
Maybe someone who knows of a configuration or some way that would allow me to do this, I would be very grateful.
You can configure triggers and webhooks to be added to your app as requirements. If you must use a ticket_sidebar app, you can listen to the following events:
var client = ZAFClient.init();
client.on('ticket.comments.changed', (e) => {
// Here is the latest comment
let comment = e[0];
console.log(comment);
// Check author role
console.log((comment.author.role !== "end-user") ? "Comment made by agent" : "Comment made by end user");
// Get ticket object if needed
client.get('ticket').then(
(res) => {
// Send ticket payload to my backend
},
(err) => {
console.error(err);
}
)
});
client.on('ticket.status.changed', (e) => {
// Here is the new status
console.log("Status changed to", e);
// Get ticket object if needed
client.get('ticket').then(
(res) => {
// Send ticket payload to my backend
},
(err) => {
console.error(err);
}
)
});
I have two applications. I get the same message several times.
For example:
app1 -> user_1 writes message: 'I need help' topic: demo
app2 -> show list of chats
app2 -> user_2 open chat from list and subscribe to topic:demo and now can talk with user_1
app2 user_2
I need help
I need help
I need help
I need help
I need help
It seems that connection is created several times.
It seems that subscribe and publish to different topic also not working. App1:
let client = mqtt.connect('http://127.0.0.1:9001', {
clientId: "front-client",
username: "admin",
password: "admin",
});
client.on('connect', (topic) => {
client.subscribe(this.topic, (err) => {
if (!err) {}
});
});
this.mqttClient = client;
//handle incoming messages
client.on('message', (topic, message, packet) => {
this.showMessageFromMqtt(message);
});
}
sendMsgByMqtt(message: string) {
this.mqttClient.publish(this.topic + '/MSG', message, {retain: true});
}
App2:
configureMqttChannel() {
let client = mqtt.connect('http://127.0.0.1:9001', {
clientId: "front-client-angular",
username: "admin",
password: "admin"
});
// console.log("connected flag: " + client.connected);
client.on('connect', (topic) => {
// console.log("connected " + client.connected);
client.subscribe(this.topic + '/MSG', (err) => {
if (!err) {
// console.log('message sent by mqtt')
// client.publish(this.topic, '', {retain: true})
}
});
});
this.mqttClient = client;
//handle incoming messages
client.on('message', (topic, message, packet) => {
// console.log("message is " + message);
// console.log("topic is " + topic);
this.showMessageFromMqtt(message);
});
}
sendMsgByMqtt(message: string) {
this.mqttClient.publish(this.topic, message, {retain: true});
}
In browser console:
connected true
connected true
connected true
connected true
connected true
connected true
connected true
......
If you are going to publish and subscribe to the same topic then your message must include an identifier that allows you to filter out messages that you've published when they invariably come back to you.
An example of this would be (pseudo code):
const MY_IDENTIFIER = generateSomeIdentifierUniqueToThisClient();
publish({
identifier:MY_IDENTIFIER,
data: // the actual content of your message
});
handleIncoming = data => {
// this was a message you sent
if(data.identifier === MY_IDENTIFIER) return;
// handle it
}
An alternative solution is to publish to say, demo/someUniqueClientIdentifier and subscribe to demo/+
When you receive a message, you can use the topic name to see which client it came from and filter it out that way - a variation on the exact same thing as above.
I am trying to create a chat lobby where users can create a private room, and others can join through a uuid. This is built with node.js and socket.io, and it seems as if io.to(room).emit('event', 'data') and io.sockets.to(room).emit('event', 'data') do not work, while io.emit('event', 'data') works. My code can be found below:
io.sockets.on('connection', (socket) => {
socket.on('createRoom', function(data) {
let room = new Room(data).create();
let id = room.data.uuid
socket.join(id)
io.to(id).emit('roomcreated', {data: data, msg: 'Room Created'}) //Does not work!
io.emit('roomcreated', {data: data, msg: 'Room Created'}) //This Works
});
});
A common problem with .join() is that it is actually asynchronous. It does not immediately complete. This is probably because it's designed to work with the multiple process, redis-based adapter that supports clustering and there the join process has to be asynchronous because it's communicating with other processes.
You can fix your issue, by sending a callback to .join() and only emitting to the room AFTER it has finished joining.
io.sockets.on('connection', (socket) => {
socket.on('createRoom', function(data) {
let room = new Room(data).create();
let id = room.data.uuid
socket.join(id, (err) => {
if (err) {
// do something here if the join fails
console.log(err);
return;
}
// call this only after the join has completed
io.to(id).emit('roomcreated', {data: data, msg: 'Room Created'});
});
});
});
I'm not sure entirely what's not working with io.in(id).emit('roomCreated', {d: data}); but I found a work around method.
I created an object of all sockets called SOCKET_LIST, and an array for each room that holds the id of that socket:
var SOCKET_LIST = {};
io.sockets.on('connection', (socket) => {
socket.id = Math.random();
SOCKET_LIST[socket.id] = socket;
socket.on('createRoom', function(data) {
let room = new Room(data).create();
room.data.users.push(socket.id)
let id = room.data.uuid
socket.join(id, (err) => {
if(err) {
return console.log(err);
}
room.data.users.forEach(i => {
SOCKET_LIST[i].emit('roomcreated', {data: data, msg: 'Room Created'})
})
})
});
});
I'm trying to develop a Node.js server that acts as a metronome (sends a repeated timing message) for everybody connected. I have socket.io rooms working so clients can "subscribe" to different metronomes, but sync between them drifts wildly. I've been using multiple setInterval() calls to schedule timing messages, and I understand that it isn't a very reliable clock source:
Client:
const socket = io('localhost:8000', {});
socket
.on('connect', function() {
console.log('connected');
})
.on('message', function(payload) {
console.log(payload);
});
// window.max is specific to the Max/MSP web browser object.
// This basically just subscribes to room 'inst0' or 'inst1'.
window.max.bindInlet('subscribe', function(inst) {
socket.emit('subscribe', inst);
});
Server:
io.sockets
.on('connection', (socket) => {
console.log('max connected!');
socket
.on('subscribe', (payload) => {
console.log('subscription: ' + payload);
Object.keys(socket.rooms).forEach(key => socket.leave(key));
socket.join('inst'+payload);
});
});
setInterval(() => {
io.to('inst0').emit('message', 'bang');
}, 500);
setInterval(() => {
io.to('inst1').emit('message', 'bang');
}, 250);
Are there other strategies for event scheduling like this? In a latency-free world the messages would have sample-accurate timing (44.1kHz resolution), but I'd be ecstatic to get something even around 30Hz.
I am building a chat app with React, Node/Express and socket.io. I have my sockets successfully set to my express server via http.createServer. I have a listener on client and server listening for new messages coming into the chat room. Ideally, I want each instance of the chat to be updated when there is an additional message, like any chat room that ever existed :)
Now I have a successful listen between client and server. I know because of a console.log server-side. However, I am not re-rendering the chat component when I submit a new message from a different instance.
So my code in my client-side (again React) component is as follows and I am using the socket CDN with script tags in my index.html (script tags not shown):
Socket CDN here
var socket = io('')
So that is the socket you see client side :
componentDidMount() {
return axios.get(`api/messages`)
.then((result) => {
if (result.data.length) {
this.setState({
messages: [ ...this.state.messages, ...result.data]
} , () => {
console.log("The state after messages are mounted : ", this.state)
})
}
})
.catch((err) => { throw err})
socket.on('new message', msg => {
this.newMessage(msg);
})
};
newMessage(msg) {
this.setState({
messages: [...this.state.messages, msg]
}, () => {
this.setState({ message: '' })
return this.scrollToBottom()
});
};
onSubmitMessage(event) {
event.preventDefault();
const content = this.state.message;
const msg = {
content,
createdAt : new Date(),
userId : "one",
chatRoomId : "two"
}
axios.post(`api/messages/`, msg)
.then(() => {
this.newMessage(msg);
socket.emit('new message', msg); //HERE'S THE SOCKETS IN ACTION
})
};
Here is the server-side code Node/Express:
//in server.js
const io = new socketIo(server)
require('./socketEvents')(io);
const connections = [];
Then a separate file for my socket events
//in socketEvents.js
module.exports = (io) => {
io.on('connection', (socket) => {
console.log("Beautiful sockets are connected")
socket.once('disconnect', () => {
console.log("socket is disconnected");
});
//DOESN'T DO ANYTHING YET
socket.on('join global', (username) => {
socket.join(username);
console.log("New user in the global chat : ", username)
});
socket.on('new message', (msg) => {
console.log("The new message from sockets : ", msg);
socket.emit('new message', msg.content);
});
});
}
My sockets server side are linked up with the client. I'm just not seeing new messages in different instances. Is it because I'm not re-rendering after the server receives the message?
Thanks in advance, please let me know if you need me to clarify anything.
Cheers!
I figured it out... I'm going to leave this post up with a walkthrough in an attempt to help others who are having trouble with sockets. I may post a blog about it. Will update if I do.
So the code listens on the client side for a message to be sent inside of my onSubmitMessage function.
onSubmitMessage(event) {
event.preventDefault(); //prevents HTML <form> from going on its own post
const content = this.state.message;
//Create message object
const msg = {
content,
createdAt : new Date(),
userId : "one",
chatRoomId : "two"
}
//HERE'S THE IMPORTANT PART!!!
axios.post(`api/messages/`, msg)
.then(() => {
// wrapped in a promise, send a handler to server called
// ('new message') with the message object
this.newMessage(msg);
socket.emit('new message', msg);
})
.then(() => {
//Another promise then waits for the handler to come back from server
//*****IMPORTANT*************
//Then invoke newMessage function to get the post on all sockets
socket.on('message', (msg) => {
this.newMessage(msg);
})
})
};
Now on the server side this is what's happening:
// This is where the listener is for the client side handle
socket.on('new message', (msg) => {
// broadcast.emit will send the msg object back to client side and
// post to every instance expcept for the creator of the message
socket.broadcast.emit('message', msg);
});
SO the data path is (C) for client, (S) for server:
receive message object from user and -------->
(C)socket.emit('new message') -----> (S) socket.on('new message') -------> (S) socket.broadcast.emit('message') --------> (C)socket.on('message')
Back in the client side, I can invoke my newMessage function, which will set the message to state so I can display it.
I hope someone finds this useful! Surprisingly, this seems to go relatively unanswered on Stack. If anyone has any questions feel free to ask!