socky 0.2.1 → 0.4.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.
@@ -1,99 +0,0 @@
1
- module Socky
2
- class Connection
3
- # authentication module - included in Socky::Connection
4
- module Authentication
5
- include Socky::Misc
6
-
7
- # check if user is valid and then send him authentication data and add to pool
8
- # if not then user is given failure response(so client javascript
9
- # will know that is should not reconnect again) and then is disconnected
10
- # admin user is automaticaly authenticated but isn't added to pool
11
- # he will be authenticated when he will try to send message
12
- # thanks to that admin don't need to wait for authentication confirmation
13
- # on every connection so it will fasten things for him
14
- def subscribe_request
15
- send_subscribe_request do |response|
16
- if response
17
- debug [self.name, "authentication successed"]
18
- add_to_pool
19
- EventMachine.add_timer(0.1) do
20
- send_authentication("success")
21
- end
22
- @authenticated_by_url = true
23
- else
24
- debug [self.name, "authentication failed"]
25
- EventMachine.add_timer(0.1) do
26
- send_authentication("failure")
27
- disconnect
28
- end
29
- end
30
- end unless admin || authenticated?
31
- end
32
-
33
- # if user is authenticated then he is removed from pool and
34
- # unsubscribe notification is sent to server unless he is admin
35
- # if user is not authenticated then nothing will happen
36
- def unsubscribe_request
37
- if authenticated?
38
- remove_from_pool
39
- send_unsubscribe_request{} unless admin
40
- end
41
- end
42
-
43
- # if user is admin then his secred is compared with server secred
44
- # in user isn't admin then it checks if user is authenticated by
45
- # server request(defaults to true if subscribe_url is nil)
46
- def authenticated?
47
- @authenticated ||= (admin ? authenticate_as_admin : authenticate_as_user)
48
- end
49
-
50
- private
51
-
52
- def send_authentication(msg)
53
- send_data({:type => :authentication, :body => msg})
54
- end
55
-
56
- def authenticate_as_admin
57
- options[:secret].nil? || secret == options[:secret]
58
- end
59
-
60
- def authenticate_as_user
61
- authenticated_by_url?
62
- end
63
-
64
- def authenticated_by_url?
65
- @authenticated_by_url
66
- end
67
-
68
- def send_subscribe_request(&block)
69
- subscribe_url = options[:subscribe_url]
70
- if subscribe_url
71
- debug [self.name, "sending subscribe request to", subscribe_url]
72
- Socky::NetRequest.post(subscribe_url, params_for_request, &block)
73
- true
74
- else
75
- yield true
76
- end
77
- end
78
-
79
- def send_unsubscribe_request(&block)
80
- unsubscribe_url = options[:unsubscribe_url]
81
- if unsubscribe_url
82
- debug [self.name, "sending unsubscribe request to", unsubscribe_url]
83
- Socky::NetRequest.post(unsubscribe_url, params_for_request, &block)
84
- else
85
- yield true
86
- end
87
- end
88
-
89
- def params_for_request
90
- {
91
- :client_id => client,
92
- :client_secret => secret,
93
- :channels => channels
94
- }.reject{|key,value| value.nil? || value.empty?}
95
- end
96
-
97
- end
98
- end
99
- end
@@ -1,67 +0,0 @@
1
- module Socky
2
- class Connection
3
- # finders module - extends Socky::Connection
4
- module Finders
5
-
6
- # Return list of all connections
7
- def find_all
8
- Socky::Connection.connections
9
- end
10
-
11
- # Return filtered list of connections
12
- # @param [Hash] opts the options for filters.
13
- # @option opts [Hash] :to ({}) return only listed clients/channels. keys supported: clients, channels
14
- # @option opts [Hash] :except ({}) return all clients/channels except listed. keys supported: clients, channels
15
- # @return [Array] list of connections
16
- # @example return all connections
17
- # Socky::Connection.find
18
- # @example return no connections
19
- # # empty array as param means "no channels"
20
- # # nil is handles as "ignore param" so all clients/channels will be executed
21
- # Socky::Connection.find(:to => { :clients => [] })
22
- # Socky::Connection.find(:to => { :channels => [] })
23
- # @example return connections of users "first" and "second" from channels "some_channel"
24
- # Socky::Connection.find(:to => { :clients => ["first","second"], :channels => "some_channel" })
25
- # @example return all connections from channel "some_channel" except of ones belonging to "first"
26
- # Socky::Connection.find(:to => { :channels => "some_channel" }, :except => { :clients => "first" })
27
- def find(opts = {})
28
- to = symbolize_keys(opts[:to]) || {}
29
- exclude = symbolize_keys(opts[:except]) || {}
30
-
31
- connections = find_all
32
- connections = filter_by_clients(connections, to[:clients], exclude[:clients])
33
- connections = filter_by_channels(connections, to[:channels], exclude[:channels])
34
-
35
- connections
36
- end
37
-
38
- private
39
-
40
- def filter_by_clients(connections, included_clients = nil, excluded_clients = nil)
41
- # Empty table means "no users" - nil means "all users"
42
- return [] if (included_clients.is_a?(Array) && included_clients.empty?)
43
-
44
- included_clients = Array(included_clients)
45
- excluded_clients = Array(excluded_clients)
46
-
47
- connections.collect do |connection|
48
- connection if (included_clients.empty? || included_clients.include?(connection.client)) && !excluded_clients.include?(connection.client)
49
- end.compact
50
- end
51
-
52
- def filter_by_channels(connections, included_channels = nil, excluded_channels = nil)
53
- # Empty table means "no channels" - nil means "all channels"
54
- return [] if (included_channels.is_a?(Array) && included_channels.empty?)
55
-
56
- included_channels = Array(included_channels)
57
- excluded_channels = Array(excluded_channels)
58
-
59
- connections.collect do |connection|
60
- connection if connection.channels.any? do |channel|
61
- (included_channels.empty? || included_channels.include?(channel) ) && !excluded_channels.include?(channel)
62
- end
63
- end.compact
64
- end
65
- end
66
- end
67
- end
@@ -1,85 +0,0 @@
1
- require 'json'
2
-
3
- module Socky
4
- # every message from admin is stored as instance of Message
5
- # and then processed by #process method
6
- class Message
7
- include Socky::Misc
8
-
9
- class InvalidJSON < Socky::SockyError; end #:nodoc:
10
- class UnauthorisedQuery < Socky::SockyError; end #:nodoc:
11
- class InvalidQuery < Socky::SockyError; end #:nodoc:
12
-
13
- # message params like command type or message content
14
- attr_reader :params
15
- # message sender(admin) required when some data are returned
16
- attr_reader :creator
17
-
18
- class << self
19
- # create new message and process it
20
- # @see #process
21
- # @param [Connection] connection creator of message
22
- # @param [String] message message content
23
- def process(connection, message)
24
- message = new(connection, message)
25
- message.process
26
- rescue SockyError => error
27
- error connection.name, error
28
- connection.send_message(error.message)
29
- end
30
- end
31
-
32
- # initialize new message
33
- # @param [Connection] creator creator of message
34
- # @param [String] message valid json containing hash of params
35
- # @raise [InvalidJSON] if message is invalid json or don't evaluate to hash
36
- def initialize(creator, message)
37
- @params = symbolize_keys(JSON.parse(message)) rescue raise(InvalidJSON, "invalid request")
38
- @creator = creator
39
- end
40
-
41
- # process message - check command('broadcast' or 'query')
42
- # and send message to correct connections
43
- # 'broadcast' command require 'body' of message and allows 'to' and 'except' hashes for filters
44
- # 'query' command require 'type' of query - currently only 'show_connections' is supported
45
- # @see Socky::Connection::Finders.find filtering options
46
- # @raise [InvalidQuery, 'unknown command'] when 'command' param is invalid
47
- # @raise [InvalidQuery, 'unknown query type'] when 'command' is 'queru' but no 'type' is provided
48
- def process
49
- debug [self.name, "processing", params.inspect]
50
-
51
- case params.delete(:command).to_s
52
- when "broadcast" then broadcast
53
- when "query" then query
54
- else raise(InvalidQuery, "unknown command")
55
- end
56
- end
57
-
58
- private
59
-
60
- def broadcast
61
- connections = Socky::Connection.find(params)
62
- send_message(params[:body], connections)
63
- end
64
-
65
- def query
66
- case params[:type].to_s
67
- when "show_connections" then query_show_connections
68
- else raise(InvalidQuery, "unknown query type")
69
- end
70
- end
71
-
72
- def query_show_connections
73
- respond Socky::Connection.find_all
74
- end
75
-
76
- def respond(message)
77
- creator.send_message(message)
78
- end
79
-
80
- def send_message(message, connections)
81
- connections.each{|connection| connection.send_message message}
82
- end
83
-
84
- end
85
- end
@@ -1,74 +0,0 @@
1
- module Socky
2
- # common methods for all other classes
3
- module Misc
4
-
5
- # extend including class by itself
6
- def self.included(base)
7
- base.extend Socky::Misc
8
- end
9
-
10
- # return server-wide options
11
- # @see Socky.options
12
- def options
13
- Socky.options
14
- end
15
-
16
- # write server-wide options
17
- def options=(ob)
18
- Socky.options = ob
19
- end
20
-
21
- # return name of current object
22
- # @example when included in connection
23
- # @connection.name #=> "Connection(2149785820)"
24
- def name
25
- "#{self.class.to_s.split("::").last}(#{self.object_id})"
26
- end
27
-
28
- # return log path
29
- def log_path
30
- Socky.log_path
31
- end
32
-
33
- # return pid path
34
- def pid_path
35
- Socky.pid_path
36
- end
37
-
38
- # return config path
39
- def config_path
40
- Socky.config_path
41
- end
42
-
43
- # log message at info level
44
- # @param [Array] args data for logging
45
- def info(args)
46
- Socky.logger.info args.join(" ")
47
- end
48
-
49
- # log message at debug level
50
- # @param [Array] args data for logging
51
- def debug(args)
52
- Socky.logger.debug args.join(" ")
53
- end
54
-
55
- # log message at error level
56
- # @param [String] name object name with raised error
57
- # @param [Error] error error instance that was raised
58
- def error(name, error)
59
- debug [name, "raised:", error.class, error.message]
60
- end
61
-
62
- # convert keys of hash to symbol
63
- # @param [Hash] hash hash to symbolize
64
- # @return [Hash] with symbolized keys
65
- # @return [Object] if hash isn't instance of Hash
66
- def symbolize_keys(hash)
67
- return hash unless hash.is_a?(Hash)
68
- hash.inject({}) do |options, (key, value)|
69
- options[(key.to_sym if key.respond_to?(:to_sym)) || key] = value
70
- options
71
- end
72
- end
73
- end
74
- end
@@ -1,27 +0,0 @@
1
- require 'em-http'
2
-
3
- module Socky
4
- # this class provide unobtrusive http request methods
5
- class NetRequest
6
- include Socky::Misc
7
-
8
- class << self
9
-
10
- # send unobtrusive http POST request to gived address and return status of request as block response
11
- # @param [String] url address to send request in format 'http://address[:port]/[path]'
12
- # @param [Hash] params params for request(will be attached in post message)
13
- # @yield [Boolean] called after request is finished - if response status is 200 then it's true, else false
14
- def post(url, params = {}, &block)
15
- http = EventMachine::HttpRequest.new(url).post :body => params, :timeout => options[:timeout] || 3
16
- http.errback { yield false }
17
- http.callback { yield http.response_header.status == 200 }
18
- true
19
- rescue => error
20
- error "Bad request", error
21
- false
22
- end
23
-
24
- end
25
-
26
- end
27
- end
@@ -1,39 +0,0 @@
1
- require 'socky/options/config'
2
- require 'socky/options/parser'
3
-
4
- module Socky
5
- # options parser - reads options from STDIN and config file and set Socky.options
6
- class Options
7
- include Socky::Misc
8
-
9
- class << self
10
- # prepare server-wide options from config and parser
11
- # @param [Array] argv arguments that will be provided to parser
12
- # @see default_options default options
13
- # @see Config.read merged with default options
14
- # @see Parser.parse merges with default options after config
15
- def prepare(argv)
16
- self.options = default_options
17
-
18
- parsed_options = Parser.parse(argv)
19
- config_options = Config.read(parsed_options[:config_path] || config_path, :kill => parsed_options[:kill])
20
-
21
- self.options.merge!(config_options)
22
- self.options.merge!(parsed_options)
23
- end
24
-
25
- # default options for server
26
- def default_options
27
- {
28
- :config_path => config_path,
29
- :port => 8080,
30
- :debug => false,
31
- :deep_debug => false,
32
- :secure => false,
33
- :log_path => log_path
34
- }
35
- end
36
- end
37
-
38
- end
39
- end
@@ -1,79 +0,0 @@
1
- require 'yaml'
2
- require 'erb'
3
-
4
- module Socky
5
- class Options
6
- # config parser class - used by Socky::Options
7
- class Config
8
-
9
- class NoConfigFile < Socky::SockyError; end #:nodoc:
10
- class InvalidConfig < Socky::SockyError; end #:nodoc:
11
- class AlreadyExists < Socky::SockyError; end #:nodoc:
12
- class ConfigUnavailable < Socky::SockyError; end #:nodoc:
13
- class SuccessfullyCreated < Socky::SockyError; end #:nodoc:
14
-
15
- class << self
16
- # read config file or exits if file don't exists or is invalid
17
- # @param [String] path path to valid yaml file
18
- # @param [Hash] args args to rescue eventual problems
19
- # @option args [Any] kill (nil) if not nil then empty hash will be returned if config file isn't found
20
- # @return [Hash] parsed config options
21
- # @raise [NoConfigFile] if file doesn't exists
22
- # @raise [InvalidConfig] if file isn't valid yaml
23
- def read(path, args = {})
24
- raise(NoConfigFile, "You must generate a config file (socky -g filename.yml)") unless File.exists?(path)
25
- result = YAML::load(ERB.new(IO.read(path)).result)
26
- raise(InvalidConfig, "Provided config file is invalid.") unless result.is_a?(Hash)
27
- result
28
- rescue SockyError => error
29
- if args[:kill]
30
- return {}
31
- else
32
- puts error.message
33
- exit
34
- end
35
- end
36
-
37
- # generate default config file
38
- # @see DEFAULT_CONFIG_FILE
39
- # @param [String] path path to file that will be created
40
- # @raise [AlreadyExists] if file exists(you must delete it manually)
41
- # @raise [ConfigUnavailable] if file cannot be created(wrong privilages?)
42
- # @raise [SuccessfullyCreated] if file is successfully created
43
- def generate(path)
44
- raise(AlreadyExists, "Config file already exists. You must remove it before generating a new one.") if File.exists?(path)
45
- File.open(path, 'w+') do |file|
46
- file.write DEFAULT_CONFIG_FILE
47
- end rescue raise(ConfigUnavailable, "Config file is unavailable - please choose another.")
48
- raise(SuccessfullyCreated, "Config file generated at #{path}")
49
- rescue SockyError => error
50
- puts error.message
51
- exit
52
- end
53
-
54
- # default config file content
55
- DEFAULT_CONFIG_FILE= <<-EOF
56
- :port: 8080
57
- :debug: false
58
-
59
- # :subscribe_url: http://localhost:3000/socky/subscribe
60
- # :unsubscribe_url: http://localhost:3000/socky/unsubscribe
61
-
62
- :secret: my_secret_key
63
-
64
- :secure: false
65
-
66
- # :timeout: 3
67
-
68
- # :log_path: /var/log/socky.log
69
- # :pid_path: /var/run/socky.pid
70
-
71
- # :tls_options:
72
- # :private_key_file: /private/key
73
- # :cert_chain_file: /ssl/certificate
74
- EOF
75
- end
76
-
77
- end
78
- end
79
- end