rapns 2.0.5 → 3.0.0.beta.1

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 (82) hide show
  1. data/lib/generators/rapns_generator.rb +1 -0
  2. data/lib/generators/templates/add_gcm.rb +86 -0
  3. data/lib/generators/templates/create_rapns_notifications.rb +1 -1
  4. data/lib/rapns/apns/app.rb +8 -0
  5. data/lib/rapns/apns/binary_notification_validator.rb +12 -0
  6. data/lib/rapns/apns/device_token_format_validator.rb +12 -0
  7. data/lib/rapns/apns/feedback.rb +14 -0
  8. data/lib/rapns/apns/notification.rb +84 -0
  9. data/lib/rapns/app.rb +5 -6
  10. data/lib/rapns/{config.rb → configuration.rb} +5 -5
  11. data/lib/rapns/daemon/apns/app_runner.rb +36 -0
  12. data/lib/rapns/daemon/apns/connection.rb +113 -0
  13. data/lib/rapns/daemon/apns/delivery.rb +63 -0
  14. data/lib/rapns/daemon/apns/delivery_handler.rb +21 -0
  15. data/lib/rapns/daemon/apns/disconnection_error.rb +20 -0
  16. data/lib/rapns/daemon/apns/feedback_receiver.rb +74 -0
  17. data/lib/rapns/daemon/app_runner.rb +76 -77
  18. data/lib/rapns/daemon/database_reconnectable.rb +3 -3
  19. data/lib/rapns/daemon/delivery.rb +43 -0
  20. data/lib/rapns/daemon/delivery_error.rb +6 -2
  21. data/lib/rapns/daemon/delivery_handler.rb +13 -79
  22. data/lib/rapns/daemon/delivery_queue_18.rb +2 -2
  23. data/lib/rapns/daemon/delivery_queue_19.rb +3 -3
  24. data/lib/rapns/daemon/feeder.rb +5 -5
  25. data/lib/rapns/daemon/gcm/app_runner.rb +13 -0
  26. data/lib/rapns/daemon/gcm/delivery.rb +206 -0
  27. data/lib/rapns/daemon/gcm/delivery_handler.rb +20 -0
  28. data/lib/rapns/daemon.rb +31 -20
  29. data/lib/rapns/gcm/app.rb +7 -0
  30. data/lib/rapns/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  31. data/lib/rapns/gcm/notification.rb +31 -0
  32. data/lib/rapns/gcm/payload_size_validator.rb +13 -0
  33. data/lib/rapns/multi_json_helper.rb +16 -0
  34. data/lib/rapns/notification.rb +28 -95
  35. data/lib/rapns/version.rb +1 -1
  36. data/lib/rapns.rb +14 -4
  37. data/lib/tasks/cane.rake +19 -0
  38. data/lib/tasks/test.rake +34 -0
  39. data/spec/acceptance/gcm_upgrade_spec.rb +34 -0
  40. data/spec/acceptance_spec_helper.rb +85 -0
  41. data/spec/support/simplecov_helper.rb +13 -0
  42. data/spec/support/simplecov_quality_formatter.rb +8 -0
  43. data/spec/unit/apns/app_spec.rb +15 -0
  44. data/spec/unit/apns/feedback_spec.rb +12 -0
  45. data/spec/{rapns → unit/apns}/notification_spec.rb +44 -72
  46. data/spec/unit/app_spec.rb +18 -0
  47. data/spec/unit/daemon/apns/app_runner_spec.rb +37 -0
  48. data/spec/{rapns/daemon → unit/daemon/apns}/connection_spec.rb +9 -9
  49. data/spec/unit/daemon/apns/delivery_handler_spec.rb +48 -0
  50. data/spec/unit/daemon/apns/delivery_spec.rb +154 -0
  51. data/spec/{rapns/daemon → unit/daemon/apns}/feedback_receiver_spec.rb +14 -14
  52. data/spec/unit/daemon/app_runner_shared.rb +66 -0
  53. data/spec/unit/daemon/app_runner_spec.rb +78 -0
  54. data/spec/{rapns → unit}/daemon/database_reconnectable_spec.rb +4 -5
  55. data/spec/{rapns → unit}/daemon/delivery_error_spec.rb +2 -2
  56. data/spec/unit/daemon/delivery_handler_shared.rb +19 -0
  57. data/spec/{rapns → unit}/daemon/delivery_queue_spec.rb +1 -1
  58. data/spec/{rapns → unit}/daemon/feeder_spec.rb +33 -33
  59. data/spec/unit/daemon/gcm/app_runner_spec.rb +15 -0
  60. data/spec/unit/daemon/gcm/delivery_handler_spec.rb +36 -0
  61. data/spec/unit/daemon/gcm/delivery_spec.rb +236 -0
  62. data/spec/{rapns → unit}/daemon/interruptible_sleep_spec.rb +1 -1
  63. data/spec/{rapns → unit}/daemon/logger_spec.rb +1 -1
  64. data/spec/{rapns → unit}/daemon_spec.rb +1 -1
  65. data/spec/unit/gcm/app_spec.rb +5 -0
  66. data/spec/unit/gcm/notification_spec.rb +55 -0
  67. data/spec/unit/notification_shared.rb +38 -0
  68. data/spec/unit/notification_spec.rb +6 -0
  69. data/spec/{rapns/app_spec.rb → unit_spec_helper.rb} +76 -16
  70. metadata +107 -45
  71. data/lib/rapns/binary_notification_validator.rb +0 -10
  72. data/lib/rapns/daemon/connection.rb +0 -114
  73. data/lib/rapns/daemon/delivery_handler_pool.rb +0 -18
  74. data/lib/rapns/daemon/disconnection_error.rb +0 -14
  75. data/lib/rapns/daemon/feedback_receiver.rb +0 -82
  76. data/lib/rapns/device_token_format_validator.rb +0 -10
  77. data/lib/rapns/feedback.rb +0 -12
  78. data/spec/rapns/daemon/app_runner_spec.rb +0 -193
  79. data/spec/rapns/daemon/delivery_handler_pool_spec.rb +0 -17
  80. data/spec/rapns/daemon/delivery_handler_spec.rb +0 -206
  81. data/spec/rapns/feedback_spec.rb +0 -12
  82. data/spec/spec_helper.rb +0 -78
@@ -0,0 +1,206 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Gcm
4
+ # http://developer.android.com/guide/google/gcm/gcm.html#response
5
+ class Delivery < Rapns::Daemon::Delivery
6
+ include Rapns::MultiJsonHelper
7
+
8
+ GCM_URI = URI.parse('https://android.googleapis.com/gcm/send')
9
+ UNAVAILABLE_STATES = ['Unavailable', 'InternalServerError']
10
+
11
+ def initialize(app, http, notification)
12
+ @app = app
13
+ @http = http
14
+ @notification = notification
15
+ end
16
+
17
+ def perform
18
+ begin
19
+ handle_response(do_post)
20
+ rescue Rapns::DeliveryError => error
21
+ mark_failed(error.code, error.description)
22
+ raise
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ def handle_response(response)
29
+ case response.code.to_i
30
+ when 200
31
+ ok(response)
32
+ when 400
33
+ bad_request(response)
34
+ when 401
35
+ unauthorized(response)
36
+ when 500
37
+ internal_server_error(response)
38
+ when 503
39
+ service_unavailable(response)
40
+ else
41
+ raise Rapns::DeliveryError.new(response.code, @notification.id, HTTP_STATUS_CODES[response.code.to_i])
42
+ end
43
+ end
44
+
45
+ def ok(response)
46
+ body = multi_json_load(response.body)
47
+
48
+ if body['failure'].to_i == 0
49
+ mark_delivered
50
+ Rapns::Daemon.logger.info("[#{@app.name}] #{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
51
+ else
52
+ handle_errors(response, body)
53
+ end
54
+ end
55
+
56
+ def handle_errors(response, body)
57
+ errors = {}
58
+
59
+ body['results'].each_with_index do |result, i|
60
+ errors[i] = result['error'] if result['error']
61
+ end
62
+
63
+ if body['success'].to_i == 0 && errors.values.all? { |error| error.in?(UNAVAILABLE_STATES) }
64
+ all_devices_unavailable(response)
65
+ elsif errors.values.any? { |error| error.in?(UNAVAILABLE_STATES) }
66
+ some_devices_unavailable(response, errors)
67
+ else
68
+ raise Rapns::DeliveryError.new(nil, @notification.id, describe_errors(errors))
69
+ end
70
+ end
71
+
72
+ def bad_request(response)
73
+ raise Rapns::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an rapns bug, please open an issue.')
74
+ end
75
+
76
+ def unauthorized(response)
77
+ raise Rapns::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
78
+ end
79
+
80
+ def internal_server_error(response)
81
+ retry_delivery(@notification, response)
82
+ Rapns::Daemon.logger.warn("GCM responded with an Internal Error. " + retry_message)
83
+ end
84
+
85
+ def service_unavailable(response)
86
+ retry_delivery(@notification, response)
87
+ Rapns::Daemon.logger.warn("GCM responded with an Service Unavailable Error. " + retry_message)
88
+ end
89
+
90
+ def all_devices_unavailable(response)
91
+ retry_delivery(@notification, response)
92
+ Rapns::Daemon.logger.warn("All recipients unavailable. " + retry_message)
93
+ end
94
+
95
+ def some_devices_unavailable(response, errors)
96
+ unavailable_idxs = errors.find_all { |i, error| error.in?(UNAVAILABLE_STATES) }.map(&:first)
97
+ new_notification = build_new_notification(response, unavailable_idxs)
98
+ with_database_reconnect_and_retry { new_notification.save! }
99
+ raise Rapns::DeliveryError.new(nil, @notification.id,
100
+ describe_errors(errors) + " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}.")
101
+ end
102
+
103
+ def build_new_notification(response, idxs)
104
+ notification = Rapns::Gcm::Notification.new
105
+ notification.assign_attributes(@notification.attributes.slice('app_id', 'collapse_key', 'delay_while_idle'))
106
+ notification.data = @notification.data
107
+ notification.registration_ids = idxs.map { |i| @notification.registration_ids[i] }
108
+ notification.deliver_after = deliver_after_header(response)
109
+ notification
110
+ end
111
+
112
+ def deliver_after_header(response)
113
+ if response.header['retry-after']
114
+ retry_after = if response.header['retry-after'].to_s =~ /^[0-9]+$/
115
+ Time.now + response.header['retry-after'].to_i
116
+ else
117
+ Time.httpdate(response.header['retry-after'])
118
+ end
119
+ end
120
+ end
121
+
122
+ def retry_delivery(notification, response)
123
+ if time = deliver_after_header(response)
124
+ retry_after(notification, time)
125
+ else
126
+ retry_exponentially(notification)
127
+ end
128
+ end
129
+
130
+ def describe_errors(errors)
131
+ description = if errors.size == @notification.registration_ids.size
132
+ "Failed to deliver to all recipients. Errors: #{errors.values.join(', ')}."
133
+ else
134
+ "Failed to deliver to recipients #{errors.keys.join(', ')}. Errors: #{errors.values.join(', ')}."
135
+ end
136
+ end
137
+
138
+ def retry_message
139
+ "Notification #{@notification.id} will be retired after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
140
+ end
141
+
142
+ def do_post
143
+ post = Net::HTTP::Post.new(GCM_URI.path, initheader = {'Content-Type' => 'application/json',
144
+ 'Authorization' => "key=#{@notification.app.auth_key}"})
145
+ post.body = @notification.as_json.to_json
146
+ @http.request(GCM_URI, post)
147
+ end
148
+
149
+ HTTP_STATUS_CODES = {
150
+ 100 => 'Continue',
151
+ 101 => 'Switching Protocols',
152
+ 102 => 'Processing',
153
+ 200 => 'OK',
154
+ 201 => 'Created',
155
+ 202 => 'Accepted',
156
+ 203 => 'Non-Authoritative Information',
157
+ 204 => 'No Content',
158
+ 205 => 'Reset Content',
159
+ 206 => 'Partial Content',
160
+ 207 => 'Multi-Status',
161
+ 226 => 'IM Used',
162
+ 300 => 'Multiple Choices',
163
+ 301 => 'Moved Permanently',
164
+ 302 => 'Found',
165
+ 303 => 'See Other',
166
+ 304 => 'Not Modified',
167
+ 305 => 'Use Proxy',
168
+ 306 => 'Reserved',
169
+ 307 => 'Temporary Redirect',
170
+ 400 => 'Bad Request',
171
+ 401 => 'Unauthorized',
172
+ 402 => 'Payment Required',
173
+ 403 => 'Forbidden',
174
+ 404 => 'Not Found',
175
+ 405 => 'Method Not Allowed',
176
+ 406 => 'Not Acceptable',
177
+ 407 => 'Proxy Authentication Required',
178
+ 408 => 'Request Timeout',
179
+ 409 => 'Conflict',
180
+ 410 => 'Gone',
181
+ 411 => 'Length Required',
182
+ 412 => 'Precondition Failed',
183
+ 413 => 'Request Entity Too Large',
184
+ 414 => 'Request-URI Too Long',
185
+ 415 => 'Unsupported Media Type',
186
+ 416 => 'Requested Range Not Satisfiable',
187
+ 417 => 'Expectation Failed',
188
+ 418 => "I'm a Teapot",
189
+ 422 => 'Unprocessable Entity',
190
+ 423 => 'Locked',
191
+ 424 => 'Failed Dependency',
192
+ 426 => 'Upgrade Required',
193
+ 500 => 'Internal Server Error',
194
+ 501 => 'Not Implemented',
195
+ 502 => 'Bad Gateway',
196
+ 503 => 'Service Unavailable',
197
+ 504 => 'Gateway Timeout',
198
+ 505 => 'HTTP Version Not Supported',
199
+ 506 => 'Variant Also Negotiates',
200
+ 507 => 'Insufficient Storage',
201
+ 510 => 'Not Extended',
202
+ }
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,20 @@
1
+ module Rapns
2
+ module Daemon
3
+ module Gcm
4
+ class DeliveryHandler < Rapns::Daemon::DeliveryHandler
5
+ def initialize(app)
6
+ @app = app
7
+ @http = Net::HTTP::Persistent.new('rapns')
8
+ end
9
+
10
+ def deliver(notification)
11
+ Rapns::Daemon::Gcm::Delivery.perform(@app, @http, notification)
12
+ end
13
+
14
+ def stopped
15
+ @http.shutdown
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/rapns/daemon.rb CHANGED
@@ -3,18 +3,28 @@ require 'socket'
3
3
  require 'pathname'
4
4
  require 'openssl'
5
5
 
6
+ require 'net/http/persistent'
7
+
6
8
  require 'rapns/daemon/interruptible_sleep'
7
9
  require 'rapns/daemon/delivery_error'
8
- require 'rapns/daemon/disconnection_error'
9
- require 'rapns/daemon/connection'
10
10
  require 'rapns/daemon/database_reconnectable'
11
+ require 'rapns/daemon/delivery'
11
12
  require 'rapns/daemon/delivery_queue'
12
- require 'rapns/daemon/delivery_handler'
13
- require 'rapns/daemon/delivery_handler_pool'
14
- require 'rapns/daemon/feedback_receiver'
15
- require 'rapns/daemon/app_runner'
16
13
  require 'rapns/daemon/feeder'
17
14
  require 'rapns/daemon/logger'
15
+ require 'rapns/daemon/app_runner'
16
+ require 'rapns/daemon/delivery_handler'
17
+
18
+ require 'rapns/daemon/apns/delivery'
19
+ require 'rapns/daemon/apns/disconnection_error'
20
+ require 'rapns/daemon/apns/connection'
21
+ require 'rapns/daemon/apns/app_runner'
22
+ require 'rapns/daemon/apns/delivery_handler'
23
+ require 'rapns/daemon/apns/feedback_receiver'
24
+
25
+ require 'rapns/daemon/gcm/delivery'
26
+ require 'rapns/daemon/gcm/app_runner'
27
+ require 'rapns/daemon/gcm/delivery_handler'
18
28
 
19
29
  module Rapns
20
30
  module Daemon
@@ -90,19 +100,6 @@ module Rapns
90
100
  delete_pid_file
91
101
  end
92
102
 
93
- def self.daemonize
94
- exit if pid = fork
95
- Process.setsid
96
- exit if pid = fork
97
-
98
- Dir.chdir '/'
99
- File.umask 0000
100
-
101
- STDIN.reopen '/dev/null'
102
- STDOUT.reopen '/dev/null', 'a'
103
- STDERR.reopen STDOUT
104
- end
105
-
106
103
  def self.write_pid_file
107
104
  if !config.pid_file.blank?
108
105
  begin
@@ -117,5 +114,19 @@ module Rapns
117
114
  pid_file = config.pid_file
118
115
  File.delete(pid_file) if !pid_file.blank? && File.exists?(pid_file)
119
116
  end
117
+
118
+ # :nocov:
119
+ def self.daemonize
120
+ exit if pid = fork
121
+ Process.setsid
122
+ exit if pid = fork
123
+
124
+ Dir.chdir '/'
125
+ File.umask 0000
126
+
127
+ STDIN.reopen '/dev/null'
128
+ STDOUT.reopen '/dev/null', 'a'
129
+ STDERR.reopen STDOUT
130
+ end
120
131
  end
121
- end
132
+ end
@@ -0,0 +1,7 @@
1
+ module Rapns
2
+ module Gcm
3
+ class App < Rapns::App
4
+ validates :auth_key, :presence => true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Rapns
2
+ module Gcm
3
+ class ExpiryCollapseKeyMutualInclusionValidator < ActiveModel::Validator
4
+ def validate(record)
5
+ if record.collapse_key && !record.expiry
6
+ record.errors[:expiry] << "must be set when using a collapse_key"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Rapns
2
+ module Gcm
3
+ class Notification < Rapns::Notification
4
+ validates :registration_ids, :presence => true
5
+ validates_with Rapns::Gcm::ExpiryCollapseKeyMutualInclusionValidator
6
+ validates_with Rapns::Gcm::PayloadSizeValidator
7
+
8
+ def registration_ids=(ids)
9
+ ids = [ids] if ids && !ids.is_a?(Array)
10
+ super
11
+ end
12
+
13
+ def as_json
14
+ json = {
15
+ 'registration_ids' => registration_ids,
16
+ 'delay_while_idle' => delay_while_idle,
17
+ 'data' => data
18
+ }
19
+
20
+ if collapse_key
21
+ json.merge!({
22
+ 'collapse_key' => collapse_key,
23
+ 'time_to_live' => expiry
24
+ })
25
+ end
26
+
27
+ json
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ module Rapns
2
+ module Gcm
3
+ class PayloadSizeValidator < ActiveModel::Validator
4
+ LIMIT = 4096
5
+
6
+ def validate(record)
7
+ if record.payload_size > LIMIT
8
+ record.errors[:base] << "GCM notification payload cannot be larger than #{LIMIT} bytes."
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Rapns
2
+ module MultiJsonHelper
3
+ def multi_json_load(string, options = {})
4
+ # Calling load on multi_json less than v1.3.0 attempts to load a file from disk.
5
+ if Gem.loaded_specs['multi_json'].version >= Gem::Version.create('1.3.0')
6
+ MultiJson.load(string, options)
7
+ else
8
+ MultiJson.decode(string, options)
9
+ end
10
+ end
11
+
12
+ def multi_json_dump(string, options = {})
13
+ MultiJson.respond_to?(:dump) ? MultiJson.dump(string, options) : MultiJson.encode(string, options)
14
+ end
15
+ end
16
+ end
@@ -1,90 +1,46 @@
1
1
  module Rapns
2
2
  class Notification < ActiveRecord::Base
3
- self.table_name = 'rapns_notifications'
3
+ include Rapns::MultiJsonHelper
4
4
 
5
- attr_accessible :badge, :device_token, :sound, :alert, :attributes_for_device, :expiry,:delivered,
6
- :delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after, :alert_is_json, :app
5
+ self.table_name = 'rapns_notifications'
7
6
 
8
- validates :app, :presence => true
9
- validates :device_token, :presence => true
10
- validates :badge, :numericality => true, :allow_nil => true
11
- validates :expiry, :numericality => true, :presence => true
7
+ # TODO: Dump using multi json.
8
+ serialize :registration_ids
12
9
 
13
- validates_with Rapns::DeviceTokenFormatValidator
14
- validates_with Rapns::BinaryNotificationValidator
10
+ belongs_to :app, :class_name => 'Rapns::App'
15
11
 
16
- scope :ready_for_delivery, lambda { where('delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, Time.now) }
12
+ attr_accessible :badge, :device_token, :sound, :alert, :data, :expiry,:delivered,
13
+ :delivered_at, :failed, :failed_at, :error_code, :error_description, :deliver_after,
14
+ :alert_is_json, :app, :app_id, :collapse_key, :delay_while_idle, :registration_ids
17
15
 
18
- def device_token=(token)
19
- write_attribute(:device_token, token.delete(" <>")) if !token.nil?
20
- end
16
+ validates :expiry, :numericality => true, :allow_nil => true
17
+ validates :app, :presence => true
21
18
 
22
- def alert=(alert)
23
- if alert.is_a?(Hash)
24
- write_attribute(:alert, multi_json_dump(alert))
25
- self.alert_is_json = true if has_attribute?(:alert_is_json)
26
- else
27
- write_attribute(:alert, alert)
28
- self.alert_is_json = false if has_attribute?(:alert_is_json)
29
- end
30
- end
19
+ scope :ready_for_delivery, lambda {
20
+ where('delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)',
21
+ false, false, Time.now)
22
+ }
31
23
 
32
- def alert
33
- string_or_json = read_attribute(:alert)
24
+ scope :for_apps, lambda { |apps|
25
+ where(:app_id => apps.map(&:id))
26
+ }
34
27
 
35
- if has_attribute?(:alert_is_json)
36
- if alert_is_json?
37
- multi_json_load(string_or_json)
38
- else
39
- string_or_json
40
- end
41
- else
42
- multi_json_load(string_or_json) rescue string_or_json
28
+ def initialize(attributes = nil, options = {})
29
+ if attributes.is_a?(Hash) && attributes.keys.include?(:attributes_for_device)
30
+ msg = ":attributes_for_device via mass-assignment is deprecated. Use :data or the attributes_for_device= instance method."
31
+ ActiveSupport::Deprecation.warn(msg, caller(1))
43
32
  end
33
+ super
44
34
  end
45
35
 
46
- def attributes_for_device=(attrs)
47
- raise ArgumentError, "attributes_for_device must be a Hash" if !attrs.is_a?(Hash)
48
- write_attribute(:attributes_for_device, multi_json_dump(attrs))
36
+ def data=(attrs)
37
+ return unless attrs
38
+ raise ArgumentError, "must be a Hash" if !attrs.is_a?(Hash)
39
+ write_attribute(:data, multi_json_dump(attrs))
49
40
  end
50
41
 
51
- def attributes_for_device
52
- multi_json_load(read_attribute(:attributes_for_device)) if read_attribute(:attributes_for_device)
53
- end
54
-
55
- MDM_KEY = '__rapns_mdm__'
56
- def mdm=(magic)
57
- self.attributes_for_device = { MDM_KEY => magic }
58
- end
59
-
60
- CONTENT_AVAILABLE_KEY = '__rapns_content_available__'
61
- def content_available=(bool)
62
- return unless bool
63
- self.attributes_for_device = { CONTENT_AVAILABLE_KEY => true }
64
- end
65
-
66
- def as_json
67
- json = ActiveSupport::OrderedHash.new
68
-
69
- if attributes_for_device && attributes_for_device.key?(MDM_KEY)
70
- json['mdm'] = attributes_for_device[MDM_KEY]
71
- else
72
- json['aps'] = ActiveSupport::OrderedHash.new
73
- json['aps']['alert'] = alert if alert
74
- json['aps']['badge'] = badge if badge
75
- json['aps']['sound'] = sound if sound
76
-
77
- if attributes_for_device && attributes_for_device[CONTENT_AVAILABLE_KEY]
78
- json['aps']['content-available'] = 1
79
- end
80
-
81
- if attributes_for_device
82
- non_aps_attributes = attributes_for_device.reject { |k, v| k == CONTENT_AVAILABLE_KEY }
83
- non_aps_attributes.each { |k, v| json[k.to_s] = v.to_s }
84
- end
85
- end
86
-
87
- json
42
+ def data
43
+ multi_json_load(read_attribute(:data)) if read_attribute(:data)
88
44
  end
89
45
 
90
46
  def payload
@@ -94,28 +50,5 @@ module Rapns
94
50
  def payload_size
95
51
  payload.bytesize
96
52
  end
97
-
98
- # This method conforms to the enhanced binary format.
99
- # http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
100
- def to_binary(options = {})
101
- id_for_pack = options[:for_validation] ? 0 : id
102
- [1, id_for_pack, expiry, 0, 32, device_token, payload_size, payload].pack("cNNccH*na*")
103
- end
104
-
105
-
106
- private
107
-
108
- def multi_json_load(string, options = {})
109
- # Calling load on multi_json less than v1.3.0 attempts to load a file from disk. Check the version explicitly.
110
- if Gem.loaded_specs['multi_json'].version >= Gem::Version.create('1.3.0')
111
- MultiJson.load(string, options)
112
- else
113
- MultiJson.decode(string, options)
114
- end
115
- end
116
-
117
- def multi_json_dump(string, options = {})
118
- MultiJson.respond_to?(:dump) ? MultiJson.dump(string, options) : MultiJson.encode(string, options)
119
- end
120
53
  end
121
54
  end
data/lib/rapns/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rapns
2
- VERSION = '2.0.5'
2
+ VERSION = '3.0.0.beta.1'
3
3
  end
data/lib/rapns.rb CHANGED
@@ -2,9 +2,19 @@ require 'active_record'
2
2
  require 'multi_json'
3
3
 
4
4
  require 'rapns/version'
5
- require 'rapns/binary_notification_validator'
6
- require 'rapns/device_token_format_validator'
5
+ require 'rapns/multi_json_helper'
7
6
  require 'rapns/notification'
8
- require 'rapns/feedback'
9
7
  require 'rapns/app'
10
- require 'rapns/config'
8
+ require 'rapns/configuration'
9
+
10
+ require 'rapns/apns/binary_notification_validator'
11
+ require 'rapns/apns/device_token_format_validator'
12
+ require 'rapns/apns/notification'
13
+ require 'rapns/apns/feedback'
14
+ require 'rapns/apns/app'
15
+
16
+ require 'rapns/gcm/expiry_collapse_key_mutual_inclusion_validator'
17
+ require 'rapns/gcm/payload_size_validator'
18
+ require 'rapns/gcm/notification'
19
+ require 'rapns/gcm/app'
20
+
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'cane/rake_task'
3
+
4
+ desc "Run cane to check quality metrics"
5
+ Cane::RakeTask.new(:quality) do |cane|
6
+ cane.add_threshold 'coverage/covered_percent', :>=, 97
7
+ cane.no_style = false
8
+ cane.style_measure = 1000
9
+ cane.no_doc = true
10
+ cane.abc_max = 15
11
+ cane.abc_exclude = %w(Rapns::Daemon::Gcm::Delivery#handle_errors)
12
+ end
13
+
14
+ namespace :spec do
15
+ task :cane => ['spec', 'quality']
16
+ end
17
+ rescue LoadError
18
+ warn "cane not available, quality task not provided."
19
+ end
@@ -0,0 +1,34 @@
1
+ namespace :test do
2
+ task :build_rails do
3
+ require 'fileutils'
4
+
5
+ def cmd(str)
6
+ puts "* #{str}"
7
+ retval = Bundler.with_clean_env { `#{str}` }
8
+ puts retval.strip
9
+ retval
10
+ end
11
+
12
+ rapns_root = Dir.pwd
13
+ path = '/tmp/rails_test'
14
+ cmd("rm -rf #{path}")
15
+ FileUtils.mkdir_p(path)
16
+ pwd = Dir.pwd
17
+
18
+ cmd("bundle exec rails new #{path} --skip-bundle")
19
+ branch = cmd("git branch | grep '\*'").split(' ').last
20
+
21
+ begin
22
+ Dir.chdir(path)
23
+ cmd('echo "gem \'rake\'" >> Gemfile')
24
+ cmd("echo \"gem 'rapns', :git => '#{rapns_root}', :branch => '#{branch}'\" >> Gemfile")
25
+ cmd('bundle install')
26
+ cmd('bundle exec rails g rapns')
27
+ cmd('bundle exec rake db:migrate')
28
+ ensure
29
+ Dir.chdir(pwd)
30
+ end
31
+
32
+ puts "Built into #{path}"
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'acceptance_spec_helper'
2
+
3
+ describe 'GCM upgrade' do
4
+ before do
5
+ setup_rails
6
+ generate
7
+ migrate('create_rapns_notifications', 'create_rapns_feedback',
8
+ 'add_alert_is_json_to_rapns_notifications', 'add_app_to_rapns',
9
+ 'create_rapns_apps')
10
+
11
+ as_test_rails_db do
12
+ now = Time.now.to_s(:db)
13
+ ActiveRecord::Base.connection.execute <<-SQL
14
+ INSERT INTO rapns_apps (key, environment, certificate, created_at, updated_at)
15
+ VALUES ('test', 'development', 'c3rt', '#{now}', '#{now}')
16
+ SQL
17
+
18
+ ActiveRecord::Base.connection.execute <<-SQL
19
+ INSERT INTO rapns_notifications (app, device_token, created_at, updated_at)
20
+ VALUES ('test', 't0k3n', '#{now}', '#{now}')
21
+ SQL
22
+ end
23
+
24
+ migrate('add_gcm')
25
+ end
26
+
27
+ it 'associates apps and notifications' do
28
+ as_test_rails_db do
29
+ app = Rapns::Apns::App.first
30
+ app.name.should == 'test'
31
+ app.notifications.count.should == 1
32
+ end
33
+ end
34
+ end