rpush 5.1.0 → 5.2.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 +11 -3
- data/README.md +56 -15
- data/lib/generators/templates/rpush.rb +4 -0
- data/lib/rpush/configuration.rb +2 -1
- data/lib/rpush/daemon.rb +1 -1
- data/lib/rpush/daemon/apns/feedback_receiver.rb +1 -1
- data/lib/rpush/daemon/app_runner.rb +1 -1
- data/lib/rpush/daemon/batch.rb +12 -5
- data/lib/rpush/daemon/delivery.rb +1 -2
- data/lib/rpush/logger.rb +1 -0
- data/lib/rpush/version.rb +1 -1
- data/spec/functional/apns2_spec.rb +50 -1
- data/spec/unit/daemon/batch_spec.rb +50 -2
- data/spec/unit/daemon/delivery_spec.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6576c1456435dd596c992846892b840f204b4cf8956b13f0c06b9b336b6e0fd8
|
4
|
+
data.tar.gz: 57ba3957011570e259585b3528f0bd344411f22db72bf03cea01bfe049107ac2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fcce116cbddf64aacbf00d2b7294056f3c502b59f108783b53a1c90807cd0816a68be2c43e543625d556ad7de484786edb2baad1dbdc2652cfc1c4f582539f9
|
7
|
+
data.tar.gz: 0e468a1f43d8c422db967946fdc8d867bfc8bce6eaea511bff04e34108f77b75e9b0f903a3c008594482fe159ee9b820cc65be81008d60e2261b26f61c6ca274
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v5.2.0](https://github.com/rpush/rpush/tree/v5.2.0) (2020-10-08)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/rpush/rpush/compare/v5.1.0...v5.2.0)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Allow opting out of foreground stdout logging [\#571](https://github.com/rpush/rpush/pull/571) ([benlangfeld](https://github.com/benlangfeld))
|
10
|
+
- Do not retry notifications which already have been delivered/failed [\#567](https://github.com/rpush/rpush/pull/567) ([AlexTatarnikov](https://github.com/AlexTatarnikov))
|
11
|
+
- Improve APNs documentation. [\#553](https://github.com/rpush/rpush/pull/553) ([timdiggins](https://github.com/timdiggins))
|
12
|
+
|
3
13
|
## [v5.1.0](https://github.com/rpush/rpush/tree/v5.1.0) (2020-09-25)
|
4
14
|
|
5
15
|
[Full Changelog](https://github.com/rpush/rpush/compare/v5.0.0...v5.1.0)
|
@@ -481,6 +491,4 @@ Bug fixes:
|
|
481
491
|
- Removed rpush.yml in favour of command line options.
|
482
492
|
- Started the changelog!
|
483
493
|
|
484
|
-
\*
|
485
|
-
|
486
|
-
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
|
494
|
+
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/README.md
CHANGED
@@ -52,56 +52,97 @@ $ bundle exec rpush init
|
|
52
52
|
|
53
53
|
#### Apple Push Notification Service
|
54
54
|
|
55
|
+
There is a choice of two modes (and one legacy mode) using certificates or using tokens:
|
55
56
|
|
56
|
-
|
57
|
+
* `Rpush::Apns2` This requires an annually renewable certificate. see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns
|
58
|
+
* `Rpush::Apnsp8` This uses encrypted tokens and requires an encryption key id and encryption key (provide as a p8 file). (see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns)
|
59
|
+
* `Rpush::Apns` There is also the original APNS (the original version using certificates with a binary underlying protocol over TCP directly rather than over Http/2).
|
60
|
+
Apple have [announced](https://developer.apple.com/news/?id=11042019a) that this is not supported after November 2020.
|
61
|
+
|
62
|
+
If this is your first time using the APNs, you will need to generate either SSL certificates (for Apns2 or Apns) or an Encryption Key (p8) and an Encryption Key ID (for Apnsp8). See [Generating Certificates](https://github.com/rpush/rpush/wiki/Generating-Certificates) for instructions.
|
63
|
+
|
64
|
+
##### Apnsp8
|
65
|
+
|
66
|
+
To use the p8 APNs Api:
|
57
67
|
|
58
68
|
```ruby
|
59
|
-
app = Rpush::
|
69
|
+
app = Rpush::Apnsp8::App.new
|
60
70
|
app.name = "ios_app"
|
61
|
-
app.
|
71
|
+
app.apn_key = File.read("/path/to/sandbox.p8")
|
62
72
|
app.environment = "development" # APNs environment.
|
63
|
-
app.
|
73
|
+
app.apn_key_id = "APN KEY ID" # This is the Encryption Key ID provided by apple
|
74
|
+
app.team_id = "TEAM ID" # the team id - e.g. ABCDE12345
|
75
|
+
app.bundle_id = "BUNDLE ID" # the unique bundle id of the app, like com.example.appname
|
64
76
|
app.connections = 1
|
65
77
|
app.save!
|
66
78
|
```
|
67
79
|
|
68
80
|
```ruby
|
69
81
|
n = Rpush::Apns::Notification.new
|
70
|
-
n.app = Rpush::
|
82
|
+
n.app = Rpush::Apnsp8::App.find_by_name("ios_app")
|
71
83
|
n.device_token = "..." # hex string
|
72
84
|
n.alert = "hi mom!"
|
73
85
|
n.data = { foo: :bar }
|
74
86
|
n.save!
|
75
87
|
```
|
76
88
|
|
77
|
-
|
89
|
+
##### Apns2
|
78
90
|
|
79
|
-
|
91
|
+
(NB this uses the same protocol as Apnsp8, but authenticates with a certificate rather than tokens)
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
app = Rpush::Apns2::App.new
|
95
|
+
app.name = "ios_app"
|
96
|
+
app.certificate = File.read("/path/to/sandbox.pem")
|
97
|
+
app.environment = "development"
|
98
|
+
app.password = "certificate password"
|
99
|
+
app.connections = 1
|
100
|
+
app.save!
|
101
|
+
```
|
80
102
|
|
81
|
-
|
103
|
+
```ruby
|
104
|
+
n = Rpush::Apns2::Notification.new
|
105
|
+
n.app = Rpush::Apns2::App.find_by_name("ios_app")
|
106
|
+
n.device_token = "..." # hex string
|
107
|
+
n.alert = "hi mom!"
|
108
|
+
n.data = {
|
109
|
+
headers: { 'apns-topic': "BUNDLE ID", # the bundle id of the app, like com.example.appname
|
110
|
+
foo: :bar }
|
111
|
+
}
|
112
|
+
n.save!
|
113
|
+
```
|
82
114
|
|
83
|
-
|
115
|
+
You should also implement the [ssl_certificate_will_expire](https://github.com/rpush/rpush/wiki/Reflection-API) reflection to monitor when your certificate is due to expire.
|
116
|
+
|
117
|
+
##### Apns (legacy protocol)
|
84
118
|
|
85
119
|
```ruby
|
86
|
-
app = Rpush::
|
120
|
+
app = Rpush::Apns::App.new
|
87
121
|
app.name = "ios_app"
|
88
|
-
app.
|
122
|
+
app.certificate = File.read("/path/to/sandbox.pem")
|
89
123
|
app.environment = "development" # APNs environment.
|
90
|
-
app.
|
91
|
-
app.team_id = "TEAM ID"
|
92
|
-
app.bundle_id = "BUNDLE ID"
|
124
|
+
app.password = "certificate password"
|
93
125
|
app.connections = 1
|
94
126
|
app.save!
|
95
127
|
```
|
96
128
|
|
97
129
|
```ruby
|
98
130
|
n = Rpush::Apns::Notification.new
|
99
|
-
n.app = Rpush::
|
131
|
+
n.app = Rpush::Apns::App.find_by_name("ios_app")
|
100
132
|
n.device_token = "..." # hex string
|
101
133
|
n.alert = "hi mom!"
|
102
134
|
n.data = { foo: :bar }
|
103
135
|
n.save!
|
104
136
|
```
|
137
|
+
|
138
|
+
##### Safari Push Notifications
|
139
|
+
|
140
|
+
Using one of the notifications methods above, the `url_args` attribute is available for Safari Push Notifications.
|
141
|
+
|
142
|
+
##### Environment
|
143
|
+
|
144
|
+
The app `environment` for any Apns* option is "development" for XCode installs, and "production" for app store and TestFlight. Note that for Apns2 you can now use one (production + sandbox) certificate (you don't need a separate "sandbox" or development certificate), but if you do generate a development/sandbox certificate it can only be used for "development". With Apnsp8 tokens, you can target either "development" or "production" environments.
|
145
|
+
|
105
146
|
#### Firebase Cloud Messaging
|
106
147
|
|
107
148
|
FCM and GCM are – as of writing – compatible with each other. See also [this comment](https://github.com/rpush/rpush/issues/284#issuecomment-228330206) for further references.
|
@@ -26,6 +26,10 @@ Rpush.configure do |config|
|
|
26
26
|
# Define a custom logger.
|
27
27
|
# config.logger = MyLogger.new
|
28
28
|
|
29
|
+
# By default in foreground mode logs are directed both to the logger and to stdout.
|
30
|
+
# If the logger goes to stdout, you can disable foreground logging to avoid duplication.
|
31
|
+
# config.foreground_logging = false
|
32
|
+
|
29
33
|
# config.apns.feedback_receiver.enabled = true
|
30
34
|
# config.apns.feedback_receiver.frequency = 60
|
31
35
|
|
data/lib/rpush/configuration.rb
CHANGED
@@ -16,7 +16,7 @@ module Rpush
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :log_level, :plugin, :apns]
|
19
|
+
CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :foreground_logging, :log_level, :plugin, :apns]
|
20
20
|
DEPRECATED_ATTRS = []
|
21
21
|
CONFIG_ATTRS = CURRENT_ATTRS + DEPRECATED_ATTRS
|
22
22
|
|
@@ -53,6 +53,7 @@ module Rpush
|
|
53
53
|
self.log_level = (defined?(Rails) && Rails.logger) ? Rails.logger.level : ::Logger::Severity::DEBUG
|
54
54
|
self.plugin = OpenStruct.new
|
55
55
|
self.foreground = false
|
56
|
+
self.foreground_logging = true
|
56
57
|
|
57
58
|
self.apns = ApnsConfiguration.new
|
58
59
|
|
data/lib/rpush/daemon.rb
CHANGED
@@ -29,7 +29,7 @@ module Rpush
|
|
29
29
|
Rpush.logger.info("[#{app.name}] Starting #{pluralize(app.connections, 'dispatcher')}... ", true)
|
30
30
|
runner = @runners[app.id] = new(app)
|
31
31
|
runner.start_dispatchers
|
32
|
-
puts Rainbow('✔').green if Rpush.config.foreground
|
32
|
+
puts Rainbow('✔').green if Rpush.config.foreground && Rpush.config.foreground_logging
|
33
33
|
runner.start_loops
|
34
34
|
rescue StandardError => e
|
35
35
|
@runners.delete(app.id)
|
data/lib/rpush/daemon/batch.rb
CHANGED
@@ -2,6 +2,7 @@ module Rpush
|
|
2
2
|
module Daemon
|
3
3
|
class Batch
|
4
4
|
include Reflectable
|
5
|
+
include Loggable
|
5
6
|
|
6
7
|
attr_reader :num_processed, :notifications, :delivered, :failed, :retryable
|
7
8
|
|
@@ -31,16 +32,21 @@ module Rpush
|
|
31
32
|
@retryable[deliver_after] ||= []
|
32
33
|
@retryable[deliver_after] << notification
|
33
34
|
end
|
35
|
+
|
34
36
|
Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
|
35
37
|
end
|
36
38
|
|
37
|
-
def mark_all_retryable(deliver_after)
|
38
|
-
|
39
|
-
|
40
|
-
end
|
39
|
+
def mark_all_retryable(deliver_after, error)
|
40
|
+
retryable_count = 0
|
41
|
+
|
41
42
|
each_notification do |notification|
|
42
|
-
|
43
|
+
next if notification.delivered || notification.failed
|
44
|
+
|
45
|
+
retryable_count += 1
|
46
|
+
mark_retryable(notification, deliver_after)
|
43
47
|
end
|
48
|
+
|
49
|
+
log_warn("Will retry #{retryable_count} of #{@notifications.size} notifications after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
|
44
50
|
end
|
45
51
|
|
46
52
|
def mark_delivered(notification)
|
@@ -54,6 +60,7 @@ module Rpush
|
|
54
60
|
@mutex.synchronize do
|
55
61
|
@delivered = @notifications
|
56
62
|
end
|
63
|
+
|
57
64
|
each_notification do |notification|
|
58
65
|
Rpush::Daemon.store.mark_delivered(notification, Time.now, persist: false)
|
59
66
|
end
|
@@ -20,8 +20,7 @@ module Rpush
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def mark_batch_retryable(deliver_after, error)
|
23
|
-
|
24
|
-
@batch.mark_all_retryable(deliver_after)
|
23
|
+
@batch.mark_all_retryable(deliver_after, error)
|
25
24
|
end
|
26
25
|
|
27
26
|
def mark_delivered
|
data/lib/rpush/logger.rb
CHANGED
data/lib/rpush/version.rb
CHANGED
@@ -177,6 +177,13 @@ describe 'APNs http2 adapter' do
|
|
177
177
|
end
|
178
178
|
|
179
179
|
context 'when there is SocketError' do
|
180
|
+
let(:fake_http_resp_headers) {
|
181
|
+
{
|
182
|
+
":status" => "500",
|
183
|
+
"apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6"
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
180
187
|
before(:each) do
|
181
188
|
expect(fake_client).to receive(:call_async) { raise(SocketError) }
|
182
189
|
end
|
@@ -201,6 +208,24 @@ describe 'APNs http2 adapter' do
|
|
201
208
|
notification = create_notification
|
202
209
|
Rpush.push
|
203
210
|
end
|
211
|
+
|
212
|
+
context 'when specific notification was delivered before request failed' do
|
213
|
+
let(:fake_http_resp_headers) {
|
214
|
+
{
|
215
|
+
":status" => "200",
|
216
|
+
"apns-id"=>"C6D65840-5E3F-785A-4D91-B97D305C12F6"
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
it 'fails but will not retry this notification' do
|
221
|
+
notification = create_notification
|
222
|
+
expect do
|
223
|
+
Rpush.push
|
224
|
+
notification.reload
|
225
|
+
end.to change(notification, :retries).by(0)
|
226
|
+
.and change(notification, :delivered).to(true)
|
227
|
+
end
|
228
|
+
end
|
204
229
|
end
|
205
230
|
|
206
231
|
context 'when any StandardError occurs' do
|
@@ -230,8 +255,17 @@ describe 'APNs http2 adapter' do
|
|
230
255
|
end
|
231
256
|
|
232
257
|
context 'when waiting for requests to complete times out' do
|
258
|
+
let(:on_close) do
|
259
|
+
proc { |&block| @thread = Thread.new { sleep(0.01) } }
|
260
|
+
end
|
261
|
+
|
233
262
|
before(:each) do
|
234
|
-
|
263
|
+
@thread = nil
|
264
|
+
|
265
|
+
expect(fake_http2_request).
|
266
|
+
to receive(:on).with(:close), &on_close
|
267
|
+
|
268
|
+
expect(fake_client).to receive(:join) { @thread.join; raise(NetHttp2::AsyncRequestTimeout) }
|
235
269
|
end
|
236
270
|
|
237
271
|
it 'closes the client' do
|
@@ -263,6 +297,21 @@ describe 'APNs http2 adapter' do
|
|
263
297
|
notification.reload
|
264
298
|
end.to change(notification, :retries)
|
265
299
|
end
|
300
|
+
|
301
|
+
context 'when specific notification was delivered before another async call failed' do
|
302
|
+
let(:on_close) do
|
303
|
+
proc { |&block| @thread = Thread.new { sleep(0.01); block.call } }
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'fails but retries delivery several times' do
|
307
|
+
notification = create_notification
|
308
|
+
expect do
|
309
|
+
Rpush.push
|
310
|
+
notification.reload
|
311
|
+
end.to change(notification, :retries).by(0)
|
312
|
+
.and change(notification, :delivered).to(true)
|
313
|
+
end
|
314
|
+
end
|
266
315
|
end
|
267
316
|
end
|
268
317
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'unit_spec_helper'
|
2
2
|
|
3
3
|
describe Rpush::Daemon::Batch do
|
4
|
-
let(:notification1) { double(:notification1, id: 1) }
|
5
|
-
let(:notification2) { double(:notification2, id: 2) }
|
4
|
+
let(:notification1) { double(:notification1, id: 1, delivered: false, failed: false) }
|
5
|
+
let(:notification2) { double(:notification2, id: 2, delivered: false, failed: false) }
|
6
6
|
let(:batch) { Rpush::Daemon::Batch.new([notification1, notification2]) }
|
7
7
|
let(:store) { double.as_null_object }
|
8
8
|
let(:time) { Time.now }
|
@@ -50,6 +50,54 @@ describe Rpush::Daemon::Batch do
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
describe 'mark_all_retryable' do
|
54
|
+
let(:error) { StandardError.new('Exception') }
|
55
|
+
|
56
|
+
it 'marks all notifications as retryable without persisting' do
|
57
|
+
expect(store).to receive(:mark_retryable).ordered.with(notification1, time, persist: false)
|
58
|
+
expect(store).to receive(:mark_retryable).ordered.with(notification2, time, persist: false)
|
59
|
+
|
60
|
+
batch.mark_all_retryable(time, error)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'defers persisting' do
|
64
|
+
batch.mark_all_retryable(time, error)
|
65
|
+
expect(batch.retryable).to eq(time => [notification1, notification2])
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when one of the notifications delivered' do
|
69
|
+
let(:notification2) { double(:notification2, id: 2, delivered: true, failed: false) }
|
70
|
+
|
71
|
+
it 'marks all only pending notification as retryable without persisting' do
|
72
|
+
expect(store).to receive(:mark_retryable).ordered.with(notification1, time, persist: false)
|
73
|
+
expect(store).not_to receive(:mark_retryable).ordered.with(notification2, time, persist: false)
|
74
|
+
|
75
|
+
batch.mark_all_retryable(time, error)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'defers persisting' do
|
79
|
+
batch.mark_all_retryable(time, error)
|
80
|
+
expect(batch.retryable).to eq(time => [notification1])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when one of the notifications failed' do
|
85
|
+
let(:notification2) { double(:notification2, id: 2, delivered: false, failed: true) }
|
86
|
+
|
87
|
+
it 'marks all only pending notification as retryable without persisting' do
|
88
|
+
expect(store).to receive(:mark_retryable).ordered.with(notification1, time, persist: false)
|
89
|
+
expect(store).not_to receive(:mark_retryable).ordered.with(notification2, time, persist: false)
|
90
|
+
|
91
|
+
batch.mark_all_retryable(time, error)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'defers persisting' do
|
95
|
+
batch.mark_all_retryable(time, error)
|
96
|
+
expect(batch.retryable).to eq(time => [notification1])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
53
101
|
describe 'mark_failed' do
|
54
102
|
it 'marks the notification as failed without persisting' do
|
55
103
|
expect(store).to receive(:mark_failed).with(notification1, 1, 'an error', time, persist: false)
|
@@ -41,6 +41,16 @@ describe Rpush::Daemon::Delivery do
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
describe 'mark_batch_retryable' do
|
45
|
+
let(:batch) { double(Rpush::Daemon::Batch) }
|
46
|
+
let(:error) { StandardError.new('Exception') }
|
47
|
+
|
48
|
+
it 'marks all notifications as retryable' do
|
49
|
+
expect(batch).to receive(:mark_all_retryable)
|
50
|
+
delivery.mark_batch_retryable(Time.now + 1.hour, error)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
describe 'mark_batch_failed' do
|
45
55
|
it 'marks all notifications as delivered' do
|
46
56
|
error = Rpush::DeliveryError.new(1, 42, 'an error')
|
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: 5.
|
4
|
+
version: 5.2.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: 2020-
|
11
|
+
date: 2020-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: multi_json
|