oceanex-slanger 0.7.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.
- checksums.yaml +7 -0
 - data/README.md +231 -0
 - data/bin/slanger +137 -0
 - data/lib/slanger/api.rb +5 -0
 - data/lib/slanger/api/event.rb +16 -0
 - data/lib/slanger/api/event_publisher.rb +21 -0
 - data/lib/slanger/api/request_validation.rb +105 -0
 - data/lib/slanger/api/server.rb +56 -0
 - data/lib/slanger/channel.rb +104 -0
 - data/lib/slanger/config.rb +27 -0
 - data/lib/slanger/connection.rb +46 -0
 - data/lib/slanger/handler.rb +117 -0
 - data/lib/slanger/logger.rb +8 -0
 - data/lib/slanger/presence_channel.rb +140 -0
 - data/lib/slanger/presence_subscription.rb +33 -0
 - data/lib/slanger/private_subscription.rb +9 -0
 - data/lib/slanger/redis.rb +65 -0
 - data/lib/slanger/service.rb +20 -0
 - data/lib/slanger/subscription.rb +53 -0
 - data/lib/slanger/version.rb +3 -0
 - data/lib/slanger/web_socket_server.rb +38 -0
 - data/lib/slanger/webhook.rb +31 -0
 - data/slanger.rb +23 -0
 - data/spec/have_attributes.rb +65 -0
 - data/spec/integration/channel_spec.rb +113 -0
 - data/spec/integration/integration_spec.rb +67 -0
 - data/spec/integration/presence_channel_spec.rb +148 -0
 - data/spec/integration/private_channel_spec.rb +77 -0
 - data/spec/integration/replaced_handler_spec.rb +23 -0
 - data/spec/integration/ssl_spec.rb +18 -0
 - data/spec/server.crt +12 -0
 - data/spec/server.key +15 -0
 - data/spec/slanger_helper_methods.rb +107 -0
 - data/spec/spec_helper.rb +43 -0
 - data/spec/unit/channel_spec.rb +105 -0
 - data/spec/unit/request_validation_spec.rb +71 -0
 - data/spec/unit/webhook_spec.rb +42 -0
 - metadata +392 -0
 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 2 
     | 
    
         
            +
              class PresenceSubscription < Subscription
         
     | 
| 
      
 3 
     | 
    
         
            +
                def subscribe
         
     | 
| 
      
 4 
     | 
    
         
            +
                  return handle_invalid_signature if invalid_signature?
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                  unless channel_data?
         
     | 
| 
      
 7 
     | 
    
         
            +
                    return connection.error({
         
     | 
| 
      
 8 
     | 
    
         
            +
                             message: "presence-channel is a presence channel and subscription must include channel_data",
         
     | 
| 
      
 9 
     | 
    
         
            +
                           })
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  channel.subscribe(@msg, callback) { |m| send_message m }
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                private
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def channel_data?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @msg["data"]["channel_data"]
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def callback
         
     | 
| 
      
 22 
     | 
    
         
            +
                  Proc.new {
         
     | 
| 
      
 23 
     | 
    
         
            +
                    connection.send_payload(channel_id, "pusher_internal:subscription_succeeded", {
         
     | 
| 
      
 24 
     | 
    
         
            +
                      presence: {
         
     | 
| 
      
 25 
     | 
    
         
            +
                        count: channel.subscribers.size,
         
     | 
| 
      
 26 
     | 
    
         
            +
                        ids: channel.ids,
         
     | 
| 
      
 27 
     | 
    
         
            +
                        hash: channel.subscribers,
         
     | 
| 
      
 28 
     | 
    
         
            +
                      },
         
     | 
| 
      
 29 
     | 
    
         
            +
                    })
         
     | 
| 
      
 30 
     | 
    
         
            +
                  }
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Redis class.
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Interface with Redis.
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require "forwardable"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "oj"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 8 
     | 
    
         
            +
              module Redis
         
     | 
| 
      
 9 
     | 
    
         
            +
                extend Forwardable
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def_delegators :subscriber, :subscribe
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def regular_connection
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @regular_connection ||= new_connection
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def publisher
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @publisher ||= new_connection
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def send_command(method, *arg)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  regular_connection.send(method, *arg)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def hincrby(*arg)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  send_command :hincrby, *arg
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def hset(*arg)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  send_command :hset, *arg
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def hdel(*arg)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  send_command :hdel, *arg
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def hgetall(*arg)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  send_command :hgetall, *arg
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def publish(*arg)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  publish_event(:publish, *arg)
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def publish_event(method, *args)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  publisher.send(method, *args)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def subscriber
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @subscriber ||= new_connection.pubsub.tap do |c|
         
     | 
| 
      
 51 
     | 
    
         
            +
                    c.on(:message) do |channel, message|
         
     | 
| 
      
 52 
     | 
    
         
            +
                      message = Oj.strict_load(message)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      c = Channel.from message["channel"]
         
     | 
| 
      
 54 
     | 
    
         
            +
                      c.dispatch message, channel
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def new_connection
         
     | 
| 
      
 60 
     | 
    
         
            +
                  EM::Hiredis.connect Slanger::Config.redis_address
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "thin"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "rack"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Service
         
     | 
| 
      
 6 
     | 
    
         
            +
                def run
         
     | 
| 
      
 7 
     | 
    
         
            +
                  Slanger::Config[:require].each { |f| require f }
         
     | 
| 
      
 8 
     | 
    
         
            +
                  Thin::Logging.silent = true
         
     | 
| 
      
 9 
     | 
    
         
            +
                  Rack::Handler::Thin.run Slanger::Api::Server, Host: Slanger::Config.api_host, Port: Slanger::Config.api_port
         
     | 
| 
      
 10 
     | 
    
         
            +
                  Slanger::WebSocketServer.run
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 14 
     | 
    
         
            +
                  EM.stop if EM.reactor_running?
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 18 
     | 
    
         
            +
                Signal.trap("HUP") { Slanger::Service.stop }
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Subscription
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :connection, :socket
         
     | 
| 
      
 4 
     | 
    
         
            +
                delegate :send_payload, :send_message, :error, :socket_id, to: :connection
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(socket, socket_id, msg)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @connection = Connection.new socket, socket_id
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @msg = msg
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def subscribe
         
     | 
| 
      
 12 
     | 
    
         
            +
                  send_payload channel_id, "pusher_internal:subscription_succeeded"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  channel.subscribe { |m| send_message m }
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def channel
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Channel.from channel_id
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def channel_id
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @msg["data"]["channel"]
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def token(channel_id, params = nil)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  to_sign = [socket_id, channel_id, params].compact.join ":"
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  digest = OpenSSL::Digest::SHA256.new
         
     | 
| 
      
 31 
     | 
    
         
            +
                  OpenSSL::HMAC.hexdigest digest, Slanger::Config.secret, to_sign
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def invalid_signature?
         
     | 
| 
      
 35 
     | 
    
         
            +
                  token(channel_id, data) != auth.split(":")[1]
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                def auth
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @msg["data"]["auth"]
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                def data
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @msg["data"]["channel_data"]
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def handle_invalid_signature
         
     | 
| 
      
 47 
     | 
    
         
            +
                  message = "Invalid signature: Expected HMAC SHA256 hex digest of "
         
     | 
| 
      
 48 
     | 
    
         
            +
                  message << "#{socket_id}:#{channel_id}, but got #{auth}"
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  error({ message: message })
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "eventmachine"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "em-websocket"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 5 
     | 
    
         
            +
              module WebSocketServer
         
     | 
| 
      
 6 
     | 
    
         
            +
                def run
         
     | 
| 
      
 7 
     | 
    
         
            +
                  case
         
     | 
| 
      
 8 
     | 
    
         
            +
                  when EM.epoll? then EM.epoll
         
     | 
| 
      
 9 
     | 
    
         
            +
                  when EM.kqueue? then EM.kqueue
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  EM.run do
         
     | 
| 
      
 13 
     | 
    
         
            +
                    options = {
         
     | 
| 
      
 14 
     | 
    
         
            +
                      host: Slanger::Config[:websocket_host],
         
     | 
| 
      
 15 
     | 
    
         
            +
                      port: Slanger::Config[:websocket_port],
         
     | 
| 
      
 16 
     | 
    
         
            +
                      debug: Slanger::Config[:debug],
         
     | 
| 
      
 17 
     | 
    
         
            +
                      app_key: Slanger::Config[:app_key],
         
     | 
| 
      
 18 
     | 
    
         
            +
                    }
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    if Slanger::Config[:tls_options]
         
     | 
| 
      
 21 
     | 
    
         
            +
                      options.merge! secure: true,
         
     | 
| 
      
 22 
     | 
    
         
            +
                                     tls_options: Slanger::Config[:tls_options]
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    EM::WebSocket.start options do |ws|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      # Keep track of handler instance in instance of EM::Connection to ensure a unique handler instance is used per connection.
         
     | 
| 
      
 27 
     | 
    
         
            +
                      ws.class_eval { attr_accessor :connection_handler }
         
     | 
| 
      
 28 
     | 
    
         
            +
                      # Delegate connection management to handler instance.
         
     | 
| 
      
 29 
     | 
    
         
            +
                      ws.onopen { |handshake| ws.connection_handler = Slanger::Config.socket_handler.new ws, handshake }
         
     | 
| 
      
 30 
     | 
    
         
            +
                      ws.onmessage { |msg| ws.connection_handler.onmessage msg }
         
     | 
| 
      
 31 
     | 
    
         
            +
                      ws.onclose { ws.connection_handler.onclose }
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,31 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "fiber"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "em-http-request"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "oj"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Slanger
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Webhook
         
     | 
| 
      
 7 
     | 
    
         
            +
                def post(payload)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  return unless Slanger::Config.webhook_url
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  payload = {
         
     | 
| 
      
 11 
     | 
    
         
            +
                    time_ms: Time.now.strftime("%s%L"), events: [payload],
         
     | 
| 
      
 12 
     | 
    
         
            +
                  }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  payload = Oj.dump(payload, mode: :compat)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  digest = OpenSSL::Digest::SHA256.new
         
     | 
| 
      
 17 
     | 
    
         
            +
                  hmac = OpenSSL::HMAC.hexdigest(digest, Slanger::Config.secret, payload)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  content_type = "application/json"
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  EM::HttpRequest.new(Slanger::Config.webhook_url).
         
     | 
| 
      
 21 
     | 
    
         
            +
                    post(body: payload, head: {
         
     | 
| 
      
 22 
     | 
    
         
            +
                           "X-Pusher-Key" => Slanger::Config.app_key,
         
     | 
| 
      
 23 
     | 
    
         
            +
                           "X-Pusher-Signature" => hmac,
         
     | 
| 
      
 24 
     | 
    
         
            +
                           "Content-Type" => content_type,
         
     | 
| 
      
 25 
     | 
    
         
            +
                         })
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # TODO: Exponentially backed off retries for errors
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
            end
         
     | 
    
        data/slanger.rb
    ADDED
    
    | 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "eventmachine"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "em-hiredis"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "rack"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "active_support/core_ext/string"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), "lib", "slanger", "version")
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Slanger; end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            case
         
     | 
| 
      
 11 
     | 
    
         
            +
            when EM.epoll? then EM.epoll
         
     | 
| 
      
 12 
     | 
    
         
            +
            when EM.kqueue? then EM.kqueue
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
            File.tap do |f|
         
     | 
| 
      
 16 
     | 
    
         
            +
              Dir[f.expand_path(f.join(f.dirname(__FILE__), "lib", "slanger", "*.rb"))].each do |file|
         
     | 
| 
      
 17 
     | 
    
         
            +
                Slanger.autoload File.basename(file, ".rb").camelize, file
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              Dir[f.expand_path(f.join(f.dirname(__FILE__), "lib", "slanger", "api", "*.rb"))].each do |file|
         
     | 
| 
      
 21 
     | 
    
         
            +
                Slanger::Api.autoload File.basename(file, ".rb").camelize, file
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,65 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SlangerHelperMethods
         
     | 
| 
      
 2 
     | 
    
         
            +
              class HaveAttributes
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :messages, :attributes
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(attributes)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @attributes = attributes
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                CHECKS = %w(first_event last_event last_data)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def matches?(messages)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @messages = messages
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @failures = []
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  check_connection_established if attributes[:connection_established]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  check_id_present if attributes[:id_present]
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  CHECKS.each { |a| attributes[a.to_sym] ? check(a) : true }
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  @failures.empty?
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def check(message)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  send(message) == attributes[message.to_sym] or @failures << message
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def failure_message
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @failures.map { |f| "expected #{f}: to equal #{attributes[f]} but got #{send(f)}" }.join "\n"
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                #  private
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def check_connection_established
         
     | 
| 
      
 34 
     | 
    
         
            +
                  if first_event != "pusher:connection_established"
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @failures << :connection_established
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def check_id_present
         
     | 
| 
      
 40 
     | 
    
         
            +
                  if messages.first["data"]["socket_id"] == nil
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @failures << :id_present
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def first_event
         
     | 
| 
      
 46 
     | 
    
         
            +
                  messages.first["event"]
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def last_event
         
     | 
| 
      
 50 
     | 
    
         
            +
                  messages.last["event"]
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                def last_data
         
     | 
| 
      
 54 
     | 
    
         
            +
                  messages.last["data"]
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def count
         
     | 
| 
      
 58 
     | 
    
         
            +
                  messages.length
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              def have_attributes(attributes)
         
     | 
| 
      
 63 
     | 
    
         
            +
                HaveAttributes.new attributes
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,113 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #encoding: utf-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "spec_helper"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe "Integration:" do
         
     | 
| 
      
 5 
     | 
    
         
            +
              before(:each) { start_slanger }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              describe "channel" do
         
     | 
| 
      
 8 
     | 
    
         
            +
                it "pushes messages to interested websocket connections" do
         
     | 
| 
      
 9 
     | 
    
         
            +
                  messages = em_stream do |websocket, messages|
         
     | 
| 
      
 10 
     | 
    
         
            +
                    case messages.length
         
     | 
| 
      
 11 
     | 
    
         
            +
                    when 1
         
     | 
| 
      
 12 
     | 
    
         
            +
                      websocket.callback { websocket.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 13 
     | 
    
         
            +
                    when 2
         
     | 
| 
      
 14 
     | 
    
         
            +
                      Pusher["MY_CHANNEL"].trigger "an_event", some: "Mit Raben Und Wölfen"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    when 3
         
     | 
| 
      
 16 
     | 
    
         
            +
                      EM.stop
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(messages).to have_attributes connection_established: true, id_present: true,
         
     | 
| 
      
 21 
     | 
    
         
            +
                                                      last_event: "an_event", last_data: { some: "Mit Raben Und Wölfen" }.to_json
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                it "does not send message to excluded sockets" do
         
     | 
| 
      
 25 
     | 
    
         
            +
                  messages = em_stream do |websocket, messages|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    case messages.length
         
     | 
| 
      
 27 
     | 
    
         
            +
                    when 1
         
     | 
| 
      
 28 
     | 
    
         
            +
                      websocket.callback { websocket.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 29 
     | 
    
         
            +
                    when 2
         
     | 
| 
      
 30 
     | 
    
         
            +
                      socket_id = JSON.parse(messages.first["data"])["socket_id"]
         
     | 
| 
      
 31 
     | 
    
         
            +
                      Pusher["MY_CHANNEL"].trigger "not_excluded_socket_event", { some: "Mit Raben Und Wölfen" }
         
     | 
| 
      
 32 
     | 
    
         
            +
                      Pusher["MY_CHANNEL"].trigger "excluded_socket_event", { some: "Mit Raben Und Wölfen" }, socket_id
         
     | 
| 
      
 33 
     | 
    
         
            +
                    when 3
         
     | 
| 
      
 34 
     | 
    
         
            +
                      EM.stop
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  expect(messages).to have_attributes connection_established: true, id_present: true,
         
     | 
| 
      
 39 
     | 
    
         
            +
                                                      last_event: "not_excluded_socket_event", last_data: { some: "Mit Raben Und Wölfen" }.to_json
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                it "enforces one subcription per channel, per socket" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                  messages = em_stream do |websocket, messages|
         
     | 
| 
      
 44 
     | 
    
         
            +
                    case messages.length
         
     | 
| 
      
 45 
     | 
    
         
            +
                    when 1
         
     | 
| 
      
 46 
     | 
    
         
            +
                      websocket.callback { websocket.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 47 
     | 
    
         
            +
                    when 2
         
     | 
| 
      
 48 
     | 
    
         
            +
                      websocket.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    when 3
         
     | 
| 
      
 50 
     | 
    
         
            +
                      EM.stop
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  expect(messages.last).to eq({ "event" => "pusher:error", "data" => "{\"code\":null,\"message\":\"Existing subscription to MY_CHANNEL\"}" })
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                it "supports unsubscribing to channels without closing the socket" do
         
     | 
| 
      
 58 
     | 
    
         
            +
                  client2_messages = nil
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  messages = em_stream do |client, messages|
         
     | 
| 
      
 61 
     | 
    
         
            +
                    case messages.length
         
     | 
| 
      
 62 
     | 
    
         
            +
                    when 1
         
     | 
| 
      
 63 
     | 
    
         
            +
                      client.callback { client.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 64 
     | 
    
         
            +
                    when 2
         
     | 
| 
      
 65 
     | 
    
         
            +
                      client.send({ event: "pusher:unsubscribe", data: { channel: "MY_CHANNEL" } }.to_json)
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                      client2_messages = em_stream do |client2, client2_messages|
         
     | 
| 
      
 68 
     | 
    
         
            +
                        case client2_messages.length
         
     | 
| 
      
 69 
     | 
    
         
            +
                        when 1
         
     | 
| 
      
 70 
     | 
    
         
            +
                          client2.callback { client2.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 71 
     | 
    
         
            +
                        when 2
         
     | 
| 
      
 72 
     | 
    
         
            +
                          Pusher["MY_CHANNEL"].trigger "an_event", { some: "data" }
         
     | 
| 
      
 73 
     | 
    
         
            +
                          EM.next_tick { EM.stop }
         
     | 
| 
      
 74 
     | 
    
         
            +
                        end
         
     | 
| 
      
 75 
     | 
    
         
            +
                      end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  expect(messages).to have_attributes connection_established: true, id_present: true,
         
     | 
| 
      
 80 
     | 
    
         
            +
                                                      last_event: "pusher_internal:subscription_succeeded", count: 2
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                it "avoids sending duplicate events" do
         
     | 
| 
      
 84 
     | 
    
         
            +
                  client2_messages = nil
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  client1_messages = em_stream do |client1, client1_messages|
         
     | 
| 
      
 87 
     | 
    
         
            +
                    # if this is the first message to client 1 set up another connection from the same client
         
     | 
| 
      
 88 
     | 
    
         
            +
                    if client1_messages.one?
         
     | 
| 
      
 89 
     | 
    
         
            +
                      client1.callback do
         
     | 
| 
      
 90 
     | 
    
         
            +
                        client1.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json)
         
     | 
| 
      
 91 
     | 
    
         
            +
                      end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                      client2_messages = em_stream do |client2, client2_messages|
         
     | 
| 
      
 94 
     | 
    
         
            +
                        case client2_messages.length
         
     | 
| 
      
 95 
     | 
    
         
            +
                        when 1
         
     | 
| 
      
 96 
     | 
    
         
            +
                          client2.callback { client2.send({ event: "pusher:subscribe", data: { channel: "MY_CHANNEL" } }.to_json) }
         
     | 
| 
      
 97 
     | 
    
         
            +
                        when 2
         
     | 
| 
      
 98 
     | 
    
         
            +
                          socket_id = JSON.parse(client1_messages.first["data"])["socket_id"]
         
     | 
| 
      
 99 
     | 
    
         
            +
                          Pusher["MY_CHANNEL"].trigger "an_event", { some: "data" }, socket_id
         
     | 
| 
      
 100 
     | 
    
         
            +
                        when 3
         
     | 
| 
      
 101 
     | 
    
         
            +
                          EM.stop
         
     | 
| 
      
 102 
     | 
    
         
            +
                        end
         
     | 
| 
      
 103 
     | 
    
         
            +
                      end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  expect(client1_messages).to have_attributes count: 2
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  expect(client2_messages).to have_attributes last_event: "an_event",
         
     | 
| 
      
 110 
     | 
    
         
            +
                                                              last_data: { some: "data" }.to_json
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
            end
         
     |