Files
prism/examples/blog/app/channels/post_activity_channel.rb

57 lines
2.2 KiB
Ruby

require 'uri'
require 'json'
# ActionCable-lite channel for posts#show: anyone with a post open can
# subscribe (GET /cable?post_id=N, upgraded to a WebSocket) to hear about
# moderation events on that post's comments - approved, rejected, or
# removed - the instant they happen, instead of polling or reloading.
# CommentsController broadcasts through the same Hub every time one of
# those three actions changes the post's *visible* (approved) comment
# count; this channel itself never accepts anything back from the client,
# it only ever pushes.
class PostActivityChannel
# A hard cap on live viewers per post - without one, one post going viral
# would mean one Hub#broadcast call fanning out to an unbounded number of
# live sockets on every single moderation action. Real, but generous
# enough that the demo's handful of viewers never trips it by accident.
MAX_VIEWERS_PER_POST = 500
def self.hub
@hub ||= Prism::WebSocket::Hub.new(max_subscribers_per_stream: MAX_VIEWERS_PER_POST)
end
def self.stream_for(post_id)
"post_#{post_id}_comments"
end
def on_open(connection)
post_id = post_id_from(connection)
return connection.close(1008, 'post_id query parameter is required') unless post_id
unless self.class.hub.subscribe(self.class.stream_for(post_id), connection)
return connection.close(1008, 'this post already has the maximum number of live viewers')
end
connection.write_text(JSON.generate(event: 'subscribed', post_id: post_id))
end
def on_message(connection, _message)
# Read-only stream - nothing a client sends is ever acted on.
end
def on_close(connection)
self.class.hub.unsubscribe_all(connection)
end
private
# The handshake request never reaches a Prism::Request/Params, just the
# raw Rack env the server captured before the Upgrade (see
# Prism::Server#handle_websocket) - so the query string is decoded by
# hand here instead of through Prism::Params#nested_from, which only
# ever runs as part of an ordinary Request#params call.
def post_id_from(connection)
query = URI.decode_www_form(connection.env['QUERY_STRING'].to_s).to_h
id = query['post_id'].to_s
id.empty? ? nil : id
end
end