class Sockeye::Server

Attributes

authentication_method[RW]
connection_map[RW]
connections[RW]
host[RW]
port[RW]
secret_token[RW]

Public Class Methods

new(host:, port:, secret_token:, authentication_method: nil) click to toggle source
# File lib/sockeye/server.rb, line 10
def initialize(host:, port:, secret_token:, authentication_method: nil)
  self.connections = {}
  self.connection_map = {}
  self.host = host
  self.port = port
  self.secret_token = secret_token
  self.authentication_method = authentication_method
end

Public Instance Methods

add_connection(identifier:, connection:) click to toggle source

Add a connection to the list and add a map entry to link the connection object with an authenticated identifier

# File lib/sockeye/server.rb, line 38
def add_connection(identifier:, connection:)
  connections[identifier] = [] if connections[identifier].nil?
  connections[identifier] << connection
  connection_map[connection.object_id] = identifier
end
authenticate(token) click to toggle source

Call the supplied authentication method

# File lib/sockeye/server.rb, line 31
def authenticate(token)
  return self.authentication_method.call(token)
end
deliver_to_many(payload:, identifiers:) click to toggle source

Find all open connections associated with the specified identifiers then attempt to push the payload to each of them

# File lib/sockeye/server.rb, line 58
def deliver_to_many(payload:, identifiers:)
  identifiers.each do |identifier|
    identified_connections = connections[identifier]
    next unless identified_connections.is_a? Array
    identified_connections.each do |connection|
      begin
        connection.send({payload: payload, status: payload.dig(:status) || 200}.to_json, :type => :text)
      rescue
      end
    end
  end
end
json_try_parse(data) click to toggle source

Safely parse data as JSON, but return nil values on failure

# File lib/sockeye/server.rb, line 21
def json_try_parse(data)
  begin
    return JSON.parse(data, symbolize_names: true)
  rescue JSON::ParserError => e
    return nil
  end
end
listen() click to toggle source

Main server connection listener loop. Uses an EventMachine and websocket server to handle and abstract raw connections. Handles authentication and delivery actions for clients and pushers.

# File lib/sockeye/server.rb, line 75
def listen
  EM.run do
    WebSocket::EventMachine::Server.start(host: self.host, port: self.port) do |ws|

      # Called when a new message arrives at the server
      #
      ws.onmessage do |message, type|

        # Attempt to parse the received data as JSON
        #
        message_json = json_try_parse(message)
        if message_json.nil?
          ws.send({payload: "invalid message", status: 400}.to_json, :type => :text)
          ws.close
        else

          # Execute the appropriate action based on JSON action
          #
          case message_json[:action].to_sym

          # Handle authentication requests by calling the authentication
          # method supplied on server setup
          #
          when :authenticate
            authentication_result = authenticate(message_json[:payload])
            if authentication_result
              add_connection(identifier: authentication_result, connection: ws)
              ws.send({payload: "authenticated", status: 200}.to_json, :type => :text)
            else
              ws.send({payload: "authentication failure", status: 401}.to_json, :type => :text)
              ws.close
            end

          # Handle delivery requests by verifying the auth token supplied
          # then push out the payload to all connected specified clients
          #
          when :deliver
            if message_json[:secret_token] == self.secret_token
              deliver_to_many(payload: message_json[:payload], identifiers: [message_json[:identifiers]].flatten)
              ws.send({payload: "payload pushed", status: 201}.to_json, :type => :text)
              ws.close
            else
              ws.send({payload: "authentication failure", status: 401}.to_json, :type => :text)
              ws.close
            end

          else
            ws.send({payload: "invalid action", status: 405}.to_json, :type => :text)
            ws.close
          end

        end
      end

      # Cleanup connection lists when a connection is closed
      #
      ws.onclose do
        remove_connection(ws)
      end

    end
  end
end
remove_connection(connection) click to toggle source

Safely remove the specified connection from the connections lists

# File lib/sockeye/server.rb, line 46
def remove_connection(connection)
  identifier = connection_map[connection.object_id]
  if connections[identifier].is_a? Array
    connections[identifier].delete(connection)
    connections.delete(identifier) if connections[identifier].empty?
  end
  connection_map.delete(connection.object_id)
end