julien51-babylon 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  == DESCRIPTION:
4
4
 
5
- Babylon is a framework to EventMachine based XMPP External Components in Ruby.
5
+ Babylon is a framework to build XMPP Applications in Ruby. The framework uses EventMachine to handle network connections.
6
+
7
+ This framework can use both an XMPP Component (XEP-0114) and an XMPP Client. However, we strongly discourage any production application using a regular client.
6
8
 
7
9
  == FEATURES/PROBLEMS:
8
10
 
@@ -11,11 +13,17 @@ Babylon is a framework to EventMachine based XMPP External Components in Ruby.
11
13
 
12
14
  == ROADMAP :
13
15
 
14
- - Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic" datastructure, so that app developers don't have to learn/know/understand Nokogiri
15
- - Adding the ability to send several messages all at once (like render :collection in Rails)
16
+ - Instead of passing the stanzas as Nokogiri:XML:Node elements we should find a way to pass an "agnostic" ruby data-structure, so that app developers don't have to learn/know/understand Nokogiri
17
+ - HELPERS :
18
+ * strip jids (...)
19
+ - Improve the Client Connection to support other authentication than PLAIN SASL
16
20
 
17
21
  == SYNOPSIS:
18
22
 
23
+ You can build applications directly with Babylon, or you can use the Babylon::ClientConnection and Babylon::ComponentConnection to create simple apps, but you will then have to handle stanza routing and creation yourself. You can also use these classes in external gems.
24
+
25
+ To create an Application with Babylon:
26
+
19
27
  1. Install the gem
20
28
  2. The app contains a generator that will "build" a scaffold for your application.
21
29
 
@@ -82,8 +90,9 @@ This code hasn't been tested at all! (yes, i know it's bad, but I couldn't have
82
90
 
83
91
  == REQUIREMENTS:
84
92
 
85
- Our goal is to limit the number of dependencies. Nokogiri seems to be only XML Library on Ruby that has a Push SAX Parser, that is why we are using it. It also seems pretty 'fast'.
86
- Gems : Eventmachine, nokogiri
93
+ Gems : Eventmachine, nokogiri, YAML
94
+
95
+ Our goal is to limit the number of dependencies. Nokogiri seems to be only XML Library on Ruby that has a Push SAX Parser, that is why we are using it. It also seems pretty 'fast'. However, we think it is bad to "force" the framework users to receive Nokogiri objects (too much coupling), so we're looking for a Ruby datastcuture that would have an easy mapping with XML. Any thoughts?
87
96
 
88
97
  == INSTALL:
89
98
 
data/lib/babylon.rb CHANGED
@@ -4,12 +4,13 @@ $:.unshift(File.dirname(__FILE__)) unless
4
4
  require 'eventmachine'
5
5
  require 'nokogiri'
6
6
 
7
- require 'babylon/xmpp_connection'
8
- require 'babylon/component_connection'
9
- require 'babylon/router'
10
- require 'babylon/runner'
11
- require 'babylon/base/controller'
12
- require 'babylon/base/view'
7
+ require 'babylon/xmpp_connection.rb'
8
+ require 'babylon/component_connection.rb'
9
+ require 'babylon/client_connection.rb'
10
+ require 'babylon/router.rb'
11
+ require 'babylon/runner.rb'
12
+ require 'babylon/base/controller.rb'
13
+ require 'babylon/base/view.rb'
13
14
 
14
15
  # Babylon is a XMPP Component Framework based on EventMachine. It uses the Nokogiri GEM, which is a Ruby wrapper for Libxml2.
15
16
  # It implements the MVC paradigm.
@@ -18,7 +19,7 @@ require 'babylon/base/view'
18
19
  # This will generate some folders and files for your application. Please see README for further instructions
19
20
 
20
21
  module Babylon
21
- # 0.0.4 : Not suited for production, use at your own risks
22
- VERSION = '0.0.4'
22
+ # 0.0.5 : Not suited for production, use at your own risks
23
+ VERSION = '0.0.5'
23
24
  end
24
25
 
@@ -0,0 +1,147 @@
1
+ module Babylon
2
+
3
+ ##
4
+ # ClientConnection is in charge of the XMPP connection for a Regular XMPP Client.
5
+ # So far, SASL Plain authenticationonly is supported
6
+ # Upon stanza reception, and depending on the status (connected... etc), this component will handle or forward the stanzas.
7
+ class ClientConnection < XmppConnection
8
+ require 'digest/sha1'
9
+ require 'base64'
10
+
11
+ attr_reader :binding_iq_id, :session_iq_id
12
+
13
+ ##
14
+ # Returns true only if we're in connected state
15
+ def connected?
16
+ @state == :connected
17
+ end
18
+
19
+ ##
20
+ # Creates a new ClientConnection and waits for data in the stream
21
+ def initialize(params)
22
+ super(params)
23
+ @state = :wait_for_stream
24
+ end
25
+
26
+ ##
27
+ # Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
28
+ # to establish the XMPP connection itself.
29
+ # We use a "tweak" here to send only the starting tag of stream:stream
30
+ def connection_completed
31
+ super
32
+ builder = Nokogiri::XML::Builder.new do
33
+ self.send('stream:stream', {'xmlns' => @context.stream_namespace(), 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['host'], 'version' => '1.0'}) do
34
+ paste_content_here # The stream:stream element should be cut here ;)
35
+ end
36
+ end
37
+ @outstream = builder.doc
38
+ start_stream, stop_stream = builder.to_xml.split('<paste_content_here/>')
39
+ send_data(start_stream)
40
+ end
41
+
42
+ ##
43
+ # Called upon stanza reception
44
+ # Marked as connected when the client has been SASLed, authenticated, biund to a resource and when the session has been created
45
+ def receive_stanza(stanza)
46
+ case @state
47
+ when :connected
48
+ super # Can be dispatched
49
+
50
+ when :wait_for_stream
51
+ if stanza.name == "stream:stream" && stanza.attributes['id']
52
+ @state = :wait_for_auth_mechanisms unless @success
53
+ @state = :wait_for_bind if @success
54
+ end
55
+
56
+ when :wait_for_auth_mechanisms
57
+ if stanza.name == "stream:features"
58
+ if stanza.at("starttls") # we shall start tls
59
+ starttls = Nokogiri::XML::Node.new("starttls", @outstream)
60
+ starttls["xmlns"] = stanza.at("starttls").namespaces.first.last
61
+ send(starttls)
62
+ @state = :wait_for_proceed
63
+ elsif stanza.at("mechanisms") # tls is ok
64
+ if stanza.at("mechanisms/[contains(mechanism,'PLAIN')]")
65
+ # auth_text = "#{jid.strip}\x00#{jid.node}\x00#{password}"
66
+ auth = Nokogiri::XML::Node.new("auth", @outstream)
67
+ auth['mechanism'] = "PLAIN"
68
+ auth['xmlns'] = stanza.at("mechanisms").namespaces.first.last
69
+ auth.content = Base64::encode64([@config['jid'],@config['jid'].split("@").first,@config['password']].join("\000")).gsub(/\s/, '')
70
+ send(auth)
71
+ @state = :wait_for_success
72
+ end
73
+ end
74
+ end
75
+
76
+ when :wait_for_success
77
+ if stanza.name == "success" # Yay! Success
78
+ @success = true
79
+ @state = :wait_for_stream
80
+ send_data @outstream.root.to_xml.split('<paste_content_here/>').first
81
+ else
82
+ # Hum Failure...
83
+ end
84
+
85
+ when :wait_for_bind
86
+ if stanza.name == "stream:features"
87
+ if stanza.at("bind")
88
+ # Let's build the binding_iq
89
+ @binding_iq_id = Integer(rand(10000))
90
+ builder = Nokogiri::XML::Builder.new do
91
+ iq(:type => "set", :id => @context.binding_iq_id) do
92
+ bind(:xmlns => "urn:ietf:params:xml:ns:xmpp-bind") do
93
+ if @context.config['resource']
94
+ resource(@context.config['resource'] )
95
+ else
96
+ resource("babylon_client_#{@context.binding_iq_id}")
97
+ end
98
+ end
99
+ end
100
+ end
101
+ iq = @outstream.add_child(builder.doc.root)
102
+ send(iq)
103
+ @state = :wait_for_confirmed_binding
104
+ end
105
+ end
106
+
107
+ when :wait_for_confirmed_binding
108
+ if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @binding_iq_id
109
+ if stanza.at("jid")
110
+ @jid = stanza.at("jid").text
111
+ end
112
+ end
113
+ # And now, we must initiate the session
114
+ @session_iq_id = Integer(rand(10000))
115
+ builder = Nokogiri::XML::Builder.new do
116
+ iq(:type => "set", :id => @context.session_iq_id) do
117
+ session(:xmlns => "urn:ietf:params:xml:ns:xmpp-session")
118
+ end
119
+ end
120
+ iq = @outstream.add_child(builder.doc.root)
121
+ send(iq)
122
+ @state = :wait_for_confirmed_session
123
+
124
+ when :wait_for_confirmed_session
125
+ if stanza.name == "iq" && stanza["type"] == "result" && Integer(stanza["id"]) == @session_iq_id && stanza.at("session")
126
+ # And now, send a presence!
127
+ presence = Nokogiri::XML::Node.new("presence", @outstream)
128
+ send(presence)
129
+ @state = :connected
130
+ end
131
+
132
+ when :wait_for_proceed
133
+ start_tls() # starting TLS
134
+ @state = :wait_for_stream
135
+ send_data @outstream.root.to_xml.split('<paste_content_here/>').first
136
+ end
137
+
138
+ end
139
+
140
+ ##
141
+ # Namespace of the client
142
+ def stream_namespace
143
+ "jabber:client"
144
+ end
145
+
146
+ end
147
+ end
@@ -5,19 +5,42 @@ module Babylon
5
5
  class ComponentConnection < XmppConnection
6
6
  require 'digest/sha1'
7
7
 
8
+ ##
9
+ # Returns true only if we're in connected state
10
+ def connected?
11
+ @state == :connected
12
+ end
13
+
8
14
  ##
9
15
  # Creates a new ComponentConnection and waits for data in the stream
10
16
  def initialize(*a)
11
17
  super
12
18
  @state = :wait_for_stream
13
19
  end
20
+
21
+ ##
22
+ # Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream
23
+ # to establish the XMPP connection itself.
24
+ # We use a "tweak" here to send only the starting tag of stream:stream
25
+ def connection_completed
26
+ super
27
+ builder = Nokogiri::XML::Builder.new do
28
+ self.send('stream:stream', {'xmlns' => "jabber:component:accept", 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['jid']}) do
29
+ paste_content_here # The stream:stream element should be cut here ;)
30
+ end
31
+ end
32
+ @start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
33
+ send_data(@start_stream)
34
+ end
14
35
 
15
36
  ##
16
37
  # XMPP Component handshake as defined in XEP-0114:
17
38
  # http://xmpp.org/extensions/xep-0114.html
18
39
  def receive_stanza(stanza)
19
40
  case @state
20
-
41
+ when :connected # Most frequent case
42
+ super # Can be dispatched
43
+
21
44
  when :wait_for_stream
22
45
  if stanza.name == "stream:stream" && stanza.attributes['id']
23
46
  # This means the XMPP session started!
@@ -40,8 +63,6 @@ module Babylon
40
63
  raise
41
64
  end
42
65
 
43
- when :connected
44
- super # Can be dispatched
45
66
  end
46
67
  end
47
68
 
@@ -51,10 +72,5 @@ module Babylon
51
72
  'jabber:component:accept'
52
73
  end
53
74
 
54
- ##
55
- # Jid of the component
56
- def stream_to
57
- @config['jid']
58
- end
59
75
  end
60
76
  end
@@ -24,8 +24,15 @@ module Babylon
24
24
  # Starting the EventMachine
25
25
  EventMachine.epoll
26
26
  EventMachine::run do
27
- Babylon::ComponentConnection.connect(config)
28
-
27
+ if config["application_type"] == "client"
28
+ Babylon::ClientConnection.connect(config) do |stanza|
29
+ CentralRouter.route stanza # Upon reception of new stanza, we Route them through the controller
30
+ end
31
+ else
32
+ Babylon::ComponentConnection.connect(config) do |stanza|
33
+ CentralRouter.route stanza # Upon reception of new stanza, we Route them through the controller
34
+ end
35
+ end
29
36
  # And finally, let's allow the application to do all it wants to do after we started the EventMachine!
30
37
  callback.call if callback
31
38
  end
@@ -13,8 +13,8 @@ module Babylon
13
13
  ##
14
14
  # Connects the XmppConnection to the right host with the right port.
15
15
  # It passes itself (as handler) and the configuration
16
- def self.connect(config)
17
- EventMachine::connect config['host'], config['port'], self, config
16
+ def self.connect(config, &block)
17
+ EventMachine::connect config['host'], config['port'], self, config.merge({:callback => block})
18
18
  end
19
19
 
20
20
  ##
@@ -34,40 +34,32 @@ module Babylon
34
34
  ##
35
35
  # Called when a full stanza has been received and returns it to the central router to be sent to the corresponding controller. Eventually it displays this data for debugging purposes
36
36
  def receive_stanza(stanza)
37
- puts "<< #{stanza}\n" if debug? # Low level Logging
38
37
  # If not handled by subclass (for authentication)
39
- CentralRouter.route stanza
40
- end
41
-
42
- ##
43
- # Connection_completed is called when the connection (socket) has been established and is in charge of "building" the XML stream to establish the XMPP connection itself
44
- # We use a "tweak" here to send only the starting tag of stream:stream
45
- def connection_completed
46
- super
47
- builder = Nokogiri::XML::Builder.new {
48
- self.send('stream:stream', 'xmlns' => "jabber:component:accept", 'xmlns:stream' => 'http://etherx.jabber.org/streams', 'to' => @context.config['jid']) {
49
- paste_content_here # The stream:stream element should be cut here ;)
50
- }
51
- }
52
- @start_stream, @stop_stream = builder.to_xml.split('<paste_content_here/>')
53
- send_data(@start_stream)
38
+ @config[:callback].call(stanza) if @config[:callback]
54
39
  end
55
40
 
56
41
  ##
57
42
  # Sends the Nokogiri::XML data (after converting to string) on the stream. It also appends the right "from" to be the component's JId if none has been mentionned. Eventually it displays this data for debugging purposes
58
43
  def send(xml)
59
44
  if !xml.attributes["from"]
60
- xml["from"] = config['jid']
45
+ xml["from"] = jid
61
46
  end
62
- puts ">> #{xml}\n" if debug? # Very low level Logging
47
+ puts "SENDING #{xml}\n" if debug? # Very low level Logging
63
48
  send_data "#{xml}"
64
49
  end
50
+
51
+ ##
52
+ # Memoizer for jid. The jid can actually be changed in subclasses (client will probbaly want to change it to include the resource)
53
+ def jid
54
+ @jid ||= config['jid']
55
+ end
65
56
 
66
57
  private
67
58
 
68
59
  ##
69
60
  # receive_data is called when data is received. It is then passed to the parser.
70
61
  def receive_data(data)
62
+ puts "RECEIVED #{data}\n" if debug? # Low level Logging
71
63
  @parser.parse data
72
64
  end
73
65
 
@@ -121,8 +113,10 @@ module Babylon
121
113
  # If we don't have any elem yet, we are at the root
122
114
  @elem = @elem ? @elem.add_child(e) : (@root = e)
123
115
 
124
- if @elem.parent.nil?
116
+ if @elem.name == "stream:stream"
125
117
  # Should be called only for stream:stream
118
+ @doc = Nokogiri::XML::Document.new
119
+ @root = @elem
126
120
  @doc.root = @elem
127
121
  @callback.call(@elem)
128
122
  end
@@ -5,6 +5,7 @@
5
5
  # host: host on which the XMPP server is running
6
6
  # port: port to which your component should connect
7
7
  # debug: outputs the xmpp stanzas
8
+ # application_type: client | component (by default it is component and we strongly discourage any client application in production)
8
9
 
9
10
  development:
10
11
  jid: component.server.com
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: julien51-babylon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Genestoux
@@ -47,6 +47,7 @@ files:
47
47
  - lib/babylon/base/controller.rb
48
48
  - lib/babylon/base/view.rb
49
49
  - lib/babylon/component_connection.rb
50
+ - lib/babylon/client_connection.rb
50
51
  - lib/babylon/router.rb
51
52
  - lib/babylon/runner.rb
52
53
  - lib/babylon/xmpp_connection.rb