push 0.0.1

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.
@@ -0,0 +1,205 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Cross-domain XHMLHttpRequest proxy</title>
5
+ <%= javascript_include_tag *%w[json2 easyXDM] %>
6
+ <script type="text/javascript">
7
+ /*
8
+ * This is a CORS (Cross-Origin Resource Sharing) and AJAX enabled endpoint for easyXDM.
9
+ * The ACL code is adapted from pmxdr (http://github.com/eligrey/pmxdr/) by Eli Grey (http://eligrey.com/)
10
+ *
11
+ */
12
+ // From http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
13
+ function isHostMethod(object, property){
14
+ var t = typeof object[property];
15
+ return t == 'function' ||
16
+ (!!(t == 'object' && object[property])) ||
17
+ t == 'unknown';
18
+ }
19
+
20
+ /**
21
+ * Creates a cross-browser XMLHttpRequest object
22
+ * @return {XMLHttpRequest} A XMLHttpRequest object.
23
+ */
24
+ var getXhr = (function(){
25
+ if (isHostMethod(window, "XMLHttpRequest")) {
26
+ return function(){
27
+ return new XMLHttpRequest();
28
+ };
29
+ }
30
+ else {
31
+ var item = (function(){
32
+ var list = ["Microsoft", "Msxml2", "Msxml3"], i = list.length;
33
+ while (i--) {
34
+ try {
35
+ item = list[i] + ".XMLHTTP";
36
+ var obj = new ActiveXObject(item);
37
+ return item;
38
+ }
39
+ catch (e) {
40
+ }
41
+ }
42
+ }());
43
+ return function(){
44
+ return new ActiveXObject(item);
45
+ };
46
+ }
47
+ }());
48
+
49
+ // this file is by default set up to use Access Control - this means that it will use the headers set by the server to decide whether or not to allow the call to return
50
+ // TODO turn this back on once the CORS headers are implemented
51
+ var useAccessControl = false;
52
+ // always trusted origins, can be exact strings or regular expressions
53
+ var alwaysTrustedOrigins = [(/\.?easyxdm\.net/), (/xdm1/)];
54
+
55
+ // instantiate a new easyXDM object which will handle the request
56
+ var remote = new easyXDM.Rpc({
57
+ local: "../name.html",
58
+ swf: "../easyxdm.swf"
59
+ }, {
60
+ local: {
61
+ // define the exposed method
62
+ request: function(config, success, error){
63
+ // apply default values if not set
64
+ easyXDM.apply(config, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/x-www-form-urlencoded",
68
+ "X-Requested-With": "XMLHttpRequest"
69
+ },
70
+ success: Function.prototype,
71
+ error: function(msg){
72
+ throw new Error(msg);
73
+ },
74
+ data: {},
75
+ timeout: 10 * 1000
76
+ }, true);
77
+
78
+ var isPOST = config.method == "POST";
79
+
80
+ // convert the data into a format we can send to the server
81
+ var pairs = [];
82
+ for (var key in config.data) {
83
+ if (config.data.hasOwnProperty(key)) {
84
+ pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(config.data[key]));
85
+ }
86
+ }
87
+ var data = pairs.join("&");
88
+
89
+ // create the XMLHttpRequest object
90
+ var req = getXhr();
91
+ req.open(config.method, config.url + (isPOST ? "" : "?" + data), true);
92
+
93
+ // apply the request headers
94
+ for (var prop in config.headers) {
95
+ if (config.headers.hasOwnProperty(prop) && config.headers[prop]) {
96
+ req.setRequestHeader(prop, config.headers[prop]);
97
+ }
98
+ }
99
+
100
+ // set a timeout
101
+ var timeout;
102
+ timeout = setTimeout(function(){
103
+ req.abort();
104
+
105
+ if(req && req.readyState != 4){
106
+ error({
107
+ message: "timeout after " + config.timeout + " second",
108
+ status: 0,
109
+ data: null,
110
+ toString: function(){
111
+ return this.message + " Status: " + this.status;
112
+ }
113
+ }, null);
114
+ }
115
+ }, config.timeout);
116
+
117
+ // check if this origin should always be trusted
118
+ var alwaysTrusted = false, i = alwaysTrustedOrigins.length;
119
+ while (i-- && !alwaysTrusted) {
120
+ if (alwaysTrustedOrigins[i] instanceof RegExp) {
121
+ alwaysTrusted = alwaysTrustedOrigins[i].test(remote.origin);
122
+ }
123
+ else if (typeof alwaysTrustedOrigins[i] == "string") {
124
+ alwaysTrusted = (remote.origin === alwaysTrustedOrigins[i]);
125
+ }
126
+ }
127
+
128
+
129
+ // define the onreadystate handler
130
+ req.onreadystatechange = function(){
131
+ if (req.readyState == 4) {
132
+ clearTimeout(timeout);
133
+
134
+ // parse the response headers
135
+ var rawHeaders = req.getAllResponseHeaders(), headers = {}, headers_lowercase = {}, reHeader = /([\w-_]+):\s+(.*)$/gm, m;
136
+ while ((m = reHeader.exec(rawHeaders))) {
137
+ headers_lowercase[m[1].toLowerCase()] = headers[m[1]] = m[2];
138
+ }
139
+
140
+ // In IE status 204 is translated to 1223
141
+ var normalizedStatus = req.status;
142
+
143
+ if(normalizedStatus === 1223) {
144
+ normalizedStatus = 204;
145
+ }
146
+
147
+ if (normalizedStatus < 200 || normalizedStatus >= 300) {
148
+ if (useAccessControl) {
149
+ error("INVALID_STATUS_CODE");
150
+ }
151
+ else {
152
+ error("INVALID_STATUS_CODE", {
153
+ status: normalizedStatus,
154
+ data: req.responseText
155
+ });
156
+ }
157
+ }
158
+ else {
159
+
160
+ var errorMessage;
161
+ if (useAccessControl) {
162
+ // normalize the valuse access controls
163
+ var aclAllowedOrigin = (headers_lowercase["access-control-allow-origin"] || "").replace(/\s/g, "");
164
+ var aclAllowedMethods = (headers_lowercase["access-control-allow-methods"] || "").replace(/\s/g, "");
165
+
166
+ // determine if origin is trusted
167
+ if (alwaysTrusted || aclAllowedOrigin == "*" || aclAllowedOrigin.indexOf(remote.origin) != -1) {
168
+ // determine if the request method was allowed
169
+ if (aclAllowedMethods && aclAllowedMethods != "*" && aclAllowedMethods.indexOf(config.method) == -1) {
170
+ errorMessage = "DISALLOWED_REQUEST_METHOD";
171
+ }
172
+ }
173
+ else {
174
+ errorMessage = "DISALLOWED_ORIGIN";
175
+ }
176
+
177
+ }
178
+
179
+ if (errorMessage) {
180
+ error(errorMessage);
181
+ }
182
+ else {
183
+ success({
184
+ data: req.responseText,
185
+ status: normalizedStatus,
186
+ headers: headers
187
+ });
188
+ }
189
+ }
190
+ // reset the handler
191
+ req.onreadystatechange = Function.prototype;
192
+ req = null;
193
+ }
194
+ };
195
+
196
+ // issue the request
197
+ req.send(isPOST ? data : "");
198
+ }
199
+ }
200
+ });
201
+ </script>
202
+ </head>
203
+ <body>
204
+ </body>
205
+ </html>
@@ -0,0 +1,46 @@
1
+ module Push::Transport::Controller
2
+ class WebSocket < Cramp::Websocket
3
+ include Push::Logger
4
+
5
+ on_start :bind_queue
6
+ on_finish :unbind_queue
7
+ on_data :message_received
8
+
9
+ def bind_queue
10
+ logger.info "Subscribed to '#{exchange}'"
11
+ queue.bind(channel.fanout(exchange)).subscribe(:ack => true) {|header, message|
12
+ header.ack
13
+ render message
14
+ logger.info "Sent message: #{message}"
15
+ }
16
+ end
17
+
18
+ def unbind_queue
19
+ queue.unsubscribe {
20
+ channel.close
21
+ logger.info "Unsubscribed from '#{exchange}'"
22
+ }
23
+ end
24
+
25
+ def message_received(data)
26
+ logger.info "Received #{data}" # Who cares? Do nothing.
27
+ end
28
+
29
+ private
30
+ def channel
31
+ @channel ||= AMQP::Channel.new
32
+ end
33
+
34
+ def queue
35
+ @queue ||= channel.queue("#{sid}@#{exchange}", :arguments => {'x-expires' => Push.config.amqp.queue_ttl})
36
+ end
37
+
38
+ def sid
39
+ @sid ||= UUID.new.generate
40
+ end
41
+
42
+ def exchange
43
+ request.env['PATH_INFO']
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Push
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "push/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "push"
7
+ s.version = Push::VERSION
8
+ s.authors = ["Brad Gessler"]
9
+ s.email = ["brad@bradgessler.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Build realtime Ruby web applications}
12
+ s.description = %q{Push is a realtime web application toolkit for building realtime Ruby web applications.}
13
+
14
+ s.rubyforge_project = "push"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "guard-rspec"
24
+ s.add_runtime_dependency "rack", ">= 1.1.0"
25
+ s.add_runtime_dependency "uuid"
26
+ s.add_runtime_dependency "async_sinatra"
27
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ describe Push do
4
+ before(:all) do
5
+ Push.config.backend = :test
6
+ end
7
+
8
+ context "config" do
9
+ before(:each) do
10
+ @env = Push::Configuration.new
11
+ end
12
+
13
+ it "should have backend" do
14
+ @env.backend.should eql(:amqp)
15
+ end
16
+
17
+ context "amqp" do
18
+ it "should default host to 127.0.0.1" do
19
+ @env.amqp.host.should eql('127.0.0.1')
20
+ end
21
+
22
+ it "should default username to guest" do
23
+ @env.amqp.username.should eql('guest')
24
+ end
25
+
26
+ it "should default password to guest" do
27
+ @env.amqp.password.should eql('guest')
28
+ end
29
+
30
+ it "should default vhost to /" do
31
+ @env.amqp.vhost.should eql('/')
32
+ end
33
+
34
+ it "should have queue_ttl" do
35
+ @env.amqp.queue_ttl.should eql(5)
36
+ end
37
+ end
38
+
39
+ it "should have logger" do
40
+ @env.logger.should be_instance_of(Logger)
41
+ end
42
+
43
+ context "web_socket_url" do
44
+ it "should have web_socket_url" do
45
+ @env.web_socket.url.should eql('ws://localhost:3000/_push')
46
+ end
47
+ end
48
+
49
+ context "long_poll_url" do
50
+ it "should have long_poll_url" do
51
+ @env.long_poll.url.should eql('http://localhost:3000/_push')
52
+ end
53
+
54
+ it "should have long_poll_timeout" do
55
+ @env.long_poll.timeout.should eql(30)
56
+ end
57
+ end
58
+
59
+ context "from_hash" do
60
+ before(:each) do
61
+ @env.from_hash({
62
+ 'backend' => 'test',
63
+ 'web_socket' => {
64
+ 'url' => 'ws://push.polleverywhere.com'
65
+ },
66
+ 'long_poll' => {
67
+ 'url' => 'http://push.polleverywhere.com'
68
+ },
69
+ 'amqp' => {
70
+ 'host' => 'intra.push.polleverywhere.net',
71
+ 'port' => 999,
72
+ 'username' => 'brad',
73
+ 'password' => 'fun',
74
+ 'queue_ttl' => 10,
75
+ 'vhost' => 'hi'
76
+ }
77
+ })
78
+ end
79
+
80
+ it "should config backend" do
81
+ @env.backend.should eql(:test)
82
+ end
83
+
84
+ it "should config web_socket url" do
85
+ @env.web_socket.url.should eql('ws://push.polleverywhere.com')
86
+ end
87
+
88
+ it "should config long_poll url" do
89
+ @env.long_poll.url.should eql('http://push.polleverywhere.com')
90
+ end
91
+
92
+ context "amqp" do
93
+ it "should config host" do
94
+ @env.amqp.host.should eql('intra.push.polleverywhere.net')
95
+ end
96
+
97
+ it "should config username" do
98
+ @env.amqp.username.should eql('brad')
99
+ end
100
+
101
+ it "should config password" do
102
+ @env.amqp.password.should eql('fun')
103
+ end
104
+
105
+ it "should config vhost" do
106
+ @env.amqp.vhost.should eql('hi')
107
+ end
108
+
109
+ it "should config queue_ttl" do
110
+ @env.amqp.queue_ttl.should eql(10)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ context "backend" do
118
+ context "adapters" do
119
+ before(:all) do
120
+ @an_adapter = Class.new
121
+ end
122
+
123
+ it "register new adapter" do
124
+ lambda{
125
+ Push::Backend.register_adapter(:super_cool, @an_adapter)
126
+ }.should change(Push::Backend.adapters, :count).by(1)
127
+ end
128
+
129
+ it "should return instance of an adapter" do
130
+ Push::Backend.register_adapter(:super_cool, @an_adapter)
131
+ Push::Backend.adapter(:super_cool).should be_an_instance_of(@an_adapter)
132
+ end
133
+ end
134
+ end
135
+
136
+ context "producer" do
137
+ before(:all) do
138
+ @producer = Push::Producer.new
139
+ end
140
+
141
+ it "should publish" do
142
+ lambda{
143
+ @producer.publish('hi').to('/exchange')
144
+ }.should change(@producer.backend.exchange['/exchange'], :count).by(1)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'push'
4
+ require 'rspec'
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: push
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brad Gessler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70258581885400 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70258581885400
25
+ - !ruby/object:Gem::Dependency
26
+ name: guard-rspec
27
+ requirement: &70258581884700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70258581884700
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ requirement: &70258581883280 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70258581883280
47
+ - !ruby/object:Gem::Dependency
48
+ name: uuid
49
+ requirement: &70258581873460 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70258581873460
58
+ - !ruby/object:Gem::Dependency
59
+ name: async_sinatra
60
+ requirement: &70258581871780 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70258581871780
69
+ description: Push is a realtime web application toolkit for building realtime Ruby
70
+ web applications.
71
+ email:
72
+ - brad@bradgessler.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - .rbenv-version
79
+ - Gemfile
80
+ - Guardfile
81
+ - README.md
82
+ - Rakefile
83
+ - lib/push.rb
84
+ - lib/push/backend.rb
85
+ - lib/push/configuration.rb
86
+ - lib/push/logging.rb
87
+ - lib/push/producer.rb
88
+ - lib/push/transport.rb
89
+ - lib/push/transport/controller/http_long_poll.rb
90
+ - lib/push/transport/controller/http_long_poll/public/flash/easyxdm.swf
91
+ - lib/push/transport/controller/http_long_poll/public/javascripts/easyXDM.debug.js
92
+ - lib/push/transport/controller/http_long_poll/public/javascripts/easyXDM.js
93
+ - lib/push/transport/controller/http_long_poll/public/javascripts/json2.js
94
+ - lib/push/transport/controller/http_long_poll/views/proxy.erb
95
+ - lib/push/transport/controller/web_socket.rb
96
+ - lib/push/version.rb
97
+ - push.gemspec
98
+ - spec/pusher_spec.rb
99
+ - spec/spec_helper.rb
100
+ homepage: ''
101
+ licenses: []
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project: push
120
+ rubygems_version: 1.8.10
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Build realtime Ruby web applications
124
+ test_files:
125
+ - spec/pusher_spec.rb
126
+ - spec/spec_helper.rb
127
+ has_rdoc: