realpush 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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