# frozen_string_literal: true class PresenceController < ApplicationController skip_before_action :check_xhr before_action :ensure_logged_in, only: [:update] before_action :skip_persist_session MAX_CHANNELS_PER_REQUEST ||= 50 def get names = params.require(:channels) if !(names.is_a?(Array) && names.all? { |n| n.is_a? String }) raise Discourse::InvalidParameters.new(:channels) end names.uniq! if names.length > MAX_CHANNELS_PER_REQUEST raise Discourse::InvalidParameters.new("Too many channels") end user_group_ids = if current_user GroupUser.where(user_id: current_user.id).pluck("group_id") else [] end result = {} names.each do |name| channel = PresenceChannel.new(name) if channel.can_view?(user_id: current_user&.id, group_ids: user_group_ids) result[name] = PresenceChannelStateSerializer.new(channel.state, root: nil) else result[name] = nil end rescue PresenceChannel::NotFound result[name] = nil end render json: result end def update client_id = params[:client_id] if !client_id.is_a?(String) || client_id.blank? raise Discourse::InvalidParameters.new(:client_id) end # JS client is designed to throttle to one request per second # When no changes are being made, it makes one request every 30 seconds RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed! present_channels = params[:present_channels] if present_channels && !(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String }) raise Discourse::InvalidParameters.new(:present_channels) end leave_channels = params[:leave_channels] if leave_channels && !(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String }) raise Discourse::InvalidParameters.new(:leave_channels) end if present_channels && present_channels.length > MAX_CHANNELS_PER_REQUEST raise Discourse::InvalidParameters.new("Too many present_channels") end response = {} present_channels&.each do |name| PresenceChannel.new(name).present(user_id: current_user&.id, client_id: params[:client_id]) response[name] = true rescue PresenceChannel::NotFound, PresenceChannel::InvalidAccess response[name] = false end leave_channels&.each do |name| PresenceChannel.new(name).leave(user_id: current_user&.id, client_id: params[:client_id]) rescue PresenceChannel::NotFound # Do nothing. Don't reveal that this channel doesn't exist end render json: response end private def skip_persist_session # Presence endpoints are often called asynchronously at the same time as other request, # and never need to modify the session. Skipping ensures that an unneeded cookie rotation # doesn't race against another request and cause issues. session.options[:skip] = true end end