rapns 2.0.5 → 3.0.0.beta.1

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