blather 0.4.7 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/README.md +162 -0
  2. data/examples/{print_heirarchy.rb → print_hierarchy.rb} +5 -5
  3. data/examples/stream_only.rb +27 -0
  4. data/lib/blather.rb +4 -0
  5. data/lib/blather/client/client.rb +91 -73
  6. data/lib/blather/client/dsl.rb +156 -32
  7. data/lib/blather/client/dsl/pubsub.rb +86 -54
  8. data/lib/blather/core_ext/active_support.rb +9 -9
  9. data/lib/blather/core_ext/active_support/inheritable_attributes.rb +2 -2
  10. data/lib/blather/core_ext/nokogiri.rb +12 -7
  11. data/lib/blather/errors.rb +25 -14
  12. data/lib/blather/errors/sasl_error.rb +21 -3
  13. data/lib/blather/errors/stanza_error.rb +37 -21
  14. data/lib/blather/errors/stream_error.rb +27 -17
  15. data/lib/blather/jid.rb +79 -24
  16. data/lib/blather/roster.rb +39 -21
  17. data/lib/blather/roster_item.rb +43 -21
  18. data/lib/blather/stanza.rb +88 -40
  19. data/lib/blather/stanza/disco.rb +12 -2
  20. data/lib/blather/stanza/disco/disco_info.rb +112 -20
  21. data/lib/blather/stanza/disco/disco_items.rb +81 -12
  22. data/lib/blather/stanza/iq.rb +94 -38
  23. data/lib/blather/stanza/iq/query.rb +16 -22
  24. data/lib/blather/stanza/iq/roster.rb +98 -20
  25. data/lib/blather/stanza/message.rb +266 -111
  26. data/lib/blather/stanza/presence.rb +118 -42
  27. data/lib/blather/stanza/presence/status.rb +140 -60
  28. data/lib/blather/stanza/presence/subscription.rb +44 -10
  29. data/lib/blather/stanza/pubsub.rb +70 -15
  30. data/lib/blather/stanza/pubsub/affiliations.rb +36 -7
  31. data/lib/blather/stanza/pubsub/create.rb +26 -4
  32. data/lib/blather/stanza/pubsub/errors.rb +13 -4
  33. data/lib/blather/stanza/pubsub/event.rb +56 -10
  34. data/lib/blather/stanza/pubsub/items.rb +46 -6
  35. data/lib/blather/stanza/pubsub/publish.rb +52 -7
  36. data/lib/blather/stanza/pubsub/retract.rb +45 -6
  37. data/lib/blather/stanza/pubsub/subscribe.rb +30 -4
  38. data/lib/blather/stanza/pubsub/subscription.rb +74 -6
  39. data/lib/blather/stanza/pubsub/subscriptions.rb +35 -9
  40. data/lib/blather/stanza/pubsub/unsubscribe.rb +30 -4
  41. data/lib/blather/stanza/pubsub_owner.rb +17 -7
  42. data/lib/blather/stanza/pubsub_owner/delete.rb +23 -5
  43. data/lib/blather/stanza/pubsub_owner/purge.rb +23 -5
  44. data/lib/blather/stream.rb +96 -29
  45. data/lib/blather/stream/parser.rb +6 -9
  46. data/lib/blather/xmpp_node.rb +101 -153
  47. data/spec/blather/client/client_spec.rb +1 -1
  48. data/spec/blather/errors_spec.rb +5 -5
  49. data/spec/blather/stanza/message_spec.rb +56 -0
  50. data/spec/blather/stanza/presence/status_spec.rb +1 -1
  51. data/spec/blather/stanza_spec.rb +3 -3
  52. data/spec/blather/xmpp_node_spec.rb +19 -74
  53. metadata +6 -10
  54. data/README.rdoc +0 -185
  55. data/examples/drb_client.rb +0 -5
  56. data/examples/ping.rb +0 -11
  57. data/examples/pong.rb +0 -6
  58. data/examples/pubsub/cli.rb +0 -64
  59. data/examples/pubsub/ping_pong.rb +0 -18
@@ -2,14 +2,19 @@ module Blather
2
2
  class Stanza
3
3
  class PubSub
4
4
 
5
+ # # PubSub Subscriptions Stanza
6
+ #
7
+ # [XEP-0060 Section 5.6 Retrieve Subscriptions](http://xmpp.org/extensions/xep-0060.html#entity-subscriptions)
8
+ #
9
+ # @handler :pubsub_subscriptions
5
10
  class Subscriptions < PubSub
6
11
  register :pubsub_subscriptions, :subscriptions, self.registered_ns
7
12
 
8
13
  include Enumerable
9
14
  alias_method :find, :xpath
10
15
 
11
- ##
12
- # Ensure the namespace is set to the query node
16
+ # Overrides the parent to ensure a subscriptions node is created
17
+ # @private
13
18
  def self.new(type = nil, host = nil)
14
19
  new_node = super type
15
20
  new_node.to = host
@@ -17,27 +22,48 @@ class PubSub
17
22
  new_node
18
23
  end
19
24
 
20
- ##
21
- # Kill the pubsub node before running inherit
25
+ # Overrides the parent to ensure the subscriptions node is destroyed
26
+ # @private
22
27
  def inherit(node)
23
28
  subscriptions.remove
24
29
  super
25
30
  end
26
31
 
32
+ # Get or create the actual subscriptions node
33
+ #
34
+ # @return [Blather::XMPPNode]
27
35
  def subscriptions
28
36
  aff = pubsub.find_first('subscriptions', self.class.registered_ns)
29
- (self.pubsub << (aff = XMPPNode.new('subscriptions', self.document))) unless aff
37
+ unless aff
38
+ (self.pubsub << (aff = XMPPNode.new('subscriptions', self.document)))
39
+ end
30
40
  aff
31
41
  end
32
42
 
43
+ # Iterate over the list of subscriptions
44
+ #
45
+ # @yieldparam [Hash] subscription
46
+ # @see {#list}
33
47
  def each(&block)
34
48
  list.each &block
35
49
  end
36
50
 
51
+ # Get the size of the subscriptions list
52
+ #
53
+ # @return [Fixnum]
37
54
  def size
38
55
  list.size
39
56
  end
40
57
 
58
+ # Get a hash of subscriptions
59
+ #
60
+ # @example
61
+ # { :subscribed => [{:node => 'node1', :jid => 'francisco@denmark.lit'}, {:node => 'node2', :jid => 'francisco@denmark.lit'}],
62
+ # :unconfigured => [{:node => 'node3', :jid => 'francisco@denmark.lit'}],
63
+ # :pending => [{:node => 'node4', :jid => 'francisco@denmark.lit'}],
64
+ # :none => [{:node => 'node5', :jid => 'francisco@denmark.lit'}] }
65
+ #
66
+ # @return [Hash]
41
67
  def list
42
68
  subscriptions.find('//ns:subscription', :ns => self.class.registered_ns).inject({}) do |hash, item|
43
69
  hash[item[:subscription].to_sym] ||= []
@@ -48,8 +74,8 @@ class PubSub
48
74
  hash
49
75
  end
50
76
  end
51
- end #Subscriptions
77
+ end # Subscriptions
52
78
 
53
- end #PubSub
54
- end #Stanza
55
- end #Blather
79
+ end # PubSub
80
+ end # Stanza
81
+ end # Blather
@@ -2,9 +2,20 @@ module Blather
2
2
  class Stanza
3
3
  class PubSub
4
4
 
5
+ # # PubSub Unsubscribe Stanza
6
+ #
7
+ # [XEP-0060 Section 6.2 - Unsubscribe from a Node](http://xmpp.org/extensions/xep-0060.html#subscriber-unsubscribe)
8
+ #
9
+ # @handler :pubsub_unsubscribe
5
10
  class Unsubscribe < PubSub
6
11
  register :pubsub_unsubscribe, :unsubscribe, self.registered_ns
7
12
 
13
+ # Create a new unsubscribe node
14
+ #
15
+ # @param [Blather::Stanza::Iq::VALID_TYPES] type the IQ stanza type
16
+ # @param [String] host the host to send the request to
17
+ # @param [String] node the node to unsubscribe from
18
+ # @param [Blather::JID, #to_s] jid the JID of the unsubscription
8
19
  def self.new(type = :set, host = nil, node = nil, jid = nil)
9
20
  new_node = super(type, host)
10
21
  new_node.node = node
@@ -12,22 +23,37 @@ class PubSub
12
23
  new_node
13
24
  end
14
25
 
26
+ # Get the JID of the unsubscription
27
+ #
28
+ # @return [Blather::JID]
15
29
  def jid
16
30
  JID.new(unsubscribe[:jid])
17
31
  end
18
32
 
33
+ # Set the JID of the unsubscription
34
+ #
35
+ # @param [Blather::JID, #to_s] jid
19
36
  def jid=(jid)
20
37
  unsubscribe[:jid] = jid
21
38
  end
22
39
 
40
+ # Get the name of the node to unsubscribe from
41
+ #
42
+ # @return [String]
23
43
  def node
24
44
  unsubscribe[:node]
25
45
  end
26
46
 
47
+ # Set the name of the node to unsubscribe from
48
+ #
49
+ # @param [String] node
27
50
  def node=(node)
28
51
  unsubscribe[:node] = node
29
52
  end
30
53
 
54
+ # Get or create the actual unsubscribe node
55
+ #
56
+ # @return [Blather::XMPPNode]
31
57
  def unsubscribe
32
58
  unless unsubscribe = pubsub.find_first('ns:unsubscribe', :ns => self.class.registered_ns)
33
59
  self.pubsub << (unsubscribe = XMPPNode.new('unsubscribe', self.document))
@@ -35,8 +61,8 @@ class PubSub
35
61
  end
36
62
  unsubscribe
37
63
  end
38
- end #Unsubscribe
64
+ end # Unsubscribe
39
65
 
40
- end #PubSub
41
- end #Stanza
42
- end #Blather
66
+ end # PubSub
67
+ end # Stanza
68
+ end # Blather
@@ -1,9 +1,16 @@
1
1
  module Blather
2
2
  class Stanza
3
3
 
4
+ # # PubSubOwner Base Class
5
+ #
6
+ # [XEP-0060 - Publish-Subscribe](http://xmpp.org/extensions/xep-0060.html)
7
+ #
8
+ # @handler :pubsub_owner
4
9
  class PubSubOwner < Iq
5
10
  register :pubsub_owner, :pubsub, 'http://jabber.org/protocol/pubsub#owner'
6
11
 
12
+ # Creates the proper class from the stana's child
13
+ # @private
7
14
  def self.import(node)
8
15
  klass = nil
9
16
  if pubsub = node.document.find_first('//ns:pubsub', :ns => self.registered_ns)
@@ -12,8 +19,8 @@ class Stanza
12
19
  (klass || self).new(node[:type]).inherit(node)
13
20
  end
14
21
 
15
- ##
16
- # Ensure the namespace is set to the query node
22
+ # Overrides the parent to ensure a pubsub node is created
23
+ # @private
17
24
  def self.new(type = nil, host = nil)
18
25
  new_node = super type
19
26
  new_node.to = host
@@ -21,13 +28,16 @@ class Stanza
21
28
  new_node
22
29
  end
23
30
 
24
- ##
25
- # Kill the pubsub node before running inherit
31
+ # Overrides the parent to ensure the pubsub node is destroyed
32
+ # @private
26
33
  def inherit(node)
27
34
  remove_children :pubsub
28
35
  super
29
36
  end
30
37
 
38
+ # Get or create the pubsub node on the stanza
39
+ #
40
+ # @return [Blather::XMPPNode]
31
41
  def pubsub
32
42
  unless p = find_first('ns:pubsub', :ns => self.class.registered_ns)
33
43
  self << (p = XMPPNode.new('pubsub', self.document))
@@ -35,7 +45,7 @@ class Stanza
35
45
  end
36
46
  p
37
47
  end
38
- end
48
+ end # PubSubOwner
39
49
 
40
- end #Stanza
41
- end #Blather
50
+ end # Stanza
51
+ end # Blather
@@ -2,24 +2,42 @@ module Blather
2
2
  class Stanza
3
3
  class PubSubOwner
4
4
 
5
+ # # PubSubOwner Delete Stanza
6
+ #
7
+ # [XEP-0060 Section 8.4 Delete a Node](http://xmpp.org/extensions/xep-0060.html#owner-delete)
8
+ #
9
+ # @handler :pubsub_delete
5
10
  class Delete < PubSubOwner
6
-
7
11
  register :pubsub_delete, :delete, self.registered_ns
8
12
 
13
+ # Create a new delete stanza
14
+ #
15
+ # @param [Blather::Stanza::Iq::VALID_TYPES] type the IQ stanza type
16
+ # @param [String] host the host to send the request to
17
+ # @param [String] node the name of the node to delete
9
18
  def self.new(type = :set, host = nil, node = nil)
10
19
  new_node = super(type, host)
11
20
  new_node.node = node
12
21
  new_node
13
22
  end
14
23
 
24
+ # Get the name of the node to delete
25
+ #
26
+ # @return [String]
15
27
  def node
16
28
  delete_node[:node]
17
29
  end
18
30
 
31
+ # Set the name of the node to delete
32
+ #
33
+ # @param [String] node
19
34
  def node=(node)
20
35
  delete_node[:node] = node
21
36
  end
22
37
 
38
+ # Get or create the actual delete node on the stanza
39
+ #
40
+ # @return [Blather::XMPPNode]
23
41
  def delete_node
24
42
  unless delete_node = pubsub.find_first('ns:delete', :ns => self.class.registered_ns)
25
43
  self.pubsub << (delete_node = XMPPNode.new('delete', self.document))
@@ -27,8 +45,8 @@ class PubSubOwner
27
45
  end
28
46
  delete_node
29
47
  end
30
- end #Retract
48
+ end # Retract
31
49
 
32
- end #PubSub
33
- end #Stanza
34
- end #Blather
50
+ end # PubSub
51
+ end # Stanza
52
+ end # Blather
@@ -2,24 +2,42 @@ module Blather
2
2
  class Stanza
3
3
  class PubSubOwner
4
4
 
5
+ # # PubSubOwner Purge Stanza
6
+ #
7
+ # [XEP-0060 Section 8.5 - Purge All Node Items](http://xmpp.org/extensions/xep-0060.html#owner-purge)
8
+ #
9
+ # @handler :pubsub_purge
5
10
  class Purge < PubSubOwner
6
-
7
11
  register :pubsub_purge, :purge, self.registered_ns
8
12
 
13
+ # Create a new purge stanza
14
+ #
15
+ # @param [Blather::Stanza::Iq::VALID_TYPES] type the IQ stanza type
16
+ # @param [String] host the host to send the request to
17
+ # @param [String] node the name of the node to purge
9
18
  def self.new(type = :set, host = nil, node = nil)
10
19
  new_node = super(type, host)
11
20
  new_node.node = node
12
21
  new_node
13
22
  end
14
23
 
24
+ # Get the name of the node to delete
25
+ #
26
+ # @return [String]
15
27
  def node
16
28
  purge_node[:node]
17
29
  end
18
30
 
31
+ # Set the name of the node to delete
32
+ #
33
+ # @param [String] node
19
34
  def node=(node)
20
35
  purge_node[:node] = node
21
36
  end
22
37
 
38
+ # Get or create the actual purge node on the stanza
39
+ #
40
+ # @return [Blather::XMPPNode]
23
41
  def purge_node
24
42
  unless purge_node = pubsub.find_first('ns:purge', :ns => self.class.registered_ns)
25
43
  self.pubsub << (purge_node = XMPPNode.new('purge', self.document))
@@ -27,8 +45,8 @@ class PubSubOwner
27
45
  end
28
46
  purge_node
29
47
  end
30
- end #Retract
48
+ end # Retract
31
49
 
32
- end #PubSub
33
- end #Stanza
34
- end #Blather
50
+ end # PubSub
51
+ end # Stanza
52
+ end # Blather
@@ -1,5 +1,52 @@
1
1
  module Blather
2
2
 
3
+ # # A pure XMPP stream.
4
+ #
5
+ # Blather::Stream can be used to build your own handler system if Blather's
6
+ # doesn't suit your needs. It will take care of the entire connection
7
+ # process then start sending Stanza objects back to the registered client.
8
+ #
9
+ # The client you register with Blather::Stream needs to implement the following
10
+ # methods:
11
+ # * #post_init(stream, jid = nil)
12
+ # Called after the stream has been initiated.
13
+ # @param [Blather::Stream] stream is the connected stream object
14
+ # @param [Blather::JID, nil] jid is the full JID as recognized by the server
15
+ #
16
+ # * #receive_data(stanza)
17
+ # Called every time the stream receives a new stanza
18
+ # @param [Blather::Stanza] stanza a stanza object from the server
19
+ #
20
+ # * #unbind
21
+ # Called when the stream is shutdown. This will be called regardless of which
22
+ # side shut the stream down.
23
+ #
24
+ # @example Create a new stream and handle it with our own class
25
+ # class MyClient
26
+ # attr :jid
27
+ #
28
+ # def post_init(stream, jid = nil)
29
+ # @stream = stream
30
+ # self.jid = jid
31
+ # p "Stream Started"
32
+ # end
33
+ #
34
+ # # Pretty print the stream
35
+ # def receive_data(stanza)
36
+ # pp stanza
37
+ # end
38
+ #
39
+ # def unbind
40
+ # p "Stream Ended"
41
+ # end
42
+ #
43
+ # def write(what)
44
+ # @stream.write what
45
+ # end
46
+ # end
47
+ #
48
+ # client = Blather::Stream.start MyClient.new, "jid@domain/res", "pass"
49
+ # client.write "[pure xml over the wire]"
3
50
  class Stream < EventMachine::Connection
4
51
  class NoConnection < RuntimeError; end
5
52
 
@@ -7,13 +54,16 @@ module Blather
7
54
  attr_accessor :password
8
55
  attr_reader :jid
9
56
 
10
- ##
11
57
  # Start the stream between client and server
12
- # [client] must be an object that will respond to #call and #jid=
13
- # [jid] must be a valid argument for JID.new (see JID)
14
- # [pass] must be the password
15
- # [host] (optional) must be the hostname or IP to connect to. defaults to the domain of [jid]
16
- # [port] (optional) must be the port to connect to. defaults to 5222
58
+ #
59
+ # @param [Object] client an object that will respond to #post_init,
60
+ # #unbind #receive_data
61
+ # @param [Blather::JID, #to_s] jid the jid to authenticate with
62
+ # @param [String] pass the password to authenticate with
63
+ # @param [String, nil] host the hostname or IP to connect to. Default is
64
+ # to use the domain on the JID
65
+ # @param [Fixnum, nil] port the port to connect on. Default is the XMPP
66
+ # default of 5222
17
67
  def self.start(client, jid, pass, host = nil, port = 5222)
18
68
  jid = JID.new jid
19
69
  if host
@@ -21,20 +71,32 @@ module Blather
21
71
  else
22
72
  require 'resolv'
23
73
  srv = []
24
- Resolv::DNS.open { |dns| srv = dns.getresources("_xmpp-client._tcp.#{jid.domain}", Resolv::DNS::Resource::IN::SRV) }
74
+ Resolv::DNS.open do |dns|
75
+ srv = dns.getresources(
76
+ "_xmpp-client._tcp.#{jid.domain}",
77
+ Resolv::DNS::Resource::IN::SRV
78
+ )
79
+ end
80
+
25
81
  if srv.empty?
26
82
  connect jid.domain, port, self, client, jid, pass
27
83
  else
28
- srv.sort! { |a,b| (a.priority != b.priority) ? (a.priority <=> b.priority) : (b.weight <=> a.weight) }
29
- srv.each { |r| break unless connect(r.target.to_s, r.port, self, client, jid, pass) === false }
84
+ srv.sort! do |a,b|
85
+ (a.priority != b.priority) ? (a.priority <=> b.priority) :
86
+ (b.weight <=> a.weight)
87
+ end
88
+
89
+ srv.detect do |r|
90
+ not connect(r.target.to_s, r.port, self, client, jid, pass) === false
91
+ end
30
92
  end
31
93
  end
32
94
  end
33
95
 
34
- ##
35
96
  # Attempt a connection
36
97
  # Stream will raise +NoConnection+ if it receives #unbind before #post_init
37
98
  # this catches that and returns false prompting for another attempt
99
+ # @private
38
100
  def self.connect(host, port, conn, client, jid, pass)
39
101
  EM.connect host, port, conn, client, jid, pass
40
102
  rescue NoConnection
@@ -45,19 +107,19 @@ module Blather
45
107
  define_method("#{state}?") { @state == state }
46
108
  end
47
109
 
48
- ##
49
110
  # Send data over the wire
50
- # The argument for this can be anything that
51
- # responds to #to_s
111
+ #
112
+ # @todo Queue if not ready
113
+ #
114
+ # @param [#to_xml, #to_s] stanza the stanza to send over the wire
52
115
  def send(stanza)
53
- #TODO Queue if not ready
54
116
  Blather.logger.debug "SENDING: (#{caller[1]}) #{stanza}"
55
117
  send_data stanza.respond_to?(:to_xml) ? stanza.to_xml : stanza.to_s
56
118
  end
57
119
 
58
- ##
59
120
  # Called by EM.connect to initialize stream variables
60
- def initialize(client, jid, pass) # :nodoc:
121
+ # @private
122
+ def initialize(client, jid, pass)
61
123
  super()
62
124
 
63
125
  @error = nil
@@ -68,17 +130,17 @@ module Blather
68
130
  @password = pass
69
131
  end
70
132
 
71
- ##
72
133
  # Called when EM completes the connection to the server
73
134
  # this kicks off the starttls/authorize/bind process
74
- def connection_completed # :nodoc:
135
+ # @private
136
+ def connection_completed
75
137
  # @keepalive = EM::PeriodicTimer.new(60) { send_data ' ' }
76
138
  start
77
139
  end
78
140
 
79
- ##
80
141
  # Called by EM with data from the wire
81
- def receive_data(data) # :nodoc:
142
+ # @private
143
+ def receive_data(data)
82
144
  Blather.logger.debug "\n#{'-'*30}\n"
83
145
  Blather.logger.debug "STREAM IN: #{data}"
84
146
  @parser << data
@@ -89,13 +151,15 @@ module Blather
89
151
  stop
90
152
  end
91
153
 
154
+ # Called by EM after the connection has started
155
+ # @private
92
156
  def post_init
93
157
  @connected = true
94
158
  end
95
159
 
96
- ##
97
160
  # Called by EM when the connection is closed
98
- def unbind # :nodoc:
161
+ # @private
162
+ def unbind
99
163
  raise NoConnection unless @connected
100
164
 
101
165
  # @keepalive.cancel
@@ -104,9 +168,9 @@ module Blather
104
168
  @client.unbind
105
169
  end
106
170
 
107
- ##
108
171
  # Called by the parser with parsed nodes
109
- def receive(node) # :nodoc:
172
+ # @private
173
+ def receive(node)
110
174
  Blather.logger.debug "RECEIVING (#{node.element_name}) #{node}"
111
175
  @node = node
112
176
 
@@ -133,16 +197,16 @@ module Blather
133
197
  @receiver.receive_data @node.to_stanza
134
198
  end
135
199
 
136
- ##
137
200
  # Ensure the JID gets attached to the client
138
- def jid=(new_jid) # :nodoc:
201
+ # @private
202
+ def jid=(new_jid)
139
203
  Blather.logger.debug "NEW JID: #{new_jid}"
140
204
  @jid = JID.new new_jid
141
205
  end
142
206
 
143
207
  protected
144
- ##
145
208
  # Stop the stream
209
+ # @private
146
210
  def stop
147
211
  unless @state == :stopped
148
212
  @state = :stopped
@@ -150,15 +214,18 @@ module Blather
150
214
  end
151
215
  end
152
216
 
217
+ # @private
153
218
  def handle_stream_error
154
219
  @error = StreamError.import(@node)
155
220
  stop
156
221
  end
157
222
 
223
+ # @private
158
224
  def ready!
159
225
  @state = :started
160
226
  @receiver = @client
161
227
  @client.post_init self, @jid
162
228
  end
163
- end
164
- end
229
+ end # Stream
230
+
231
+ end # Blather