rpush 5.2.0 → 6.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/README.md +53 -8
- data/lib/generators/templates/add_adm.rb +1 -1
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +2 -2
- data/lib/generators/templates/add_app_to_rapns.rb +2 -2
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
- data/lib/generators/templates/add_gcm.rb +11 -25
- data/lib/generators/templates/add_rpush.rb +33 -83
- data/lib/generators/templates/add_wpns.rb +1 -1
- data/lib/generators/templates/create_rapns_apps.rb +1 -1
- data/lib/generators/templates/create_rapns_feedback.rb +3 -9
- data/lib/generators/templates/create_rapns_notifications.rb +3 -9
- data/lib/generators/templates/rename_rapns_to_rpush.rb +9 -33
- data/lib/generators/templates/rpush.rb +1 -4
- data/lib/generators/templates/rpush_2_0_0_updates.rb +5 -17
- data/lib/generators/templates/rpush_2_1_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_2_6_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_2_7_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_0_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_0_1_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +1 -1
- data/lib/generators/templates/rpush_3_1_1_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_2_0_add_apns_p8.rb +1 -1
- data/lib/generators/templates/rpush_3_2_4_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_3_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_3_3_1_updates.rb +3 -3
- data/lib/generators/templates/rpush_4_1_0_updates.rb +1 -1
- data/lib/generators/templates/rpush_4_1_1_updates.rb +1 -1
- data/lib/generators/templates/rpush_4_2_0_updates.rb +1 -1
- data/lib/rpush/client/active_model/adm/data_validator.rb +1 -1
- data/lib/rpush/client/active_model/apns/app.rb +1 -17
- data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +2 -2
- data/lib/rpush/client/active_model/apns/notification.rb +4 -0
- data/lib/rpush/client/active_model/apns/notification_payload_size_validator.rb +1 -1
- data/lib/rpush/client/active_model/apns2/app.rb +7 -1
- data/lib/rpush/client/active_model/certificate_private_key_validator.rb +19 -0
- data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +1 -1
- data/lib/rpush/client/active_model/payload_data_size_validator.rb +1 -1
- data/lib/rpush/client/active_model/registration_ids_count_validator.rb +1 -1
- data/lib/rpush/client/active_model/webpush/app.rb +41 -0
- data/lib/rpush/client/active_model/webpush/notification.rb +66 -0
- data/lib/rpush/client/active_model.rb +4 -0
- data/lib/rpush/client/active_record/apnsp8/notification.rb +1 -0
- data/lib/rpush/client/active_record/webpush/app.rb +11 -0
- data/lib/rpush/client/active_record/webpush/notification.rb +12 -0
- data/lib/rpush/client/active_record.rb +3 -0
- data/lib/rpush/client/redis/apnsp8/notification.rb +2 -0
- data/lib/rpush/client/redis/webpush/app.rb +15 -0
- data/lib/rpush/client/redis/webpush/notification.rb +15 -0
- data/lib/rpush/client/redis.rb +3 -0
- data/lib/rpush/configuration.rb +1 -1
- data/lib/rpush/daemon/apns2/delivery.rb +8 -1
- data/lib/rpush/daemon/apnsp8/delivery.rb +7 -1
- data/lib/rpush/daemon/string_helpers.rb +1 -1
- data/lib/rpush/daemon/webpush/delivery.rb +114 -0
- data/lib/rpush/daemon/webpush.rb +10 -0
- data/lib/rpush/daemon.rb +3 -0
- data/lib/rpush/version.rb +3 -3
- data/spec/functional/apns2_spec.rb +14 -2
- data/spec/functional/retry_spec.rb +1 -1
- data/spec/functional/webpush_spec.rb +31 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/active_record_setup.rb +4 -3
- data/spec/support/config/database.yml +4 -4
- data/spec/support/simplecov_helper.rb +1 -1
- data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
- data/spec/unit/client/active_record/apns2/app_spec.rb +1 -0
- data/spec/unit/client/active_record/apns2/notification_spec.rb +1 -1
- data/spec/unit/client/active_record/apnsp8/notification_spec.rb +28 -0
- data/spec/unit/client/active_record/webpush/app_spec.rb +6 -0
- data/spec/unit/client/active_record/webpush/notification_spec.rb +6 -0
- data/spec/unit/client/redis/apns/notification_spec.rb +1 -1
- data/spec/unit/client/redis/apns2/notification_spec.rb +1 -1
- data/spec/unit/client/redis/apnsp8/notification_spec.rb +29 -0
- data/spec/unit/client/redis/webpush/app_spec.rb +5 -0
- data/spec/unit/client/redis/webpush/notification_spec.rb +5 -0
- data/spec/unit/client/shared/apns/notification.rb +15 -0
- data/spec/unit/client/shared/webpush/app.rb +33 -0
- data/spec/unit/client/shared/webpush/notification.rb +83 -0
- data/spec/unit/daemon/apnsp8/delivery_spec.rb +53 -0
- data/spec/unit/daemon/pushy/delivery_spec.rb +5 -3
- data/spec/unit/daemon/webpush/delivery_spec.rb +144 -0
- metadata +50 -5
|
@@ -8,6 +8,7 @@ module Rpush
|
|
|
8
8
|
class Delivery < Rpush::Daemon::Delivery
|
|
9
9
|
RETRYABLE_CODES = [ 429, 500, 503 ]
|
|
10
10
|
CLIENT_JOIN_TIMEOUT = 60
|
|
11
|
+
DEFAULT_MAX_CONCURRENT_STREAMS = 100
|
|
11
12
|
|
|
12
13
|
def initialize(app, http2_client, token_provider, batch)
|
|
13
14
|
@app = app
|
|
@@ -85,7 +86,11 @@ module Rpush
|
|
|
85
86
|
def remote_max_concurrent_streams
|
|
86
87
|
# 0x7fffffff is the default value from http-2 gem (2^31)
|
|
87
88
|
if @client.remote_settings[:settings_max_concurrent_streams] == 0x7fffffff
|
|
88
|
-
|
|
89
|
+
# Ideally we'd fall back to `#local_settings` here, but `NetHttp2::Client`
|
|
90
|
+
# doesn't expose that attr from the `HTTP2::Client` it wraps. Instead, we
|
|
91
|
+
# chose a hard-coded value matching the default local setting from the
|
|
92
|
+
# `HTTP2::Client` class
|
|
93
|
+
DEFAULT_MAX_CONCURRENT_STREAMS
|
|
89
94
|
else
|
|
90
95
|
@client.remote_settings[:settings_max_concurrent_streams]
|
|
91
96
|
end
|
|
@@ -144,6 +149,7 @@ module Rpush
|
|
|
144
149
|
headers['apns-priority'] = '10'
|
|
145
150
|
headers['apns-topic'] = @app.bundle_id
|
|
146
151
|
headers['authorization'] = "bearer #{jwt_token}"
|
|
152
|
+
headers['apns-push-type'] = 'background' if notification.content_available?
|
|
147
153
|
|
|
148
154
|
headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
|
|
149
155
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webpush"
|
|
4
|
+
|
|
5
|
+
module Rpush
|
|
6
|
+
module Daemon
|
|
7
|
+
module Webpush
|
|
8
|
+
|
|
9
|
+
# Webpush::Request handles all the encryption / signing.
|
|
10
|
+
# We just override #perform to inject the http instance that is managed
|
|
11
|
+
# by Rpush.
|
|
12
|
+
#
|
|
13
|
+
class Request < ::Webpush::Request
|
|
14
|
+
def perform(http)
|
|
15
|
+
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
16
|
+
req.body = body
|
|
17
|
+
http.request(uri, req)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Delivery < Rpush::Daemon::Delivery
|
|
22
|
+
|
|
23
|
+
OK = [ 200, 201, 202 ].freeze
|
|
24
|
+
TEMPORARY_FAILURES = [ 429, 500, 502, 503, 504 ].freeze
|
|
25
|
+
|
|
26
|
+
def initialize(app, http, notification, batch)
|
|
27
|
+
@app = app
|
|
28
|
+
@http = http
|
|
29
|
+
@notification = notification
|
|
30
|
+
@batch = batch
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def perform
|
|
34
|
+
response = send_request
|
|
35
|
+
process_response response
|
|
36
|
+
rescue SocketError, SystemCallError => error
|
|
37
|
+
mark_retryable(@notification, Time.now + 10.seconds, error)
|
|
38
|
+
raise
|
|
39
|
+
rescue StandardError => error
|
|
40
|
+
mark_failed(error)
|
|
41
|
+
raise
|
|
42
|
+
ensure
|
|
43
|
+
@batch.notification_processed
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def send_request
|
|
49
|
+
# The initializer is inherited from Webpush::Request and looks like
|
|
50
|
+
# this:
|
|
51
|
+
#
|
|
52
|
+
# initialize(message: '', subscription:, vapid:, **options)
|
|
53
|
+
#
|
|
54
|
+
# where subscription is a hash of :endpoint and :keys, and vapid
|
|
55
|
+
# holds the vapid public and private keys and the :subject (which is
|
|
56
|
+
# an email address).
|
|
57
|
+
Request.new(
|
|
58
|
+
message: @notification.message,
|
|
59
|
+
subscription: @notification.subscription,
|
|
60
|
+
vapid: @app.vapid,
|
|
61
|
+
ttl: @notification.time_to_live,
|
|
62
|
+
urgency: @notification.urgency
|
|
63
|
+
).perform(@http)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def process_response(response)
|
|
67
|
+
case response.code.to_i
|
|
68
|
+
when *OK
|
|
69
|
+
mark_delivered
|
|
70
|
+
when *TEMPORARY_FAILURES
|
|
71
|
+
retry_delivery(response)
|
|
72
|
+
else
|
|
73
|
+
fail_delivery(response)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def retry_delivery(response)
|
|
78
|
+
time = deliver_after_header(response)
|
|
79
|
+
if time
|
|
80
|
+
mark_retryable(@notification, time)
|
|
81
|
+
else
|
|
82
|
+
mark_retryable_exponential(@notification)
|
|
83
|
+
end
|
|
84
|
+
log_info("Webpush endpoint responded with a #{response.code} error. #{retry_message}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def fail_delivery(response)
|
|
88
|
+
fail_message = fail_message(response)
|
|
89
|
+
log_error("#{@notification.id} failed: #{fail_message}")
|
|
90
|
+
fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, fail_message)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def deliver_after_header(response)
|
|
94
|
+
Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def retry_message
|
|
98
|
+
deliver_after = @notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')
|
|
99
|
+
"Notification #{@notification.id} will be retried after #{deliver_after} (retry #{@notification.retries})."
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fail_message(response)
|
|
103
|
+
msg = Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i]
|
|
104
|
+
if explanation = response.body.to_s[0..200].presence
|
|
105
|
+
msg += ": #{explanation}"
|
|
106
|
+
end
|
|
107
|
+
msg
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
data/lib/rpush/daemon.rb
CHANGED
data/lib/rpush/version.rb
CHANGED
|
@@ -42,6 +42,7 @@ describe 'APNs http2 adapter' do
|
|
|
42
42
|
app.certificate = TEST_CERT
|
|
43
43
|
app.name = 'test'
|
|
44
44
|
app.environment = 'development'
|
|
45
|
+
app.bundle_id = 'com.example.app'
|
|
45
46
|
app.save!
|
|
46
47
|
app
|
|
47
48
|
end
|
|
@@ -75,7 +76,13 @@ describe 'APNs http2 adapter' do
|
|
|
75
76
|
:post,
|
|
76
77
|
"/3/device/#{fake_device_token}",
|
|
77
78
|
{ body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\",\"content-available\":1}}",
|
|
78
|
-
headers: {
|
|
79
|
+
headers: {
|
|
80
|
+
'apns-expiration' => '0',
|
|
81
|
+
'apns-priority' => '10',
|
|
82
|
+
'apns-topic' => 'com.example.app',
|
|
83
|
+
'apns-push-type' => 'background'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
79
86
|
)
|
|
80
87
|
.and_return(fake_http2_request)
|
|
81
88
|
|
|
@@ -104,7 +111,12 @@ describe 'APNs http2 adapter' do
|
|
|
104
111
|
"/3/device/#{fake_device_token}",
|
|
105
112
|
{ body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\","\
|
|
106
113
|
"\"content-available\":1},\"some_field\":\"some value\"}",
|
|
107
|
-
headers: {
|
|
114
|
+
headers: {
|
|
115
|
+
'apns-topic' => bundle_id,
|
|
116
|
+
'apns-expiration' => '0',
|
|
117
|
+
'apns-priority' => '10',
|
|
118
|
+
'apns-push-type' => 'background'
|
|
119
|
+
}
|
|
108
120
|
}
|
|
109
121
|
).and_return(fake_http2_request)
|
|
110
122
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'functional_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Webpush' do
|
|
4
|
+
let(:code) { 201 }
|
|
5
|
+
let(:response) { instance_double('Net::HTTPResponse', code: code, body: '') }
|
|
6
|
+
let(:http) { instance_double('Net::HTTP::Persistent', request: response, shutdown: nil) }
|
|
7
|
+
let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
|
|
8
|
+
|
|
9
|
+
let(:device_reg) {
|
|
10
|
+
{ endpoint: 'https://webpush-provider.example.org/push/some-id',
|
|
11
|
+
expirationTime: nil,
|
|
12
|
+
keys: {'auth' => 'DgN9EBia1o057BdhCOGURA', 'p256dh' => 'BAtxJ--7vHq9IVm8utUB3peJ4lpxRqk1rukCIkVJOomS83QkCnrQ4EyYQsSaCRgy_c8XPytgXxuyAvRJdnTPK4A'} }
|
|
13
|
+
}
|
|
14
|
+
let(:notification) { Rpush::Webpush::Notification.create!(app: app, registration_ids: [device_reg], data: { message: 'test' }) }
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
allow(Net::HTTP::Persistent).to receive_messages(new: http)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'deliveres a notification successfully' do
|
|
21
|
+
expect { Rpush.push }.to change { notification.reload.delivered }.to(true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when delivery failed' do
|
|
25
|
+
let(:code) { 404 }
|
|
26
|
+
it 'marks a notification as failed' do
|
|
27
|
+
expect { Rpush.push }.to change { notification.reload.failed }.to(true)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
data/spec/spec_helper.rb
CHANGED
|
@@ -3,7 +3,7 @@ def client
|
|
|
3
3
|
(ENV['CLIENT'] || :active_record).to_sym
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
-
if !ENV['
|
|
6
|
+
if !ENV['CI'] || (ENV['CI'] && ENV['QUALITY'] == 'true')
|
|
7
7
|
begin
|
|
8
8
|
require './spec/support/simplecov_helper'
|
|
9
9
|
include SimpleCovHelper
|
|
@@ -46,6 +46,8 @@ path = File.join(File.dirname(__FILE__), 'support')
|
|
|
46
46
|
TEST_CERT = File.read(File.join(path, 'cert_without_password.pem'))
|
|
47
47
|
TEST_CERT_WITH_PASSWORD = File.read(File.join(path, 'cert_with_password.pem'))
|
|
48
48
|
|
|
49
|
+
VAPID_KEYPAIR = Webpush.generate_key.to_hash.merge(subject: 'rpush-test@example.org').to_json
|
|
50
|
+
|
|
49
51
|
def after_example_cleanup
|
|
50
52
|
Rpush.logger = nil
|
|
51
53
|
Rpush::Daemon.store = nil
|
|
@@ -6,14 +6,15 @@ SPEC_ADAPTER = ENV['ADAPTER'] || 'postgresql'
|
|
|
6
6
|
SPEC_ADAPTER = 'jdbc' + SPEC_ADAPTER if jruby
|
|
7
7
|
|
|
8
8
|
require 'yaml'
|
|
9
|
-
|
|
9
|
+
db_config_path = File.expand_path("config/database.yml", File.dirname(__FILE__))
|
|
10
|
+
db_config = YAML.load(ERB.new(File.read(db_config_path)).result)
|
|
10
11
|
|
|
11
12
|
if db_config[SPEC_ADAPTER].nil?
|
|
12
13
|
puts "No such adapter '#{SPEC_ADAPTER}'. Valid adapters are #{db_config.keys.join(', ')}."
|
|
13
14
|
exit 1
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
if ENV['
|
|
17
|
+
if ENV['CI']
|
|
17
18
|
db_config[SPEC_ADAPTER]['username'] = 'postgres'
|
|
18
19
|
else
|
|
19
20
|
require 'etc'
|
|
@@ -62,7 +63,7 @@ migrations = [
|
|
|
62
63
|
Rpush420Updates
|
|
63
64
|
]
|
|
64
65
|
|
|
65
|
-
unless ENV['
|
|
66
|
+
unless ENV['CI']
|
|
66
67
|
migrations.reverse_each do |m|
|
|
67
68
|
begin
|
|
68
69
|
m.down
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
postgresql:
|
|
4
4
|
adapter: postgresql
|
|
5
|
-
database: rpush_test
|
|
6
|
-
host: localhost
|
|
7
|
-
username: postgres
|
|
8
|
-
password:
|
|
5
|
+
database: <%= ENV.fetch('POSTGRES_DB', 'rpush_test') %>
|
|
6
|
+
host: <%= ENV.fetch('POSTGRES_HOST', 'localhost') %>
|
|
7
|
+
username: <%= ENV.fetch('POSTGRES_USER', 'postgres') %>
|
|
8
|
+
password: <%= ENV.fetch('PGPASSWORD', '') %>
|
|
9
9
|
|
|
10
10
|
jdbcpostgresql:
|
|
11
11
|
adapter: jdbcpostgresql
|
|
@@ -8,7 +8,7 @@ describe Rpush::Client::ActiveRecord::Apns::Notification do
|
|
|
8
8
|
|
|
9
9
|
it "should validate the length of the binary conversion of the notification" do
|
|
10
10
|
notification = described_class.new
|
|
11
|
-
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
|
|
11
|
+
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
|
|
12
12
|
notification.device_token = "a" * 108
|
|
13
13
|
notification.alert = ""
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ describe Rpush::Client::ActiveRecord::Apns2::Notification do
|
|
|
8
8
|
|
|
9
9
|
it "should validate the length of the binary conversion of the notification" do
|
|
10
10
|
notification = described_class.new
|
|
11
|
-
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
|
|
11
|
+
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
|
|
12
12
|
notification.device_token = "a" * 108
|
|
13
13
|
notification.alert = ""
|
|
14
14
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rpush::Client::ActiveRecord::Apnsp8::Notification do
|
|
4
|
+
subject(:notification) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it_behaves_like 'Rpush::Client::Apns::Notification'
|
|
7
|
+
it_behaves_like 'Rpush::Client::ActiveRecord::Notification'
|
|
8
|
+
|
|
9
|
+
it "should validate the length of the binary conversion of the notification", :aggregate_failures do
|
|
10
|
+
notification = described_class.new
|
|
11
|
+
notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
|
|
12
|
+
apn_key_id: "2",
|
|
13
|
+
name: 'test',
|
|
14
|
+
environment: 'development',
|
|
15
|
+
team_id: "3",
|
|
16
|
+
bundle_id: "4")
|
|
17
|
+
notification.device_token = "a" * 108
|
|
18
|
+
notification.alert = ""
|
|
19
|
+
|
|
20
|
+
notification.alert << "a" until notification.payload.bytesize == 4096
|
|
21
|
+
expect(notification.valid?).to be_truthy
|
|
22
|
+
expect(notification.errors[:base]).to be_empty
|
|
23
|
+
|
|
24
|
+
notification.alert << "a"
|
|
25
|
+
expect(notification.valid?).to be_falsey
|
|
26
|
+
expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
|
|
27
|
+
end
|
|
28
|
+
end if active_record?
|
|
@@ -7,7 +7,7 @@ describe Rpush::Client::Redis::Apns::Notification do
|
|
|
7
7
|
|
|
8
8
|
it "should validate the length of the binary conversion of the notification" do
|
|
9
9
|
notification = described_class.new
|
|
10
|
-
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
|
|
10
|
+
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
|
|
11
11
|
notification.device_token = "a" * 108
|
|
12
12
|
notification.alert = ""
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ describe Rpush::Client::Redis::Apns2::Notification do
|
|
|
7
7
|
|
|
8
8
|
it "should validate the length of the binary conversion of the notification" do
|
|
9
9
|
notification = described_class.new
|
|
10
|
-
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
|
|
10
|
+
notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
|
|
11
11
|
notification.device_token = "a" * 108
|
|
12
12
|
notification.alert = ""
|
|
13
13
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "unit_spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Rpush::Client::Redis::Apnsp8::Notification do
|
|
4
|
+
after do
|
|
5
|
+
Rpush::Apnsp8::App.all.select { |a| a.environment == "development" && a.apn_key == "1" }.each(&:destroy)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it_behaves_like "Rpush::Client::Apns::Notification"
|
|
9
|
+
|
|
10
|
+
it "should validate the length of the binary conversion of the notification", :aggregate_failures do
|
|
11
|
+
notification = described_class.new
|
|
12
|
+
notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
|
|
13
|
+
apn_key_id: "2",
|
|
14
|
+
name: 'test',
|
|
15
|
+
environment: 'development',
|
|
16
|
+
team_id: "3",
|
|
17
|
+
bundle_id: "4")
|
|
18
|
+
notification.device_token = "a" * 108
|
|
19
|
+
notification.alert = ""
|
|
20
|
+
|
|
21
|
+
notification.alert << "a" until notification.payload.bytesize == 4096
|
|
22
|
+
expect(notification.valid?).to be_truthy
|
|
23
|
+
expect(notification.errors[:base]).to be_empty
|
|
24
|
+
|
|
25
|
+
notification.alert << "a"
|
|
26
|
+
expect(notification.valid?).to be_falsey
|
|
27
|
+
expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
|
|
28
|
+
end
|
|
29
|
+
end if redis?
|
|
@@ -165,6 +165,21 @@ shared_examples 'Rpush::Client::Apns::Notification' do
|
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
describe 'content_available?' do
|
|
169
|
+
context 'if not set' do
|
|
170
|
+
it 'should be false' do
|
|
171
|
+
expect(notification.content_available?).to be_falsey
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
context 'if set' do
|
|
176
|
+
it 'should be true' do
|
|
177
|
+
notification.content_available = true
|
|
178
|
+
expect(notification.content_available?).to be_truthy
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
168
183
|
describe 'url-args' do
|
|
169
184
|
it 'includes url-args in the payload' do
|
|
170
185
|
notification.url_args = ['url-arg-1']
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
shared_examples 'Rpush::Client::Webpush::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 vapid_keypair' do
|
|
13
|
+
is_expected.not_to be_valid
|
|
14
|
+
expect(subject.errors[:vapid_keypair]).to eq ["can't be blank"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should require the vapid keypair to have subject, public and private key' do
|
|
18
|
+
subject.vapid_keypair = {}.to_json
|
|
19
|
+
is_expected.not_to be_valid
|
|
20
|
+
expect(subject.errors[:vapid_keypair].sort).to eq [
|
|
21
|
+
'must have a private_key entry',
|
|
22
|
+
'must have a public_key entry',
|
|
23
|
+
'must have a subject entry',
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'should require valid json for the keypair' do
|
|
28
|
+
subject.vapid_keypair = 'invalid'
|
|
29
|
+
is_expected.not_to be_valid
|
|
30
|
+
expect(subject.errors[:vapid_keypair].sort).to eq [ 'must be valid JSON' ]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
shared_examples 'Rpush::Client::Webpush::Notification' do
|
|
4
|
+
subject(:notification) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe 'notification attributes' do
|
|
7
|
+
describe 'data' do
|
|
8
|
+
subject { described_class.new(data: { message: 'test', urgency: 'normal' } ) }
|
|
9
|
+
it 'has a message' do
|
|
10
|
+
expect(subject.message).to eq "test"
|
|
11
|
+
end
|
|
12
|
+
it 'has an urgency' do
|
|
13
|
+
expect(subject.urgency).to eq "normal"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe 'subscription' do
|
|
18
|
+
let(:subscription){ { endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}} }
|
|
19
|
+
subject { described_class.new(registration_ids: [subscription]) }
|
|
20
|
+
|
|
21
|
+
it "has a subscription" do
|
|
22
|
+
expect(subject.subscription).to eq({ endpoint: 'https://push.example.org/foo', keys: {foo: 'bar'} })
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
describe 'validates' do
|
|
29
|
+
let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
|
|
30
|
+
|
|
31
|
+
describe 'data' do
|
|
32
|
+
subject { described_class.new(app: app, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
|
|
33
|
+
it 'validates presence' do
|
|
34
|
+
is_expected.not_to be_valid
|
|
35
|
+
expect(subject.errors[:data]).to eq ["can't be blank"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "has a 'data' payload limit of 4096 bytes" do
|
|
39
|
+
subject.data = { message: 'a' * 4096 }
|
|
40
|
+
is_expected.not_to be_valid
|
|
41
|
+
expected_errors = ["Notification payload data cannot be larger than 4096 bytes."]
|
|
42
|
+
expect(subject.errors[:base]).to eq expected_errors
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'registration_ids' do
|
|
47
|
+
subject { described_class.new(app: app, data: { message: 'test' }) }
|
|
48
|
+
it 'validates presence' do
|
|
49
|
+
is_expected.not_to be_valid
|
|
50
|
+
expect(subject.errors[:registration_ids]).to eq ["can't be blank"]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'limits the number of registration ids to exactly 1' do
|
|
54
|
+
subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}] * 2
|
|
55
|
+
is_expected.not_to be_valid
|
|
56
|
+
expected_errors = ["Number of registration_ids cannot be larger than 1."]
|
|
57
|
+
expect(subject.errors[:base]).to eq expected_errors
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'validates the structure of the registration' do
|
|
61
|
+
subject.registration_ids = ['a']
|
|
62
|
+
is_expected.not_to be_valid
|
|
63
|
+
expect(subject.errors[:base]).to eq [
|
|
64
|
+
"Registration must have :endpoint (String) and :keys (Hash) keys"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}]
|
|
68
|
+
is_expected.to be_valid
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe 'time_to_live' do
|
|
73
|
+
subject { described_class.new(app: app, data: { message: 'test' }, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
|
|
74
|
+
|
|
75
|
+
it 'should be > 0' do
|
|
76
|
+
subject.time_to_live = -1
|
|
77
|
+
is_expected.not_to be_valid
|
|
78
|
+
expect(subject.errors[:time_to_live]).to eq ['must be greater than 0']
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'unit_spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Rpush::Daemon::Apnsp8::Delivery do
|
|
4
|
+
subject(:delivery) { described_class.new(app, http2_client, token_provider, batch) }
|
|
5
|
+
|
|
6
|
+
let(:app) { double(bundle_id: 'MY BUNDLE ID') }
|
|
7
|
+
let(:notification1) { double('Notification 1', data: {}, as_json: {}).as_null_object }
|
|
8
|
+
let(:notification2) { double('Notification 2', data: {}, as_json: {}).as_null_object }
|
|
9
|
+
|
|
10
|
+
let(:token_provider) { double(token: 'MY JWT TOKEN') }
|
|
11
|
+
let(:max_concurrent_streams) { 100 }
|
|
12
|
+
let(:remote_settings) { { settings_max_concurrent_streams: max_concurrent_streams } }
|
|
13
|
+
let(:http_request) { double(on: nil) }
|
|
14
|
+
let(:http2_client) do
|
|
15
|
+
double(
|
|
16
|
+
stream_count: 0,
|
|
17
|
+
call_async: nil,
|
|
18
|
+
join: nil,
|
|
19
|
+
prepare_request: http_request,
|
|
20
|
+
remote_settings: remote_settings
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:batch) { double(mark_delivered: nil, all_processed: nil) }
|
|
25
|
+
let(:logger) { double(info: nil) }
|
|
26
|
+
|
|
27
|
+
before do
|
|
28
|
+
allow(batch).to receive(:each_notification) do |&blk|
|
|
29
|
+
[notification1, notification2].each(&blk)
|
|
30
|
+
end
|
|
31
|
+
allow(Rpush).to receive_messages(logger: logger)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '#perform' do
|
|
35
|
+
context 'with an HTTP2 client where max concurrent streams is not set' do
|
|
36
|
+
let(:max_concurrent_streams) { 0x7fffffff }
|
|
37
|
+
|
|
38
|
+
it 'does not fall into an infinite loop on notifications after the first' do
|
|
39
|
+
start = Time.now
|
|
40
|
+
thread = Thread.new { delivery.perform }
|
|
41
|
+
|
|
42
|
+
loop do
|
|
43
|
+
break unless thread.alive?
|
|
44
|
+
|
|
45
|
+
if Time.now - start > 0.5
|
|
46
|
+
thread.kill
|
|
47
|
+
fail 'Stuck in an infinite loop'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|