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.
Related
I am working on Azure service bus topic. Following the documentation, created a sender and reciever code.
This is the sender code i am having,
const { ServiceBusClient } = require("#azure/service-bus");
const connectionString = "<SERVICE BUS NAMESPACE CONNECTION STRING>"
const topicName = "<TOPIC NAME>";
const messages = [
{ body: "Albert Einstein" },
{ body: "Werner Heisenberg" },
{ body: "Marie Curie" },
{ body: "Steven Hawking" },
{ body: "Isaac Newton" },
{ body: "Niels Bohr" },
{ body: "Michael Faraday" },
{ body: "Galileo Galilei" },
{ body: "Johannes Kepler" },
{ body: "Nikolaus Kopernikus" }
];
async function main() {
// create a Service Bus client using the connection string to the Service Bus namespace
const sbClient = new ServiceBusClient(connectionString);
// createSender() can also be used to create a sender for a queue.
const sender = sbClient.createSender(topicName);
try {
// Tries to send all messages in a single batch.
// Will fail if the messages cannot fit in a batch.
// await sender.sendMessages(messages);
// create a batch object
let batch = await sender.createMessageBatch();
for (let i = 0; i < messages.length; i++) {
// for each message in the arry
// try to add the message to the batch
if (!batch.tryAddMessage(messages[i])) {
// if it fails to add the message to the current batch
// send the current batch as it is full
await sender.sendMessages(batch);
// then, create a new batch
batch = await sender.createMessageBatch();
// now, add the message failed to be added to the previous batch to this batch
if (!batch.tryAddMessage(messages[i])) {
// if it still can't be added to the batch, the message is probably too big to fit in a batch
throw new Error("Message too big to fit in a batch");
}
}
}
// Send the last created batch of messages to the topic
await sender.sendMessages(batch);
console.log(`Sent a batch of messages to the topic: ${topicName}`);
// Close the sender
await sender.close();
} finally {
await sbClient.close();
}
}
// call the main function
main().catch((err) => {
console.log("Error occurred: ", err);
process.exit(1);
});
This code is working fine, but instead of sending a batch of dummy data to the service bus topic i want to implement my use case here.
My use case is I will be using this sender code in a react front end application, where there is a node API call happening at the end of a form submission. So at the end of form submission, i will send that unique form ID to the topic and i need to somehow trigger the api call for that form id.
I am unable to connect the dots. How to do this?
Added reciever side code.
const { delay, ServiceBusClient, ServiceBusMessage } = require("#azure/service-bus");
const axios = require("axios").default;
const connectionString = "<ConnectionString>"
const topicName = "<TopicName>";
const subscriptionName = "<Subscription>";
async function main() {
// create a Service Bus client using the connection string to the Service Bus namespace
const sbClient = new ServiceBusClient(connectionString);
// createReceiver() can also be used to create a receiver for a queue.
const receiver = sbClient.createReceiver(topicName, subscriptionName);
// function to handle messages
const myMessageHandler = async (messageReceived) => {
console.log(`Received message: ${messageReceived.body}`);
const response = axios({
method: 'post',
url: 'http://localhost:8080/gitWrite?userprojectid=63874e2e3981e40a6f4e04a7',
});
console.log(response);
};
// function to handle any errors
const myErrorHandler = async (error) => {
console.log(error);
};
// subscribe and specify the message and error handlers
receiver.subscribe({
processMessage: myMessageHandler,
processError: myErrorHandler
});
// Waiting long enough before closing the sender to send messages
await delay(5000);
await receiver.close();
await sbClient.close();
}
// call the main function
main().catch((err) => {
console.log("Error occurred: ", err);
process.exit(1);
});
While messages are published to a topic, they are recieved by subscriptions under the topic. You'll need to define one or more subscriptions to receive the messages. That's on the broker. For your code, you'll need a receiving code on the server-side/backend. Could be something like a node.js service or Azure Function. But a code that would receive from the subscription(s).
I would review the idea of publishing messages from the client side directly to Azure Service Bus. If the code is a React front end application, make sure the connection string is not embedded in resources or can be revealed.
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 want to implement a simple notification system. When user1 likes user2's post, user2 should get a real-time notification from user1.
Here is a part of the client function (Redux action) where someone likes a post:
.then(() => {
const socket = require("socket.io-client")(
"http://localhost:5000"
);
socket.emit("like", user, post);
});
Here is the server socket function where a notification is created after user1 likes user2's post:
io.on("connection", (socket) => {
socket.on("like", async (user, post) => {
if (!post.post.likedBy.includes(user.user._id)) {
const Notification = require("./models/Notification");
let newNotification = new Notification({
notification: `${user.user.username} liked your post!`,
sender: user.user._id,
recipient: post.post.by._id,
read: false,
});
await newNotification.save();
io.emit("notification");
}
});
});
Here is the client function after the notification is created:
socket.on("notification", () => {
console.log("liked");
});
Now the problem with this is the console.log('liked') appears for both user1 and user2. How can I emit to only that user that receives the notification? How can socket.io find this specific user2 that receives the notification from user1?
You should store a list (array or object) of all users like this :
(note that the list has to be updated when a user connects or leaves the socket server)
// an example of structure in order to store the users
const users = [
{
id: 1,
socket: socket
},
// ...
];
And then you can target the post owner and send him a notification like this :
// assuming the the 'post' object contains the id of the owner
const user = users.find(user => user.id == post.user.id);
// (or depending of the storage structure)
// const user = users[post.user.id]
user.socket.emit('notification');
Here an example :
const withObject = {};
const withArray = [];
io.on('connection', socket => {
const user = { socket : socket };
socket.on('data', id => {
// here you do as you want, if you want to store just their socket or another data, in this example I store their id and socket
user.id = id;
withObject[id] = user;
withArray[id] = user;
// or withArray.push(user);
});
socket.on('disconnect', () => {
delete withObject[user.id];
delete withArray[user.id];
// or let index = users.indexOf(user);
// if(index !=== -1) users.splice(index, 1);
});
});
There is plenty way of achieving what i'm trying to explain but the main idea is to link the socket with an index (user id for example) in other to retrieve it later in the code.
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!