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.
Related
I'm trying to send notifications based on business logic that runs (on nodejs) on my server via a cron.
Issue
Notifications aren't appearing on the device.
Description
I'm using the firebase admin node package.
My code looks something like this
import admin from "firebase-admin";
import serviceAccount from "../../firebase-admin.json" assert { type: 'json' };
import { getMessaging } from 'firebase-admin/messaging';
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
...
console.log(message);
await getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
My log output is something like this
{
notification: {
title: 'This is a string',
body: 'This is another string'
},
token: 'aLphaNumeric:reallyLongAlphaNumericWithDashesAndUnderscores'
}
Successfully sent message: projects/<project-name>/messages/<id>
Everything I'm seeing suggests this should be sent!
sendMulticast and the Admin FCM APIs allow you to multicast a message to a list of device registration tokens. You can specify up to 500 device registration tokens per invocation.
sendMulticast take 2 arguments as input, 1st one is notification which contains the title and body of the message.
The other argument is fcmTokens with type array, so you must pass that argument as array even though there is only one fcmToken
//Import the file where you have imported the service file.
const adminApp = require("../firebase/firebaseConfig");
const notificationToAll = (title, body, tokens) => {
var notibody = {
notification: {
title: title,
body: body,
},
tokens: tokens,
};
return new Promise((resolve, reject) => {
adminApp
.messaging()
.sendMulticast(notibody)
.then((response) => {
console.log(response.responses);
if (response.responses[0].error != undefined) {
console.log(JSON.stringify(response.responses[0].error));
}
resolve(response);
})
.catch((error) => {
console.log(JSON.stringify(error));
reject(error);
});
});
};
module.exports = notificationToAll;
app.js
const notificationToAll = require("./helper/notification");
notificationToAll(
"This is a string",
`This is another string`,
["aLphaNumeric:reallyLongAlphaNumericWithDashesAndUnderscores"]
)
This is tested code and working in a live environment.
Essentially, this is what I had in mind :
Client
const Messages = new Mongo.Collection("messages");
Meteor.call("message", { room: "foo", message: "Hello world");
Meteor.subsribe("messages", "foo");
// elsewhere
const messages = Messages.find({ room: "foo" });
Server
Meteor.methods({
message: ({ room, message }) => {
// 1. remove old messages in the room
// 2. add new message to the room
}
});
Meteor.publish("messages", function (room) {
// 1. return message collection for room
});
The client, I assume, uses minimongo, which is fine, but the server does not have access to a MongoDB instance whatsoever.
What would be the implementation on the server?
As mentioned in comments, I think this can be achieved using the manual publication methods described in the documentation. Something like this might work:
// Server:
const rooms = {};
Meteor.publish('manualMessages', function(roomId) {
check(roomId, String);
rooms[roomId] = this;
this.ready();
});
Meteor.methods({
message: ({ roomId, message }) => {
// 1. remove old messages in the room
const room = rooms[roomId];
if (!room) {
throw new Meteor.Error('no room', 'that room does not exist');
}
room.removed('messages', roomId);
// 2. add new message to the room
room.added('messages', roomId, { message });
}
});
// Client:
const Messages = new Mongo.Collection("messages");
Meteor.call("message", { room: "foo", message: "Hello world");
Meteor.subsribe("manualMessages", "foo");
// elsewhere
const messages = Messages.find({ room: "foo" });
One thing to verify is whether this in the publish function changes per client, in which case rooms should contain arrays, or whether it is the same object for all clients, as assumed here. But hopefully this point you in the right direction.
I'm using mqtt.js to receive websocket from mqtt server , the subscription works fine , but i need to change the subscription to topic dynamically through changing the configuration of the websocket , the issue is that after changing the configurations the i start to receive messages from both the new topic and the old topic , the old listener remains active even after i do client.unsubscribe().
is there a way to delete the old lister or stop the old websocket and keep only the new websocket, below is a snippet from the code:
import React, { Component } from 'react'
import mqtt from 'mqtt';
var options = {
protocol: 'ws',
rejectUnauthorized: false,
clientId: 'clientid' ,
username: 'username',
password: 'password',
clean: true
};
if (subscribed && (subscription != subscription1) && (subscription !=''))
{
console.log("subscription != subscription1 and changing configurations");
console.log("previous subscription is : " , subscription)
console.log("New subscription is : ", subscription1)
client.unsubscribe(subscription, () =>
{
client.end(true, () => {
console.log("client ended")
// console.log("connection status is : ", connectionStatus, "subscription is : ", subscription1);
console.log("subscribing to :", subscription1.toString());
client = mqtt.connect('ws://server:port/mqtt', options);
Configchanged = true;
client.subscribe(subscription1.toString(), (err, granted) => {
if (granted) {
var note = '';
if (client) {
client.on('message', function dispatchMqttTempMsg(topic, message) {
note = message.toString();
setValue(parseFloat(note));
console.log("received value to listener is", note)
})
}
}
else {
console.log("error during subscription", err)
}
});
})
})
// client.close(true)
// console.log("unsubscribing for guage no. ", props.id, "topic : ", subscription )
}
subscription = subscription1;
}
}
the client.on('message',..) above keeps active even if unsubscribed successfully..
It appeared that the unsubscribe is not working correctly , I have to make another procedure to unsubscribe from all previous topics before i subscribe to the new topic , this way the websocket listener will be removed and i wont receive messages from the old topic.
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 :)
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!