Class: Jabber::Client

Inherits:
Connection show all
Defined in:
lib/xmpp4r/client.rb

Overview

The client class provides everything needed to build a basic XMPP Client.

If you want your connection to survive disconnects and timeouts, catch exception in Stream#on_exception and re-call Client#connect and Client#auth. Don’t forget to re-send initial Presence and everything else you need to setup your session.

Direct Known Subclasses

HTTPBinding::Client, Reliable::Connection

Constant Summary

Constants inherited from Stream

Stream::CONNECTED, Stream::DISCONNECTED

Instance Attribute Summary collapse

Attributes inherited from Connection

#allow_tls, #features_timeout, #host, #keepalive_interval, #port, #ssl_capath, #ssl_verifycb, #use_ssl

Attributes inherited from Stream

#fd, #processing, #status

Instance Method Summary collapse

Methods inherited from Connection

#accept_features, #close!, #is_tls?, #starttls

Methods inherited from Stream

#add_iq_callback, #add_message_callback, #add_presence_callback, #add_stanza_callback, #add_xml_callback, #close!, #delete_iq_callback, #delete_message_callback, #delete_presence_callback, #delete_stanza_callback, #delete_xml_callback, #iq_callbacks, #is_connected?, #is_disconnected?, #message_callbacks, #on_exception, #parse_failure, #parser_end, #presence_callbacks, #receive, #send, #send_data, #send_with_id, #stanza_callbacks, #stop, #xml_callbacks

Constructor Details

#initialize(jid) ⇒ Client

Create a new Client.

Remember to always put a resource in your JID unless the server can do SASL.



27
28
29
30
# File 'lib/xmpp4r/client.rb', line 27

def initialize(jid)
  super()
  @jid = (jid.kind_of?(JID) ? jid : JID.new(jid.to_s))
end

Instance Attribute Details

#jidObject (readonly)

The client’s JID



21
22
23
# File 'lib/xmpp4r/client.rb', line 21

def jid
  @jid
end

Instance Method Details

#auth(password) ⇒ Object

Authenticate with the server

Throws ClientAuthenticationFailure

Authentication mechanisms are used in the following preference:

  • SASL DIGEST-MD5

  • SASL PLAIN

  • Non-SASL digest

password
String


107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/xmpp4r/client.rb', line 107

def auth(password)
  begin
    if @stream_mechanisms.include? 'DIGEST-MD5'
      auth_sasl SASL.new(self, 'DIGEST-MD5'), password
    elsif @stream_mechanisms.include? 'PLAIN'
      auth_sasl SASL.new(self, 'PLAIN'), password
    else
      auth_nonsasl(password)
    end
  rescue
    Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}")
    raise ClientAuthenticationFailure.new, $!.to_s
  end
end

#auth_anonymousObject

See Client#auth_anonymous_sasl



196
197
198
# File 'lib/xmpp4r/client.rb', line 196

def auth_anonymous
  auth_anonymous_sasl
end

#auth_anonymous_saslObject

Shortcut for anonymous connection to server

Throws ClientAuthenticationFailure



205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/xmpp4r/client.rb', line 205

def auth_anonymous_sasl
  if self.supports_anonymous?
    begin
      auth_sasl SASL.new(self, 'ANONYMOUS'), ""
    rescue
      Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}")
      raise ClientAuthenticationFailure, $!.to_s
    end
  else
    raise ClientAuthenticationFailure, 'Anonymous authentication unsupported'
  end
end

#auth_nonsasl(password, digest = true) ⇒ Object

Send auth with given password and wait for result (non-SASL)

Throws ServerError

password
String

the password

digest
Boolean

use Digest authentication



234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/xmpp4r/client.rb', line 234

def auth_nonsasl(password, digest=true)
  authset = nil
  if digest
    authset = Iq.new_authset_digest(@jid, @streamid.to_s, password)
  else
    authset = Iq.new_authset(@jid, password)
  end
  send_with_id(authset)
  $defout.flush

  true
end

#auth_sasl(sasl, password) ⇒ Object

Use a SASL authentication mechanism and bind to a resource

If there was no resource given in the jid, the jid/resource generated by the server will be accepted.

This method should not be used directly. Instead, Client#auth may look for the best mechanism suitable.

sasl

Descendant of [Jabber::SASL::Base]

password
String


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/xmpp4r/client.rb', line 170

def auth_sasl(sasl, password)
  sasl.auth(password)

  # Restart stream after SASL auth
  stop
  start
  # And wait for features - again
  @features_sem.wait

  # Resource binding (RFC3920 - 7)
  if @stream_features.has_key? 'bind'
    @jid = bind(@jid.resource)
  end

  # Session starting
  if @stream_features.has_key? 'session'
    iq = Iq.new(:set)
    session = iq.add REXML::Element.new('session')
    session.add_namespace @stream_features['session']

    send_with_id(iq)
  end
end

#bind(desired_resource = nil) ⇒ Object

Resource binding (RFC3920bis-06 - section 8.)

XMPP allows to bind to multiple resources



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/xmpp4r/client.rb', line 126

def bind(desired_resource=nil)
  iq = Iq.new(:set)
  bind = iq.add REXML::Element.new('bind')
  bind.add_namespace @stream_features['bind']
  if desired_resource
    resource = bind.add REXML::Element.new('resource')
    resource.text = desired_resource
  end

  jid = nil
  send_with_id(iq) do |reply|
    reply_bind = reply.first_element('bind')
    if reply_bind
      reported_jid = reply_bind.first_element('jid')
      if reported_jid and reported_jid.text
        jid = JID.new(reported_jid.text)
      end
    end
  end
  jid
end

#closeObject

Close the connection, sends </stream:stream> tag first



77
78
79
80
81
82
# File 'lib/xmpp4r/client.rb', line 77

def close
  if @status == CONNECTED
    send("</stream:stream>")
  end
  super
end

#connect(host = nil, port = 5222) ⇒ Object

connect to the server (chaining-friendly)

If you omit the optional host argument SRV records for your jid will be resolved. If none works, fallback is connecting to the domain part of the jid.

host
String

Optional c2s host, will be extracted from jid if nil

use_ssl
Boolean

Optional. Use (old, deprecated) SSL when connecting.

return

self



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/xmpp4r/client.rb', line 42

def connect(host = nil, port = 5222)
  if host.nil?
    begin
      srv = []
      Resolv::DNS.open { |dns|
        # If ruby version is too old and SRV is unknown, this will raise a NameError
        # which is caught below
        Jabber::debuglog("RESOLVING:\n_xmpp-client._tcp.#{@jid.domain} (SRV)")
        srv = dns.getresources("_xmpp-client._tcp.#{@jid.domain}", Resolv::DNS::Resource::IN::SRV)
      }
      # Sort SRV records: lowest priority first, highest weight first
      srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }

      srv.each { |record|
        begin
          connect(record.target.to_s, record.port)
          # Success
          return self
        rescue SocketError, Errno::ECONNREFUSED
          # Try next SRV record
        end
      }
    rescue NameError
      Jabber::debuglog "Resolv::DNS does not support SRV records. Please upgrade to ruby-1.8.3 or later!"
    end
    # Fallback to normal connect method
  end

  super(host.nil? ? jid.domain : host, port)
  self
end

#password=(new_password) ⇒ Object

Change the client’s password

Threading is suggested, as this code waits for an answer.

Raises an exception upon error response (ServerError from Stream#send_with_id).

new_password
String

New password



334
335
336
337
338
339
340
341
342
# File 'lib/xmpp4r/client.rb', line 334

def password=(new_password)
  iq = Iq.new_query(:set, @jid.domain)
  iq.query.add_namespace('jabber:iq:register')
  iq.query.add(REXML::Element.new('username')).text = @jid.node
  iq.query.add(REXML::Element.new('password')).text = new_password

  err = nil
  send_with_id(iq)
end

#register(password, fields = {}) ⇒ Object

Register a new user account (may be used instead of Client#auth)

This method may raise ServerError if the registration was not successful.

password

String

fields

String=>String additional registration information

XEP-0077 Defines the following fields for registration information: www.xmpp.org/extensions/xep-0077.html

‘username’ => ‘Account name associated with the user’ ‘nick’ => ‘Familiar name of the user’ ‘password’ => ‘Password or secret for the user’ ‘name’ => ‘Full name of the user’ ‘first’ => ‘First name or given name of the user’ ‘last’ => ‘Last name, surname, or family name of the user’ ‘email’ => ‘Email address of the user’ ‘address’ => ‘Street portion of a physical or mailing address’ ‘city’ => ‘Locality portion of a physical or mailing address’ ‘state’ => ‘Region portion of a physical or mailing address’ ‘zip’ => ‘Postal code portion of a physical or mailing address’ ‘phone’ => ‘Telephone number of the user’ ‘url’ => ‘URL to web page describing the user’ ‘date’ => ‘Some date (e.g., birth date, hire date, sign-up date)’



303
304
305
306
307
308
309
310
311
# File 'lib/xmpp4r/client.rb', line 303

def register(password, fields={})
  reg = Iq.new_register(jid.node, password)
  reg.to = jid.domain
  fields.each { |name,value|
    reg.query.add(REXML::Element.new(name)).text = value
  }

  send_with_id(reg)
end

#register_infoObject

Get instructions and available fields for registration

return
instructions, fields

Where instructions is a String and fields is an Array of Strings



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/xmpp4r/client.rb', line 250

def register_info
  instructions = nil
  fields = []

  reg = Iq.new_registerget
  reg.to = jid.domain
  send_with_id(reg) do |answer|
    if answer.query
      answer.query.each_element { |e|
        if e.namespace == 'jabber:iq:register'
          if e.name == 'instructions'
            instructions = e.text.strip
          else
            fields << e.name
          end
        end
      }
    end

    true
  end

  [instructions, fields]
end

#remove_registrationObject

Remove the registration of a user account

WARNING: this deletes your roster and everything else stored on the server!



318
319
320
321
322
323
# File 'lib/xmpp4r/client.rb', line 318

def remove_registration
  reg = Iq.new_register
  reg.to = jid.domain
  reg.query.add(REXML::Element.new('remove'))
  send_with_id(reg)
end

#startObject

Start the stream-parser and send the client-specific stream opening element



86
87
88
89
90
91
92
93
94
95
# File 'lib/xmpp4r/client.rb', line 86

def start
  super
  send(generate_stream_start(@jid.domain)) { |e|
    if e.name == 'stream'
      true
    else
      false
    end
  }
end

#supports_anonymous?Boolean

Reports whether or not anonymous authentication is reported by the client.

Returns true or false

Returns:

  • (Boolean)


223
224
225
# File 'lib/xmpp4r/client.rb', line 223

def supports_anonymous?
  @stream_mechanisms.include? 'ANONYMOUS'
end

#unbind(desired_resource) ⇒ Object

Resource unbinding (RFC3920bis-06 - section 8.6.3.)



150
151
152
153
154
155
156
157
158
# File 'lib/xmpp4r/client.rb', line 150

def unbind(desired_resource)
  iq = Iq.new(:set)
  unbind = iq.add REXML::Element.new('unbind')
  unbind.add_namespace @stream_features['unbind']
  resource = unbind.add REXML::Element.new('resource')
  resource.text = desired_resource

  send_with_id(iq)
end