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.

Constant Summary

Constants inherited from Stream

Stream::CONNECTED, Stream::DISCONNECTED

Instance Attribute Summary collapse

Attributes inherited from Connection

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

Attributes inherited from Stream

#fd, #status

Instance Method Summary collapse

Methods inherited from Connection

#accept_features, #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, #is_connected?, #is_disconnected?, #on_exception, #parse_failure, #parser_end, #poll, #process, #receive, #send, #send_with_id, #stop, #wait_and_process

Constructor Details

#initialize(jid, threaded = true) ⇒ Client

Create a new Client. If threaded mode is activated, callbacks are called as soon as messages are received; If it isn't, you have to call Stream#process from time to time.

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


31
32
33
34
# File 'lib/xmpp4r/client.rb', line 31

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

Instance Attribute Details

#jidObject (readonly)

The client's JID


23
24
25
# File 'lib/xmpp4r/client.rb', line 23

def jid
  @jid
end

Instance Method Details

#auth(password) ⇒ Object

Authenticate with the server

Throws AuthenticationFailure

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

#auth_nonsasl(password, digest = true) ⇒ Object

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

Throws ErrorException

password
String

the password

digest
Boolean

use Digest authentication


180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/xmpp4r/client.rb', line 180

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) do |r|
    true
  end
  $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

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/xmpp4r/client.rb', line 133

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

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

  # Resource binding (RFC3920 - 7)
  if @stream_features.has_key? 'bind'
    iq = Iq.new(:set)
    bind = iq.add REXML::Element.new('bind')
    bind.add_namespace @stream_features['bind']
    if jid.resource
      resource = bind.add REXML::Element.new('resource')
      resource.text = jid.resource
    end

    send_with_id(iq) { |reply|
      reported_jid = reply.first_element('jid')
      if reply.type == :result and reported_jid and reported_jid.text
        @jid = JID.new(reported_jid.text)
      end

      true
    }
  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) { true }
  end
end

#closeObject

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


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

def close
  send("</stream:stream>")
  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

return

self


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
74
75
# File 'lib/xmpp4r/client.rb', line 45

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 catched 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
          # Try next SRV record
        end
      }
    rescue NameError
      $stderr.puts "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 (ErrorException from Stream#send_with_id).

new_password
String

New password


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

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) { |answer|
    if answer.type == :result
      true
    else
      false
    end
  }
end

#register(password) ⇒ Object

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

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


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

def register(password)
  reg = Iq.new_register(jid.node, password)
  reg.to = jid.domain
  send_with_id(reg) { |answer|
    true
  }
end

#remove_registrationObject

Remove the registration of a user account

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


214
215
216
217
218
219
220
221
222
# File 'lib/xmpp4r/client.rb', line 214

def remove_registration
  reg = Iq.new_register
  reg.to = jid.domain
  reg.query.add(REXML::Element.new('remove'))
  send_with_id(reg) { |answer|
    p answer.to_s
    true
  }
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