I created application in Ionic 4, and for backend I use Lumen. Application should have chat page and in that purpose I add Redis, Socket.io and nodejs. I successfully created public room, and chat between users in that room works. Problem is how to send private message for user, how to initialize users for their private room.
This is how I created public room:
constructor(private socket: Socket) {
this.getMessages().subscribe(message => {
this.messages.push(message);
});
}
getMessages() {
const observable = new Observable(observer => {
this.socket.on('message', (data) => {
observer.next(data);
});
});
return observable;
}
I send message from Lumen application and Redis:
public function sendMessage()
{
$redis = Redis::Connection();
$sendMessage = json_encode(['user' => 'John Doe', 'text' => 'Some message, text', 'channel' => 'message']);
$redis->publish('add-message', $sendMessage);
}
And my node server is:
let express = require('express');
let app = express();
let http = require('http').Server(app);
let redis = require('redis');
let client = redis.createClient("redis://127.0.0.1:6379");
let io = require('socket.io')(http);
app.use('/', express.static('www'));
http.listen(3000, '192.168.10.10', function(){
console.log('listening on *:3000');
});
client.on('message', function(chan, msg) {
let data = JSON.parse(msg);
io.sockets.emit(data.channel, msg);
});
client.subscribe('add-message');
Bottom line everyone who is subscribed on 'message' channel will get message. Problem is that I subscribe user on channel when came to chat page. I don't know how to subscribe user, and when, on channel where some another user send him message.Also how that sender user to create new room, I suppose to use id of users for room name (per instance user1_user2).
Does anyone know how I can solve this problem? I don't know even I described well.
Thanks in advance
Related
I'm trying to create a user to user chat application - no group chat or anything.
I'm using NodeJS and Socket.io on the backend and React Native on the frontend.
I ended up having a Map that stores a user id and it's corresponding socket id, my problem is that only when a user connects to the server, he will get a socket id.
But what if User A is connect and is trying to send a message to User B, and User B is not connected, so it does not have a socket id, I don't really know what to do then.
This is what I got so far:
io.on("connection", (socket: Socket) => {
//Whenever a user will connect, the user will emit 'initiate' to register itself and it's socket id to the server.
//We will be using this userSocketMap to send messages between users.
socket.on(SocketEvents.INITIATE, (data) => {
const uid = data.uid;
const socketID = socket.id;
userSocketMap.set(uid, socketID);
});
//This function will get called when a user is sending message to another user.
socket.on(SocketEvents.SEND, (data) => {
const to = data.to;
const from = data.from;
const content = data.content;
const payload: MessagePayload = {
to: to,
from: from,
content: content,
};
const dstUser = userSocketMap.get(to); // getting the socket id of the receiver.
// '/#' is the prefix for the socketID, if the socketID already has this prefix, this should be deleted - need to check this.
//MessageEvent.RECEIVE is used to signal the dstUser to receive a message.
io.to("/#" + dstUser).emit(SocketEvents.RECEIVE, { payload: payload });
});
socket.on(SocketEvents.DISCONNECT, (socket: Socket) => {
const userID = getByValue(socket.id);
if (!userID) return;
userSocketMap.delete(userID);
});
});
You should do two things when working with react-native and socket.io in case user lost internet connection. Use socket.io heartbeat mechanism inorder to get the users that lost connection and are not responding and user NetInfo package to inform the mobile user that he has lost internet connection.
Socket.io
var server = app.listen(80);
var io = socketio(server,{'pingInterval': 2000});
io.on("connection", (socket: Socket) => {
socket.on(SocketEvents.INITIATE, (data) => {
const uid = data.uid;
const socketID = socket.id;
userSocketMap.set(uid, socketID);
})
socket.on('heartbeat', (socket: Socket) => {
const userID = getByValue(socket.id)
userSocketMap.MARK_USER_AS_INACTIVE(userID)
})
});
React-Native - use NetInfo - it used to be part of the core but got separated to a community module
import NetInfo from "#react-native-community/netinfo";
NetInfo.fetch().then(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
});
const unsubscribe = NetInfo.addEventListener(state => {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
});
// Unsubscribe
unsubscribe();
It's been a while since I've worked with Node and Websockets. Basically how do I get socket.send() to work from another function is what I'm stuck on.
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', socket => {
socket.on('message', message => {
console.log(`received from a client: ${message}`);
});
socket.send('yo world!');
});
function onMessageHandler (target, context, msg, self) {
client.say(target, response);
server.socket.send(response);
console.log(response);
}
}
How do I get my onMessageHandler to trigger a socket send, this is fail... server.socket.send(response);
Seeing your question i think there is a lack of understanding on how Websockets work. I am assuming you're using https://github.com/websockets/ws
There are two things. First is the WebSocketerver which you've named as server and then an Individual Socket which you've named as socket
Now the thing to understand is socket is not accessible outside server.on() callback The reason for this is there could be 1000 of sockets connected at a given instance and there would be no way to uniquely identify a particular socket you want to send message to.
So ask yourself the question that your application wants to send message to an individual socket to send to everyone who is connected to your server (basically broadcast)
If you want to send to an individual, you will have to uniquely identify the user
this._wss = new WebSocket.Server({
port: ENV_APP_PORT_WS
});
this._wss.on("connection", async (ws: AppWebSocket, req: IncomingMessage) => {
// const ipAddress = req.connection.remoteAddress; // IP Address of User
logger.info(req);
const queryParams = url.parse(req.url, true).query;
let authUser: User;
try {
authUser = await this._authenticateWebSocket(queryParams);
} catch (e) {
// Terminate connection and return...
}
// WS User INIT
ws.isAlive = true;
ws.userId = authUser.id;
ws.uuid = Helpers.generateUUIDV4();
ws.send(JSON.stringify({
type: "connected",
env: ENV
}));
});
The above code will add a property to each socket object that will enable it to uniquely identify a particular user/socket.
While sending =>
onMessageHandler(targetUserId: number, message: string) {
const allSockets = <AppWebSocket[]>Array.from(this._wss.clients.values());
const targetSocket = allSockets.find(w => w.userId === targetUserId);
targetSocket.send(message);
}
If You want to send to all connect users, it's quite easy:
https://github.com/websockets/ws#server-broadcast
I am creating a rest api on nodejs. I have email id and user id in database. I want to share a file (present on same server) from one user to a particular user. Can anyone tell me how this can be done in a best way ?
Here is the code i have tried yet.
const server = require('./../server/server.js')
const socketIO = require('socket.io');
const io = socketIO(server);
const mongoose = require('mongoose');
const User = require('../models/user.js');
let sockets = [];
io.on('connection', socket=>{
console.log("User connected");
socket.on('online', (data)=>{
socket.name = data._id;
sockets[data._id] = socket.id;
console.log("user is online")
})
socket.on('send_file', (data)=>{
User.find({emailId: data},{emailId:0, password:0})
.exec()
.then(userid => {
if(userid.length<1){
console.log("No such user");
}
else{
console.log(userid[0].id);
socket.to(sockets[userid[0].id]).emit('hello', "HELLO");
}
})
.catch(err =>{
console.log(err);
});
});
socket.on('disconnect', ()=>{
console.log("User disconnected");
})
})
module.exports = io;
server.listen('8080', (err)=>{
if(err) throw err;
console.log("running on port 8080");
});
Assuming that you have already configured the socketio and express sever properly with he mechanism to save the file path and file name in you database.
Try something like this (with socketio)
let sockets = [];
io.on("connection", function(socket) {
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
socket.on("send_file", function(data) {
// your logic to retrieve the file path from you database in to the variable **filedata**
// let filedata = ................
socket.to(sockets[data.user]).emit("file",filedata);
});
socket.on("disconnect", reason => {
sockets.splice(sockets.findIndex(id => id === socket.id), 1);
});
});
in send_file event you will have receive the username from the sender inside the data object. The following code will be one which will help you to send file to selected user.
socket.to(sockets[data.user]).emit("file",filedata);
Replying to your 1st comment.
history.push() will not refresh the client since its a single page application.
But when you refresh(from user A side) a new socket session will be created then the other user(user B) will still be referring the old socket session(which is already being disconnected by the refresh). So to handle this use the following lines
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
where you will be keeping a pool(an array) of sockets with the usernames so when ever a user refresh their client the newly created socket will be referring to the same user. Since you will be updating the the socket.id to the same person.
For example assume that you the user who refresh the client and im the other user. so when you refresh a new socket session will be created an it will be sent to the back end along with the user name. When the data comes to the server it will get your session object from the array(sockets[data.username]) and update it with the new socketio sent from your front-end sockets[data.username] = socket.id;.
for this to happen you will have to send the user name along with the socket message. like this
socket.emit("online", {
username: "username"
});
Replying to your 2nd comment
To send data in real time the users should be online. if not you can just create an array of notifications with the following information (sender and receiver). So when the receiver logs in or clicks on the notification panel the list of shared files notification can be shown. This is just a suggestion you can come up with you own idea. Hope this helps.
In case of Non-real time data:
Server Side-:
use res.sendFile() is a way to solve this problem. In addition to file send the receiver and sender id in headers
Client Side-:
Increase the notification count if the receiver id matches the logged in user.
res.sendFile(path.join("path to file"),
{headers:{
receiverid:result[0]._id,
senderid:result[1]._id
}
});
In case of real time data:
follow the answer posted by TRomesh
Hi I'm really new to MQTT and I've read a lot of posts and blogs about it the last days, yet I seem not to fully understand the different parts needed, such as Broker, Clients.
I want two node apps to communicate with each other via a local mqtt service. As far as I understand, this mqtt service is called broker. I managed it to let 2 node apps communicate via a public broker like this:
app1 (sender)
const mqtt = require('mqtt');
// to be changed to own local server/service
const client = mqtt.connect('http://broker.hivemq.com');
client.on('connect', () => {
let id = 0;
setInterval(() => {
client.publish('myTopic', 'my message - ' + ++id);
}, 3000);
});
app2 (receiver)
const mqtt = require('mqtt');
// to be changed to own local server/service
const client = mqtt.connect('http://broker.hivemq.com');
client.on('connect', () => {
client.subscribe('myTopic');
});
client.on('message', (topic, message) => {
console.log('MSG: %s: %s', topic, message);
});
As this worked, I wanted to move on by replacing the public broker with a private one. After a while I found mqtt-server as a node package.
So I tried the following as a third node app, which is supposed to be the broker for app1 and app2:
server (MQTT broker)
fs = require('fs');
mqttServer = require('mqtt-server');
let subscriptions = [];
let servers = mqttServer(
// servers to start
{
mqtt: 'tcp://127.0.0.1:1883',
mqttws: 'ws://127.0.0.1:1884',
},
// options
{
emitEvents: true
},
// client handler
function (client) {
client.connack({
returnCode: 0
});
client.on('publish', (msg) => {
let topic = msg.topic;
let content = msg.payload.toString();
// this works, it outputs the topic and the message.
// problem is: app2 does not receive them.
// do we have to forward them somehow here?
console.log(topic, content);
});
client.on('subscribe', (sub) => {
let newSubscription = sub.subscriptions[0];
subscriptions.push(newSubscription);
console.log('New subscriber to topic:', newSubscription.topic);
});
});
servers.listen(function () {
console.log('MQTT servers now listening.');
});
Problem
After adjusting the connection-Uris (both to ws://127.0.0.1:1884) in app1 and app2 The server app receives all messages that are published and recognises that someone connected and listens to a specific topic.
But: While the server gets all those events/messages, app2 does not receive those messages anymore. Deducing that, something with this broker must be wrong, since using the public broker everything works just fine.
Any help is appreciated! Thanks in advance.
I can't get mqtt-server to work either, so try Mosca.
Mosca only needs a back end if you want to send QOS1/2 messages, it will work with out one.
var mosca = require('mosca');
var settings = {
port:1883
}
var server = new mosca.Server(settings);
server.on('ready', function(){
console.log("ready");
});
That will start a mqtt broker on port 1883
You need to make sure your clients are connecting with raw mqtt not websockets, so makes sure the urls start mqtt://
i try create a multi channel depending on the path with nodejs in the same port.
For exemple (my port is 8080) :
wwww.exemple.com:8080/channel/1
wwww.exemple.com:8080/channel/2
wwww.exemple.com:8080/channel/3
wwww.exemple.com:8080/channel/4
Each url path channel corresponds to a channel redis
For exemple (channel-X) :
var channel = 'channel-X'
getRedis.subscribe(channel);
but I do not know how to link the channel and repeat the url.
here is my code to the current time.
socket.on('connection', function(client) {
const getRedis = redis.createClient();
const sendRedis = redis.createClient();
getRedis.subscribe('channel-1');
getRedis.on("message", function(channel, message) {
client.send(message);
});
client.on('message', function(msg) {
sendRedis.publish('channel-1',msg);
});
client.on('disconnect', function() {
getRedis.quit();
sendRedis.quit();
});
});
I am a bit in the fog all proposals will be welcome:)
You cannot link your channel name to your socket.io. Because socket.io client only carry latest user infor only.
Solution 1:
You can pass the channel name from client side.
//Client side
var socket = new io.Socket();
socket.on('connect', function() {
socket.send({ChannelName:'channel-X',Message:'HI !!'});
});
//Server side
socket.on('connection', function(client) {
const getRedis = redis.createClient();
getRedis.on("message", function(channel, message) {
client.send(message);
});
client.on('message', function(msg) {
//Assume the input msg is JSON structure {ChannelName:'channel-X',Message:'HI !!'}
var data = JSON.parse(msg);
getRedis.subscribe(data.ChannelName);
});
});
Solution 2:
Use session store to store/get the channel name by socket session id.
Problem : I not understand what you need "url to managed subscriptions to channels repeat"
What is channels repeat ?