How to start learning socket programming using node.js? - node.js

I am interested to learn socket programming and to create a chat application using Node.js.
There are two different things exist
1) net.Socket class exposed by built-in net module
2) socket.io package. (Node.js implementation)
Which one of them can be referred?
If socket.io is a wrapper for net.Socket, why it's not imported by socket.io?

I am not that familiar with net.socket but if you are new to sockets in node, I would recommend socket.io. There are a lot more resources available and I would say it is a lot more commonly used.
Practicing by building a chat application is a good use case.
You can even follow socket.io's toturial here

I just finished writing an app using socket.io, nodejs (express) and mongodb. For front end, I used ios 11.
I have to say that it is very easy to use these technologies given all the knowledge out there.
The basic socket.io flow works with duplex communication between front end (client) and backend (client).
1. A client "emits" an event to server
2. Server acts upon it can can choose to emit an even back.
3. Server receives the event and acts on it..
For example:
In my ios app:
func sendMessageToTheRoom(message: String, name: String, roomName: String) {
socket.emit("messageToRoom", message, name, roomName)
}
Then client responds like so:
clientSocket.on('messageToRoom', function(message, name, roomName) {
console.log(message);
console.log(name);
console.log(roomName);
io.to(roomName).emit('messageReceived', message, name, roomName);
});
And finally, the receiver clients act upon server's emit action like so:
socket.on("messageReceived") { (dataArray, socketAck) -> Void in
var messageDictionary = [String: AnyObject]()
messageDictionary["message"] = dataArray[0] as! String as AnyObject
messageDictionary["name"] = dataArray[1] as! String as AnyObject
messageDictionary["roomName"] = dataArray[2] as! String as AnyObject
print(messageDictionary["message"] as! String)
if messageDictionary["name"] as? String != self.nickName {
DispatchQueue.main.async {
let message = Message()
message.text = messageDictionary["message"] as? String
message.isSender = false
self.messages?.append(message)
self.chatCollectionView.reloadData()
}
}
NotificationCenter.default.post(name: Notifications.newMessageSent.name, object: nil)
}
But this is just an example.

Related

Messages not coming thru to Azure SignalR Service

I'm implementing Azure SignalR service in my ASP.NET Core 2.2 app with React front-end. When I send a message, I'm NOT getting any errors but my messages are not reaching the Azure SignalR service.
To be specific, this is a private chat application so when a message reaches the hub, I only need to send it to participants in that particular chat and NOT to all connections.
When I send a message, it hits my hub but I see no indication that the message is making it to the Azure Service.
For security, I use Auth0 JWT Token authentication. In my hub, I correctly see the authorized user claims so I don't think there's any issues with security. As I mentioned, the fact that I'm able to hit the hub tells me that the frontend and security are working fine.
In the Azure portal however, I see no indication of any messages but if I'm reading the data correctly, I do see 2 client connections which is correct in my tests i.e. two open browsers I'm using for testing. Here's a screen shot:
Here's my Startup.cs code:
public void ConfigureServices(IServiceCollection services)
{
// Omitted for brevity
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = authority;
jwtOptions.Audience = audience;
jwtOptions.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// Check to see if the message is coming into chat
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/im")))
{
context.Token = accessToken;
}
return System.Threading.Tasks.Task.CompletedTask;
}
};
});
// Add SignalR
services.AddSignalR(hubOptions => {
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(10);
}).AddAzureSignalR(Configuration["AzureSignalR:ConnectionString"]);
}
And here's the Configure() method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Omitted for brevity
app.UseSignalRQueryStringAuth();
app.UseAzureSignalR(routes =>
{
routes.MapHub<Hubs.IngridMessaging>("/im");
});
}
Here's the method I use to map a user's connectionId to the userName:
public override async Task OnConnectedAsync()
{
// Get connectionId
var connectionId = Context.ConnectionId;
// Get current userId
var userId = Utils.GetUserId(Context.User);
// Add connection
var connections = await _myServices.AddHubConnection(userId, connectionId);
await Groups.AddToGroupAsync(connectionId, "Online Users");
await base.OnConnectedAsync();
}
Here's one of my hub methods. Please note that I'm aware a user may have multiple connections simultaneously. I just simplified the code here to make it easier to digest. My actual code accounts for users having multiple connections:
[Authorize]
public async Task CreateConversation(Conversation conversation)
{
// Get sender
var user = Context.User;
var connectionId = Context.ConnectionId;
// Send message to all participants of this chat
foreach(var person in conversation.Participants)
{
var userConnectionId = Utils.GetUserConnectionId(user.Id);
await Clients.User(userConnectionId.ToString()).SendAsync("new_conversation", conversation.Message);
}
}
Any idea what I'm doing wrong that prevents messages from reaching the Azure SignalR service?
It might be caused by misspelled method, incorrect method signature, incorrect hub name, duplicate method name on the client, or missing JSON parser on the client, as it might fail silently on the server.
Taken from Calling methods between the client and server silently fails
:
Misspelled method, incorrect method signature, or incorrect hub name
If the name or signature of a called method does not exactly match an appropriate method on the client, the call will fail. Verify that the method name called by the server matches the name of the method on the client. Also, SignalR creates the hub proxy using camel-cased methods, as is appropriate in JavaScript, so a method called SendMessage on the server would be called sendMessage in the client proxy. If you use the HubName attribute in your server-side code, verify that the name used matches the name used to create the hub on the client. If you do not use the HubName attribute, verify that the name of the hub in a JavaScript client is camel-cased, such as chatHub instead of ChatHub.
Duplicate method name on client
Verify that you do not have a duplicate method on the client that differs only by case. If your client application has a method called sendMessage, verify that there isn't also a method called SendMessage as well.
Missing JSON parser on the client
SignalR requires a JSON parser to be present to serialize calls between the server and the client. If your client doesn't have a built-in JSON parser (such as Internet Explorer 7), you'll need to include one in your application.
Update
In response to your comments, I would suggest you try one of the Azure SignalR samples, such as
Get Started with SignalR: a Chat Room Example to see if you get the same behavior.
Hope it helps!

multi redux store on node socket server

I'm currently learning react and redux and I am making a practical project in order to be face of some issues.
The project is a multiplayer labyrinth so I decided that using a socket.io server and also that player will be limited in a labyrinth game context, so I am using room (io namespace).
All the game context is store in redux on the node server and is connect to the clients UI.
because of that I have multi-Rooms server I want a different store for each room/game. In absolute I can combine reducers but that mean lot of performance lost and need a dark architecture of action and store.
the problem is that I don't know how create multi-store on node.js
currently the server work in this case ->
on client connect -> if the current room is full generate a new game else assign on current room
public generate = (server:SocketIO.Server) =>{
let newIDGenetator = new Idgenerator();
let store = redux.createStore(labActionReducer);
let room = server.of('/room'+this.labs.length);
let socketHandler = new SocketHandler(room,store,newIDGenetator);
this.labs.push(socketHandler);
this is the function call for generate a new game:
-> create idgenerator which is just an incr who's return is value cast in string
-> create a new store
-> create a io namespace
-> create a new socketHandler which implements:
room.on('connect' , (client,store) =>
client.on('foo',()=>
store.dispatch({type:bar});
)
)
and listen store in order to send the new state to each clients of the handling room
the first room work well but when a second root is create the client show the data of the first room state when I attempt to get the initial state
The client is connect on the good namespace,
So I suppose that createStore do not create a new store. Could you help me to create a new store independent of the first? if need more details I can give you the GitHub link of the project.
thank you for reading
edit: socketHandler code:
export class SocketHandler{
private playersId = {};
private store:Store<Lab,LabActionTypes>;
constructor(server:Namespace,store:Store<Lab,LabActionTypes>,idgenerator:Idgenerator){
this.store = store;
store.subscribe(()=>{
console.log(this.store.getState());
for(let playerId in this.playersId){
let newState = this.store.getState() //utiliser reducer_relatif(state,playerId)
this.playersId[playerId].emit('gridChanged',newState);
}
})
server.on('connect',(client)=>{
let newID = idgenerator.generetaId()
this.playersId[newID]=client;
store.dispatch(action.NewPlayer(newID));
NewClient(client,store,this);
});
}
public getPlayerBySocket = (socket:SocketIO.Socket)=>{
for(let playerId in this.playersId){
if(this.playersId[playerId]===socket){
return playerId;
}
}
}
}
export const NewClient = (client: SocketIO.Socket,store:redux.Store<Lab,LabActionTypes>,socketHandler:SocketHandler) => {
client.on('up',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveUp(player,store));
console.log(JSON.stringify(store.getState()));
})
client.on('down',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveDown(player,store));
})
client.on('left',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveLeft(player,store));
})
client.on('right',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveRight(player,store));
})

Using Asp.net MVC 5 with RabbitMQ and SignalR

I have an asp.net MVC 5 application. Now I want to have a new feature allow me to receive GPS signal from devices and update devices real time in web client side.
My plan is to use RabbitMQ to handle message queue and SignalR to notify clients for postion update
My code in Rabbit consumer console application like this
var client = GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients;
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
var queueName = channel.QueueDeclare().QueueName;
channel.ExchangeDeclare("mychannel", "fanout");
channel.QueueBind(queueName, "mychannel", "");
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("[x] Receive {0}", message);
client.All.hello(message);
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
Console.WriteLine("Press Enter to quit");
Console.ReadLine();
}
}
But the code is not fired when I produce a message to RabbitMQ. If I remove code of signalR client:
var client = GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients;
things work as expected and I can receive message as usual
My question is:
Is my approach correct
if the approach correct then how to make it work?
Many thanks

How to share dynamic objects across workers?

I'm trying to make a game, which works on rooms, lobby and such (imagine the chat app, except with additional checks/information storing).
Let's say, I have a module room.js
var EventEmitter = require('events');
class Room extends EventEmitter {
constructor (id, name) {
super();
this.id = id;
this.name = name;
this.users = [];
}
}
Room.prototype.addUser = function (user) {
if(this.users.indexOf(user) === -1) {
this.users.push(user);
this.emit('user_joined', user);
} else {
/* error handling */
}
};
module.exports = {
Room: Room,
byId: function (id) {
// where should I look up?
}
};
How can I get exactly this object (with events)? How can I access events emitted by this object?
In a single instance of node, I would do something like:
var rooms = [];
var room = new Room(1234, 'test room');
room.on('user_joined', console.log);
rooms.push(room);
Also, I don't quite understood how Redis is actually helping (is it replacement of EventEmitter?)
Regards.
EDIT: Would accept PM2 solutions too.
Instead of handling rooms in Node, you can replace them with channels in Redis).
When a new client wants to join in a room, the NodeJS app returns it the ID of this given room (that is to say the name of the channel), then the client suscribes to the selected room (your client is directly connected to Redis.
You can use a Redis Set to manage the list of rooms.
In this scenario, you don't need any event emitter, and your node servers are stateless.
Otherwise, it would mean Redis would be exposed on the Internet (assuming your game is public), so you must activate Redis authentication. A major problem with this solution is that you have to give the server password to all clients, so it's definitely unsecure.
Moreover, Redis' performances allow brute force attacks so exposing it on Internet is not recommended. That's why I think all communications should go through a Node instance, even if Redis is used as a backend.
To solve this, you can use socket.io to open sockets between Node and your clients, and make the Node instances (not the client) subscribe to the Redis channel. When a message is published by Redis, send it to the client through the socket. And add a layer of authentication to ensure only valid clients connect to a given channel.
Event emitter is not required. It's the Redis client which will be an event emitter (like in this example based on ioRedis)

how can I make private chat rooms with sockjs?

I am trying to make a chat system where only two users are able to talk to each other at a time ( much like facebook's chat )
I've tried multiplexing, using mongoDB's _id as the name so every channel is unique.
The problem I'm facing is that I cannot direct a message to a single client connection.
this is the client side code that first sends the message
$scope.sendMessage = function() {
specificChannel.send(message)
$scope.messageText = '';
};
this is the server side receiving the message
specificChannel.on('connection', function (conn) {
conn.on('data', function(message){
conn.write('message')
}
}
When I send a message, to any channel, every channel still receives the message.
How can I make it so that each client only listens to the messages sent to a specific channel?
It appeared that SockJS doesn't support "private" channels. I used the following solution for a similar issue:
var channel_id = 'my-very-private-channel'
var connection = new SockJS('/pubsub', '')
connection.onopen = function(){
connection.send({'method': 'set-channel', 'data': {'channel': channel_id}})
}
Backend solution is specific for every technology stack so I can't give a universal solution here. General idea is the following:
1) Parse the message in "on_message" function to find the requested "method name"
2) If the method is "set-channel" -> set the "self.channel" to this value
3) Broadcast further messages to subscribers with the same channel (I'm using Redis for that, but it also depends on your platform)
Hope it helps!

Resources