rpush 3.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -3
  3. data/README.md +26 -0
  4. data/lib/generators/rpush_migration_generator.rb +1 -0
  5. data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +9 -0
  6. data/lib/rpush/client/active_model/apns/notification.rb +1 -1
  7. data/lib/rpush/client/active_model/pushy/app.rb +20 -0
  8. data/lib/rpush/client/active_model/pushy/notification.rb +31 -0
  9. data/lib/rpush/client/active_model/pushy/time_to_live_validator.rb +14 -0
  10. data/lib/rpush/client/active_model.rb +4 -0
  11. data/lib/rpush/client/active_record/pushy/app.rb +11 -0
  12. data/lib/rpush/client/active_record/pushy/notification.rb +11 -0
  13. data/lib/rpush/client/active_record.rb +3 -0
  14. data/lib/rpush/client/redis/app.rb +1 -0
  15. data/lib/rpush/client/redis/pushy/app.rb +16 -0
  16. data/lib/rpush/client/redis/pushy/notification.rb +18 -0
  17. data/lib/rpush/client/redis.rb +3 -0
  18. data/lib/rpush/configuration.rb +1 -1
  19. data/lib/rpush/daemon/dispatcher/apns_http2.rb +2 -1
  20. data/lib/rpush/daemon/gcm/delivery.rb +2 -2
  21. data/lib/rpush/daemon/pushy/delivery.rb +90 -0
  22. data/lib/rpush/daemon/pushy.rb +9 -0
  23. data/lib/rpush/daemon.rb +3 -0
  24. data/lib/rpush/version.rb +2 -2
  25. data/spec/functional/pushy_spec.rb +22 -0
  26. data/spec/support/active_record_setup.rb +3 -1
  27. data/spec/unit/client/active_record/apns/notification_spec.rb +4 -2
  28. data/spec/unit/client/active_record/pushy/app_spec.rb +17 -0
  29. data/spec/unit/client/active_record/pushy/notification_spec.rb +65 -0
  30. data/spec/unit/daemon/pushy/delivery_spec.rb +159 -0
  31. metadata +28 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7de52efb63f861275eeec5a6c22d80ae891df704140742127da8709a873b373
4
- data.tar.gz: e9c82eb246ba4f268f2553d974703c78a8eb8fa2d3df87b603956382f8823aa2
3
+ metadata.gz: d01653298bef15cc73e0010be8bb353c3adcb0aecc442f3883858415189a9265
4
+ data.tar.gz: ff64bca96549d684dfd2c4c6c0184dd55d3c93750c377a857f99d7ddb51405ec
5
5
  SHA512:
6
- metadata.gz: 7d17f2506d4b26c69ddd9b371c55997b7c64a7244597393ee8d2f95542abbc43fe4d1f753020ddb9f9e180c5ca5745926d88406ea07385b0c8a129768cb5df30
7
- data.tar.gz: 3c222ffb736101b9c8178766402b8adf9f7f41ae94187344ec2c2449ade4ec47a89ea5ef1c9cced95573dd1b896d4ac5be43651aa42d86f04b8e732080c3fd0f
6
+ metadata.gz: b4ced1c3c0a14607758568a0e717b75651b4721dcb10b6f0466450e65e8a3665f6b696b90d586085e358c79c6da7cc11c336b74ddeb82561be56a5cb5bcc58eb
7
+ data.tar.gz: 69f82291e5c071e93703f25385397a2dbc968f2f22685721382df52f657a2f788597dea2d110778ee342b82d0e0dab043da96f66d3a9402fcedbaf50d0a8df28
data/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
- ## HEAD
1
+ ## HEAD
2
+
3
+ ### Breaking Changes
4
+
5
+ - None
6
+
7
+ ### Added
8
+
9
+ - None
10
+
11
+ ### Fixed
12
+
13
+ - None
14
+
15
+ ## 3.1.0 (2018-04-11)
16
+
17
+ When upgrading, don't forget to run `bundle exec rpush init` to get all the latest migrations.
2
18
 
3
19
  ### Breaking Changes
4
20
 
@@ -6,11 +22,20 @@
6
22
 
7
23
  ### Added
8
24
 
9
- - None
25
+ - Added sandbox URL to `ApnsHttp2` dispatcher ([#392](https://github.com/rpush/rpush/pull/392) by [@brianlittmann](https://github.com/brianlittmann))
26
+
27
+ ### Features
28
+
29
+ - Added support for [Pushy](https://pushy.me/) ([#404](https://github.com/rpush/rpush/pull/404) by [@zabolotnov87](https://github.com/zabolotnov87))
10
30
 
11
31
  ### Fixed
12
32
 
13
- - None
33
+ - `@notification.app` triggers loading of association :app ([#410](https://github.com/rpush/rpush/issues/410) by [@loadhigh](https://github.com/loadhigh))
34
+ - APNS expiry should be number of seconds since epoch ([#416](https://github.com/rpush/rpush/issues/416) by [@loadhigh](https://github.com/loadhigh))
35
+
36
+ ### Enhancements
37
+
38
+ - Test rpush with Ruby 2.5 on Travis CI ([#407](https://github.com/rpush/rpush/pull/407) by [@Atul9](https://github.com/Atul9))
14
39
 
15
40
  ## 3.0.2 (2018-01-08)
16
41
 
data/README.md CHANGED
@@ -17,6 +17,7 @@ Rpush aims to be the *de facto* gem for sending push notifications in Ruby. Its
17
17
  * [**Firebase Cloud Messaging**](#firebase-cloud-messaging) (used to be Google Cloud Messaging)
18
18
  * [**Amazon Device Messaging**](#amazon-device-messaging)
19
19
  * [**Windows Phone Push Notification Service**](#windows-phone-notification-service)
20
+ * [**Pushy**](#pushy)
20
21
 
21
22
  #### Feature Highlights
22
23
 
@@ -206,6 +207,31 @@ n.badge = 4
206
207
  n.save!
207
208
  ```
208
209
 
210
+ #### Pushy
211
+
212
+ [Pushy](https://pushy.me/) is a highly-reliable push notification gateway, based on [MQTT](https://pushy.me/support#what-is-mqtt) protocol for cross platform push notification delivery that includes web, Android, and iOS. One of its advantages is it allows for reliable notification delivery to Android devices in China where Google Cloud Messaging and Firebase Cloud Messaging are blocked and to custom hardware devices that use Android OS but are not using Google Play Services.
213
+
214
+ Note: current implementation of Pushy only supports Android devices and does not include [subscriptions](https://pushy.me/docs/android/subscribe-topics).
215
+
216
+ ```ruby
217
+ app = Rpush::Pushy::App.new
218
+ app.name = "android_app"
219
+ app.api_key = YOUR_API_KEY
220
+ app.connections = 1
221
+ app.save!
222
+ ```
223
+
224
+ ```ruby
225
+ n = Rpush::Pushy::Notification.new
226
+ n.app = Rpush::Pushy::App.find_by_name("android_app")
227
+ n.registration_ids = ["..."]
228
+ n.data = { message: "hi mom!"}
229
+ n.time_to_live = 60 # seconds
230
+ n.save!
231
+ ```
232
+
233
+ For more documentation on [Pushy](https://pushy.me/docs).
234
+
209
235
  ### Running Rpush
210
236
 
211
237
  It is recommended to run Rpush as a separate process in most cases, though embedding and manual modes are provided for low-workload environments.
@@ -44,6 +44,7 @@ class RpushMigrationGenerator < Rails::Generators::Base
44
44
  add_rpush_migration('rpush_2_7_0_updates')
45
45
  add_rpush_migration('rpush_3_0_0_updates')
46
46
  add_rpush_migration('rpush_3_0_1_updates')
47
+ add_rpush_migration('rpush_3_1_0_add_pushy')
47
48
  end
48
49
 
49
50
  protected
@@ -0,0 +1,9 @@
1
+ class Rpush310AddPushy < ActiveRecord::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[5.0] : ActiveRecord::Migration
2
+ def self.up
3
+ add_column :rpush_notifications, :external_device_id, :string, null: true
4
+ end
5
+
6
+ def self.down
7
+ remove_column :rpush_notifications, :external_device_id
8
+ end
9
+ end
@@ -81,7 +81,7 @@ module Rpush
81
81
  frame << [1, 32, device_token].pack("cnH*")
82
82
  frame << [2, frame_payload.bytesize, frame_payload].pack("cna*")
83
83
  frame << [3, 4, frame_id].pack("cnN")
84
- frame << [4, 4, expiry || APNS_DEFAULT_EXPIRY].pack("cnN")
84
+ frame << [4, 4, expiry ? Time.now.to_i + expiry.to_i : 0].pack("cnN")
85
85
  frame << [5, 1, priority_for_frame].pack("cnc")
86
86
  [2, frame.bytesize].pack("cN") + frame
87
87
  end
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Pushy
5
+ module App
6
+ def self.included(base)
7
+ base.instance_eval do
8
+ alias_attribute :api_key, :auth_key
9
+ validates :api_key, presence: true
10
+ end
11
+ end
12
+
13
+ def service_name
14
+ 'pushy'
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Pushy
5
+ module Notification
6
+ def self.included(base)
7
+ base.instance_eval do
8
+ alias_attribute :time_to_live, :expiry
9
+
10
+ validates :time_to_live, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
11
+ validates :registration_ids, presence: true
12
+ validates :data, presence: true
13
+
14
+ validates_with Rpush::Client::ActiveModel::Pushy::TimeToLiveValidator
15
+ validates_with Rpush::Client::ActiveModel::PayloadDataSizeValidator, limit: 4096
16
+ validates_with Rpush::Client::ActiveModel::RegistrationIdsCountValidator, limit: 1000
17
+ end
18
+ end
19
+
20
+ def as_json(_options = nil)
21
+ {
22
+ 'data' => data,
23
+ 'time_to_live' => time_to_live,
24
+ 'registration_ids' => registration_ids
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Pushy
5
+ class TimeToLiveValidator < ::ActiveModel::Validator
6
+ def validate(record)
7
+ return if record.time_to_live.blank? || record.time_to_live <= 1.year.seconds
8
+ record.errors.add(:time_to_live, 'The maximum value is 1 year')
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -25,3 +25,7 @@ require 'rpush/client/active_model/wpns/notification'
25
25
 
26
26
  require 'rpush/client/active_model/wns/app'
27
27
  require 'rpush/client/active_model/wns/notification'
28
+
29
+ require 'rpush/client/active_model/pushy/app'
30
+ require 'rpush/client/active_model/pushy/notification'
31
+ require 'rpush/client/active_model/pushy/time_to_live_validator'
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Pushy
5
+ class App < Rpush::Client::ActiveRecord::App
6
+ include Rpush::Client::ActiveModel::Pushy::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Pushy
5
+ class Notification < Rpush::Client::ActiveRecord::Notification
6
+ include Rpush::Client::ActiveModel::Pushy::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -25,3 +25,6 @@ require 'rpush/client/active_record/wns/app'
25
25
 
26
26
  require 'rpush/client/active_record/adm/notification'
27
27
  require 'rpush/client/active_record/adm/app'
28
+
29
+ require 'rpush/client/active_record/pushy/notification'
30
+ require 'rpush/client/active_record/pushy/app'
@@ -13,6 +13,7 @@ module Rpush
13
13
  attribute :auth_key, :string
14
14
  attribute :client_id, :string
15
15
  attribute :client_secret, :string
16
+ attribute :api_key, :string
16
17
 
17
18
  index :name
18
19
 
@@ -0,0 +1,16 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Pushy
5
+ class App < Rpush::Client::Redis::App
6
+ include Rpush::Client::ActiveModel::Pushy::App
7
+
8
+ def api_key=(value)
9
+ self.auth_key = value
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Pushy
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Pushy::Notification
7
+
8
+ attribute :external_device_id, :string
9
+
10
+ def time_to_live=(value)
11
+ self.expiry = value
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -38,6 +38,9 @@ require 'rpush/client/redis/wns/notification'
38
38
  require 'rpush/client/redis/wns/raw_notification'
39
39
  require 'rpush/client/redis/wns/badge_notification'
40
40
 
41
+ require 'rpush/client/redis/pushy/app'
42
+ require 'rpush/client/redis/pushy/notification'
43
+
41
44
  Modis.configure do |config|
42
45
  config.namespace = :rpush
43
46
  end
@@ -106,7 +106,7 @@ module Rpush
106
106
  client_module = Rpush::Client.const_get(client.to_s.camelize)
107
107
  Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module)
108
108
 
109
- [:Apns, :Gcm, :Wpns, :Wns, :Adm].each do |service|
109
+ [:Apns, :Gcm, :Wpns, :Wns, :Adm, :Pushy].each do |service|
110
110
  Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service)
111
111
  end
112
112
 
@@ -5,7 +5,8 @@ module Rpush
5
5
 
6
6
  URLS = {
7
7
  production: 'https://api.push.apple.com:443',
8
- development: 'https://api.development.push.apple.com:443'
8
+ development: 'https://api.development.push.apple.com:443',
9
+ sandbox: 'https://api.development.push.apple.com:443'
9
10
  }
10
11
 
11
12
  DEFAULT_TIMEOUT = 60
@@ -103,7 +103,7 @@ module Rpush
103
103
  attrs = { 'app_id' => @notification.app_id, 'collapse_key' => @notification.collapse_key, 'delay_while_idle' => @notification.delay_while_idle }
104
104
  registration_ids = @notification.registration_ids.values_at(*unavailable_idxs)
105
105
  Rpush::Daemon.store.create_gcm_notification(attrs, @notification.data,
106
- registration_ids, deliver_after_header(response), @notification.app)
106
+ registration_ids, deliver_after_header(response), @app)
107
107
  end
108
108
 
109
109
  def bad_request
@@ -143,7 +143,7 @@ module Rpush
143
143
 
144
144
  def do_post
145
145
  post = Net::HTTP::Post.new(FCM_URI.path, 'Content-Type' => 'application/json',
146
- 'Authorization' => "key=#{@notification.app.auth_key}")
146
+ 'Authorization' => "key=#{@app.auth_key}")
147
147
  post.body = @notification.as_json.to_json
148
148
  @http.request(FCM_URI, post)
149
149
  end
@@ -0,0 +1,90 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Pushy
4
+ class Delivery < Rpush::Daemon::Delivery
5
+ include MultiJsonHelper
6
+
7
+ attr_reader :http, :notification, :batch, :pushy_uri
8
+
9
+ def initialize(app, http, notification, batch)
10
+ @http = http
11
+ @notification = notification
12
+ @batch = batch
13
+ @pushy_uri = URI.parse("https://api.pushy.me/push?api_key=#{app.api_key}")
14
+ end
15
+
16
+ def perform
17
+ response = send_request
18
+ process_response(response)
19
+ rescue SocketError => error
20
+ mark_retryable(notification, Time.now + 10.seconds, error)
21
+ raise
22
+ rescue StandardError => error
23
+ mark_failed(error)
24
+ raise
25
+ ensure
26
+ batch.notification_processed
27
+ end
28
+
29
+ private
30
+
31
+ def send_request
32
+ post = Net::HTTP::Post.new(pushy_uri)
33
+ post.content_type = 'application/json'
34
+ post.body = notification.to_json
35
+ http.request(pushy_uri, post)
36
+ end
37
+
38
+ def process_response(response)
39
+ case response.code.to_i
40
+ when 200
41
+ process_delivery(response)
42
+ when 429, 500, 502, 503, 504
43
+ retry_delivery(response)
44
+ else
45
+ fail_delivery(response)
46
+ end
47
+ end
48
+
49
+ def process_delivery(response)
50
+ mark_delivered
51
+ body = multi_json_load(response.body)
52
+ external_device_id = body['id']
53
+ notification.external_device_id = external_device_id
54
+ Rpush::Daemon.store.update_notification(notification)
55
+ log_info("#{notification.id} received an external id=#{external_device_id}")
56
+ end
57
+
58
+ def retry_delivery(response)
59
+ time = deliver_after_header(response)
60
+ if time
61
+ mark_retryable(notification, time)
62
+ else
63
+ mark_retryable_exponential(notification)
64
+ end
65
+ log_warn("Pushy responded with a #{response.code} error. #{retry_message}")
66
+ end
67
+
68
+ def deliver_after_header(response)
69
+ Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
70
+ end
71
+
72
+ def retry_message
73
+ deliver_after = notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')
74
+ "Notification #{notification.id} will be retried after #{deliver_after} (retry #{notification.retries})."
75
+ end
76
+
77
+ def fail_delivery(response)
78
+ fail_message = fail_message(response)
79
+ log_error("#{notification.id} failed: #{fail_message}")
80
+ fail Rpush::DeliveryError.new(response.code.to_i, notification.id, fail_message)
81
+ end
82
+
83
+ def fail_message(response)
84
+ body = multi_json_load(response.body)
85
+ body['error'] || Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Pushy
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
data/lib/rpush/daemon.rb CHANGED
@@ -60,6 +60,9 @@ require 'rpush/daemon/wns'
60
60
  require 'rpush/daemon/adm/delivery'
61
61
  require 'rpush/daemon/adm'
62
62
 
63
+ require 'rpush/daemon/pushy'
64
+ require 'rpush/daemon/pushy/delivery'
65
+
63
66
  module Rpush
64
67
  module Daemon
65
68
  class << self
data/lib/rpush/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Rpush
2
2
  module VERSION
3
3
  MAJOR = 3
4
- MINOR = 0
5
- TINY = 2
4
+ MINOR = 1
5
+ TINY = 0
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
@@ -0,0 +1,22 @@
1
+ require 'functional_spec_helper'
2
+
3
+ describe 'Pushy' do
4
+ let(:external_device_id) { '5a622ae5813e2875bfdbe496' }
5
+ let(:response) { instance_double('Net::HTTPResponse', code: 200, body: { id: external_device_id }.to_json) }
6
+ let(:http) { instance_double('Net::HTTP::Persistent', request: response, shutdown: nil) }
7
+ let(:app) { Rpush::Pushy::App.create!(name: 'MyApp', api_key: 'my_api_key') }
8
+
9
+ let(:notification) do
10
+ Rpush::Pushy::Notification.create!(app: app, data: { message: 'test' }, registration_ids: ['id'])
11
+ end
12
+
13
+ before do
14
+ allow(Net::HTTP::Persistent).to receive_messages(new: http)
15
+ end
16
+
17
+ it 'deliveres a notification successfully' do
18
+ expect { Rpush.push }.to change { notification.reload.delivered }.to(true)
19
+ end
20
+
21
+ it { expect { Rpush.push }.to change { notification.reload.external_device_id }.to(external_device_id) }
22
+ end
@@ -33,6 +33,7 @@ require 'generators/templates/rpush_2_6_0_updates'
33
33
  require 'generators/templates/rpush_2_7_0_updates'
34
34
  require 'generators/templates/rpush_3_0_0_updates'
35
35
  require 'generators/templates/rpush_3_0_1_updates'
36
+ require 'generators/templates/rpush_3_1_0_add_pushy'
36
37
 
37
38
  migrations = [
38
39
  AddRpush,
@@ -41,7 +42,8 @@ migrations = [
41
42
  Rpush260Updates,
42
43
  Rpush270Updates,
43
44
  Rpush300Updates,
44
- Rpush301Updates
45
+ Rpush301Updates,
46
+ Rpush310AddPushy
45
47
  ]
46
48
 
47
49
  unless ENV['TRAVIS']
@@ -254,10 +254,12 @@ describe Rpush::Client::ActiveRecord::Apns::Notification, 'to_binary' do
254
254
  notification.badge = 3
255
255
  notification.alert = "Don't panic Mr Mainwaring, don't panic!"
256
256
  notification.data = { hi: :mom }
257
- notification.expiry = 86_400 # 1 day, \x00\x01Q\x80
257
+ notification.expiry = 86_400 # 1 day
258
258
  notification.priority = Rpush::Client::ActiveRecord::Apns::Notification::APNS_PRIORITY_IMMEDIATE
259
259
  notification.app = Rpush::Client::ActiveRecord::Apns::App.new(name: 'my_app', environment: 'development', certificate: TEST_CERT)
260
- expect(notification.to_binary).to eq "\x02\x00\x00\x00\x99\x01\x00 \xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x02\x00a{\"aps\":{\"alert\":\"Don't panic Mr Mainwaring, don't panic!\",\"badge\":3,\"sound\":\"1.aiff\"},\"hi\":\"mom\"}\x03\x00\x04\x00\x00\x04\xD2\x04\x00\x04\x00\x01Q\x80\x05\x00\x01\n"
260
+ now = Time.now
261
+ allow(Time).to receive_messages(now: now)
262
+ expect(notification.to_binary).to eq "\x02\x00\x00\x00\x99\x01\x00 \xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA\x02\x00a{\"aps\":{\"alert\":\"Don't panic Mr Mainwaring, don't panic!\",\"badge\":3,\"sound\":\"1.aiff\"},\"hi\":\"mom\"}\x03\x00\x04\x00\x00\x04\xD2\x04\x00\x04#{[now.to_i + 86_400].pack('N')}\x05\x00\x01\n"
261
263
  end
262
264
  end if active_record?
263
265
 
@@ -0,0 +1,17 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::ActiveRecord::Pushy::App do
4
+ describe 'validates' do
5
+ subject { described_class.new }
6
+
7
+ it 'validates presence of name' do
8
+ is_expected.not_to be_valid
9
+ expect(subject.errors[:name]).to eq ["can't be blank"]
10
+ end
11
+
12
+ it 'validates presence of api_key' do
13
+ is_expected.not_to be_valid
14
+ expect(subject.errors[:api_key]).to eq ["can't be blank"]
15
+ end
16
+ end
17
+ end if active_record?
@@ -0,0 +1,65 @@
1
+ require 'unit_spec_helper'
2
+ require 'unit/notification_shared.rb'
3
+
4
+ describe Rpush::Client::ActiveRecord::Pushy::Notification do
5
+ let(:notification_class) { described_class }
6
+ subject(:notification) { notification_class.new }
7
+
8
+ it_behaves_like 'an Notification subclass'
9
+
10
+ describe 'validates' do
11
+ let(:app) { Rpush::Client::ActiveRecord::Pushy::App.create!(name: 'MyApp', api_key: 'my_api_key') }
12
+
13
+ describe 'data' do
14
+ subject { described_class.new(app: app, registration_ids: ['id']) }
15
+ it 'validates presence' do
16
+ is_expected.not_to be_valid
17
+ expect(subject.errors[:data]).to eq ["can't be blank"]
18
+ end
19
+
20
+ it "has a 'data' payload limit of 4096 bytes" do
21
+ subject.data = { message: 'a' * 4096 }
22
+ is_expected.not_to be_valid
23
+ expected_errors = ["Notification payload data cannot be larger than 4096 bytes."]
24
+ expect(subject.errors[:base]).to eq expected_errors
25
+ end
26
+ end
27
+
28
+ describe 'registration_ids' do
29
+ subject { described_class.new(app: app, data: { message: 'test' }) }
30
+ it 'validates presence' do
31
+ is_expected.not_to be_valid
32
+ expect(subject.errors[:registration_ids]).to eq ["can't be blank"]
33
+ end
34
+
35
+ it 'limits the number of registration ids to 1000' do
36
+ subject.registration_ids = ['a'] * (1000 + 1)
37
+ is_expected.not_to be_valid
38
+ expected_errors = ["Number of registration_ids cannot be larger than 1000."]
39
+ expect(subject.errors[:base]).to eq expected_errors
40
+ end
41
+ end
42
+
43
+ describe 'time_to_live' do
44
+ subject { described_class.new(app: app, data: { message: 'test' }, registration_ids: ['id']) }
45
+
46
+ it 'should be > 0' do
47
+ subject.time_to_live = -1
48
+ is_expected.not_to be_valid
49
+ expect(subject.errors[:time_to_live]).to eq ['must be greater than 0']
50
+ end
51
+
52
+ it 'should be integer' do
53
+ subject.time_to_live = 1.4
54
+ is_expected.not_to be_valid
55
+ expect(subject.errors[:time_to_live]).to eq ['must be an integer']
56
+ end
57
+
58
+ it 'should be <= 1.year.seconds' do
59
+ subject.time_to_live = 2.years.seconds.to_i
60
+ is_expected.not_to be_valid
61
+ expect(subject.errors[:time_to_live]).to eq ['The maximum value is 1 year']
62
+ end
63
+ end
64
+ end
65
+ end if active_record?
@@ -0,0 +1,159 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Pushy::Delivery do
4
+ let(:app) { Rpush::Pushy::App.create!(name: 'MyApp', api_key: 'my_api_key') }
5
+ let(:token) { 'device token' }
6
+ let(:data) { { message: 'some message' } }
7
+ let(:notification) { Rpush::Pushy::Notification.create!(app: app, registration_ids: [token], data: data) }
8
+ let(:batch) { instance_double('Rpush::Daemon::Batch', notification_processed: nil) }
9
+ let(:response) { instance_double('Net::HTTPResponse', code: response_code, header: response_header) }
10
+ let(:response_code) { 200 }
11
+ let(:response_header) { {} }
12
+ let(:http) { instance_double('Net::HTTP::Persistent', request: response) }
13
+ let(:logger) { instance_double('Rpush::Logger', error: nil, info: nil, warn: nil, internal_logger: nil) }
14
+ let(:now) { Time.parse('2018-01-19 00:00:00 UTC') }
15
+
16
+ before do
17
+ allow(Rpush).to receive_messages(logger: logger)
18
+ allow(Time).to receive_messages(now: now)
19
+ end
20
+
21
+ subject(:delivery) { described_class.new(app, http, notification, batch) }
22
+
23
+ describe '#pushy_uri' do
24
+ it { expect(subject.pushy_uri).to eq URI.parse('https://api.pushy.me/push?api_key=my_api_key') }
25
+ end
26
+
27
+ describe '#perform' do
28
+ shared_examples 'process notification' do
29
+ it 'invoke batch.notification_processed' do
30
+ subject.perform rescue nil
31
+ expect(batch).to have_received(:notification_processed)
32
+ end
33
+ end
34
+
35
+ context 'when response code is 200' do
36
+ let(:external_device_id) { '5a60ca7f6ad08477b5070dd3' }
37
+
38
+ before do
39
+ allow(batch).to receive(:mark_delivered)
40
+ response_body = {
41
+ success: true,
42
+ id: external_device_id
43
+ }
44
+ allow(response).to receive(:body) { response_body.to_json }
45
+ Rpush::Daemon.store = Rpush::Daemon::Store.const_get(Rpush.config.client.to_s.camelcase).new
46
+ end
47
+
48
+ it 'marks the notification as delivered' do
49
+ delivery.perform
50
+ expect(batch).to have_received(:mark_delivered).with(notification)
51
+ end
52
+
53
+ it { expect { delivery.perform }.to change { notification.external_device_id }.to(external_device_id) }
54
+
55
+ it 'logs than notification received an external id' do
56
+ delivery.perform
57
+ expected = "#{notification.id} received an external id=5a60ca7f6ad08477b5070dd3"
58
+ expect(logger).to have_received(:info).with(expected)
59
+ end
60
+
61
+ it_behaves_like 'process notification'
62
+ end
63
+
64
+ shared_examples 'retry delivery' do |response_code:|
65
+ let(:response_code) { response_code }
66
+
67
+ shared_examples 'logs' do |deliver_after:|
68
+ let(:expected_log_message) do
69
+ "Pushy responded with a #{response_code} error. Notification #{notification.id} " \
70
+ "will be retried after #{deliver_after} (retry 1)."
71
+ end
72
+
73
+ it 'logs that the notification will be retried' do
74
+ delivery.perform
75
+ expect(logger).to have_received(:warn).with(expected_log_message)
76
+ end
77
+ end
78
+
79
+ context 'when Retry-After header is present' do
80
+ let(:response_header) { { 'retry-after' => 10 } }
81
+
82
+ before do
83
+ allow(delivery).to receive(:mark_retryable) do
84
+ notification.deliver_after = now + 10.seconds
85
+ notification.retries += 1
86
+ end
87
+ end
88
+
89
+ it 'retry the notification' do
90
+ delivery.perform
91
+ expect(delivery).to have_received(:mark_retryable).with(notification, now + 10.seconds)
92
+ end
93
+
94
+ it_behaves_like 'logs', deliver_after: '2018-01-19 00:00:10'
95
+ it_behaves_like 'process notification'
96
+ end
97
+
98
+ context 'when Retry-After header is not present' do
99
+ before do
100
+ allow(delivery).to receive(:mark_retryable_exponential) do
101
+ notification.deliver_after = now + 2.seconds
102
+ notification.retries = 1
103
+ end
104
+ end
105
+
106
+ it 'retry the notification' do
107
+ delivery.perform
108
+ expect(delivery).to have_received(:mark_retryable_exponential).with(notification)
109
+ end
110
+
111
+ it_behaves_like 'logs', deliver_after: '2018-01-19 00:00:02'
112
+ it_behaves_like 'process notification'
113
+ end
114
+ end
115
+
116
+ it_behaves_like 'retry delivery', response_code: 429
117
+ it_behaves_like 'retry delivery', response_code: 500
118
+ it_behaves_like 'retry delivery', response_code: 502
119
+ it_behaves_like 'retry delivery', response_code: 503
120
+ it_behaves_like 'retry delivery', response_code: 504
121
+
122
+ context 'when delivery failed' do
123
+ let(:response_code) { 400 }
124
+ let(:fail_message) { 'No devices matched the specified condition.' }
125
+ before do
126
+ allow(response).to receive(:body) { { error: fail_message }.to_json }
127
+ allow(batch).to receive(:mark_failed)
128
+ end
129
+
130
+ it 'logs that the notifications failed' do
131
+ expect { delivery.perform }.to raise_error(Rpush::DeliveryError)
132
+ expect(logger).to have_received(:error).with("#{notification.id} failed: #{fail_message}")
133
+ end
134
+
135
+ it 'marks the notification as failed' do
136
+ expect { delivery.perform }.to raise_error(Rpush::DeliveryError)
137
+ expected_message = "Unable to deliver notification #{notification.id}, " \
138
+ "received error 400 (#{fail_message})"
139
+ expect(batch).to have_received(:mark_failed).with(notification, 400, expected_message)
140
+ end
141
+
142
+ it_behaves_like 'process notification'
143
+ end
144
+
145
+ context 'when SocketError raised' do
146
+ before do
147
+ allow(http).to receive(:request) { raise SocketError }
148
+ allow(delivery).to receive(:mark_retryable)
149
+ end
150
+
151
+ it 'retry delivery after 10 seconds' do
152
+ expect { delivery.perform }.to raise_error(SocketError)
153
+ expect(delivery).to have_received(:mark_retryable).with(notification, now + 10.seconds, SocketError)
154
+ end
155
+
156
+ it_behaves_like 'process notification'
157
+ end
158
+ end
159
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpush
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ian Leitch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-08 00:00:00.000000000 Z
11
+ date: 2018-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -188,30 +188,30 @@ dependencies:
188
188
  name: modis
189
189
  requirement: !ruby/object:Gem::Requirement
190
190
  requirements:
191
- - - '='
191
+ - - "~>"
192
192
  - !ruby/object:Gem::Version
193
- version: 1.4.2
193
+ version: '2.0'
194
194
  type: :development
195
195
  prerelease: false
196
196
  version_requirements: !ruby/object:Gem::Requirement
197
197
  requirements:
198
- - - '='
198
+ - - "~>"
199
199
  - !ruby/object:Gem::Version
200
- version: 1.4.2
200
+ version: '2.0'
201
201
  - !ruby/object:Gem::Dependency
202
202
  name: rpush-redis
203
203
  requirement: !ruby/object:Gem::Requirement
204
204
  requirements:
205
- - - '='
205
+ - - "~>"
206
206
  - !ruby/object:Gem::Version
207
- version: 0.4.1
207
+ version: '1.0'
208
208
  type: :development
209
209
  prerelease: false
210
210
  version_requirements: !ruby/object:Gem::Requirement
211
211
  requirements:
212
- - - '='
212
+ - - "~>"
213
213
  - !ruby/object:Gem::Version
214
- version: 0.4.1
214
+ version: '1.0'
215
215
  - !ruby/object:Gem::Dependency
216
216
  name: appraisal
217
217
  requirement: !ruby/object:Gem::Requirement
@@ -370,6 +370,7 @@ files:
370
370
  - lib/generators/templates/rpush_2_7_0_updates.rb
371
371
  - lib/generators/templates/rpush_3_0_0_updates.rb
372
372
  - lib/generators/templates/rpush_3_0_1_updates.rb
373
+ - lib/generators/templates/rpush_3_1_0_add_pushy.rb
373
374
  - lib/rpush.rb
374
375
  - lib/rpush/apns_feedback.rb
375
376
  - lib/rpush/cli.rb
@@ -388,6 +389,9 @@ files:
388
389
  - lib/rpush/client/active_model/gcm/notification.rb
389
390
  - lib/rpush/client/active_model/notification.rb
390
391
  - lib/rpush/client/active_model/payload_data_size_validator.rb
392
+ - lib/rpush/client/active_model/pushy/app.rb
393
+ - lib/rpush/client/active_model/pushy/notification.rb
394
+ - lib/rpush/client/active_model/pushy/time_to_live_validator.rb
391
395
  - lib/rpush/client/active_model/registration_ids_count_validator.rb
392
396
  - lib/rpush/client/active_model/wns/app.rb
393
397
  - lib/rpush/client/active_model/wns/notification.rb
@@ -405,6 +409,8 @@ files:
405
409
  - lib/rpush/client/active_record/gcm/app.rb
406
410
  - lib/rpush/client/active_record/gcm/notification.rb
407
411
  - lib/rpush/client/active_record/notification.rb
412
+ - lib/rpush/client/active_record/pushy/app.rb
413
+ - lib/rpush/client/active_record/pushy/notification.rb
408
414
  - lib/rpush/client/active_record/wns/app.rb
409
415
  - lib/rpush/client/active_record/wns/badge_notification.rb
410
416
  - lib/rpush/client/active_record/wns/notification.rb
@@ -423,6 +429,8 @@ files:
423
429
  - lib/rpush/client/redis/gcm/app.rb
424
430
  - lib/rpush/client/redis/gcm/notification.rb
425
431
  - lib/rpush/client/redis/notification.rb
432
+ - lib/rpush/client/redis/pushy/app.rb
433
+ - lib/rpush/client/redis/pushy/notification.rb
426
434
  - lib/rpush/client/redis/wns/app.rb
427
435
  - lib/rpush/client/redis/wns/badge_notification.rb
428
436
  - lib/rpush/client/redis/wns/notification.rb
@@ -455,6 +463,8 @@ files:
455
463
  - lib/rpush/daemon/interruptible_sleep.rb
456
464
  - lib/rpush/daemon/loggable.rb
457
465
  - lib/rpush/daemon/proc_title.rb
466
+ - lib/rpush/daemon/pushy.rb
467
+ - lib/rpush/daemon/pushy/delivery.rb
458
468
  - lib/rpush/daemon/queue_payload.rb
459
469
  - lib/rpush/daemon/retry_header_parser.rb
460
470
  - lib/rpush/daemon/retryable_error.rb
@@ -500,6 +510,7 @@ files:
500
510
  - spec/functional/embed_spec.rb
501
511
  - spec/functional/gcm_spec.rb
502
512
  - spec/functional/new_app_spec.rb
513
+ - spec/functional/pushy_spec.rb
503
514
  - spec/functional/retry_spec.rb
504
515
  - spec/functional/synchronization_spec.rb
505
516
  - spec/functional/wpns_spec.rb
@@ -522,6 +533,8 @@ files:
522
533
  - spec/unit/client/active_record/gcm/app_spec.rb
523
534
  - spec/unit/client/active_record/gcm/notification_spec.rb
524
535
  - spec/unit/client/active_record/notification_spec.rb
536
+ - spec/unit/client/active_record/pushy/app_spec.rb
537
+ - spec/unit/client/active_record/pushy/notification_spec.rb
525
538
  - spec/unit/client/active_record/wns/badge_notification_spec.rb
526
539
  - spec/unit/client/active_record/wns/raw_notification_spec.rb
527
540
  - spec/unit/client/active_record/wpns/app_spec.rb
@@ -541,6 +554,7 @@ files:
541
554
  - spec/unit/daemon/feeder_spec.rb
542
555
  - spec/unit/daemon/gcm/delivery_spec.rb
543
556
  - spec/unit/daemon/proc_title_spec.rb
557
+ - spec/unit/daemon/pushy/delivery_spec.rb
544
558
  - spec/unit/daemon/retryable_error_spec.rb
545
559
  - spec/unit/daemon/service_config_methods_spec.rb
546
560
  - spec/unit/daemon/signal_handler_spec.rb
@@ -596,6 +610,7 @@ test_files:
596
610
  - spec/functional/embed_spec.rb
597
611
  - spec/functional/gcm_spec.rb
598
612
  - spec/functional/new_app_spec.rb
613
+ - spec/functional/pushy_spec.rb
599
614
  - spec/functional/retry_spec.rb
600
615
  - spec/functional/synchronization_spec.rb
601
616
  - spec/functional/wpns_spec.rb
@@ -618,6 +633,8 @@ test_files:
618
633
  - spec/unit/client/active_record/gcm/app_spec.rb
619
634
  - spec/unit/client/active_record/gcm/notification_spec.rb
620
635
  - spec/unit/client/active_record/notification_spec.rb
636
+ - spec/unit/client/active_record/pushy/app_spec.rb
637
+ - spec/unit/client/active_record/pushy/notification_spec.rb
621
638
  - spec/unit/client/active_record/wns/badge_notification_spec.rb
622
639
  - spec/unit/client/active_record/wns/raw_notification_spec.rb
623
640
  - spec/unit/client/active_record/wpns/app_spec.rb
@@ -637,6 +654,7 @@ test_files:
637
654
  - spec/unit/daemon/feeder_spec.rb
638
655
  - spec/unit/daemon/gcm/delivery_spec.rb
639
656
  - spec/unit/daemon/proc_title_spec.rb
657
+ - spec/unit/daemon/pushy/delivery_spec.rb
640
658
  - spec/unit/daemon/retryable_error_spec.rb
641
659
  - spec/unit/daemon/service_config_methods_spec.rb
642
660
  - spec/unit/daemon/signal_handler_spec.rb