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
31
# File 'lib/xmpp4r/client.rb', line 27

def initialize(jid)
  super()
  @jid = (jid.kind_of?(JID) ? jid : JID.new(jid.to_s))
  @authenticated = false
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

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

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
    @authenticated = true
  rescue
    Jabber::debuglog("#{$!.class}: #{$!}\n#{$!.backtrace.join("\n")}")
    raise ClientAuthenticationFailure.new, $!.to_s
  end
end

#auth_anonymousObject

See Client#auth_anonymous_sasl


210
211
212
# File 'lib/xmpp4r/client.rb', line 210

def auth_anonymous
  auth_anonymous_sasl
end

#auth_anonymous_saslObject

Shortcut for anonymous connection to server

Throws ClientAuthenticationFailure


219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/xmpp4r/client.rb', line 219

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


248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/xmpp4r/client.rb', line 248

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)
  $>.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

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/xmpp4r/client.rb', line 175

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

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

  # Resource binding (RFC3920 - 7)
  if @stream_features.has_key? 'bind'
    Jabber::debuglog("**********Handling 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']

    semaphore = Semaphore.new
    send_with_id(iq) {
      semaphore.run
    }
    semaphore.wait
  end
end

#bind(desired_resource = nil) ⇒ Object

Resource binding (RFC3920bis-06 - section 8.)

XMPP allows to bind to multiple resources


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

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
  semaphore = Semaphore.new
  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
    semaphore.run
  end
  semaphore.wait
  jid
end

#closeObject

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


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

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

port
Fixnum

The server port (default: 5222)

return

self


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
73
# File 'lib/xmpp4r/client.rb', line 43

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


348
349
350
351
352
353
354
355
356
# File 'lib/xmpp4r/client.rb', line 348

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)'


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

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


264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/xmpp4r/client.rb', line 264

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!


332
333
334
335
336
337
# File 'lib/xmpp4r/client.rb', line 332

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

#restartObject


203
204
205
206
# File 'lib/xmpp4r/client.rb', line 203

def restart
  stop
  start
end

#startObject

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


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

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)

237
238
239
# File 'lib/xmpp4r/client.rb', line 237

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

#unbind(desired_resource) ⇒ Object

Resource unbinding (RFC3920bis-06 - section 8.6.3.)


155
156
157
158
159
160
161
162
163
# File 'lib/xmpp4r/client.rb', line 155

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