rpush 8.0.0 → 9.1.0
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 +20 -6
- data/README.md +8 -65
- data/lib/generators/templates/rpush.rb +9 -16
- data/lib/rpush/client/active_model/fcm/notification.rb +1 -0
- data/lib/rpush/client/active_model.rb +0 -4
- data/lib/rpush/client/active_record/notification.rb +2 -0
- data/lib/rpush/client/active_record.rb +0 -3
- data/lib/rpush/client/redis.rb +0 -3
- data/lib/rpush/configuration.rb +2 -19
- data/lib/rpush/daemon/service_config_methods.rb +0 -2
- data/lib/rpush/daemon/store/active_record.rb +2 -14
- data/lib/rpush/daemon/store/interface.rb +2 -2
- data/lib/rpush/daemon/store/redis.rb +2 -11
- data/lib/rpush/daemon.rb +0 -10
- data/lib/rpush/reflection_collection.rb +1 -2
- data/lib/rpush/version.rb +2 -2
- data/lib/rpush.rb +0 -1
- data/spec/functional/cli_spec.rb +41 -15
- data/spec/functional/embed_spec.rb +57 -26
- data/spec/functional/retry_spec.rb +21 -4
- data/spec/functional/synchronization_spec.rb +1 -1
- data/spec/functional_spec_helper.rb +0 -6
- data/spec/spec_helper.rb +17 -7
- data/spec/unit/client/active_record/shared/app.rb +1 -1
- data/spec/unit/client/shared/fcm/notification.rb +6 -1
- data/spec/unit/daemon/shared/store.rb +0 -42
- metadata +61 -61
- data/lib/rpush/apns_feedback.rb +0 -18
- data/lib/rpush/client/active_model/gcm/app.rb +0 -19
- data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +0 -14
- data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
- data/lib/rpush/client/active_record/gcm/app.rb +0 -11
- data/lib/rpush/client/active_record/gcm/notification.rb +0 -11
- data/lib/rpush/client/redis/gcm/app.rb +0 -11
- data/lib/rpush/client/redis/gcm/notification.rb +0 -11
- data/lib/rpush/daemon/apns/delivery.rb +0 -43
- data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
- data/lib/rpush/daemon/apns.rb +0 -17
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
- data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
- data/lib/rpush/daemon/gcm/delivery.rb +0 -241
- data/lib/rpush/daemon/gcm.rb +0 -9
- data/lib/rpush/daemon/tcp_connection.rb +0 -190
- data/spec/functional/apns_spec.rb +0 -162
- data/spec/functional/gcm_priority_spec.rb +0 -40
- data/spec/functional/gcm_spec.rb +0 -46
- data/spec/functional/new_app_spec.rb +0 -44
- data/spec/unit/apns_feedback_spec.rb +0 -39
- data/spec/unit/client/active_record/gcm/app_spec.rb +0 -6
- data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
- data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
- data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
- data/spec/unit/client/shared/gcm/app.rb +0 -4
- data/spec/unit/client/shared/gcm/notification.rb +0 -77
- data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
- data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
- data/spec/unit/daemon/tcp_connection_spec.rb +0 -293
data/lib/rpush/apns_feedback.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
def self.apns_feedback
|
3
|
-
require 'rpush/daemon'
|
4
|
-
Rpush::Daemon.common_init
|
5
|
-
|
6
|
-
Rpush::Apns::App.all.each do |app|
|
7
|
-
# Redis stores every App type on the same namespace, hence the
|
8
|
-
# additional filtering
|
9
|
-
next unless app.service_name == 'apns'
|
10
|
-
next unless app.feedback_enabled
|
11
|
-
|
12
|
-
receiver = Rpush::Daemon::Apns::FeedbackReceiver.new(app)
|
13
|
-
receiver.check_for_feedback
|
14
|
-
end
|
15
|
-
|
16
|
-
nil
|
17
|
-
end
|
18
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Client
|
3
|
-
module ActiveModel
|
4
|
-
module Gcm
|
5
|
-
module App
|
6
|
-
def self.included(base)
|
7
|
-
base.instance_eval do
|
8
|
-
validates :auth_key, presence: true
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def service_name
|
13
|
-
'gcm'
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Client
|
3
|
-
module ActiveModel
|
4
|
-
module Gcm
|
5
|
-
class ExpiryCollapseKeyMutualInclusionValidator < ::ActiveModel::Validator
|
6
|
-
def validate(record)
|
7
|
-
return unless record.collapse_key && !record.expiry
|
8
|
-
record.errors.add :expiry, 'must be set when using a collapse_key'
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Client
|
3
|
-
module ActiveModel
|
4
|
-
module Gcm
|
5
|
-
module Notification
|
6
|
-
GCM_PRIORITY_HIGH = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_IMMEDIATE
|
7
|
-
GCM_PRIORITY_NORMAL = Rpush::Client::ActiveModel::Apns::Notification::APNS_PRIORITY_CONSERVE_POWER
|
8
|
-
GCM_PRIORITIES = [GCM_PRIORITY_HIGH, GCM_PRIORITY_NORMAL]
|
9
|
-
|
10
|
-
def self.included(base)
|
11
|
-
base.instance_eval do
|
12
|
-
validates :registration_ids, presence: true
|
13
|
-
validates :priority, inclusion: { in: GCM_PRIORITIES }, allow_nil: true
|
14
|
-
validates :dry_run, inclusion: { in: [true, false] }
|
15
|
-
|
16
|
-
validates_with Rpush::Client::ActiveModel::PayloadDataSizeValidator, limit: 4096
|
17
|
-
validates_with Rpush::Client::ActiveModel::RegistrationIdsCountValidator, limit: 1000
|
18
|
-
|
19
|
-
validates_with Rpush::Client::ActiveModel::Gcm::ExpiryCollapseKeyMutualInclusionValidator
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# This is a hack. The schema defines `priority` to be an integer, but GCM expects a string.
|
24
|
-
# But for users of rpush to have an API they might expect (setting priority to `high`, not 10)
|
25
|
-
# we do a little conversion here.
|
26
|
-
# I'm not happy about it, but this will have to do until I can take a further look.
|
27
|
-
def priority=(priority)
|
28
|
-
case priority
|
29
|
-
when 'high', GCM_PRIORITY_HIGH
|
30
|
-
super(GCM_PRIORITY_HIGH)
|
31
|
-
when 'normal', GCM_PRIORITY_NORMAL
|
32
|
-
super(GCM_PRIORITY_NORMAL)
|
33
|
-
else
|
34
|
-
errors.add(:priority, 'must be one of either "normal" or "high"')
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def as_json(options = nil) # rubocop:disable Metrics/PerceivedComplexity
|
39
|
-
json = {
|
40
|
-
'registration_ids' => registration_ids,
|
41
|
-
'delay_while_idle' => delay_while_idle,
|
42
|
-
'data' => data
|
43
|
-
}
|
44
|
-
json['collapse_key'] = collapse_key if collapse_key
|
45
|
-
json['content_available'] = content_available if content_available
|
46
|
-
json['mutable_content'] = mutable_content if mutable_content
|
47
|
-
json['dry_run'] = dry_run if dry_run
|
48
|
-
json['notification'] = notification if notification
|
49
|
-
json['priority'] = priority_for_notification if priority
|
50
|
-
json['time_to_live'] = expiry if expiry
|
51
|
-
json
|
52
|
-
end
|
53
|
-
|
54
|
-
def priority_for_notification
|
55
|
-
return 'high' if priority == GCM_PRIORITY_HIGH
|
56
|
-
'normal' if priority == GCM_PRIORITY_NORMAL
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
module Apns
|
4
|
-
class Delivery < Rpush::Daemon::Delivery
|
5
|
-
def initialize(app, connection, batch)
|
6
|
-
@app = app
|
7
|
-
@connection = connection
|
8
|
-
@batch = batch
|
9
|
-
end
|
10
|
-
|
11
|
-
def perform
|
12
|
-
@connection.write(batch_to_binary)
|
13
|
-
mark_batch_delivered
|
14
|
-
describe_deliveries
|
15
|
-
rescue Rpush::Daemon::TcpConnectionError => error
|
16
|
-
mark_batch_retryable(Time.now + 10.seconds, error)
|
17
|
-
raise
|
18
|
-
rescue StandardError => error
|
19
|
-
mark_batch_failed(error)
|
20
|
-
raise
|
21
|
-
ensure
|
22
|
-
@batch.all_processed
|
23
|
-
end
|
24
|
-
|
25
|
-
protected
|
26
|
-
|
27
|
-
def batch_to_binary
|
28
|
-
payload = ""
|
29
|
-
@batch.each_notification do |notification|
|
30
|
-
payload << notification.to_binary
|
31
|
-
end
|
32
|
-
payload
|
33
|
-
end
|
34
|
-
|
35
|
-
def describe_deliveries
|
36
|
-
@batch.each_notification do |notification|
|
37
|
-
log_info("#{notification.id} sent to #{notification.device_token}")
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,91 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
module Rpush
|
4
|
-
module Daemon
|
5
|
-
module Apns
|
6
|
-
class FeedbackReceiver
|
7
|
-
include Reflectable
|
8
|
-
include Loggable
|
9
|
-
|
10
|
-
TUPLE_BYTES = 38
|
11
|
-
HOSTS = {
|
12
|
-
production: ['feedback.push.apple.com', 2196],
|
13
|
-
development: ['feedback.sandbox.push.apple.com', 2196], # deprecated
|
14
|
-
sandbox: ['feedback.sandbox.push.apple.com', 2196]
|
15
|
-
}
|
16
|
-
|
17
|
-
def initialize(app)
|
18
|
-
@app = app
|
19
|
-
@host, @port = HOSTS[@app.environment.to_sym]
|
20
|
-
@certificate = app.certificate
|
21
|
-
@password = app.password
|
22
|
-
@interruptible_sleep = InterruptibleSleep.new
|
23
|
-
end
|
24
|
-
|
25
|
-
def start
|
26
|
-
return if Rpush.config.push
|
27
|
-
return unless @app.feedback_enabled
|
28
|
-
Rpush.logger.info("[#{@app.name}] Starting feedback receiver... ", true)
|
29
|
-
|
30
|
-
@thread = Thread.new do
|
31
|
-
loop do
|
32
|
-
break if @stop
|
33
|
-
check_for_feedback
|
34
|
-
@interruptible_sleep.sleep(Rpush.config.apns.feedback_receiver.frequency)
|
35
|
-
end
|
36
|
-
|
37
|
-
Rpush::Daemon.store.release_connection
|
38
|
-
end
|
39
|
-
|
40
|
-
puts Rainbow('✔').green if Rpush.config.foreground && Rpush.config.foreground_logging
|
41
|
-
end
|
42
|
-
|
43
|
-
def stop
|
44
|
-
@stop = true
|
45
|
-
@interruptible_sleep.stop
|
46
|
-
@thread.join if @thread
|
47
|
-
rescue StandardError => e
|
48
|
-
log_error(e)
|
49
|
-
reflect(:error, e)
|
50
|
-
ensure
|
51
|
-
@thread = nil
|
52
|
-
end
|
53
|
-
|
54
|
-
def check_for_feedback
|
55
|
-
connection = nil
|
56
|
-
begin
|
57
|
-
connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
|
58
|
-
connection.connect
|
59
|
-
tuple = connection.read(TUPLE_BYTES)
|
60
|
-
|
61
|
-
while tuple
|
62
|
-
timestamp, device_token = parse_tuple(tuple)
|
63
|
-
create_feedback(timestamp, device_token)
|
64
|
-
tuple = connection.read(TUPLE_BYTES)
|
65
|
-
end
|
66
|
-
rescue StandardError => e
|
67
|
-
log_error(e)
|
68
|
-
reflect(:error, e)
|
69
|
-
ensure
|
70
|
-
connection.close if connection
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
protected
|
75
|
-
|
76
|
-
def parse_tuple(tuple)
|
77
|
-
failed_at, _, device_token = tuple.unpack("N1n1H*")
|
78
|
-
[Time.at(failed_at).utc, device_token]
|
79
|
-
end
|
80
|
-
|
81
|
-
def create_feedback(failed_at, device_token)
|
82
|
-
formatted_failed_at = failed_at.strftime('%Y-%m-%d %H:%M:%S UTC')
|
83
|
-
log_info("[FeedbackReceiver] Delivery failed at #{formatted_failed_at} for #{device_token}.")
|
84
|
-
|
85
|
-
feedback = Rpush::Daemon.store.create_apns_feedback(failed_at, device_token, @app)
|
86
|
-
reflect(:apns_feedback, feedback)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
data/lib/rpush/daemon/apns.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
module Apns
|
4
|
-
extend ServiceConfigMethods
|
5
|
-
|
6
|
-
HOSTS = {
|
7
|
-
production: ['gateway.push.apple.com', 2195],
|
8
|
-
development: ['gateway.sandbox.push.apple.com', 2195], # deprecated
|
9
|
-
sandbox: ['gateway.sandbox.push.apple.com', 2195]
|
10
|
-
}
|
11
|
-
|
12
|
-
batch_deliveries true
|
13
|
-
dispatcher :apns_tcp, host: proc { |app| HOSTS[app.environment.to_sym] }
|
14
|
-
loops Rpush::Daemon::Apns::FeedbackReceiver, if: -> { Rpush.config.apns.feedback_receiver.enabled && !Rpush.config.push }
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,152 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
module Dispatcher
|
4
|
-
class ApnsTcp < Rpush::Daemon::Dispatcher::Tcp
|
5
|
-
include Loggable
|
6
|
-
include Reflectable
|
7
|
-
|
8
|
-
SELECT_TIMEOUT = 10
|
9
|
-
ERROR_TUPLE_BYTES = 6
|
10
|
-
APNS_ERRORS = {
|
11
|
-
1 => 'Processing error',
|
12
|
-
2 => 'Missing device token',
|
13
|
-
3 => 'Missing topic',
|
14
|
-
4 => 'Missing payload',
|
15
|
-
5 => 'Missing token size',
|
16
|
-
6 => 'Missing topic size',
|
17
|
-
7 => 'Missing payload size',
|
18
|
-
8 => 'Invalid device token',
|
19
|
-
10 => 'APNs closed connection (possible maintenance)',
|
20
|
-
255 => 'None (unknown error)'
|
21
|
-
}
|
22
|
-
|
23
|
-
def initialize(*args)
|
24
|
-
super
|
25
|
-
@dispatch_mutex = Mutex.new
|
26
|
-
@stop_error_receiver = false
|
27
|
-
@connection.on_connect { start_error_receiver }
|
28
|
-
end
|
29
|
-
|
30
|
-
def dispatch(payload)
|
31
|
-
@dispatch_mutex.synchronize do
|
32
|
-
@delivery_class.new(@app, @connection, payload.batch).perform
|
33
|
-
record_batch(payload.batch)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def cleanup
|
38
|
-
if Rpush.config.push
|
39
|
-
# In push mode only a single batch is sent, followed by immediate shutdown.
|
40
|
-
# Allow the error receiver time to handle any errors.
|
41
|
-
@reconnect_disabled = true
|
42
|
-
sleep 1
|
43
|
-
end
|
44
|
-
|
45
|
-
@stop_error_receiver = true
|
46
|
-
super
|
47
|
-
@error_receiver_thread.join if @error_receiver_thread
|
48
|
-
rescue StandardError => e
|
49
|
-
log_error(e)
|
50
|
-
reflect(:error, e)
|
51
|
-
ensure
|
52
|
-
@error_receiver_thread = nil
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def start_error_receiver
|
58
|
-
@error_receiver_thread = Thread.new do
|
59
|
-
check_for_error until @stop_error_receiver
|
60
|
-
Rpush::Daemon.store.release_connection
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def delivered_buffer
|
65
|
-
@delivered_buffer ||= RingBuffer.new(Rpush.config.batch_size * 10)
|
66
|
-
end
|
67
|
-
|
68
|
-
def record_batch(batch)
|
69
|
-
batch.each_delivered do |notification|
|
70
|
-
delivered_buffer << notification.id
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def check_for_error
|
75
|
-
begin
|
76
|
-
# On Linux, select returns nil from a dropped connection.
|
77
|
-
# On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call.
|
78
|
-
return unless @connection.select(SELECT_TIMEOUT)
|
79
|
-
tuple = @connection.read(ERROR_TUPLE_BYTES)
|
80
|
-
rescue *TcpConnection::TCP_ERRORS
|
81
|
-
reconnect unless @stop_error_receiver
|
82
|
-
return
|
83
|
-
end
|
84
|
-
|
85
|
-
@dispatch_mutex.synchronize { handle_error_response(tuple) }
|
86
|
-
rescue StandardError => e
|
87
|
-
log_error(e)
|
88
|
-
end
|
89
|
-
|
90
|
-
def handle_error_response(tuple)
|
91
|
-
if tuple
|
92
|
-
_, code, notification_id = tuple.unpack('ccN')
|
93
|
-
handle_error(code, notification_id)
|
94
|
-
else
|
95
|
-
handle_disconnect
|
96
|
-
end
|
97
|
-
|
98
|
-
if Rpush.config.push
|
99
|
-
# Only attempt to handle a single error in Push mode.
|
100
|
-
@stop_error_receiver = true
|
101
|
-
return
|
102
|
-
end
|
103
|
-
|
104
|
-
reconnect
|
105
|
-
ensure
|
106
|
-
delivered_buffer.clear
|
107
|
-
end
|
108
|
-
|
109
|
-
def reconnect
|
110
|
-
return if @reconnect_disabled
|
111
|
-
log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
|
112
|
-
@connection.reconnect_with_rescue
|
113
|
-
end
|
114
|
-
|
115
|
-
def handle_disconnect
|
116
|
-
log_error("The APNs disconnected before any notifications could be delivered. This usually indicates you are using an invalid certificate.") if delivered_buffer.size == 0
|
117
|
-
end
|
118
|
-
|
119
|
-
def handle_error(code, notification_id)
|
120
|
-
notification_id = Rpush::Daemon.store.translate_integer_notification_id(notification_id)
|
121
|
-
failed_pos = delivered_buffer.index(notification_id)
|
122
|
-
description = description_for_code(code)
|
123
|
-
log_error("Notification #{notification_id} failed with error: " + description)
|
124
|
-
Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now)
|
125
|
-
reflect(:notification_id_failed, @app, notification_id, code, description)
|
126
|
-
|
127
|
-
if failed_pos
|
128
|
-
retry_ids = delivered_buffer[(failed_pos + 1)..-1]
|
129
|
-
retry_notification_ids(retry_ids, notification_id)
|
130
|
-
elsif delivered_buffer.size > 0
|
131
|
-
log_error("Delivery sequence unknown for notifications following #{notification_id}.")
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def description_for_code(code)
|
136
|
-
APNS_ERRORS[code.to_i] ? "#{APNS_ERRORS[code.to_i]} (#{code})" : "Unknown error code #{code.inspect}. Possible Rpush bug?"
|
137
|
-
end
|
138
|
-
|
139
|
-
def retry_notification_ids(ids, notification_id)
|
140
|
-
return if ids.size == 0
|
141
|
-
|
142
|
-
now = Time.now
|
143
|
-
Rpush::Daemon.store.mark_ids_retryable(ids, now)
|
144
|
-
notifications_str = 'Notification'
|
145
|
-
notifications_str += 's' if ids.size > 1
|
146
|
-
log_warn("#{notifications_str} #{ids.join(', ')} will be retried due to the failure of notification #{notification_id}.")
|
147
|
-
ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) }
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
module Dispatcher
|
4
|
-
class Tcp
|
5
|
-
def initialize(app, delivery_class, options = {})
|
6
|
-
@app = app
|
7
|
-
@delivery_class = delivery_class
|
8
|
-
@host, @port = options[:host].call(@app)
|
9
|
-
@connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
|
10
|
-
end
|
11
|
-
|
12
|
-
def dispatch(payload)
|
13
|
-
@delivery_class.new(@app, @connection, payload.notification, payload.batch).perform
|
14
|
-
end
|
15
|
-
|
16
|
-
def cleanup
|
17
|
-
@connection.close if @connection
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|