ruby-push-notifications 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.hound.yml +2 -0
- data/.ruby-style.yml +1063 -0
- data/.travis.yml +7 -0
- data/Gemfile.lock +2 -0
- data/README.md +6 -2
- data/examples/mpns.rb +17 -0
- data/lib/ruby-push-notifications/mpns/mpns_connection.rb +82 -0
- data/lib/ruby-push-notifications/mpns/mpns_notification.rb +96 -0
- data/lib/ruby-push-notifications/mpns/mpns_pusher.rb +29 -0
- data/lib/ruby-push-notifications/mpns/mpns_response.rb +81 -0
- data/lib/ruby-push-notifications/mpns/mpns_result.rb +182 -0
- data/lib/ruby-push-notifications/mpns.rb +5 -0
- data/lib/ruby-push-notifications/version.rb +1 -1
- data/lib/ruby-push-notifications.rb +1 -0
- data/ruby-push-notifications.gemspec +2 -0
- data/spec/factories/notifications.rb +8 -0
- data/spec/factories.rb +5 -0
- data/spec/ruby-push-notifications/mpns/mpns_connection_spec.rb +46 -0
- data/spec/ruby-push-notifications/mpns/mpns_notification_spec.rb +43 -0
- data/spec/ruby-push-notifications/mpns/mpns_pusher_spec.rb +59 -0
- data/spec/ruby-push-notifications/mpns/mpns_response_spec.rb +64 -0
- metadata +33 -2
data/.travis.yml
CHANGED
@@ -5,3 +5,10 @@ rvm:
|
|
5
5
|
- "2.0.0"
|
6
6
|
- "1.9.3"
|
7
7
|
env: CODECLIMATE_REPO_TOKEN=efdb12c380287a25b2b26362aa710a9b59020122cbe4edecf0d353bf50e0046a
|
8
|
+
notifications:
|
9
|
+
webhooks:
|
10
|
+
urls:
|
11
|
+
- https://webhooks.gitter.im/e/b740ebfeb7e0dba10293
|
12
|
+
on_success: change # options: [always|never|change] default: always
|
13
|
+
on_failure: always # options: [always|never|change] default: always
|
14
|
+
on_start: false # default: false
|
data/Gemfile.lock
CHANGED
@@ -2,6 +2,7 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
ruby-push-notifications (0.1.0)
|
5
|
+
builder (~> 3.2)
|
5
6
|
|
6
7
|
GEM
|
7
8
|
remote: https://rubygems.org/
|
@@ -13,6 +14,7 @@ GEM
|
|
13
14
|
thread_safe (~> 0.3, >= 0.3.4)
|
14
15
|
tzinfo (~> 1.1)
|
15
16
|
addressable (2.3.8)
|
17
|
+
builder (3.2.2)
|
16
18
|
codeclimate-test-reporter (0.4.7)
|
17
19
|
simplecov (>= 0.7.1, < 1.0.0)
|
18
20
|
crack (0.4.2)
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Ruby Push Notifications
|
2
2
|
|
3
|
-
[](https://travis-ci.org/calonso/ruby-push-notifications) [](https://gemnasium.com/calonso/ruby-push-notifications) [](https://codeclimate.com/github/calonso/ruby-push-notifications) [](https://codeclimate.com/github/calonso/ruby-push-notifications) [](http://badge.fury.io/rb/ruby-push-notifications)
|
3
|
+
[](https://travis-ci.org/calonso/ruby-push-notifications) [](https://gemnasium.com/calonso/ruby-push-notifications) [](https://codeclimate.com/github/calonso/ruby-push-notifications) [](https://codeclimate.com/github/calonso/ruby-push-notifications) [](http://badge.fury.io/rb/ruby-push-notifications) [](https://gitter.im/calonso/ruby-push-notifications?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
4
4
|
|
5
|
-
###iOS and
|
5
|
+
###iOS, Android and Windows Phone Push Notifications made easy!
|
6
6
|
|
7
7
|
## Features
|
8
8
|
|
@@ -38,6 +38,7 @@ For completely detailed examples:
|
|
38
38
|
|
39
39
|
1. [Apple iOS example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/apns.rb)
|
40
40
|
2. [Google Android example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/gcm.rb)
|
41
|
+
3. [Windows Phone example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/mpns.rb)
|
41
42
|
|
42
43
|
## Pending tasks
|
43
44
|
|
@@ -49,6 +50,9 @@ Feel free to contribute!!
|
|
49
50
|
* Validate GCM notifications format and max size
|
50
51
|
* Split GCM notifications in parts if more than 1000 destinations are given (currently raising exception)
|
51
52
|
* Integrate with APNS Feedback service
|
53
|
+
* Validate MPNS notifications format
|
54
|
+
* Validate MPNS data
|
55
|
+
* Add other platforms (Amazon Fire...)
|
52
56
|
|
53
57
|
## Contributing
|
54
58
|
|
data/examples/mpns.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'ruby-push-notifications'
|
6
|
+
|
7
|
+
device_urls = [
|
8
|
+
'First device url here',
|
9
|
+
'Second device url here'
|
10
|
+
]
|
11
|
+
|
12
|
+
# Notification with toast type
|
13
|
+
notification = RubyPushNotifications::MPNS::MPNSNotification.new device_urls, { title: 'Title', message: 'Hello MPNS World!', type: :toast }
|
14
|
+
|
15
|
+
pusher = RubyPushNotifications::MPNS::MPNSPusher.new
|
16
|
+
pusher.push [notification]
|
17
|
+
p notification.results
|
@@ -0,0 +1,82 @@
|
|
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 optional cert [String]. Contents of the PEM encoded certificate
|
37
|
+
# @return [Array]. The response of post
|
38
|
+
# (http://msdn.microsoft.com/pt-br/library/windows/apps/ff941099)
|
39
|
+
def self.post(n, cert = nil)
|
40
|
+
headers = build_headers(n.data[:type], n.data[:delay])
|
41
|
+
body = n.as_mpns_xml
|
42
|
+
responses = []
|
43
|
+
n.each_device do |url|
|
44
|
+
http = Net::HTTP.new url.host, url.port
|
45
|
+
if cert && url.scheme == 'https'
|
46
|
+
http.use_ssl = true
|
47
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
48
|
+
http.ca_file = cert
|
49
|
+
end
|
50
|
+
response = http.post url.path, body, headers
|
51
|
+
responses << { device_url: url.to_s, headers: extract_headers(response), code: response.code.to_i }
|
52
|
+
end
|
53
|
+
MPNSResponse.new responses
|
54
|
+
end
|
55
|
+
|
56
|
+
# Build Header based on type and delay
|
57
|
+
#
|
58
|
+
# @param type [Symbol]. The type of notification
|
59
|
+
# @param delay [Symbol]. The delay to be used
|
60
|
+
# @return [Hash]. Correct delay based on notification type
|
61
|
+
def self.build_headers(type, delay)
|
62
|
+
headers = {
|
63
|
+
CONTENT_TYPE_HEADER => XML_CONTENT_TYPE,
|
64
|
+
X_NOTIFICATION_CLASS => "#{(BASEBATCH[type] + (BATCHADDS[delay] || 0))}"
|
65
|
+
}
|
66
|
+
headers[X_WINDOWSPHONE_TARGET] = WP_TARGETS[type] unless type == :raw
|
67
|
+
headers
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extract headers from response
|
71
|
+
# @param response [Net::HTTPResponse]. HTTP response for request
|
72
|
+
#
|
73
|
+
# @return [Hash]. Hash with headers with case-insensitive keys and string values
|
74
|
+
def self.extract_headers(response)
|
75
|
+
headers = {}
|
76
|
+
response.each_header { |k, v| headers[k] = v }
|
77
|
+
headers
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,96 @@
|
|
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
|
+
|
11
|
+
# @return [MPNSResponse]. MPNSResponse with the results from sending this notification.
|
12
|
+
attr_accessor :results
|
13
|
+
|
14
|
+
# @return [Hash]. Payload to send.
|
15
|
+
# Toast :title => a bold message
|
16
|
+
# :message => the small message
|
17
|
+
# :param => a string parameter that is passed to the app
|
18
|
+
# Tile :image => a new image for the tile
|
19
|
+
# :count => a number to show on the tile
|
20
|
+
# :title => the new title of the tile
|
21
|
+
# :back_image => an image for the back of the tile
|
22
|
+
# :back_title => a title on the back of the tile
|
23
|
+
# :back_content => some content (text) for the back
|
24
|
+
# Raw :message => the full Hash message body
|
25
|
+
attr_reader :data
|
26
|
+
|
27
|
+
# @return [Array]. Array with the receiver's MPNS device URLs.
|
28
|
+
attr_reader :device_urls
|
29
|
+
|
30
|
+
# Initializes the notification
|
31
|
+
#
|
32
|
+
# @param [Array]. Array with the receiver's device urls.
|
33
|
+
# @param [Hash]. Payload to send.
|
34
|
+
# Toast :title => a bold message
|
35
|
+
# :message => the small message
|
36
|
+
# :param => a string parameter that is passed to the app
|
37
|
+
# Tile :image => a new image for the tile
|
38
|
+
# :count => a number to show on the tile
|
39
|
+
# :title => the new title of the tile
|
40
|
+
# :back_image => an image for the back of the tile
|
41
|
+
# :back_title => a title on the back of the tile
|
42
|
+
# :back_content => some content (text) for the back
|
43
|
+
# Raw :message => the full XML message body
|
44
|
+
def initialize(device_urls, data)
|
45
|
+
@device_urls = device_urls
|
46
|
+
@data = data
|
47
|
+
@data[:type] ||= :raw
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# @return [String]. The GCM's XML format for the payload to send.
|
52
|
+
# (http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202945)
|
53
|
+
def as_mpns_xml
|
54
|
+
xml = Builder::XmlMarkup.new
|
55
|
+
xml.instruct!
|
56
|
+
if data[:type] != :raw
|
57
|
+
xml.tag!('wp:Notification', 'xmlns:wp' => 'WPNotification') do
|
58
|
+
case data[:type]
|
59
|
+
when :toast
|
60
|
+
xml.tag!('wp:Toast') do
|
61
|
+
xml.tag!('wp:Text1') { xml.text!(data[:title]) }
|
62
|
+
xml.tag!('wp:Text2') { xml.text!(data[:message]) }
|
63
|
+
xml.tag!('wp:Param') { xml.text!(data[:param]) } if data[:param]
|
64
|
+
end
|
65
|
+
when :tile
|
66
|
+
xml.tag!('wp:Tile') do
|
67
|
+
xml.tag!('wp:BackgroundImage') { xml.text!(data[:image]) } if data[:image]
|
68
|
+
xml.tag!('wp:Count') { xml.text!(data[:count].to_s) } if data[:count]
|
69
|
+
xml.tag!('wp:Title') { xml.text!(data[:title]) } if data[:title]
|
70
|
+
xml.tag!('wp:BackBackgroundImage') { xml.text!(data[:back_image]) } if data[:back_image]
|
71
|
+
xml.tag!('wp:BackTitle') { xml.text!(data[:back_title]) } if data[:back_title]
|
72
|
+
xml.tag!('wp:BackContent') { xml.text!(data[:back_content]) } if data[:back_content]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
else
|
77
|
+
xml.root { build_hash(xml, data[:message]) }
|
78
|
+
end
|
79
|
+
xml.target!
|
80
|
+
end
|
81
|
+
|
82
|
+
def each_device
|
83
|
+
@device_urls.each do |url|
|
84
|
+
yield(URI.parse url)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_hash(xml, options)
|
89
|
+
options.each do |k, v|
|
90
|
+
xml.tag!(k.to_s) { v.is_a?(Hash) ? build_hash(xml, v) : xml.text!(v.to_s) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module RubyPushNotifications
|
3
|
+
module MPNS
|
4
|
+
|
5
|
+
# This class is responsible for sending notifications to the MPNS service.
|
6
|
+
#
|
7
|
+
class MPNSPusher
|
8
|
+
|
9
|
+
# Initializes the MPNSPusher
|
10
|
+
#
|
11
|
+
# @param certificate [String]. The PEM encoded MPNS certificate.
|
12
|
+
# (http://msdn.microsoft.com/pt-br/library/windows/apps/ff941099)
|
13
|
+
def initialize(certificate = nil)
|
14
|
+
@certificate = certificate
|
15
|
+
end
|
16
|
+
|
17
|
+
# Actually pushes the given notifications.
|
18
|
+
# Assigns every notification an array with the result of each
|
19
|
+
# individual notification.
|
20
|
+
#
|
21
|
+
# @param notifications [Array]. Array of MPNSNotification to send.
|
22
|
+
def push(notifications)
|
23
|
+
notifications.each do |notif|
|
24
|
+
notif.results = MPNSConnection.post notif, @certificate
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
|
2
|
+
module RubyPushNotifications
|
3
|
+
module MPNS
|
4
|
+
|
5
|
+
# This class encapsulates a response received from the MPNS service
|
6
|
+
# and helps parsing and understanding the received messages/codes.
|
7
|
+
#
|
8
|
+
class MPNSResponse
|
9
|
+
|
10
|
+
# @return [Integer] the number of successfully sent notifications
|
11
|
+
attr_reader :success
|
12
|
+
|
13
|
+
# @return [Integer] the number of failed notifications
|
14
|
+
attr_reader :failed
|
15
|
+
|
16
|
+
# @return [Array] Array of a MPNSResult for every receiver of the notification
|
17
|
+
# sent indicating the result of the operation.
|
18
|
+
attr_reader :results
|
19
|
+
|
20
|
+
# Initializes the MPNSResponse and runs response parsing
|
21
|
+
#
|
22
|
+
# @param responses [Array]. Array with device_urls and http responses
|
23
|
+
def initialize(responses)
|
24
|
+
parse_response responses
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
(other.is_a?(MPNSResponse) &&
|
29
|
+
success == other.success &&
|
30
|
+
failed == other.failed &&
|
31
|
+
results == other.results) || super(other)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Parses the response extracting counts for successful, failed messages.
|
37
|
+
# Also creates the results array assigning a MPNSResult subclass for each
|
38
|
+
# device URL the notification was sent to.
|
39
|
+
#
|
40
|
+
# @param responses [Array]. Array of hash responses
|
41
|
+
def parse_response(responses)
|
42
|
+
@success = responses.count { |response| response[:code] == 200 }
|
43
|
+
@failed = responses.count { |response| response[:code] != 200 }
|
44
|
+
@results = responses.map do |response|
|
45
|
+
mpns_result_for response[:code],
|
46
|
+
response[:device_url],
|
47
|
+
response[:headers]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Factory method that, for each MPNS result object assigns a MPNSResult
|
52
|
+
# subclass.
|
53
|
+
#
|
54
|
+
# @param code [Integer]. The HTTP status code received
|
55
|
+
# @param device_url [String]. The receiver's MPNS device url.
|
56
|
+
# @param headers [Hash]. The HTTP headers received.
|
57
|
+
# @return [MPNSResult]. Corresponding MPNSResult subclass
|
58
|
+
def mpns_result_for(code, device_url, headers)
|
59
|
+
case code
|
60
|
+
when 200
|
61
|
+
MPNSResultOK.new device_url, headers
|
62
|
+
when 400
|
63
|
+
MalformedMPNSResultError.new device_url
|
64
|
+
when 401
|
65
|
+
MPNSAuthError.new device_url
|
66
|
+
when 404
|
67
|
+
MPNSInvalidError.new device_url, headers
|
68
|
+
when 406
|
69
|
+
MPNSLimitError.new device_url, headers
|
70
|
+
when 412
|
71
|
+
MPNSPreConditionError.new device_url, headers
|
72
|
+
when 500..599
|
73
|
+
MPNSInternalError.new device_url
|
74
|
+
else
|
75
|
+
MPNSResultError.new device_url
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
|
2
|
+
module RubyPushNotifications
|
3
|
+
module MPNS
|
4
|
+
# Class that encapsulates the result of a single sent notification to a single
|
5
|
+
# Device URL
|
6
|
+
# (https://msdn.microsoft.com/en-us/library/windows/apps/ff941100%28v=vs.105%29.aspx)
|
7
|
+
class MPNSResult
|
8
|
+
# @return [String]. Receiver MPNS device URL.
|
9
|
+
attr_accessor :device_url
|
10
|
+
|
11
|
+
# @private X-NotificationStatus HTTP Header string
|
12
|
+
X_NOTIFICATION_STATUS = 'x-notificationstatus'
|
13
|
+
|
14
|
+
# @private X-DeviceConnectionStatus HTTP Header string
|
15
|
+
X_DEVICE_CONNECTION_STATUS = 'x-deviceconnectionstatus'
|
16
|
+
|
17
|
+
# @private X-SubscriptionStatus HTTP Header string
|
18
|
+
X_SUBSCRIPTION_STATUS = 'x-subscriptionstatus'
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
# Indicates that the notification was successfully sent to the corresponding
|
23
|
+
# device URL
|
24
|
+
class MPNSResultOK < MPNSResult
|
25
|
+
# @return [String]. The status of the notification received
|
26
|
+
# by the Microsoft Push Notification Service.
|
27
|
+
attr_accessor :notification_status
|
28
|
+
|
29
|
+
# @return [String]. The connection status of the device.
|
30
|
+
attr_accessor :device_connection_status
|
31
|
+
|
32
|
+
# @return [String]. The subscription status.
|
33
|
+
attr_accessor :subscription_status
|
34
|
+
|
35
|
+
def initialize(device_url, headers = nil)
|
36
|
+
@device_url = device_url
|
37
|
+
@notification_status = headers[X_NOTIFICATION_STATUS]
|
38
|
+
@device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
|
39
|
+
@subscription_status = headers[X_SUBSCRIPTION_STATUS]
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
(other.is_a?(MPNSResultOK) &&
|
44
|
+
device_url == other.device_url &&
|
45
|
+
notification_status == other.notification_status &&
|
46
|
+
device_connection_status == other.device_connection_status &&
|
47
|
+
subscription_status == other.subscription_status) || super(other)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# This error occurs when the cloud service sends a notification
|
52
|
+
# request with a bad XML document or malformed notification URI.
|
53
|
+
class MalformedMPNSResultError < MPNSResult
|
54
|
+
def initialize(device_url)
|
55
|
+
@device_url = device_url
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
(other.is_a?(MalformedMPNSResultError) &&
|
60
|
+
device_url == other.device_url) || super(other)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sending this notification is unauthorized.
|
65
|
+
class MPNSAuthError < MPNSResult
|
66
|
+
def initialize(device_url)
|
67
|
+
@device_url = device_url
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other)
|
71
|
+
(other.is_a?(MPNSAuthError) &&
|
72
|
+
device_url == other.device_url) || super(other)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# The subscription is invalid and is not present on the Push Notification Service.
|
77
|
+
class MPNSInvalidError < MPNSResult
|
78
|
+
# @return [String]. The status of the notification received
|
79
|
+
# by the Microsoft Push Notification Service.
|
80
|
+
attr_accessor :notification_status
|
81
|
+
|
82
|
+
# @return [String]. The connection status of the device.
|
83
|
+
attr_accessor :device_connection_status
|
84
|
+
|
85
|
+
# @return [String]. The subscription status.
|
86
|
+
attr_accessor :subscription_status
|
87
|
+
|
88
|
+
def initialize(device_url, headers)
|
89
|
+
@device_url = device_url
|
90
|
+
@notification_status = headers[X_NOTIFICATION_STATUS]
|
91
|
+
@device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
|
92
|
+
@subscription_status = headers[X_SUBSCRIPTION_STATUS]
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
(other.is_a?(MPNSInvalidError) &&
|
97
|
+
device_url == other.device_url &&
|
98
|
+
notification_status == other.notification_status &&
|
99
|
+
device_connection_status == other.device_connection_status &&
|
100
|
+
subscription_status == other.subscription_status) || super(other)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# This error occurs when an unauthenticated cloud service has reached
|
105
|
+
# the per-day throttling limit for a subscription,
|
106
|
+
# or when a cloud service (authenticated or unauthenticated)
|
107
|
+
# has sent too many notifications per second.
|
108
|
+
class MPNSLimitError < MPNSResult
|
109
|
+
# @return [String]. The status of the notification received
|
110
|
+
# by the Microsoft Push Notification Service.
|
111
|
+
attr_accessor :notification_status
|
112
|
+
|
113
|
+
# @return [String]. The connection status of the device.
|
114
|
+
attr_accessor :device_connection_status
|
115
|
+
|
116
|
+
# @return [String]. The subscription status.
|
117
|
+
attr_accessor :subscription_status
|
118
|
+
|
119
|
+
def initialize(device_url, headers)
|
120
|
+
@device_url = device_url
|
121
|
+
@notification_status = headers[X_NOTIFICATION_STATUS]
|
122
|
+
@device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
|
123
|
+
@subscription_status = headers[X_SUBSCRIPTION_STATUS]
|
124
|
+
end
|
125
|
+
|
126
|
+
def ==(other)
|
127
|
+
(other.is_a?(MPNSLimitError) &&
|
128
|
+
device_url == other.device_url &&
|
129
|
+
notification_status == other.notification_status &&
|
130
|
+
device_connection_status == other.device_connection_status &&
|
131
|
+
subscription_status == other.subscription_status) || super(other)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# The device is in a disconnected state.
|
136
|
+
class MPNSPreConditionError < MPNSResult
|
137
|
+
# @return [String]. The status of the notification received
|
138
|
+
# by the Microsoft Push Notification Service.
|
139
|
+
attr_accessor :notification_status
|
140
|
+
|
141
|
+
# @return [String]. The connection status of the device.
|
142
|
+
attr_accessor :device_connection_status
|
143
|
+
|
144
|
+
def initialize(device_url, headers)
|
145
|
+
@device_url = device_url
|
146
|
+
@notification_status = headers[X_NOTIFICATION_STATUS]
|
147
|
+
@device_connection_status = headers[X_DEVICE_CONNECTION_STATUS]
|
148
|
+
end
|
149
|
+
|
150
|
+
def ==(other)
|
151
|
+
(other.is_a?(MPNSPreConditionError) &&
|
152
|
+
device_url == other.device_url &&
|
153
|
+
notification_status == other.notification_status &&
|
154
|
+
device_connection_status == other.device_connection_status) || super(other)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# The Push Notification Service is unable to process the request.
|
159
|
+
class MPNSInternalError < MPNSResult
|
160
|
+
def initialize(device_url)
|
161
|
+
@device_url = device_url
|
162
|
+
end
|
163
|
+
|
164
|
+
def ==(other)
|
165
|
+
(other.is_a?(MPNSInternalError) &&
|
166
|
+
device_url == other.device_url) || super(other)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Unknow Error
|
171
|
+
class MPNSResultError < MPNSResult
|
172
|
+
def initialize(device_url)
|
173
|
+
@device_url = device_url
|
174
|
+
end
|
175
|
+
|
176
|
+
def ==(other)
|
177
|
+
(other.is_a?(MPNSResultError) &&
|
178
|
+
device_url == other.device_url) || super(other)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
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'
|
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
21
|
spec.require_paths = ['lib']
|
22
22
|
|
23
|
+
spec.add_dependency 'builder', '~> 3.2'
|
24
|
+
|
23
25
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
24
26
|
spec.add_development_dependency 'rake', '~> 10.4'
|
25
27
|
spec.add_development_dependency 'rspec', '~> 3.2'
|
@@ -12,4 +12,12 @@ FactoryGirl.define do
|
|
12
12
|
|
13
13
|
initialize_with { new registration_ids, data }
|
14
14
|
end
|
15
|
+
|
16
|
+
factory :mpns_notification, class: 'RubyPushNotifications::MPNS::MPNSNotification' do
|
17
|
+
device_urls { [generate(:mpns_device_url)] }
|
18
|
+
data message: { value1: 'hello' }
|
19
|
+
|
20
|
+
initialize_with { new device_urls, data }
|
21
|
+
end
|
22
|
+
|
15
23
|
end
|
data/spec/factories.rb
CHANGED
@@ -7,4 +7,9 @@ FactoryGirl.define do
|
|
7
7
|
sequence :gcm_registration_id do |i|
|
8
8
|
"APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ#{i}"
|
9
9
|
end
|
10
|
+
|
11
|
+
sequence :mpns_device_url do |i|
|
12
|
+
"http://s.notify.live.net/u/1/bn1/HmQAAACP-0esPuxBSkzBNNXH4W0lV3lK-stEw6eRfpXX39uYbM7IwehXOTO9pRBjaaGECWOdD_7x5j5U4w4iXG4hGxer/d2luZG93c3Bob25lZGVmYXVsdA/EMDhx32Q5BG0DWnZpuVX1g/kRFAu0-jnhMQ-HG94rXzrbb0wQk#{i}"
|
13
|
+
end
|
14
|
+
|
10
15
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module RubyPushNotifications
|
3
|
+
module MPNS
|
4
|
+
describe MPNSConnection do
|
5
|
+
|
6
|
+
describe '::post' do
|
7
|
+
|
8
|
+
let(:body) { 'abc' }
|
9
|
+
let(:headers) {
|
10
|
+
{
|
11
|
+
'x-notificationstatus' => 'Received',
|
12
|
+
'x-deviceconnectionstatus' => 'Connected',
|
13
|
+
'x-subscriptionstatus' => 'Active'
|
14
|
+
}
|
15
|
+
}
|
16
|
+
let(:device_urls) { [generate(:mpns_device_url)] }
|
17
|
+
let(:toast_data) { { title: 'Title', message: 'Hello MPNS World!', type: :toast } }
|
18
|
+
let(:toast_notification) { build :mpns_notification, device_urls: device_urls, data: toast_data }
|
19
|
+
|
20
|
+
before do
|
21
|
+
stub_request(:post, %r{s.notify.live.net}).
|
22
|
+
to_return status: [200, 'OK'], body: body, headers: headers
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'runs the right request' do
|
26
|
+
MPNSConnection.post toast_notification
|
27
|
+
|
28
|
+
expect(WebMock).
|
29
|
+
to have_requested(:post, toast_notification.device_urls[0]).
|
30
|
+
with(body: toast_notification.as_mpns_xml, headers: { 'Content-Type' => 'text/xml', 'X-NotificationClass' => '2', 'X-WindowsPhone-Target' => :toast}).
|
31
|
+
once
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns the response encapsulated in a Hash object' do
|
35
|
+
responses = [
|
36
|
+
{ device_url: toast_notification.device_urls[0],
|
37
|
+
headers: headers,
|
38
|
+
code: 200
|
39
|
+
}
|
40
|
+
]
|
41
|
+
expect(MPNSConnection.post toast_notification).to eq MPNSResponse.new(responses)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|