Class: Jabber::Client
- Inherits:
-
Connection
- Object
- Stream
- Connection
- Jabber::Client
- 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
Constant Summary
Constants inherited from Stream
Stream::CONNECTED, Stream::DISCONNECTED
Instance Attribute Summary collapse
-
#jid ⇒ Object
readonly
The client’s JID.
Attributes inherited from Connection
#allow_tls, #features_timeout, #host, #keepalive_interval, #port, #ssl_capath, #ssl_verifycb, #use_ssl
Attributes inherited from Stream
Instance Method Summary collapse
-
#auth(password) ⇒ Object
Authenticate with the server.
-
#auth_anonymous ⇒ Object
See Client#auth_anonymous_sasl.
-
#auth_anonymous_sasl ⇒ Object
Shortcut for anonymous connection to server.
-
#auth_nonsasl(password, digest = true) ⇒ Object
Send auth with given password and wait for result (non-SASL).
-
#auth_sasl(sasl, password) ⇒ Object
Use a SASL authentication mechanism and bind to a resource.
-
#bind(desired_resource = nil) ⇒ Object
Resource binding (RFC3920bis-06 - section 8.).
-
#close ⇒ Object
Close the connection, sends
</stream:stream>
tag first. -
#connect(host = nil, port = 5222) ⇒ Object
connect to the server (chaining-friendly).
-
#initialize(jid) ⇒ Client
constructor
Create a new Client.
-
#password=(new_password) ⇒ Object
Change the client’s password.
-
#register(password, fields = {}) ⇒ Object
Register a new user account (may be used instead of Client#auth).
-
#register_info ⇒ Object
- Get instructions and available fields for registration return
- instructions, fields
-
Where instructions is a String and fields is an Array of Strings.
-
#remove_registration ⇒ Object
Remove the registration of a user account.
- #restart ⇒ Object
-
#start ⇒ Object
Start the stream-parser and send the client-specific stream opening element.
-
#supports_anonymous? ⇒ Boolean
Reports whether or not anonymous authentication is reported by the client.
-
#unbind(desired_resource) ⇒ Object
Resource unbinding (RFC3920bis-06 - section 8.6.3.).
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
Instance Attribute Details
#jid ⇒ Object (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_anonymous ⇒ Object
See Client#auth_anonymous_sasl
210 211 212 |
# File 'lib/xmpp4r/client.rb', line 210 def auth_anonymous auth_anonymous_sasl end |
#auth_anonymous_sasl ⇒ Object
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 |
#close ⇒ Object
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
- use_ssl
- Boolean
-
Optional. Use (old, deprecated) SSL when connecting.
- 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_info ⇒ Object
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_registration ⇒ Object
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 |
#restart ⇒ Object
203 204 205 206 |
# File 'lib/xmpp4r/client.rb', line 203 def restart stop start end |
#start ⇒ Object
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
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 |