discourse/app/controllers/presence_controller.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

99 lines
3.0 KiB
Ruby
Raw Normal View History

DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
# frozen_string_literal: true
class PresenceController < ApplicationController
skip_before_action :check_xhr, :redirect_to_profile_if_required
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
before_action :ensure_logged_in, only: [:update]
before_action :skip_persist_session
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
MAX_CHANNELS_PER_REQUEST = 50
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
def get
names = params.require(:channels)
if !(names.is_a?(Array) && names.all? { |n| n.is_a? String })
raise Discourse::InvalidParameters.new(:channels)
end
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
names.uniq!
if names.length > MAX_CHANNELS_PER_REQUEST
raise Discourse::InvalidParameters.new("Too many channels")
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
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
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
end
render json: result
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
end
def update
raise Discourse::ReadOnly if @readonly_mode
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
client_id = params[:client_id]
if !client_id.is_a?(String) || client_id.blank?
raise Discourse::InvalidParameters.new(:client_id)
end
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
# 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!
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
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
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
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
DEV: Introduce PresenceChannel API for core and plugin use PresenceChannel aims to be a generic system for allow the server, and end-users, to track the number and identity of users performing a specific task on the site. For example, it might be used to track who is currently 'replying' to a specific topic, editing a specific wiki post, etc. A few key pieces of information about the system: - PresenceChannels are identified by a name of the format `/prefix/blah`, where `prefix` has been configured by some core/plugin implementation, and `blah` can be any string the implementation wants to use. - Presence is a boolean thing - each user is either present, or not present. If a user has multiple clients 'present' in a channel, they will be deduplicated so that the user is only counted once - Developers can configure the existence and configuration of channels 'just in time' using a callback. The result of this is cached for 2 minutes. - Configuration of a channel can specify permissions in a similar way to MessageBus (public boolean, a list of allowed_user_ids, and a list of allowed_group_ids). A channel can also be placed in 'count_only' mode, where the identity of present users is not revealed to end-users. - The backend implementation uses redis lua scripts, and is designed to scale well. In the future, hard limits may be introduced on the maximum number of users that can be present in a channel. - Clients can enter/leave at will. If a client has not marked itself 'present' in the last 60 seconds, they will automatically 'leave' the channel. The JS implementation takes care of this regular check-in. - On the client-side, PresenceChannel instances can be fetched from the `presence` ember service. Each PresenceChannel can be used entered/left/subscribed/unsubscribed, and the service will automatically deduplicate information before interacting with the server. - When a client joins a PresenceChannel, the JS implementation will automatically make a GET request for the current channel state. To avoid this, the channel state can be serialized into one of your existing endpoints, and then passed to the `subscribe` method on the channel. - The PresenceChannel JS object is an ember object. The `users` and `count` property can be used directly in ember templates, and in computed properties. - It is important to make sure that you `unsubscribe()` and `leave()` any PresenceChannel objects after use An example implementation may look something like this. On the server: ```ruby register_presence_channel_prefix("site") do |channel| next nil unless channel == "/site/online" PresenceChannel::Config.new(public: true) end ``` And on the client, a component could be implemented like this: ```javascript import Component from "@ember/component"; import { inject as service } from "@ember/service"; export default Component.extend({ presence: service(), init() { this._super(...arguments); this.set("presenceChannel", this.presence.getChannel("/site/online")); }, didInsertElement() { this.presenceChannel.enter(); this.presenceChannel.subscribe(); }, willDestroyElement() { this.presenceChannel.leave(); this.presenceChannel.unsubscribe(); }, }); ``` With this template: ```handlebars Online: {{presenceChannel.count}} <ul> {{#each presenceChannel.users as |user|}} <li>{{avatar user imageSize="tiny"}} {{user.username}}</li> {{/each}} </ul> ```
2021-08-27 21:43:39 +08:00
end