edmond-danthes 2.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +10 -0
- data/CHANGELOG.md +84 -0
- data/Gemfile +16 -0
- data/Guardfile +7 -0
- data/LICENSE +22 -0
- data/README.md +156 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/danthes.js.coffee +174 -0
- data/defaults.reek +11 -0
- data/edmond-danthes.gemspec +30 -0
- data/lib/danthes.rb +123 -0
- data/lib/danthes/engine.rb +16 -0
- data/lib/danthes/faye_extension.rb +40 -0
- data/lib/danthes/version.rb +3 -0
- data/lib/danthes/view_helpers.rb +22 -0
- data/lib/generators/danthes/install_generator.rb +13 -0
- data/lib/generators/danthes/redis_install_generator.rb +17 -0
- data/lib/generators/danthes_generator.rb +24 -0
- data/lib/generators/templates/danthes.ru +15 -0
- data/lib/generators/templates/danthes.yml +16 -0
- data/lib/generators/templates/danthes_redis.yml +14 -0
- data/spec/coffeescripts/danthesSpec.js.coffee +211 -0
- data/spec/danthes/faye_extension_spec.rb +67 -0
- data/spec/danthes/view_helpers_spec.rb +26 -0
- data/spec/danthes_spec.rb +193 -0
- data/spec/fake_app/rails_app.rb +18 -0
- data/spec/fixtures/danthes.yml +10 -0
- data/spec/fixtures/danthes_redis.yml +6 -0
- data/spec/fixtures/danthes_with_erb.yml +10 -0
- data/spec/javascripts/support/jasmine.yml +9 -0
- data/spec/javascripts/support/jasmine_helper.rb +11 -0
- data/spec/spec_helper.rb +29 -0
- metadata +274 -0
data/defaults.reek
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
|
3
|
+
require 'danthes/version'
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = 'edmond-danthes'
|
|
6
|
+
s.version = Danthes::VERSION
|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
|
8
|
+
s.author = ['Alexander Simonov', 'Dmitry Zuev']
|
|
9
|
+
s.email = ['alex@simonov.me', 'mail@dmitryzuev.com']
|
|
10
|
+
s.homepage = 'https://github.com/dmitryzuev/edmond-danthes'
|
|
11
|
+
s.summary = 'Private pub/sub messaging through Faye.'
|
|
12
|
+
s.description = 'Private pub/sub messaging in Rails through Faye. More Faye features supported. Based on PrivatePub.'
|
|
13
|
+
s.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
|
14
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
15
|
+
s.require_paths = ['lib']
|
|
16
|
+
s.add_dependency 'faye', '>= 1.0.1'
|
|
17
|
+
s.add_dependency 'faye-redis'
|
|
18
|
+
s.add_dependency 'yajl-ruby', '>= 1.2.0'
|
|
19
|
+
|
|
20
|
+
s.add_development_dependency 'rake'
|
|
21
|
+
s.add_development_dependency 'guard'
|
|
22
|
+
s.add_development_dependency 'guard-coffeescript'
|
|
23
|
+
s.add_development_dependency 'jasmine', '>= 2.0.0'
|
|
24
|
+
s.add_development_dependency 'rspec', '>= 3.0.0'
|
|
25
|
+
s.add_development_dependency 'rspec-mocks', '>= 3.0.0'
|
|
26
|
+
s.add_development_dependency 'webmock'
|
|
27
|
+
s.add_development_dependency 'coveralls'
|
|
28
|
+
s.add_development_dependency 'therubyracer'
|
|
29
|
+
s.add_development_dependency 'rails'
|
|
30
|
+
end
|
data/lib/danthes.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'digest/sha1'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'net/https'
|
|
4
|
+
require 'yajl/json_gem'
|
|
5
|
+
require 'erb'
|
|
6
|
+
|
|
7
|
+
require 'danthes/faye_extension'
|
|
8
|
+
|
|
9
|
+
module Danthes
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_reader :config
|
|
14
|
+
attr_accessor :env
|
|
15
|
+
|
|
16
|
+
# List of accepted options in config file
|
|
17
|
+
ACCEPTED_KEYS = %w(adapter server secret_token mount signature_expiration timeout)
|
|
18
|
+
|
|
19
|
+
# List of accepted options in redis config file
|
|
20
|
+
REDIS_ACCEPTED_KEYS = %w(host port password database namespace socket)
|
|
21
|
+
|
|
22
|
+
# Default options
|
|
23
|
+
DEFAULT_OPTIONS = { mount: '/faye', timeout: 60, extensions: [FayeExtension.new] }
|
|
24
|
+
|
|
25
|
+
# Resets the configuration to the default
|
|
26
|
+
# Set environment
|
|
27
|
+
def startup
|
|
28
|
+
@config = DEFAULT_OPTIONS.dup
|
|
29
|
+
@env = if defined? ::Rails
|
|
30
|
+
::Rails.env
|
|
31
|
+
else
|
|
32
|
+
ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Loads the configuration from a given YAML file
|
|
37
|
+
def load_config(filename)
|
|
38
|
+
yaml = ::YAML.load(::ERB.new(::File.read(filename)).result)[env]
|
|
39
|
+
fail ArgumentError, "The #{env} environment does not exist in #{filename}" if yaml.nil?
|
|
40
|
+
yaml.each do |key, val|
|
|
41
|
+
config[key.to_sym] = val if ACCEPTED_KEYS.include?(key)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Loads the options from a given YAML file
|
|
46
|
+
def load_redis_config(filename)
|
|
47
|
+
require 'faye/redis'
|
|
48
|
+
yaml = ::YAML.load(::ERB.new(::File.read(filename)).result)[env]
|
|
49
|
+
# default redis options
|
|
50
|
+
options = { type: Faye::Redis, host: 'localhost', port: 6379 }
|
|
51
|
+
yaml.each do |key, val|
|
|
52
|
+
options[key.to_sym] = val if REDIS_ACCEPTED_KEYS.include?(key)
|
|
53
|
+
end
|
|
54
|
+
config[:engine] = options
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Publish the given data to a specific channel. This ends up sending
|
|
58
|
+
# a Net::HTTP POST request to the Faye server.
|
|
59
|
+
def publish_to(channel, data)
|
|
60
|
+
publish_message(message(channel, data))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Sends the given message hash to the Faye server using Net::HTTP.
|
|
64
|
+
def publish_message(message)
|
|
65
|
+
fail Error, 'No server specified, ensure danthes.yml was loaded properly.' unless config[:server]
|
|
66
|
+
url = URI.parse(server_url)
|
|
67
|
+
|
|
68
|
+
form = ::Net::HTTP::Post.new(url.path.empty? ? '/' : url.path)
|
|
69
|
+
form.set_form_data(message: message.to_json)
|
|
70
|
+
|
|
71
|
+
http = ::Net::HTTP.new(url.host, url.port)
|
|
72
|
+
http.use_ssl = url.scheme == 'https'
|
|
73
|
+
http.start { |h| h.request(form) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns a message hash for sending to Faye
|
|
77
|
+
def message(channel, data)
|
|
78
|
+
message = { channel: channel,
|
|
79
|
+
data: { channel: channel },
|
|
80
|
+
ext: { danthes_token: config[:secret_token] }
|
|
81
|
+
}
|
|
82
|
+
if data.is_a? String
|
|
83
|
+
message[:data][:eval] = data
|
|
84
|
+
else
|
|
85
|
+
message[:data][:data] = data
|
|
86
|
+
end
|
|
87
|
+
message
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def server_url
|
|
91
|
+
[config[:server], config[:mount].gsub(/^\//, '')].join('/')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns a subscription hash to pass to the PrivatePub.sign call in JavaScript.
|
|
95
|
+
# Any options passed are merged to the hash.
|
|
96
|
+
def subscription(options = {})
|
|
97
|
+
sub = { server: server_url, timestamp: (Time.now.to_f * 1000).round }.merge(options)
|
|
98
|
+
sub[:signature] = ::Digest::SHA1.hexdigest([config[:secret_token],
|
|
99
|
+
sub[:channel],
|
|
100
|
+
sub[:timestamp]].join)
|
|
101
|
+
sub
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Determine if the signature has expired given a timestamp.
|
|
105
|
+
def signature_expired?(timestamp)
|
|
106
|
+
return false unless config[:signature_expiration]
|
|
107
|
+
timestamp < ((Time.now.to_f - config[:signature_expiration]) * 1000).round
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns the Faye Rack application.
|
|
111
|
+
def faye_app
|
|
112
|
+
rack_config = {}
|
|
113
|
+
[:engine, :mount, :ping, :timeout, :extensions, :websocket_extensions ].each do |k|
|
|
114
|
+
rack_config[k] = config[k] if config[k]
|
|
115
|
+
end
|
|
116
|
+
::Faye::RackAdapter.new(rack_config)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
startup
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
require 'danthes/engine' if defined? ::Rails
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'danthes/view_helpers'
|
|
2
|
+
|
|
3
|
+
module Danthes
|
|
4
|
+
class Engine < Rails::Engine
|
|
5
|
+
# Loads the danthes.yml file if it exists.
|
|
6
|
+
initializer 'danthes.config' do |app|
|
|
7
|
+
path = app.root.join('config/danthes.yml')
|
|
8
|
+
::Danthes.load_config(path) if path.exist?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Adds the ViewHelpers into ActionView::Base
|
|
12
|
+
initializer 'danthes.view_helpers' do
|
|
13
|
+
ActionView::Base.send :include, ViewHelpers
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Danthes
|
|
2
|
+
# This class is an extension for the Faye::RackAdapter.
|
|
3
|
+
# It is used inside of Danthes.faye_app.
|
|
4
|
+
class FayeExtension
|
|
5
|
+
# Callback to handle incoming Faye messages. This authenticates both
|
|
6
|
+
# subscribe and publish calls.
|
|
7
|
+
def incoming(message, callback)
|
|
8
|
+
if message['channel'] == '/meta/subscribe'
|
|
9
|
+
authenticate_subscribe(message)
|
|
10
|
+
elsif message['channel'] !~ %r{^/meta/}
|
|
11
|
+
authenticate_publish(message)
|
|
12
|
+
end
|
|
13
|
+
callback.call(message)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Ensure the subscription signature is correct and that it has not expired.
|
|
19
|
+
def authenticate_subscribe(message)
|
|
20
|
+
subscription = Danthes.subscription(channel: message['subscription'],
|
|
21
|
+
timestamp: message['ext']['danthes_timestamp'])
|
|
22
|
+
if message['ext']['danthes_signature'] != subscription[:signature]
|
|
23
|
+
message['error'] = 'Incorrect signature.'
|
|
24
|
+
elsif Danthes.signature_expired? message['ext']['danthes_timestamp'].to_i
|
|
25
|
+
message['error'] = 'Signature has expired.'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Ensures the secret token is correct before publishing.
|
|
30
|
+
def authenticate_publish(message)
|
|
31
|
+
if Danthes.config[:secret_token].nil?
|
|
32
|
+
fail Error, 'No secret_token config set, ensure danthes.yml is loaded properly.'
|
|
33
|
+
elsif message['ext']['danthes_token'] != Danthes.config[:secret_token]
|
|
34
|
+
message['error'] = 'Incorrect token.'
|
|
35
|
+
else
|
|
36
|
+
message['ext']['danthes_token'] = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Danthes
|
|
2
|
+
module ViewHelpers
|
|
3
|
+
# Publish the given data or block to the client by sending
|
|
4
|
+
# a Net::HTTP POST request to the Faye server. If a block
|
|
5
|
+
# or string is passed in, it is evaluated as JavaScript
|
|
6
|
+
# on the client. Otherwise it will be converted to JSON
|
|
7
|
+
# for use in a JavaScript callback.
|
|
8
|
+
def publish_to(channel, data = nil, &block)
|
|
9
|
+
Danthes.publish_to(channel, data || capture(&block))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Subscribe the client to the given channel. This generates
|
|
13
|
+
# some JavaScript calling Danthes.sign with the subscription
|
|
14
|
+
# options.
|
|
15
|
+
def subscribe_to(channel, opts = {})
|
|
16
|
+
js_tag = opts.delete(:include_js_tag){ true }
|
|
17
|
+
subscription = Danthes.subscription(channel: channel)
|
|
18
|
+
content = raw("if (typeof Danthes != 'undefined') { Danthes.sign(#{subscription.to_json}) }")
|
|
19
|
+
js_tag ? content_tag('script', content, type: 'text/javascript') : content
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'generators/danthes_generator'
|
|
2
|
+
|
|
3
|
+
module Danthes
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Base
|
|
6
|
+
desc 'Create sample config file and add rackup file'
|
|
7
|
+
def copy_files
|
|
8
|
+
template 'danthes.yml', 'config/danthes.yml'
|
|
9
|
+
copy_file 'danthes.ru', 'danthes.ru'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'generators/danthes_generator'
|
|
2
|
+
|
|
3
|
+
module Danthes
|
|
4
|
+
module Generators
|
|
5
|
+
class RedisInstallGenerator < Base
|
|
6
|
+
desc 'Create sample redis config file and add faye-redis gem to Gemfile'
|
|
7
|
+
|
|
8
|
+
def copy_files
|
|
9
|
+
template 'danthes_redis.yml', 'config/danthes_redis.yml'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add_redis_gem
|
|
13
|
+
add_gem 'faye-redis'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require 'rails/generators/base'
|
|
2
|
+
|
|
3
|
+
module Danthes
|
|
4
|
+
module Generators
|
|
5
|
+
class Base < Rails::Generators::Base
|
|
6
|
+
def self.source_root
|
|
7
|
+
File.dirname(__FILE__) + '/templates'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.banner
|
|
11
|
+
"rails generate danthes:#{generator_name}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def add_gem(name, options = {})
|
|
17
|
+
gemfile_path = File.join(destination_root, 'Gemfile')
|
|
18
|
+
gemfile_content = File.read(gemfile_path)
|
|
19
|
+
File.open(gemfile_path, 'a') { |f| f.write("\n") } unless gemfile_content =~ /\n\Z/
|
|
20
|
+
gem name, options unless gemfile_content.include? name
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Run with: rackup danthes.ru -s thin -E production
|
|
2
|
+
require "bundler/setup"
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "faye"
|
|
5
|
+
require "danthes"
|
|
6
|
+
|
|
7
|
+
::Danthes.load_config(File.expand_path("../config/danthes.yml", __FILE__))
|
|
8
|
+
Faye::WebSocket.load_adapter(Danthes.config[:adapter])
|
|
9
|
+
|
|
10
|
+
path = File.expand_path("../config/danthes_redis.yml", __FILE__)
|
|
11
|
+
if File.exist?(path)
|
|
12
|
+
::Danthes.load_redis_config(path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
run ::Danthes.faye_app
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
development:
|
|
2
|
+
adapter: thin
|
|
3
|
+
server: "http://localhost:9292"
|
|
4
|
+
secret_token: "secret"
|
|
5
|
+
mount: '/faye'
|
|
6
|
+
test:
|
|
7
|
+
adapter: thin
|
|
8
|
+
server: "http://localhost:9292"
|
|
9
|
+
secret_token: "secret"
|
|
10
|
+
mount: '/faye'
|
|
11
|
+
production:
|
|
12
|
+
adapter: thin
|
|
13
|
+
server: "http://faye.example.com"
|
|
14
|
+
mount: '/faye'
|
|
15
|
+
secret_token: "<%= defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32) %>"
|
|
16
|
+
signature_expiration: 3600 # one hour
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
development:
|
|
2
|
+
host: localhost
|
|
3
|
+
port: 6379
|
|
4
|
+
namespace: '/danthes'
|
|
5
|
+
test:
|
|
6
|
+
host: localhost
|
|
7
|
+
port: 6379
|
|
8
|
+
namespace: '/danthes'
|
|
9
|
+
production:
|
|
10
|
+
host: redis_host
|
|
11
|
+
port: redis_port
|
|
12
|
+
password: redis_password
|
|
13
|
+
database: redis_database
|
|
14
|
+
namespace: '/namespace'
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
describe "Danthes", ->
|
|
2
|
+
window.Faye = undefined
|
|
3
|
+
pub = undefined
|
|
4
|
+
|
|
5
|
+
signToChannel = (channel, addOptions = {}) ->
|
|
6
|
+
sub = {callback: jasmine.createSpy(), errback: jasmine.createSpy()}
|
|
7
|
+
faye = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
8
|
+
spyOn(pub, 'faye').and.callFake (callback) ->
|
|
9
|
+
callback(faye)
|
|
10
|
+
options = {server: "server", channel: "#{channel}", timestamp: 1234567890, signature: '1234567890'}
|
|
11
|
+
options['connect'] = addOptions['connect'] if addOptions['connect']?
|
|
12
|
+
options['error'] = addOptions['error'] if addOptions['error']?
|
|
13
|
+
pub.sign(options)
|
|
14
|
+
return [faye, options]
|
|
15
|
+
|
|
16
|
+
beforeEach ->
|
|
17
|
+
pub = window.Danthes
|
|
18
|
+
pub.reset()
|
|
19
|
+
script = document.getElementById('faye-connection-script')
|
|
20
|
+
if script?
|
|
21
|
+
script.parentNode.removeChild(script)
|
|
22
|
+
|
|
23
|
+
it "not adds a subscription callback without signing", ->
|
|
24
|
+
expect(pub.subscribe("hello", "callback")).toBe(false)
|
|
25
|
+
expect(pub.subscriptions).toEqual({})
|
|
26
|
+
|
|
27
|
+
it "adds a subscription callback", ->
|
|
28
|
+
signToChannel('hello')
|
|
29
|
+
pub.subscribe("hello", "callback")
|
|
30
|
+
expect(pub.subscriptions["hello"]['callback']).toEqual("callback")
|
|
31
|
+
|
|
32
|
+
it "has a fayeExtension which adds matching subscription signature and timestamp to outgoing message", ->
|
|
33
|
+
called = false
|
|
34
|
+
message = {channel: "/meta/subscribe", subscription: "hello"}
|
|
35
|
+
pub.subscriptions['hello'] = {}
|
|
36
|
+
pub.subscriptions['hello']['opts'] = {signature: "abcd", timestamp: "1234"}
|
|
37
|
+
pub.fayeExtension.outgoing message, (message) ->
|
|
38
|
+
expect(message.ext.danthes_signature).toEqual("abcd")
|
|
39
|
+
expect(message.ext.danthes_timestamp).toEqual("1234")
|
|
40
|
+
called = true
|
|
41
|
+
expect(called).toBeTruthy()
|
|
42
|
+
|
|
43
|
+
it "evaluates javascript in message response", ->
|
|
44
|
+
pub.handleResponse(eval: 'this.subscriptions.foo = "bar"')
|
|
45
|
+
expect(pub.subscriptions.foo).toEqual("bar")
|
|
46
|
+
|
|
47
|
+
it "triggers callback matching message channel in response", ->
|
|
48
|
+
called = false
|
|
49
|
+
signToChannel('test')
|
|
50
|
+
pub.subscribe "test", (data, channel) ->
|
|
51
|
+
expect(data).toEqual("abcd")
|
|
52
|
+
expect(channel).toEqual("test")
|
|
53
|
+
called = true
|
|
54
|
+
pub.handleResponse(channel: "test", data: "abcd")
|
|
55
|
+
expect(called).toBeTruthy()
|
|
56
|
+
|
|
57
|
+
it "triggers faye callback function immediately when fayeClient is available", ->
|
|
58
|
+
called = false
|
|
59
|
+
pub.fayeClient = "faye"
|
|
60
|
+
pub.faye (faye) ->
|
|
61
|
+
expect(faye).toEqual("faye")
|
|
62
|
+
called = true
|
|
63
|
+
expect(called).toBeTruthy()
|
|
64
|
+
|
|
65
|
+
it "adds fayeCallback when client and server aren't available", ->
|
|
66
|
+
pub.faye("callback")
|
|
67
|
+
expect(pub.fayeCallbacks[0]).toEqual("callback")
|
|
68
|
+
|
|
69
|
+
it "adds a script tag loading faye js when the server is present", ->
|
|
70
|
+
client = {addExtension: jasmine.createSpy()}
|
|
71
|
+
callback = jasmine.createSpy()
|
|
72
|
+
pub.server = "path/to/faye"
|
|
73
|
+
pub.faye(callback)
|
|
74
|
+
expect(pub.fayeCallbacks[0]).toEqual(callback)
|
|
75
|
+
script = document.getElementById('faye-connection-script')
|
|
76
|
+
expect(script).toBeDefined()
|
|
77
|
+
expect(script.type).toEqual("text/javascript")
|
|
78
|
+
expect(script.src).toMatch("path/to/faye/client.js")
|
|
79
|
+
|
|
80
|
+
it "adds a signed channel to subscribe later", ->
|
|
81
|
+
pub.fayeClient = 'string'
|
|
82
|
+
[faye, options] = signToChannel('somechannel')
|
|
83
|
+
expect(faye.subscribe).not.toHaveBeenCalled()
|
|
84
|
+
expect(pub.subscriptions.somechannel.activated).toBeUndefined()
|
|
85
|
+
expect(pub.subscriptions.somechannel).toBeDefined()
|
|
86
|
+
expect(pub.subscriptions.somechannel.opts.signature).toEqual('1234567890')
|
|
87
|
+
expect(pub.subscriptions.somechannel.opts.timestamp).toEqual(1234567890)
|
|
88
|
+
expect(pub.subscriptions.somechannel.activated).toBeUndefined()
|
|
89
|
+
|
|
90
|
+
it "adds a faye subscription with response handler when sign with connect option", ->
|
|
91
|
+
pub.fayeClient = 'string'
|
|
92
|
+
[faye, options] = signToChannel('somechannel', {'connect': jasmine.createSpy()})
|
|
93
|
+
expect(faye.subscribe).toHaveBeenCalled()
|
|
94
|
+
expect(pub.subscriptions.somechannel.activated).toBeDefined()
|
|
95
|
+
|
|
96
|
+
it "adds a faye subscription with response handler when sign with error option", ->
|
|
97
|
+
pub.fayeClient = 'string'
|
|
98
|
+
[faye, options] = signToChannel('somechannel', {'error': jasmine.createSpy()})
|
|
99
|
+
expect(faye.subscribe).toHaveBeenCalled()
|
|
100
|
+
expect(pub.subscriptions.somechannel.activated).toBeDefined()
|
|
101
|
+
|
|
102
|
+
it "adds a faye subscription with response handler when first time subscribing", ->
|
|
103
|
+
pub.fayeClient = 'string'
|
|
104
|
+
[faye, options] = signToChannel('somechannel')
|
|
105
|
+
pub.subscribe('somechannel', ->)
|
|
106
|
+
expect(faye.subscribe).toHaveBeenCalled()
|
|
107
|
+
expect(pub.subscriptions.somechannel.activated).toBeDefined()
|
|
108
|
+
|
|
109
|
+
it "connects to faye server, adds extension, and executes callbacks", ->
|
|
110
|
+
callback = jasmine.createSpy()
|
|
111
|
+
client = {addExtension: jasmine.createSpy()}
|
|
112
|
+
window.Faye = {}
|
|
113
|
+
window.Faye.Client = (server) ->
|
|
114
|
+
expect(server).toEqual("server")
|
|
115
|
+
return client
|
|
116
|
+
pub.server = "server"
|
|
117
|
+
pub.fayeCallbacks.push(callback)
|
|
118
|
+
spyOn(pub, 'fayeExtension')
|
|
119
|
+
pub.connectToFaye()
|
|
120
|
+
expect(pub.fayeClient).toEqual(client)
|
|
121
|
+
expect(client.addExtension).toHaveBeenCalledWith(pub.fayeExtension)
|
|
122
|
+
expect(callback).toHaveBeenCalledWith(client)
|
|
123
|
+
|
|
124
|
+
it "adds transport to disables", ->
|
|
125
|
+
expect(pub.disableTransport('websocket')).toBeTruthy()
|
|
126
|
+
expect(pub.disables).toEqual(['websocket'])
|
|
127
|
+
|
|
128
|
+
it "adds transport to disables only one time", ->
|
|
129
|
+
pub.disableTransport('websocket')
|
|
130
|
+
pub.disableTransport('websocket')
|
|
131
|
+
expect(pub.disables).toEqual(['websocket'])
|
|
132
|
+
|
|
133
|
+
it "returns false if not accepted transport wants to be disabled", ->
|
|
134
|
+
expect(pub.disableTransport('websocket123')).toBeUndefined()
|
|
135
|
+
expect(pub.disables).toEqual([])
|
|
136
|
+
|
|
137
|
+
it "connects to faye server, and executes disable once", ->
|
|
138
|
+
callback = jasmine.createSpy()
|
|
139
|
+
client = {addExtension: jasmine.createSpy(), disable: jasmine.createSpy()}
|
|
140
|
+
window.Faye = {}
|
|
141
|
+
window.Faye.Client = (server) -> client
|
|
142
|
+
pub.server = "server"
|
|
143
|
+
pub.disableTransport('websocket')
|
|
144
|
+
pub.connectToFaye()
|
|
145
|
+
expect(client.disable).toHaveBeenCalledWith('websocket')
|
|
146
|
+
|
|
147
|
+
it "connects to faye server, and executes disable once", ->
|
|
148
|
+
callback = jasmine.createSpy()
|
|
149
|
+
client = {addExtension: jasmine.createSpy(), disable: jasmine.createSpy()}
|
|
150
|
+
window.Faye = {}
|
|
151
|
+
window.Faye.Client = (server) -> client
|
|
152
|
+
pub.server = "server"
|
|
153
|
+
pub.disableTransport('websocket')
|
|
154
|
+
pub.disableTransport('long-polling')
|
|
155
|
+
pub.connectToFaye()
|
|
156
|
+
expect(client.disable).toHaveBeenCalledWith('websocket')
|
|
157
|
+
expect(client.disable).toHaveBeenCalledWith('long-polling')
|
|
158
|
+
|
|
159
|
+
it "adds subscription faye object into channel object", ->
|
|
160
|
+
sub = {callback: jasmine.createSpy(), errback: jasmine.createSpy()}
|
|
161
|
+
pub.fayeClient = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
162
|
+
options = {server: "server", channel: 'somechannel'}
|
|
163
|
+
pub.sign(options)
|
|
164
|
+
pub.subscribe("somechannel", jasmine.createSpy())
|
|
165
|
+
expect(sub.callback).toHaveBeenCalled()
|
|
166
|
+
expect(sub.errback).toHaveBeenCalled()
|
|
167
|
+
expect(pub.subscriptions.somechannel.sub).toEqual(sub)
|
|
168
|
+
|
|
169
|
+
it "adds subscription faye object into channel object and call connect callback after connection", ->
|
|
170
|
+
sub =
|
|
171
|
+
callback: (f) ->
|
|
172
|
+
f()
|
|
173
|
+
errback: jasmine.createSpy()
|
|
174
|
+
pub.fayeClient = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
175
|
+
options = {server: "server", channel: 'somechannel'}
|
|
176
|
+
pub.sign(options)
|
|
177
|
+
connectSpy = jasmine.createSpy()
|
|
178
|
+
pub.subscribe('somechannel', jasmine.createSpy(), connect: connectSpy)
|
|
179
|
+
expect(connectSpy).toHaveBeenCalledWith(sub)
|
|
180
|
+
|
|
181
|
+
it "adds subscription faye object into channel object and call error callback after connection", ->
|
|
182
|
+
sub =
|
|
183
|
+
callback: jasmine.createSpy()
|
|
184
|
+
errback: (f) ->
|
|
185
|
+
f('error')
|
|
186
|
+
pub.fayeClient = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
187
|
+
erroSpy = jasmine.createSpy()
|
|
188
|
+
options = {server: "server", channel: 'somechannel'}
|
|
189
|
+
pub.sign(options)
|
|
190
|
+
pub.subscribe("somechannel", jasmine.createSpy(), error: erroSpy)
|
|
191
|
+
expect(erroSpy).toHaveBeenCalledWith(sub, 'error')
|
|
192
|
+
|
|
193
|
+
it "removes subscription to the channel", ->
|
|
194
|
+
sub = {callback: jasmine.createSpy(), errback: jasmine.createSpy(), cancel: jasmine.createSpy()}
|
|
195
|
+
pub.fayeClient = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
196
|
+
options = {server: "server", channel: 'somechannel'}
|
|
197
|
+
pub.sign(options)
|
|
198
|
+
pub.subscribe('somechannel', jasmine.createSpy())
|
|
199
|
+
pub.unsubscribe('somechannel')
|
|
200
|
+
expect(sub.cancel).toHaveBeenCalled()
|
|
201
|
+
expect(pub.subscriptions.somechannel.sub).toBeUndefined()
|
|
202
|
+
|
|
203
|
+
it "removes all subscription to the channels", ->
|
|
204
|
+
sub = {callback: jasmine.createSpy(), errback: jasmine.createSpy(), cancel: jasmine.createSpy()}
|
|
205
|
+
pub.fayeClient = {subscribe: jasmine.createSpy().and.returnValue(sub)}
|
|
206
|
+
options = {server: "server", channel: 'somechannel'}
|
|
207
|
+
pub.sign(options)
|
|
208
|
+
pub.subscribe "somechannel", jasmine.createSpy()
|
|
209
|
+
pub.unsubscribeAll()
|
|
210
|
+
expect(sub.cancel).toHaveBeenCalled()
|
|
211
|
+
expect(pub.subscriptions.somechannel.sub).toBeUndefined()
|