blather 0.4.7 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
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