rpush 2.7.0 → 3.0.0.rc1

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +36 -15
  4. data/lib/generators/rpush_migration_generator.rb +1 -0
  5. data/lib/generators/templates/add_adm.rb +1 -1
  6. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +1 -1
  7. data/lib/generators/templates/add_app_to_rapns.rb +1 -1
  8. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
  9. data/lib/generators/templates/add_gcm.rb +1 -1
  10. data/lib/generators/templates/add_rpush.rb +11 -11
  11. data/lib/generators/templates/add_wpns.rb +1 -1
  12. data/lib/generators/templates/create_rapns_apps.rb +1 -1
  13. data/lib/generators/templates/create_rapns_feedback.rb +1 -1
  14. data/lib/generators/templates/create_rapns_notifications.rb +1 -1
  15. data/lib/generators/templates/rename_rapns_to_rpush.rb +1 -1
  16. data/lib/generators/templates/rpush.rb +1 -1
  17. data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
  18. data/lib/generators/templates/rpush_2_1_0_updates.rb +1 -1
  19. data/lib/generators/templates/rpush_2_6_0_updates.rb +1 -1
  20. data/lib/generators/templates/rpush_2_7_0_updates.rb +1 -1
  21. data/lib/generators/templates/rpush_3_0_0_updates.rb +11 -0
  22. data/lib/rpush.rb +2 -9
  23. data/lib/rpush/apns_feedback.rb +4 -0
  24. data/lib/rpush/cli.rb +2 -2
  25. data/lib/rpush/client/active_model.rb +3 -0
  26. data/lib/rpush/client/active_model/apns/notification.rb +11 -1
  27. data/lib/rpush/client/active_model/apns2/app.rb +15 -0
  28. data/lib/rpush/client/active_model/apns2/notification.rb +9 -0
  29. data/lib/rpush/client/active_record.rb +3 -0
  30. data/lib/rpush/client/active_record/apns/feedback.rb +0 -4
  31. data/lib/rpush/client/active_record/apns2/app.rb +11 -0
  32. data/lib/rpush/client/active_record/apns2/notification.rb +10 -0
  33. data/lib/rpush/client/active_record/app.rb +0 -4
  34. data/lib/rpush/client/active_record/notification.rb +0 -7
  35. data/lib/rpush/client/redis.rb +3 -0
  36. data/lib/rpush/client/redis/apns2/app.rb +11 -0
  37. data/lib/rpush/client/redis/apns2/notification.rb +11 -0
  38. data/lib/rpush/client/redis/notification.rb +1 -0
  39. data/lib/rpush/daemon.rb +5 -3
  40. data/lib/rpush/daemon/apns2.rb +10 -0
  41. data/lib/rpush/daemon/apns2/delivery.rb +127 -0
  42. data/lib/rpush/daemon/dispatcher/apns_http2.rb +50 -0
  43. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +1 -1
  44. data/lib/rpush/daemon/dispatcher/http.rb +1 -1
  45. data/lib/rpush/daemon/gcm/delivery.rb +5 -5
  46. data/lib/rpush/daemon/service_config_methods.rb +4 -3
  47. data/lib/rpush/daemon/store/active_record/reconnectable.rb +11 -3
  48. data/lib/rpush/daemon/synchronizer.rb +14 -12
  49. data/lib/rpush/version.rb +12 -1
  50. data/spec/functional/apns2_spec.rb +232 -0
  51. data/spec/functional/apns_spec.rb +1 -2
  52. data/spec/functional/synchronization_spec.rb +29 -0
  53. data/spec/spec_helper.rb +0 -5
  54. data/spec/support/active_record_setup.rb +2 -1
  55. data/spec/unit/apns_feedback_spec.rb +9 -2
  56. data/spec/unit/client/active_record/apns/notification_spec.rb +34 -2
  57. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +30 -0
  58. data/spec/unit_spec_helper.rb +2 -21
  59. metadata +256 -29
  60. data/lib/rpush/client/mongoid.rb +0 -36
  61. data/lib/rpush/client/mongoid/adm/app.rb +0 -14
  62. data/lib/rpush/client/mongoid/adm/notification.rb +0 -11
  63. data/lib/rpush/client/mongoid/apns/app.rb +0 -11
  64. data/lib/rpush/client/mongoid/apns/feedback.rb +0 -24
  65. data/lib/rpush/client/mongoid/apns/notification.rb +0 -15
  66. data/lib/rpush/client/mongoid/app.rb +0 -23
  67. data/lib/rpush/client/mongoid/gcm/app.rb +0 -11
  68. data/lib/rpush/client/mongoid/gcm/notification.rb +0 -11
  69. data/lib/rpush/client/mongoid/notification.rb +0 -51
  70. data/lib/rpush/client/mongoid/wns/app.rb +0 -14
  71. data/lib/rpush/client/mongoid/wns/badge_notification.rb +0 -15
  72. data/lib/rpush/client/mongoid/wns/notification.rb +0 -11
  73. data/lib/rpush/client/mongoid/wns/raw_notification.rb +0 -11
  74. data/lib/rpush/client/mongoid/wpns/app.rb +0 -11
  75. data/lib/rpush/client/mongoid/wpns/notification.rb +0 -11
  76. data/lib/rpush/daemon/store/mongoid.rb +0 -157
  77. data/spec/support/config/mongoid.yml +0 -69
  78. data/spec/support/mongoid_setup.rb +0 -10
  79. data/spec/unit/daemon/store/mongoid_spec.rb +0 -339
@@ -2,9 +2,10 @@ module Rpush
2
2
  module Daemon
3
3
  module ServiceConfigMethods
4
4
  DISPATCHERS = {
5
- http: Rpush::Daemon::Dispatcher::Http,
6
- tcp: Rpush::Daemon::Dispatcher::Tcp,
7
- apns_tcp: Rpush::Daemon::Dispatcher::ApnsTcp
5
+ http: Rpush::Daemon::Dispatcher::Http,
6
+ tcp: Rpush::Daemon::Dispatcher::Tcp,
7
+ apns_tcp: Rpush::Daemon::Dispatcher::ApnsTcp,
8
+ apns_http2: Rpush::Daemon::Dispatcher::ApnsHttp2
8
9
  }
9
10
 
10
11
  def batch_deliveries(value = nil)
@@ -20,9 +20,17 @@ module Rpush
20
20
  module Store
21
21
  class ActiveRecord
22
22
  module Reconnectable
23
- ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, PG::Error,
24
- Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError,
25
- SQLite3::Exception, ::ActiveRecord::ConnectionTimeoutError]
23
+ ADAPTER_ERRORS = [
24
+ ::ActiveRecord::ConnectionNotEstablished,
25
+ ::ActiveRecord::ConnectionTimeoutError,
26
+ ::ActiveRecord::JDBCError,
27
+ ::ActiveRecord::StatementInvalid,
28
+ Mysql::Error,
29
+ Mysql2::Error,
30
+ PG::Error,
31
+ PGError,
32
+ SQLite3::Exception
33
+ ]
26
34
 
27
35
  def with_database_reconnect_and_retry
28
36
  ::ActiveRecord::Base.connection_pool.with_connection do
@@ -4,6 +4,8 @@ module Rpush
4
4
  extend Loggable
5
5
  extend StringHelpers
6
6
 
7
+ APP_ATTRIBUTES_TO_CHECK = [:certificate, :environment, :auth_key, :client_id, :client_secret].freeze
8
+
7
9
  def self.sync
8
10
  apps = Rpush::Daemon.store.all_apps
9
11
  apps.each { |app| sync_app(app) }
@@ -16,12 +18,9 @@ module Rpush
16
18
  def self.sync_app(app)
17
19
  if !AppRunner.app_running?(app)
18
20
  AppRunner.start_app(app)
19
- elsif certificate_changed?(app)
20
- log_info("[#{app.name}] Certificate changed, restarting...")
21
- AppRunner.stop_app(app.id)
22
- AppRunner.start_app(app)
23
- elsif environment_changed?(app)
24
- log_info("[#{app.name}] Environment changed, restarting...")
21
+ elsif (changed_attrs = changed_attributes(app)).count > 0
22
+ changed_attrs_str = changed_attrs.map(&:to_s).join(", ")
23
+ log_info("[#{app.name}] #{changed_attrs_str} changed, restarting...")
25
24
  AppRunner.stop_app(app.id)
26
25
  AppRunner.start_app(app)
27
26
  else
@@ -46,14 +45,17 @@ module Rpush
46
45
  log_info("[#{app.name}] #{start_stop_str} #{pluralize(diff.abs, 'dispatcher')}. #{num_dispatchers} running.")
47
46
  end
48
47
 
49
- def self.certificate_changed?(app)
50
- old_app = AppRunner.app_with_id(app.id)
51
- app.certificate != old_app.certificate
48
+ def self.changed_attributes(app)
49
+ APP_ATTRIBUTES_TO_CHECK.select { |attr| attribute_changed?(app, attr) }
52
50
  end
53
51
 
54
- def self.environment_changed?(app)
55
- old_app = AppRunner.app_with_id(app.id)
56
- app.environment != old_app.environment
52
+ def self.attribute_changed?(app, attr)
53
+ if app.respond_to?(attr)
54
+ old_app = AppRunner.app_with_id(app.id)
55
+ app.send(attr) != old_app.send(attr)
56
+ else
57
+ false
58
+ end
57
59
  end
58
60
  end
59
61
  end
@@ -1,3 +1,14 @@
1
1
  module Rpush
2
- VERSION = '2.7.0'
2
+ module VERSION
3
+ MAJOR = 3
4
+ MINOR = 0
5
+ TINY = 0
6
+ PRE = 'rc1'.freeze
7
+
8
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
9
+
10
+ def self.to_s
11
+ STRING
12
+ end
13
+ end
3
14
  end
@@ -0,0 +1,232 @@
1
+ require 'functional_spec_helper'
2
+
3
+ describe 'APNs http2 adapter' do
4
+ let(:fake_client) {
5
+ double(
6
+ prepare_request: fake_http2_request,
7
+ close: 'ok',
8
+ call_async: 'ok',
9
+ join: 'ok',
10
+ on: 'ok'
11
+ )
12
+ }
13
+ let(:app) { create_app }
14
+ let(:fake_device_token) { 'a' * 64 }
15
+ let(:fake_http2_request) { double }
16
+ let(:fake_http_resp_headers) {
17
+ {
18
+ ":status" => "200",
19
+ "apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6"
20
+ }
21
+ }
22
+ let(:fake_http_resp_body) { '' }
23
+ let(:notification_data) { nil }
24
+
25
+ before do
26
+ Rpush.config.push_poll = 0.5
27
+ allow(NetHttp2::Client).
28
+ to receive(:new).and_return(fake_client)
29
+ allow(fake_http2_request).
30
+ to receive(:on).with(:headers).
31
+ and_yield(fake_http_resp_headers)
32
+ allow(fake_http2_request).
33
+ to receive(:on).with(:body_chunk).
34
+ and_yield(fake_http_resp_body)
35
+ allow(fake_http2_request).
36
+ to receive(:on).with(:close).
37
+ and_yield
38
+ end
39
+
40
+ def create_app
41
+ app = Rpush::Apns2::App.new
42
+ app.certificate = TEST_CERT
43
+ app.name = 'test'
44
+ app.environment = 'development'
45
+ app.save!
46
+ app
47
+ end
48
+
49
+ def create_notification
50
+ notification = Rpush::Apns2::Notification.new
51
+ notification.app = app
52
+ notification.sound = 'default'
53
+ notification.alert = 'test'
54
+ notification.device_token = fake_device_token
55
+ notification.data = notification_data
56
+ notification.content_available = 1
57
+ notification.save!
58
+ notification
59
+ end
60
+
61
+ it 'delivers a notification successfully' do
62
+ notification = create_notification
63
+
64
+ thread = nil
65
+ expect(fake_http2_request).
66
+ to receive(:on).with(:close) { |&block|
67
+ # imitate HTTP2 delay
68
+ thread = Thread.new { sleep(0.01); block.call }
69
+ }
70
+ expect(fake_client).to receive(:join) { thread.join }
71
+
72
+ expect(fake_client)
73
+ .to receive(:prepare_request)
74
+ .with(
75
+ :post,
76
+ "/3/device/#{fake_device_token}",
77
+ { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\",\"content-available\":1}}",
78
+ headers: {} }
79
+ )
80
+ .and_return(fake_http2_request)
81
+
82
+ expect do
83
+ Rpush.push
84
+ notification.reload
85
+ end.to change(notification, :delivered).to(true)
86
+ end
87
+
88
+ context 'when there is "headers" field in a data' do
89
+ let(:bundle_id) { 'some.example.com' }
90
+ let(:notification_data) {
91
+ {
92
+ 'headers' => { 'apns-topic' => bundle_id },
93
+ 'some_field' => 'some value'
94
+ }
95
+ }
96
+
97
+ it 'delivers notification with custom headers' do
98
+ notification = create_notification
99
+
100
+ expect(fake_client)
101
+ .to receive(:prepare_request)
102
+ .with(
103
+ :post,
104
+ "/3/device/#{fake_device_token}",
105
+ { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\","\
106
+ "\"content-available\":1},\"some_field\":\"some value\"}",
107
+ headers: { 'apns-topic' => bundle_id }
108
+ }
109
+ ).and_return(fake_http2_request)
110
+
111
+ expect do
112
+ Rpush.push
113
+ notification.reload
114
+ end.to change(notification, :delivered).to(true)
115
+ end
116
+ end
117
+
118
+ describe 'delivery failures' do
119
+ context 'when response is about incorrect request' do
120
+ let(:fake_http_resp_headers) {
121
+ {
122
+ ":status" => "404",
123
+ "apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6"
124
+ }
125
+ }
126
+
127
+ it 'fails to deliver a notification' do
128
+ notification = create_notification
129
+ expect do
130
+ Rpush.push
131
+ notification.reload
132
+ end.to change(notification, :failed).to(true)
133
+ end
134
+
135
+ it 'reflects :notification_id_failed' do
136
+ Rpush.reflect do |on|
137
+ on.notification_id_failed do |app, id, code, descr|
138
+ expect(app).to be_kind_of(Rpush::Client::Apns2::App)
139
+ expect(id).to eq 1
140
+ expect(code).to eq 404
141
+ expect(descr).to be_nil
142
+ end
143
+ end
144
+
145
+ notification = create_notification
146
+ Rpush.push
147
+ end
148
+ end
149
+
150
+ context 'when response returns 500 error for APNs maintenance' do
151
+ let(:fake_http_resp_headers) {
152
+ {
153
+ ":status" => "500",
154
+ "apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6"
155
+ }
156
+ }
157
+
158
+ it 'fails but retries delivery several times' do
159
+ notification = create_notification
160
+ expect do
161
+ Rpush.push
162
+ notification.reload
163
+ end.to change(notification, :retries)
164
+ end
165
+
166
+ it 'reflects :notification_id_will_retry' do
167
+ Rpush.reflect do |on|
168
+ on.notification_id_will_retry do |app, id, timer|
169
+ expect(app).to be_kind_of(Rpush::Client::Apns2::App)
170
+ expect(id).to eq 1
171
+ end
172
+ end
173
+
174
+ notification = create_notification
175
+ Rpush.push
176
+ end
177
+ end
178
+
179
+ context 'when there is SocketError' do
180
+ before(:each) do
181
+ expect(fake_client).to receive(:call_async) { raise(SocketError) }
182
+ end
183
+
184
+ it 'fails but retries delivery several times' do
185
+ notification = create_notification
186
+ expect do
187
+ Rpush.push
188
+ notification.reload
189
+ end.to change(notification, :retries)
190
+ end
191
+
192
+ it 'reflects :notification_id_will_retry' do
193
+ Rpush.reflect do |on|
194
+ on.notification_id_will_retry do |app, id, timer|
195
+ expect(app).to be_kind_of(Rpush::Client::Apns2::App)
196
+ expect(id).to eq 1
197
+ expect(timer).to be_kind_of(Time)
198
+ end
199
+ end
200
+
201
+ notification = create_notification
202
+ Rpush.push
203
+ end
204
+ end
205
+
206
+ context 'when any StandardError occurs' do
207
+ before(:each) do
208
+ expect(fake_client).to receive(:call_async) { raise(StandardError) }
209
+ end
210
+
211
+ it 'marks notification failed' do
212
+ notification = create_notification
213
+ expect do
214
+ Rpush.push
215
+ notification.reload
216
+ end.to change(notification, :failed).to(true)
217
+ end
218
+
219
+ it 'reflects :error' do
220
+ Rpush.reflect do |on|
221
+ on.error do |error|
222
+ expect(error).to be_kind_of(StandardError)
223
+ reflector.accept
224
+ end
225
+ end
226
+
227
+ notification = create_notification
228
+ Rpush.push
229
+ end
230
+ end
231
+ end
232
+ end
@@ -49,8 +49,7 @@ describe 'APNs' do
49
49
  end
50
50
 
51
51
  def fail_notification(notification)
52
- id = (defined?(Mongoid) && notification.is_a?(Mongoid::Document)) ? notification.integer_id : notification.id
53
- allow(ssl_socket).to receive_messages(read: [8, 4, id].pack('ccN'))
52
+ allow(ssl_socket).to receive_messages(read: [8, 4, notification.id].pack('ccN'))
54
53
  enable_io_select
55
54
  end
56
55
 
@@ -15,6 +15,8 @@ describe 'Synchronization' do
15
15
  before do
16
16
  app.name = 'test'
17
17
  app.auth_key = 'abc123'
18
+ app.client_id = 'client'
19
+ app.client_secret = 'secret'
18
20
  app.connections = 2
19
21
  app.certificate = TEST_CERT_WITH_PASSWORD
20
22
  app.password = 'fubar'
@@ -65,4 +67,31 @@ describe 'Synchronization' do
65
67
  running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
66
68
  expect(running_app.environment).to eql('production')
67
69
  end
70
+
71
+ it 'restarts an app when the auth_key is changed' do
72
+ app.auth_key = '321cba'
73
+ app.save!
74
+ Rpush.sync
75
+
76
+ running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
77
+ expect(running_app.auth_key).to eql('321cba')
78
+ end
79
+
80
+ it 'restarts an app when the client_id is changed' do
81
+ app.client_id = 'another_client'
82
+ app.save!
83
+ Rpush.sync
84
+
85
+ running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
86
+ expect(running_app.client_id).to eql('another_client')
87
+ end
88
+
89
+ it 'restarts an app when the client_secret is changed' do
90
+ app.client_secret = 'another_secret'
91
+ app.save!
92
+ Rpush.sync
93
+
94
+ running_app = Rpush::Daemon::AppRunner.app_with_id(app.id)
95
+ expect(running_app.client_secret).to eql('another_secret')
96
+ end
68
97
  end
@@ -31,11 +31,6 @@ def redis?
31
31
  client == :redis
32
32
  end
33
33
 
34
- def mongoid?
35
- client == :mongoid
36
- end
37
-
38
- require 'support/mongoid_setup' if mongoid?
39
34
  require 'support/active_record_setup' if active_record?
40
35
 
41
36
  RPUSH_ROOT = '/tmp/rails_root'
@@ -31,8 +31,9 @@ require 'generators/templates/rpush_2_0_0_updates'
31
31
  require 'generators/templates/rpush_2_1_0_updates'
32
32
  require 'generators/templates/rpush_2_6_0_updates'
33
33
  require 'generators/templates/rpush_2_7_0_updates'
34
+ require 'generators/templates/rpush_3_0_0_updates'
34
35
 
35
- migrations = [AddRpush, Rpush200Updates, Rpush210Updates, Rpush260Updates, Rpush270Updates]
36
+ migrations = [AddRpush, Rpush200Updates, Rpush210Updates, Rpush260Updates, Rpush270Updates, Rpush300Updates]
36
37
 
37
38
  unless ENV['TRAVIS']
38
39
  migrations.reverse_each do |m|
@@ -1,7 +1,14 @@
1
1
  require 'unit_spec_helper'
2
2
 
3
3
  describe Rpush, 'apns_feedback' do
4
- let!(:app) { Rpush::Apns::App.create!(name: 'test', environment: 'production', certificate: TEST_CERT) }
4
+ let!(:apns_app) do
5
+ Rpush::Apns::App.create!(name: 'test', environment: 'production', certificate: TEST_CERT)
6
+ end
7
+
8
+ let!(:gcm_app) do
9
+ Rpush::Gcm::App.create!(name: 'MyApp', auth_key: 'abc123')
10
+ end
11
+
5
12
  let(:receiver) { double(check_for_feedback: nil) }
6
13
 
7
14
  before do
@@ -14,7 +21,7 @@ describe Rpush, 'apns_feedback' do
14
21
  end
15
22
 
16
23
  it 'checks feedback for each app' do
17
- expect(Rpush::Daemon::Apns::FeedbackReceiver).to receive(:new).with(app).and_return(receiver)
24
+ expect(Rpush::Daemon::Apns::FeedbackReceiver).to receive(:new).with(apns_app).and_return(receiver)
18
25
  expect(receiver).to receive(:check_for_feedback)
19
26
  Rpush.apns_feedback
20
27
  end
@@ -34,8 +34,8 @@ describe Rpush::Client::ActiveRecord::Apns::Notification do
34
34
  expect(notification.alert).to eq("*" * 300)
35
35
  end
36
36
 
37
- it "should default the sound to 'default'" do
38
- expect(notification.sound).to eq('default')
37
+ it "should default the sound to nil" do
38
+ expect(notification.sound).to be_nil
39
39
  end
40
40
 
41
41
  it "should default the expiry to 1 day" do
@@ -131,6 +131,38 @@ describe Rpush::Client::ActiveRecord::Apns::Notification, 'MDM' do
131
131
  end
132
132
  end if active_record?
133
133
 
134
+ describe Rpush::Client::ActiveRecord::Apns::Notification, 'mutable-content' do
135
+ let(:notification) { Rpush::Client::ActiveRecord::Apns::Notification.new }
136
+
137
+ it 'includes mutable-content in the payload' do
138
+ notification.mutable_content = true
139
+ expect(notification.as_json['aps']['mutable-content']).to eq 1
140
+ end
141
+
142
+ it 'does not include content-available in the payload if not set' do
143
+ expect(notification.as_json['aps'].key?('mutable-content')).to be_falsey
144
+ end
145
+
146
+ it 'does not include mutable-content as a non-aps attribute' do
147
+ notification.mutable_content = true
148
+ expect(notification.as_json.key?('mutable-content')).to be_falsey
149
+ end
150
+
151
+ it 'does not overwrite existing attributes for the device' do
152
+ notification.data = { hi: :mom }
153
+ notification.mutable_content = true
154
+ expect(notification.as_json['aps']['mutable-content']).to eq 1
155
+ expect(notification.as_json['hi']).to eq 'mom'
156
+ end
157
+
158
+ it 'does not overwrite the mutable-content flag when setting attributes for the device' do
159
+ notification.mutable_content = true
160
+ notification.data = { hi: :mom }
161
+ expect(notification.as_json['aps']['mutable-content']).to eq 1
162
+ expect(notification.as_json['hi']).to eq 'mom'
163
+ end
164
+ end if active_record?
165
+
134
166
  describe Rpush::Client::ActiveRecord::Apns::Notification, 'content-available' do
135
167
  let(:notification) { Rpush::Client::ActiveRecord::Apns::Notification.new }
136
168