pushkin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +6 -0
- data/Rakefile +16 -0
- data/app/assets/javascripts/pushkin.js +1 -0
- data/app/assets/javascripts/pushkin/pushkin.js.coffee +59 -0
- data/config.ru +13 -0
- data/lib/generators/pushkin/install_generator.rb +14 -0
- data/lib/generators/pushkin/templates/pushkin.ru +8 -0
- data/lib/generators/pushkin/templates/pushkin.yml +10 -0
- data/lib/pushkin.rb +63 -0
- data/lib/pushkin/action_view/helpers.rb +25 -0
- data/lib/pushkin/configuration.rb +22 -0
- data/lib/pushkin/engine.rb +16 -0
- data/lib/pushkin/faye/authentication.rb +41 -0
- data/lib/pushkin/subscription.rb +55 -0
- data/lib/pushkin/version.rb +3 -0
- data/pushkin.gemspec +32 -0
- data/spec/javascripts/pushkin_spec.js.coffee +124 -0
- data/spec/javascripts/support/jasmine.yml +9 -0
- data/spec/pushkin/faye/authentication_spec.rb +76 -0
- data/spec/pushkin_spec.rb +84 -0
- data/spec/spec_helper.rb +11 -0
- metadata +155 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
desc "Run RSpec"
|
5
|
+
RSpec::Core::RakeTask.new do |t|
|
6
|
+
t.verbose = false
|
7
|
+
end
|
8
|
+
|
9
|
+
namespace :spec do
|
10
|
+
desc "Run javascript specs via jasmine-headless-webkit runner"
|
11
|
+
task :javascripts do
|
12
|
+
system "jasmine-headless-webkit -c"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => [:spec, "spec:javascripts"]
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require ./pushkin/pushkin
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Pushkin
|
2
|
+
constructor: (@options) ->
|
3
|
+
@fayeClient = null
|
4
|
+
@fayeCallbacks = []
|
5
|
+
@subscriptions = {}
|
6
|
+
@subscriptionCallbacks = {}
|
7
|
+
|
8
|
+
subscribe: (channel, callback) ->
|
9
|
+
@subscriptionCallbacks[channel] = callback
|
10
|
+
|
11
|
+
faye: (callback) ->
|
12
|
+
if @fayeClient
|
13
|
+
callback(@fayeClient)
|
14
|
+
else
|
15
|
+
@fayeCallbacks.push(callback)
|
16
|
+
if (@server && !@connecting)
|
17
|
+
@connecting = true
|
18
|
+
doc = @document()
|
19
|
+
script = doc.createElement("script")
|
20
|
+
script.type = "text/javascript"
|
21
|
+
script.src = @server + ".js"
|
22
|
+
script.onload = @connectToFaye
|
23
|
+
doc.documentElement.appendChild(script)
|
24
|
+
|
25
|
+
fayeExtension: ->
|
26
|
+
@fayeExtensionInstance ||= new FayeExtension(@subscriptions)
|
27
|
+
|
28
|
+
connectToFaye: ->
|
29
|
+
@fayeClient = new Faye.Client(@server)
|
30
|
+
@fayeClient.addExtension(@fayeExtension())
|
31
|
+
callback(@fayeClient) for callback in @fayeCallbacks
|
32
|
+
|
33
|
+
handleResponse: (message) ->
|
34
|
+
eval(message.eval) if message.eval
|
35
|
+
if (callback = @subscriptionCallbacks[message.channel])
|
36
|
+
callback(message.data, message.channel)
|
37
|
+
|
38
|
+
sign: (subscription) ->
|
39
|
+
@server = subscription.server if !@server
|
40
|
+
@subscriptions[subscription.channel] = subscription
|
41
|
+
@faye((faye) =>
|
42
|
+
faye.subscribe(subscription.channel, @handleResponse)
|
43
|
+
)
|
44
|
+
|
45
|
+
document: -> window.document
|
46
|
+
|
47
|
+
class FayeExtension
|
48
|
+
constructor: (@subscriptions) ->
|
49
|
+
|
50
|
+
outgoing: (message, callback) ->
|
51
|
+
if (message.channel == "/meta/subscribe")
|
52
|
+
subscription = @subscriptions[message.subscription]
|
53
|
+
message.ext ||= {}
|
54
|
+
message.ext.pushkin_signature = subscription.signature;
|
55
|
+
message.ext.pushkin_timestamp = subscription.timestamp;
|
56
|
+
|
57
|
+
callback(message)
|
58
|
+
|
59
|
+
this.Pushkin = Pushkin
|
data/config.ru
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Run with: rackup pushkin.ru -s thin -E production
|
2
|
+
require "bundler/setup"
|
3
|
+
require "yaml"
|
4
|
+
require "faye"
|
5
|
+
require "pushkin"
|
6
|
+
|
7
|
+
Pushkin.configure do |config|
|
8
|
+
config.host = "http://localhost:9292"
|
9
|
+
config.endpoint = "/faye"
|
10
|
+
config.secret_token = "secret"
|
11
|
+
end
|
12
|
+
|
13
|
+
run Pushkin.server
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Pushkin
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
def self.source_root
|
5
|
+
File.dirname(__FILE__) + "/templates"
|
6
|
+
end
|
7
|
+
|
8
|
+
def copy_files
|
9
|
+
template "pushkin.yml", "config/pushkin.yml"
|
10
|
+
copy_file "pushkin.ru", "pushkin.ru"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
development:
|
2
|
+
server: "http://localhost:9292/faye"
|
3
|
+
secret_token: "secret"
|
4
|
+
test:
|
5
|
+
server: "http://localhost:9292/faye"
|
6
|
+
secret_token: "secret"
|
7
|
+
production:
|
8
|
+
server: "http://example.com/faye"
|
9
|
+
secret_token: "<%= defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32) %>"
|
10
|
+
signature_expiration: 3600 # one hour
|
data/lib/pushkin.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require "pushkin/version"
|
2
|
+
require "pushkin/configuration"
|
3
|
+
require "pushkin/subscription"
|
4
|
+
require "pushkin/faye/authentication"
|
5
|
+
|
6
|
+
module Pushkin
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def connection
|
10
|
+
# conn.post do |req|
|
11
|
+
# req.url '/nigiri'
|
12
|
+
# req.headers['Content-Type'] = 'application/json'
|
13
|
+
# req.body = '{ "name": "Unagi" }'
|
14
|
+
# end
|
15
|
+
@connection ||= begin
|
16
|
+
Faraday.new host do |conn|
|
17
|
+
conn.request :json
|
18
|
+
|
19
|
+
conn.response :logger # log the request to STDOUT
|
20
|
+
conn.response :json, :content_type => /\bjson$/
|
21
|
+
|
22
|
+
conn.adapter Faraday.default_adapter
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset!
|
28
|
+
@configuration = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def config
|
32
|
+
@configuration ||= Configuration.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def configure(opts = {}, &block)
|
36
|
+
opts.each do |attribute, setting|
|
37
|
+
Configuration.config.send("#{attribute}=", setting)
|
38
|
+
end
|
39
|
+
Configuration.configure &block if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
delegate :host, :endpoint, :secret_token, :signature_expiration, to: :config
|
43
|
+
|
44
|
+
def publish_message(message)
|
45
|
+
response = connection.post(endpoint, message)
|
46
|
+
response.body
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a subscription hash to pass to the Pushkin.sign call in JavaScript.
|
50
|
+
# Any options passed are merged to the hash.
|
51
|
+
def subscription(options = {})
|
52
|
+
Subscription.new(options).to_hash
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the Faye Rack application.
|
56
|
+
# Any options given are passed to the Faye::RackAdapter.
|
57
|
+
def server(options = {})
|
58
|
+
options = {:mount => endpoint, :timeout => 45, :extensions => []}.merge(options)
|
59
|
+
::Faye::RackAdapter.new(options)
|
60
|
+
end
|
61
|
+
|
62
|
+
reset!
|
63
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pushkin
|
2
|
+
module ActionView
|
3
|
+
module Helpers
|
4
|
+
|
5
|
+
# Publish the given data or block to the client by sending
|
6
|
+
# a Faraday POST request to the Faye server. If a block
|
7
|
+
# or string is passed in, it is evaluated as JavaScript
|
8
|
+
# on the client. Otherwise it will be converted to JSON
|
9
|
+
# for use in a JavaScript callback.
|
10
|
+
def publish_to(channel, data = nil, &block)
|
11
|
+
PrivatePub.publish_to(channel, data || capture(&block))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Subscribe the client to the given channel. This generates
|
15
|
+
# some JavaScript calling PrivatePub.sign with the subscription
|
16
|
+
# options.
|
17
|
+
def subscribe_to(channel)
|
18
|
+
subscription = PrivatePub.subscription(:channel => channel)
|
19
|
+
content_tag "script", :type => "text/javascript" do
|
20
|
+
raw("PrivatePub.sign(#{subscription.to_json});")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'active_support/configurable'
|
3
|
+
|
4
|
+
module Pushkin
|
5
|
+
class Configuration
|
6
|
+
include ActiveSupport::Configurable
|
7
|
+
|
8
|
+
config_accessor :host, :endpoint, :secret_token, :signature_expiration
|
9
|
+
|
10
|
+
def self.generate_token
|
11
|
+
defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Pushkin.configure do |config|
|
15
|
+
# config.host = 'http://localhost:9292'
|
16
|
+
# config.secret_token = generate_token
|
17
|
+
# config.endpoint = '/faye'
|
18
|
+
# # config.signature_expiration = 3600 # 1 hour
|
19
|
+
# end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "pushkin/action_view/helpers"
|
2
|
+
|
3
|
+
module Pushkin
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
# Loads the pushkin.yml file if it exists.
|
6
|
+
initializer "pushkin.config" do
|
7
|
+
path = Rails.root.join("config/pushkin.yml")
|
8
|
+
Pushkin.configure(path[Rails.env]) if path.exist?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Adds the ViewHelpers into ActionView::Base
|
12
|
+
initializer "private_pub.view_helpers" do
|
13
|
+
ActionView::Base.send :include, Pushkin::ActionView::Helpers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pushkin
|
2
|
+
module Faye
|
3
|
+
class AuthenticationError < StandardError; end
|
4
|
+
|
5
|
+
class Authentication
|
6
|
+
def incoming(message, callback)
|
7
|
+
if message["channel"] == '/meta/subscribe'
|
8
|
+
authenticate_subscribe(message)
|
9
|
+
elsif message["channel"] !~ %r{^/meta/}
|
10
|
+
authenticate_publish(message)
|
11
|
+
end
|
12
|
+
callback.call(message)
|
13
|
+
end
|
14
|
+
|
15
|
+
def authenticate_subscribe(message)
|
16
|
+
signature = message["ext"]["pushkin_signature"]
|
17
|
+
timestamp = message["ext"]["pushkin_timestamp"]
|
18
|
+
|
19
|
+
subscription = Pushkin::Subscription.new \
|
20
|
+
channel: message["subscription"],
|
21
|
+
timestamp: timestamp
|
22
|
+
|
23
|
+
if signature != subscription.signature
|
24
|
+
message["error"] = "Incorrect signature."
|
25
|
+
elsif subscription.signature_expired?
|
26
|
+
message["error"] = "Signature has expired."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticate_publish(message)
|
31
|
+
if Pushkin.secret_token.nil?
|
32
|
+
raise AuthenticationError.new("No secret_token config set")
|
33
|
+
elsif message["ext"]["pushkin_token"] != Pushkin.secret_token
|
34
|
+
message["error"] = "Incorrect token."
|
35
|
+
else
|
36
|
+
message["ext"]["pushkin_token"] = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Pushkin
|
2
|
+
class Subscription
|
3
|
+
attr_accessor :host, :endpoint, :channel, :timestamp
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@timestamp = options[:timestamp]
|
7
|
+
@host = options[:host]
|
8
|
+
@endpoint = options[:endpoint]
|
9
|
+
@channel = options[:channel]
|
10
|
+
end
|
11
|
+
|
12
|
+
def signature
|
13
|
+
@signature ||= Digest::SHA1.hexdigest([secret_token, channel, timestamp].join)
|
14
|
+
end
|
15
|
+
|
16
|
+
def signature_expired?
|
17
|
+
return false unless expiration
|
18
|
+
timestamp < ((Time.now.to_f - expiration)*1000).round
|
19
|
+
end
|
20
|
+
|
21
|
+
def expiration
|
22
|
+
Pushkin.signature_expiration
|
23
|
+
end
|
24
|
+
|
25
|
+
def secret_token
|
26
|
+
Pushkin.secret_token
|
27
|
+
end
|
28
|
+
|
29
|
+
def endpoint
|
30
|
+
@endpoint ||= Pushkin.endpoint
|
31
|
+
end
|
32
|
+
|
33
|
+
def host
|
34
|
+
@host ||= Pushkin.host
|
35
|
+
end
|
36
|
+
|
37
|
+
def server
|
38
|
+
host + endpoint
|
39
|
+
end
|
40
|
+
|
41
|
+
def timestamp
|
42
|
+
@timestamp ||= (Time.now.to_f * 1000).round
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
{
|
47
|
+
server: server,
|
48
|
+
timestamp: timestamp,
|
49
|
+
channel: channel,
|
50
|
+
signature: signature
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/pushkin.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pushkin/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pushkin"
|
7
|
+
s.version = Pushkin::VERSION
|
8
|
+
s.authors = ["Ross Kaffenberger"]
|
9
|
+
s.email = ["rosskaff@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Pub/Sub for Rails with Faye}
|
12
|
+
s.description = %q{Pub/Sub for Rails with Faye based on PrivatePub}
|
13
|
+
|
14
|
+
s.rubyforge_project = "pushkin"
|
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_runtime_dependency "rest-client"
|
24
|
+
s.add_dependency "faye"
|
25
|
+
s.add_dependency "faraday", "0.8.0.rc2"
|
26
|
+
s.add_dependency "faraday_middleware"
|
27
|
+
s.add_dependency "activesupport"
|
28
|
+
|
29
|
+
s.add_development_dependency "rake"
|
30
|
+
s.add_development_dependency "rspec"
|
31
|
+
s.add_development_dependency "jasmine-headless-webkit"
|
32
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
describe "Pushkin", ->
|
2
|
+
pushkin = null
|
3
|
+
|
4
|
+
it "should be defined", ->
|
5
|
+
expect(Pushkin).toEqual jasmine.any(Function)
|
6
|
+
|
7
|
+
describe "instance functions", ->
|
8
|
+
doc = null
|
9
|
+
called = null
|
10
|
+
|
11
|
+
beforeEach ->
|
12
|
+
window.Faye = {}
|
13
|
+
pushkin = new Pushkin
|
14
|
+
called = false
|
15
|
+
doc = {}
|
16
|
+
|
17
|
+
it "adds callback to subscriptions", ->
|
18
|
+
pushkin.subscribe("hello", "callback");
|
19
|
+
expect(pushkin.subscriptionCallbacks["hello"]).toEqual("callback")
|
20
|
+
|
21
|
+
it "has a fayeExtension which adds matching subscription signature and timestamp to outgoing message", ->
|
22
|
+
message =
|
23
|
+
channel: "/meta/subscribe"
|
24
|
+
subscription: "hello"
|
25
|
+
|
26
|
+
pushkin.subscriptions["hello"] = {signature: "abcd", timestamp: "1234"}
|
27
|
+
fayeExtension = pushkin.fayeExtension()
|
28
|
+
fayeExtension.outgoing(message, (message) ->
|
29
|
+
expect(message.ext.pushkin_signature).toEqual "abcd"
|
30
|
+
expect(message.ext.pushkin_timestamp).toEqual "1234"
|
31
|
+
called = true
|
32
|
+
)
|
33
|
+
expect(called).toBeTruthy()
|
34
|
+
|
35
|
+
it "evaluates javascript in message response", ->
|
36
|
+
pushkin.handleResponse(
|
37
|
+
eval: 'this.subscriptions.foo = "bar"'
|
38
|
+
)
|
39
|
+
expect(pushkin.subscriptions.foo).toEqual "bar"
|
40
|
+
|
41
|
+
it "triggers callback matching message channel in response", ->
|
42
|
+
pushkin.subscribe("test", (data, channel) ->
|
43
|
+
expect(data).toEqual "abcd"
|
44
|
+
expect(channel).toEqual "test"
|
45
|
+
called = true
|
46
|
+
)
|
47
|
+
pushkin.handleResponse(
|
48
|
+
channel: "test"
|
49
|
+
data: "abcd"
|
50
|
+
)
|
51
|
+
expect(called).toBeTruthy()
|
52
|
+
|
53
|
+
it "adds a faye subscription with response handler when signing", ->
|
54
|
+
faye =
|
55
|
+
subscribe: jasmine.createSpy()
|
56
|
+
subscription =
|
57
|
+
server: "server"
|
58
|
+
channel: "somechannel"
|
59
|
+
|
60
|
+
spyOn(pushkin, 'faye').andCallFake((callback) -> callback(faye))
|
61
|
+
|
62
|
+
pushkin.sign(subscription)
|
63
|
+
expect(faye.subscribe).toHaveBeenCalledWith "somechannel", pushkin.handleResponse
|
64
|
+
expect(pushkin.server).toEqual "server"
|
65
|
+
expect(pushkin.subscriptions.somechannel).toEqual subscription
|
66
|
+
|
67
|
+
it "adds a faye subscription with response handler when signing", ->
|
68
|
+
faye =
|
69
|
+
subscribe: jasmine.createSpy()
|
70
|
+
|
71
|
+
options =
|
72
|
+
server: "server"
|
73
|
+
channel: "somechannel"
|
74
|
+
|
75
|
+
spyOn(pushkin, 'faye').andCallFake((callback) -> callback(faye))
|
76
|
+
pushkin.sign(options)
|
77
|
+
expect(faye.subscribe).toHaveBeenCalledWith "somechannel", pushkin.handleResponse
|
78
|
+
expect(pushkin.server).toEqual "server"
|
79
|
+
expect(pushkin.subscriptions.somechannel).toEqual options
|
80
|
+
|
81
|
+
it "triggers faye callback function immediately when fayeClient is available", ->
|
82
|
+
pushkin.fayeClient = "faye"
|
83
|
+
pushkin.faye((faye) ->
|
84
|
+
expect(faye).toEqual "faye"
|
85
|
+
called = true
|
86
|
+
)
|
87
|
+
expect(called).toBeTruthy()
|
88
|
+
|
89
|
+
it "adds fayeCallback when client and server aren't available", ->
|
90
|
+
pushkin.faye "callback"
|
91
|
+
expect(pushkin.fayeCallbacks[0]).toEqual "callback"
|
92
|
+
|
93
|
+
it "adds a script tag loading faye js when the server is present", ->
|
94
|
+
script = {}
|
95
|
+
doc.createElement = -> return script
|
96
|
+
doc.documentElement =
|
97
|
+
appendChild: jasmine.createSpy()
|
98
|
+
spyOn(pushkin, "document").andReturn doc
|
99
|
+
|
100
|
+
pushkin.server = "path/to/faye"
|
101
|
+
pushkin.faye("callback")
|
102
|
+
expect(pushkin.fayeCallbacks[0]).toEqual "callback"
|
103
|
+
expect(script.type).toEqual "text/javascript"
|
104
|
+
expect(script.src).toEqual "path/to/faye.js"
|
105
|
+
expect(script.onload).toEqual pushkin.connectToFaye
|
106
|
+
expect(doc.documentElement.appendChild).toHaveBeenCalledWith script
|
107
|
+
|
108
|
+
it "connects to faye server, adds extension, and executes callbacks", ->
|
109
|
+
callback = jasmine.createSpy()
|
110
|
+
client =
|
111
|
+
addExtension: jasmine.createSpy()
|
112
|
+
Faye.Client = (server) ->
|
113
|
+
expect(server).toEqual("server")
|
114
|
+
client
|
115
|
+
|
116
|
+
pushkin.server = "server"
|
117
|
+
pushkin.fayeCallbacks.push(callback)
|
118
|
+
pushkin.connectToFaye()
|
119
|
+
expect(pushkin.fayeClient).toEqual client
|
120
|
+
expect(client.addExtension).toHaveBeenCalledWith pushkin.fayeExtension()
|
121
|
+
expect(callback).toHaveBeenCalledWith client
|
122
|
+
|
123
|
+
|
124
|
+
this.Faye = {}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pushkin::Faye::Authentication do
|
4
|
+
let(:faye) { Pushkin::Faye::Authentication.new }
|
5
|
+
let(:message) { {"channel" => "/meta/subscribe", "ext" => {}} }
|
6
|
+
let(:callback) { lambda { |m| m } }
|
7
|
+
|
8
|
+
it "adds an error on an incoming subscription with a bad signature" do
|
9
|
+
message["subscription"] = "hello"
|
10
|
+
message["ext"]["pushkin_signature"] = "bad"
|
11
|
+
message["ext"]["pushkin_timestamp"] = "123"
|
12
|
+
|
13
|
+
incoming = faye.incoming(message, callback)
|
14
|
+
incoming["error"].should == "Incorrect signature."
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has no error when the signature matches the subscription" do
|
18
|
+
subscription = Pushkin::Subscription.new(:channel => "hello")
|
19
|
+
message["subscription"] = subscription.channel
|
20
|
+
message["ext"]["pushkin_signature"] = subscription.signature
|
21
|
+
message["ext"]["pushkin_timestamp"] = subscription.timestamp
|
22
|
+
|
23
|
+
incoming = faye.incoming(message, callback)
|
24
|
+
incoming["error"].should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has an error when signature just expired" do
|
28
|
+
Pushkin.config.signature_expiration = 1
|
29
|
+
subscription = Pushkin::Subscription.new(:timestamp => 123, :channel => "hello")
|
30
|
+
message["subscription"] = subscription.channel
|
31
|
+
message["ext"]["pushkin_signature"] = subscription.signature
|
32
|
+
message["ext"]["pushkin_timestamp"] = subscription.timestamp
|
33
|
+
|
34
|
+
incoming = faye.incoming(message, callback)
|
35
|
+
incoming["error"].should == "Signature has expired."
|
36
|
+
end
|
37
|
+
|
38
|
+
it "has an error when trying to publish to a custom channel with a bad token" do
|
39
|
+
Pushkin.config.secret_token = "good"
|
40
|
+
message["channel"] = "/custom/channel"
|
41
|
+
message["ext"]["pushkin_token"] = "bad"
|
42
|
+
|
43
|
+
incoming = faye.incoming(message, callback)
|
44
|
+
incoming["error"].should == "Incorrect token."
|
45
|
+
end
|
46
|
+
|
47
|
+
it "raises an exception when attempting to call a custom channel without a secret_token set" do
|
48
|
+
Pushkin.config.secret_token = nil
|
49
|
+
message["channel"] = "/custom/channel"
|
50
|
+
message["ext"]["pushkin_token"] = "bad"
|
51
|
+
|
52
|
+
lambda {
|
53
|
+
faye.incoming(message, callback)
|
54
|
+
}.should raise_error(Pushkin::Faye::AuthenticationError)
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
it "has no error on other meta calls" do
|
59
|
+
message["channel"] = "/meta/connect"
|
60
|
+
|
61
|
+
incoming = faye.incoming(message, callback)
|
62
|
+
incoming["error"].should be_nil
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not let message carry the pushkin token after server's validation" do
|
67
|
+
Pushkin.config.secret_token = "good"
|
68
|
+
|
69
|
+
message["channel"] = "/custom/channel"
|
70
|
+
message["ext"]["pushkin_token"] = Pushkin.secret_token
|
71
|
+
|
72
|
+
incoming = faye.incoming(message, callback)
|
73
|
+
incoming['ext']["pushkin_token"].should be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pushkin do
|
4
|
+
before(:each) do
|
5
|
+
Pushkin.reset!
|
6
|
+
end
|
7
|
+
|
8
|
+
it "is version 0.0.1" do
|
9
|
+
Pushkin::VERSION.should == '0.0.1'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "publishes message as json to server using Faraday" do
|
13
|
+
Pushkin.configure { |c| c.endpoint = '/faye' }
|
14
|
+
connection = Faraday.new do |builder|
|
15
|
+
builder.adapter :test do |stub|
|
16
|
+
stub.post('/faye', "message_json") { [200, {}, 'result'] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
Pushkin.stub!(:connection => connection)
|
20
|
+
Pushkin.publish_message("message_json").should == 'result'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "has a Faraday connection instance" do
|
24
|
+
Pushkin.connection.should be_kind_of(Faraday::Connection)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has a Faye rack app instance" do
|
28
|
+
Pushkin.server.should be_kind_of(Faye::RackAdapter)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "configure" do
|
32
|
+
|
33
|
+
it "has a server endpoint" do
|
34
|
+
Pushkin.configure { |c| c.endpoint = '/faye' }
|
35
|
+
Pushkin.endpoint.should == '/faye'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "has a host" do
|
39
|
+
Pushkin.configure { |c| c.host = 'server' }
|
40
|
+
Pushkin.host.should == 'server'
|
41
|
+
end
|
42
|
+
|
43
|
+
it "has a secret token" do
|
44
|
+
Pushkin.configure { |c| c.secret_token = 'secret_token' }
|
45
|
+
Pushkin.secret_token.should == 'secret_token'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "has an unsettabe secret token" do
|
49
|
+
Pushkin.configure { |c| c.signature_expiration = 600 }
|
50
|
+
Pushkin.signature_expiration.should == 600
|
51
|
+
end
|
52
|
+
|
53
|
+
it "accepts hash" do
|
54
|
+
Pushkin.configure host: 'server', endpoint: '/faye'
|
55
|
+
Pushkin.host.should == 'server'
|
56
|
+
Pushkin.endpoint.should == '/faye'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "block settings take precedence" do
|
60
|
+
Pushkin.configure host: 'server', endpoint: '/faye' do |config|
|
61
|
+
config.host = 'server1'
|
62
|
+
config.endpoint = '/foo'
|
63
|
+
end
|
64
|
+
Pushkin.host.should == 'server1'
|
65
|
+
Pushkin.endpoint.should == '/foo'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
it "includes channel, server, and custom time in subscription" do
|
71
|
+
Pushkin.configure { |c| c.host = 'localhost'; c.endpoint = '/faye' }
|
72
|
+
subscription = Pushkin.subscription(:timestamp => 123, :channel => "hello")
|
73
|
+
subscription[:timestamp].should eq(123)
|
74
|
+
subscription[:channel].should == "hello"
|
75
|
+
subscription[:server].should == 'localhost/faye'
|
76
|
+
end
|
77
|
+
|
78
|
+
it "does a sha1 digest of channel, timestamp, and secret token" do
|
79
|
+
Pushkin.configure { |c| c.secret_token = 'token' }
|
80
|
+
subscription = Pushkin.subscription(:timestamp => 123, :channel => "channel")
|
81
|
+
subscription[:signature].should == Digest::SHA1.hexdigest("tokenchannel123")
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pushkin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ross Kaffenberger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-18 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faye
|
16
|
+
requirement: &70352006466220 !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: *70352006466220
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: faraday
|
27
|
+
requirement: &70352006465480 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - =
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.8.0.rc2
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70352006465480
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: faraday_middleware
|
38
|
+
requirement: &70352006464940 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70352006464940
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: activesupport
|
49
|
+
requirement: &70352006464420 !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: *70352006464420
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &70352006463800 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70352006463800
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &70352006463380 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70352006463380
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: jasmine-headless-webkit
|
82
|
+
requirement: &70352006462960 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70352006462960
|
91
|
+
description: Pub/Sub for Rails with Faye based on PrivatePub
|
92
|
+
email:
|
93
|
+
- rosskaff@gmail.com
|
94
|
+
executables: []
|
95
|
+
extensions: []
|
96
|
+
extra_rdoc_files: []
|
97
|
+
files:
|
98
|
+
- .gitignore
|
99
|
+
- Gemfile
|
100
|
+
- Rakefile
|
101
|
+
- app/assets/javascripts/pushkin.js
|
102
|
+
- app/assets/javascripts/pushkin/pushkin.js.coffee
|
103
|
+
- config.ru
|
104
|
+
- lib/generators/pushkin/install_generator.rb
|
105
|
+
- lib/generators/pushkin/templates/pushkin.ru
|
106
|
+
- lib/generators/pushkin/templates/pushkin.yml
|
107
|
+
- lib/pushkin.rb
|
108
|
+
- lib/pushkin/action_view/helpers.rb
|
109
|
+
- lib/pushkin/configuration.rb
|
110
|
+
- lib/pushkin/engine.rb
|
111
|
+
- lib/pushkin/faye/authentication.rb
|
112
|
+
- lib/pushkin/subscription.rb
|
113
|
+
- lib/pushkin/version.rb
|
114
|
+
- pushkin.gemspec
|
115
|
+
- spec/javascripts/pushkin_spec.js.coffee
|
116
|
+
- spec/javascripts/support/jasmine.yml
|
117
|
+
- spec/pushkin/faye/authentication_spec.rb
|
118
|
+
- spec/pushkin_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
homepage: ''
|
121
|
+
licenses: []
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
hash: -3435973028842535534
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
hash: -3435973028842535534
|
144
|
+
requirements: []
|
145
|
+
rubyforge_project: pushkin
|
146
|
+
rubygems_version: 1.8.10
|
147
|
+
signing_key:
|
148
|
+
specification_version: 3
|
149
|
+
summary: Pub/Sub for Rails with Faye
|
150
|
+
test_files:
|
151
|
+
- spec/javascripts/pushkin_spec.js.coffee
|
152
|
+
- spec/javascripts/support/jasmine.yml
|
153
|
+
- spec/pushkin/faye/authentication_spec.rb
|
154
|
+
- spec/pushkin_spec.rb
|
155
|
+
- spec/spec_helper.rb
|