pushkin 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|