Broadcast to different channel Phoenix 1.1.6 - sockets

I'm trying to broadcast to a different channel in my app but I can not get it to work. Also I trying to write a test but I'm not sure how.
From what I can gather I succeed in broadcasting the message from the notification_channel but it's not received in chat_channel.
Notification should send to chat.
notification_channel.ex
def handle_in("new:group:recommendation", msg, socket) do
payload = %{
message: msg["message"],
url: msg["url"],
title: msg["title"],
user_name: get_name_of_user(socket.assigns.user_grapqhl_id),
user_grapqhl_id: socket.assigns.user_grapqhl_id
}
IO.puts "incomming"
IO.inspect msg
Enum.map(msg["groups"], fn(x) ->
App.Endpoint.broadcast_from! self(), "chat:"<>x,
"new:recommendation", payload
end)
{:reply, :ok, socket}
end
chat_channel.ex
def handle_in("new:recommendation", msg, socket) do
IO.puts "i am a recommendation !"
IO.inspect msg
chat_msg = %{
"creator_id" => msg["user_grapqhl_id"],
"text" => msg["message"],
"creator_name" => msg["user_name"]
}
broadcast! socket, "new:msg", create_chat_msg(chat_msg,socket)
{:reply, :ok, socket}
end
test
test "do we send a new:recommendation to chat ?", %{guardian_token: guardian_token} do
nils_base_64 = Base.encode64("user:nils")
{:ok, socket} = connect(UserSocket, %{})
{:ok, _, socket1} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
{:ok, _, socket} = subscribe_and_join(socket1, "chat:Y2hhdDpjaGF0Mw==", %{"guardian_token" => guardian_token})
payload = %{
"message" => "look at this cool thing!",
"url" => "link to stuff",
"title" => "AWESOME EVENT",
"groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
}
reply = %{message: "look at this cool thing!", title: "AWESOME EVENT", url: "link to stuff", user_grapqhl_id: nils_base_64, user_name: "Nils Eriksson"}
ref = push socket1, "new:group:recommendation", payload
assert_reply ref, :ok
assert_broadcast "new:recommendation", ^reply
end
This test passes and i can get it to fail by changing the reply
or commenting out the broadcast. I can not get it to fail by changing the handle_in to receive fail:please in chat_channel.
That is something that it would complain on if i would send change this
ref = push socket1, "new:group:recommendation", payload
to ref = push socket, "new:group:recommendation", payload not supprising in that case.
This is what is on the wire.
Process mailbox:
%Phoenix.Socket.Message{event: "init:msgs", payload: %{messages: []}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}
%Phoenix.Socket.Broadcast{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, topic: "chat:Y2hhdDpjaGF0Mw=="}
%Phoenix.Socket.Message{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}
I use channel authentication since the elm package i use does not support authentication on socket level yet. so this is what it looks like in chat
def join("chat:" <> chat_id, %{"guardian_token" => token}, socket) do
IO.puts chat_id
case sign_in(socket, token) do
{:ok, authed_socket, _guardian_params} ->
Process.flag(:trap_exit, true)
send(self, {:after_join})
[_type, node_chat_id] = Node.from_global_id(chat_id)
{:ok, assign(authed_socket, :chat_id, node_chat_id)}
{:error, reason} ->
IO.puts "Can't join channel cuz: " <> reason
# handle error TODO
end
end

Since you use broadcast_from/4 from your Endpoint.You should use handle_info/2 in your chat_channel:
alias Phoenix.Socket.Broadcast
...
def handle_info(%Broadcast{topic: _, event: ev, payload: payload}, socket) do
IO.puts ev
IO.inspect payload
# do something with ev and payload( push or broadcast)
{:noreply, socket}
end
Or you can listen that event from your client:
chatChannel.on("new:recommendation", resp => {
// doSomething with response
}
Edit:
Let's explain a bit about how the channel and PubSub system work.
When you want to broadcast or push an event with payload.First it will send to the PubSub system and then the PubSub system will send it to all the subscriber process (channel) with the topic that channel registered itself with the PubSub system.
And when you use Endpoint.broadcast_from/4 to broadcast an event from your server.The PubSub system will receive an event with payload and broadcast that event to the topic that channel registered.
The channel will trigger handle_out callback and push the message to the client.
So in your chat_channel you don't need to handle_in "new:recommendation" event.Your client just need to listen to that event.
chatChannel.on("new:recommendation", resp => {
// do something with response
}
And let me rewrite your test:
setup do
nils_base_64 = Base.encode64("user:nils")
{:ok, socket} = connect(UserSocket, %{})
{:ok, _, socket} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
{:ok, socket: socket}
end
test "do we send a new:recommendation to chat ?", %{socket: socket} do
MyApp.Endpoint.subscribe("chat:Y2hhdDpjaGF0Mw==")
payload = %{
"message" => "look at this cool thing!",
"url" => "link to stuff",
"title" => "AWESOME EVENT",
"groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
}
reply = %Phoenix.Socket.Broadcast{message: "look at this cool thing!",
title: "AWESOME EVENT",
url: "link to stuff",
user_grapqhl_id: nils_base_64,
user_name: "Nils Eriksson"}
ref = push socket, "new:group:recommendation", payload
assert_reply ref, :ok
assert_receive ^reply
end
By subscribe to the topic that you want to listen you can make sure that your channel is received the message with assert_receive.
That is the way to test broadcast to a different channel.
Give it a try and tell me.The test will pass.

Use App.Endpoint.broadcast topic, event, payload with the topic of your chat channel. It should work.

Related

io.emit() didnot send the message to the connected clients ( in browser console)

Client side --> emitting the 'message' event
socket.emit("message", {
from: "Santosh",
to: "Leela",
text: "hey !Leela "
});
//server side --. listening the event
io.on("connection", (socket) => {
console.log("a new user just connected
",socket.id);
socket.on("message", (payload) => {
io.emit("new-message", payload);
});
});
Emitting the "message " event from the client side
but server did not broadcast the message to the connected client (in the console)

Server Sent Events with Kitura

I try to do a client-server application with on the client side a Angular2/typescript web site and on the server side a Kitura server in Swift on Mac OSX.
On the client side, the typescript code instanciates an EventSource object :
this.eventSource = new EventSource(this.webSocketServerUrl);
this.eventSource.onopen = (event: Event): any => {
console.log("ServerNotificationsService.onopen - " + JSON.stringify(event) + " " + this.eventSource.readyState);
event.stopPropagation();
return null;
}
this.eventSource.onerror = (event: sse.IOnMessageEvent) => {
console.log("ServerNotificationsService.onerror - " + JSON.stringify(event) + " " + this.eventSource.readyState);
}
this.eventSource.onmessage = (event: Event): any => {
console.log("ServerNotificationsService.onmessage - " + JSON.stringify(event) + " " + this.eventSource.readyState);
event.stopPropagation();
return null;
}
console.log("ServerNotificationsService.constructor - " + this.eventSource.readyState);
On the server side, my code to handle the GET request looks like this :
router.get("/notifications") { request, response, next in
response.headers.append("Access-Control-Allow-Origin", value: "*")
if((request.accepts(header: "Accept", type: "text/event-stream")) != nil)
{
response.headers.append("content-type", value: "text/event-stream; charset=utf-8")
response.headers.append("cache-control", value: "no-cache")
response.headers.append("connection", value: "keep-alive")
try response.end()
Logger.sharedInstance.verbose(msg: "Request GET /notifications OK")
}
else
{
try response.status(.internalServerError).end()
Logger.sharedInstance.verbose(msg: "Request GET /notifications internalServerError")
}
next()
}
and to handle the post request :
router.post("/notifications") { request, response, next in
Logger.sharedInstance.verbose(msg: "Request POST /notifications ...")
response.headers.append("Access-Control-Allow-Origin", value: "*")
response.headers.append("content-type", value: "text/event-stream; charset=utf-8")
response.headers.append("cache-control", value: "no-cache")
response.headers.append("connection", value: "keep-alive")
while (true)
{
// wait here 5s. for the <nextMessage>
response.send(<nextMessage>)
try response.end()
Logger.sharedInstance.verbose(msg: "Request POST /notifications OK")
next()
}
}
The problem is that on the client side I receive the onopen notification, the event source's readyState pass to "Open" (1) but I receive immediately after a onerror notification and the readyState pass to "Connecting" (0). And so on : connecting, close, connecting, close, ... And in consequence the post request is never executed.
I will appreciate some help to have a code that maintains an open connexion.
Thank you,
Notux
Kitura does not currently support persistent, open HTTP connections. However, you might be able to replicate the functionality using WebSocket instead of server-sent events over HTTP (you will need to rewrite your frontend code to use WebSockets instead of EventSource):
https://github.com/IBM-Swift/Kitura-WebSocket
And example Kitura-WebSocket app can be found here:
https://github.com/IBM-Swift/Kitura-Chat-Server

Displaying rethinkDB tables in real time in Phoenix Framework

I'm attempting to go a step further from my previous question regarding tables from RethinkDB in Phoenix.
Now I'm attempting to retrieve them over a channel in order to display in real-time, new insertions to the table.
I'm already inserting into the table through the handle_in function:
def handle_in("new_person", %{"firstName" => firstName, "lastName" => lastName}, socket) do
broadcast! socket, "new_person", %{firstName: firstName, lastName: lastName}
table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
{:noreply, socket}
end
And in the app.js:
dbInsert.on("click", event => { //Detect a click on the dbInsert div (to act as a button)
//Use a module from the channel to create a new person
chan.push("new_person", {firstName: firstName.val(), lastName: lastName.val()});
// Clear the fields once the push has been made
firstName.val("");
lastName.val("");
});
chan.join().receive("ok", chan => {
console.log("Ok");
});
Which function should I use to handle the:
table("users")
|> RethinkExample.Database.run
And how should I render the data if it's now a channel and not html?
I can render the inserted person with HTML+Javascript but what I want is to retrieve the new user from the DB and render it with my other table results in real-time.
Here's how I'm at visually speaking:
users.html.eex
<div class="jumbotron">
<div id="userList">
<%= for %{"first_name" => first_name, "last_name" => last_name} <- #users.data do %>
<p><%= "#{first_name} #{last_name}" %>!</p>
<% end %>
</div>
</div>
<div class="dbOperation">
First name: <input type="text" id="firstName"><br>
Last name: <input type="text" id="lastName"><br>
<div id="dbInsert">Insert</div>
<br>
<div id="userToInsert">User to insert: </div>
</div>
user_controller.ex
defmodule RethinkExample.UsersController do
use RethinkExample.Web, :controller
use RethinkDB.Query
def users(conn, _params) do
# List all elements of a table from the database
q = table("users")
# Query for filtering results:
# |> filter(%{last_name: "Palmer"})
|> RethinkExample.Database.run #Run the query through the database
render conn, "users.html", users: q #Render users searched on the users template
end
end
people_channel.ex
defmodule RethinkExample.PeopleChannel do
use Phoenix.Channel
use RethinkDB.Query
#Handles the insert subtopic of people
def join("people:insert", auth_msg, socket) do
{:ok, socket}
end
# handles any other subtopic as the people ID, ie `"people:12"`, `"people:34"`
def join("people:" <> _private_room_id, _auth_msg, socket) do
{:error, %{reason: "unauthorized"}}
end
def handle_in("new_person", %{"firstName" => firstName, "lastName" => lastName}, socket) do
broadcast! socket, "new_person", %{firstName: firstName, lastName: lastName}
query = table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
new_person = %{"id": hd(query.data["generated_keys"]), "firstName": firstName, "lastName": lastName}
broadcast! socket, "new_person", new_person
{:noreply, socket}
end
def handle_out("new_person", payload, socket) do
push socket, "new_person", payload
{:noreply, socket}
end
end
app.js
import {Socket} from "phoenix"
let App = {
}
export default App
// Fetch fields from HTML through Jquery
let firstName = $("#firstName")
let lastName = $("#lastName")
let dbInsert = $("#dbInsert")
let userToInsert = $("#userToInsert")
let userList = $("#userList")
let socket = new Socket("/ws") //Declare a new socket
socket.connect() //Connect to the created socket
let chan = socket.chan("people:insert", {}) //Assign the people insertion channel to the socket
dbInsert.on("click", event => { //Detect a click on the dbInsert div (to act as a button)
//Use a module from the channel to create a new person
chan.push("new_person", {firstName: firstName.val(), lastName: lastName.val()});
// Clear the fields once the push has been made
firstName.val("");
lastName.val("");
})
chan.on("new_person", payload => {
userToInsert.append(`<br/>[${Date()}] ${payload.firstName} ${payload.lastName}`);
console.log("New Person", payload);
userList.append(`<br><p> ${payload.firstName} ${payload.lastName}!</p>`);
})
chan.join().receive("ok", chan => {
console.log("Ok");
})
You need a handle_out function in your channel that you listen to for the insert. If you use broadcast_from! then the sender will be excluded, if you use broadcast! then the sender will also receive the message.
Add the following to your channel:
def handle_out("new_person", payload, socket) do
push socket, "new_person", payload
{:noreply, socket}
end
And the following to your JS client:
chan.on("new_person", payload => {
console.log("New Person", payload);
});
The Channel documentation is available at http://www.phoenixframework.org/docs/channels
edit
When inserting a record in Rethink - the output looks like:
%RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0,
"generated_keys" => ["7136199a-564b-42af-ad49-5c84cbd5b3e7"],
"inserted" => 1, "replaced" => 0, "skipped" => 0, "unchanged" => 0}}
We know that the data you get back from a rethink query looks like:
{"first_name" => "John",
"id" => "57c5d0d2-5285-4a24-a999-8bb7e2081661", "last_name" => "Smith"},
So - to broadcast the new record to the browser, we want to replicate this data structure so if you change your handle_in function to:
def handle_in("new_person", %{"firstName" => first_name, "lastName" => last_name}, socket) do
query = table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
new_person = %{"id": hd(query.data["generated_keys"]), "first_name": first_name, "last_name": last_name}
broadcast! socket, "new_person", new_person
{:noreply, socket}
end
Then - if you follow the steps above with the handle_out and chat.on then you will see the person logged out in your JavaScript console. From there - you can append it to your DOM using Javascript.

calling 2 https requests simultaneously in hubot coffescript

module.exports = (robot) ->
robot.respond /log (.*)/i, (msg) ->
group = "test"
incident_message = msg.match[0]
service_key = keys[group]
curtime = new Date().getTime()
incident_key = "hubot/#{curtime}"
reporter = msg.message.user.name
query = {
"service_key": service_key,
"incident_key": incident_key,
"event_type": "trigger",
"description": "Change Log #{reporter}",
"details": "#{incident_message}"
}
string_query = JSON.stringify(query)
content_length = string_query.length
msg
.http("https://events.pagerduty.com/generic/2010-04-15/create_event.json")
.headers
"Content-type": "application/json",
"Content-length": content_length
.post(string_query) (err, res, body) ->
result = JSON.parse(body)
if result.status == "success"
msg.send "Your log has been sent"
else
msg.send "There was an error sending your log."
msg
.http("https://xyz.pagerduty.com/api/v1/incidents/#{incident_key}/acknowledge")
I am trying to auto acknowledge an event triggered in pagerduty. The first http request takes effect. But the second http request (last line of the code) never takes effect. I have tried varipus combinations. But it does not help. I am new to coffeescript and only use it for hubot.Can anyone help me ?
First, you don't specify what HTTP action you are doing:
msg.http("https://xyz.pagerduty.com/api/v1/incidents/#{incident_key}/acknowledge")
Should end with .get() or .post().
Also, possibly due to bad paste, your indentation if off a bit in the middle:
.post(string_query) (err, res, body) ->
result = JSON.parse(body)
if result.status == "success"
msg.send "Your log has been sent"
else
msg.send "There was an error sending your log."
Should be:
.post(string_query) (err, res, body) ->
result = JSON.parse(body)
if result.status == "success"
msg.send "Your log has been sent"
else
msg.send "There was an error sending your log."
Another thing, due to nature of NodeJS, these HTTP calls are made asynchronously, and there is a good chance that second call finishes before first one does. To avoid that, run the second request from callback of the first one:
msg
.http("https://events.pagerduty.com/generic/2010-04-15/create_event.json")
.headers
"Content-type": "application/json",
"Content-length": content_length
.post(string_query) (err, res, body) ->
result = JSON.parse(body)
if result.status == "success"
msg.send "Your log has been sent"
msg
.http("https://xyz.pagerduty.com/api/v1/incidents/#{incident_key}/acknowledge")
.get()
else
msg.send "There was an error sending your log."
You can find some examples of Hubot HTTP requests and other scripting tricks here.

Push notification using Urban AirShip and Rails 3 for iphone

What is the best way to send push notifications through Urban AirShip using a Rails 3 web app?
From Urban Airship documentation:
An HTTP POST to /api/push/broadcast/
sends the given notification to all
registered device tokens for the
application. The payload is in JSON
with content-type application/json,
with this structure:
{
"aps": {
"badge": 15,
"alert": "Hello from Urban Airship!",
"sound": "cat.caf"
},
"exclude_tokens": [
"device token you want to skip",
"another device token you want to skip"
]
}
I really don't know where to start!
Better way is to use UrbanAirship Groupon version. Their docs specify every thing very clearly and it will much neater code. Worked and tested in my application.
From the read me file, (with some more comments and all):-
Note: if you are using Ruby 1.8, you should also install the system_timer gem for more reliable timeout behaviour. See http://ph7spot.com/musings/system-timer for more information. Baically
gem install system_timer
Installation
gem install urbanairship
# or specify in your gem file
gem "urbanairship"
Configuration define all this in initializes directory and then make sure u restart your application.
Urbanairship.application_key = 'application-key'
Urbanairship.application_secret = 'application-secret'
Urbanairship.master_secret = 'master-secret'
Urbanairship.logger = Rails.logger
Urbanairship.request_timeout = 5 # default
Usage
Registering a device token
Urbanairship.register_device 'DEVICE-TOKEN' # => true
Unregistering a device token
Urbanairship.unregister_device 'DEVICE-TOKEN' # => true
Sending a push notification (for instant delivery remove schedule_for attribute)
notification = {
:schedule_for => 1.hour.from_now,
:device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
:aps => {:alert => 'You have a new message!', :badge => 1}
}
Urbanairship.push notification # => true
Batching push notification sends (for instant delivery remove schedule_for attribute)
notifications = [
{
:schedule_for => 1.hour.from_now,
:device_tokens => ['DEVICE-TOKEN-ONE', 'DEVICE-TOKEN-TWO'],
:aps => {:alert => 'You have a new message!', :badge => 1}
},
{
:schedule_for => 3.hours.from_now,
:device_tokens => ['DEVICE-TOKEN-THREE'],
:aps => {:alert => 'You have a new message!', :badge => 1}
}
]
Urbanairship.batch_push notifications # => true
Sending broadcast notifications
Urbanairship allows you to send a broadcast notification to all active registered device tokens for your app.(for instant delivery remove schedule_for attribute)
notification = {
:schedule_for => 1.hour.from_now,
:aps => {:alert => 'Important announcement!', :badge => 1}
}
Urbanairship.broadcast_push notification # => true
Check out this example:
https://github.com/bhedana/urbanairship_on_rails
It was linked from the Urban Airship support site
Ok, this is a simple way of sending a urban airship broadcast from ROR controller:
require 'net/http'
require 'net/https'
require 'open-uri'
app_key = 'JJqr...'
app_secret = 'lAu7...'
master_secret = 'K81P...'
payload ={
"aps" => {"badge" => "0", "alert" => "My own message", "sound" => ""}
}.to_json
full_path = 'https://go.urbanairship.com/api/push/broadcast/'
url = URI.parse(full_path)
req = Net::HTTP::Post.new(url.path, initheader = {'Content-Type' =>'application/json'})
req.body = payload
req.basic_auth app_key, master_secret
con = Net::HTTP.new(url.host, url.port)
con.use_ssl = true
r = con.start {|http| http.request(req)}
logger.info "\n\n##############\n\n " + "Resonse body: " + r.body + " \n\n##############\n\n"
Cheers!
In Rails 4.2.x and Ruby 2.3.x, I'm using the 'urbanairship' gem, it is best to reference the most recent documentation found here.
Example of sending to ios:
def deliver_push_notification recipient_uuid, message
ua = Urbanairship
client = ua::Client.new(key: ENV["URBAN_AIRSHIP_APP_KEY"], secret: ENV["URBAN_AIRSHIP_MASTER_SECRET"])
p = client.create_push
p.audience = ua.ios_channel(recipient_uuid)
p.notification = ua.notification(ios: ua.ios(alert: message))
p.device_types = ua.device_types(["ios"])
p.send_push
end