private_pub 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +27 -0
- data/LICENSE +1 -1
- data/README.md +115 -0
- data/Rakefile +3 -1
- data/app/assets/javascripts/private_pub.js +72 -0
- data/lib/generators/private_pub/install_generator.rb +4 -4
- data/lib/generators/private_pub/templates/private_pub.ru +8 -0
- data/lib/private_pub/{railtie.rb → engine.rb} +1 -1
- data/lib/private_pub/faye_extension.rb +3 -1
- data/lib/private_pub/view_helpers.rb +6 -8
- data/lib/private_pub.rb +20 -5
- data/spec/javascripts/private_pub_spec.js +108 -0
- data/spec/javascripts/support/jasmine.yml +73 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +32 -0
- data/spec/private_pub/faye_extension_spec.rb +9 -0
- data/spec/private_pub_spec.rb +36 -5
- data/spec/spec_helper.rb +1 -0
- metadata +67 -40
- data/CHANGELOG.rdoc +0 -12
- data/README.rdoc +0 -66
- data/lib/generators/private_pub/templates/faye.ru +0 -12
- data/lib/generators/private_pub/templates/private_pub.js +0 -27
data/CHANGELOG.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
## 0.3.0 (January 14, 2012)
|
2
|
+
|
3
|
+
* adding `PrivatePub.publish_to` method for publishing from anywhere - issue #15
|
4
|
+
|
5
|
+
* rewriting `private_pub.js` so it is framework agnostic
|
6
|
+
|
7
|
+
* Rails 3.1 compatibility (thanks BinaryMuse) - issue #25
|
8
|
+
|
9
|
+
* adding faye gem dependency so it doesn't need to be installed separately
|
10
|
+
|
11
|
+
* renaming `faye.ru` to `private_pub.ru`
|
12
|
+
|
13
|
+
* truncate token for client for security (thanks jameshuynh) - issue #19
|
14
|
+
|
15
|
+
|
16
|
+
## 0.2.0 (April 7, 2011)
|
17
|
+
|
18
|
+
* switched to YAML file for config. BACKWARDS INCOMPATIBLE: you will need to remove config/initializers/private_pub.rb
|
19
|
+
|
20
|
+
* moved view helpers into Railtie so helper file is no longer generated
|
21
|
+
|
22
|
+
* error out when signature has expired
|
23
|
+
|
24
|
+
|
25
|
+
## 0.1.0 (April 4, 2011)
|
26
|
+
|
27
|
+
* initial release
|
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Private Pub
|
2
|
+
|
3
|
+
Private Pub is a Ruby gem for use with Rails to publish and subscribe to messages through [Faye](http://faye.jcoglan.com/). It allows you to easily provide real-time updates through an open socket without tying up a Rails process. All channels are private so users can only listen to events you subscribe them to.
|
4
|
+
|
5
|
+
This project was motivated by [RailsCasts episode #260](http://railscasts.com/episodes/260-messaging-with-faye).
|
6
|
+
|
7
|
+
|
8
|
+
## Setup
|
9
|
+
|
10
|
+
Add the gem to your Gemfile and run the `bundle` command to install it.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem "private_pub"
|
14
|
+
```
|
15
|
+
|
16
|
+
Run the generator to create the initial files.
|
17
|
+
|
18
|
+
```
|
19
|
+
rails g private_pub:install
|
20
|
+
```
|
21
|
+
|
22
|
+
Next, start up Faye using the rackup file that was generated.
|
23
|
+
|
24
|
+
```
|
25
|
+
rackup private_pub.ru -s thin -E production
|
26
|
+
```
|
27
|
+
|
28
|
+
**In Rails 3.1** add the JavaScript file to your application.js file manifest.
|
29
|
+
|
30
|
+
```javascript
|
31
|
+
//= require private_pub
|
32
|
+
```
|
33
|
+
|
34
|
+
**In Rails 3.0** add the generated private_pub.js file to your layout.
|
35
|
+
|
36
|
+
```rhtml
|
37
|
+
<%= javascript_include_tag "private_pub" %>
|
38
|
+
```
|
39
|
+
|
40
|
+
It's not necessary to include faye.js since that will be handled automatically for you.
|
41
|
+
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
Use the `subscribe_to` helper method on any page to subscribe to a channel.
|
46
|
+
|
47
|
+
```rhtml
|
48
|
+
<%= subscribe_to "/messages/new" %>
|
49
|
+
```
|
50
|
+
|
51
|
+
Use the `publish_to` helper method to send JavaScript to that channel. This is usually done in a JavaScript AJAX template (such as a create.js.erb file).
|
52
|
+
|
53
|
+
```rhtml
|
54
|
+
<% publish_to "/messages/new" do %>
|
55
|
+
$("#chat").append("<%= j render(@messages) %>");
|
56
|
+
<% end %>
|
57
|
+
```
|
58
|
+
|
59
|
+
This JavaScript will be immediately evaluated on all clients who have subscribed to that channel. In this example they will see the new chat message appear in real-time without reloading the browser.
|
60
|
+
|
61
|
+
|
62
|
+
## Alternative Usage
|
63
|
+
|
64
|
+
If you prefer to work through JSON instead of JavaScript templates to handle AJAX responses, you can pass an argument to `publish_to` instead of a block and it will be converted `to_json` behind the scenes. This can also be done anywhere (such as the controller).
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
PrivatePub.publish_to "/messages/new", :chat_message => "Hello, world!"
|
68
|
+
```
|
69
|
+
|
70
|
+
And then handle this through JavaScript on the client side.
|
71
|
+
|
72
|
+
```javascript
|
73
|
+
PrivatePub.subscribe("/messages/new", function(data, channel) {
|
74
|
+
$("#chat").append(data.chat_message);
|
75
|
+
});
|
76
|
+
```
|
77
|
+
|
78
|
+
The Ruby `subscribe_to` call is still necessary with this approach to grant the user access to the channel. The JavaScript is just a callback for any custom behavior.
|
79
|
+
|
80
|
+
|
81
|
+
## Security
|
82
|
+
|
83
|
+
Security is handled automatically for you. Only the Rails app is able to publish messages. Users are only able to receive messages on the channels you subscribe them to so every channel is private.
|
84
|
+
|
85
|
+
Here's how it works. The `subscribe_to` helper will output a script element containing data information about the channel.
|
86
|
+
|
87
|
+
```html
|
88
|
+
<script type="text/javascript">
|
89
|
+
PrivatePub.sign({
|
90
|
+
channel: "/messages/new",
|
91
|
+
timestamp: 1302306682972,
|
92
|
+
signature: "dc1c71d3e959ebb6f49aa6af0c86304a0740088d",
|
93
|
+
server: "http://localhost:9292/faye"
|
94
|
+
});
|
95
|
+
</script>
|
96
|
+
```
|
97
|
+
|
98
|
+
The signature is a combination of the channel, timestamp, and secret token set in the Rails app. This is checked by the Faye extension when subscribing to a channel to ensure the signature is correct. The signature automatically expires after 1 hour but this can be configured in the generated YAML config file.
|
99
|
+
|
100
|
+
```yaml
|
101
|
+
signature_expiration: 600 # 10 minutes, expressed in seconds
|
102
|
+
```
|
103
|
+
|
104
|
+
Or use a blank value for no expiration.
|
105
|
+
|
106
|
+
```yaml
|
107
|
+
signature_expiration:
|
108
|
+
```
|
109
|
+
|
110
|
+
Note: if Faye is on a separate server from the Rails app it's important that the system clocks be in sync so the expiration works properly.
|
111
|
+
|
112
|
+
|
113
|
+
## Development & Feedback
|
114
|
+
|
115
|
+
Questions or comments? Please use the [issue tracker](https://github.com/ryanb/private_pub/issues). If you would like to contribue to this project, clone this repository and run `bundle` and `rake` to run the tests.
|
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
+
require 'jasmine'
|
5
|
+
load 'jasmine/tasks/jasmine.rake'
|
4
6
|
|
5
7
|
desc "Run RSpec"
|
6
8
|
RSpec::Core::RakeTask.new do |t|
|
7
9
|
t.verbose = false
|
8
10
|
end
|
9
11
|
|
10
|
-
task :default => :spec
|
12
|
+
task :default => [:spec, "jasmine:ci"]
|
@@ -0,0 +1,72 @@
|
|
1
|
+
function buildPrivatePub(doc) {
|
2
|
+
var self = {
|
3
|
+
connecting: false,
|
4
|
+
fayeClient: null,
|
5
|
+
fayeCallbacks: [],
|
6
|
+
subscriptions: {},
|
7
|
+
subscriptionCallbacks: {},
|
8
|
+
|
9
|
+
faye: function(callback) {
|
10
|
+
if (self.fayeClient) {
|
11
|
+
callback(self.fayeClient);
|
12
|
+
} else {
|
13
|
+
self.fayeCallbacks.push(callback);
|
14
|
+
if (self.subscriptions.server && !self.connecting) {
|
15
|
+
self.connecting = true;
|
16
|
+
var script = doc.createElement("script");
|
17
|
+
script.type = "text/javascript";
|
18
|
+
script.src = self.subscriptions.server + ".js";
|
19
|
+
script.onload = self.connectToFaye;
|
20
|
+
doc.documentElement.appendChild(script);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
connectToFaye: function() {
|
26
|
+
self.fayeClient = new Faye.Client(self.subscriptions.server);
|
27
|
+
self.fayeClient.addExtension(self.fayeExtension);
|
28
|
+
for (var i=0; i < self.fayeCallbacks.length; i++) {
|
29
|
+
self.fayeCallbacks[i](self.fayeClient);
|
30
|
+
};
|
31
|
+
},
|
32
|
+
|
33
|
+
fayeExtension: {
|
34
|
+
outgoing: function(message, callback) {
|
35
|
+
if (message.channel == "/meta/subscribe") {
|
36
|
+
// Attach the signature and timestamp to subscription messages
|
37
|
+
var subscription = self.subscriptions[message.subscription];
|
38
|
+
if (!message.ext) message.ext = {};
|
39
|
+
message.ext.private_pub_signature = subscription.signature;
|
40
|
+
message.ext.private_pub_timestamp = subscription.timestamp;
|
41
|
+
}
|
42
|
+
callback(message);
|
43
|
+
}
|
44
|
+
},
|
45
|
+
|
46
|
+
sign: function(options) {
|
47
|
+
if (!self.subscriptions.server) {
|
48
|
+
self.subscriptions.server = options.server;
|
49
|
+
}
|
50
|
+
self.subscriptions[options.channel] = options;
|
51
|
+
self.faye(function(faye) {
|
52
|
+
faye.subscribe(options.channel, self.handleResponse);
|
53
|
+
});
|
54
|
+
},
|
55
|
+
|
56
|
+
handleResponse: function(message) {
|
57
|
+
if (message.eval) {
|
58
|
+
eval(message.eval);
|
59
|
+
}
|
60
|
+
if (callback = self.subscriptionCallbacks[message.channel]) {
|
61
|
+
callback(message.data, message.channel);
|
62
|
+
}
|
63
|
+
},
|
64
|
+
|
65
|
+
subscribe: function(channel, callback) {
|
66
|
+
self.subscriptionCallbacks[channel] = callback;
|
67
|
+
}
|
68
|
+
};
|
69
|
+
return self;
|
70
|
+
}
|
71
|
+
|
72
|
+
var PrivatePub = buildPrivatePub(document);
|
@@ -6,11 +6,11 @@ module PrivatePub
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def copy_files
|
9
|
-
remove_file "config/initializers/private_pub.rb"
|
10
|
-
remove_file "app/helpers/private_pub_helper.rb"
|
11
9
|
template "private_pub.yml", "config/private_pub.yml"
|
12
|
-
|
13
|
-
|
10
|
+
if ::Rails.version < "3.1"
|
11
|
+
copy_file "../../../../app/assets/javascripts/private_pub.js", "public/javascripts/private_pub.js"
|
12
|
+
end
|
13
|
+
copy_file "private_pub.ru", "private_pub.ru"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Run with: rackup private_pub.ru -s thin -E production
|
2
|
+
require "bundler/setup"
|
3
|
+
require "yaml"
|
4
|
+
require "faye"
|
5
|
+
require "private_pub"
|
6
|
+
|
7
|
+
PrivatePub.load_config(File.expand_path("../config/private_pub.yml", __FILE__), ENV["RAILS_ENV"] || "development")
|
8
|
+
run PrivatePub.faye_app
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "private_pub/view_helpers"
|
2
2
|
|
3
3
|
module PrivatePub
|
4
|
-
class
|
4
|
+
class Engine < Rails::Engine
|
5
5
|
initializer "private_pub.config" do
|
6
6
|
path = Rails.root.join("config/private_pub.yml")
|
7
7
|
PrivatePub.load_config(path, Rails.env) if path.exist?
|
@@ -9,7 +9,7 @@ module PrivatePub
|
|
9
9
|
callback.call(message)
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
private
|
13
13
|
|
14
14
|
def authenticate_subscribe(message)
|
15
15
|
subscription = PrivatePub.subscription(:channel => message["subscription"], :timestamp => message["ext"]["private_pub_timestamp"])
|
@@ -25,6 +25,8 @@ module PrivatePub
|
|
25
25
|
raise Error, "No secret_token config set, ensure private_pub.yml is loaded properly."
|
26
26
|
elsif message["ext"]["private_pub_token"] != PrivatePub.config[:secret_token]
|
27
27
|
message["error"] = "Incorrect token."
|
28
|
+
else
|
29
|
+
message["ext"]["private_pub_token"] = nil
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
module PrivatePub
|
2
2
|
module ViewHelpers
|
3
|
-
def publish_to(channel, &block)
|
4
|
-
|
5
|
-
PrivatePub.publish(:message => message.to_json)
|
3
|
+
def publish_to(channel, data = nil, &block)
|
4
|
+
PrivatePub.publish_to(channel, data || capture(&block))
|
6
5
|
end
|
7
6
|
|
8
7
|
def subscribe_to(channel)
|
9
8
|
subscription = PrivatePub.subscription(:channel => channel)
|
10
|
-
|
11
|
-
|
12
|
-
"
|
13
|
-
|
14
|
-
"data-timestamp" => subscription[:timestamp]
|
9
|
+
subscription[:server] = PrivatePub.config[:server]
|
10
|
+
content_tag "script", :type => "text/javascript" do
|
11
|
+
raw("PrivatePub.sign(#{subscription.to_json});")
|
12
|
+
end
|
15
13
|
end
|
16
14
|
end
|
17
15
|
end
|
data/lib/private_pub.rb
CHANGED
@@ -2,7 +2,7 @@ require "digest/sha1"
|
|
2
2
|
require "net/http"
|
3
3
|
|
4
4
|
require "private_pub/faye_extension"
|
5
|
-
require "private_pub/
|
5
|
+
require "private_pub/engine" if defined? Rails
|
6
6
|
|
7
7
|
module PrivatePub
|
8
8
|
class Error < StandardError; end
|
@@ -29,17 +29,32 @@ module PrivatePub
|
|
29
29
|
sub
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
|
32
|
+
def publish_to(channel, data)
|
33
|
+
publish_message(message(channel, data))
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
36
|
+
def message(channel, data)
|
37
|
+
message = {:channel => channel, :data => {:channel => channel}, :ext => {:private_pub_token => config[:secret_token]}}
|
38
|
+
if data.kind_of? String
|
39
|
+
message[:data][:eval] = data
|
40
|
+
else
|
41
|
+
message[:data][:data] = data
|
42
|
+
end
|
43
|
+
message
|
44
|
+
end
|
45
|
+
|
46
|
+
def publish_message(message)
|
47
|
+
Net::HTTP.post_form(URI.parse(config[:server]), :message => message.to_json)
|
38
48
|
end
|
39
49
|
|
40
50
|
def signature_expired?(timestamp)
|
41
51
|
timestamp < ((Time.now.to_f - config[:signature_expiration])*1000).round if config[:signature_expiration]
|
42
52
|
end
|
53
|
+
|
54
|
+
def faye_app(options = {})
|
55
|
+
options = {:mount => "/faye", :timeout => 45, :extensions => [FayeExtension.new]}.merge(options)
|
56
|
+
Faye::RackAdapter.new(options)
|
57
|
+
end
|
43
58
|
end
|
44
59
|
|
45
60
|
reset_config
|
@@ -0,0 +1,108 @@
|
|
1
|
+
describe("PrivatePub", function() {
|
2
|
+
var pub, doc;
|
3
|
+
beforeEach(function() {
|
4
|
+
Faye = {}; // To simulate global Faye object
|
5
|
+
doc = {};
|
6
|
+
pub = buildPrivatePub(doc);
|
7
|
+
});
|
8
|
+
|
9
|
+
it("adds a subscription callback", function() {
|
10
|
+
pub.subscribe("hello", "callback");
|
11
|
+
expect(pub.subscriptionCallbacks["hello"]).toEqual("callback");
|
12
|
+
});
|
13
|
+
|
14
|
+
it("has a fayeExtension which adds matching subscription signature and timestamp to outgoing message", function() {
|
15
|
+
var called = false;
|
16
|
+
var message = {channel: "/meta/subscribe", subscription: "hello"}
|
17
|
+
pub.subscriptions["hello"] = {signature: "abcd", timestamp: "1234"}
|
18
|
+
pub.fayeExtension.outgoing(message, function(message) {
|
19
|
+
expect(message.ext.private_pub_signature).toEqual("abcd");
|
20
|
+
expect(message.ext.private_pub_timestamp).toEqual("1234");
|
21
|
+
called = true;
|
22
|
+
});
|
23
|
+
expect(called).toBeTruthy();
|
24
|
+
});
|
25
|
+
|
26
|
+
it("evaluates javascript in message response", function() {
|
27
|
+
pub.handleResponse({eval: 'self.subscriptions.foo = "bar"'});
|
28
|
+
expect(pub.subscriptions.foo).toEqual("bar");
|
29
|
+
});
|
30
|
+
|
31
|
+
it("triggers callback matching message channel in response", function() {
|
32
|
+
var called = false;
|
33
|
+
pub.subscribe("test", function(data, channel) {
|
34
|
+
expect(data).toEqual("abcd");
|
35
|
+
expect(channel).toEqual("test");
|
36
|
+
called = true;
|
37
|
+
});
|
38
|
+
pub.handleResponse({channel: "test", data: "abcd"});
|
39
|
+
expect(called).toBeTruthy();
|
40
|
+
});
|
41
|
+
|
42
|
+
it("adds a faye subscription with response handler when signing", function() {
|
43
|
+
var faye = {subscribe: jasmine.createSpy()};
|
44
|
+
spyOn(pub, 'faye').andCallFake(function(callback) {
|
45
|
+
callback(faye);
|
46
|
+
});
|
47
|
+
var options = {server: "server", channel: "somechannel"};
|
48
|
+
pub.sign(options);
|
49
|
+
expect(faye.subscribe).toHaveBeenCalledWith("somechannel", pub.handleResponse);
|
50
|
+
expect(pub.subscriptions.server).toEqual("server");
|
51
|
+
expect(pub.subscriptions.somechannel).toEqual(options);
|
52
|
+
});
|
53
|
+
|
54
|
+
it("adds a faye subscription with response handler when signing", function() {
|
55
|
+
var faye = {subscribe: jasmine.createSpy()};
|
56
|
+
spyOn(pub, 'faye').andCallFake(function(callback) {
|
57
|
+
callback(faye);
|
58
|
+
});
|
59
|
+
var options = {server: "server", channel: "somechannel"};
|
60
|
+
pub.sign(options);
|
61
|
+
expect(faye.subscribe).toHaveBeenCalledWith("somechannel", pub.handleResponse);
|
62
|
+
expect(pub.subscriptions.server).toEqual("server");
|
63
|
+
expect(pub.subscriptions.somechannel).toEqual(options);
|
64
|
+
});
|
65
|
+
|
66
|
+
it("triggers faye callback function immediately when fayeClient is available", function() {
|
67
|
+
var called = false;
|
68
|
+
pub.fayeClient = "faye";
|
69
|
+
pub.faye(function(faye) {
|
70
|
+
expect(faye).toEqual("faye");
|
71
|
+
called = true;
|
72
|
+
});
|
73
|
+
expect(called).toBeTruthy();
|
74
|
+
});
|
75
|
+
|
76
|
+
it("adds fayeCallback when client and server aren't available", function() {
|
77
|
+
pub.faye("callback");
|
78
|
+
expect(pub.fayeCallbacks[0]).toEqual("callback");
|
79
|
+
});
|
80
|
+
|
81
|
+
it("adds a script tag loading faye js when the server is present", function() {
|
82
|
+
script = {};
|
83
|
+
doc.createElement = function() { return script; };
|
84
|
+
doc.documentElement = {appendChild: jasmine.createSpy()};
|
85
|
+
pub.subscriptions.server = "path/to/faye";
|
86
|
+
pub.faye("callback");
|
87
|
+
expect(pub.fayeCallbacks[0]).toEqual("callback");
|
88
|
+
expect(script.type).toEqual("text/javascript");
|
89
|
+
expect(script.src).toEqual("path/to/faye.js");
|
90
|
+
expect(script.onload).toEqual(pub.connectToFaye);
|
91
|
+
expect(doc.documentElement.appendChild).toHaveBeenCalledWith(script);
|
92
|
+
});
|
93
|
+
|
94
|
+
it("connects to faye server, adds extension, and executes callbacks", function() {
|
95
|
+
callback = jasmine.createSpy();
|
96
|
+
client = {addExtension: jasmine.createSpy()};
|
97
|
+
Faye.Client = function(server) {
|
98
|
+
expect(server).toEqual("server")
|
99
|
+
return client;
|
100
|
+
};
|
101
|
+
pub.subscriptions.server = "server";
|
102
|
+
pub.fayeCallbacks.push(callback);
|
103
|
+
pub.connectToFaye();
|
104
|
+
expect(pub.fayeClient).toEqual(client);
|
105
|
+
expect(client.addExtension).toHaveBeenCalledWith(pub.fayeExtension);
|
106
|
+
expect(callback).toHaveBeenCalledWith(client);
|
107
|
+
});
|
108
|
+
});
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# src_files
|
2
|
+
#
|
3
|
+
# Return an array of filepaths relative to src_dir to include before jasmine specs.
|
4
|
+
# Default: []
|
5
|
+
#
|
6
|
+
# EXAMPLE:
|
7
|
+
#
|
8
|
+
# src_files:
|
9
|
+
# - lib/source1.js
|
10
|
+
# - lib/source2.js
|
11
|
+
# - dist/**/*.js
|
12
|
+
#
|
13
|
+
src_files:
|
14
|
+
- app/assets/javascripts/private_pub.js
|
15
|
+
|
16
|
+
# stylesheets
|
17
|
+
#
|
18
|
+
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
|
19
|
+
# Default: []
|
20
|
+
#
|
21
|
+
# EXAMPLE:
|
22
|
+
#
|
23
|
+
# stylesheets:
|
24
|
+
# - css/style.css
|
25
|
+
# - stylesheets/*.css
|
26
|
+
#
|
27
|
+
stylesheets:
|
28
|
+
|
29
|
+
# helpers
|
30
|
+
#
|
31
|
+
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
|
32
|
+
# Default: ["helpers/**/*.js"]
|
33
|
+
#
|
34
|
+
# EXAMPLE:
|
35
|
+
#
|
36
|
+
# helpers:
|
37
|
+
# - helpers/**/*.js
|
38
|
+
#
|
39
|
+
helpers:
|
40
|
+
|
41
|
+
# spec_files
|
42
|
+
#
|
43
|
+
# Return an array of filepaths relative to spec_dir to include.
|
44
|
+
# Default: ["**/*[sS]pec.js"]
|
45
|
+
#
|
46
|
+
# EXAMPLE:
|
47
|
+
#
|
48
|
+
# spec_files:
|
49
|
+
# - **/*[sS]pec.js
|
50
|
+
#
|
51
|
+
spec_files:
|
52
|
+
|
53
|
+
# src_dir
|
54
|
+
#
|
55
|
+
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
|
56
|
+
# Default: project root
|
57
|
+
#
|
58
|
+
# EXAMPLE:
|
59
|
+
#
|
60
|
+
# src_dir: public
|
61
|
+
#
|
62
|
+
src_dir:
|
63
|
+
|
64
|
+
# spec_dir
|
65
|
+
#
|
66
|
+
# Spec directory path. Your spec_files must be returned relative to this path.
|
67
|
+
# Default: spec/javascripts
|
68
|
+
#
|
69
|
+
# EXAMPLE:
|
70
|
+
#
|
71
|
+
# spec_dir: spec/javascripts
|
72
|
+
#
|
73
|
+
spec_dir:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Jasmine
|
2
|
+
class Config
|
3
|
+
|
4
|
+
# Add your overrides or custom config code here
|
5
|
+
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# Note - this is necessary for rspec2, which has removed the backtrace
|
11
|
+
module Jasmine
|
12
|
+
class SpecBuilder
|
13
|
+
def declare_spec(parent, spec)
|
14
|
+
me = self
|
15
|
+
example_name = spec["name"]
|
16
|
+
@spec_ids << spec["id"]
|
17
|
+
backtrace = @example_locations[parent.description + " " + example_name]
|
18
|
+
parent.it example_name, {} do
|
19
|
+
me.report_spec(spec["id"])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'jasmine'
|
5
|
+
jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
|
6
|
+
require jasmine_config_overrides if File.exist?(jasmine_config_overrides)
|
7
|
+
if Jasmine::Dependencies.rspec2?
|
8
|
+
require 'rspec'
|
9
|
+
else
|
10
|
+
require 'spec'
|
11
|
+
end
|
12
|
+
|
13
|
+
jasmine_config = Jasmine::Config.new
|
14
|
+
spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
|
15
|
+
|
16
|
+
should_stop = false
|
17
|
+
|
18
|
+
if Jasmine::Dependencies.rspec2?
|
19
|
+
RSpec.configuration.after(:suite) do
|
20
|
+
spec_builder.stop if should_stop
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Spec::Runner.configure do |config|
|
24
|
+
config.after(:suite) do
|
25
|
+
spec_builder.stop if should_stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec_builder.start
|
31
|
+
should_stop = true
|
32
|
+
spec_builder.declare_suites
|
@@ -54,4 +54,13 @@ describe PrivatePub::FayeExtension do
|
|
54
54
|
message = @faye.incoming(@message, lambda { |m| m })
|
55
55
|
message["error"].should be_nil
|
56
56
|
end
|
57
|
+
|
58
|
+
it "should not let message carry the private pub token after server's validation" do
|
59
|
+
PrivatePub.config[:secret_token] = "good"
|
60
|
+
@message["channel"] = "/custom/channel"
|
61
|
+
@message["ext"]["private_pub_token"] = PrivatePub.config[:secret_token]
|
62
|
+
message = @faye.incoming(@message, lambda { |m| m })
|
63
|
+
message['ext']["private_pub_token"].should be_nil
|
64
|
+
end
|
65
|
+
|
57
66
|
end
|
data/spec/private_pub_spec.rb
CHANGED
@@ -49,13 +49,44 @@ describe PrivatePub do
|
|
49
49
|
subscription[:signature].should == Digest::SHA1.hexdigest("tokenchannel123")
|
50
50
|
end
|
51
51
|
|
52
|
-
it "
|
53
|
-
|
54
|
-
PrivatePub.
|
52
|
+
it "formats a message hash given a channel and a string for eval" do
|
53
|
+
PrivatePub.config[:secret_token] = "token"
|
54
|
+
PrivatePub.message("chan", "foo").should eq(
|
55
|
+
:ext => {:private_pub_token => "token"},
|
56
|
+
:channel => "chan",
|
57
|
+
:data => {
|
58
|
+
:channel => "chan",
|
59
|
+
:eval => "foo"
|
60
|
+
}
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "formats a message hash given a channel and a hash" do
|
65
|
+
PrivatePub.config[:secret_token] = "token"
|
66
|
+
PrivatePub.message("chan", :foo => "bar").should eq(
|
67
|
+
:ext => {:private_pub_token => "token"},
|
68
|
+
:channel => "chan",
|
69
|
+
:data => {
|
70
|
+
:channel => "chan",
|
71
|
+
:data => {:foo => "bar"}
|
72
|
+
}
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "publish message as json to server using Net::HTTP" do
|
77
|
+
message = stub(:to_json => "message_json")
|
78
|
+
Net::HTTP.should_receive(:post_form).with(URI.parse(PrivatePub.config[:server]), :message => "message_json").and_return(:result)
|
79
|
+
PrivatePub.publish_message(message).should == :result
|
80
|
+
end
|
81
|
+
|
82
|
+
it "publish_to passes message to publish_message call" do
|
83
|
+
PrivatePub.should_receive(:message).with("chan", "foo").and_return("message")
|
84
|
+
PrivatePub.should_receive(:publish_message).with("message").and_return(:result)
|
85
|
+
PrivatePub.publish_to("chan", "foo").should == :result
|
55
86
|
end
|
56
87
|
|
57
|
-
it "has a
|
58
|
-
PrivatePub.
|
88
|
+
it "has a Faye rack app instance" do
|
89
|
+
PrivatePub.faye_app.should be_kind_of(Faye::RackAdapter)
|
59
90
|
end
|
60
91
|
|
61
92
|
it "says signature has expired when time passed in is greater than expiration" do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,82 +1,109 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: private_pub
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
4
5
|
prerelease:
|
5
|
-
version: 0.2.0
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Ryan Bates
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
date: 2012-01-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faye
|
16
|
+
requirement: &70172946940680 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70172946940680
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70172946940000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
18
34
|
prerelease: false
|
19
|
-
|
35
|
+
version_requirements: *70172946940000
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70172946939000 !ruby/object:Gem::Requirement
|
20
39
|
none: false
|
21
|
-
requirements:
|
40
|
+
requirements:
|
22
41
|
- - ~>
|
23
|
-
- !ruby/object:Gem::Version
|
24
|
-
version: 2.
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.8.0
|
25
44
|
type: :development
|
26
|
-
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70172946939000
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jasmine
|
49
|
+
requirement: &70172946936820 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.1.1
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70172946936820
|
27
58
|
description: Private pub/sub messaging in Rails through Faye.
|
28
59
|
email: ryan@railscasts.com
|
29
60
|
executables: []
|
30
|
-
|
31
61
|
extensions: []
|
32
|
-
|
33
62
|
extra_rdoc_files: []
|
34
|
-
|
35
|
-
|
63
|
+
files:
|
64
|
+
- app/assets/javascripts/private_pub.js
|
36
65
|
- lib/generators/private_pub/install_generator.rb
|
37
|
-
- lib/generators/private_pub/templates/
|
38
|
-
- lib/generators/private_pub/templates/private_pub.js
|
66
|
+
- lib/generators/private_pub/templates/private_pub.ru
|
39
67
|
- lib/generators/private_pub/templates/private_pub.yml
|
68
|
+
- lib/private_pub/engine.rb
|
40
69
|
- lib/private_pub/faye_extension.rb
|
41
|
-
- lib/private_pub/railtie.rb
|
42
70
|
- lib/private_pub/view_helpers.rb
|
43
71
|
- lib/private_pub.rb
|
44
72
|
- spec/fixtures/private_pub.yml
|
73
|
+
- spec/javascripts/private_pub_spec.js
|
74
|
+
- spec/javascripts/support/jasmine.yml
|
75
|
+
- spec/javascripts/support/jasmine_config.rb
|
76
|
+
- spec/javascripts/support/jasmine_runner.rb
|
45
77
|
- spec/private_pub/faye_extension_spec.rb
|
46
78
|
- spec/private_pub_spec.rb
|
47
79
|
- spec/spec_helper.rb
|
48
|
-
- CHANGELOG.
|
80
|
+
- CHANGELOG.md
|
49
81
|
- Gemfile
|
50
82
|
- LICENSE
|
51
83
|
- Rakefile
|
52
|
-
- README.
|
53
|
-
has_rdoc: true
|
84
|
+
- README.md
|
54
85
|
homepage: http://github.com/ryanb/private_pub
|
55
86
|
licenses: []
|
56
|
-
|
57
87
|
post_install_message:
|
58
88
|
rdoc_options: []
|
59
|
-
|
60
|
-
require_paths:
|
89
|
+
require_paths:
|
61
90
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
92
|
none: false
|
64
|
-
requirements:
|
65
|
-
- -
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version:
|
68
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
98
|
none: false
|
70
|
-
requirements:
|
71
|
-
- -
|
72
|
-
- !ruby/object:Gem::Version
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
73
102
|
version: 1.3.4
|
74
103
|
requirements: []
|
75
|
-
|
76
104
|
rubyforge_project: private_pub
|
77
|
-
rubygems_version: 1.
|
105
|
+
rubygems_version: 1.8.11
|
78
106
|
signing_key:
|
79
107
|
specification_version: 3
|
80
108
|
summary: Private pub/sub messaging in Rails.
|
81
109
|
test_files: []
|
82
|
-
|
data/CHANGELOG.rdoc
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
0.2.0 (April 7, 2011)
|
2
|
-
|
3
|
-
* switched to YAML file for config. BACKWARDS INCOMPATIBLE: you will need to remove config/initializers/private_pub.rb
|
4
|
-
|
5
|
-
* moved view helpers into Railtie so helper file is no longer generated
|
6
|
-
|
7
|
-
* error out when signature has expired
|
8
|
-
|
9
|
-
|
10
|
-
0.1.0 (April 4, 2011)
|
11
|
-
|
12
|
-
* initial release
|
data/README.rdoc
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
= Private Pub
|
2
|
-
|
3
|
-
This is a Ruby gem for use with Rails to publish and subscribe to messages through {Faye}[http://faye.jcoglan.com/]. All channels are automatically made private.
|
4
|
-
|
5
|
-
This project was motivated by {Railscasts episode #260}[http://railscasts.com/episodes/260-messaging-with-faye].
|
6
|
-
|
7
|
-
|
8
|
-
== Setup
|
9
|
-
|
10
|
-
Add the gem to your Gemfile and run +bundle+.
|
11
|
-
|
12
|
-
gem "private_pub"
|
13
|
-
|
14
|
-
Run the generator to create the initial files.
|
15
|
-
|
16
|
-
rails g private_pub:install
|
17
|
-
|
18
|
-
Add the generated JavaScript to your layout file. Currently this uses jQuery, so you will need to customize it if you aren't using jQuery.
|
19
|
-
|
20
|
-
<%= javascript_include_tag "private_pub" %>
|
21
|
-
|
22
|
-
Next, install and start up Faye using the rackup file that was generated.
|
23
|
-
|
24
|
-
gem install faye
|
25
|
-
rackup faye.ru -s thin -E production
|
26
|
-
|
27
|
-
It's not necessary to include faye.js since that will be handled automatically.
|
28
|
-
|
29
|
-
|
30
|
-
== Usage
|
31
|
-
|
32
|
-
Use the +subscribe_to+ helper method on any page to subscribe to a channel.
|
33
|
-
|
34
|
-
<%= subscribe_to "/messages/new" %>
|
35
|
-
|
36
|
-
Use the +publish_to+ helper method to publish messages to that channel. If a block of JavaScript is passed it will be evaluated automatically. This is usually done in a JavaScript AJAX response (such as a <tt>create.js.erb</tt> file).
|
37
|
-
|
38
|
-
<% publish_to "/messages/new" do %>
|
39
|
-
$("#chat").append("<%= escape_javascript render(@messages) %>");
|
40
|
-
<% end %>
|
41
|
-
|
42
|
-
There will be alternative ways to publish/subscribe to channels in the future.
|
43
|
-
|
44
|
-
|
45
|
-
== Security
|
46
|
-
|
47
|
-
Security is handled automatically for you. Only the Rails app is able to publish. Users are only able to receive messages on the channels you subscribe them to. This means every channel is private.
|
48
|
-
|
49
|
-
Here's how it works. The +subscribe_to+ helper will output an element containing data information about the channel.
|
50
|
-
|
51
|
-
<span class="private_pub_subscription" data-channel="/messages/new" data-signature="2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" data-timestamp="13019431281234"></span>
|
52
|
-
|
53
|
-
The data-signature is a combination of the channel, timestamp, and secret token set in the Rails app. This is checked by the Faye extension when subscribing to a channel to ensure the signature is correct. The signature is automatically expired after 1 hour but this can be configured in the generated YAML file.
|
54
|
-
|
55
|
-
signature_expiration: 600 # 10 minutes, expressed in seconds
|
56
|
-
|
57
|
-
Or use a blank value for no expiration.
|
58
|
-
|
59
|
-
signature_expiration:
|
60
|
-
|
61
|
-
Note: if Faye is on a separate server from the Rails app it's important that the system clocks be in sync.
|
62
|
-
|
63
|
-
|
64
|
-
== Development & Feedback
|
65
|
-
|
66
|
-
Questions or comments? Please use the {issue tracker}[https://github.com/ryanb/private_pub/issues]. If you would like to contribue to this project, clone this repository and run +bundle+ and +rake+ to run the tests.
|
@@ -1,12 +0,0 @@
|
|
1
|
-
# Run with: rackup faye.ru -s thin -E production
|
2
|
-
require "yaml"
|
3
|
-
require "faye"
|
4
|
-
begin
|
5
|
-
require "private_pub"
|
6
|
-
rescue LoadError
|
7
|
-
require "bundler/setup"
|
8
|
-
require "private_pub"
|
9
|
-
end
|
10
|
-
|
11
|
-
PrivatePub.load_config(File.expand_path("../config/private_pub.yml", __FILE__), ENV["RAILS_ENV"] || "development")
|
12
|
-
run Faye::RackAdapter.new(:mount => "/faye", :timeout => 45, :extensions => [PrivatePub.faye_extension])
|
@@ -1,27 +0,0 @@
|
|
1
|
-
PrivatePubExtension = {
|
2
|
-
outgoing: function(message, callback) {
|
3
|
-
if (message.channel == "/meta/subscribe") {
|
4
|
-
// Attach the signature and timestamp to subscription messages
|
5
|
-
var subscription = $(".private_pub_subscription[data-channel='" + message.subscription + "']");
|
6
|
-
if (!message.ext) message.ext = {};
|
7
|
-
message.ext.private_pub_signature = subscription.data("signature");
|
8
|
-
message.ext.private_pub_timestamp = subscription.data("timestamp");
|
9
|
-
}
|
10
|
-
callback(message);
|
11
|
-
}
|
12
|
-
};
|
13
|
-
|
14
|
-
jQuery(function() {
|
15
|
-
var faye;
|
16
|
-
if ($(".private_pub_subscription").length > 0) {
|
17
|
-
jQuery.getScript($(".private_pub_subscription").data("server") + ".js", function() {
|
18
|
-
faye = new Faye.Client($(".private_pub_subscription").data("server"));
|
19
|
-
faye.addExtension(PrivatePubExtension);
|
20
|
-
$(".private_pub_subscription").each(function(index) {
|
21
|
-
faye.subscribe($(this).data("channel"), function(data) {
|
22
|
-
if (data._eval) eval(data._eval);
|
23
|
-
});
|
24
|
-
});
|
25
|
-
});
|
26
|
-
}
|
27
|
-
});
|