blather 0.2.1 → 0.2.2

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 (84) hide show
  1. data/LICENSE +2 -0
  2. data/README.rdoc +54 -29
  3. data/Rakefile +94 -13
  4. data/VERSION.yml +4 -0
  5. data/examples/drb_client.rb +2 -4
  6. data/examples/echo.rb +13 -8
  7. data/examples/pubsub/cli.rb +64 -0
  8. data/examples/pubsub/ping_pong.rb +18 -0
  9. data/examples/pubsub/pubsub_dsl.rb +52 -0
  10. data/examples/pubsub_client.rb +39 -0
  11. data/examples/rosterprint.rb +14 -0
  12. data/examples/xmpp4r/echo.rb +35 -0
  13. data/ext/extconf.rb +65 -0
  14. data/lib/blather.rb +18 -121
  15. data/lib/blather/client.rb +13 -0
  16. data/lib/blather/client/client.rb +165 -0
  17. data/lib/blather/client/dsl.rb +99 -0
  18. data/lib/blather/client/pubsub.rb +53 -0
  19. data/lib/blather/client/pubsub/node.rb +27 -0
  20. data/lib/blather/core_ext/active_support.rb +1 -0
  21. data/lib/blather/core_ext/libxml.rb +7 -1
  22. data/lib/blather/errors.rb +39 -18
  23. data/lib/blather/errors/sasl_error.rb +87 -0
  24. data/lib/blather/errors/stanza_error.rb +262 -0
  25. data/lib/blather/errors/stream_error.rb +253 -0
  26. data/lib/blather/jid.rb +9 -16
  27. data/lib/blather/roster.rb +9 -0
  28. data/lib/blather/roster_item.rb +7 -4
  29. data/lib/blather/stanza.rb +19 -25
  30. data/lib/blather/stanza/disco.rb +9 -0
  31. data/lib/blather/stanza/disco/disco_info.rb +84 -0
  32. data/lib/blather/stanza/disco/disco_items.rb +59 -0
  33. data/lib/blather/stanza/iq.rb +16 -4
  34. data/lib/blather/stanza/iq/query.rb +6 -4
  35. data/lib/blather/stanza/iq/roster.rb +38 -38
  36. data/lib/blather/stanza/pubsub.rb +33 -0
  37. data/lib/blather/stanza/pubsub/affiliations.rb +52 -0
  38. data/lib/blather/stanza/pubsub/errors.rb +9 -0
  39. data/lib/blather/stanza/pubsub/event.rb +21 -0
  40. data/lib/blather/stanza/pubsub/items.rb +59 -0
  41. data/lib/blather/stanza/pubsub/owner.rb +9 -0
  42. data/lib/blather/stanza/pubsub/subscriptions.rb +57 -0
  43. data/lib/blather/stream.rb +125 -57
  44. data/lib/blather/stream/client.rb +26 -0
  45. data/lib/blather/stream/component.rb +34 -0
  46. data/lib/blather/stream/parser.rb +17 -27
  47. data/lib/blather/stream/resource.rb +21 -24
  48. data/lib/blather/stream/sasl.rb +60 -37
  49. data/lib/blather/stream/session.rb +12 -19
  50. data/lib/blather/stream/stream_handler.rb +39 -0
  51. data/lib/blather/stream/tls.rb +22 -18
  52. data/lib/blather/xmpp_node.rb +91 -17
  53. data/spec/blather/core_ext/libxml_spec.rb +58 -0
  54. data/spec/blather/errors/sasl_error_spec.rb +56 -0
  55. data/spec/blather/errors/stanza_error_spec.rb +148 -0
  56. data/spec/blather/errors/stream_error_spec.rb +114 -0
  57. data/spec/blather/errors_spec.rb +40 -0
  58. data/spec/blather/jid_spec.rb +0 -7
  59. data/spec/blather/roster_item_spec.rb +5 -0
  60. data/spec/blather/roster_spec.rb +6 -6
  61. data/spec/blather/stanza/discos/disco_info_spec.rb +207 -0
  62. data/spec/blather/stanza/discos/disco_items_spec.rb +136 -0
  63. data/spec/blather/stanza/iq/query_spec.rb +9 -2
  64. data/spec/blather/stanza/iq/roster_spec.rb +117 -1
  65. data/spec/blather/stanza/iq_spec.rb +29 -0
  66. data/spec/blather/stanza/presence/subscription_spec.rb +12 -1
  67. data/spec/blather/stanza/presence_spec.rb +29 -0
  68. data/spec/blather/stanza/pubsub/affiliations_spec.rb +46 -0
  69. data/spec/blather/stanza/pubsub/items_spec.rb +59 -0
  70. data/spec/blather/stanza/pubsub/subscriptions_spec.rb +63 -0
  71. data/spec/blather/stanza/pubsub_spec.rb +26 -0
  72. data/spec/blather/stanza_spec.rb +13 -1
  73. data/spec/blather/stream/client_spec.rb +787 -0
  74. data/spec/blather/stream/component_spec.rb +86 -0
  75. data/spec/blather/xmpp_node_spec.rb +75 -22
  76. data/spec/fixtures/pubsub.rb +157 -0
  77. data/spec/spec_helper.rb +6 -14
  78. metadata +86 -74
  79. data/CHANGELOG +0 -5
  80. data/Manifest +0 -47
  81. data/blather.gemspec +0 -41
  82. data/lib/blather/stanza/error.rb +0 -31
  83. data/spec/blather/stream_spec.rb +0 -462
  84. data/spec/build_safe.rb +0 -20
@@ -0,0 +1,59 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ class DiscoItems < Disco
5
+ register :disco_items, nil, 'http://jabber.org/protocol/disco#items'
6
+
7
+ def initialize(type = nil, node = nil, items = [])
8
+ super type
9
+ self.node = node
10
+ [items].flatten.each do |item|
11
+ query << (item.is_a?(Item) ? item : Item.new(item[:jid], item[:node], item[:name]))
12
+ end
13
+ end
14
+
15
+ def items
16
+ items = query.find('item')
17
+ items = query.find('query_ns:item', :query_ns => self.class.ns) if items.empty?
18
+ items.map { |i| Item.new i }
19
+ end
20
+
21
+ def node=(node)
22
+ query.attributes[:node] = node
23
+ end
24
+
25
+ def node
26
+ query.attributes[:node]
27
+ end
28
+
29
+ class Item < XMPPNode
30
+ def initialize(jid, node = nil, name = nil)
31
+ super :item
32
+
33
+ if jid.is_a?(XML::Node)
34
+ self.inherit jid
35
+ else
36
+ self.jid = jid
37
+ self.node = node
38
+ self.name = name
39
+ end
40
+ end
41
+
42
+ def jid
43
+ (j = attributes[:jid]) ? JID.new(j) : nil
44
+ end
45
+ attribute_writer :jid
46
+
47
+ attribute_accessor :node, :name, :to_sym => false
48
+ end
49
+
50
+ def eql?(other)
51
+ other.kind_of?(self.class) &&
52
+ other.jid == self.jid &&
53
+ other.node == self.node &&
54
+ other.name == self.name
55
+ end
56
+ end
57
+
58
+ end #Stanza
59
+ end #Blather
@@ -4,22 +4,34 @@ class Stanza
4
4
  ##
5
5
  # Base Iq stanza
6
6
  class Iq < Stanza
7
+ VALID_TYPES = [:get, :set, :result, :error]
8
+
7
9
  register :iq
8
10
 
9
11
  def self.import(node)
10
- raise "Import missmatch #{[node.element_name, self.name].inspect}" if node.element_name != self.name.to_s
12
+ raise(ArgumentError, "Import missmatch #{[node.element_name, self.name].inspect}") if node.element_name != self.name.to_s
11
13
  klass = nil
12
- node.each { |e| break if klass = class_from_registration(e.element_name, e.xmlns) }
13
- (klass || self).new(node['type']).inherit(node)
14
+ node.children.each { |e| break if klass = class_from_registration(e.element_name, e.namespace) }
15
+ (klass || self).new(node.attributes[:type]).inherit(node)
14
16
  end
15
17
 
16
18
  def initialize(type = nil, to = nil, id = nil)
17
19
  super :iq
18
- self.xmlns = nil
19
20
  self.type = type || :get
20
21
  self.to = to
21
22
  self.id = id if id
22
23
  end
24
+
25
+ VALID_TYPES.each do |valid_type|
26
+ define_method("#{valid_type}?") { self.type == valid_type }
27
+ end
28
+
29
+ ##
30
+ # Ensures type is :get, :set, :result or :error
31
+ def type=(type)
32
+ raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
33
+ super
34
+ end
23
35
  end
24
36
 
25
37
  end #Stanza
@@ -9,14 +9,13 @@ class Iq
9
9
  # Ensure the namespace is set to the query node
10
10
  def initialize(type = nil)
11
11
  super
12
- query.xmlns = self.class.xmlns
12
+ query.namespace = self.class.ns
13
13
  end
14
14
 
15
15
  ##
16
16
  # Kill the query node before running inherit
17
17
  def inherit(node)
18
18
  query.remove!
19
- @query = nil
20
19
  super
21
20
  end
22
21
 
@@ -24,7 +23,9 @@ class Iq
24
23
  # Query node accessor
25
24
  # This will ensure there actually is a query node
26
25
  def query
27
- (self << (q = XMPPNode.new('query'))) unless q = find_first('query')
26
+ q = find_first('query')
27
+ q = find_first('//query_ns:query', :query_ns => self.class.ns) if !q && self.class.ns
28
+ (self << (q = XMPPNode.new('query'))) unless q
28
29
  q
29
30
  end
30
31
 
@@ -39,8 +40,9 @@ class Iq
39
40
  ##
40
41
  # A query reply should have type set to "result"
41
42
  def reply!
42
- self.type = :result
43
43
  super
44
+ self.type = :result
45
+ self
44
46
  end
45
47
  end #Query
46
48
 
@@ -5,27 +5,42 @@ class Iq
5
5
  class Roster < Query
6
6
  register :roster, nil, 'jabber:iq:roster'
7
7
 
8
+ ##
9
+ # Any new items are added to the query
8
10
  def initialize(type = nil, item = nil)
9
- super(type)
11
+ super type
10
12
  query << item if item
11
13
  end
12
14
 
15
+ ##
16
+ # Inherit the XMPPNode to create a proper Roster object.
17
+ # Creates RosterItem objects out of each roster item as well.
13
18
  def inherit(node)
19
+ # remove the current set of nodes
14
20
  items.each { |i| i.remove! }
15
- @items = nil
16
21
  super
22
+ # transmogrify nodes into RosterItems
17
23
  items.each { |i| query << RosterItem.new(i); i.remove! }
18
- @items = nil
19
24
  self
20
25
  end
21
26
 
27
+ ##
28
+ # Roster items
22
29
  def items
23
- query.find('item')#.map { |g| RosterItem.new g }
30
+ items = query.find('//item', self.class.ns)
31
+ items = query.find('//query_ns:item', :query_ns => self.class.ns) if items.empty?
32
+ items.map { |i| RosterItem.new(i) }
24
33
  end
25
34
 
26
35
  class RosterItem < XMPPNode
36
+ ##
37
+ # [jid] may be either a JID or XMPPNode.
38
+ # [name] name alias of the given JID
39
+ # [subscription] subscription type
40
+ # [ask] ask subscription sub-state
27
41
  def initialize(jid = nil, name = nil, subscription = nil, ask = nil)
28
- super('item')
42
+ super :item
43
+
29
44
  if jid.is_a?(XML::Node)
30
45
  self.inherit jid
31
46
  else
@@ -36,49 +51,34 @@ class Iq
36
51
  end
37
52
  end
38
53
 
54
+ ##
55
+ # Roster item's JID
39
56
  def jid
40
- (j = attributes['jid']) ? JID.new(j) : nil
41
- end
42
-
43
- def jid=(jid)
44
- attributes['jid'] = jid
45
- end
46
-
47
- def name
48
- attributes['name']
49
- end
50
-
51
- def name=(name)
52
- attributes['name'] = name
57
+ (j = attributes[:jid]) ? JID.new(j) : nil
53
58
  end
59
+ attribute_writer :jid
54
60
 
55
- def subscription
56
- attributes['subscription'].to_sym if attributes['subscription']
57
- end
58
-
59
- def subscription=(subscription)
60
- attributes['subscription'] = subscription
61
- end
61
+ attribute_accessor :name, :to_sym => false
62
62
 
63
- def ask
64
- attributes['ask'].to_sym if attributes['ask']
65
- end
66
-
67
- def ask=(ask)
68
- attributes['ask'] = ask
69
- end
63
+ attribute_accessor :subscription, :ask
70
64
 
65
+ ##
66
+ # The groups roster item belongs to
71
67
  def groups
72
- @groups ||= find('group').map { |g| g.content }
68
+ find(:group).map { |g| g.content }
73
69
  end
74
70
 
75
- def groups=(grps)
76
- find('group').each { |g| g.remove! }
77
- @groups = nil
78
-
79
- grps.uniq.each { |g| add_node XMPPNode.new('group', g.to_s) } if grps
71
+ ##
72
+ # Set the roster item's groups
73
+ # must be an array
74
+ def groups=(new_groups)
75
+ find(:group).each { |g| g.remove! }
76
+ new_groups.uniq.each { |g| self << XMPPNode.new(:group, g) } if new_groups
80
77
  end
81
78
 
79
+ ##
80
+ # Convert the roster item to a proper stanza all wrapped up
81
+ # This facilitates new subscriptions
82
82
  def to_stanza
83
83
  Roster.new(:set, self)
84
84
  end
@@ -0,0 +1,33 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ class PubSub < Iq
5
+ register :pubsub, :pubsub, 'http://jabber.org/protocol/pubsub'
6
+
7
+ ##
8
+ # Ensure the namespace is set to the query node
9
+ def initialize(type = nil, host = nil)
10
+ super type
11
+ self.to = host
12
+ pubsub.namespace = self.class.ns unless pubsub.namespace
13
+ end
14
+
15
+ ##
16
+ # Kill the pubsub node before running inherit
17
+ def inherit(node)
18
+ pubsub.remove!
19
+ super
20
+ end
21
+
22
+ def pubsub
23
+ unless p = find_first('//pubsub', Stanza::PubSub::Affiliations.ns)
24
+ p = XMPPNode.new('pubsub')
25
+ p.namespace = self.class.ns
26
+ self << p
27
+ end
28
+ p
29
+ end
30
+ end
31
+
32
+ end #Stanza
33
+ end #Blather
@@ -0,0 +1,52 @@
1
+ module Blather
2
+ class Stanza
3
+ class PubSub
4
+
5
+ class Affiliations < PubSub
6
+ register :pubsub_affiliations, :pubsub_affiliations, self.ns
7
+
8
+ include Enumerable
9
+
10
+ def initialize(type = nil, host = nil)
11
+ super
12
+ affiliations
13
+ end
14
+
15
+ ##
16
+ # Kill the affiliations node before running inherit
17
+ def inherit(node)
18
+ affiliations.remove!
19
+ super
20
+ end
21
+
22
+ def affiliations
23
+ aff = pubsub.find_first('//pubsub_ns:affiliations', :pubsub_ns => self.class.ns)
24
+ (self.pubsub << (aff = XMPPNode.new('affiliations'))) unless aff
25
+ aff
26
+ end
27
+
28
+ def [](affiliation)
29
+ list[affiliation]
30
+ end
31
+
32
+ def each(&block)
33
+ list.each &block
34
+ end
35
+
36
+ def size
37
+ list.size
38
+ end
39
+
40
+ def list
41
+ items = affiliations.find('//pubsub_ns:affiliation', :pubsub_ns => self.class.ns)
42
+ items.inject({}) do |hash, item|
43
+ hash[item.attributes[:affiliation].to_sym] ||= []
44
+ hash[item.attributes[:affiliation].to_sym] << item.attributes[:node]
45
+ hash
46
+ end
47
+ end
48
+ end #Affiliations
49
+
50
+ end #PubSub
51
+ end #Stanza
52
+ end #Blather
@@ -0,0 +1,9 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ class PubSubErrors < PubSub
5
+ attribute_accessor :node, :to_sym => false
6
+ end
7
+
8
+ end #Stanza
9
+ end #Blather
@@ -0,0 +1,21 @@
1
+ module Blather
2
+ class Stanza
3
+ class PubSub
4
+
5
+ class Event < Message
6
+ register :pubsub_event, nil, 'http://jabber.org/protocol/pubsub#event'
7
+
8
+ def items
9
+ end
10
+
11
+ class Item
12
+ attribute_accessor :id, :node, :to_sym => false
13
+
14
+ alias_method :payload, :content
15
+ alias_method :payload=, :content=
16
+ end
17
+ end
18
+
19
+ end #PubSub
20
+ end #Stanza
21
+ end #Blather
@@ -0,0 +1,59 @@
1
+ module Blather
2
+ class Stanza
3
+ class PubSub
4
+
5
+ class Items < PubSub
6
+ register :pubsub_items, :pubsub_items, self.ns
7
+
8
+ include Enumerable
9
+
10
+ def self.request(host, path, list = [], max = nil)
11
+ node = self.new :get, host
12
+
13
+ node.items.attributes[:node] = path
14
+ node.items.attributes[:max_items] = max
15
+
16
+ (list || []).each do |id|
17
+ item = XMPPNode.new 'item'
18
+ item.attributes[:id] = id
19
+ node.items << item
20
+ end
21
+
22
+ node
23
+ end
24
+
25
+ def initialize(type = nil, host = nil)
26
+ super
27
+ items
28
+ end
29
+
30
+ ##
31
+ # Kill the items node before running inherit
32
+ def inherit(node)
33
+ items.remove!
34
+ super
35
+ end
36
+
37
+ def [](id)
38
+ items[id]
39
+ end
40
+
41
+ def each(&block)
42
+ items.each &block
43
+ end
44
+
45
+ def size
46
+ items.size
47
+ end
48
+
49
+ def items
50
+ items = pubsub.find_first('//items', self.class.ns)
51
+ items = pubsub.find_first('//pubsub_ns:items', :pubsub_ns => self.class.ns) unless items
52
+ (self.pubsub << (items = XMPPNode.new('items'))) unless items
53
+ items
54
+ end
55
+ end
56
+
57
+ end #PubSub
58
+ end #Stanza
59
+ end #Blather
@@ -0,0 +1,9 @@
1
+ module Blather
2
+ class Stanza
3
+
4
+ class PubSubOwner < PubSub
5
+ attribute_accessor :node, :to_sym => false
6
+ end
7
+
8
+ end #Stanza
9
+ end #Blather
@@ -0,0 +1,57 @@
1
+ module Blather
2
+ class Stanza
3
+ class PubSub
4
+
5
+ class Subscriptions < PubSub
6
+ register :pubsub_subscriptions, :pubsub_subscriptions, self.ns
7
+
8
+ include Enumerable
9
+
10
+ ##
11
+ # Ensure the namespace is set to the query node
12
+ def initialize(type = nil, host = nil)
13
+ super type
14
+ self.to = host
15
+ subscriptions
16
+ end
17
+
18
+ ##
19
+ # Kill the pubsub node before running inherit
20
+ def inherit(node)
21
+ subscriptions.remove!
22
+ super
23
+ end
24
+
25
+ def subscriptions
26
+ aff = pubsub.find_first('//pubsub_ns:subscriptions', :pubsub_ns => self.class.ns)
27
+ (self.pubsub << (aff = XMPPNode.new('subscriptions'))) unless aff
28
+ aff
29
+ end
30
+
31
+ def [](subscription)
32
+ list[subscription]
33
+ end
34
+
35
+ def each(&block)
36
+ list.each &block
37
+ end
38
+
39
+ def size
40
+ list.size
41
+ end
42
+
43
+ def list
44
+ @subscription_list ||= begin
45
+ items = subscriptions.find('//pubsub_ns:subscription', :pubsub_ns => self.class.ns)
46
+ items.inject({}) do |hash, item|
47
+ hash[item.attributes[:subscription].to_sym] ||= []
48
+ hash[item.attributes[:subscription].to_sym] << item.attributes[:node]
49
+ hash
50
+ end
51
+ end
52
+ end
53
+ end #Subscriptions
54
+
55
+ end #PubSub
56
+ end #Stanza
57
+ end #Blather