danthes 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.swp
2
+ **/*.swp
3
+ *.gem
4
+ Gemfile.lock
5
+ .bundle
6
+ app/assets/javascripts/compiled
7
+ spec/coffeescripts/compiled
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@danthes --create
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
4
+ bundler_args: --without development
5
+ before_script:
6
+ - "export DISPLAY=:99.0"
7
+ - "sh -e /etc/init.d/xvfb start"
data/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # d'Anthès
2
+
3
+ ## 1.0.1 (August 26 2012)
4
+
5
+ * Generator for redis config file
6
+ * Fixes for rackup file
7
+ * Fixes for full server url
8
+ * CoffeeScript class documentation
9
+
10
+ ## 1.0 (August 25 2012)
11
+
12
+ * initial release
13
+
14
+
15
+ # PrivatePub
16
+
17
+ ## 1.0.0 (January 25, 2012)
18
+
19
+ * Rails 3.2 compatibility with SecureRandom fix (thanks windigo) - issue #26
20
+
21
+ ## 1.0.0 (January 15, 2012)
22
+
23
+ * setting config defaults to nil so everything must be set in `private_pub.yml`
24
+
25
+ * Documentation improvements
26
+
27
+
28
+ ## 0.3.0 (January 14, 2012)
29
+
30
+ * adding `PrivatePub.publish_to` method for publishing from anywhere - issue #15
31
+
32
+ * rewriting `private_pub.js` so it is framework agnostic
33
+
34
+ * Rails 3.1 compatibility (thanks BinaryMuse) - issue #25
35
+
36
+ * adding faye gem dependency so it doesn't need to be installed separately
37
+
38
+ * renaming `faye.ru` to `private_pub.ru`
39
+
40
+ * truncate token for client for security (thanks jameshuynh) - issue #19
41
+
42
+
43
+ ## 0.2.0 (April 7, 2011)
44
+
45
+ * switched to YAML file for config. BACKWARDS INCOMPATIBLE: you will need to remove config/initializers/private_pub.rb
46
+
47
+ * moved view helpers into Railtie so helper file is no longer generated
48
+
49
+ * error out when signature has expired
50
+
51
+
52
+ ## 0.1.0 (April 4, 2011)
53
+
54
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'faye-redis'
6
+ gem 'guard'
7
+ gem 'guard-coffeescript'
8
+ gem 'jasmine', '1.2.0.rc3'
9
+ gem 'rspec', '2.10.0'
10
+ gem 'rspec-mocks', '2.10.0'
11
+ gem 'webmock'
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'coffeescript', :output => 'app/assets/javascripts/compiled' do
2
+ watch /^app\/assets\/javascripts\/(.*)\.coffee/
3
+ end
4
+
5
+ guard 'coffeescript', :output => 'spec/coffeescripts/compiled' do
6
+ watch /^spec\/coffeescripts\/(.*)\.coffee/
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Alexander Simonov
2
+ Copyright (c) 2012 Ryan Bates
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # d'Anthès [![Build Status](https://secure.travis-ci.org/phenomena/danthes.png?branch=master)](http://travis-ci.org/phenomena/danthes)
2
+
3
+ d'Anthès 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. Based on PrivatePub gem.
4
+
5
+ ## Docs
6
+
7
+ [Ruby](http://rubydoc.info/github/phenomena/danthes/frames)
8
+
9
+ [CoffeeScript](https://github.com/phenomena/danthes/wiki/CoffeeScript-documentation)
10
+
11
+ ## Setup
12
+
13
+ Add the gem to your Gemfile and run the `bundle` command to install it.
14
+
15
+ ```ruby
16
+ gem 'danthes'
17
+ ```
18
+
19
+ Run the generator to create the initial files.
20
+
21
+ ```
22
+ rails g danthes:install
23
+ ```
24
+
25
+ Next, start up Faye using the rackup file that was generated.
26
+
27
+ ```
28
+ rackup danthes.ru -s thin -E production
29
+ ```
30
+
31
+ **In Rails 3.1** add the JavaScript file to your application.js file manifest.
32
+
33
+ ```javascript
34
+ //= require danthes
35
+ ```
36
+
37
+
38
+ It's not necessary to include faye's connect.js since that will be handled automatically for you.
39
+
40
+ ## Serving Faye over HTTPS (with Thin)
41
+
42
+ To serve Faye over HTTPS you could create a thin configuration file `config/private_pub_thin.yml` similar to the following:
43
+
44
+ ```yaml
45
+ ---
46
+ port: 4443
47
+ ssl: true
48
+ ssl_key_file: /path/to/server.pem
49
+ ssl_cert_file: /path/to/certificate_chain.pem
50
+ environment: production
51
+ rackup: danthes.ru
52
+ ```
53
+
54
+ The `certificate_chain.pem` file should contain your signed certificate, followed by intermediate certificates (if any) and the root certificate of the CA that signed the key.
55
+
56
+ Next reconfigure the URL in `config/danthes.yml` to look like `https://your.hostname.com:4443/faye`
57
+
58
+ Finally start up Thin from the project root.
59
+
60
+ ```
61
+ thin -C config/danthes_thin.yml start
62
+ ```
63
+
64
+ ## Serving Faye with Redis engine
65
+
66
+ Run the generator to create the initial redis config file.
67
+
68
+ ```
69
+ rails g danthes:redis_install
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ Use the `subscribe_to` helper method on any page to subscribe to a channel.
75
+
76
+ ```rhtml
77
+ <%= subscribe_to "/messages/new" %>
78
+ ```
79
+
80
+ 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).
81
+
82
+ ```rhtml
83
+ <% publish_to "/messages/new" do %>
84
+ $("#chat").append("<%= j render(@messages) %>");
85
+ <% end %>
86
+ ```
87
+
88
+ 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.
89
+
90
+
91
+ ## Alternative Usage
92
+
93
+ If you prefer to work through JSON instead of `.js.erb` templates, you can pass a hash to `publish_to` instead of a block and it will be converted `to_json` behind the scenes. This can be done anywhere (such as the controller).
94
+
95
+ ```ruby
96
+ Danthes.publish_to "/messages/new", :chat_message => "Hello, world!"
97
+ ```
98
+
99
+ And then handle this through JavaScript on the client side.
100
+
101
+ ```javascript
102
+ Danthes.subscribe("/messages/new", function(data, channel) {
103
+ $("#chat").append(data.chat_message);
104
+ });
105
+ ```
106
+
107
+ The Ruby `subscribe_to` helper 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.
108
+
109
+ ## Debugging
110
+
111
+ To enable debugging for faye connection process set `debug` to true before first `sign` call.
112
+
113
+ ``` javascript
114
+ Danthes.debug = true
115
+ ```
116
+
117
+ ## Configuration
118
+
119
+ The configuration is set separately for each environment in the generated `config/danthes.yml` file. Here are the options.
120
+
121
+ * `server`: The URL to use for the Faye server such as `http://localhost:9292`.
122
+ * `mount`: The mount path for Faye
123
+ * `secret_token`: A secret hash to secure the server. Can be any string.
124
+ * `signature_expiration`: The length of time in seconds before a subscription signature expires. If this is not set there is no expiration. Note: if Faye is on a separate server from the Rails app, the system clocks must be in sync for the expiration to work properly.
125
+ * `adapter`: Adapter for the Rack application
126
+
127
+
128
+ ## How It Works
129
+
130
+ The `subscribe_to` helper will output the following script which subscribes the user to a specific channel and server.
131
+
132
+ ```html
133
+ <script type="text/javascript">
134
+ Danthes.sign({
135
+ channel: "/messages/new",
136
+ timestamp: 1302306682972,
137
+ signature: "dc1c71d3e959ebb6f49aa6af0c86304a0740088d",
138
+ server: "http://localhost:9292/faye"
139
+ });
140
+ </script>
141
+ ```
142
+
143
+ The signature and timestamp checked on the Faye server to ensure users are only able to access channels you subscribe them to. The signature will automatically expire after the time specified in the configuration.
144
+
145
+ The `publish_to` method will send a post request to the Faye server (using `Net::HTTP`) instructing it to send the given data back to the browser.
146
+
147
+ ## Development & Feedback
148
+
149
+ Questions or comments? Please use the [issue tracker](https://github.com/phenomena/danthes/issues). Tests can be run with `bundle` and `rake` commands.
150
+
151
+ ## TODO
152
+
153
+ - Add support for faye subscribe callbacks
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+ require 'jasmine'
5
+ require 'coffee-script'
6
+ load 'jasmine/tasks/jasmine.rake'
7
+
8
+ desc "Run RSpec"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.verbose = false
11
+ end
12
+
13
+ task :default => [:compile_js, :spec, "jasmine:ci"]
14
+
15
+ def compile_coffee_script(file, path)
16
+ source = File.read File.expand_path("#{path}/#{file}.coffee", __FILE__)
17
+ compiled_path = File.expand_path("#{path}/compiled/", __FILE__)
18
+ if !(File.exists?(compiled_path) && File.directory?(compiled_path))
19
+ Dir.mkdir compiled_path
20
+ end
21
+ destination = File.open File.join(compiled_path, file), 'w+'
22
+ destination.write CoffeeScript.compile(source)
23
+ end
24
+
25
+ desc "Compile coffeescript"
26
+ task :compile_js do
27
+ compile_coffee_script('danthes.js', '../app/assets/javascripts')
28
+ compile_coffee_script('danthes_spec.js', '../spec/coffeescripts')
29
+ end
@@ -0,0 +1,142 @@
1
+ # Danthes Privat pub/sub Faye wrapper
2
+ #
3
+ # @example Howto enable debug
4
+ # Danthes.debug = true
5
+ # @example reset all internal data
6
+ # Danthes.reset()
7
+ # @example Howto sign and subscribe on channel with callback function
8
+ # Danthes.sign
9
+ # server: 'faye.example.com'
10
+ # channel: 'somechannel'
11
+ # signature: 'dc1c71d3e959ebb6f49aa6af0c86304a0740088d'
12
+ # timestamp: 1302306682972
13
+ # callback: (data) ->
14
+ # console.log(data)
15
+
16
+ window.Danthes = class Danthes
17
+
18
+ @debug: false
19
+
20
+ @debugMessage: (message) ->
21
+ console.log(message) if @debug
22
+
23
+ # Reset all
24
+ @reset: ->
25
+ @connecting = false
26
+ @fayeClient = null
27
+ @fayeCallbacks = []
28
+ @subscriptions = {}
29
+ @server = null
30
+ @disables = []
31
+ @connectionSettings =
32
+ timeout: 120
33
+ retry: 5
34
+ endpoints: {}
35
+
36
+ # Connect to faye
37
+ @faye: (callback) =>
38
+ if @fayeClient?
39
+ callback(@fayeClient)
40
+ else
41
+ @fayeCallbacks.push(callback)
42
+ if @server && !@connecting
43
+ @connecting = true
44
+ unless Faye?
45
+ script = document.createElement 'script'
46
+ script.type = 'text/javascript'
47
+ script.src = "#{@server}/client.js"
48
+ script.id = "faye-connection-script"
49
+ complete = false
50
+ script.onload = script.onreadystatechange = () =>
51
+ if !complete && (!this.readyState || this.readyState is "loaded" || this.readyState is "complete")
52
+ complete = true
53
+ script.onload = script.onreadystatechange = null
54
+ @debugMessage 'connect to faye after script loaded'
55
+ @connectToFaye()
56
+ @debugMessage 'faye script init'
57
+ document.documentElement.appendChild script
58
+ else
59
+ @debugMessage 'faye already inited'
60
+ @connectToFaye()
61
+
62
+ # Faye extension for incoming and outgoing messages
63
+ @fayeExtension:
64
+ incoming : (message, callback) =>
65
+ @debugMessage "incomming message #{JSON.stringify(message)}"
66
+ callback(message)
67
+ outgoing : (message, callback) =>
68
+ @debugMessage "outgoing message #{JSON.stringify(message)}"
69
+ if message.channel == "/meta/subscribe"
70
+ subscription = @subscriptions[message.subscription]['opts']
71
+ # Attach the signature and timestamp to subscription messages
72
+ message.ext = {} unless message.ext?
73
+ message.ext.danthes_signature = subscription.signature
74
+ message.ext.danthes_timestamp = subscription.timestamp
75
+ callback(message)
76
+
77
+ # Initialize Faye client
78
+ @connectToFaye: ->
79
+ if @server && Faye?
80
+ @debugMessage 'trying to connect faye'
81
+ @fayeClient = new Faye.Client(@server, @connectionSettings)
82
+ @fayeClient.addExtension(@fayeExtension)
83
+ # Disable any features what we want
84
+ @fayeClient.disable(key) for key in @disables
85
+ @debugMessage 'faye connected'
86
+ callback(@fayeClient) for callback in @fayeCallbacks
87
+
88
+ # Sign to channel
89
+ # @param [Object] options for signing
90
+ @sign: (options) ->
91
+ @debugMessage 'sign to faye'
92
+ @server = options.server unless @server
93
+ channel = options.channel
94
+ unless @subscriptions[channel]?
95
+ @subscriptions[channel] = {}
96
+ @subscriptions[channel]['opts'] = options
97
+ @faye (faye) =>
98
+ subscription = faye.subscribe channel, (message) =>
99
+ @handleResponse(message)
100
+ if subscription?
101
+ @subscriptions[channel]['sub'] = subscription
102
+ subscription.callback =>
103
+ @debugMessage "subscription for #{channel} is active now"
104
+ subscription.errback (error) =>
105
+ @debugMessage "error for #{channel}: #{error.message}"
106
+
107
+ # Handle response from Faye
108
+ # @param [Object] message from Faye
109
+ @handleResponse: (message) ->
110
+ if message.eval
111
+ eval(message.eval)
112
+ channel = message.channel
113
+ return unless @subscriptions[channel]?
114
+ if callback = @subscriptions[channel]['callback']
115
+ callback(message.data, channel)
116
+
117
+ # Subscribe to channel with callback
118
+ # @param channel [String] Channel name
119
+ # @param callback [Function] Callback function
120
+ @subscribe: (channel, callback) ->
121
+ @debugMessage "subscribing to #{channel}"
122
+ if @subscriptions[channel]?
123
+ # Changing callback on every call
124
+ @subscriptions[channel]['callback'] = callback
125
+ else
126
+ @debugMessage "Cannot subscribe on channel '#{channel}'. You need sign to channel first."
127
+ return false
128
+ true
129
+
130
+ # Unsubscribe from channel
131
+ # @param [String] Channel name
132
+ @unsubscribe: (channel) ->
133
+ @debugMessage "unsubscribing from #{channel}"
134
+ if @subscriptions[channel]
135
+ @subscriptions[channel]['sub'].cancel()
136
+ delete @subscriptions[channel]
137
+
138
+ # Unsubscribe from all channels
139
+ @unsubscribeAll: ->
140
+ @unsubscribe(channel) for channel, _ of @subscriptions
141
+
142
+ window.Danthes.reset()
data/danthes.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path("../lib", __FILE__)
3
+ require 'danthes/version'
4
+ Gem::Specification.new do |s|
5
+ s.name = "danthes"
6
+ s.version = Danthes::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.author = ["Alexander Simonov"]
9
+ s.email = ["alex@simonov.me"]
10
+ s.homepage = "http://github.com/phenomena/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($\)
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+ s.require_paths = ["lib"]
16
+ s.add_dependency 'faye', '>= 0.8.0'
17
+ end
@@ -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
7
+ path = Rails.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
+ raise 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,3 @@
1
+ module Danthes
2
+ VERSION = '1.0.1'
3
+ 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)
16
+ subscription = Danthes.subscription(:channel => channel)
17
+ content_tag "script", :type => "text/javascript" do
18
+ raw("if (typeof Danthes != 'undefined') { Danthes.sign(#{subscription.to_json}) }")
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/danthes.rb ADDED
@@ -0,0 +1,109 @@
1
+ require 'digest/sha1'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'yajl/json_gem'
5
+
6
+ require 'danthes/faye_extension'
7
+ require 'danthes/engine' if defined? Rails
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"] || "development"
33
+ end
34
+ end
35
+
36
+ # Loads the configuration from a given YAML file
37
+ def load_config(filename)
38
+ yaml = ::YAML.load_file(filename)[env]
39
+ raise ArgumentError, "The #{environment} environment does not exist in #{filename}" if yaml.nil?
40
+ (yaml.keys - ACCEPTED_KEYS).each {|k| yaml.delete(k)}
41
+ yaml.each {|k, v| config[k.to_sym] = v}
42
+ end
43
+
44
+ # Loads the options from a given YAML file
45
+ def load_redis_config(filename)
46
+ require 'faye/redis'
47
+ yaml = YAML.load_file(filename)[env]
48
+ # default redis options
49
+ options = {:type => Faye::Redis, :host => 'localhost', :port => 6379}
50
+ (yaml.keys - REDIS_ACCEPTED_KEYS).each {|k| yaml.delete(k)}
51
+ yaml.each {|k, v| options[k.to_sym] = v}
52
+ config[:engine] = options
53
+ end
54
+
55
+ # Publish the given data to a specific channel. This ends up sending
56
+ # a Net::HTTP POST request to the Faye server.
57
+ def publish_to(channel, data)
58
+ publish_message(message(channel, data))
59
+ end
60
+
61
+ # Sends the given message hash to the Faye server using Net::HTTP.
62
+ def publish_message(message)
63
+ raise Error, "No server specified, ensure danthes.yml was loaded properly." unless config[:server]
64
+ url = URI.parse(server_url)
65
+
66
+ form = Net::HTTP::Post.new(url.path.empty? ? '/' : url.path)
67
+ form.set_form_data(:message => message.to_json)
68
+
69
+ http = Net::HTTP.new(url.host, url.port)
70
+ http.use_ssl = url.scheme == "https"
71
+ http.start {|h| h.request(form)}
72
+ end
73
+
74
+ # Returns a message hash for sending to Faye
75
+ def message(channel, data)
76
+ message = {:channel => channel, :data => {:channel => channel}, :ext => {:danthes_token => config[:secret_token]}}
77
+ if data.kind_of? String
78
+ message[:data][:eval] = data
79
+ else
80
+ message[:data][:data] = data
81
+ end
82
+ message
83
+ end
84
+
85
+ def server_url
86
+ [config[:server], config[:mount].gsub(/^\//,'')].join('/')
87
+ end
88
+
89
+ # Returns a subscription hash to pass to the PrivatePub.sign call in JavaScript.
90
+ # Any options passed are merged to the hash.
91
+ def subscription(options = {})
92
+ sub = {:server => server_url, :timestamp => (Time.now.to_f * 1000).round}.merge(options)
93
+ sub[:signature] = Digest::SHA1.hexdigest([config[:secret_token], sub[:channel], sub[:timestamp]].join)
94
+ sub
95
+ end
96
+
97
+ # Determine if the signature has expired given a timestamp.
98
+ def signature_expired?(timestamp)
99
+ timestamp < ((Time.now.to_f - config[:signature_expiration])*1000).round if config[:signature_expiration]
100
+ end
101
+
102
+ # Returns the Faye Rack application.
103
+ def faye_app
104
+ Faye::RackAdapter.new(config)
105
+ end
106
+ end
107
+
108
+ startup
109
+ end
@@ -0,0 +1,16 @@
1
+ require 'generators/danthes'
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
+ if ::Rails.version < "3.1"
10
+ copy_file "../../../../app/assets/javascripts/danthes.js.coffee", "public/javascripts/danthes.js.coffee"
11
+ end
12
+ copy_file "danthes.ru", "danthes.ru"
13
+ end
14
+ end
15
+ end
16
+ end