rpush 8.0.0 → 9.1.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 +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
|