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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +36 -15
  4. data/lib/generators/rpush_migration_generator.rb +1 -0
  5. data/lib/generators/templates/add_adm.rb +1 -1
  6. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +1 -1
  7. data/lib/generators/templates/add_app_to_rapns.rb +1 -1
  8. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
  9. data/lib/generators/templates/add_gcm.rb +1 -1
  10. data/lib/generators/templates/add_rpush.rb +11 -11
  11. data/lib/generators/templates/add_wpns.rb +1 -1
  12. data/lib/generators/templates/create_rapns_apps.rb +1 -1
  13. data/lib/generators/templates/create_rapns_feedback.rb +1 -1
  14. data/lib/generators/templates/create_rapns_notifications.rb +1 -1
  15. data/lib/generators/templates/rename_rapns_to_rpush.rb +1 -1
  16. data/lib/generators/templates/rpush.rb +1 -1
  17. data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
  18. data/lib/generators/templates/rpush_2_1_0_updates.rb +1 -1
  19. data/lib/generators/templates/rpush_2_6_0_updates.rb +1 -1
  20. data/lib/generators/templates/rpush_2_7_0_updates.rb +1 -1
  21. data/lib/generators/templates/rpush_3_0_0_updates.rb +11 -0
  22. data/lib/rpush.rb +2 -9
  23. data/lib/rpush/apns_feedback.rb +4 -0
  24. data/lib/rpush/cli.rb +2 -2
  25. data/lib/rpush/client/active_model.rb +3 -0
  26. data/lib/rpush/client/active_model/apns/notification.rb +11 -1
  27. data/lib/rpush/client/active_model/apns2/app.rb +15 -0
  28. data/lib/rpush/client/active_model/apns2/notification.rb +9 -0
  29. data/lib/rpush/client/active_record.rb +3 -0
  30. data/lib/rpush/client/active_record/apns/feedback.rb +0 -4
  31. data/lib/rpush/client/active_record/apns2/app.rb +11 -0
  32. data/lib/rpush/client/active_record/apns2/notification.rb +10 -0
  33. data/lib/rpush/client/active_record/app.rb +0 -4
  34. data/lib/rpush/client/active_record/notification.rb +0 -7
  35. data/lib/rpush/client/redis.rb +3 -0
  36. data/lib/rpush/client/redis/apns2/app.rb +11 -0
  37. data/lib/rpush/client/redis/apns2/notification.rb +11 -0
  38. data/lib/rpush/client/redis/notification.rb +1 -0
  39. data/lib/rpush/daemon.rb +5 -3
  40. data/lib/rpush/daemon/apns2.rb +10 -0
  41. data/lib/rpush/daemon/apns2/delivery.rb +127 -0
  42. data/lib/rpush/daemon/dispatcher/apns_http2.rb +50 -0
  43. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +1 -1
  44. data/lib/rpush/daemon/dispatcher/http.rb +1 -1
  45. data/lib/rpush/daemon/gcm/delivery.rb +5 -5
  46. data/lib/rpush/daemon/service_config_methods.rb +4 -3
  47. data/lib/rpush/daemon/store/active_record/reconnectable.rb +11 -3
  48. data/lib/rpush/daemon/synchronizer.rb +14 -12
  49. data/lib/rpush/version.rb +12 -1
  50. data/spec/functional/apns2_spec.rb +232 -0
  51. data/spec/functional/apns_spec.rb +1 -2
  52. data/spec/functional/synchronization_spec.rb +29 -0
  53. data/spec/spec_helper.rb +0 -5
  54. data/spec/support/active_record_setup.rb +2 -1
  55. data/spec/unit/apns_feedback_spec.rb +9 -2
  56. data/spec/unit/client/active_record/apns/notification_spec.rb +34 -2
  57. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +30 -0
  58. data/spec/unit_spec_helper.rb +2 -21
  59. metadata +256 -29
  60. data/lib/rpush/client/mongoid.rb +0 -36
  61. data/lib/rpush/client/mongoid/adm/app.rb +0 -14
  62. data/lib/rpush/client/mongoid/adm/notification.rb +0 -11
  63. data/lib/rpush/client/mongoid/apns/app.rb +0 -11
  64. data/lib/rpush/client/mongoid/apns/feedback.rb +0 -24
  65. data/lib/rpush/client/mongoid/apns/notification.rb +0 -15
  66. data/lib/rpush/client/mongoid/app.rb +0 -23
  67. data/lib/rpush/client/mongoid/gcm/app.rb +0 -11
  68. data/lib/rpush/client/mongoid/gcm/notification.rb +0 -11
  69. data/lib/rpush/client/mongoid/notification.rb +0 -51
  70. data/lib/rpush/client/mongoid/wns/app.rb +0 -14
  71. data/lib/rpush/client/mongoid/wns/badge_notification.rb +0 -15
  72. data/lib/rpush/client/mongoid/wns/notification.rb +0 -11
  73. data/lib/rpush/client/mongoid/wns/raw_notification.rb +0 -11
  74. data/lib/rpush/client/mongoid/wpns/app.rb +0 -11
  75. data/lib/rpush/client/mongoid/wpns/notification.rb +0 -11
  76. data/lib/rpush/daemon/store/mongoid.rb +0 -157
  77. data/spec/support/config/mongoid.yml +0 -69
  78. data/spec/support/mongoid_setup.rb +0 -10
  79. 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
@@ -0,0 +1,15 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Apns2
5
+ module App
6
+ extend Rpush::Client::ActiveModel::Apns::App
7
+
8
+ def service_name
9
+ 'apns2'
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveModel
4
+ module Apns2
5
+ include Rpush::Client::ActiveModel::Apns
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Apns2
5
+ class App < Rpush::Client::ActiveRecord::App
6
+ include Rpush::Client::ActiveModel::Apns2::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Rpush
2
+ module Client
3
+ module ActiveRecord
4
+ module Apns2
5
+ class Notification < Rpush::Client::ActiveRecord::Apns::Notification
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -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)
@@ -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
 
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Apns2
5
+ class App < Rpush::Client::Redis::App
6
+ include Rpush::Client::ActiveModel::Apns2::App
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Apns2
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Apns2::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -43,6 +43,7 @@ module Rpush
43
43
  attribute :url_args, :array
44
44
  attribute :category, :string
45
45
  attribute :content_available, :boolean, default: false
46
+ attribute :mutable_content, :boolean, default: false
46
47
  attribute :notification, :hash
47
48
 
48
49
  def app
@@ -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
- return @shutdown_lock if @shutdown_lock
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,10 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns2
4
+ extend ServiceConfigMethods
5
+
6
+ batch_deliveries true
7
+ dispatcher :apns_http2
8
+ end
9
+ end
10
+ end
@@ -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 my immediate shutdown.
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
@@ -5,7 +5,7 @@ module Rpush
5
5
  def initialize(app, delivery_class, _options = {})
6
6
  @app = app
7
7
  @delivery_class = delivery_class
8
- @http = Net::HTTP::Persistent.new('rpush')
8
+ @http = Net::HTTP::Persistent.new(name: 'rpush')
9
9
  end
10
10
 
11
11
  def dispatch(payload)
@@ -1,12 +1,12 @@
1
1
  module Rpush
2
2
  module Daemon
3
3
  module Gcm
4
- # http://developer.android.com/guide/google/gcm/gcm.html#response
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://gcm-http.googleapis.com'
9
- GCM_URI = URI.parse("#{host}/gcm/send")
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(GCM_URI.path, 'Content-Type' => 'application/json',
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(GCM_URI, post)
148
+ @http.request(FCM_URI, post)
149
149
  end
150
150
  end
151
151