ruby-push-notifications 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +35 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +55 -0
  6. data/LICENSE +22 -0
  7. data/README.md +56 -0
  8. data/Rakefile +12 -0
  9. data/examples/apns copy.rb +17 -0
  10. data/examples/apns.rb +16 -0
  11. data/examples/gcm copy.rb +16 -0
  12. data/examples/gcm.rb +16 -0
  13. data/lib/ruby-push-notifications.rb +8 -0
  14. data/lib/ruby-push-notifications/apns.rb +19 -0
  15. data/lib/ruby-push-notifications/apns/apns_connection.rb +45 -0
  16. data/lib/ruby-push-notifications/apns/apns_notification.rb +62 -0
  17. data/lib/ruby-push-notifications/apns/apns_pusher.rb +51 -0
  18. data/lib/ruby-push-notifications/gcm.rb +7 -0
  19. data/lib/ruby-push-notifications/gcm/gcm_connection.rb +31 -0
  20. data/lib/ruby-push-notifications/gcm/gcm_error.rb +14 -0
  21. data/lib/ruby-push-notifications/gcm/gcm_notification.rb +21 -0
  22. data/lib/ruby-push-notifications/gcm/gcm_pusher.rb +17 -0
  23. data/lib/ruby-push-notifications/gcm/gcm_response.rb +52 -0
  24. data/lib/ruby-push-notifications/gcm/gcm_result.rb +38 -0
  25. data/lib/ruby-push-notifications/version.rb +3 -0
  26. data/ruby-push-notifications.gemspec +26 -0
  27. data/spec/factories.rb +10 -0
  28. data/spec/factories/notifications.rb +15 -0
  29. data/spec/ruby-push-notifications/apns/apns_connection_spec.rb +79 -0
  30. data/spec/ruby-push-notifications/apns/apns_notification_spec.rb +30 -0
  31. data/spec/ruby-push-notifications/apns/apns_pusher_spec.rb +299 -0
  32. data/spec/ruby-push-notifications/gcm/gcm_connection_spec.rb +32 -0
  33. data/spec/ruby-push-notifications/gcm/gcm_notification_spec.rb +24 -0
  34. data/spec/ruby-push-notifications/gcm/gcm_pusher_spec.rb +45 -0
  35. data/spec/ruby-push-notifications/gcm/gcm_response_spec.rb +82 -0
  36. data/spec/spec_helper.rb +110 -0
  37. data/spec/support/dummy.pem +44 -0
  38. data/spec/support/factory_girl.rb +5 -0
  39. metadata +177 -0
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
35
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.2.0"
4
+ - "2.1.5"
5
+ - "2.0.0"
6
+ - "1.9.3"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby-push-notifications.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby-push-notifications (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (4.2.0)
10
+ i18n (~> 0.7)
11
+ json (~> 1.7, >= 1.7.7)
12
+ minitest (~> 5.1)
13
+ thread_safe (~> 0.3, >= 0.3.4)
14
+ tzinfo (~> 1.1)
15
+ addressable (2.3.7)
16
+ crack (0.4.2)
17
+ safe_yaml (~> 1.0.0)
18
+ diff-lcs (1.2.5)
19
+ factory_girl (4.5.0)
20
+ activesupport (>= 3.0.0)
21
+ i18n (0.7.0)
22
+ json (1.8.2)
23
+ minitest (5.5.1)
24
+ rake (10.4.2)
25
+ rspec (3.2.0)
26
+ rspec-core (~> 3.2.0)
27
+ rspec-expectations (~> 3.2.0)
28
+ rspec-mocks (~> 3.2.0)
29
+ rspec-core (3.2.0)
30
+ rspec-support (~> 3.2.0)
31
+ rspec-expectations (3.2.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.2.0)
34
+ rspec-mocks (3.2.0)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.2.0)
37
+ rspec-support (3.2.1)
38
+ safe_yaml (1.0.4)
39
+ thread_safe (0.3.4)
40
+ tzinfo (1.2.2)
41
+ thread_safe (~> 0.1)
42
+ webmock (1.20.4)
43
+ addressable (>= 2.3.6)
44
+ crack (>= 0.3.2)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ bundler (~> 1.6)
51
+ factory_girl (~> 4.0)
52
+ rake (~> 10.4)
53
+ rspec (~> 3.2)
54
+ ruby-push-notifications!
55
+ webmock (~> 1.20)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Carlos Alonso
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Ruby::Push::Notifications [![Build Status](https://travis-ci.org/calonso/ruby-push-notifications.svg)](https://travis-ci.org/calonso/ruby-push-notifications) [![Dependency Status](https://gemnasium.com/calonso/ruby-push-notifications.svg)](https://gemnasium.com/calonso/ruby-push-notifications)
2
+
3
+ ###iOS and Android Push Notifications made easy!
4
+
5
+ ## Features
6
+
7
+ * iOS and Android support
8
+ * Complete error and retry management
9
+ * Easy and intuitive API
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'ruby-push-notifications'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install ruby-push-notifications
24
+
25
+ ## Usage
26
+
27
+ **Ruby Push Notifications** gem usage is really easy.
28
+
29
+ 1. After installing, require the gem
30
+ 2. Create one or more notifications
31
+ 3. Create the corresponding `pusher`
32
+ 4. Push!!
33
+ 5. Get feedback
34
+
35
+ For completely detailed examples:
36
+
37
+ 1. [Apple iOS example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/apns.rb)
38
+ 2. [Google Android example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/gcm.rb)
39
+
40
+ ## Pending tasks
41
+
42
+ Feel free to contribute!!
43
+
44
+ * Validate iOS notifications format and max size
45
+ * Validate iOS tokens format
46
+ * Validate GCM registration ids format
47
+ * Validate GCM notifications format and max size
48
+ * Split GCM notifications in parts if more than 1000 destinations are given (currently raising exception)
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it ( https://github.com/calonso/ruby-push-notifications/fork )
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
12
+
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'ruby-push-notifications'
6
+
7
+ tokens = [
8
+ '6400b2a275eacf035d22d285d0ffb58fde5b647e54c7da882703136ba4ecd7d6',
9
+ '16400b2a275eacf035d22d285d0ffb58fde5b647e54c7da882703136ba4ecd7d6',
10
+ '6400b2a275eacf035d22d285d0ffb58fde5b647e54c7da882703136ba4ecd7d6'
11
+ ]
12
+
13
+ notification = RubyPushNotifications::APNS::APNSNotification.new tokens, { aps: { alert: 'Hello APNS World!', sound: 'true', badge: 1 } }
14
+
15
+ pusher = RubyPushNotifications::APNS::APNSPusher.new(File.read('/Users/calonso/Desktop/apns.pem'), true)
16
+ pusher.push [notification]
17
+ p notification.results
data/examples/apns.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'ruby-push-notifications'
6
+
7
+ tokens = [
8
+ 'First token here',
9
+ 'Second token here'
10
+ ]
11
+
12
+ notification = RubyPushNotifications::APNS::APNSNotification.new tokens, { aps: { alert: 'Hello APNS World!', sound: 'true', badge: 1 } }
13
+
14
+ pusher = RubyPushNotifications::APNS::APNSPusher.new(File.read('/path/to/your/apps/certificate.pem'), true)
15
+ pusher.push [notification]
16
+ p notification.results
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'ruby-push-notifications'
6
+
7
+ registration_ids = [
8
+ 'APA91bFrtpS1bEo4BtTjE3V-GvRTX6KATZmh3ZGZ-wrQVY5UvsuQ4F-UmShwwjiG4uY0qtXG0RS4Tq2ir6t7gN6ziU7fpb1HtuiUKpdkY6WpE38mCxTa7cNeotIGgaKXOdNTEV10GU6Txp-Oxakqavuga6SrYNoVyA',
9
+ 'APA91bFwufZkwhPhhLlQyIbM3MUksfxSvXXmXRP9L8LrJ8RMvUbRExERKHAzDR_pXZryKYICuqdS18fiytmks0WmKTZFd9_5AR8nK_m-5djqzM7AfBOyyv7Hy1uWCunJ2FcAbapGfaFYOTaW3MQGxjUIav_8Wj1R0OYANVMvZNGSDcu_j5wA80Y'
10
+ ]
11
+
12
+ notification = RubyPushNotifications::GCM::GCMNotification.new registration_ids, { text: 'Hello GCM World!' }
13
+
14
+ pusher = RubyPushNotifications::GCM::GCMPusher.new 'AIzaSyAEO2CE_ipX217WwWsbHvGr8fiVHDdKUIc'
15
+ pusher.push [notification]
16
+ p notification.results
data/examples/gcm.rb ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'ruby-push-notifications'
6
+
7
+ registration_ids = [
8
+ 'First registration id here',
9
+ 'Second registration id here'
10
+ ]
11
+
12
+ notification = RubyPushNotifications::GCM::GCMNotification.new registration_ids, { text: 'Hello GCM World!' }
13
+
14
+ pusher = RubyPushNotifications::GCM::GCMPusher.new "Your app's GCM key"
15
+ pusher.push [notification]
16
+ p notification.results
@@ -0,0 +1,8 @@
1
+ require "ruby-push-notifications/version"
2
+
3
+ module RubyPushNotifications
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'ruby-push-notifications/apns'
8
+ require 'ruby-push-notifications/gcm'
@@ -0,0 +1,19 @@
1
+
2
+ module RubyPushNotifications::APNS
3
+ NO_ERROR_STATUS_CODE = 0
4
+ PROCESSING_ERROR_STATUS_CODE = 1
5
+ MISSING_DEVICE_TOKEN_STATUS_CODE = 2
6
+ MISSING_TOPIC_STATUS_CODE = 3 # You're writing to the TCPSocket rather than the SSL one
7
+ MISSING_PAYLOAD_STATUS_CODE = 4
8
+ INVALID_TOKEN_SIZE_STATUS_CODE = 5
9
+ INVALID_TOPIC_SIZE_STATUS_CODE = 6
10
+ INVALID_PAYLOAD_SIZE_STATUS_CODE = 7
11
+ INVALID_TOKEN_STATUS_CODE = 8 # The token is for dev and the env is prod or viceversa, or simply wrong
12
+ SHUTDOWN_STATUS_CODE = 10
13
+ UNKNOWN_ERROR_STATUS_CODE = 255
14
+ end
15
+
16
+ require 'ruby-push-notifications/apns/apns_connection'
17
+ require 'ruby-push-notifications/apns/apns_notification'
18
+ require 'ruby-push-notifications/apns/apns_pusher'
19
+
@@ -0,0 +1,45 @@
1
+
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'forwardable'
5
+
6
+ module RubyPushNotifications
7
+ module APNS
8
+ class APNSConnection
9
+ extend Forwardable
10
+
11
+ APNS_SANDBOX_URL = 'gateway.sandbox.push.apple.com'
12
+ APNS_PRODUCTION_URL = 'gateway.push.apple.com'
13
+ APNS_PORT = 2195
14
+
15
+ def_delegators :@sslsock, :write, :flush, :to_io, :read
16
+
17
+ def self.open(cert, sandbox)
18
+ ctx = OpenSSL::SSL::SSLContext.new
19
+ ctx.key = OpenSSL::PKey::RSA.new cert
20
+ ctx.cert = OpenSSL::X509::Certificate.new cert
21
+
22
+ h = host sandbox
23
+ socket = TCPSocket.new h, APNS_PORT
24
+ ssl = OpenSSL::SSL::SSLSocket.new socket, ctx
25
+ ssl.connect
26
+
27
+ new socket, ssl
28
+ end
29
+
30
+ def self.host(sandbox)
31
+ sandbox ? APNS_SANDBOX_URL : APNS_PRODUCTION_URL
32
+ end
33
+
34
+ def initialize(tcpsock, sslsock)
35
+ @tcpsock = tcpsock
36
+ @sslsock = sslsock
37
+ end
38
+
39
+ def close
40
+ @sslsock.close
41
+ @tcpsock.close
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,62 @@
1
+
2
+ require 'json'
3
+
4
+ module RubyPushNotifications
5
+ module APNS
6
+ class APNSNotification
7
+
8
+ WEEKS_4 = 2419200 # 4 weeks
9
+
10
+ attr_accessor :results
11
+
12
+ def initialize(tokens, data)
13
+ @tokens = tokens
14
+ @data = data
15
+ end
16
+
17
+ def each_message(starting_id)
18
+ @tokens.each_with_index do |token, i|
19
+ # Notification = 2(1), FrameLength(4), items(FrameLength)
20
+ # Item = ItemID(1), ItemLength(2), data(ItemLength)
21
+ # Items:
22
+ # Device Token => Id: 1, length: 32, data: binary device token
23
+ # Payload => Id: 2, length: ??, data: json formatted payload
24
+ # Notification ID => Id: 3, length: 4, data: notif id as int
25
+ # Expiration Date => Id: 4, length: 4, data: Unix timestamp as int
26
+ # Priority => Id: 5, length: 1, data: 10 as 1 byte int
27
+ bytes = device_token(token) + payload + notification_id(starting_id + i) + expiration_date + priority
28
+ yield [2, bytes.bytesize, bytes].pack 'cNa*'
29
+ end
30
+ end
31
+
32
+ def count
33
+ @tokens.count
34
+ end
35
+
36
+ private
37
+
38
+ def device_token(token)
39
+ [1, 32, token].pack 'cnH64'
40
+ end
41
+
42
+ def payload
43
+ @encoded_payload ||= -> {
44
+ json = JSON.dump(@data).force_encoding 'ascii-8bit'
45
+ [2, json.bytesize, json].pack 'cna*'
46
+ }.call
47
+ end
48
+
49
+ def notification_id(id)
50
+ [3, 4, id].pack 'cnN'
51
+ end
52
+
53
+ def expiration_date
54
+ [4, 4, (Time.now + WEEKS_4).to_i].pack 'cnN'
55
+ end
56
+
57
+ def priority
58
+ [5, 1, 10].pack 'cnc'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module RubyPushNotifications
3
+ module APNS
4
+ class APNSPusher
5
+
6
+ def initialize(certificate, sandbox)
7
+ @certificate = certificate
8
+ @sandbox = sandbox
9
+ end
10
+
11
+ def push(notifications)
12
+ conn = APNSConnection.open @certificate, @sandbox
13
+
14
+ binaries = notifications.each_with_object([]) do |notif, binaries|
15
+ notif.each_message(binaries.count) do |msg|
16
+ binaries << msg
17
+ end
18
+ end
19
+
20
+ results = []
21
+ i = 0
22
+ while i < binaries.count
23
+ conn.write binaries[i]
24
+
25
+ if i == binaries.count-1
26
+ conn.flush
27
+ rs, = IO.select([conn], nil, nil, 2)
28
+ else
29
+ rs, = IO.select([conn], [conn])
30
+ end
31
+ if rs && rs.any?
32
+ err = rs[0].read(6).unpack 'ccN'
33
+ results.slice! err[2]..-1
34
+ results << err[1]
35
+ i = err[2]
36
+ conn = APNSConnection.open @certificate, @sandbox
37
+ else
38
+ results << NO_ERROR_STATUS_CODE
39
+ end
40
+ i += 1
41
+ end
42
+
43
+ conn.close
44
+
45
+ notifications.each do |notif|
46
+ notif.results = results.slice! 0, notif.count
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end