dotpromo/danthes

View on GitHub
lib/danthes.rb

Summary

Maintainability
A
0 mins
Test Coverage
require 'digest/sha1'
require 'net/http'
require 'net/https'
require 'yajl/json_gem'
require 'erb'

require 'danthes/faye_extension'

module Danthes
  class Error < StandardError; end

  class << self
    attr_reader :config
    attr_accessor :env

    # List of accepted options in config file
    ACCEPTED_KEYS = %w(adapter server secret_token mount signature_expiration timeout)

    # List of accepted options in redis config file
    REDIS_ACCEPTED_KEYS = %w(host port password database namespace socket)

    # Default options
    DEFAULT_OPTIONS = { mount: '/faye', timeout: 60, extensions: [FayeExtension.new] }

    # Resets the configuration to the default
    # Set environment
    def startup
      @config = DEFAULT_OPTIONS.dup
      @env = if defined? ::Rails
               ::Rails.env
             else
               ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
             end
    end

    # Loads the configuration from a given YAML file
    def load_config(filename)
      yaml = ::YAML.load(::ERB.new(::File.read(filename)).result)[env]
      fail ArgumentError, "The #{env} environment does not exist in #{filename}" if yaml.nil?
      yaml.each do |key, val|
        config[key.to_sym] = val if ACCEPTED_KEYS.include?(key)
      end
    end

    # Loads the options from a given YAML file
    def load_redis_config(filename)
      require 'faye/redis'
      yaml = ::YAML.load(::ERB.new(::File.read(filename)).result)[env]
      # default redis options
      options = { type: Faye::Redis, host: 'localhost', port: 6379 }
      yaml.each do |key, val|
        options[key.to_sym] = val if REDIS_ACCEPTED_KEYS.include?(key)
      end
      config[:engine] = options
    end

    # Publish the given data to a specific channel. This ends up sending
    # a Net::HTTP POST request to the Faye server.
    def publish_to(channel, data)
      publish_message(message(channel, data))
    end

    # Sends the given message hash to the Faye server using Net::HTTP.
    def publish_message(message)
      fail Error, 'No server specified, ensure danthes.yml was loaded properly.' unless config[:server]
      url = URI.parse(server_url)

      form = ::Net::HTTP::Post.new(url.path.empty? ? '/' : url.path)
      form.set_form_data(message: message.to_json)

      http = ::Net::HTTP.new(url.host, url.port)
      http.use_ssl = url.scheme == 'https'
      http.start { |h| h.request(form) }
    end

    # Returns a message hash for sending to Faye
    def message(channel, data)
      message = { channel: channel,
                  data: { channel: channel },
                  ext: { danthes_token: config[:secret_token] }
                }
      if data.is_a? String
        message[:data][:eval] = data
      else
        message[:data][:data] = data
      end
      message
    end

    def server_url
      [config[:server], config[:mount].gsub(/^\//, '')].join('/')
    end

    # Returns a subscription hash to pass to the PrivatePub.sign call in JavaScript.
    # Any options passed are merged to the hash.
    def subscription(options = {})
      sub = { server: server_url, timestamp: (Time.now.to_f * 1000).round }.merge(options)
      sub[:signature] = ::Digest::SHA1.hexdigest([config[:secret_token],
                                                sub[:channel],
                                                sub[:timestamp]].join)
      sub
    end

    # Determine if the signature has expired given a timestamp.
    def signature_expired?(timestamp)
      return false unless config[:signature_expiration]
      timestamp < ((Time.now.to_f - config[:signature_expiration]) * 1000).round
    end

    # Returns the Faye Rack application.
    def faye_app
      rack_config = {}
      [:engine, :mount, :ping, :timeout, :extensions, :websocket_extensions ].each do |k|
        rack_config[k] = config[k] if config[k]
      end
      ::Faye::RackAdapter.new(rack_config)
    end
  end

  startup
end

require 'danthes/engine' if defined? ::Rails