fcm-ruby-push-notifications 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +19 -0
  5. data/CHANGELOG.md +32 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE +22 -0
  8. data/README.md +82 -0
  9. data/Rakefile +12 -0
  10. data/examples/apns.rb +27 -0
  11. data/examples/gcm.rb +25 -0
  12. data/examples/mpns.rb +22 -0
  13. data/examples/wns.rb +25 -0
  14. data/gemfiles/Gemfile-legacy +8 -0
  15. data/lib/ruby-push-notifications.rb +7 -0
  16. data/lib/ruby-push-notifications/apns.rb +44 -0
  17. data/lib/ruby-push-notifications/apns/apns_connection.rb +70 -0
  18. data/lib/ruby-push-notifications/apns/apns_notification.rb +91 -0
  19. data/lib/ruby-push-notifications/apns/apns_pusher.rb +84 -0
  20. data/lib/ruby-push-notifications/apns/apns_results.rb +30 -0
  21. data/lib/ruby-push-notifications/fcm.rb +8 -0
  22. data/lib/ruby-push-notifications/fcm/fcm_connection.rb +60 -0
  23. data/lib/ruby-push-notifications/fcm/fcm_error.rb +17 -0
  24. data/lib/ruby-push-notifications/fcm/fcm_notification.rb +31 -0
  25. data/lib/ruby-push-notifications/fcm/fcm_pusher.rb +29 -0
  26. data/lib/ruby-push-notifications/fcm/fcm_request.rb +28 -0
  27. data/lib/ruby-push-notifications/fcm/fcm_response.rb +89 -0
  28. data/lib/ruby-push-notifications/fcm/fcm_result.rb +52 -0
  29. data/lib/ruby-push-notifications/gcm.rb +6 -0
  30. data/lib/ruby-push-notifications/gcm/gcm_connection.rb +54 -0
  31. data/lib/ruby-push-notifications/gcm/gcm_error.rb +17 -0
  32. data/lib/ruby-push-notifications/gcm/gcm_notification.rb +31 -0
  33. data/lib/ruby-push-notifications/gcm/gcm_pusher.rb +35 -0
  34. data/lib/ruby-push-notifications/gcm/gcm_response.rb +89 -0
  35. data/lib/ruby-push-notifications/gcm/gcm_result.rb +52 -0
  36. data/lib/ruby-push-notifications/mpns.rb +5 -0
  37. data/lib/ruby-push-notifications/mpns/mpns_connection.rb +87 -0
  38. data/lib/ruby-push-notifications/mpns/mpns_notification.rb +94 -0
  39. data/lib/ruby-push-notifications/mpns/mpns_pusher.rb +33 -0
  40. data/lib/ruby-push-notifications/mpns/mpns_response.rb +82 -0
  41. data/lib/ruby-push-notifications/mpns/mpns_result.rb +182 -0
  42. data/lib/ruby-push-notifications/notification_results_manager.rb +14 -0
  43. data/lib/ruby-push-notifications/wns.rb +6 -0
  44. data/lib/ruby-push-notifications/wns/wns_access.rb +82 -0
  45. data/lib/ruby-push-notifications/wns/wns_connection.rb +97 -0
  46. data/lib/ruby-push-notifications/wns/wns_notification.rb +101 -0
  47. data/lib/ruby-push-notifications/wns/wns_pusher.rb +32 -0
  48. data/lib/ruby-push-notifications/wns/wns_response.rb +83 -0
  49. data/lib/ruby-push-notifications/wns/wns_result.rb +208 -0
  50. data/ruby-push-notifications.gemspec +28 -0
  51. data/spec/factories.rb +17 -0
  52. data/spec/factories/notifications.rb +30 -0
  53. data/spec/ruby-push-notifications/apns/apns_connection_spec.rb +92 -0
  54. data/spec/ruby-push-notifications/apns/apns_notification_spec.rb +42 -0
  55. data/spec/ruby-push-notifications/apns/apns_pusher_spec.rb +295 -0
  56. data/spec/ruby-push-notifications/gcm/gcm_connection_spec.rb +46 -0
  57. data/spec/ruby-push-notifications/gcm/gcm_notification_spec.rb +37 -0
  58. data/spec/ruby-push-notifications/gcm/gcm_pusher_spec.rb +45 -0
  59. data/spec/ruby-push-notifications/gcm/gcm_response_spec.rb +82 -0
  60. data/spec/ruby-push-notifications/mpns/mpns_connection_spec.rb +46 -0
  61. data/spec/ruby-push-notifications/mpns/mpns_notification_spec.rb +53 -0
  62. data/spec/ruby-push-notifications/mpns/mpns_pusher_spec.rb +59 -0
  63. data/spec/ruby-push-notifications/mpns/mpns_response_spec.rb +64 -0
  64. data/spec/ruby-push-notifications/wns/wns_access_spec.rb +76 -0
  65. data/spec/ruby-push-notifications/wns/wns_connection_spec.rb +53 -0
  66. data/spec/ruby-push-notifications/wns/wns_notification_spec.rb +177 -0
  67. data/spec/ruby-push-notifications/wns/wns_pusher_spec.rb +58 -0
  68. data/spec/ruby-push-notifications/wns/wns_response_spec.rb +65 -0
  69. data/spec/spec_helper.rb +23 -0
  70. data/spec/support/dummy.pem +44 -0
  71. data/spec/support/factory_girl.rb +5 -0
  72. data/spec/support/results_shared_examples.rb +31 -0
  73. metadata +249 -0
@@ -0,0 +1,52 @@
1
+
2
+ module RubyPushNotifications
3
+ module FCM
4
+ # Class that encapsulates the result of a single sent notification to a single
5
+ # Registration ID
6
+ # (https://developer.android.com/google/gcm/server-ref.html#table4)
7
+ # @author Carlos Alonso
8
+ class FCMResult ; end
9
+
10
+ # Indicates that the notification was successfully sent to the corresponding
11
+ # registration ID
12
+ class FCMResultOK < FCMResult
13
+
14
+ def ==(other)
15
+ other.is_a?(FCMResultOK) || super(other)
16
+ end
17
+ end
18
+
19
+ # Indicates that the notification was successfully sent to the corresponding
20
+ # registration ID but GCM sent a canonical ID for it, so the received canonical
21
+ # ID should be used as registration ID ASAP.
22
+ class FCMCanonicalIDResult < FCMResult
23
+
24
+ # @return [String]. The suggested canonical ID from GCM
25
+ attr_reader :canonical_id
26
+
27
+ def initialize(canonical_id)
28
+ @canonical_id = canonical_id
29
+ end
30
+
31
+ def ==(other)
32
+ (other.is_a?(FCMCanonicalIDResult) && @canonical_id == other.canonical_id) ||
33
+ super(other)
34
+ end
35
+ end
36
+
37
+ # An error occurred sending the notification to the registration ID.
38
+ class FCMResultError < FCMResult
39
+
40
+ # @return [String]. The error sent by GCM
41
+ attr_accessor :error
42
+
43
+ def initialize(error)
44
+ @error = error
45
+ end
46
+
47
+ def ==(other)
48
+ (other.is_a?(FCMResultError) && @error == other.error) || super(other)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ require 'ruby-push-notifications/gcm/gcm_connection'
2
+ require 'ruby-push-notifications/gcm/gcm_notification'
3
+ require 'ruby-push-notifications/gcm/gcm_pusher'
4
+ require 'ruby-push-notifications/gcm/gcm_response'
5
+ require 'ruby-push-notifications/gcm/gcm_error'
6
+ require 'ruby-push-notifications/gcm/gcm_result'
@@ -0,0 +1,54 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ module RubyPushNotifications
5
+ module GCM
6
+ # Encapsulates a connection to the GCM service
7
+ # Responsible for final connection with the service.
8
+ #
9
+ # @author Carlos Alonso
10
+ class GCMConnection
11
+
12
+ # @private The URL of the Android GCM endpoint
13
+ GCM_URL = 'https://android.googleapis.com/gcm/send'
14
+
15
+ # @private Content-Type HTTP Header string
16
+ CONTENT_TYPE_HEADER = 'Content-Type'
17
+
18
+ # @private Application/JSON content type
19
+ JSON_CONTENT_TYPE = 'application/json'
20
+
21
+ # @private Authorization HTTP Header String
22
+ AUTHORIZATION_HEADER = 'Authorization'
23
+
24
+ # Issues a POST request to the GCM send endpoint to
25
+ # submit the given notifications.
26
+ #
27
+ # @param notification [String]. The text to POST
28
+ # @param key [String]. The GCM sender id to use
29
+ # (https://developer.android.com/google/gcm/gcm.html#senderid)
30
+ # @param options [Hash] optional. Options for #post. Currently supports:
31
+ # * url [String]: URL of the GCM endpoint. Defaults to the official GCM URL.
32
+ # * open_timeout [Integer]: Number of seconds to wait for the connection to open. Defaults to 30.
33
+ # * read_timeout [Integer]: Number of seconds to wait for one block to be read. Defaults to 30.
34
+ # @return [GCMResponse]. The GCMResponse that encapsulates the received response
35
+ def self.post(notification, key, options = {})
36
+ headers = {
37
+ CONTENT_TYPE_HEADER => JSON_CONTENT_TYPE,
38
+ AUTHORIZATION_HEADER => "key=#{key}"
39
+ }
40
+
41
+ url = URI.parse options.fetch(:url, GCM_URL)
42
+ http = Net::HTTP.new url.host, url.port
43
+ http.use_ssl = url.scheme == 'https'
44
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
45
+ http.open_timeout = options.fetch(:open_timeout, 30)
46
+ http.read_timeout = options.fetch(:read_timeout, 30)
47
+
48
+ response = http.post url.path, notification, headers
49
+
50
+ GCMResponse.new response.code.to_i, response.body
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module RubyPushNotifications
3
+ module GCM
4
+ # Base class for all GCM related errors
5
+ #
6
+ # @author Carlos Alonso
7
+ class GCMError < StandardError ; end
8
+
9
+ class MalformedGCMJSONError < GCMError ; end
10
+
11
+ class GCMAuthError < GCMError ; end
12
+
13
+ class GCMInternalError < GCMError ; end
14
+
15
+ class UnexpectedGCMResponseError < GCMError ; end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module RubyPushNotifications
3
+ module GCM
4
+ # Encapsulates a GCM Notification.
5
+ # By default only Required fields are set.
6
+ # (https://developer.android.com/google/gcm/server-ref.html#send-downstream)
7
+ #
8
+ # @author Carlos Alonso
9
+ class GCMNotification
10
+ include RubyPushNotifications::NotificationResultsManager
11
+
12
+ # Initializes the notification
13
+ #
14
+ # @param [Array]. Array with the receiver's GCM registration ids.
15
+ # @param [Hash]. Payload to send.
16
+ def initialize(registration_ids, data)
17
+ @registration_ids = registration_ids
18
+ @data = data
19
+ end
20
+
21
+ # @return [String]. The GCM's JSON format for the payload to send.
22
+ # (https://developer.android.com/google/gcm/server-ref.html#send-downstream)
23
+ def as_gcm_json
24
+ JSON.dump(
25
+ registration_ids: @registration_ids,
26
+ data: @data
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+
2
+ module RubyPushNotifications
3
+ module GCM
4
+
5
+ # This class is responsible for sending notifications to the GCM service.
6
+ #
7
+ # @author Carlos Alonso
8
+ class GCMPusher
9
+
10
+ # Initializes the GCMPusher
11
+ #
12
+ # @param key [String]. GCM sender id to use
13
+ # ((https://developer.android.com/google/gcm/gcm.html#senderid))
14
+ # @param options [Hash] optional. Options for GCMPusher. Currently supports:
15
+ # * url [String]: URL of the GCM endpoint. Defaults to the official GCM URL.
16
+ # * open_timeout [Integer]: Number of seconds to wait for the connection to open. Defaults to 30.
17
+ # * read_timeout [Integer]: Number of seconds to wait for one block to be read. Defaults to 30.
18
+ def initialize(key, options = {})
19
+ @key = key
20
+ @options = options
21
+ end
22
+
23
+ # Actually pushes the given notifications.
24
+ # Assigns every notification an array with the result of each
25
+ # individual notification.
26
+ #
27
+ # @param notifications [Array]. Array of GCMNotification to send.
28
+ def push(notifications)
29
+ notifications.each do |notif|
30
+ notif.results = GCMConnection.post notif.as_gcm_json, @key, @options
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,89 @@
1
+
2
+ module RubyPushNotifications
3
+ module GCM
4
+
5
+ # This class encapsulates a response received from the GCM service
6
+ # and helps parsing and understanding the received meesages/codes.
7
+ #
8
+ # @author Carlos Alonso
9
+ class GCMResponse
10
+
11
+ # @return [Integer] the number of successfully sent notifications
12
+ attr_reader :success
13
+
14
+ # @return [Integer] the number of failed notifications
15
+ attr_reader :failed
16
+
17
+ # @return [Integer] the number of received canonical IDS
18
+ # (https://developer.android.com/google/gcm/server-ref.html#table4)
19
+ attr_reader :canonical_ids
20
+
21
+ # @return [Array] Array of a GCMResult for every receiver of the notification
22
+ # sent indicating the result of the operation.
23
+ attr_reader :results
24
+ alias_method :individual_results, :results
25
+
26
+ # Initializes the GCMResponse and runs response parsing
27
+ #
28
+ # @param code [Integer]. The HTTP status code received
29
+ # @param body [String]. The response body received.
30
+ # @raise MalformedGCMJsonError if code == 400 Bad Request
31
+ # @raise GCMAuthError if code == 401 Unauthorized
32
+ # @raise GCMInternalError if code == 5xx
33
+ # @raise UnexpectedGCMResponseError if code != 200
34
+ def initialize(code, body)
35
+ case code
36
+ when 200
37
+ parse_response body
38
+ when 400
39
+ raise MalformedGCMJSONError, body
40
+ when 401
41
+ raise GCMAuthError, body
42
+ when 500..599
43
+ raise GCMInternalError, body
44
+ else
45
+ raise UnexpectedGCMResponseError, code
46
+ end
47
+ end
48
+
49
+ def ==(other)
50
+ (other.is_a?(GCMResponse) &&
51
+ success == other.success &&
52
+ failed == other.failed &&
53
+ canonical_ids == other.canonical_ids &&
54
+ results == other.results) || super(other)
55
+ end
56
+
57
+ private
58
+
59
+ # Parses the response extracting counts for successful, failed and
60
+ # containing canonical ID messages.
61
+ # Also creates the results array assigning a GCMResult subclass for each
62
+ # registration ID the notification was sent to.
63
+ #
64
+ # @param body [String]. The response body
65
+ def parse_response(body)
66
+ json = JSON.parse body, symbolize_names: true
67
+ @success = json[:success]
68
+ @failed = json[:failure]
69
+ @canonical_ids = json[:canonical_ids]
70
+ @results = (json[:results] || []).map { |result| gcm_result_for result }
71
+ end
72
+
73
+ # Factory method that, for each GCM result object assigns a GCMResult
74
+ # subclass.
75
+ #
76
+ # @param result [Hash]. GCM Result parsed hash
77
+ # @return [GCMResult]. Corresponding GCMResult subclass
78
+ def gcm_result_for(result)
79
+ if canonical_id = result[:registration_id]
80
+ GCMCanonicalIDResult.new canonical_id
81
+ elsif error = result[:error]
82
+ GCMResultError.new error
83
+ else
84
+ GCMResultOK.new
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,52 @@
1
+
2
+ module RubyPushNotifications
3
+ module GCM
4
+ # Class that encapsulates the result of a single sent notification to a single
5
+ # Registration ID
6
+ # (https://developer.android.com/google/gcm/server-ref.html#table4)
7
+ # @author Carlos Alonso
8
+ class GCMResult ; end
9
+
10
+ # Indicates that the notification was successfully sent to the corresponding
11
+ # registration ID
12
+ class GCMResultOK < GCMResult
13
+
14
+ def ==(other)
15
+ other.is_a?(GCMResultOK) || super(other)
16
+ end
17
+ end
18
+
19
+ # Indicates that the notification was successfully sent to the corresponding
20
+ # registration ID but GCM sent a canonical ID for it, so the received canonical
21
+ # ID should be used as registration ID ASAP.
22
+ class GCMCanonicalIDResult < GCMResult
23
+
24
+ # @return [String]. The suggested canonical ID from GCM
25
+ attr_reader :canonical_id
26
+
27
+ def initialize(canonical_id)
28
+ @canonical_id = canonical_id
29
+ end
30
+
31
+ def ==(other)
32
+ (other.is_a?(GCMCanonicalIDResult) && @canonical_id == other.canonical_id) ||
33
+ super(other)
34
+ end
35
+ end
36
+
37
+ # An error occurred sending the notification to the registration ID.
38
+ class GCMResultError < GCMResult
39
+
40
+ # @return [String]. The error sent by GCM
41
+ attr_accessor :error
42
+
43
+ def initialize(error)
44
+ @error = error
45
+ end
46
+
47
+ def ==(other)
48
+ (other.is_a?(GCMResultError) && @error == other.error) || super(other)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ require 'ruby-push-notifications/mpns/mpns_connection'
2
+ require 'ruby-push-notifications/mpns/mpns_notification'
3
+ require 'ruby-push-notifications/mpns/mpns_pusher'
4
+ require 'ruby-push-notifications/mpns/mpns_response'
5
+ require 'ruby-push-notifications/mpns/mpns_result'
@@ -0,0 +1,87 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ module RubyPushNotifications
5
+ module MPNS
6
+ # Encapsulates a connection to the MPNS service
7
+ # Responsible for final connection with the service.
8
+ #
9
+ class MPNSConnection
10
+
11
+ # @private Content-Type HTTP Header string
12
+ CONTENT_TYPE_HEADER = 'Content-Type'
13
+
14
+ # @private text/xml content type
15
+ X_NOTIFICATION_CLASS = 'X-NotificationClass'
16
+
17
+ # @private Windows Phone Target
18
+ X_WINDOWSPHONE_TARGET = 'X-WindowsPhone-Target'
19
+
20
+ # @private text/xml content type
21
+ XML_CONTENT_TYPE = 'text/xml'
22
+
23
+ # @private Enumators for notification types
24
+ BASEBATCH = { tile: 1, toast: 2, raw: 3 }
25
+
26
+ # @private Enumators for delay
27
+ BATCHADDS = { delay450: 10, delay900: 20 }
28
+
29
+ # @private Windows Phone Target Types
30
+ WP_TARGETS = { toast: 'toast', tile: 'token' }
31
+
32
+ # Issues a POST request to the MPNS send endpoint to
33
+ # submit the given notifications.
34
+ #
35
+ # @param n [MPNSNotification]. The notification object to POST
36
+ # @param cert [String] optional. Contents of the PEM encoded certificate.
37
+ # @param options [Hash] optional. Options for GCMPusher. Currently supports:
38
+ # * open_timeout [Integer]: Number of seconds to wait for the connection to open. Defaults to 30.
39
+ # * read_timeout [Integer]: Number of seconds to wait for one block to be read. Defaults to 30.
40
+ # @return [Array]. The response of post
41
+ # (http://msdn.microsoft.com/pt-br/library/windows/apps/ff941099)
42
+ def self.post(n, cert = nil, options = {})
43
+ headers = build_headers(n.data[:type], n.data[:delay])
44
+ body = n.as_mpns_xml
45
+ responses = []
46
+ n.each_device do |url|
47
+ http = Net::HTTP.new url.host, url.port
48
+ http.open_timeout = options.fetch(:open_timeout, 30)
49
+ http.read_timeout = options.fetch(:read_timeout, 30)
50
+ if cert && url.scheme == 'https'
51
+ http.use_ssl = true
52
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
53
+ http.ca_file = cert
54
+ end
55
+ response = http.post url.path, body, headers
56
+ responses << { device_url: url.to_s, headers: extract_headers(response), code: response.code.to_i }
57
+ end
58
+ MPNSResponse.new responses
59
+ end
60
+
61
+ # Build Header based on type and delay
62
+ #
63
+ # @param type [Symbol]. The type of notification
64
+ # @param delay [Symbol]. The delay to be used
65
+ # @return [Hash]. Correct delay based on notification type
66
+ def self.build_headers(type, delay)
67
+ headers = {
68
+ CONTENT_TYPE_HEADER => XML_CONTENT_TYPE,
69
+ X_NOTIFICATION_CLASS => "#{(BASEBATCH[type] + (BATCHADDS[delay] || 0))}"
70
+ }
71
+ headers[X_WINDOWSPHONE_TARGET] = WP_TARGETS[type] unless type == :raw
72
+ headers
73
+ end
74
+
75
+ # Extract headers from response
76
+ # @param response [Net::HTTPResponse]. HTTP response for request
77
+ #
78
+ # @return [Hash]. Hash with headers with case-insensitive keys and string values
79
+ def self.extract_headers(response)
80
+ headers = {}
81
+ response.each_header { |k, v| headers[k] = v }
82
+ headers
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,94 @@
1
+ require 'builder'
2
+
3
+ module RubyPushNotifications
4
+ module MPNS
5
+ # Encapsulates a MPNS Notification.
6
+ # Actually support for raw, toast, tiles notifications
7
+ # (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202945)
8
+ #
9
+ class MPNSNotification
10
+ include RubyPushNotifications::NotificationResultsManager
11
+
12
+ # @return [Hash]. Payload to send.
13
+ # Toast :title => a bold message
14
+ # :message => the small message
15
+ # :param => a string parameter that is passed to the app
16
+ # Tile :image => a new image for the tile
17
+ # :count => a number to show on the tile
18
+ # :title => the new title of the tile
19
+ # :back_image => an image for the back of the tile
20
+ # :back_title => a title on the back of the tile
21
+ # :back_content => some content (text) for the back
22
+ # Raw :message => the full Hash message body
23
+ attr_reader :data
24
+
25
+ # @return [Array]. Array with the receiver's MPNS device URLs.
26
+ attr_reader :device_urls
27
+
28
+ # Initializes the notification
29
+ #
30
+ # @param [Array]. Array with the receiver's device urls.
31
+ # @param [Hash]. Payload to send.
32
+ # Toast :title => a bold message
33
+ # :message => the small message
34
+ # :param => a string parameter that is passed to the app
35
+ # Tile :image => a new image for the tile
36
+ # :count => a number to show on the tile
37
+ # :title => the new title of the tile
38
+ # :back_image => an image for the back of the tile
39
+ # :back_title => a title on the back of the tile
40
+ # :back_content => some content (text) for the back
41
+ # Raw :message => the full XML message body
42
+ def initialize(device_urls, data)
43
+ @device_urls = device_urls
44
+ @data = data
45
+ @data[:type] ||= :raw
46
+ end
47
+
48
+
49
+ # @return [String]. The GCM's XML format for the payload to send.
50
+ # (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202945)
51
+ def as_mpns_xml
52
+ xml = Builder::XmlMarkup.new
53
+ xml.instruct!
54
+ if data[:type] != :raw
55
+ xml.tag!('wp:Notification', 'xmlns:wp' => 'WPNotification') do
56
+ case data[:type]
57
+ when :toast
58
+ xml.tag!('wp:Toast') do
59
+ xml.tag!('wp:Text1') { xml.text!(data[:title]) }
60
+ xml.tag!('wp:Text2') { xml.text!(data[:message]) }
61
+ xml.tag!('wp:Param') { xml.text!(data[:param]) } if data[:param]
62
+ end
63
+ when :tile
64
+ xml.tag!('wp:Tile') do
65
+ xml.tag!('wp:BackgroundImage') { xml.text!(data[:image]) } if data[:image]
66
+ xml.tag!('wp:Count') { xml.text!(data[:count].to_s) } if data[:count]
67
+ xml.tag!('wp:Title') { xml.text!(data[:title]) } if data[:title]
68
+ xml.tag!('wp:BackBackgroundImage') { xml.text!(data[:back_image]) } if data[:back_image]
69
+ xml.tag!('wp:BackTitle') { xml.text!(data[:back_title]) } if data[:back_title]
70
+ xml.tag!('wp:BackContent') { xml.text!(data[:back_content]) } if data[:back_content]
71
+ end
72
+ end
73
+ end
74
+ else
75
+ xml.root { build_hash(xml, data[:message]) }
76
+ end
77
+ xml.target!
78
+ end
79
+
80
+ def each_device
81
+ @device_urls.each do |url|
82
+ yield(URI.parse url)
83
+ end
84
+ end
85
+
86
+ def build_hash(xml, options)
87
+ options.each do |k, v|
88
+ xml.tag!(k.to_s) { v.is_a?(Hash) ? build_hash(xml, v) : xml.text!(v.to_s) }
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end