phoenix_rails 0.0.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/lib/phoenix_rails/channel.rb +115 -0
- data/lib/phoenix_rails/client.rb +124 -0
- data/lib/phoenix_rails/request.rb +65 -0
- data/lib/phoenix_rails/resource.rb +27 -0
- data/lib/phoenix_rails.rb +41 -0
- metadata +76 -0
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: []
|