ruby_bosh 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Pradeep Elankumaran
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,37 @@
1
+ ruby_bosh
2
+ =========
3
+
4
+ The RubyBOSH library handles creating and pre-authenticating BOSH streams inside your Ruby application before passing them off to your template engine.
5
+
6
+ This method allows you to hide authentication details for your users' XMPP accounts.
7
+
8
+ Tested on Rails 2.x with eJabberd 1.2+
9
+
10
+ References
11
+ ==========
12
+ BOSH: http://xmpp.org/extensions/xep-0124.html
13
+ XMPP via BOSH: http://xmpp.org/extensions/xep-0206.html
14
+
15
+ Example
16
+ =======
17
+ In your Ruby app controller (or equivalent):
18
+
19
+ @session_jid, @session_id, @session_random_id =
20
+ RubyBOSH.initialize_session("me@jabber.org", "my_password", "http://localhost:5280/http-bind")
21
+
22
+ In your template, you would then pass these directly to your javascript BOSH connector:
23
+
24
+ var bosh_jid = '<%= @session_jid %>';
25
+ var bosh_sid = '<%= @session_id %>';
26
+ var bosh_rid = '<%= @session_random_id %>';
27
+
28
+ // using Strophe:
29
+ connect.attach(bosh_jid, bosh_sid, bosh_rid, onConnectHandlerFunction);
30
+
31
+ Acknowledgements
32
+ ================
33
+ Jack Moffit
34
+ - thanks for the nice Django example :)
35
+ #=> http://metajack.im/2008/10/03/getting-attached-to-strophe/
36
+
37
+ Copyright (c) 2008 Pradeep Elankumaran. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "ruby_bosh"
7
+ s.summary = %Q{A BOSH session pre-initializer for Ruby web applications}
8
+ s.email = "pradeep@intridea.com"
9
+ s.homepage = "http://github.com/skyfallsin/ruby_bosh"
10
+ s.description = "An XMPP BOSH session pre-initializer for Ruby web applications"
11
+ s.authors = ["Pradeep Elankumaran"]
12
+
13
+ s.add_dependency("builder")
14
+ s.add_dependency("adamwiggins-rest-client")
15
+ s.add_dependency("hpricot")
16
+ s.add_dependency("SystemTimer")
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'rake/rdoctask'
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'rdoc'
25
+ rdoc.title = 'ruby_bosh'
26
+ rdoc.options << '--line-numbers' << '--inline-source'
27
+ rdoc.rdoc_files.include('README*')
28
+ rdoc.rdoc_files.include('lib/**/*.rb')
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib' << 'test'
34
+ t.pattern = 'test/**/*_test.rb'
35
+ t.verbose = false
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+ Rcov::RcovTask.new do |t|
41
+ t.libs << 'test'
42
+ t.test_files = FileList['test/**/*_test.rb']
43
+ t.verbose = true
44
+ end
45
+ rescue LoadError
46
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
47
+ end
48
+
49
+ begin
50
+ require 'cucumber/rake/task'
51
+ Cucumber::Rake::Task.new(:features)
52
+ rescue LoadError
53
+ puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
54
+ end
55
+
56
+ task :default => :test
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ write basic tests
2
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 4
3
+ :major: 0
4
+ :minor: 5
@@ -0,0 +1,5 @@
1
+ $:.push(File.join(File.dirname(__FILE__), %w[.. .. rspec]))
2
+
3
+ Autotest.add_discovery do
4
+ "rspec"
5
+ end
data/lib/ruby_bosh.rb ADDED
@@ -0,0 +1,162 @@
1
+ require 'rest_client'
2
+ require 'builder'
3
+ require 'rexml/document'
4
+ require 'base64'
5
+ require 'hpricot'
6
+ require 'timeout'
7
+ require 'system_timer'
8
+
9
+ class RubyBOSH
10
+ BOSH_XMLNS = 'http://jabber.org/protocol/httpbind'
11
+ TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls'
12
+ SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl'
13
+ BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind'
14
+ SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session'
15
+ CLIENT_XMLNS = 'jabber:client'
16
+
17
+ class Error < StandardError; end
18
+ class Timeout < RubyBOSH::Error; end
19
+ class AuthFailed < RubyBOSH::Error; end
20
+ class ConnFailed < RubyBOSH::Error; end
21
+
22
+ @@logging = true
23
+ def self.logging=(value)
24
+ @@logging = value
25
+ end
26
+
27
+ attr_accessor :jid, :rid, :sid, :success
28
+ def initialize(jid, pw, service_url, opts={})
29
+ @service_url = service_url
30
+ @jid, @pw = jid, pw
31
+ @host = jid.split("@").last
32
+ @success = false
33
+ @timeout = opts[:timeout] || 3 #seconds
34
+ @headers = {"Content-Type" => "text/xml; charset=utf-8",
35
+ "Accept" => "text/xml"}
36
+ @wait = opts[:wait] || 5
37
+ @hold = opts[:hold] || 3
38
+ @window = opts[:window] || 5
39
+ end
40
+
41
+ def success?
42
+ @success == true
43
+ end
44
+
45
+ def self.initialize_session(*args)
46
+ new(*args).connect
47
+ end
48
+
49
+ def connect
50
+ initialize_bosh_session
51
+ if send_auth_request
52
+ send_restart_request
53
+ request_resource_binding
54
+ @success = send_session_request
55
+ end
56
+
57
+ raise RubyBOSH::AuthFailed, "could not authenticate #{@jid}" unless success?
58
+ @rid += 1 #updates the rid for the next call from the browser
59
+
60
+ [@jid, @sid, @rid]
61
+ end
62
+
63
+ private
64
+ def initialize_bosh_session
65
+ response = deliver(construct_body(:wait => @wait, :to => @host,
66
+ :hold => @hold, :window => @window,
67
+ "xmpp:version" => '1.0'))
68
+ parse(response)
69
+ end
70
+
71
+ def construct_body(params={}, &block)
72
+ @rid ? @rid+=1 : @rid=rand(100000)
73
+
74
+ builder = Builder::XmlMarkup.new
75
+ parameters = {:rid => @rid, :xmlns => BOSH_XMLNS,
76
+ "xmpp:version" => "1.0",
77
+ "xmlns:xmpp" => "urn:xmpp:xbosh"}.merge(params)
78
+
79
+ if block_given?
80
+ builder.body(parameters) {|body| yield(body)}
81
+ else
82
+ builder.body(parameters)
83
+ end
84
+ end
85
+
86
+ def send_auth_request
87
+ request = construct_body(:sid => @sid) do |body|
88
+ auth_string = "#{@jid}\x00#{@jid.split("@").first.strip}\x00#{@pw}"
89
+ body.auth(Base64.encode64(auth_string).gsub(/\s/,''),
90
+ :xmlns => SASL_XMLNS, :mechanism => 'PLAIN')
91
+ end
92
+
93
+ response = deliver(request)
94
+ response.include?("success")
95
+ end
96
+
97
+ def send_restart_request
98
+ request = construct_body(:sid => @sid, "xmpp:restart" => true, "xmlns:xmpp" => 'urn:xmpp:xbosh')
99
+ deliver(request).include?("stream:features")
100
+ end
101
+
102
+ def request_resource_binding
103
+ request = construct_body(:sid => @sid) do |body|
104
+ body.iq(:id => "bind_#{rand(100000)}", :type => "set",
105
+ :xmlns => "jabber:client") do |iq|
106
+ iq.bind(:xmlns => BIND_XMLNS) do |bind|
107
+ bind.resource("bosh_#{rand(10000)}")
108
+ end
109
+ end
110
+ end
111
+
112
+ response = deliver(request)
113
+ response.include?("<jid>")
114
+ end
115
+
116
+ def send_session_request
117
+ request = construct_body(:sid => @sid) do |body|
118
+ body.iq(:xmlns => CLIENT_XMLNS, :type => "set",
119
+ :id => "sess_#{rand(100000)}") do |iq|
120
+ iq.session(:xmlns => SESSION_XMLNS)
121
+ end
122
+ end
123
+
124
+ response = deliver(request)
125
+ response.include?("body")
126
+ end
127
+
128
+ def parse(_response)
129
+ doc = Hpricot(_response)
130
+ doc.search("//body").each do |body|
131
+ @sid = body.attributes["sid"].to_s
132
+ end
133
+ _response
134
+ end
135
+
136
+ def deliver(xml)
137
+ SystemTimer.timeout(@timeout) do
138
+ send(xml)
139
+ recv(RestClient.post(@service_url, xml, @headers))
140
+ end
141
+ rescue ::Timeout::Error => e
142
+ raise RubyBOSH::Timeout, e.message
143
+ rescue Errno::ECONNREFUSED => e
144
+ raise RubyBOSH::ConnFailed, "could not connect to #{@host}\n#{e.message}"
145
+ rescue Exception => e
146
+ raise RubyBOSH::Error, e.message
147
+ end
148
+
149
+ def send(msg)
150
+ puts("Ruby-BOSH - SEND\n#{msg}") if @@logging; msg
151
+ end
152
+
153
+ def recv(msg)
154
+ puts("Ruby-BOSH - RECV\n#{msg}") if @logging; msg
155
+ end
156
+ end
157
+
158
+
159
+ if __FILE__ == $0
160
+ p RubyBOSH.initialize_session(ARGV[0], ARGV[1],
161
+ "http://localhost:5280/http-bind")
162
+ end
data/ruby_bosh.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ruby_bosh}
5
+ s.version = "0.5.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Pradeep Elankumaran"]
9
+ s.date = %q{2009-04-21}
10
+ s.description = %q{An XMPP BOSH session pre-initializer for Ruby web applications}
11
+ s.email = %q{pradeep@intridea.com}
12
+ s.files = ["VERSION.yml", "lib/ruby_bosh.rb", "spec/ruby_bosh_spec.rb", "spec/spec_helper.rb"]
13
+ s.has_rdoc = true
14
+ s.homepage = %q{http://github.com/skyfallsin/ruby_bosh}
15
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
16
+ s.require_paths = ["lib"]
17
+ s.rubygems_version = %q{1.3.1}
18
+ s.summary = %q{A BOSH session pre-initializer for Ruby web applications}
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 2
23
+
24
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
25
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
26
+ s.add_runtime_dependency(%q<adamwiggins-rest-client>, [">= 0"])
27
+ s.add_runtime_dependency(%q<hpricot>, [">= 0"])
28
+ s.add_runtime_dependency(%q<SystemTimer>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<builder>, [">= 0"])
31
+ s.add_dependency(%q<adamwiggins-rest-client>, [">= 0"])
32
+ s.add_dependency(%q<hpricot>, [">= 0"])
33
+ s.add_dependency(%q<SystemTimer>, [">= 0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<builder>, [">= 0"])
37
+ s.add_dependency(%q<adamwiggins-rest-client>, [">= 0"])
38
+ s.add_dependency(%q<hpricot>, [">= 0"])
39
+ s.add_dependency(%q<SystemTimer>, [">= 0"])
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe RubyBOSH do
4
+ before(:each) do
5
+ RubyBOSH.logging = false
6
+ @rbosh = RubyBOSH.new("skyfallsin@localhost", "skyfallsin",
7
+ "http://localhost:5280/http-bind")
8
+ #@rbosh.stub!(:success?).and_return(true)
9
+ #@rbosh.stub!(:initialize_bosh_session).and_return(true)
10
+ @rbosh.stub!(:send_auth_request).and_return(true)
11
+ @rbosh.stub!(:send_restart_request).and_return(true)
12
+ @rbosh.stub!(:request_resource_binding).and_return(true)
13
+ @rbosh.stub!(:send_session_request).and_return(true)
14
+ RestClient.stub!(:post).and_return("<body sid='123456'></body>")
15
+ end
16
+
17
+ it "should set the sid attribute after the session creation request" do
18
+ @rbosh.connect
19
+ @rbosh.sid.should == '123456'
20
+ end
21
+
22
+ it "should update the rid on every call to the BOSH server" do
23
+ @rbosh.rid = 100
24
+ @rbosh.connect
25
+ @rbosh.rid.should > 100
26
+ end
27
+
28
+ it "should return an array with [jid, sid, rid] on success" do
29
+ s = @rbosh.connect
30
+ s.should be_kind_of(Array)
31
+ s.size.should == 3
32
+ s.first.should == 'skyfallsin@localhost'
33
+ s.last.should be_kind_of(Fixnum)
34
+ s[1].should == '123456'
35
+ end
36
+
37
+ describe "Errors" do
38
+ it "should crash with AuthFailed when its not a success?" do
39
+ @rbosh.stub!(:send_session_request).and_return(false)
40
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::AuthFailed)
41
+ end
42
+
43
+ it "should raise a ConnFailed if a connection could not be made to the XMPP server" do
44
+ RestClient.stub!(:post).and_raise(Errno::ECONNREFUSED)
45
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::ConnFailed)
46
+ end
47
+
48
+ it "should raise a Timeout::Error if the BOSH call takes forever" do
49
+ SystemTimer.stub!(:timeout).and_raise(::Timeout::Error)
50
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Timeout)
51
+ end
52
+
53
+ it "should crash with a generic error on any other problem" do
54
+ [RestClient::ServerBrokeConnection, RestClient::RequestTimeout].each{|err|
55
+ RestClient.stub!(:post).and_raise(err)
56
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Error)
57
+ }
58
+ end
59
+
60
+ after(:each) do
61
+ lambda { @rbosh.connect }.should raise_error(RubyBOSH::Error)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', "lib", "ruby_bosh")
3
+ require 'spec'
4
+
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_bosh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.4
5
+ platform: ruby
6
+ authors:
7
+ - Pradeep Elankumaran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-06 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: builder
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: adamwiggins-rest-client
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: hpricot
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: SystemTimer
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: An XMPP BOSH session pre-initializer for Ruby web applications
56
+ email: pradeep@intridea.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README
64
+ files:
65
+ - .gitignore
66
+ - LICENSE
67
+ - README
68
+ - Rakefile
69
+ - TODO
70
+ - VERSION.yml
71
+ - autotest/discover.rb
72
+ - lib/ruby_bosh.rb
73
+ - ruby_bosh.gemspec
74
+ - spec/ruby_bosh_spec.rb
75
+ - spec/spec_helper.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/skyfallsin/ruby_bosh
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ version:
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.5
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: A BOSH session pre-initializer for Ruby web applications
104
+ test_files:
105
+ - spec/ruby_bosh_spec.rb
106
+ - spec/spec_helper.rb