bmo 0.8.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 39304f792a910d0a9f4e7e1e50067919aa28ec67
4
+ data.tar.gz: 2a478f2e6fc7e7b0e80fc87a492bf63a024e7338
5
+ SHA512:
6
+ metadata.gz: 2979469971ebf385203814428d3d7fe6e18a3bdae760f6994fef7c43eacb964bb160b00f41b179f4346540988fe036678a8e00b186f4847cbf98c99492048ee6
7
+ data.tar.gz: a41eedfd88a9f130fe2408ddc2c6894843792019badc9ed10df6fbd239b6860822cce989e97c1d6bd8975d2794be747f1e188f92d2133049143298104b5f2111
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+
2
+ tags
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format progress
3
+ --warnings
4
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ source 'https://rubygems.org'
3
+
4
+ # Specify your gem's dependencies in poleica.gemspec
5
+ gemspec
6
+
7
+ group :test do
8
+ gem 'rspec'
9
+ gem 'coveralls'
10
+ gem 'flay'
11
+ gem 'flog'
12
+ gem 'reek'
13
+ gem 'rubocop'
14
+ gem 'simplecov'
15
+ end
16
+
17
+ group :development do
18
+ gem 'jazz_hands'
19
+
20
+ # Guards
21
+ gem 'guard'
22
+ gem 'guard-bundler'
23
+ gem 'guard-rspec'
24
+ gem 'guard-rubocop'
25
+ gem 'guard-flay', github: 'pericles/guard-flay'
26
+ gem 'guard-reek', github: 'pericles/guard-reek'
27
+ gem 'guard-flog', github: 'antoinelyset/guard-flog'
28
+ end
29
+
30
+ group :darwin do
31
+ gem 'terminal-notifier-guard'
32
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,237 @@
1
+ GIT
2
+ remote: git://github.com/antoinelyset/guard-flog.git
3
+ revision: 40ea28590ae29349860901d3c9b8253b24c31588
4
+ specs:
5
+ guard-flog (0.0.1)
6
+ flog (~> 4.2.0)
7
+ guard (>= 1.8)
8
+
9
+ GIT
10
+ remote: git://github.com/pericles/guard-flay.git
11
+ revision: 75b9be79c12394626af0064bcec1075959c3cbdd
12
+ specs:
13
+ guard-flay (0.0.1)
14
+ flay (~> 2.4.0)
15
+ flay-haml (~> 0.0.3)
16
+ guard (>= 1.8)
17
+
18
+ GIT
19
+ remote: git://github.com/pericles/guard-reek.git
20
+ revision: febe7ac4f4c6f029a30a9213e92bc1ba44b4e2d4
21
+ specs:
22
+ guard-reek (0.0.1)
23
+ guard (>= 1.8)
24
+ reek (~> 1.3.2)
25
+
26
+ PATH
27
+ remote: .
28
+ specs:
29
+ bmo (0.8.0)
30
+ equalizer (~> 0.0.0)
31
+ faraday (~> 0.8.0)
32
+
33
+ GEM
34
+ remote: https://rubygems.org/
35
+ specs:
36
+ actionpack (4.0.2)
37
+ activesupport (= 4.0.2)
38
+ builder (~> 3.1.0)
39
+ erubis (~> 2.7.0)
40
+ rack (~> 1.5.2)
41
+ rack-test (~> 0.6.2)
42
+ activesupport (4.0.2)
43
+ i18n (~> 0.6, >= 0.6.4)
44
+ minitest (~> 4.2)
45
+ multi_json (~> 1.3)
46
+ thread_safe (~> 0.1)
47
+ tzinfo (~> 0.3.37)
48
+ ast (1.1.0)
49
+ atomic (1.1.14)
50
+ awesome_print (1.2.0)
51
+ binding_of_caller (0.7.2)
52
+ debug_inspector (>= 0.0.1)
53
+ builder (3.1.4)
54
+ celluloid (0.15.2)
55
+ timers (~> 1.1.0)
56
+ coderay (1.1.0)
57
+ columnize (0.3.6)
58
+ coolline (0.4.2)
59
+ coveralls (0.7.0)
60
+ multi_json (~> 1.3)
61
+ rest-client
62
+ simplecov (>= 0.7)
63
+ term-ansicolor
64
+ thor
65
+ debug_inspector (0.0.2)
66
+ debugger (1.6.3)
67
+ columnize (>= 0.3.1)
68
+ debugger-linecache (~> 1.2.0)
69
+ debugger-ruby_core_source (~> 1.2.4)
70
+ debugger-linecache (1.2.0)
71
+ debugger-ruby_core_source (1.2.4)
72
+ diff-lcs (1.2.5)
73
+ diffy (3.0.1)
74
+ docile (1.1.1)
75
+ equalizer (0.0.8)
76
+ erubis (2.7.0)
77
+ faraday (0.8.8)
78
+ multipart-post (~> 1.2.0)
79
+ ffi (1.9.3)
80
+ flay (2.4.0)
81
+ ruby_parser (~> 3.0)
82
+ sexp_processor (~> 4.0)
83
+ flay-haml (0.0.3)
84
+ flay (>= 1.2, < 3)
85
+ haml (>= 3, < 5)
86
+ flog (4.2.0)
87
+ ruby_parser (~> 3.1, > 3.1.0)
88
+ sexp_processor (~> 4.4)
89
+ formatador (0.2.4)
90
+ grit (2.5.0)
91
+ diff-lcs (~> 1.1)
92
+ mime-types (~> 1.15)
93
+ posix-spawn (~> 0.3.6)
94
+ guard (2.2.4)
95
+ formatador (>= 0.2.4)
96
+ listen (~> 2.1)
97
+ lumberjack (~> 1.0)
98
+ pry (>= 0.9.12)
99
+ thor (>= 0.18.1)
100
+ guard-bundler (2.0.0)
101
+ bundler (~> 1.0)
102
+ guard (~> 2.2)
103
+ guard-rspec (4.2.0)
104
+ guard (>= 2.1.1)
105
+ rspec (>= 2.14, < 4.0)
106
+ guard-rubocop (1.0.1)
107
+ guard (~> 2.0)
108
+ rubocop (~> 0.10)
109
+ haml (4.0.4)
110
+ tilt
111
+ hirb (0.7.1)
112
+ i18n (0.6.9)
113
+ jazz_hands (0.5.2)
114
+ awesome_print (~> 1.2)
115
+ coolline (>= 0.4.2)
116
+ hirb (~> 0.7.1)
117
+ pry (~> 0.9.12)
118
+ pry-debugger (~> 0.2.2)
119
+ pry-doc (~> 0.4.6)
120
+ pry-git (~> 0.2.3)
121
+ pry-rails (~> 0.3.2)
122
+ pry-remote (>= 0.1.7)
123
+ pry-stack_explorer (~> 0.4.9)
124
+ railties (>= 3.0, < 5.0)
125
+ listen (2.4.0)
126
+ celluloid (>= 0.15.2)
127
+ rb-fsevent (>= 0.9.3)
128
+ rb-inotify (>= 0.9)
129
+ lumberjack (1.0.4)
130
+ method_source (0.8.2)
131
+ mime-types (1.25.1)
132
+ minitest (4.7.5)
133
+ multi_json (1.8.2)
134
+ multipart-post (1.2.0)
135
+ parser (2.0.0)
136
+ ast (~> 1.1)
137
+ slop (~> 3.4, >= 3.4.5)
138
+ posix-spawn (0.3.8)
139
+ powerpack (0.0.9)
140
+ pry (0.9.12.4)
141
+ coderay (~> 1.0)
142
+ method_source (~> 0.8)
143
+ slop (~> 3.4)
144
+ pry-debugger (0.2.2)
145
+ debugger (~> 1.3)
146
+ pry (~> 0.9.10)
147
+ pry-doc (0.4.6)
148
+ pry (>= 0.9)
149
+ yard (>= 0.8)
150
+ pry-git (0.2.3)
151
+ diffy
152
+ grit
153
+ pry (>= 0.9.8)
154
+ pry-rails (0.3.2)
155
+ pry (>= 0.9.10)
156
+ pry-remote (0.1.7)
157
+ pry (~> 0.9)
158
+ slop (~> 3.0)
159
+ pry-stack_explorer (0.4.9.1)
160
+ binding_of_caller (>= 0.7)
161
+ pry (>= 0.9.11)
162
+ rack (1.5.2)
163
+ rack-test (0.6.2)
164
+ rack (>= 1.0)
165
+ railties (4.0.2)
166
+ actionpack (= 4.0.2)
167
+ activesupport (= 4.0.2)
168
+ rake (>= 0.8.7)
169
+ thor (>= 0.18.1, < 2.0)
170
+ rainbow (1.1.4)
171
+ rake (10.1.0)
172
+ rb-fsevent (0.9.3)
173
+ rb-inotify (0.9.2)
174
+ ffi (>= 0.5.0)
175
+ reek (1.3.4)
176
+ ruby2ruby (~> 2.0.2)
177
+ ruby_parser (~> 3.2)
178
+ sexp_processor
179
+ rest-client (1.6.7)
180
+ mime-types (>= 1.16)
181
+ rspec (2.14.1)
182
+ rspec-core (~> 2.14.0)
183
+ rspec-expectations (~> 2.14.0)
184
+ rspec-mocks (~> 2.14.0)
185
+ rspec-core (2.14.7)
186
+ rspec-expectations (2.14.4)
187
+ diff-lcs (>= 1.1.3, < 2.0)
188
+ rspec-mocks (2.14.4)
189
+ rubocop (0.15.0)
190
+ parser (~> 2.0)
191
+ powerpack (~> 0.0.6)
192
+ rainbow (>= 1.1.4)
193
+ ruby2ruby (2.0.6)
194
+ ruby_parser (~> 3.1)
195
+ sexp_processor (~> 4.0)
196
+ ruby_parser (3.2.2)
197
+ sexp_processor (~> 4.1)
198
+ sexp_processor (4.4.0)
199
+ simplecov (0.8.2)
200
+ docile (~> 1.1.0)
201
+ multi_json
202
+ simplecov-html (~> 0.8.0)
203
+ simplecov-html (0.8.0)
204
+ slop (3.4.7)
205
+ term-ansicolor (1.2.2)
206
+ tins (~> 0.8)
207
+ terminal-notifier-guard (1.5.3)
208
+ thor (0.18.1)
209
+ thread_safe (0.1.3)
210
+ atomic
211
+ tilt (2.0.0)
212
+ timers (1.1.0)
213
+ tins (0.13.1)
214
+ tzinfo (0.3.38)
215
+ yard (0.8.7.3)
216
+
217
+ PLATFORMS
218
+ ruby
219
+
220
+ DEPENDENCIES
221
+ bmo!
222
+ coveralls
223
+ flay
224
+ flog
225
+ guard
226
+ guard-bundler
227
+ guard-flay!
228
+ guard-flog!
229
+ guard-reek!
230
+ guard-rspec
231
+ guard-rubocop
232
+ jazz_hands
233
+ reek
234
+ rspec
235
+ rubocop
236
+ simplecov
237
+ terminal-notifier-guard
data/Guardfile ADDED
@@ -0,0 +1,27 @@
1
+ guard 'bundler' do
2
+ watch('Gemfile')
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
9
+ watch('spec/spec_helper.rb') { "spec" }
10
+ end
11
+
12
+ guard :rubocop do
13
+ watch(%r{.+\.rb$})
14
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
15
+ end
16
+
17
+ guard :flog do
18
+ watch(%r{^lib/(.+)\.rb$})
19
+ end
20
+
21
+ guard :reek do
22
+ watch(%r{^lib/(.+)\.rb$})
23
+ end
24
+
25
+ guard :flay do
26
+ watch(%r{^lib/(.+)\.rb$})
27
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Antoine Lyset
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ <img src="https://raw.github.com/antoinelyset/bmo/master/bmo.png"
2
+ alt="BMO"
3
+ align="right"
4
+ width="150px"/>
5
+
6
+ <br>
7
+ <br>
8
+ <br>
9
+
10
+ # BMO (Beemo)
11
+
12
+ BMO is a gem to Push Notifications to iOS (via APNS) and Android (via GCM)
13
+
14
+ ## Installation
15
+
16
+ ```
17
+ gem install bmo
18
+ ```
19
+
20
+ In Gemfile :
21
+
22
+ ```
23
+ gem 'bmo'
24
+ ```
25
+
26
+ ## APNS
27
+
28
+ ### Usage
29
+
30
+ ```ruby
31
+ token = "123456789" # The device token given to you by Apple
32
+ data = {aps: {alert: "Hello from BMO!"}}
33
+ BMO.send_ios_notification(token, data)
34
+ ```
35
+
36
+ The aps Hash content's is described here :
37
+
38
+ [https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html)
39
+
40
+ ### Configuration
41
+
42
+ Default Params :
43
+
44
+ ```ruby
45
+ BMO.configuration do |config|
46
+ config.apns.gateway_host = 'gateway.push.apple.com'
47
+ config.apns.gateway_port = 2195
48
+ config.apns.feedback_host = 'feedback.push.apple.com'
49
+ config.apns.feedback_port = 2196
50
+ config.apns.cert_path = nil
51
+ config.apns.cert_pass = nil
52
+ end
53
+ ```
54
+
55
+ If you set a cert_path option it will use a SSL encapsulation otherwise it will use Pure TCP.
56
+ This option is particularly useful if you use a stunnel.
57
+
58
+ ## GCM
59
+
60
+ ### Usage
61
+
62
+ ```ruby
63
+ token = "123456789" # The device token given to you by Apple
64
+ data = {message: "Hello from BMO!"}
65
+ BMO.send_android_notification(token, data)
66
+ ```
67
+
68
+ The data contennt is described here :
69
+
70
+ [http://developer.android.com/google/gcm/server.html](http://developer.android.com/google/gcm/server.html)
71
+
72
+ ### Configuration
73
+
74
+ Default Params :
75
+
76
+ ```ruby
77
+ BMO.configuration do |config|
78
+ config.gcm.gateway_url = 'https://android.googleapis.com/gcm/send'
79
+ config.gcm.api_key = nil
80
+ end
81
+ ```
82
+
83
+ You should set the api_key.
84
+
85
+ ## License
86
+
87
+ BMO is released under the [MIT
88
+ License](http://www.opensource.org/licenses/MIT)
data/bmo.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../lib/bmo/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = 'bmo'
7
+ gem.summary = 'Push notifications to iOS and Android devices'
8
+ gem.description = gem.summary
9
+ gem.authors = ['Antoine Lyset']
10
+ gem.email = ['antoinelyset@gmail.com']
11
+ gem.homepage = 'https://github.com/antoinelyset/bmo'
12
+ gem.require_paths = ['lib']
13
+ gem.version = BMO::VERSION.dup
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ gem.add_runtime_dependency('equalizer', '~> 0.0.0')
18
+ gem.add_runtime_dependency('faraday', '~> 0.8.0')
19
+ gem.license = 'MIT'
20
+ end
data/bmo.png ADDED
Binary file
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module APNS
4
+ # APNS Client Class
5
+ #
6
+ # @!attribute gateway_host
7
+ # @!attribute gateway_port
8
+ # @!attribute feedback_host
9
+ # @!attribute feedback_port
10
+ # @!attribute cert_path
11
+ # @!attribute cert_pass
12
+ class Client
13
+ attr_reader :cert_path, :cert_pass,
14
+ :gateway_host, :gateway_port,
15
+ :feedback_host, :feedback_port
16
+
17
+ # The constructor of the Client object
18
+ # it will only use a ssl connection if you pass a cert_path option
19
+ #
20
+ # @param gateway_host [String]
21
+ # @param gateway_port [Integer]
22
+ # @param feedback_host [String]
23
+ # @param feedback_port [Integer]
24
+ #
25
+ # @options options [String] :cert_pass
26
+ # @options options [String] :cert_path path to certificate file
27
+ #
28
+ def initialize(gateway_host, gateway_port,
29
+ feedback_host, feedback_port,
30
+ options = {})
31
+ @gateway_host = gateway_host
32
+ @gateway_port = gateway_port
33
+ @feedback_host = feedback_host
34
+ @feedback_port = feedback_port
35
+ @cert_path = options[:cert_path]
36
+ @cert_pass = options[:cert_pass]
37
+ end
38
+
39
+ # @param notification [Notification] the notification to send to Apple
40
+ #
41
+ def send_notification(notification)
42
+ connection = APNS::Connection.new(@gateway_host, @gateway_port,
43
+ @cert_path, @cert_pass)
44
+ connection.connect do |socket|
45
+ socket.write(notification.to_package)
46
+ end
47
+ end
48
+
49
+ # Get the Feedback from Apple
50
+ #
51
+ # @return <Array[FeedbackTuple]> A feedback tuple contains the time
52
+ # when Apple determined that the app no longer exists on the device,
53
+ # and a the token of device token
54
+ def get_feedback
55
+ connection = APNS::Connection.new(@feedback_host, @feedback_port,
56
+ @cert_path, @cert_pass)
57
+ connection.connect do |socket|
58
+ feedback_tuples = []
59
+ while (data = socket.read(38))
60
+ tuple = data.unpack('N1n1H*')
61
+ feedback_tuples << FeedbackTuple.new(tuple[0], tuple[2])
62
+ end
63
+ feedback_tuples
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Handle the Feedback Object
70
+ #
71
+ # @!attribute time [Time] Time when the device was notified
72
+ # but didn't have the app installed
73
+ # @!attribute token [String] The Token of the device notified wrongly
74
+ class FeedbackTuple
75
+ attr_reader :time, :token
76
+
77
+ def initialize(timestamp, token)
78
+ @time = Time.at(timestamp)
79
+ @token = token
80
+ end
81
+ end
82
+ end # class Client
83
+ end # module APNS
84
+ end # module BMO
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module APNS
4
+ # Handle the connection state SSL or Pure TCP
5
+ class Connection
6
+ attr_reader :host, :port, :cert_path, :cert_pass
7
+
8
+ def initialize(host, port, cert_path = nil, cert_pass = nil)
9
+ @host = host
10
+ @port = port
11
+ @cert_path = cert_path
12
+ @cert_pass = cert_pass
13
+ end
14
+
15
+ # Create a connection to Apple. If a cert_path exists it uses SSL else
16
+ # a pure TCPSocket. It then yields the socket and handles the closing
17
+ #
18
+ # @return The yielded return
19
+ def connect(&block)
20
+ socket = cert_path ? ssl_socket : tcp_socket
21
+
22
+ yielded = yield socket
23
+
24
+ socket.close
25
+ yielded
26
+ end
27
+
28
+ private
29
+
30
+ # @return [TCPSocket]
31
+ def tcp_socket
32
+ TCPSocket.new(host, port)
33
+ end
34
+
35
+ # @return [SSLSocket] the SSLSocket connected and setted to sync_close
36
+ # to sync tcp_socket and ssl_socket closing.
37
+ def ssl_socket
38
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
39
+ ssl_socket.sync_close = true
40
+ ssl_socket.connect
41
+ ssl_socket
42
+ end
43
+
44
+ # @return [SSLContext] SSLContext setted with the certificate
45
+ #
46
+ def ssl_context
47
+ cert = File.read(cert_path)
48
+ context = OpenSSL::SSL::SSLContext.new
49
+ context.cert = OpenSSL::X509::Certificate.new(cert)
50
+ context.key = OpenSSL::PKey::RSA.new(cert, cert_pass)
51
+ context
52
+ end
53
+ end # class Connection
54
+ end # module APNS
55
+ end # module BMO
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module APNS
4
+ # The Notification Class handles all the packaging logic
5
+ class Notification
6
+ # Define ==
7
+ include Equalizer.new(:device_token, :payload)
8
+
9
+ attr_reader :device_token, :payload
10
+
11
+ def initialize(device_token, payload)
12
+ @device_token = DeviceToken.new(device_token)
13
+ @payload = Payload.new(payload)
14
+ end
15
+
16
+ def to_package
17
+ payload_packaged = payload.to_package
18
+ device_token_packaged = device_token.to_package
19
+ [
20
+ 0, 0,
21
+ device_token_packaged.size,
22
+ device_token_packaged,
23
+ 0,
24
+ payload_packaged.size,
25
+ payload_packaged
26
+ ].pack('ccca*cca*')
27
+ end
28
+
29
+ def validate!
30
+ device_token.validate!
31
+ payload.validate!
32
+ end
33
+
34
+ private
35
+
36
+ # The Payload contains the data sent to Apple
37
+ class Payload
38
+ class PayloadTooLarge < Exception; end
39
+
40
+ MAX_BYTE_SIZE = 256
41
+
42
+ # Define ==
43
+ include Equalizer.new(:data)
44
+
45
+ attr_reader :data
46
+
47
+ def initialize(data)
48
+ @data = Utils.coerce_to_symbols(data)
49
+ end
50
+
51
+ def to_package
52
+ data.to_json.bytes.to_a.pack('C*')
53
+ end
54
+
55
+ def validate!
56
+ if to_package.size > MAX_BYTE_SIZE
57
+ str = <<-EOS
58
+ Payload size should be less than #{Payload::MAX_BYTE_SIZE} bytes"
59
+ EOS
60
+ fail PayloadTooLarge, str
61
+ end
62
+ true
63
+ end
64
+ end # class Payload
65
+
66
+ # The DeviceToken is the id of a Device for an App
67
+ class DeviceToken
68
+ # Define ==
69
+ include Equalizer.new(:token)
70
+
71
+ class MalformedDeviceToken < Exception; end
72
+
73
+ attr_reader :token
74
+
75
+ def initialize(token)
76
+ @token = token
77
+ end
78
+
79
+ def to_package
80
+ [token].pack('H*')
81
+ end
82
+
83
+ def validate!
84
+ unless token =~ /^[a-z0-9]{64}$/i
85
+ fail(MalformedDeviceToken, 'Malformed Device Token')
86
+ end
87
+ true
88
+ end
89
+ end # class DeviceToken
90
+ end # class Notification
91
+ end # module APNS
92
+ end # module BMO
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ # Main BMO module
3
+ module BMO
4
+ # Handles all the configuration per provider
5
+ class Configuration
6
+ attr_reader :apns, :gcm
7
+
8
+ def initialize
9
+ @apns = APNS.new
10
+ @gcm = GCM.new
11
+ end
12
+
13
+ # APNS Configuration
14
+ #
15
+ # @!attribute gateway_host
16
+ # @!attribute gateway_port
17
+ # @!attribute feedback_host
18
+ # @!attribute feedback_port
19
+ # @!attribute cert_path
20
+ # @!attribute cert_pass
21
+ class APNS
22
+ attr_accessor :cert_path, :cert_pass,
23
+ :gateway_host, :gateway_port,
24
+ :feedback_host, :feedback_port
25
+
26
+ def initialize
27
+ @gateway_host = 'gateway.push.apple.com'
28
+ @gateway_port = 2195
29
+ @feedback_host = 'feedback.push.apple.com'
30
+ @feedback_port = 2196
31
+ @cert_path = nil
32
+ @cert_pass = nil
33
+ end
34
+ end
35
+
36
+ # GCM Configuration
37
+ class GCM
38
+ attr_accessor :gateway_url, :api_key
39
+ def initialize
40
+ @gateway_url = 'https://android.googleapis.com/gcm/send'
41
+ @api_key = nil
42
+ end
43
+ end
44
+ end # class Configuration
45
+
46
+ module_function
47
+
48
+ def configure
49
+ yield(configuration) if block_given?
50
+ configuration
51
+ end
52
+
53
+ def configuration
54
+ @configuration ||= Configuration.new
55
+ end
56
+
57
+ # Needed because we can't use module_function and attr_writer
58
+ # rubocop:disable TrivialAccessors
59
+ def configuration=(configuration)
60
+ @configuration = configuration
61
+ end
62
+ # rubocop:enable
63
+
64
+ def reset_configuration
65
+ @configuration = Configuration.new
66
+ end
67
+ end # BMO
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module GCM
4
+ # GCM Client Class
5
+ class Client
6
+ attr_reader :gateway_url, :api_key
7
+
8
+ def initialize(gateway_url, api_key)
9
+ @gateway_url = gateway_url
10
+ @api_key = api_key
11
+ end
12
+
13
+ # @param notification [Notification] the notification to send to Google
14
+ #
15
+ def send_notification(notification)
16
+ connection = GCM::Connection.new(gateway_url, api_key)
17
+ connection.connect do |request|
18
+ request.body = notification.to_package
19
+ end
20
+ end
21
+ end # class Client
22
+ end # module APNS
23
+ end # module BMO
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module GCM
4
+ # Handle the connection state SSL or Pure TCP
5
+ class Connection
6
+ def initialize(gateway_url, api_key)
7
+ @gateway_url = gateway_url
8
+ @faraday_connection = Faraday::Connection.new(gateway_url)
9
+ @api_key = api_key
10
+ end
11
+
12
+ def connect(&block)
13
+ faraday_connection.post(gateway_url) do |request|
14
+ request.headers.merge!(content_type: 'application/json',
15
+ authorization: "key=#{api_key}")
16
+ yield request
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ attr_reader :faraday_connection, :gateway_url, :api_key
23
+ end # class Connection
24
+ end # module GCM
25
+ end # module BMO
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ module GCM
4
+ # The Notification Class handles all the packaging logic
5
+ class Notification
6
+ # Define ==
7
+ include Equalizer.new(:device_token, :payload)
8
+
9
+ attr_reader :device_token, :payload
10
+
11
+ def initialize(device_token, payload)
12
+ @device_token = device_token
13
+ @payload = Payload.new(payload)
14
+ end
15
+
16
+ def to_package
17
+ {
18
+ registration_ids: [device_token],
19
+ data: payload.to_package
20
+ }.to_json
21
+ end
22
+
23
+ def validate!
24
+ payload.validate!
25
+ end
26
+
27
+ private
28
+
29
+ # The Payload contains the data sent to Apple
30
+ class Payload
31
+ # Error Raised when the payload packaged > MAX_BYTE_SIZE
32
+ class PayloadTooLarge < Exception; end
33
+
34
+ MAX_BYTE_SIZE = 4096
35
+
36
+ # Define ==
37
+ include Equalizer.new(:data)
38
+
39
+ attr_reader :data
40
+
41
+ def initialize(data)
42
+ @data = Utils.coerce_to_symbols(data)
43
+ end
44
+
45
+ def to_package
46
+ data
47
+ end
48
+
49
+ def validate!
50
+ if to_package.size > MAX_BYTE_SIZE
51
+ str = <<-EOS
52
+ Payload size should be less than #{Payload::MAX_BYTE_SIZE} bytes"
53
+ EOS
54
+ fail PayloadTooLarge, str
55
+ end
56
+ true
57
+ end
58
+ end # class Payload
59
+ end # class Notification
60
+ end # module APNS
61
+ end # module BMO
data/lib/bmo/utils.rb ADDED
@@ -0,0 +1,17 @@
1
+ # encoding: utf-8
2
+ module BMO
3
+ # Utility module
4
+ module Utils
5
+ module_function
6
+
7
+ # Coerce string hash keys to symbols
8
+ def coerce_to_symbols(hash)
9
+ hash_symbolized = {}
10
+ hash.each_pair do |key, value|
11
+ key = key.to_sym if key.respond_to?(:to_sym)
12
+ hash_symbolized[key] = value
13
+ end
14
+ hash_symbolized
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ # Main BMO module, version get right here
3
+ module BMO
4
+ VERSION = '0.8.0'.freeze
5
+ end
data/lib/bmo.rb ADDED
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+
3
+ require 'socket'
4
+ require 'openssl'
5
+ require 'json'
6
+ require 'equalizer'
7
+ require 'faraday'
8
+
9
+ require 'bmo/version'
10
+ require 'bmo/configuration'
11
+ require 'bmo/utils'
12
+
13
+ require 'bmo/apns/notification'
14
+ require 'bmo/apns/connection'
15
+ require 'bmo/apns/client'
16
+
17
+ require 'bmo/gcm/notification'
18
+ require 'bmo/gcm/connection'
19
+ require 'bmo/gcm/client'
20
+
21
+ # Main BMO namespace
22
+ module BMO
23
+ # All the methods will be Class Methods
24
+ module_function
25
+
26
+ # Send ios notification with the configuration of BMO
27
+ # (see #BMO::Configuration)
28
+ #
29
+ # @param device_token [String]
30
+ # @param data [Hash] The data you want to send
31
+ #
32
+ # @return The Socket#write return
33
+ #
34
+ # @see https://developer.apple.com/library/ios/documentation/
35
+ # NetworkingInternet/Conceptual/RemoteNotificationsPG/
36
+ # Chapters/ApplePushService.html
37
+ def send_ios_notification(device_token, data)
38
+ notification = APNS::Notification.new(device_token, data)
39
+ apns_client.send_notification(notification)
40
+ end
41
+
42
+ # Get the iOS Feedback tuples
43
+ #
44
+ # @return [Array<FeedbackTuple>] Feedback Object containing
45
+ # a time and a token
46
+ def get_ios_feedback
47
+ apns_client.get_feedback
48
+ end
49
+
50
+ # Send android notification with the configuration of BMO
51
+ # (see #BMO::Configuration)
52
+ #
53
+ # @param device_token [String]
54
+ # @param data [Hash] The data you want to send
55
+ #
56
+ # @return [Faraday::Response] The HTTP Response
57
+ #
58
+ # @see http://developer.android.com/google/gcm/server.html]
59
+ def send_android_notification(device_token, data)
60
+ notification = GCM::Notification.new(device_token, data)
61
+ android_client.send_notification(notification)
62
+ end
63
+
64
+ private
65
+
66
+ def apns_client
67
+ conf = BMO.configuration.apns
68
+ APNS::Client.new(conf.gateway_host,
69
+ conf.gateway_port,
70
+ conf.feedback_host,
71
+ conf.feedback_port,
72
+ cert_path: conf.cert_path,
73
+ cert_pass: conf.cert_pass)
74
+ end
75
+
76
+ def gcm_client
77
+ conf = BMO.configuration.gcm
78
+ GCM::Client.new(conf.gateway_url,
79
+ conf.api_key)
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '../../../spec_helper'
4
+
5
+ describe BMO::APNS::Notification::DeviceToken do
6
+ describe '#token' do
7
+ it 'returns the token' do
8
+ device_token = described_class.new('abc')
9
+ expect(device_token.token).to eq('abc')
10
+ end
11
+ end
12
+
13
+ describe '#==' do
14
+ it 'returns true for equal device token' do
15
+ device_token = described_class.new('abc')
16
+ device_token_bis = described_class.new('abc')
17
+ expect(device_token == device_token_bis).to be_true
18
+ end
19
+
20
+ it 'returns true for equal device token' do
21
+ device_token = described_class.new('abc')
22
+ device_token_bis = described_class.new('abc')
23
+ expect(device_token).to eq(device_token_bis)
24
+ end
25
+
26
+ it 'returns false for equal device token' do
27
+ device_token = described_class.new('abc')
28
+ device_token_bis = described_class.new('def')
29
+ expect(device_token).to_not eq(device_token_bis)
30
+ end
31
+
32
+ it 'returns true for equal device token' do
33
+ device_token = described_class.new('abc')
34
+ device_token_bis = described_class.new('def')
35
+ expect(device_token == device_token_bis).to be_false
36
+ end
37
+ end
38
+
39
+ describe '#validate!' do
40
+ it 'returns true if the token is 64 chars' do
41
+ device_token = described_class.new('a' * 64)
42
+ expect(device_token.validate!).to be_true
43
+ end
44
+
45
+ it 'returns false if the token is not 64 chars' do
46
+ device_token = described_class.new('a' * 63)
47
+ expect { device_token.validate! }.to raise_error
48
+ BMO::APNS::Notification::DeviceToken::MalformedDeviceToken
49
+ end
50
+
51
+ it 'returns false if the token contains a special char' do
52
+ device_token = described_class.new(('a' * 63) + '"')
53
+ expect { device_token.validate! }.to raise_error
54
+ BMO::APNS::Notification::DeviceToken::MalformedDeviceToken
55
+ end
56
+ end
57
+
58
+ describe '#to_package' do
59
+ it 'returns the packaged token' do
60
+ device_token = described_class.new('0' * 64)
61
+ expect(device_token.to_package).to eq("\x00" * 32)
62
+ end
63
+ end
64
+ end
65
+
66
+ describe BMO::APNS::Notification::Payload do
67
+ it 'coerce hash keys to symbols' do
68
+ payload = described_class.new('Finn' => 'The Human')
69
+ expect(payload.data).to eq(Finn: 'The Human')
70
+ end
71
+
72
+ it "doesn't coerce incompatible types" do
73
+ payload = described_class.new(1 => 'For Money')
74
+ expect(payload.data).to eq(1 => 'For Money')
75
+ end
76
+
77
+ it 'returns true for equality between coerced hash and symbolized hash ' do
78
+ payload = described_class.new('Jake' => 'The Dog')
79
+ expect(payload).to eq(described_class.new(Jake: 'The Dog'))
80
+ end
81
+ end
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require 'bmo'
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bmo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Antoine Lyset
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: equalizer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.8.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.8.0
41
+ description: Push notifications to iOS and Android devices
42
+ email:
43
+ - antoinelyset@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - .rspec
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - Guardfile
53
+ - LICENSE
54
+ - README.md
55
+ - bmo.gemspec
56
+ - bmo.png
57
+ - lib/bmo.rb
58
+ - lib/bmo/apns/client.rb
59
+ - lib/bmo/apns/connection.rb
60
+ - lib/bmo/apns/notification.rb
61
+ - lib/bmo/configuration.rb
62
+ - lib/bmo/gcm/client.rb
63
+ - lib/bmo/gcm/connection.rb
64
+ - lib/bmo/gcm/notification.rb
65
+ - lib/bmo/utils.rb
66
+ - lib/bmo/version.rb
67
+ - spec/bmo/apns/notification_spec.rb
68
+ - spec/spec_helper.rb
69
+ homepage: https://github.com/antoinelyset/bmo
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.1.11
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Push notifications to iOS and Android devices
93
+ test_files:
94
+ - spec/bmo/apns/notification_spec.rb
95
+ - spec/spec_helper.rb
96
+ has_rdoc: