realpush 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.
@@ -0,0 +1,22 @@
1
+ module RealPush
2
+ module API
3
+ module BaseList
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ def list
9
+ content = execute :get, url("#{self.class.params[:base_path]}.json")
10
+ parse_content content
11
+ rescue RealPush::HTTPError => e
12
+ {
13
+ status: :ERROR,
14
+ message: e.message
15
+ }
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module RealPush
2
+ module API
3
+ module BaseUpdate
4
+ extend ActiveSupport::Concern
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ def update(id, data)
9
+ valid_params? data
10
+ content = execute :patch, url("#{self.class.params[:base_path]}/#{id}.json"), {}, data
11
+ parse_content content
12
+ rescue RealPush::HTTPError,
13
+ RealPush::ConfigurationError => e
14
+ raise RealPush::ConfigurationError, e.message
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,188 @@
1
+ require 'signature'
2
+ require 'multi_json'
3
+
4
+ module RealPush
5
+
6
+ class Client
7
+
8
+ attr_accessor :scheme, :app_id, :privatekey, :hostname, :port
9
+ attr_writer :connect_timeout, :send_timeout, :receive_timeout,
10
+ :keep_alive_timeout
11
+
12
+ def initialize(options={})
13
+ options = {
14
+ port: 443,
15
+ scheme: 'http',
16
+ hostname: '127.0.0.1',
17
+ app_id: nil,
18
+ privatekey: nil
19
+ }.merge options
20
+
21
+ @encrypted = false
22
+
23
+ @scheme, @app_id, @privatekey, @hostname, @port = options.values_at(
24
+ :scheme, :app_id, :privatekey, :hostname, :port
25
+ )
26
+
27
+ # Default timeouts
28
+ @connect_timeout = 5
29
+ @send_timeout = 5
30
+ @receive_timeout = 5
31
+ @keep_alive_timeout = 30
32
+ end
33
+
34
+ def authenticate(publickey, privatekey)
35
+ @app_id, @privatekey = publickey, privatekey
36
+ end
37
+
38
+ # Generate the expected response for an authentication endpoint.
39
+ #
40
+ # @example Private channels
41
+ # render :json => RealPush.default_client.authenticate(params[:realpush])
42
+ #
43
+ # @param custom_string [String | Hash]
44
+ #
45
+ # @return [Hash]
46
+ def authentication_string(custom_string=nil)
47
+ custom_string = MultiJson.encode(custom_string) if custom_string.kind_of?(Hash)
48
+ unless custom_string.nil? || custom_string.kind_of?(String)
49
+ raise Error, 'Custom argument must be a string'
50
+ end
51
+
52
+ string_to_sign = [app_id, custom_string].compact.map(&:to_s).join(':')
53
+ digest = OpenSSL::Digest::SHA256.new
54
+ signature = OpenSSL::HMAC.hexdigest(digest, privatekey, string_to_sign)
55
+ {auth:signature}
56
+ end
57
+
58
+ # @private Returns the authentication token for the client
59
+ def authentication_token
60
+ Signature::Token.new(app_id, privatekey)
61
+ end
62
+
63
+ def config(&block)
64
+ raise ConfigurationError, 'You need a block' unless block_given?
65
+ yield self
66
+ end
67
+
68
+ def encrypted=(bool)
69
+ @scheme = bool ? 'https' : 'http'
70
+ # Configure port if it hasn't already been configured
71
+ @port = bool ? 443 : 80
72
+ end
73
+
74
+ def encrypted?
75
+ scheme == 'https'
76
+ end
77
+
78
+ def get(path, params)
79
+ Resource.new(self, "/#{app_id}#{path}").get params
80
+ end
81
+
82
+ def get_async(path, params)
83
+ Resource.new(self, "/#{app_id}#{path}").get_async params
84
+ end
85
+
86
+ def post(path, body)
87
+ Resource.new(self, "/#{app_id}#{path}").post body
88
+ end
89
+
90
+ def post_async(path, body)
91
+ Resource.new(self, "/#{app_id}#{path}").post_async body
92
+ end
93
+
94
+ # @private Construct a net/http http client
95
+ def sync_http_client
96
+ @client ||= begin
97
+ require 'httpclient'
98
+
99
+ HTTPClient.new(default_header: {'X-RealPush-Secret-Key' => @privatekey}).tap do |c|
100
+ c.connect_timeout = @connect_timeout
101
+ c.send_timeout = @send_timeout
102
+ c.receive_timeout = @receive_timeout
103
+ c.keep_alive_timeout = @keep_alive_timeout
104
+ end
105
+ end
106
+ end
107
+
108
+ # Convenience method to set all timeouts to the same value (in seconds).
109
+ # For more control, use the individual writers.
110
+ def timeout=(value)
111
+ @connect_timeout, @send_timeout, @receive_timeout = value, value, value
112
+ end
113
+
114
+ # Trigger an event on one or more channels
115
+ #
116
+ # POST /api/[api_version]/events/[event_name]/
117
+ #
118
+ # @param channels [String or Array] 1-10 channel names
119
+ # @param event_name [String]
120
+ # @param data [Object] Event data to be triggered in javascript.
121
+ # Objects other than strings will be converted to JSON
122
+ #
123
+ # @return [Hash] See Thunderpush API docs
124
+ #
125
+ # @raise [ThunderPush::Error] Unsuccessful response - see the error message
126
+ # @raise [ThunderPush::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
127
+ #
128
+ def trigger(channels, event_name, data)
129
+ post("/events/#{event_name}/", trigger_params(channels, data))
130
+ end
131
+
132
+ # Trigger an event on one or more channels
133
+ #
134
+ # POST /apps/[app_id]/events/[event_name]/
135
+ #
136
+ # @param channels [String or Array] 1-10 channel names
137
+ # @param event_name [String]
138
+ # @param data [Object] Event data to be triggered in javascript.
139
+ # Objects other than strings will be converted to JSON
140
+ #
141
+ # @raise [ThunderPush::Error] Unsuccessful response - see the error message
142
+ # @raise [ThunderPush::HTTPError] Error raised inside http client. The original error is wrapped in error.original_error
143
+ #
144
+ def trigger_async(channels, event_name, data)
145
+ post_async("/events/#{event_name}/", trigger_params(channels, data))
146
+ end
147
+
148
+ # Configure Thunderpush connection by providing a url rather than specifying
149
+ # scheme, key, secret, and app_id separately.
150
+ #
151
+ # @example
152
+ # ThunderPush.default_client.url = http://key:secret@127.0.0.1:5678
153
+ #
154
+ def url=(str)
155
+ regex = /^(?<scheme>http|https):\/\/((?<app_id>[\d-]+)(:(?<privatekey>[\w-]+){1})?@)?(?<hostname>[\w\.-]+)(:(?<port>[\d]+))?/
156
+ match = str.match regex
157
+ @scheme = match[:scheme] unless match[:scheme].nil?
158
+ self.encrypted= true if scheme == 'https'
159
+ @port = match[:port].to_i unless match[:port].nil?
160
+ @hostname = match[:hostname] unless match[:hostname].nil?
161
+ @app_id = match[:app_id] unless match[:app_id].nil?
162
+ @privatekey = match[:privatekey] unless match[:privatekey].nil?
163
+ end
164
+
165
+ # @private Builds a url for this app, optionally appending a path
166
+ def url(path = '')
167
+ path = "/#{path}" unless path.start_with? '/'
168
+ URI::Generic.build({
169
+ :scheme => @scheme,
170
+ :host => @hostname,
171
+ :port => @port,
172
+ :path => "/#{RealPush::API_VERSION_APP}#{path}"
173
+ })
174
+ end
175
+
176
+ protected
177
+
178
+ def trigger_params(channels, data)
179
+ data.merge! :channels => channels
180
+ begin
181
+ MultiJson.encode(data)
182
+ rescue MultiJson::DecodeError => e
183
+ ThunderPush.logger.error("Could not convert #{data.inspect} into JSON")
184
+ raise e
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,89 @@
1
+ module RealPush
2
+ class Request
3
+ attr_reader :body, :params
4
+
5
+ def initialize(client, verb, uri, params={}, body = nil)
6
+ @client, @verb, @uri, @params = client, verb, uri, params
7
+
8
+ raise ConfigurationError, "Invalid verb ('#{verb}')" unless %w{GET POST}.include? verb.to_s.upcase
9
+ raise ConfigurationError, "Invalid client #{client.inspect}" unless client.is_a? Client
10
+ raise ConfigurationError, "Invalid uri #{uri.inspect}" unless uri.is_a? URI
11
+
12
+ @head = {}
13
+
14
+ @body = body
15
+ if body
16
+ @params[:body_md5] = Digest::MD5.hexdigest(body)
17
+ @head['Content-Type'] = 'application/json'
18
+ end
19
+
20
+ sign_params
21
+ end
22
+
23
+ def sign_params
24
+ auth_hash = {
25
+ :auth_version => "1.0",
26
+ :auth_key => @client.app_id,
27
+ :auth_timestamp => Time.now.to_i.to_s
28
+ }
29
+ params_string = auth_hash.sort.map do |k, v|
30
+ "#{k}=#{v}"
31
+ end.join('&')
32
+ string_to_sign = [@verb.to_s.upcase, @uri.path, params_string].join "\n"
33
+ digest = OpenSSL::Digest::SHA256.new
34
+ auth_hash[:auth_signature] = OpenSSL::HMAC.hexdigest(digest, @client.privatekey, string_to_sign)
35
+ @params = @params.merge(auth_hash)
36
+ end
37
+ private :sign_params
38
+
39
+ def send_sync
40
+ http = @client.sync_http_client
41
+
42
+ begin
43
+ response = http.request(@verb, @uri, @params, @body, @head)
44
+ rescue HTTPClient::BadResponseError,
45
+ HTTPClient::TimeoutError,
46
+ SocketError,
47
+ Errno::ECONNREFUSED => e
48
+ raise RealPush::HTTPError, "#{e.message} (#{e.class})"
49
+ end
50
+
51
+ body = response.body ? response.body.chomp : nil
52
+
53
+ return handle_response(response.code.to_i, body)
54
+ end
55
+
56
+ def send_async
57
+ http = @client.sync_http_client
58
+ http.request_async(@verb, @uri, @params, @body, @head)
59
+ end
60
+
61
+ private
62
+
63
+ def handle_response(status_code, body)
64
+ case status_code
65
+ when 200
66
+ return symbolize_first_level(MultiJson.decode(body))
67
+ when 202
68
+ return true
69
+ when 400
70
+ raise Error, "Bad request: #{body}"
71
+ when 401
72
+ raise AuthenticationError, body
73
+ when 404
74
+ raise Error, "404 Not found (#{@uri.path})"
75
+ when 407
76
+ raise Error, "Proxy Authentication Required"
77
+ else
78
+ raise Error, "Unknown error (status code #{status_code}): #{body}"
79
+ end
80
+ end
81
+
82
+ def symbolize_first_level(hash)
83
+ hash.inject({}) do |result, (key, value)|
84
+ result[key.to_sym] = value
85
+ result
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ module RealPush
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_sync
10
+ end
11
+
12
+ def get_async(params)
13
+ create_request(:get, params).send_async
14
+ end
15
+
16
+ def post(body)
17
+ body = MultiJson.encode(body) unless body.is_a? String
18
+ create_request(:post, {}, body).send_sync
19
+ end
20
+
21
+ def post_async(body)
22
+ body = MultiJson.encode(body) unless body.is_a? String
23
+ create_request(:post, {}, body).send_async
24
+ end
25
+
26
+ private
27
+
28
+ def create_request(verb, params, body = nil)
29
+ Request.new(@client, verb, url, params, body)
30
+ end
31
+
32
+ def url
33
+ @_url ||= @client.url(@path)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ module RealPush
2
+ MAJOR = 0
3
+ MINOR = 0
4
+ PATCH = 1
5
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
6
+ end
data/realpush.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'realpush/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'realpush'
8
+ spec.version = RealPush::VERSION
9
+ spec.authors = ['Zaez Team']
10
+ spec.email = ['contato@zaez.net']
11
+ spec.summary = %q{Library of integration with RealPush system. Written in Ruby.}
12
+ spec.description = %q{RealPush is a private and commercial system to send notifications via websocket.
13
+ The system allows the creation of applications based on its quota through access API.
14
+ Sending notifications to trigger customers with private and public channels. Synchronous and asynchronous requests..}
15
+ spec.homepage = 'http://realpush.cc/'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'multi_json', '~> 1.0'
24
+ spec.add_dependency 'httpclient', '~> 2.6'
25
+ spec.add_dependency 'signature', '~> 0.1.6'
26
+ spec.add_dependency 'activesupport', '>= 4'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.7'
29
+ spec.add_development_dependency 'rspec', '~> 3.1.0'
30
+ spec.add_development_dependency 'pry', '>= 0.9.12'
31
+ spec.add_development_dependency 'simplecov'
32
+ spec.add_development_dependency 'codeclimate-test-reporter'
33
+ spec.add_development_dependency 'guard'
34
+ spec.add_development_dependency 'guard-rspec'
35
+ spec.add_development_dependency 'growl'
36
+ spec.add_development_dependency 'webmock'
37
+ spec.add_development_dependency 'em-http-request', '~> 1.1.0'
38
+ spec.add_development_dependency 'rake', '~> 10.0'
39
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ describe RealPush::API::App do
4
+ data = {
5
+ alias_name: 'APP 1',
6
+ max_connections: '0',
7
+ max_daily_messages: '0',
8
+ status: 'active'
9
+ }
10
+
11
+ it 'should try exception when token has invalid format' do
12
+ 50.times do
13
+ expect { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }.not_to raise_error
14
+ end
15
+ expect { RealPush::API::App.new( '' ) }.to raise_error(RealPush::ConfigurationError)
16
+ expect { RealPush::API::App.new( '@'+('2'*42) ) }.to raise_error(RealPush::ConfigurationError)
17
+ expect { RealPush::API::App.new( 'a'*44 ) }.to raise_error(RealPush::ConfigurationError)
18
+ end
19
+
20
+ describe 'Methods from base configuration' do
21
+
22
+ let(:app) { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }
23
+
24
+ it 'should accept the params in requests (alias_name, max_connections, max_daily_messages, status)' do
25
+ [:alias_name, :max_connections, :max_daily_messages, :status].each do |p|
26
+ expect(RealPush::API::App.params_accept.include? p).to eql true
27
+ end
28
+ end
29
+
30
+ it 'should have a instance method to LIST all APPs' do
31
+ expect(app.respond_to? :list).to be_truthy
32
+ end
33
+
34
+ it 'should have a instance method to DESTROY an app' do
35
+ expect(app.respond_to? :destroy).to be_truthy
36
+ end
37
+
38
+ it 'should have a instance method to CREATE an app' do
39
+ expect(app.respond_to? :create).to be_truthy
40
+ end
41
+
42
+ it 'should have a instance method to UPDATE an app' do
43
+ expect(app.respond_to? :update).to be_truthy
44
+ end
45
+
46
+ end
47
+
48
+ describe 'List method' do
49
+
50
+ let(:app) { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }
51
+
52
+ before do
53
+ api_path = %r{apps\.json}
54
+ stub_request(:get, api_path).
55
+ with({
56
+ :headers => { 'X-RealPush-Token' => app.token }
57
+ }).
58
+ to_return({
59
+ :status => 200,
60
+ :body => MultiJson.encode([
61
+ {id: SecureRandom.hex(12)},
62
+ {id: SecureRandom.hex(12)},
63
+ {id: SecureRandom.hex(12)}
64
+ ])
65
+ })
66
+ end
67
+
68
+ it 'testing connection and requesting' do
69
+ app.list
70
+ end
71
+
72
+ it 'should contains a tree elements in response' do
73
+ list = app.list
74
+ expect(list.count).to eql 3
75
+ end
76
+
77
+ end
78
+
79
+ describe 'Destroy method' do
80
+
81
+ let(:app) { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }
82
+
83
+ before do
84
+ api_path = %r{apps/123\.json}
85
+ stub_request(:delete, api_path).
86
+ with({
87
+ :headers => { 'X-RealPush-Token' => app.token }
88
+ }).
89
+ to_return({
90
+ :status => 204
91
+ })
92
+ end
93
+
94
+ it 'testing connection and requesting, with response 204' do
95
+ app.destroy(123)
96
+ end
97
+
98
+ end
99
+
100
+ describe 'Create method' do
101
+ let(:app) { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }
102
+
103
+ before do
104
+ api_path = %r{apps\.json}
105
+ stub_request(:post, api_path).
106
+ with({
107
+ :headers => { 'X-RealPush-Token' => app.token },
108
+ :body => data
109
+ }).
110
+ to_return({
111
+ :status => 200,
112
+ body: MultiJson.encode( data.merge(id: SecureRandom.hex(12) ) )
113
+ })
114
+ end
115
+
116
+ it 'testing connection and requesting, with response 200' do
117
+ app.create(data)
118
+ end
119
+
120
+ it 'must try exception when has invalid parameter' do
121
+ expect {app.create({asd: 1}) }.to raise_error(RealPush::ConfigurationError)
122
+ end
123
+
124
+ it 'should returns a App data when successfully' do
125
+ app_return = app.create(data).symbolize_keys
126
+ data.keys.each { |key| expect(app_return.keys.include? key.to_sym).to be_truthy }
127
+ expect(app_return[:id]).to be_truthy
128
+ end
129
+
130
+ it 'should returns error in Hash, when invalid data' do
131
+ api_path = %r{apps\.json}
132
+ stub_request(:post, api_path).
133
+ with({
134
+ :headers => { 'X-RealPush-Token' => app.token }
135
+ }).
136
+ to_return({
137
+ :status => 500,
138
+ body: MultiJson.encode( { error: 'Message' } )
139
+ })
140
+ app_return = app.create(data).symbolize_keys
141
+ data.keys.each { |key| expect(app_return.keys.include? key.to_sym).to be_falsey }
142
+ expect(app_return[:id]).to be_falsey
143
+ expect(app_return[:error]).to be_truthy
144
+ end
145
+
146
+ end
147
+
148
+ describe 'Update method' do
149
+ let(:app) { RealPush::API::App.new( SecureRandom.urlsafe_base64(32, true) ) }
150
+
151
+ before do
152
+ api_path = %r{apps/123\.json}
153
+ stub_request(:patch, api_path).
154
+ with({
155
+ :headers => { 'X-RealPush-Token' => app.token },
156
+ :body => data
157
+ }).
158
+ to_return({
159
+ :status => 200,
160
+ body: MultiJson.encode( data.merge(id: 123 ) )
161
+ })
162
+ end
163
+
164
+ it 'testing connection and requesting, with response 200' do
165
+ app.update(123, data)
166
+ end
167
+
168
+ it 'must try exception when has invalid parameter' do
169
+ expect {app.update(123, {asd: 1}) }.to raise_error(RealPush::ConfigurationError)
170
+ end
171
+
172
+ it 'should returns a App data when successfully' do
173
+ app_return = app.update(123, data).symbolize_keys
174
+ data.keys.each { |key| expect(app_return.keys.include? key.to_sym).to be_truthy }
175
+ expect(app_return[:id]).to be_truthy
176
+ end
177
+
178
+ end
179
+
180
+
181
+
182
+ end