class Ably::Realtime::Presence

Enables the presence set to be entered and subscribed to, and the historic presence set to be retrieved for a channel.

Constants

STATE

Attributes

channel[R]

{Ably::Realtime::Channel} this Presence object is associated with @return [Ably::Realtime::Channel]

client_id[R]

The client_id for the member present on this channel @return [String]

data[R]

The data for the member present on this channel @return [String]

manager[R]

The Presence manager responsible for actions relating to state changes such as entering a channel @return [Ably::Realtime::Presence::PresenceManager] @api private

members[R]

{MembersMap} containing an up to date list of members on this channel @return [MembersMap] @api private

Public Class Methods

new(channel) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 44
def initialize(channel)
  @channel       = channel
  @client_id     = client.client_id

  @state_machine = PresenceStateMachine.new(self)
  @state         = STATE(state_machine.current_state)
  @members       = MembersMap.new(self)
  @manager       = PresenceManager.new(self)
end

Public Instance Methods

__incoming_msgbus__() click to toggle source

@!attribute [r] incoming_msgbus @return [Ably::Util::PubSub] Client library internal channel incoming protocol message bus @api private

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 316
def __incoming_msgbus__
  @__incoming_msgbus__ ||= Ably::Util::PubSub.new(
    coerce_into: lambda { |event| Ably::Models::ProtocolMessage::ACTION(event) }
  )
end
enter(data = nil, &success_block) click to toggle source

Enter this client into this channel. This client will be added to the presence set and presence subscribers will see an enter message for this client.

@param [String,Hash,nil] data optional data (eg a status message) for this member

@yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 62
def enter(data = nil, &success_block)
  deferrable = create_deferrable

  ensure_supported_payload data
  @data = data

  return deferrable_succeed(deferrable, &success_block) if state == STATE.Entered

  requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
  return requirements_failed_deferrable if requirements_failed_deferrable

  ensure_channel_attached(deferrable) do
    if entering?
      once_or_if(STATE.Entered, else: lambda { |args| deferrable_fail deferrable, *args }) do
        deferrable_succeed deferrable, &success_block
      end
    else
      current_state = state
      change_state STATE.Entering
      send_protocol_message_and_transition_state_to(
        Ably::Models::PresenceMessage::ACTION.Enter,
        deferrable:   deferrable,
        target_state: STATE.Entered,
        data:         data,
        client_id:    client_id,
        failed_state: current_state, # return to current state if enter fails
        &success_block
      )
    end
  end
end
enter_client(client_id, data = nil, &success_block) click to toggle source

Enters the presence set of the channel for a given clientId. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success or failure of the operation.

@spec RTP4, RTP14, RTP15

@param [String] client_id id of the client @param [String,Hash,nil] data The payload associated with the presence member. A JSON object of arbitrary key-value pairs that may contain metadata, and/or ancillary payloads.

@yield [Ably::Realtime::Presence] On success, will call the block with this {Ably::Realtime::Presence} object @return [Ably::Util::SafeDeferrable] Deferrable that supports both success (callback) and failure (errback) callbacks

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 106
def enter_client(client_id, data = nil, &success_block)
  ensure_supported_client_id client_id
  ensure_supported_payload data

  send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Enter, client_id, data, &success_block)
end
get(options = {}, &block) click to toggle source

Retrieves the current members present on the channel and the metadata for each member, such as their {Ably::Models::ProtocolMessage::ACTION} and ID. Returns an array of {Ably::Models::PresenceMessage} objects.

@spec RTP11, RTP11c1, RTP11c2, RTP11c3

@param (see {Ably::Realtime::Presence::MembersMap#get}) @option options (see {Ably::Realtime::Presence::MembersMap#get}) @yield (see {Ably::Realtime::Presence::MembersMap#get})

@return (see {Ably::Realtime::Presence::MembersMap#get})

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 234
def get(options = {}, &block)
  deferrable = create_deferrable

  # #RTP11d Don't return PresenceMap when wait for sync is true
  #   if the map is stale
  wait_for_sync = options.fetch(:wait_for_sync, true)
  if wait_for_sync && channel.suspended?
    EventMachine.next_tick do
      deferrable.fail Ably::Exceptions::InvalidState.new(
        'Presence state is out of sync as channel is SUSPENDED. Presence#get on a SUSPENDED channel is only supported with option wait_for_sync: false',
        nil,
        Ably::Exceptions::Codes::PRESENCE_STATE_IS_OUT_OF_SYNC
      )
    end
    return deferrable
  end

  ensure_channel_attached(deferrable, allow_suspended: true) do
    members.get(options).tap do |members_map_deferrable|
      members_map_deferrable.callback do |members|
        safe_yield(block, members) if block_given?
        deferrable.succeed(members)
      end
      members_map_deferrable.errback do |*args|
        deferrable.fail(*args)
      end
    end
  end
end
history(options = {}, &callback) click to toggle source

Retrieves a {Ably::Models::PaginatedResult} object, containing an array of historical {Ably::Models::PresenceMessage} objects for the channel. If the channel is configured to persist messages, then presence messages can be retrieved from history for up to 72 hours in the past. If not, presence messages can only be retrieved from history for up to two minutes in the past.

@spec RTP12c, RTP12a

@param (see {Ably::Rest::Presence#history}) @option options (see {Ably::Rest::Presence#history})

@yield [Ably::Models::PaginatedResult<Ably::Models::PresenceMessage>] First {Ably::Models::PaginatedResult page} of {Ably::Models::PresenceMessage} objects accessible with {Ably::Models::PaginatedResult#items items}.

@return [Ably::Util::SafeDeferrable]

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 307
def history(options = {}, &callback)
  async_wrap(callback) do
    rest_presence.history(options.merge(async_blocking_operations: true))
  end
end
leave(data = nil, &success_block) click to toggle source

Leave this client from this channel. This client will be removed from the presence set and presence subscribers will see a leave message for this client.

@param (see Presence#enter)

@yield (see Presence#enter) @return (see Presence#enter)

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 121
def leave(data = nil, &success_block)
  deferrable = create_deferrable

  ensure_supported_payload data

  @data = data

  return deferrable_succeed(deferrable, &success_block) if state == STATE.Left

  requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
  return requirements_failed_deferrable if requirements_failed_deferrable

  ensure_channel_attached(deferrable) do
    if leaving?
      once_or_if(STATE.Left, else: lambda { |error|deferrable_fail deferrable, *args }) do
        deferrable_succeed deferrable, &success_block
      end
    else
      current_state = state
      change_state STATE.Leaving
      send_protocol_message_and_transition_state_to(
        Ably::Models::PresenceMessage::ACTION.Leave,
        deferrable:   deferrable,
        target_state: STATE.Left,
        data:         data,
        client_id:    client_id,
        failed_state: current_state, # return to current state if leave fails
        &success_block
      )
    end
  end
end
leave_client(client_id, data = nil, &success_block) click to toggle source

Leaves the presence set of the channel for a given clientId. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success or failure of the operation.

@spec RTP15

@param (see {Ably::Realtime::Presence#enter_client})

@yield (see {Ably::Realtime::Presence#enter_client}) @return (see {Ably::Realtime::Presence#enter_client})

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 165
def leave_client(client_id, data = nil, &success_block)
  ensure_supported_client_id client_id
  ensure_supported_payload data

  send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Leave, client_id, data, &success_block)
end
logger() click to toggle source

Used by {Ably::Modules::StateEmitter} to debug action changes @api private

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 324
def logger
  client.logger
end
subscribe(*actions, &callback) click to toggle source

Registers a listener that is called each time a {Ably::Models::PresenceMessage} is received on the channel, such as a new member entering the presence set. A callback may optionally be passed in to this call to be notified of success or failure of the channel {Ably::Realtime::Channel#attach} operation.

@spec RTP6a, RTP6b

@param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions @yield [Ably::Models::PresenceMessage] For each presence state change event, the block is called

@return [void]

Calls superclass method Ably::Modules::MessageEmitter#subscribe
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 275
def subscribe(*actions, &callback)
  implicit_attach
  super
end
sync_complete?() click to toggle source

Indicates whether the presence set synchronization between Ably and the clients on the channel has been completed. Set to true when the sync is complete.

@spec RTP13

return [Boolean]

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 335
def sync_complete?
  members.sync_complete?
end
unsubscribe(*actions, &callback) click to toggle source

Unsubscribe the matching block for presence events on the associated Channel. If a block is not provided, all subscriptions will be unsubscribed {Ably::Models::PresenceMessage} for the channel.

@spec RTP7a, RTP7b, RTE5

@param actions [Ably::Models::PresenceMessage::ACTION] Optional, the state change action to subscribe to. Defaults to all presence actions

@return [void]

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 289
def unsubscribe(*actions, &callback)
  super
end
update(data = nil, &success_block) click to toggle source

Updates the data payload for a presence member. If called before entering the presence set, this is treated as an {Ably::Realtime::Presence::STATE.Entered} event. An optional callback may be provided to notify of the success or failure of the operation.

@spec RTP9

@param (see {Ably::Realtime::Presence#enter})

@yield (see {Ably::Realtime::Presence#enter}) @return (see {Ably::Realtime::Presence#enter})

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 182
def update(data = nil, &success_block)
  deferrable = create_deferrable

  ensure_supported_payload data

  @data = data

  requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
  return requirements_failed_deferrable if requirements_failed_deferrable

  ensure_channel_attached(deferrable) do
    send_protocol_message_and_transition_state_to(
      Ably::Models::PresenceMessage::ACTION.Update,
      deferrable:   deferrable,
      target_state: STATE.Entered,
      client_id:    client_id,
      data:         data,
      &success_block
    )
  end
end
update_client(client_id, data = nil, &success_block) click to toggle source

Updates the data payload for a presence member using a given clientId. Enables a single client to update presence on behalf of any number of clients using a single connection. The library must have been instantiated with an API key or a token bound to a wildcard clientId. An optional callback may be provided to notify of the success or failure of the operation.

@spec RTP15

@param (see Presence#enter_client)

@yield (see Presence#enter_client) @return (see Presence#enter_client)

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 216
def update_client(client_id, data = nil, &success_block)
  ensure_supported_client_id client_id
  ensure_supported_payload data

  send_presence_action_for_client(Ably::Models::PresenceMessage::ACTION.Update, client_id, data, &success_block)
end

Private Instance Methods

attach_channel_then(deferrable) { || ... } click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 449
def attach_channel_then(deferrable)
  if channel.detached? || channel.failed?
    deferrable.fail Ably::Exceptions::InvalidState.new("Operation is not allowed when channel is in #{channel.state}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
  else
    channel.unsafe_once(:attached, :detached, :failed) do |channel_state_change|
      if channel_state_change.current == :attached
        yield
      else
        deferrable.fail Ably::Exceptions::InvalidState.new("Operation failed as channel transitioned to #{channel_state_change.current}", 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_INVALID_CHANNEL_STATE)
      end
    end
    channel.attach
  end
end
client() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 468
def client
  channel.client
end
connection() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 472
def connection
  client.connection
end
create_deferrable() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 485
def create_deferrable
  Ably::Util::SafeDeferrable.new(logger)
end
create_presence_message(action, client_id, data) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 358
def create_presence_message(action, client_id, data)
  model = {
    action:   Ably::Models::PresenceMessage.ACTION(action).to_i,
    clientId: client_id,
    data:     data
  }

  Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
    presence_message.encode(client.encoders, channel.options) do |encode_error, error_message|
      client.logger.error error_message
    end
  end
end
deferrable_fail(deferrable, *args, &block) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 430
def deferrable_fail(deferrable, *args, &block)
  safe_yield(block, *args) if block_given?
  EventMachine.next_tick { deferrable.fail(*args) } # allow errback to be added to the returned Deferrable
  deferrable
end
deferrable_succeed(deferrable, *args, &block) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 424
def deferrable_succeed(deferrable, *args, &block)
  safe_yield(block, self, *args) if block_given?
  EventMachine.next_tick { deferrable.succeed self, *args } # allow callback to be added to the returned Deferrable before calling succeed
  deferrable
end
ensure_channel_attached(deferrable = nil, options = {}) { || ... } click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 379
def ensure_channel_attached(deferrable = nil, options = {})
  if channel.attached?
    yield
  elsif options[:allow_suspended] && channel.suspended?
    yield
  else
    attach_channel_then(deferrable) { yield }
  end
  deferrable
end
ensure_presence_publishable_on_connection_deferrable() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 372
def ensure_presence_publishable_on_connection_deferrable
  if !connection.can_publish_messages?
    error = Ably::Exceptions::MessageQueueingDisabled.new("Presence event cannot be published as they cannot be queued when the connection is #{connection.state}")
    Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
  end
end
ensure_supported_client_id(check_client_id) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 390
def ensure_supported_client_id(check_client_id)
  unless check_client_id
    raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave presence channel without a client_id')
  end
  if check_client_id == '*'
    raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave presence channel with the reserved wildcard client_id')
  end
  unless check_client_id.kind_of?(String)
    raise Ably::Exceptions::IncompatibleClientId.new('Unable to enter/update/leave with a non String client_id value')
  end
  unless client.auth.can_assume_client_id?(check_client_id)
    raise Ably::Exceptions::IncompatibleClientId.new("Cannot enter with provided client_id '#{check_client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
  end
end
implicit_attach() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 464
def implicit_attach
  channel.attach if channel.initialized?
end
message_emitter_subscriptions_coerce_message_key(name) click to toggle source

Force subscriptions to match valid PresenceMessage actions

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 481
def message_emitter_subscriptions_coerce_message_key(name)
  Ably::Models::PresenceMessage.ACTION(name)
end
rest_presence() click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 476
def rest_presence
  client.rest_client.channel(channel.name).presence
end
send_presence_action_for_client(action, client_id, data, &success_block) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 436
def send_presence_action_for_client(action, client_id, data, &success_block)
  requirements_failed_deferrable = ensure_presence_publishable_on_connection_deferrable
  return requirements_failed_deferrable if requirements_failed_deferrable

  deferrable = create_deferrable
  ensure_channel_attached(deferrable) do
    send_presence_protocol_message(action, client_id, data).tap do |protocol_message|
      protocol_message.callback { |message| deferrable_succeed deferrable, &success_block }
      protocol_message.errback  { |error| deferrable_fail deferrable, error }
    end
  end
end
send_presence_protocol_message(presence_action, client_id, data) click to toggle source

@return [Ably::Models::PresenceMessage] presence message is returned allowing callbacks to be added

# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 341
def send_presence_protocol_message(presence_action, client_id, data)
  presence_message = create_presence_message(presence_action, client_id, data)
  unless presence_message.client_id
    raise Ably::Exceptions::Standard.new('Unable to enter create presence message without a client_id', 400, Ably::Exceptions::Codes::UNABLE_TO_ENTER_PRESENCE_CHANNEL_NO_CLIENTID)
  end

  protocol_message = {
    action:  Ably::Models::ProtocolMessage::ACTION.Presence,
    channel: channel.name,
    presence: [presence_message]
  }

  client.connection.send_protocol_message protocol_message

  presence_message
end
send_protocol_message_and_transition_state_to(action, options = {}, &success_block) click to toggle source
# File lib/submodules/ably-ruby/lib/ably/realtime/presence.rb, line 405
def send_protocol_message_and_transition_state_to(action, options = {}, &success_block)
  deferrable   = options.fetch(:deferrable) { raise ArgumentError, 'option :deferrable is required' }
  client_id    = options.fetch(:client_id)  { raise ArgumentError, 'option :client_id is required' }
  target_state = options.fetch(:target_state, nil)
  failed_state = options.fetch(:failed_state, nil)

  send_presence_protocol_message(action, client_id, options[:data]).tap do |protocol_message|
    protocol_message.callback do |message|
      change_state target_state, message if target_state
      deferrable_succeed deferrable, &success_block
    end

    protocol_message.errback do |error|
      change_state failed_state, error if failed_state
      deferrable_fail deferrable, error
    end
  end
end