phoenix_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7709aaa6258a7487078052fdbe24809e9890152f
4
+ data.tar.gz: 9b210022272c81b6f95dc80d6bd1e26a754dcc0d
5
+ SHA512:
6
+ metadata.gz: a272113710ee384f2b5b478ce85b2a5994ac36b9f39bf808ba052630c7aa4ea3c5e10a943a430173a4a9c6c03b191144d3d79fdc4cf07093d32f24772d88dd2e
7
+ data.tar.gz: b05cc1b45a2afee0b327925bba1b9191383c46f053ed5983089f951ad675d3321436464b7dc3e9f02cd958a20b6462e4d24c1567a9f7e573afe535d7166ef49f
@@ -0,0 +1,115 @@
1
+ require 'openssl'
2
+ require 'multi_json'
3
+
4
+ module PhoenixRails
5
+ # Trigger events on Channels
6
+ class Channel
7
+ attr_reader :name
8
+ INVALID_CHANNEL_REGEX = /[^A-Za-z0-9_\-=@,.;]/
9
+ def initialize(base_url, name, client = PhoenixRails)
10
+ @uri = base_url.dup
11
+ if PhoenixRails::Channel::INVALID_CHANNEL_REGEX.match(name)
12
+ raise PhoenixRails::Error, "Illegal channel name '#{name}'"
13
+ end
14
+ @uri.path = @uri.path + "/channels/#{name}/"
15
+ @name = name
16
+ @client = client
17
+ end
18
+
19
+ # Trigger event
20
+ def trigger!(event_name, data, socket_id = nil)
21
+ params = {}
22
+ if socket_id
23
+ validate_socket_id(socket_id)
24
+ params[:socket_id] = socket_id
25
+ end
26
+ @client.trigger(name, event_name, data, params)
27
+ end
28
+
29
+ # Trigger event, catching and logging any errors.
30
+ def trigger(event_name, data, socket_id = nil)
31
+ trigger!(event_name, data, socket_id)
32
+ rescue PhoenixRails::Error => e
33
+ PhoenixRails.logger.error("#{e.message} (#{e.class})")
34
+ PhoenixRails.logger.debug(e.backtrace.join("\n"))
35
+ end
36
+
37
+ # Request users for a presence channel
38
+ # Only works on presence channels (see: http://PhoenixRails.com/docs/client_api_guide/client_presence_channels and https://PhoenixRails.com/docs/rest_api)
39
+ #
40
+ # @example Response
41
+ # [{"id"=>"4"}]
42
+ #
43
+ # @return [Hash] Array of user hashes for this channel
44
+ # @raise [PhoenixRails::Error] on invalid PhoenixRails response - see the error message for more details
45
+ # @raise [PhoenixRails::HTTPError] on any error raised inside Net::HTTP - the original error is available in the original_error attribute
46
+ #
47
+ def users
48
+ @client.get("/channels/#{name}/users")[:users]
49
+ end
50
+
51
+ # Compute authentication string required as part of the authentication
52
+ # endpoint response. Generally the authenticate method should be used in
53
+ # preference to this one
54
+ #
55
+ # @param socket_id [String] Each PhoenixRails socket connection receives a
56
+ # unique socket_id. This is sent from phoenix.js to your server when
57
+ # channel authentication is required.
58
+ # @param custom_string [String] Allows signing additional data
59
+ # @return [String]
60
+ #
61
+ def authentication_string(socket_id, custom_string = nil)
62
+ validate_socket_id(socket_id)
63
+
64
+ unless custom_string.nil? || custom_string.kind_of?(String)
65
+ raise Error, 'Custom argument must be a string'
66
+ end
67
+
68
+ string_to_sign = [socket_id, name, custom_string].
69
+ compact.map(&:to_s).join(':')
70
+ PhoenixRails.logger.debug "Signing #{string_to_sign}"
71
+ token = @client.authentication_token
72
+ digest = OpenSSL::Digest::SHA256.new
73
+ signature = OpenSSL::HMAC.hexdigest(digest, token.secret, string_to_sign)
74
+
75
+ return "#{token.key}:#{signature}"
76
+ end
77
+
78
+ # Generate the expected response for an authentication endpoint.
79
+ #
80
+ # @example Private channels
81
+ # render :json => PhoenixRails['private-my_channel'].authenticate(params[:socket_id])
82
+ #
83
+ # @example Presence channels
84
+ # render :json => PhoenixRails['private-my_channel'].authenticate(params[:socket_id], {
85
+ # :user_id => current_user.id, # => required
86
+ # :user_info => { # => optional - for example
87
+ # :name => current_user.name,
88
+ # :email => current_user.email
89
+ # }
90
+ # })
91
+ #
92
+ # @param socket_id [String]
93
+ # @param custom_data [Hash] used for example by private channels
94
+ #
95
+ # @return [Hash]
96
+ #
97
+ # @private Custom data is sent to server as JSON-encoded string
98
+ #
99
+ def authenticate(socket_id, custom_data = nil)
100
+ custom_data = MultiJson.encode(custom_data) if custom_data
101
+ auth = authentication_string(socket_id, custom_data)
102
+ r = {:auth => auth}
103
+ r[:channel_data] = custom_data if custom_data
104
+ r
105
+ end
106
+
107
+ private
108
+
109
+ def validate_socket_id(socket_id)
110
+ unless socket_id && /\A\d+\.\d+\z/.match(socket_id)
111
+ raise PhoenixRails::Error, "Invalid socket ID #{socket_id.inspect}"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,124 @@
1
+ ire 'signature'
2
+
3
+ module PhoenixRails
4
+ class Client
5
+ attr_accessor :scheme, :host, :port, :app_id, :key, :secret
6
+ attr_writer :connect_timeout, :send_timeout, :receive_timeout, :keep_alive_timeout
7
+
8
+
9
+ def initialize(options = {})
10
+ options = {
11
+ :scheme => 'http',
12
+ :host => 'api.phoenixapp.com',
13
+ :port => 80,
14
+ }.merge(options)
15
+ @scheme, @host, @port, @app_id, @key, @secret = options.values_at(
16
+ :scheme, :host, :port, :app_id, :key, :secret
17
+ )
18
+
19
+ # Default timeouts
20
+ @connect_timeout = 5
21
+ @send_timeout = 5
22
+ @receive_timeout = 5
23
+ @keep_alive_timeout = 30
24
+ end
25
+
26
+ def url=(url)
27
+ uri = URI.parse(url)
28
+ @scheme = uri.scheme
29
+ @app_id = uri.path.split('/').last
30
+ @key = uri.user
31
+ @secret = uri.password
32
+ @host = uri.host
33
+ @port = uri.port
34
+ end
35
+
36
+ def encrypted=(boolean)
37
+ @scheme = boolean ? 'https' : 'http'
38
+ # Configure port if it hasn't already been configured
39
+ @port = boolean ? 443 : 80
40
+ end
41
+
42
+ def encrypted?
43
+ @scheme == 'https'
44
+ end
45
+
46
+ def timeout=(value)
47
+ @connect_timeout, @send_timeout, @receive_timeout = value, value, value
48
+ end
49
+
50
+ ## INTERACE WITH THE API ##
51
+
52
+ def resource(path)
53
+ Resource.new(self, path)
54
+ end
55
+
56
+ def get(path, params = {})
57
+ Resource.new(self, path).get(params)
58
+ end
59
+
60
+ def post(path, params = {})
61
+ Resource.new(self, path).post(params)
62
+ end
63
+
64
+ def channel(channel_name)
65
+ raise ConfigurationError, 'Missing client configuration: please check that key, secret and app_id are configured.' unless configured?
66
+ Channel.new(url, channel_name, self)
67
+ end
68
+
69
+ alias :[] :channel
70
+
71
+ def channels(params = {})
72
+ get('/channels', params)
73
+ end
74
+
75
+ def trigger(channels, event_name, data, params = {})
76
+ post('/events', trigger_params(channels, event_name, data, params))
77
+ end
78
+
79
+ # @private Construct a net/http http client
80
+ def http_client
81
+ @client ||= begin
82
+ require 'httpclient'
83
+
84
+ HTTPClient.new.tap do |c|
85
+ c.connect_timeout = @connect_timeout
86
+ c.send_timeout = @send_timeout
87
+ c.receive_timeout = @receive_timeout
88
+ c.keep_alive_timeout = @keep_alive_timeout
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def trigger_params(channels, event_name, data, params)
96
+ channels = Array(channels).map(&:to_s)
97
+ raise PhoenixRails::Error, "Too many channels (#{channels.length}), max 10" if channels.length > 10
98
+
99
+ case data
100
+ when String
101
+ encoded_data = data
102
+ else
103
+ begin
104
+ encoded_data = MultiJson.encode(data)
105
+ rescue MultiJson::DecodeError => e
106
+ PhoenixRails.logger.error("Could not convert #{data.inspect} into JSON")
107
+ raise e
108
+ end
109
+ end
110
+
111
+ params.merge({
112
+ :name => event_name,
113
+ :channels => channels,
114
+ :data => encoded_data
115
+ })
116
+ end
117
+
118
+ def configured?
119
+ host && scheme && key && secret && app_id
120
+ end
121
+
122
+ end
123
+ end
124
+
@@ -0,0 +1,65 @@
1
+ require 'signature'
2
+ require 'digest/md5'
3
+ require 'multi_json'
4
+
5
+ module PhoenixRails
6
+ class Request
7
+ attr_reader :body, :params
8
+
9
+ def initialize(client, verb, uri, params, body = nil)
10
+ @client, @verb, @uri = client, verb, uri
11
+ @head = {}
12
+
13
+ @body = body
14
+ if body
15
+ params[:body_md5] = Digest::MD5.hexdigest(body)
16
+ @head['Content-Type'] = 'application/json'
17
+ end
18
+
19
+ request = Signature::Request.new(verb.to_s.upcase, uri.path, params)
20
+ request.sign(client.authentication_token)
21
+ @params = request.signed_params
22
+ end
23
+
24
+ def send
25
+ http = @client.http_client
26
+
27
+ begin
28
+ response = http.request(@verb, @uri, @params, @body, @head)
29
+ rescue HTTPClient::BadResponseError, HTTPClient::TimeoutError,
30
+ SocketError, Errno::ECONNREFUSED => e
31
+ error = PhoenixRails::HTTPError.new("#{e.message} (#{e.class})")
32
+ error.original_error = e
33
+ raise error
34
+ end
35
+
36
+ body = response.body ? response.body.chomp : nil
37
+
38
+ return handle_response(response.code.to_i, body)
39
+ end
40
+
41
+ private
42
+
43
+ def handle_response(status_code, body)
44
+ case status_code
45
+ when 200
46
+ return symbolize_first_level(MultiJson.decode(body))
47
+ when 400
48
+ raise Error, "Bad request: #{body}"
49
+ when 401
50
+ raise AuthenticationError, body
51
+ when 404
52
+ raise Error, "404 Not found (#{@uri.path})"
53
+ else
54
+ raise Error, "Unknown error (status code #{status_code}): #{body}"
55
+ end
56
+ end
57
+
58
+ def symbolize_first_level(hash)
59
+ hash.inject({}) do |result, (key, value)|
60
+ result[key.to_sym] = value
61
+ result
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ module PhoenixRails
2
+ class Resource
3
+ def initialize(client, path)
4
+ @client = client
5
+ @path = path
6
+ end
7
+
8
+ def get(params)
9
+ create_request(:get, params).send
10
+ end
11
+
12
+ def post(params)
13
+ body = MultiJson.encode(params)
14
+ create_request(:post, {}, body).send
15
+ end
16
+
17
+ private
18
+
19
+ def create_request(verb, params, body = nil)
20
+ Request.new(@client, verb, url, params, body)
21
+ end
22
+
23
+ def url
24
+ @_url ||= @client.url(@path)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ class PhoenixRails
2
+ class Error < RuntimeError; end
3
+ class AuthenticationError < Error; end
4
+ class ConfigurationError < Error; end
5
+ class HTTPError < Error; attr_accessor :original_error; end
6
+
7
+ class << self
8
+ extend Forwardable
9
+
10
+ def_delegators :default_client, :scheme, :host, :port, :app_id, :key, :secret
11
+ def_delegators :default_client, :scheme=, :host=, :port=, :app_id=, :key=, :secret=
12
+
13
+ def_delegators :default_client, :authentication_token, :url
14
+ def_delegators :default_client, :encrypted=, :url=
15
+ def_delegators :default_client, :timeout=, :connect_timeout=, :send_timeout=, :receive_timeout=, :keep_alive_timeout=
16
+
17
+ def_delegators :default_client, :get, :post
18
+ def_delegators :default_client, :channels, :trigger
19
+ def_delegators :default_client, :channel, :[]
20
+
21
+ def logger
22
+ @logger ||= begin
23
+ log = Logger.new($stdout)
24
+ log.level = Logger::INFO
25
+ log
26
+ end
27
+ end
28
+
29
+ def default_client
30
+ @default_client ||= PhoenixRails::Client.new
31
+ end
32
+ end
33
+
34
+ if ENV['PHOENIX_RAILS_URL']
35
+ self.url = ENV['PHOENIX_RAILS_URL']
36
+ end
37
+ end
38
+
39
+ require 'pusher/channel'
40
+ require 'pusher/request'
41
+ require 'pusher/resource'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phoenix_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nguyen Le
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpclient
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ description: Gem for pushing event to a Phoenix server for realtime
42
+ email: nathanle89@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/phoenix_rails.rb
48
+ - lib/phoenix_rails/channel.rb
49
+ - lib/phoenix_rails/client.rb
50
+ - lib/phoenix_rails/request.rb
51
+ - lib/phoenix_rails/resource.rb
52
+ homepage: http://rubygems.org/gems/phoenix_rails
53
+ licenses:
54
+ - MIT
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 2.5.1
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Rails gem for Phoenix integration
76
+ test_files: []