rpush 8.0.0 → 9.1.0

Sign up to get free protection for your applications and to get access to all the features.
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