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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -6
  3. data/README.md +8 -65
  4. data/lib/generators/templates/rpush.rb +9 -16
  5. data/lib/rpush/client/active_model/fcm/notification.rb +1 -0
  6. data/lib/rpush/client/active_model.rb +0 -4
  7. data/lib/rpush/client/active_record/notification.rb +2 -0
  8. data/lib/rpush/client/active_record.rb +0 -3
  9. data/lib/rpush/client/redis.rb +0 -3
  10. data/lib/rpush/configuration.rb +2 -19
  11. data/lib/rpush/daemon/service_config_methods.rb +0 -2
  12. data/lib/rpush/daemon/store/active_record.rb +2 -14
  13. data/lib/rpush/daemon/store/interface.rb +2 -2
  14. data/lib/rpush/daemon/store/redis.rb +2 -11
  15. data/lib/rpush/daemon.rb +0 -10
  16. data/lib/rpush/reflection_collection.rb +1 -2
  17. data/lib/rpush/version.rb +2 -2
  18. data/lib/rpush.rb +0 -1
  19. data/spec/functional/cli_spec.rb +41 -15
  20. data/spec/functional/embed_spec.rb +57 -26
  21. data/spec/functional/retry_spec.rb +21 -4
  22. data/spec/functional/synchronization_spec.rb +1 -1
  23. data/spec/functional_spec_helper.rb +0 -6
  24. data/spec/spec_helper.rb +17 -7
  25. data/spec/unit/client/active_record/shared/app.rb +1 -1
  26. data/spec/unit/client/shared/fcm/notification.rb +6 -1
  27. data/spec/unit/daemon/shared/store.rb +0 -42
  28. metadata +61 -61
  29. data/lib/rpush/apns_feedback.rb +0 -18
  30. data/lib/rpush/client/active_model/gcm/app.rb +0 -19
  31. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +0 -14
  32. data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
  33. data/lib/rpush/client/active_record/gcm/app.rb +0 -11
  34. data/lib/rpush/client/active_record/gcm/notification.rb +0 -11
  35. data/lib/rpush/client/redis/gcm/app.rb +0 -11
  36. data/lib/rpush/client/redis/gcm/notification.rb +0 -11
  37. data/lib/rpush/daemon/apns/delivery.rb +0 -43
  38. data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
  39. data/lib/rpush/daemon/apns.rb +0 -17
  40. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
  41. data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
  42. data/lib/rpush/daemon/gcm/delivery.rb +0 -241
  43. data/lib/rpush/daemon/gcm.rb +0 -9
  44. data/lib/rpush/daemon/tcp_connection.rb +0 -190
  45. data/spec/functional/apns_spec.rb +0 -162
  46. data/spec/functional/gcm_priority_spec.rb +0 -40
  47. data/spec/functional/gcm_spec.rb +0 -46
  48. data/spec/functional/new_app_spec.rb +0 -44
  49. data/spec/unit/apns_feedback_spec.rb +0 -39
  50. data/spec/unit/client/active_record/gcm/app_spec.rb +0 -6
  51. data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
  52. data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
  53. data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
  54. data/spec/unit/client/shared/gcm/app.rb +0 -4
  55. data/spec/unit/client/shared/gcm/notification.rb +0 -77
  56. data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
  57. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
  58. data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
  59. data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
  60. data/spec/unit/daemon/tcp_connection_spec.rb +0 -293
@@ -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,11 +0,0 @@
1
- module Rpush
2
- module Client
3
- module ActiveRecord
4
- module Gcm
5
- class App < Rpush::Client::ActiveRecord::App
6
- include Rpush::Client::ActiveModel::Gcm::App
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- module Rpush
2
- module Client
3
- module ActiveRecord
4
- module Gcm
5
- class Notification < Rpush::Client::ActiveRecord::Notification
6
- include Rpush::Client::ActiveModel::Gcm::Notification
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- module Rpush
2
- module Client
3
- module Redis
4
- module Gcm
5
- class App < Rpush::Client::Redis::App
6
- include Rpush::Client::ActiveModel::Gcm::App
7
- end
8
- end
9
- end
10
- end
11
- end
@@ -1,11 +0,0 @@
1
- module Rpush
2
- module Client
3
- module Redis
4
- module Gcm
5
- class Notification < Rpush::Client::Redis::Notification
6
- include Rpush::Client::ActiveModel::Gcm::Notification
7
- end
8
- end
9
- end
10
- end
11
- 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
@@ -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