how to send message to different user in phoenix framework chat app - phoenix-framework

I am working with phoenix framework to create different type chat app. In my case, we have chat rooms but not functioning as a normal chat room.
Every user has his own room he can join to his room using different devices (mobile, pc, some other sources).
User A has his own room and user B has his own room, these two members do not connect to a single room as in a normal scenario in the real world.
Now my user A wants to send a message to user B
message data eg:
from : A
to :B
message : test message
This is a snippet from app.js I used to connect to the user's specific room :
let room = socket.channel("room:"+ user, {})
room.on("presence_state", state => {
presences = Presence.syncState(presences, status)
console.log(presences)
render(presences)
})
This is the snippet from back-end for join room function
/web/channel/RoomChannel.ex
def join("room:" <> _user, _, socket) do
send self(), :after_join
{:ok, socket}
end
But now I am stuck in the middle because I cannot find a way to send messages between users.
eg: Cannot identify way to deliver User A's message to user B using this specific scenario
This is the basic architect of this chat app :
This is rest of the code in Roomchannel file
def handle_info(:after_join, socket) do
Presence.track(socket, socket.assigns.user, %{
online_at: :os.system_time(:millisecond)
})
push socket, "presence_state", Presence.list(socket)
{:noreply, socket}
end
def handle_in("message:new", message, socket) do
broadcast! socket, "message:new", %{
user: socket.assigns.user,
body: message,
timestamp: :os.system_time(:millisecond)
}
milisecondstime = :os.system_time(:millisecond)
[room, user] = String.split(Map.get(socket, :topic), ":")
data = Poison.encode!(%{"created_at" => :os.system_time(:millisecond), "updated_at" => :os.system_time(:millisecond), "uuid" => UUID.uuid1(), "date" => :os.system_time(:millisecond), "from" => user, "to" => user, "message" => message})
Redix.command(:redix, ["SET", UUID.uuid1(), data])
{:noreply, socket}
end
Can anyone show me some way by which I can pass messages between user's chat rooms?
I'm using redis to store data and
this is the basic chat app I am following thanks to that developer.

Look into Phoenix.Endpoint.broadcast
You can send message to a given topic using the broadcast/3 function
Another way you can go about this is to subscribe each user to a unique private topic when the user joins the channel join function
def join("room:" <> _user, _, socket) do
send self(), :after_join
MyApp.Endpoint.subscribe("private-room:" <> user) # subscribe the connecting client to their private room
{:ok, socket}
end
Then in handle_in("message:new",message, socket) extract the receipient user id from the message payload and call
Endpoint.broadcast
def handle_in("message:new", message, socket) do
MyApp.Endpoint.broadcast!("private-room:" <> to_user , "message:new",%{
user: socket.assigns.user,
body: message,
timestamp: :os.system_time(:millisecond)
}) #sends the message to all connected clients for the to_user
{:noreply, socket}
end

Related

Can we connect to multiple clients/connections for same room that is emitted using flask socketio?

emit('send_to_user',data,room=user_id)
How do we send the same emit to multiple clients/connections on client side?
You have to put all the clients in a room, and then address the message to that room.
Documentation for rooms is here.
Example of adding and removing a client from a room:
from flask_socketio import join_room, leave_room
#socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
send(username + ' has entered the room.', room=room)
#socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
send(username + ' has left the room.', room=room)
Once your clients are in the room, you can emit to all the people in the room just by setting the room argument to the room name.

How can I send messages to specific client using Faye Websockets?

I've been working on a web application which is essentially a web messenger using sinatra. My goal is to have all messages encrypted using pgp and to have full duplex communication between clients using faye websocket.
My main problem is being able to send messages to a specific client using faye. To add to this all my messages in a single chatroom are saved twice for each person since it is pgp encrypted.
So far I've thought of starting up a new socket object for every client and storing them in a hash. I do not know if this approach is the most efficient one. I have seen that socket.io for example allows you to emit to a specific client but not with faye websockets it seems ? I am also considering maybe using a pub sub model but once again I am not sure.
Any advice is appreciated thanks !
I am iodine's author, so I might be biased in my approach.
I would consider naming a channel by the used ID (i.e. user1...user201983 and sending the message to the user's channel.
I think Faye will support this. I know that when using the iodine native websockets and builtin pub/sub, this is quite effective.
So far I've thought of starting up a new socket object for every client and storing them in a hash...
This is a very common mistake, often seen in simple examples.
It works only in single process environments and than you will have to recode the whole logic in order to scale your application.
The channel approach allows you to scale using Redis or any other Pub/Sub service without recoding your application's logic.
Here's a quick example you can run from the Ruby terminal (irb). I'm using plezi.io just to make it a bit shorter to code:
require 'plezi'
class Example
def index
"Use Websockets to connect."
end
def pre_connect
if(!params[:id])
puts "an attempt to connect without credentials was made."
return false
end
return true
end
def on_open
subscribe channel: params[:id]
end
def on_message data
begin
msg = JSON.parse(data)
if(!msg["to"] || !msg["data"])
puts "JSON message error", data
return
end
msg["from"] = params[:id]
publish channel: msg["to"].to_s, message: msg.to_json
rescue => e
puts "JSON parsing failed!", e.message
end
end
end
Plezi.route "/" ,Example
Iodine.threads = 1
exit
To test this example, use a Javascript client, maybe something like this:
// in browser tab 1
var id = 1
ws = new WebSocket("ws://localhost:3000/" + id)
ws.onopen = function(e) {console.log("opened connection");}
ws.onclose = function(e) {console.log("closed connection");}
ws.onmessage = function(e) {console.log(e.data);}
ws.send_to = function(to, data) {
this.send(JSON.stringify({to: to, data: data}));
}.bind(ws);
// in browser tab 2
var id = 2
ws = new WebSocket("ws://localhost:3000/" + id)
ws.onopen = function(e) {console.log("opened connection");}
ws.onclose = function(e) {console.log("closed connection");}
ws.onmessage = function(e) {console.log(e.data);}
ws.send_to = function(to, data) {
this.send(JSON.stringify({to: to, data: data}));
}.bind(ws);
ws.send_to(1, "hello!")

Send data to a decent user with Kemal over websocket

How can i send data data to a decent user connected via websockets? I know,
Websocket connections yields the context, but how can i filter a decent socket connection for sending data to only 1 (or some) connected user(s) depending on context (env)?
SOCKETS = [] of HTTP::WebSocket
ws "/chat" do |socket,env|
room = env.params.query["room"]
SOCKETS << socket
socket.on_message do |message|
SOCKETS.each { |socket| socket.send message}
end
socket.on_close do
SOCKETS.delete socket
end
end
Must socket contain the room or needs SOCKETS to be a Hash?
You can store sockets as hash and give id to your clients and then you can send saved socket of your recent client.
I mean that
SOCKETS = {} of String => HTTP::WebSocket
socket.on_message do |message|
j = JSON.parse(message)
case j["type"]
when "login"
user_id = j["id"].to_s
SOCKETS[user_id] = socket
when "whisper"
to = j["to"].to_s
from = j["from"]
user = SOCKETS[to].send("#{from} is whispering you!")
end
something like that.

Parallel POST requests to DB with Ruby/DataMapper

I'am trying to implement kind of a matchmaking REST service for the game.
My matchmaking table is simple as
ID (Serial)
Client_Name_1 (String)
Client_Name_2 (String)
Basic idea is that when client sends me his "Name" I check if there is a row with Client_Name_2 = NULL and update this row.
If there is no "NULL" rows I create new row with Client_Name_1 as recieved client "Name"
Here is the router code:
post '/api/start' do
#parsing a request with client name
body = JSON.parse request.body.read
#checking if there is a row to update
t = Match.first(:Client_Name_2 => nil)
#matchmaking client to existing game if found
if t != nil
t.update(
Client_Name_2: body['name']
)
response = {:playerIs => '2', :id => t['id']}
#creating new game if nowhere to matchmake
elsif
m = Match.create(
Client_Name_1: body['name']
)
Task.create(
Match_ID: m['id']
)
response = {:playerIs => '1', :id => m['id']}
end
status 201
response.to_json
end
The tricky part for me is that when this router called simultaneously at the very same second from several different clients, all of these requests get the same row id from
#checking if there is a row to update
t = Match.first(:Client_Name_2 => nil)
and this code updates the same row for each request.
Is there a simple solution for this or i will have to implement something like queue to handle such simultaneous requests consequentially?
I will really appreciate your advice.

How to exclude some users when broadcast?

As broadcast! will send message to all users subscribed to topic except sender, is it possible to except certain users? or send to a specific user?
I need users to push events to a channel, but only admin user will receive messages, other users will not receive messages, but only send them.
I can solve this at client side by simply let users ignore messages they receive via broadcast! and only admin user process received messages, but how to solve this at server side?
In short, join a channel, but can read only, or send only?
If you store the user on the socket as explained in https://hexdocs.pm/phoenix/Phoenix.Token.html
defmodule MyApp.UserSocket do
use Phoenix.Socket
def connect(%{"token" => token}, socket) do
case Phoenix.Token.verify(socket, "user", token, max_age: 1209600) do
{:ok, user_id} ->
socket = assign(socket, :user, Repo.get!(User, user_id))
{:ok, socket}
{:error, _} -> #...
end
end
end
Then you can check for the users admin status in the handle_out function for your channel documented here:
defmodule HelloPhoenix.RoomChannel do
intercept ["new_msg"]
...
def handle_out("new_msg", payload, socket) do
if socket.assigns.user.admin do
push socket, "new_msg", payload
end
{:noreply, socket}
end
end
Depending on your volume of messages and number of admins, you may consider having an admin-specific channel for these events. This would prevent messages being sent to processes for non-admin users instead of simply ignoring them.

Resources