websocket-rails Chat Rooms - ruby-on-rails

I am trying to create sort of Whatsapp like messaging app server side in Rails, with private conversations. now, I am trying to implement the realtime part of the app - I am using websocket-rails - and I am not sure how to send a message only to the users in the private message - I saw a feature called private channels in websocket-rails - but after reading the documentation, I got under the impression that each private channel needs to be defined statically, and I cannot create channels realtime.
Do you know how can I implement private conversations in websocket-rails, like a guide or a direction? or any other websocket service I can use to implement it?

You can pass parameters from the client side to the server side when creating a subscription. For example:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
An object passed as the first argument to subscriptions.create becomes the params hash in the cable channel. The keyword channel is required:
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
#appendLine(data)
appendLine: (data) ->
html = #createLine(data)
$("[data-chat-room='Best Room']").append(html)
createLine: (data) ->
"""
<article class="chat-line">
<span class="speaker">#{data["sent_by"]}</span>
<span class="body">#{data["body"]}</span>
</article>
"""
Somewhere in your app this is called, perhaps
ActionCable.server.broadcast(
"chat_#{room}",
sent_by: 'Paul',
body: 'This is a cool chat app.'
)

Related

Send URLs via SMS through Twilio-Rails

Working on a Rails project that needs to send a link to a record to someone via SMS.
/services/twilio_client.rb:
def send_text(job, message)
client = Twilio::REST::Client.new account_sid, auth_token
client.messages.create(
to: job.cell_number,
from: phone_number,
body: message
)
end
From Controller:
if job.save?
message = "#{#job.company} worker, you've got a new job. See it here:"
TwilioClient.new.send_text(#job, message)
In an ideal world, I could send them a link directly to the job via SMS, but Twilio won't accept ruby code as a media_url and dropping #{#job} in the message results in receiving the object #<Job:0x00007f0b60818338> in the SMS.
Clearly, this is a syntax issue, but try as I might I can't find a solution in the docs, the twilio-ruby gem, or examples published on the interweb.
I would change the interface of the send_message a bit:
# in /services/twilio_client.rb:
def send_text(number, message)
client = Twilio::REST::Client.new(account_sid, auth_token)
client.messages.create(to: number, from: phone_number, body: message)
end
And then call it from the controller like this:
if job.save?
message = "#{#job.company} worker, you've got a new job. See it here: #{media_url}"
TwilioClient.new.send_text(#job.cell_number, message)
# ...
The important fact here is that URL builders are only available in controllers and views in Rails per default. When you need a URL in another object like a service model then the easiest way is to generate it on the controller level and pass it to the service.

Twilio Chat: Error User not member of channel while joining a private channel

I am developing a 1:1 chat application. I use a PHP server to create the private channel before starting the application. The channels are created correctly.
The user tokens are genereated, chat client also created correctly. I see that the user is also created in the service.
When joining the private channel, it throws the error.
code:50400 message:"User not member of channel" status:403
Javascript code:
Twilio.Chat.Client.create(token,clientOptions).then(client => {
chatClient = client;
showMessage('Connecting.....');
chatClient.getChannelBySid(channelid)
.then(function(chosenChannel) {
showMessage('Joining Chat.....');
myChannel=chosenChannel;
joinChannel();
})
.catch(function(err) {
console.log(err);
})
});
It shows the message 'Connecting....' and then stops with the error.
PHP Code:
$client = new Client("sid", "token");
$channel = $client->chat->services("serviceid")->channels
->create(array('friendlyName' => $friendlyName, 'uniqueName' => $uniqueName, 'type' => 'private'));
Twilio developer evangelist here.
When you create a private channel there's no way to define at that stage who is allowed to enter the channel. From the documentation:
Private Channels are not visible to Users that have not been invited or added to them. Private Channel Members can only be added by other Members with sufficient permissions, or via the REST API as controlled by your business logic.
So, in order for a user to join a private channel you need to either:
Add them to the channel using the REST API
Invite the user to the channel from a channel admin of the private channel
Let me know if that makes sense at all

Using Actioncable and Rails 5 API mode together

I am creating a very basic chat app. My goal is to have a Rails API backend and then build an IOS, Android, web, and desktop client. This is purely to explore Websockets and mobile development.
I've never used Actioncable, and my knowledge of Websockets is very limited. What I'd like to know is if I can set up Actioncable on my Rails API and have it communicate with Node (for instance).
Does Actioncable act like any other Websocket? Could I connect to it from my Node app through ws://<host>/cable and have a functional pub-sub system between whatever client and Rails?
I'm sorry if that doesn't make sense, I'm having a hard time wording it :)
Thank you!
Indeed you can!
Just like you create any api app, use generator
rails new my_app --api
Create your_channel
rails generate channel your_channel
Add mount path in routes.rb
mount ActionCable.server => '/cable'
Allow stream on subscribe method in /app/channels/your_channel.rb
class YourChannel < ApplicationCable::Channel
def subscribed
stream_from 'messages' # <----- Stream Name Here
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
Call ActionCable.server.broadcast from any other part of your app to stream
ActionCable.server.broadcast 'messages', message: 'ping'
Now use your front-end to test it. Since you told you want iOS Android and also mentioned about node, I assume you are using (or would choose to use) react-native
import ActionCable from 'react-native-actioncable';
const cable = ActionCable.createConsumer("ws://localhost:3000/cable");
class YourReactClass extends React.Component {
# add or update the following
componentDidMount = () => {
console.log("componentDidMount executed");
this.subscription = cable.subscriptions.create("OrderChannel", {
connected: function() { console.log("connected: action cable") },
disconnected: function() { console.log("disconnected: action cable") },
received: function (data) { console.log(data) }
}
)
};
componentWillUnmount () {
this.subscription &&
cable.subscriptions.remove(this.subscription)
}
}
And you're good to go, build your logic on top of this... If you got any problems, let me know.

How to receive data in ActionCable Channel without JS?

I'm writing a Rails application that uses WebSockets to communicate with other machines (no browser and client side logic in this process). I have a channel:
class MachinesChannel < ApplicationCable::Channel
def subscribed
...
end
def unsubscribed
...
end
def handle_messages
...
end
end
To receive the data the only way I know about is the JavaScript client:
ActionCable.createConsumer('/cable').subscriptions.create 'MachinesChannel',
received: (message) ->
#perform('handle_messages')
I can call server side methods from JS via #perform() method.
Is there any way to omit the JS part and somehow directly handle the incoming data in MachinesChannel?
The ideal situation would be to have the handle_messages method accept a data argument and have this metod called on incoming data.
After looking into ActionCable source code I got the following solution. You just have to create a method in MachinesChannel that you want to be called, e.g. handle_messages(data). Then, in the client that connects to your websocket, you need to send a message in the following format (example in ruby):
id = { channel: 'MachinesChannel' }
ws = WebSocket::Client::Simple.connect(url)
ws.send(JSON.generate(command: 'message', identifier: JSON.generate(id), data: JSON.generate(action: 'handle_messages', foo: 'bar', biz: 'baz')))
action has to be the name of the method you want to be called in MachinesChannel. The rest of key-values are whatever you want. This the date you can receive in the ActionCable channel.
Recently a gem action_cable_client has been release which seems exactly perfect for this kind of usage. I haven't used it, so I don't know how it really works.
Instead of:
def handle_messages
...
end
This works for me:
def receive(data)
puts data
...
end

The stratigy of build a talk-to-talk system using em-websocket in rails?

Maybe it is a good example for server push system. There are many users in the system, and users can talk with each other. It can be accomplished like this: one user sends message(through websocket) to the server, then the server forward the message to the other user. The key is to find the binding between the ws(websocket object) and the user. The example code like below:
EM.run {
EM::WebSocket.run(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
ws.onopen { |handshake|
# extract the user id from handshake and store the binding between user and ws
}
ws.onmessage { |msg|
# extract the text and receiver id from msg
# extract the ws_receiver from the binding
ws_receiver.send(text)
}
end
}
I want to figure out following issues:
The ws object can be serialized so it can be stored into disk or database? Otherwise I can only store the binding into memory.
What the differences between em-websocket and websocket-rails?
Which gem do you recommend for websocket?
You're approaching a use case that websockets are pretty good for, so you're on the right track.
You could serialize the ws object with Marshal, but think of websocket objects as being a bit like http request objects in that they are abstractions for a type of communication. You are probably best off marshaling/storing the data.
em-websocket is a lower(ish) lever websocket library built more or less directly on web-machine. websocket-rails is a higher level abstraction on websockets, with a lot of nice tools built in and pretty ok docs. It is built on top of faye-websocket-rails which is itself built on web machine. *Note, action cable which is the new websocket library for Rails 5 is built on faye.
I've use websocket-rails in the past and rather like it. It will take care of a lot for you. However, if you can use Rails 5 and Action Cable, do that, its the future.
The following is in addition to Chase Gilliam's succinct answer which included references to em-websocket, websocket-rails (which hadn't been maintained in a long while), faye-websocket-rails and ActionCable.
I would recommend the Plezi framework. It works both as an independent application framework as well as a Rails Websocket enhancement.
I would consider the following points as well:
do you need the message to persist between connections (i.e. if the other user if offline, should the message wait in a "message box"? for how long should the message wait?)...?
Do you wish to preserve message history?
These points would help yo decide if to use a persistent storage (i.e. a database) for the messages or not.
i.e., to use Plezi with Rails, create an init_plezi.rb in your application's config/initializers folder. use (as an example) the following code:
class ChatDemo
# use JSON events instead of raw websockets
#auto_dispatch = true
protected #protected functions are hidden from regular Http requests
def auth msg
#user = User.auth_token(msg['token'])
return close unless #user
# creates a websocket "mailbox" that will remain open for 9 hours.
register_as #user.id, lifetime: 60*60*9, max_connections: 5
end
def chat msg, received = false
unless #user # require authentication first
close
return false
end
if received
# this is only true when we sent the message
# using the `broadcast` or `notify` methods
write msg # writes to the client websocket
end
msg['from'] = #user.id
msg['time'] = Plezi.time # an existing time object
unless msg['to'] && registered?(msg['to'])
# send an error message event
return {event: :err, data: 'No recipient or recipient invalid'}.to_json
end
# everything was good, let's send the message and inform
# this will invoke the `chat` event on the other websocket
# notice the `true` is setting the `received` flag.
notify msg['to'], :chat, msg, true
# returning a String will send it to the client
# when using the auto-dispatch feature
{event: 'message_sent', msg: msg}.to_json
end
end
# remember our route for websocket connections.
route '/ws_chat', ChatDemo
# a route to the Javascript client (optional)
route '/ws/client.js', :client
Plezi sets up it's own server (Iodine, a Ruby server), so remember to remove from your application any references to puma, thin or any other custom server.
On the client side you might want to use the Javascript helper provided by Plezi (it's optional)... add:
<script src='/es/client.js' />
<script>
TOKEN = <%= #user.token %>;
c = new PleziClient(PleziClient.origin + "/ws_chat") // the client helper
c.log_events = true // debug
c.chat = function(event) {
// do what you need to print a received message to the screen
// `event` is the JSON data. i.e.: event.event == 'chat'
}
c.error = function(event) {
// do what you need to print a received message to the screen
alert(event.data);
}
c.message_sent = function(event) {
// invoked after the message was sent
}
// authenticate once connection is established
c.onopen = function(event) {
c.emit({event: 'auth', token: TOKEN});
}
// // to send a chat message:
// c.emit{event: 'chat', to: 8, data: "my chat message"}
</script>
I didn't test the actual message code because it's just a skeleton and also it requires a Rails app with a User model and a token that I didn't want to edit just to answer a question (no offense).

Resources