Phoenix LiveView socket and en external socket - phoenix-framework

I need help with something and excuse me if it seems trivial since I’m still learning phoenix.
I have a simple LiveView to view a position of an object in 3D space, the position comes form an external socket here is how I handle the incoming position
def handle_in("new_msg", %{"x" => posX, "y"=> posY, "z" => posZ}, socket) do
GameLive.handle_event("new_position", %{"x" => posX, "y" => posY, "z" => posZ}, socket)
broadcast!(socket, "new_msg", %{x: posX, y: posY, z: posZ})
{:reply, {:ok, %{response: "Updated"}}, socket}
end
and here is how I handle the event
def handle_event("new_position", %{"x" => posX, "y" => posY, "z" => posZ}, socket) do
{:noreply, assign(socket, %{x: posX, y: posY, z: posZ})}
end
I get this error whenever I try to update the position
[error] GenServer #PID<0.481.0> terminating
** (FunctionClauseError) no function clause matching in Phoenix.LiveView.assign/2
(phoenix_live_view) lib/phoenix_live_view.ex:1252: Phoenix.LiveView.assign(%Phoenix.Socket{assigns: %{}, channel: GameviewWeb.RoomChannel, channel_pid: #PID<0.481.0>, endpoint: GameviewWeb.Endpoint, handler: GameviewWeb.UserSocket, id: nil, join_ref: "3", joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Gameview.PubSub, ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, topic: "room:lobby", transport: :websocket, transport_pid: #PID<0.478.0>}, %{x: 5, y: 5, z: 5})
I now understand that I'm passing a different a type of socket to Phoenix.LiveView.assign/2 as it expects a Phoenix.LiveView.Socket and I'm passing a Phoenix.Socket. Thanks to Elixir Forum Community.
How should I handle such a situation, I'm looking for a solution with a good practice.
Thanks in advance.

The best solution I found is to use Pubsub that applies publisher/subscriber design pattern. Upon Pubsub notifying the subscribers, you can handle the event at each subscriber with different logic based on what you need without mixing up the sockets.

Related

How to push messages from unacked to ready

My question is similar to a question asked previously, however it does not find an answer, I have a Consumer which I want to process an action called a Web Service, however, if this web service does not respond for some reason, I want the consumer not to process the message of the RabbitMQ but I encole it to process it later, my consumer is the following one:
require File.expand_path('../config/environment.rb', __FILE__)
conn=Rabbit.connect
conn.start
ch = conn.create_channel
x = ch.exchange("d_notification_ex", :type=> "x-delayed-message", :arguments=> { "x-delayed-type" => "direct"})
q = ch.queue("d_notification_q", :durable =>true)
q.bind(x)
p 'Wait ....'
q.subscribe(:manual_ack => true, :block => true) do |delivery_info, properties, body|
datos=JSON.parse(body)
if datos['status']=='request'
#I call a web service and process the json
result=Notification.send_payment_notification(datos.to_json)
else
#I call a web service and process the body
result=Notification.send_payment_notification(body)
end
#if the call to the web service, the web server is off the result will be equal to nil
#therefore, he did not notify RabbitMQ, but he puts the message in UNACKED status
# and does not process it later, when I want him to keep it in the queue and evaluate it afterwards.
unless result.nil?
ch.ack(delivery_info.delivery_tag)
end
end
An image of RabbitMQ,
There is some way that in the statement: c hack (delivery_info.delivery_tag), this instead of deleting the element of the queue can process it later, any ideas? Thanks
The RabbitMQ team monitors this mailing list and only sometimes answers questions on StackOverflow.
Try this:
if result.nil?
ch.nack(delivery_info.delivery_tag)
else
ch.ack(delivery_info.delivery_tag)
end
I decided to send the data back to the queue with a style "producer within the consumer", my code now looks like this:
if result.eql? 'ok'
ch.ack(delivery_info.delivery_tag)
else
if(datos['count'] < 5)
datos['count'] += 1
d_time=1000
x.publish(datos.to_json, :persistent => true, :headers=>{"x-delay" => d_time})
end
end
However I was forced to include one more attribute in the JSON attribute: Count! so that it does not stay in an infinite cycle.

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

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

how can i access data inside map in elixir

i have simple chat app written using phoenix framework.
i want to access some data inside the socket
this is the method im using for that
def join("room:" <> _user, _, socket) do
IO.inspect socket
send self(), :after_join
{:ok, socket}
end
it will give nice map with all the details.
what is the best way to get all rooms(topic: "room:Testuser") available using this method
this is the sample result showed in console
[info] JOIN room:Testuser to PhoenixChat.RoomChannel
Transport: Phoenix.Transports.WebSocket
Parameters: %{}
%Phoenix.Socket{assigns: %{user: "Testuser"}, channel: PhoenixChat.RoomChannel,
channel_pid: #PID<0.409.0>, endpoint: PhoenixChat.Endpoint,
handler: PhoenixChat.UserSocket, id: nil, joined: false,
pubsub_server: PhoenixChat.PubSub, ref: nil,
serializer: Phoenix.Transports.WebSocketSerializer, topic: "room:Testuser",
transport: Phoenix.Transports.WebSocket, transport_name: :websocket,
transport_pid: #PID<0.375.0>}
[info] Replied room:Testuser :ok
The thing you are tinkering with is not map per say. It is what we usually call struct! Struct is a map with well defined fields (similar to objects you may know from other languages).
As you have already discovered when you inspect it you can read all of the key value pairs.
When you want to access field of a struct you can say struct.field. Please read tutorial on Elixir website for more information.

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.

ZeroMQ Subscribers not receiving message from Publisher over an inproc: transport class

I am fairly new to pyzmq. I am trying to understand inproc: transport class and have created this sample example to play with.
It looks a Publisher instance is publishing messages but Subscriber instances are not receiving any.
In case I move Subscriber instances into a separate process and change inproc: to a tcp: transport class, the example works.
Here is the code:
import threading
import time
import zmq
context = zmq.Context.instance()
address = 'inproc://test'
class Publisher(threading.Thread):
def __init__(self):
self.socket = context.socket(zmq.PUB)
self.socket.bind(address)
def run(self):
while True:
message = 'snapshot,current_time_%s' % str(time.time())
print 'sending message %s' % message
self.socket.send(message)
time.sleep(1)
class Subscriber(object):
def __init__(self, sub_name):
self.name = sub_name
self.socket = context.socket(zmq.SUB)
self.socket.connect(address)
def listen(self):
while True:
try:
msg = self.socket.recv()
a, b = msg.split(' ', 1)
print 'Received message -> %s-%s-%s' % (self.name, a, b)
except zmq.ZMQError as e:
logger.exception(e)
if __name__ == '__main__':
thread_a = []
for i in range(0, 1):
subs = Subscriber('subscriber_%s' % str(i))
th = threading.Thread(target=subs.listen)
thread_a.append(th)
th.start()
pub = Publisher()
pub_th = threading.Thread(target=pub.run)
pub_th.start()
There is nothing wrong, but
ZeroMQ is a wonderfull toolbox.It is full of smart, bright and self-adapting services under the hood, that literally save our poor lives in many ways.Still it is worth to read and obey a few rules from the documentation.
inproc transport class has one such. .bind() first, before .connect()-s
[ Page 38, Code Connected, Volume I ]... inproc is an inter-thread signalling transport ... it is faster than tcp or ipc. This transport has a specific limitation compared to tpc and icp: the server must issue a bind before any client issues a connect. This is something future versions of ØMQ may fix, but at present this defines how you use inproc sockets.
So, as an example:
if __name__ == '__main__':
pub = Publisher()
pub_th = threading.Thread( target = pub.run )
pub_th.start()
# give it a place to start before .connect()-s may take place
# give it a time to start before .connect()-s may take place
sleep(0.5)
thread_a = []
for i in range( 0, 1 ):
subs = Subscriber( 'subscriber_%s' % str( i ) )
th = threading.Thread( target = subs.listen )
thread_a.append( th )
th.start()

Resources