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 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 Ryan Bates
1
+ Copyright (c) 2012 Ryan Bates
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
- copy_file "private_pub.js", "public/javascripts/private_pub.js"
13
- copy_file "faye.ru", "faye.ru"
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 Railtie < Rails::Railtie
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
- private
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
- message = {:channel => channel, :data => {:_eval => capture(&block)}, :ext => {:private_pub_token => PrivatePub.config[:secret_token]}}
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
- content_tag :span, "", :class => "private_pub_subscription",
11
- "data-server" => PrivatePub.config[:server],
12
- "data-channel" => subscription[:channel],
13
- "data-signature" => subscription[:signature],
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/railtie" if defined? Rails
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 publish(data)
33
- Net::HTTP.post_form(URI.parse(config[:server]), data)
32
+ def publish_to(channel, data)
33
+ publish_message(message(channel, data))
34
34
  end
35
35
 
36
- def faye_extension
37
- FayeExtension.new
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
@@ -49,13 +49,44 @@ describe PrivatePub do
49
49
  subscription[:signature].should == Digest::SHA1.hexdigest("tokenchannel123")
50
50
  end
51
51
 
52
- it "publishes to server using Net::HTTP" do
53
- Net::HTTP.should_receive(:post_form).with(URI.parse(PrivatePub.config[:server]), "hello world").and_return(:result)
54
- PrivatePub.publish("hello world").should == :result
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 FayeExtension instance" do
58
- PrivatePub.faye_extension.should be_kind_of(PrivatePub::FayeExtension)
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
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
+ require 'faye'
3
4
  Bundler.require(:default)
4
5
 
5
6
  RSpec.configure do |config|
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
- date: 2011-04-07 00:00:00 -07:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
17
- name: rspec
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
- requirement: &id001 !ruby/object:Gem::Requirement
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.1.0
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
25
44
  type: :development
26
- version_requirements: *id001
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
- files:
63
+ files:
64
+ - app/assets/javascripts/private_pub.js
36
65
  - lib/generators/private_pub/install_generator.rb
37
- - lib/generators/private_pub/templates/faye.ru
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.rdoc
80
+ - CHANGELOG.md
49
81
  - Gemfile
50
82
  - LICENSE
51
83
  - Rakefile
52
- - README.rdoc
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: "0"
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.5.0
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
- });