rpush 2.7.0 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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