class Blather::Client

# Blather Client

Blather's Client class provides a set of helpers for working with common XMPP tasks such as setting up and starting the connection, settings status, registering and dispatching filters and handlers and roster management.

Client can be used separately from the DSL if you'd like to implement your own DSL Here's the echo example using the client without the DSL:

require 'blather/client/client'
client = Client.setup 'echo@jabber.local', 'echo'

client.register_handler(:ready) do
  puts "Connected ! send messages to #{client.jid.stripped}."
end

client.register_handler :subscription, :request? do |s|
  client.write s.approve!
end

client.register_handler :message, :chat?, :body => 'exit' do |m|
  client.write Blather::Stanza::Message.new(m.from, 'Exiting...')
  client.close
end

client.register_handler :message, :chat?, :body do |m|
  client.write Blather::Stanza::Message.new(m.from, "You sent: #{m.body}")
end

Attributes

caps[R]
jid[R]
queue_size[R]
roster[R]

Public Class Methods

new() click to toggle source
# File lib/blather/client/client.rb, line 74
def initialize  # @private
  @state = :initializing

  @status = Stanza::Presence::Status.new
  @handlers = {}
  @tmp_handlers = {}
  @tmp_handlers_mutex = Mutex.new
  @filters = {:before => [], :after => []}
  @roster = Roster.new self
  @caps = Stanza::Capabilities.new
  @queue_size = 5

  setup_initial_handlers
end
setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil, options = {}) click to toggle source

Create a new client and set it up

@param [Blather::JID, to_s] jid the JID to authorize with @param [String] password the password to authorize with @param [String] host if this isn't set it'll be resolved off the JID's domain @param [Fixnum, String] port the port to connect to. @param [Hash] options a list of options to create the client with @option options [String] :authcid A custom authcid for advanced authentication scenarios @option options [Number] :workqueue_count (5) the number of threads used to process incoming XMPP messages.

If this parameter is specified with 0, no background threads are used;
instead stanzas are handled in the same process that the Client is running in.

@return [Blather::Client]

# File lib/blather/client/client.rb, line 69
def self.setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil, options = {})
  self.new.setup(jid, password, host, port, certs, connect_timeout, options)
end

Public Instance Methods

clear_handlers(type, *guards) click to toggle source

Clear handlers with given guards

@param [Symbol, nil] type remove filters for a specific handler @param [guards] guards take a look at the guards documentation

# File lib/blather/client/client.rb, line 152
def clear_handlers(type, *guards)
  @handlers[type].delete_if { |g, _| g == guards }
end
close() click to toggle source

Close the connection

# File lib/blather/client/client.rb, line 192
def close
  EM.next_tick {
    handler_queue.shutdown if handler_queue
    @handler_queue = nil
    self.stream.close_connection_after_writing if connected?
  }
end
connect()
Alias for: run
connected?() click to toggle source

Check whether the client is currently connected.

# File lib/blather/client/client.rb, line 90
def connected?
  setup? && !@stream.nil? && !@stream.stopped?
end
handle_data(stanza) click to toggle source
# File lib/blather/client/client.rb, line 221
def handle_data(stanza)
  catch(:halt) do
    run_filters :before, stanza
    handle_stanza stanza
    run_filters :after, stanza
  end
end
handler_queue() click to toggle source

@private

# File lib/blather/client/client.rb, line 248
def handler_queue
  return if queue_size == 0
  return @handler_queue if @handler_queue
  @handler_queue = Class.new(Job)
  @handler_queue.client = self
  @handler_queue.workers queue_size
  return @handler_queue
end
post_init(stream, jid = nil) click to toggle source

@private

# File lib/blather/client/client.rb, line 201
def post_init(stream, jid = nil)
  @stream = stream
  @jid = JID.new(jid) if jid
  self.jid.node ? client_post_init : ready!
end
receive_data(stanza) click to toggle source

@private

# File lib/blather/client/client.rb, line 213
def receive_data(stanza)
  if handler_queue
    handler_queue.perform_async stanza
  else
    handle_data stanza
  end
end
register_filter(type, handler = nil, *guards, &filter) click to toggle source

Register a filter to be run before or after the handler chain is run.

@param [<:before, :after>] type the filter type @param [Symbol, nil] handler set the filter on a specific handler @param [guards] guards take a look at the guards documentation @yield [Blather::Stanza] stanza the incomming stanza

# File lib/blather/client/client.rb, line 130
def register_filter(type, handler = nil, *guards, &filter)
  unless [:before, :after].include?(type)
    raise "Invalid filter: #{type}. Must be :before or :after"
  end
  @filters[type] << [guards, handler, filter]
end
register_handler(type, *guards, &handler) click to toggle source

Register a handler

@param [Symbol, nil] type set the filter on a specific handler @param [guards] guards take a look at the guards documentation @yield [Blather::Stanza] stanza the incomming stanza

# File lib/blather/client/client.rb, line 161
def register_handler(type, *guards, &handler)
  check_handler type, guards
  @handlers[type] ||= []
  @handlers[type] << [guards, handler]
end
register_tmp_handler(id, &handler) click to toggle source

Register a temporary handler. Temporary handlers are based on the ID of the JID and live only until a stanza with said ID is received.

@param [#to_s] id the ID of the stanza that should be handled @yield [Blather::Stanza] stanza the incomming stanza

# File lib/blather/client/client.rb, line 142
def register_tmp_handler(id, &handler)
  @tmp_handlers_mutex.synchronize do
    @tmp_handlers[id.to_s] = handler
  end
end
run() click to toggle source

Start the connection.

The stream type used is based on the JID. If a node exists it uses Blather::Stream::Client otherwise Blather::Stream::Component

# File lib/blather/client/client.rb, line 117
def run
  raise 'not setup!' unless setup?
  klass = @setup[0].node ? Blather::Stream::Client : Blather::Stream::Component
  klass.start self, *@setup
end
Also aliased as: connect
setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil, options = {}) click to toggle source

@private

# File lib/blather/client/client.rb, line 235
def setup(jid, password, host = nil, port = nil, certs = nil, connect_timeout = nil, options = {})
  @jid = JID.new(jid)
  @setup = [@jid, password]
  @setup << host
  @setup << port
  @setup << certs
  @setup << connect_timeout
  @setup << options
  @queue_size = options[:workqueue_count] || 5
  self
end
setup?() click to toggle source

@private

# File lib/blather/client/client.rb, line 230
def setup?
  @setup.is_a? Array
end
status() click to toggle source

Get the current status. Taken from the `state` attribute of Status

# File lib/blather/client/client.rb, line 95
def status
  @status.state
end
status=(state) click to toggle source

Set the status. Status can be set with either a single value or an array containing

[state, message, to].

# File lib/blather/client/client.rb, line 103
def status=(state)
  state, msg, to = state

  status = Stanza::Presence::Status.new state, msg
  status.to = to
  @status = status unless to

  write status
end
unbind() click to toggle source

@private

# File lib/blather/client/client.rb, line 208
def unbind
  call_handler_for(:disconnected, nil) || (EM.reactor_running? && EM.stop)
end
write(stanza) click to toggle source

Write data to the stream

@param [#to_xml, to_s] stanza the content to send down the wire

# File lib/blather/client/client.rb, line 170
def write(stanza)
  self.stream.send(stanza)
end
write_with_handler(stanza, &handler) click to toggle source

Helper that will create a temporary handler for the stanza being sent before writing it to the stream.

client.write_with_handler(stanza) { |s| "handle stanza here" }

is equivalent to:

client.register_tmp_handler(stanza.id) { |s| "handle stanza here" }
client.write stanza

@param [Blather::Stanza] stanza the stanza to send down the wire @yield [Blather::Stanza] stanza the reply stanza

# File lib/blather/client/client.rb, line 186
def write_with_handler(stanza, &handler)
  register_tmp_handler stanza.id, &handler
  write stanza
end

Protected Instance Methods

call_handler(handler, guards, stanza) click to toggle source
# File lib/blather/client/client.rb, line 336
def call_handler(handler, guards, stanza)
  if guards.first.respond_to?(:to_str)
    result = stanza.find(*guards)
    handler.call(stanza, result) unless result.empty?
  else
    handler.call(stanza) unless guarded?(guards, stanza)
  end
end
call_handler_for(type, stanza) click to toggle source
# File lib/blather/client/client.rb, line 329
def call_handler_for(type, stanza)
  return unless handler = @handlers[type]
  handler.find do |guards, handler|
    catch(:pass) { call_handler handler, guards, stanza }
  end
end
check_guards(guards) click to toggle source
# File lib/blather/client/client.rb, line 377
def check_guards(guards)
  guards.each do |guard|
    case guard
    when Array
      guard.each { |g| check_guards([g]) }
    when Symbol, Proc, Hash, String
      nil
    else
      raise "Bad guard: #{guard.inspect}"
    end
  end
end
check_handler(type, guards) click to toggle source
# File lib/blather/client/client.rb, line 263
def check_handler(type, guards)
  Blather.logger.warn "Handler for type \"#{type}\" will never be called as it's not a registered type" unless current_handlers.include?(type)
  check_guards guards
end
client_post_init() click to toggle source
# File lib/blather/client/client.rb, line 300
def client_post_init
  write_with_handler Stanza::Iq::Roster.new do |node|
    roster.process(node) unless node.error?
    write @status
    ready!
  end
end
current_handlers() click to toggle source
# File lib/blather/client/client.rb, line 268
def current_handlers
  [:ready, :disconnected] + Stanza.handler_list + BlatherError.handler_list
end
guarded?(guards, stanza) click to toggle source

If any of the guards returns FALSE this returns true the logic is reversed to allow short circuiting (why would anyone want to loop over more values than necessary?)

@private

# File lib/blather/client/client.rb, line 350
def guarded?(guards, stanza)
  guards.find do |guard|
    case guard
    when Symbol
      !stanza.__send__(guard)
    when Array
      # return FALSE if any item is TRUE
      !guard.detect { |condition| !guarded?([condition], stanza) }
    when Hash
      # return FALSE unless any inequality is found
      guard.find do |method, test|
        value = stanza.__send__(method)
        # last_match is the only method found unique to Regexp classes
        if test.class.respond_to?(:last_match)
          !(test =~ value.to_s)
        elsif test.is_a?(Array)
          !test.include? value
        else
          test != value
        end
      end
    when Proc
      !guard.call(stanza)
    end
  end
end
handle_stanza(stanza) click to toggle source
# File lib/blather/client/client.rb, line 315
def handle_stanza(stanza)
  handler = @tmp_handlers_mutex.synchronize do
    @tmp_handlers.delete(stanza.id)
  end

  if handler
    handler.call stanza
  else
    stanza.handler_hierarchy.each do |type|
      break if call_handler_for(type, stanza)
    end
  end
end
ready!() click to toggle source
# File lib/blather/client/client.rb, line 295
def ready!
  @state = :ready
  call_handler_for :ready, nil
end
run_filters(type, stanza) click to toggle source
# File lib/blather/client/client.rb, line 308
def run_filters(type, stanza)
  @filters[type].each do |guards, handler, filter|
    next if handler && !stanza.handler_hierarchy.include?(handler)
    catch(:pass) { call_handler filter, guards, stanza }
  end
end
setup_initial_handlers() click to toggle source
# File lib/blather/client/client.rb, line 272
def setup_initial_handlers
  register_handler :error do |err|
    raise err
  end

  # register_handler :iq, :type => [:get, :set] do |iq|
  #   write StanzaError.new(iq, 'service-unavailable', :cancel).to_node
  # end

  register_handler :ping, :type => :get do |ping|
    write ping.reply
  end

  register_handler :status do |status|
    roster[status.from].status = status if roster[status.from]
    nil
  end

  register_handler :roster do |node|
    roster.process node
  end
end
stream() click to toggle source
# File lib/blather/client/client.rb, line 259
def stream
  @stream || raise('Stream not ready!')
end