Situation:
I have a small chat app with two users in each room.
Lets call'em Sender and Receiver.
I'd like to make 'read/unread messages'.
To determine if Sender's message is currently being read he needs to know if Receiver is currently subscribed to the channel.
I'm trying to look through subscriptions:
# I can retrieve subscriptions from Redis:
redis_record = Redis.new.pubsub("channels", "action_cable/*")
# I can make such id
key = Base64.encode64("gid://test-app/User/1").tr('=', '').tr('actioncable/', '').chomp
But I don't know how to look for existing record.
Tried:
Redis.new.scan(0, match: key)
With no result.
Question: how to find out if subscription is active? (using Redis is not a keypoint, maybe ActionCable has something to do with it somewhere inside the box)
Related
Is there any way in action cable to modify the message being sent based on the user who established the connection (the conneciton is identified_by the current user).
Specifically, I have a subscription based on a particular model so I can write code like:
ProjectChannel.broadcast_to(project, { action: action, data: project.to_json })
However, I'd like to send different messages to subscribers based on whether or not they are owners of the project or merely are authorized to view the project. Is this possible? Or does the underlying pubsub model make this impossible?
So one can setup multiple streams inside a single channel. Thus, if one had two types of users (say admin and non-admin) you could subscribe only the admin users to an admin stream like so:
def subscribed
stream_from "all_user_stream"
stream_from "admin_stream" if current_user.is_admin?
stream_from "non_admin_stream" unless current_user.is_admin?
end
Now you can send a message to just the admin users via
ActionCable.server.broadcast "admin_stream", msg
And to all users via
ActionCable.server.broadcast "all_user_stream", msg
So if you want a different message to go to the admin users and the non-admin users you just create a method that sends one message to the admin_stream and a different message to the non_admin_stream. For instance, somewhere in your app you could write:
def send_user_msg(msg, secret) #assume msg is a hash
ActionCable.server.broadcast "non_admin_stream", msg
msg['secret'] = secret
ActionCable.server.broadcast "admin_stream", msg
end
Now only admin users will get the secret in their message and the rest of the users will get the same message without the secret.
Note that, if you are using stream_for with a model you'll probably want to retrieve the actual name of the stream (stream_for is just a wrapper to stream_from that encodes the active record object into a unique string) and then modify that.
For instance, if you are working in a channel named DocumentsChannel then stream_for doc essentially does:
stream_from DocumentsChannel.broadcasting_for(doc)
So if you want multiple streams you could stream_from "#{DocumentsChannel.broadcasting_for(doc)}:admin" and "#{DocumentsChannel.broadcasting_for(doc)}:non_admin"
One note of caution here. This doesn't update if the attribute changes so if you have a user whose admin status is removed they will keep receiving the admin messages until either they disconnect or you explicitly use stop_stream_from to end that streaming. Since that later code has to be called from the Channel instance opened for that user's connection this isn't necessarily a good option for properties that might be changed by outside factors (i.e. in response to anything other than a message on that channel from the connection you want to modify).
I can't seem to see in the docs if there is a way, given an user ID, if that user follows my channel or not.
I saw this API:
https://developers.google.com/youtube/v3/docs/subscriptions/list?apix_params=%7B%22part%22%3A%5B%22snippet%2CcontentDetails%22%5D%2C%22mine%22%3Atrue%7D
but that seems to only list my subscriptions. Unless I misconfigured it.
Is this even possible?
Querying Subscriptions.list with mine=true will return the list of channels to which you -- the authenticated user -- subscribed.
For the other way around -- i.e. for a list of channels that are subscribers of your channel -- there are two other request parameters: myRecentSubscribers and mySubscribers.
In my application, a user can create a draft of an object, and then on a "finalise" view they can check if they only want the standard price or one of the two extra features as well (e.g. their entry being featured on the platform) - these are stored as three separate products each with their own price because I want to be able to track them separately in the future.
I am using Checkout for payments and my backend is Rails.
I am listening to webhooks, but I don't know how I could actually modify the object in question, since I can't see any webhook request body that contains the information I need.
What I imagine I'd need is something like this: "there has been a successful charge of $x related to your object with id 123, and the charge included product prod_asdf and prod_sdfg", and then I could update e.g. the paid and featured boolean fields of my object.
This seems like a no-brainer, since the products and prices are actually handled by Stripe and they are passed to the checkout session, I don't see why I can't access them from the webhooks?
EDIT: I'm wondering if passing necessary metadata to the Session object and then using the same metadata listening to a checkout.session.completed event is a good idea.
You can link a Checkout Session back to your Listing object by including the related object ID in the metadata (https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-metadata) when you create the Checkout Session. When you listen for checkout.session.completed events, the event will come with a Checkout Session object. You can easily link the Checkout Session back to your Listing object by checking the metadata and it also has an amount_total field so that will tell you the total payment amount.
You can get the products included in a session from line_items (https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-line_items). Unfortunately, line_items is not included in the webhook event by default since it is expandable (https://stripe.com/docs/expand). After receiving the checkout.session.completed event you can use the ID to retrieve the Checkout Session with line_items expanded like this:
session = Stripe::Checkout::Session.retrieve(
id: 'cs_test_xxx',
expand: ['line_items'],
)
line_items = session.line_items
Alternatively, you can just retrieve the Checkout Session's line items (https://stripe.com/docs/api/checkout/sessions/line_items) like this:
line_items = Stripe::Checkout::Session.list_line_items('cs_test_D3OF4MY1VGflR8idkewd795t8MLd4PwA3wxdLCHikdRFTIkBecH6FKij', {limit: 5})
I'm using this repo to create a chat system between 2 users in a Rails and React project. I've been able to log the user input into the console, and I have created messages_controller and message_threads_controller according to the repo.
However, I'm unable to persist the message to Rails db and then authenticate a user with Pusher before sending it to Pusher. Mainly because the from_uid, to_uid and thread_uid are not present by the time the message is been sent to Rails. Sending the message to rails like this:
sendMessage = (event) => {
event.preventDefault();
const {message} = this.state;
axios.post('/api/v1/messages', {message: message})
.then((response) => {
console.log(response);
})
console.log('send Message')
this.setState({'message': message});
console.log(this.state.message);
}
In my routes.rb file I have this
resources :messages
get 'threads/:id', to: 'message_threads#index'
post '/pusher/auth', to: 'pusher#auth'
I'm missing some required parameters, this is the error I get.
Pusher::Error - Bad request: Missing required parameter:
The flow according to this tutorial is that the message needs to be persisted first by the rails database before sending it to Pusher.
My question now is how do I produce the extra parameters (from_uid, thread_uid, to_uid) being used on the React side of the app here, to enable messages to be created?
Also, how do I authenticate the user using Pusher?
According to this Stack Overflow link they are getting from Rails the CSRF value like this - csrf = $('meta[name=csrf-token]').attr('content'). But I could not implement the same in React.
Answer from the author of the git repo.
The example I created here was pretty bare bones but below are a few bullet points that I hope will explain how you could expand on it.
Add a User model and Thread model to the Rails app
When a User is created, generate a public UID for the user (you can use Ruby's built-in SecureRandom.uuid to generate the id) and save that to the DB. This would become the ID for that user that you would expose in your javascript to allow for communications between users. All users would have a UID.
When a Thread is Created, generated a thread UID this would become the unique id for the conversation taking place
Add a Users_Threads has_and_belongs_to_many relationship so that you can eventually track the users that are subscribed to which threads
When React app loads, use an Ajax request to get a list of the current User's friends (returns list of user details + their uid) and a request to get all threads for current User
So let's say for example a User named Fred clicked on a User named Bob and wanted to send Bob a message but they do not currently have a thread. Fred types the message, clicks Submit and you send an Ajax request containing the message text, from_uid (Fred) and to_uid (Bob) with thread_uid as null (since there is no existing convo and no existing thread).
Your Rails app then receives that requests at a controller and sees that Fred is trying to send Bob a message and the thread ID is null, so the controller create a new thread (with its own UID) and then add two entries to users_threads one for the new thread_uid and bob's uid and another for the new thread_uid and Fred's uid. After that, you'd create a Message with that thread_uid and the participant details.
You'd also probably want users to see that they are part of a new thread without having to reload the page so you'd I think you'd want a Pusher channel subscription just for notifying users of a new thread. So I'd say in the UserThreads model after create a new thread you could send something like Pusher.trigger('threads_channel', user_secret_uid, { thread: new_thread_uid }). You'd also need to make sure in the react app that each user subscribes to the threads_channel for their user_secret_uid. For security, i'd make sure this is a different uid than the messaging otherwise people could subscribe to a list of a different users threads.
I'm trying to use ActionCable as a transport for GraphQL queries (graphql-streaming). GraphQL queries are arbitrary requests for data, so rather than having one cable per query, I'd like to have one cable and multiplex queries over that cable. The flow might be like this:
Connect to ActionCable
Subscribe to a GraphQL query
Push a query result
Something changes in the app, push another query result
User changes pages, we should unsubscribe from that query
I'm implementing subscription events as streams, so a subscription looks like this:
stream_from(event_name) { evaluate_graphql_and_push }
But when the user changes pages, I want to keep the channel connected but stop streaming from that event.
Is this possible with ActionCable?
You can call unsubscribe method on the subscription (a.k.a. channel) object.
i.e.,
channel = App.cable.subscriptions.create "ChannelName"
onPageChange = function() {
channel.unsubscribe()
}