rpush 3.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -3
- data/README.md +26 -0
- data/lib/generators/rpush_migration_generator.rb +1 -0
- data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +9 -0
- data/lib/rpush/client/active_model/apns/notification.rb +1 -1
- data/lib/rpush/client/active_model/pushy/app.rb +20 -0
- data/lib/rpush/client/active_model/pushy/notification.rb +31 -0
- data/lib/rpush/client/active_model/pushy/time_to_live_validator.rb +14 -0
- data/lib/rpush/client/active_model.rb +4 -0
- data/lib/rpush/client/active_record/pushy/app.rb +11 -0
- data/lib/rpush/client/active_record/pushy/notification.rb +11 -0
- data/lib/rpush/client/active_record.rb +3 -0
- data/lib/rpush/client/redis/app.rb +1 -0
- data/lib/rpush/client/redis/pushy/app.rb +16 -0
- data/lib/rpush/client/redis/pushy/notification.rb +18 -0
- data/lib/rpush/client/redis.rb +3 -0
- data/lib/rpush/configuration.rb +1 -1
- data/lib/rpush/daemon/dispatcher/apns_http2.rb +2 -1
- data/lib/rpush/daemon/gcm/delivery.rb +2 -2
- data/lib/rpush/daemon/pushy/delivery.rb +90 -0
- data/lib/rpush/daemon/pushy.rb +9 -0
- data/lib/rpush/daemon.rb +3 -0
- data/lib/rpush/version.rb +2 -2
- data/spec/functional/pushy_spec.rb +22 -0
- data/spec/support/active_record_setup.rb +3 -1
- data/spec/unit/client/active_record/apns/notification_spec.rb +4 -2
- data/spec/unit/client/active_record/pushy/app_spec.rb +17 -0
- data/spec/unit/client/active_record/pushy/notification_spec.rb +65 -0
- data/spec/unit/daemon/pushy/delivery_spec.rb +159 -0
- metadata +28 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d01653298bef15cc73e0010be8bb353c3adcb0aecc442f3883858415189a9265
|
4
|
+
data.tar.gz: ff64bca96549d684dfd2c4c6c0184dd55d3c93750c377a857f99d7ddb51405ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
-
|
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
|
-
-
|
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
|
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,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
|
data/lib/rpush/client/redis.rb
CHANGED
@@ -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
|
data/lib/rpush/configuration.rb
CHANGED
@@ -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), @
|
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=#{@
|
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
|
data/lib/rpush/daemon.rb
CHANGED
data/lib/rpush/version.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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-
|
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:
|
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:
|
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
|
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
|
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
|