rpush 2.7.0 → 3.0.0.rc1

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