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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +36 -15
- data/lib/generators/rpush_migration_generator.rb +1 -0
- data/lib/generators/templates/add_adm.rb +1 -1
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +1 -1
- data/lib/generators/templates/add_app_to_rapns.rb +1 -1
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
- data/lib/generators/templates/add_gcm.rb +1 -1
- data/lib/generators/templates/add_rpush.rb +11 -11
- 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 +1 -1
- data/lib/generators/templates/create_rapns_notifications.rb +1 -1
- data/lib/generators/templates/rename_rapns_to_rpush.rb +1 -1
- data/lib/generators/templates/rpush.rb +1 -1
- data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
- 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 +11 -0
- data/lib/rpush.rb +2 -9
- data/lib/rpush/apns_feedback.rb +4 -0
- data/lib/rpush/cli.rb +2 -2
- data/lib/rpush/client/active_model.rb +3 -0
- data/lib/rpush/client/active_model/apns/notification.rb +11 -1
- data/lib/rpush/client/active_model/apns2/app.rb +15 -0
- data/lib/rpush/client/active_model/apns2/notification.rb +9 -0
- data/lib/rpush/client/active_record.rb +3 -0
- data/lib/rpush/client/active_record/apns/feedback.rb +0 -4
- data/lib/rpush/client/active_record/apns2/app.rb +11 -0
- data/lib/rpush/client/active_record/apns2/notification.rb +10 -0
- data/lib/rpush/client/active_record/app.rb +0 -4
- data/lib/rpush/client/active_record/notification.rb +0 -7
- data/lib/rpush/client/redis.rb +3 -0
- data/lib/rpush/client/redis/apns2/app.rb +11 -0
- data/lib/rpush/client/redis/apns2/notification.rb +11 -0
- data/lib/rpush/client/redis/notification.rb +1 -0
- data/lib/rpush/daemon.rb +5 -3
- data/lib/rpush/daemon/apns2.rb +10 -0
- data/lib/rpush/daemon/apns2/delivery.rb +127 -0
- data/lib/rpush/daemon/dispatcher/apns_http2.rb +50 -0
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +1 -1
- data/lib/rpush/daemon/dispatcher/http.rb +1 -1
- data/lib/rpush/daemon/gcm/delivery.rb +5 -5
- data/lib/rpush/daemon/service_config_methods.rb +4 -3
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +11 -3
- data/lib/rpush/daemon/synchronizer.rb +14 -12
- data/lib/rpush/version.rb +12 -1
- data/spec/functional/apns2_spec.rb +232 -0
- data/spec/functional/apns_spec.rb +1 -2
- data/spec/functional/synchronization_spec.rb +29 -0
- data/spec/spec_helper.rb +0 -5
- data/spec/support/active_record_setup.rb +2 -1
- data/spec/unit/apns_feedback_spec.rb +9 -2
- data/spec/unit/client/active_record/apns/notification_spec.rb +34 -2
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +2 -21
- metadata +256 -29
- data/lib/rpush/client/mongoid.rb +0 -36
- data/lib/rpush/client/mongoid/adm/app.rb +0 -14
- data/lib/rpush/client/mongoid/adm/notification.rb +0 -11
- data/lib/rpush/client/mongoid/apns/app.rb +0 -11
- data/lib/rpush/client/mongoid/apns/feedback.rb +0 -24
- data/lib/rpush/client/mongoid/apns/notification.rb +0 -15
- data/lib/rpush/client/mongoid/app.rb +0 -23
- data/lib/rpush/client/mongoid/gcm/app.rb +0 -11
- data/lib/rpush/client/mongoid/gcm/notification.rb +0 -11
- data/lib/rpush/client/mongoid/notification.rb +0 -51
- data/lib/rpush/client/mongoid/wns/app.rb +0 -14
- data/lib/rpush/client/mongoid/wns/badge_notification.rb +0 -15
- data/lib/rpush/client/mongoid/wns/notification.rb +0 -11
- data/lib/rpush/client/mongoid/wns/raw_notification.rb +0 -11
- data/lib/rpush/client/mongoid/wpns/app.rb +0 -11
- data/lib/rpush/client/mongoid/wpns/notification.rb +0 -11
- data/lib/rpush/daemon/store/mongoid.rb +0 -157
- data/spec/support/config/mongoid.yml +0 -69
- data/spec/support/mongoid_setup.rb +0 -10
- data/spec/unit/daemon/store/mongoid_spec.rb +0 -339
@@ -9,6 +9,9 @@ require 'rpush/client/active_model/apns/device_token_format_validator'
|
|
9
9
|
require 'rpush/client/active_model/apns/app'
|
10
10
|
require 'rpush/client/active_model/apns/notification'
|
11
11
|
|
12
|
+
require 'rpush/client/active_model/apns2/app'
|
13
|
+
require 'rpush/client/active_model/apns2/notification'
|
14
|
+
|
12
15
|
require 'rpush/client/active_model/adm/data_validator'
|
13
16
|
require 'rpush/client/active_model/adm/app'
|
14
17
|
require 'rpush/client/active_model/adm/notification'
|
@@ -32,6 +32,12 @@ module Rpush
|
|
32
32
|
self.data = (data || {}).merge(MDM_KEY => magic)
|
33
33
|
end
|
34
34
|
|
35
|
+
MUTABLE_CONTENT_KEY = '__rpush_mutable_content__'
|
36
|
+
def mutable_content=(bool)
|
37
|
+
return unless bool
|
38
|
+
self.data = (data || {}).merge(MUTABLE_CONTENT_KEY => true)
|
39
|
+
end
|
40
|
+
|
35
41
|
CONTENT_AVAILABLE_KEY = '__rpush_content_available__'
|
36
42
|
def content_available=(bool)
|
37
43
|
return unless bool
|
@@ -51,12 +57,16 @@ module Rpush
|
|
51
57
|
json['aps']['category'] = category if category
|
52
58
|
json['aps']['url-args'] = url_args if url_args
|
53
59
|
|
60
|
+
if data && data[MUTABLE_CONTENT_KEY]
|
61
|
+
json['aps']['mutable-content'] = 1
|
62
|
+
end
|
63
|
+
|
54
64
|
if data && data[CONTENT_AVAILABLE_KEY]
|
55
65
|
json['aps']['content-available'] = 1
|
56
66
|
end
|
57
67
|
|
58
68
|
if data
|
59
|
-
non_aps_attributes = data.reject { |k, _| k == CONTENT_AVAILABLE_KEY }
|
69
|
+
non_aps_attributes = data.reject { |k, _| k == CONTENT_AVAILABLE_KEY || k == MUTABLE_CONTENT_KEY }
|
60
70
|
non_aps_attributes.each { |k, v| json[k.to_s] = v }
|
61
71
|
end
|
62
72
|
end
|
@@ -9,6 +9,9 @@ require 'rpush/client/active_record/apns/notification'
|
|
9
9
|
require 'rpush/client/active_record/apns/feedback'
|
10
10
|
require 'rpush/client/active_record/apns/app'
|
11
11
|
|
12
|
+
require 'rpush/client/active_record/apns2/notification'
|
13
|
+
require 'rpush/client/active_record/apns2/app'
|
14
|
+
|
12
15
|
require 'rpush/client/active_record/gcm/notification'
|
13
16
|
require 'rpush/client/active_record/gcm/app'
|
14
17
|
|
@@ -5,10 +5,6 @@ module Rpush
|
|
5
5
|
class Feedback < ::ActiveRecord::Base
|
6
6
|
self.table_name = 'rpush_feedback'
|
7
7
|
|
8
|
-
if Rpush.attr_accessible_available?
|
9
|
-
attr_accessible :device_token, :failed_at, :app_id
|
10
|
-
end
|
11
|
-
|
12
8
|
belongs_to :app, class_name: 'Rpush::Client::ActiveRecord::App'
|
13
9
|
|
14
10
|
validates :device_token, presence: true
|
@@ -4,10 +4,6 @@ module Rpush
|
|
4
4
|
class App < ::ActiveRecord::Base
|
5
5
|
self.table_name = 'rpush_apps'
|
6
6
|
|
7
|
-
if Rpush.attr_accessible_available?
|
8
|
-
attr_accessible :name, :environment, :certificate, :password, :connections, :auth_key, :client_id, :client_secret
|
9
|
-
end
|
10
|
-
|
11
7
|
has_many :notifications, class_name: 'Rpush::Client::ActiveRecord::Notification', dependent: :destroy
|
12
8
|
|
13
9
|
validates :name, presence: true, uniqueness: { scope: [:type, :environment] }
|
@@ -12,13 +12,6 @@ module Rpush
|
|
12
12
|
|
13
13
|
belongs_to :app, class_name: 'Rpush::Client::ActiveRecord::App'
|
14
14
|
|
15
|
-
if Rpush.attr_accessible_available?
|
16
|
-
attr_accessible :badge, :device_token, :sound, :alert, :data, :expiry, :delivered,
|
17
|
-
:delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after,
|
18
|
-
:alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids,
|
19
|
-
:uri, :url_args, :category, :content_available, :notification
|
20
|
-
end
|
21
|
-
|
22
15
|
def data=(attrs)
|
23
16
|
return unless attrs
|
24
17
|
fail ArgumentError, 'must be a Hash' unless attrs.is_a?(Hash)
|
data/lib/rpush/client/redis.rb
CHANGED
@@ -21,6 +21,9 @@ require 'rpush/client/redis/apns/app'
|
|
21
21
|
require 'rpush/client/redis/apns/notification'
|
22
22
|
require 'rpush/client/redis/apns/feedback'
|
23
23
|
|
24
|
+
require 'rpush/client/redis/apns2/app'
|
25
|
+
require 'rpush/client/redis/apns2/notification'
|
26
|
+
|
24
27
|
require 'rpush/client/redis/gcm/app'
|
25
28
|
require 'rpush/client/redis/gcm/notification'
|
26
29
|
|
data/lib/rpush/daemon.rb
CHANGED
@@ -24,6 +24,7 @@ require 'rpush/daemon/dispatcher_loop'
|
|
24
24
|
require 'rpush/daemon/dispatcher/http'
|
25
25
|
require 'rpush/daemon/dispatcher/tcp'
|
26
26
|
require 'rpush/daemon/dispatcher/apns_tcp'
|
27
|
+
require 'rpush/daemon/dispatcher/apns_http2'
|
27
28
|
require 'rpush/daemon/service_config_methods'
|
28
29
|
require 'rpush/daemon/retry_header_parser'
|
29
30
|
require 'rpush/daemon/ring_buffer'
|
@@ -40,6 +41,9 @@ require 'rpush/daemon/apns/delivery'
|
|
40
41
|
require 'rpush/daemon/apns/feedback_receiver'
|
41
42
|
require 'rpush/daemon/apns'
|
42
43
|
|
44
|
+
require 'rpush/daemon/apns2/delivery'
|
45
|
+
require 'rpush/daemon/apns2'
|
46
|
+
|
43
47
|
require 'rpush/daemon/gcm/delivery'
|
44
48
|
require 'rpush/daemon/gcm'
|
45
49
|
|
@@ -102,9 +106,7 @@ module Rpush
|
|
102
106
|
end
|
103
107
|
|
104
108
|
def self.shutdown_lock
|
105
|
-
|
106
|
-
@shutdown_lock = Mutex.new
|
107
|
-
@shutdown_lock
|
109
|
+
@shutdown_lock ||= Mutex.new
|
108
110
|
end
|
109
111
|
|
110
112
|
def self.common_init
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Apns2
|
4
|
+
# https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
|
5
|
+
|
6
|
+
HTTP2_HEADERS_KEY = 'headers'
|
7
|
+
|
8
|
+
class Delivery < Rpush::Daemon::Delivery
|
9
|
+
RETRYABLE_CODES = [ 429, 500, 503 ]
|
10
|
+
|
11
|
+
def initialize(app, http2_client, batch)
|
12
|
+
@app = app
|
13
|
+
@client = http2_client
|
14
|
+
@batch = batch
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
@client.on(:error) { |err| mark_batch_retryable(Time.now + 10.seconds, err) }
|
19
|
+
|
20
|
+
@batch.each_notification do |notification|
|
21
|
+
prepare_async_post(notification)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Send all preprocessed requests at once
|
25
|
+
@client.join
|
26
|
+
rescue Errno::ECONNREFUSED, SocketError => error
|
27
|
+
mark_batch_retryable(Time.now + 10.seconds, error)
|
28
|
+
raise
|
29
|
+
rescue StandardError => error
|
30
|
+
mark_batch_failed(error)
|
31
|
+
raise
|
32
|
+
ensure
|
33
|
+
@batch.all_processed
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
######################################################################
|
38
|
+
|
39
|
+
def prepare_async_post(notification)
|
40
|
+
response = {}
|
41
|
+
|
42
|
+
request = build_request(notification)
|
43
|
+
http_request = @client.prepare_request(:post, request[:path],
|
44
|
+
body: request[:body],
|
45
|
+
headers: request[:headers]
|
46
|
+
)
|
47
|
+
|
48
|
+
http_request.on(:headers) do |hdrs|
|
49
|
+
response[:code] = hdrs[':status'].to_i
|
50
|
+
end
|
51
|
+
|
52
|
+
http_request.on(:body_chunk) do |body_chunk|
|
53
|
+
next unless body_chunk.present?
|
54
|
+
|
55
|
+
response[:failure_reason] = JSON.parse(body_chunk)['reason']
|
56
|
+
end
|
57
|
+
|
58
|
+
http_request.on(:close) { handle_response(notification, response) }
|
59
|
+
|
60
|
+
@client.call_async(http_request)
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle_response(notification, response)
|
64
|
+
code = response[:code]
|
65
|
+
case code
|
66
|
+
when 200
|
67
|
+
ok(notification)
|
68
|
+
when *RETRYABLE_CODES
|
69
|
+
service_unavailable(notification, response)
|
70
|
+
else
|
71
|
+
reflect(:notification_id_failed,
|
72
|
+
@app,
|
73
|
+
notification.id, code,
|
74
|
+
response[:failure_reason])
|
75
|
+
@batch.mark_failed(notification, response[:code], response[:failure_reason])
|
76
|
+
failed_message_to_log(notification, response)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def ok(notification)
|
81
|
+
log_info("#{notification.id} sent to #{notification.device_token}")
|
82
|
+
@batch.mark_delivered(notification)
|
83
|
+
end
|
84
|
+
|
85
|
+
def service_unavailable(notification, response)
|
86
|
+
@batch.mark_retryable(notification, Time.now + 10.seconds)
|
87
|
+
# Logs should go last as soon as we need to initialize
|
88
|
+
# retry time to display it in log
|
89
|
+
failed_message_to_log(notification, response)
|
90
|
+
retry_message_to_log(notification)
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_request(notification)
|
94
|
+
{
|
95
|
+
path: "/3/device/#{notification.device_token}",
|
96
|
+
headers: prepare_headers(notification),
|
97
|
+
body: prepare_body(notification)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def prepare_body(notification)
|
102
|
+
hash = notification.as_json.except(HTTP2_HEADERS_KEY)
|
103
|
+
JSON.dump(hash).force_encoding(Encoding::BINARY)
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepare_headers(notification)
|
107
|
+
notification_data(notification)[HTTP2_HEADERS_KEY] || {}
|
108
|
+
end
|
109
|
+
|
110
|
+
def notification_data(notification)
|
111
|
+
notification.data || {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def retry_message_to_log(notification)
|
115
|
+
log_warn("Notification #{notification.id} will be retried after "\
|
116
|
+
"#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\
|
117
|
+
"(retry #{notification.retries}).")
|
118
|
+
end
|
119
|
+
|
120
|
+
def failed_message_to_log(notification, response)
|
121
|
+
log_error("Notification #{notification.id} failed, "\
|
122
|
+
"#{response[:code]}/#{response[:failure_reason]}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rpush
|
2
|
+
module Daemon
|
3
|
+
module Dispatcher
|
4
|
+
class ApnsHttp2
|
5
|
+
|
6
|
+
URLS = {
|
7
|
+
production: 'https://api.push.apple.com:443',
|
8
|
+
development: 'https://api.development.push.apple.com:443'
|
9
|
+
}
|
10
|
+
|
11
|
+
DEFAULT_TIMEOUT = 60
|
12
|
+
|
13
|
+
def initialize(app, delivery_class, _options = {})
|
14
|
+
@app = app
|
15
|
+
@delivery_class = delivery_class
|
16
|
+
|
17
|
+
url = URLS[app.environment.to_sym]
|
18
|
+
@client = NetHttp2::Client.new(url,
|
19
|
+
ssl_context: prepare_ssl_context,
|
20
|
+
connect_timeout: DEFAULT_TIMEOUT)
|
21
|
+
end
|
22
|
+
|
23
|
+
def dispatch(payload)
|
24
|
+
@delivery_class.new(@app, @client, payload.batch).perform
|
25
|
+
end
|
26
|
+
|
27
|
+
def cleanup
|
28
|
+
@client.close
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def prepare_ssl_context
|
34
|
+
@ssl_context ||= begin
|
35
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
36
|
+
begin
|
37
|
+
p12 = OpenSSL::PKCS12.new(@app.certificate, @app.password)
|
38
|
+
ctx.key = p12.key
|
39
|
+
ctx.cert = p12.certificate
|
40
|
+
rescue OpenSSL::PKCS12::PKCS12Error
|
41
|
+
ctx.key = OpenSSL::PKey::RSA.new(@app.certificate, @app.password)
|
42
|
+
ctx.cert = OpenSSL::X509::Certificate.new(@app.certificate)
|
43
|
+
end
|
44
|
+
ctx
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -36,7 +36,7 @@ module Rpush
|
|
36
36
|
|
37
37
|
def cleanup
|
38
38
|
if Rpush.config.push
|
39
|
-
# In push mode only a single batch is sent, followed
|
39
|
+
# In push mode only a single batch is sent, followed by immediate shutdown.
|
40
40
|
# Allow the error receiver time to handle any errors.
|
41
41
|
@reconnect_disabled = true
|
42
42
|
sleep 1
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module Rpush
|
2
2
|
module Daemon
|
3
3
|
module Gcm
|
4
|
-
#
|
4
|
+
# https://firebase.google.com/docs/cloud-messaging/server
|
5
5
|
class Delivery < Rpush::Daemon::Delivery
|
6
6
|
include MultiJsonHelper
|
7
7
|
|
8
|
-
host = 'https://
|
9
|
-
|
8
|
+
host = 'https://fcm.googleapis.com'
|
9
|
+
FCM_URI = URI.parse("#{host}/fcm/send")
|
10
10
|
UNAVAILABLE_STATES = %w(Unavailable InternalServerError)
|
11
11
|
INVALID_REGISTRATION_ID_STATES = %w(InvalidRegistration MismatchSenderId NotRegistered InvalidPackageName)
|
12
12
|
|
@@ -142,10 +142,10 @@ module Rpush
|
|
142
142
|
end
|
143
143
|
|
144
144
|
def do_post
|
145
|
-
post = Net::HTTP::Post.new(
|
145
|
+
post = Net::HTTP::Post.new(FCM_URI.path, 'Content-Type' => 'application/json',
|
146
146
|
'Authorization' => "key=#{@notification.app.auth_key}")
|
147
147
|
post.body = @notification.as_json.to_json
|
148
|
-
@http.request(
|
148
|
+
@http.request(FCM_URI, post)
|
149
149
|
end
|
150
150
|
end
|
151
151
|
|