ruby-push-notifications 1.2.0 → 1.3.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/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.md +4 -3
- data/examples/fcm.rb +45 -0
- data/lib/ruby-push-notifications.rb +3 -0
- data/lib/ruby-push-notifications/fcm.rb +8 -0
- data/lib/ruby-push-notifications/fcm/fcm_connection.rb +57 -0
- data/lib/ruby-push-notifications/fcm/fcm_error.rb +18 -0
- data/lib/ruby-push-notifications/fcm/fcm_notification.rb +36 -0
- data/lib/ruby-push-notifications/fcm/fcm_pusher.rb +36 -0
- data/lib/ruby-push-notifications/fcm/fcm_response.rb +90 -0
- data/lib/ruby-push-notifications/fcm/fcm_result.rb +53 -0
- data/ruby-push-notifications.gemspec +3 -2
- data/spec/factories.rb +6 -0
- data/spec/factories/notifications.rb +41 -4
- data/spec/ruby-push-notifications/fcm/fcm_connection_spec.rb +56 -0
- data/spec/ruby-push-notifications/fcm/fcm_notification_spec.rb +69 -0
- data/spec/ruby-push-notifications/fcm/fcm_pusher_spec.rb +43 -0
- data/spec/ruby-push-notifications/fcm/fcm_response_spec.rb +88 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/factory_girl.rb +2 -0
- data/spec/support/results_shared_examples.rb +2 -0
- metadata +34 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d72d6fd41a6da51fd7fd7faf2cd56774da3126e
|
4
|
+
data.tar.gz: e697f8afde5d62663cf3c1dc3b84599ff770b341
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffd77ae1e4711f547a1a06adab7573e63d9a3cd136a8ead4349d2e2d963b4fb7ad899803d455ce6f8ec34cdd5b79d578ce21c2d8915acd3660759e04ca5ae09c
|
7
|
+
data.tar.gz: aa82f2753c87135fba244eb6397a1c85a72827aeb0ca53e21ee8b81067f0a0d2132d3d0cb0f3ac43d89e0ff7a291dde143316a0cd6193eb479db2c32bbf1de16
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -37,9 +37,10 @@ Or install it yourself as:
|
|
37
37
|
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
|
-
2. [Google Android example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/gcm.rb)
|
41
|
-
3. [
|
42
|
-
4. [Windows Phone(
|
40
|
+
2. [Google Android example (GCM)](https://github.com/calonso/ruby-push-notifications/tree/master/examples/gcm.rb)
|
41
|
+
3. [Google Android example (FCM)](https://github.com/calonso/ruby-push-notifications/tree/master/examples/fcm.rb)
|
42
|
+
4. [Windows Phone(MPNS) example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/mpns.rb)
|
43
|
+
5. [Windows Phone(WNS) example](https://github.com/calonso/ruby-push-notifications/tree/master/examples/wns.rb)
|
43
44
|
|
44
45
|
## Pending tasks
|
45
46
|
|
data/examples/fcm.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'ruby-push-notifications'
|
6
|
+
|
7
|
+
registration_ids = [
|
8
|
+
'First registration id here',
|
9
|
+
'Second registration id here'
|
10
|
+
]
|
11
|
+
|
12
|
+
# Example JSON payload
|
13
|
+
payload = {
|
14
|
+
"notification": {
|
15
|
+
"title": 'Greetings Test',
|
16
|
+
"body": 'Remember to test! ALOT!',
|
17
|
+
"forceStart": 1,
|
18
|
+
"sound": 'default',
|
19
|
+
"vibrate": 'true',
|
20
|
+
"icon": 'fcm_push_icon'
|
21
|
+
},
|
22
|
+
"android": {
|
23
|
+
"priority": 'high',
|
24
|
+
"vibrate": 'true'
|
25
|
+
},
|
26
|
+
"data": {
|
27
|
+
"url": 'https://www.google.com'
|
28
|
+
},
|
29
|
+
"webpush": {
|
30
|
+
"headers": {
|
31
|
+
"TTL": '60'
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"priority": 'high'
|
35
|
+
}
|
36
|
+
|
37
|
+
notification = RubyPushNotifications::FCM::FCMNotification.new registration_ids, payload
|
38
|
+
|
39
|
+
pusher = RubyPushNotifications::FCM::FCMPusher.new "Your app's FCM key"
|
40
|
+
|
41
|
+
pusher.push [notification]
|
42
|
+
p 'Notification sending results:'
|
43
|
+
p "Success: #{notification.success}, Failed: #{notification.failed}"
|
44
|
+
p 'Details:'
|
45
|
+
p notification.individual_results
|
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
require 'ruby-push-notifications/notification_results_manager'
|
3
5
|
require 'ruby-push-notifications/apns'
|
4
6
|
require 'ruby-push-notifications/gcm'
|
7
|
+
require 'ruby-push-notifications/fcm'
|
5
8
|
require 'ruby-push-notifications/mpns'
|
6
9
|
require 'ruby-push-notifications/wns'
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ruby-push-notifications/fcm/fcm_connection'
|
4
|
+
require 'ruby-push-notifications/fcm/fcm_notification'
|
5
|
+
require 'ruby-push-notifications/fcm/fcm_pusher'
|
6
|
+
require 'ruby-push-notifications/fcm/fcm_response'
|
7
|
+
require 'ruby-push-notifications/fcm/fcm_error'
|
8
|
+
require 'ruby-push-notifications/fcm/fcm_result'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'net/https'
|
5
|
+
|
6
|
+
module RubyPushNotifications
|
7
|
+
module FCM
|
8
|
+
# Encapsulates a connection to the FCM service
|
9
|
+
# Responsible for final connection with the service.
|
10
|
+
#
|
11
|
+
# @author Carlos Alonso
|
12
|
+
class FCMConnection
|
13
|
+
|
14
|
+
# @private The URL of the Android FCM endpoint
|
15
|
+
# Credits: https://github.com/calos0921 - for this url change to FCM std
|
16
|
+
FCM_URL = 'https://fcm.googleapis.com/fcm/send'
|
17
|
+
|
18
|
+
# @private Content-Type HTTP Header string
|
19
|
+
CONTENT_TYPE_HEADER = 'Content-Type'
|
20
|
+
|
21
|
+
# @private Application/JSON content type
|
22
|
+
JSON_CONTENT_TYPE = 'application/json'
|
23
|
+
|
24
|
+
# @private Authorization HTTP Header String
|
25
|
+
AUTHORIZATION_HEADER = 'Authorization'
|
26
|
+
|
27
|
+
# Issues a POST request to the FCM send endpoint to
|
28
|
+
# submit the given notifications.
|
29
|
+
#
|
30
|
+
# @param notification [String]. The text to POST
|
31
|
+
# @param key [String]. The FCM sender id to use
|
32
|
+
# (https://developer.android.com/google/fcm/fcm.html#senderid)
|
33
|
+
# @param options [Hash] optional. Options for #post. Currently supports:
|
34
|
+
# * url [String]: URL of the FCM endpoint. Defaults to the official FCM URL.
|
35
|
+
# * open_timeout [Integer]: Number of seconds to wait for the connection to open. Defaults to 30.
|
36
|
+
# * read_timeout [Integer]: Number of seconds to wait for one block to be read. Defaults to 30.
|
37
|
+
# @return [FCMResponse]. The FCMResponse that encapsulates the received response
|
38
|
+
def self.post(notification, key, options = {})
|
39
|
+
headers = {
|
40
|
+
CONTENT_TYPE_HEADER => JSON_CONTENT_TYPE,
|
41
|
+
AUTHORIZATION_HEADER => "key=#{key}"
|
42
|
+
}
|
43
|
+
|
44
|
+
url = URI.parse options.fetch(:url, FCM_URL)
|
45
|
+
http = Net::HTTP.new url.host, url.port
|
46
|
+
http.use_ssl = url.scheme == 'https'
|
47
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
48
|
+
http.open_timeout = options.fetch(:open_timeout, 30)
|
49
|
+
http.read_timeout = options.fetch(:read_timeout, 30)
|
50
|
+
|
51
|
+
response = http.post url.path, notification, headers
|
52
|
+
|
53
|
+
FCMResponse.new response.code.to_i, response.body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
# Base class for all FCM related errors
|
6
|
+
#
|
7
|
+
# @author Carlos Alonso
|
8
|
+
class FCMError < StandardError; end
|
9
|
+
|
10
|
+
class MalformedFCMJSONError < FCMError; end
|
11
|
+
|
12
|
+
class FCMAuthError < FCMError; end
|
13
|
+
|
14
|
+
class FCMInternalError < FCMError; end
|
15
|
+
|
16
|
+
class UnexpectedFCMResponseError < FCMError; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
# Encapsulates a FCM Notification.
|
6
|
+
# By default only Required fields are set.
|
7
|
+
# (https://developer.android.com/google/fcm/server-ref.html#send-downstream)
|
8
|
+
#
|
9
|
+
# @author Carlos Alonso
|
10
|
+
class FCMNotification
|
11
|
+
attr_reader :registration_ids, :data
|
12
|
+
|
13
|
+
include RubyPushNotifications::NotificationResultsManager
|
14
|
+
|
15
|
+
# Initializes the notification
|
16
|
+
#
|
17
|
+
# @param [Array]. Array with the receiver's FCM registration ids.
|
18
|
+
# @param [Hash]. Payload to send.
|
19
|
+
def initialize(registration_ids, data)
|
20
|
+
@registration_ids = registration_ids
|
21
|
+
@data = data
|
22
|
+
end
|
23
|
+
|
24
|
+
def make_payload
|
25
|
+
{ registration_ids: @registration_ids }.merge(@data)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String]. The FCM's JSON format for the payload to send.
|
29
|
+
# (https://developer.android.com/google/fcm/server-ref.html#send-downstream)
|
30
|
+
# Credits: https://github.com/calos0921 - for this url change to FCM std
|
31
|
+
def as_fcm_json
|
32
|
+
JSON.dump(make_payload)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
|
6
|
+
# This class is responsible for sending notifications to the FCM service.
|
7
|
+
#
|
8
|
+
# @author Carlos Alonso
|
9
|
+
class FCMPusher
|
10
|
+
|
11
|
+
# Initializes the FCMPusher
|
12
|
+
#
|
13
|
+
# @param key [String]. FCM sender id to use
|
14
|
+
# ((https://developer.android.com/google/fcm/fcm.html#senderid))
|
15
|
+
# @param options [Hash] optional. Options for FCMPusher. Currently supports:
|
16
|
+
# * url [String]: URL of the FCM endpoint. Defaults to the official FCM URL.
|
17
|
+
# * open_timeout [Integer]: Number of seconds to wait for the connection to open. Defaults to 30.
|
18
|
+
# * read_timeout [Integer]: Number of seconds to wait for one block to be read. Defaults to 30.
|
19
|
+
def initialize(key, options = {})
|
20
|
+
@key = key
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Actually pushes the given notifications.
|
25
|
+
# Assigns every notification an array with the result of each
|
26
|
+
# individual notification.
|
27
|
+
#
|
28
|
+
# @param notifications [Array]. Array of FCMNotification to send.
|
29
|
+
def push(notifications)
|
30
|
+
notifications.each do |notif|
|
31
|
+
notif.results = FCMConnection.post notif.as_fcm_json, @key, @options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
|
6
|
+
# This class encapsulates a response received from the FCM service
|
7
|
+
# and helps parsing and understanding the received meesages/codes.
|
8
|
+
#
|
9
|
+
# @author Carlos Alonso
|
10
|
+
class FCMResponse
|
11
|
+
|
12
|
+
# @return [Integer] the number of successfully sent notifications
|
13
|
+
attr_reader :success
|
14
|
+
|
15
|
+
# @return [Integer] the number of failed notifications
|
16
|
+
attr_reader :failed
|
17
|
+
|
18
|
+
# @return [Integer] the number of received canonical IDS
|
19
|
+
# (https://developer.android.com/google/fcm/server-ref.html#table4)
|
20
|
+
attr_reader :canonical_ids
|
21
|
+
|
22
|
+
# @return [Array] Array of a FCMResult for every receiver of the notification
|
23
|
+
# sent indicating the result of the operation.
|
24
|
+
attr_reader :results
|
25
|
+
alias_method :individual_results, :results
|
26
|
+
|
27
|
+
# Initializes the FCMResponse and runs response parsing
|
28
|
+
#
|
29
|
+
# @param code [Integer]. The HTTP status code received
|
30
|
+
# @param body [String]. The response body received.
|
31
|
+
# @raise MalformedFCMJsonError if code == 400 Bad Request
|
32
|
+
# @raise FCMAuthError if code == 401 Unauthorized
|
33
|
+
# @raise FCMInternalError if code == 5xx
|
34
|
+
# @raise UnexpectedFCMResponseError if code != 200
|
35
|
+
def initialize(code, body)
|
36
|
+
case code
|
37
|
+
when 200
|
38
|
+
parse_response body
|
39
|
+
when 400
|
40
|
+
raise MalformedFCMJSONError, body
|
41
|
+
when 401
|
42
|
+
raise FCMAuthError, body
|
43
|
+
when 500..599
|
44
|
+
raise FCMInternalError, body
|
45
|
+
else
|
46
|
+
raise UnexpectedFCMResponseError, code
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
(other.is_a?(FCMResponse) &&
|
52
|
+
success == other.success &&
|
53
|
+
failed == other.failed &&
|
54
|
+
canonical_ids == other.canonical_ids &&
|
55
|
+
results == other.results) || super(other)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Parses the response extracting counts for successful, failed and
|
61
|
+
# containing canonical ID messages.
|
62
|
+
# Also creates the results array assigning a FCMResult subclass for each
|
63
|
+
# registration ID the notification was sent to.
|
64
|
+
#
|
65
|
+
# @param body [String]. The response body
|
66
|
+
def parse_response(body)
|
67
|
+
json = JSON.parse body, symbolize_names: true
|
68
|
+
@success = json[:success]
|
69
|
+
@failed = json[:failure]
|
70
|
+
@canonical_ids = json[:canonical_ids]
|
71
|
+
@results = (json[:results] || []).map { |result| fcm_result_for result }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Factory method that, for each FCM result object assigns a FCMResult
|
75
|
+
# subclass.
|
76
|
+
#
|
77
|
+
# @param result [Hash]. FCM Result parsed hash
|
78
|
+
# @return [FCMResult]. Corresponding FCMResult subclass
|
79
|
+
def fcm_result_for(result)
|
80
|
+
if canonical_id = result[:registration_id]
|
81
|
+
FCMCanonicalIDResult.new canonical_id
|
82
|
+
elsif error = result[:error]
|
83
|
+
FCMResultError.new error
|
84
|
+
else
|
85
|
+
FCMResultOK.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
# Class that encapsulates the result of a single sent notification to a single
|
6
|
+
# Registration ID
|
7
|
+
# (https://developer.android.com/google/fcm/server-ref.html#table4)
|
8
|
+
# @author Carlos Alonso
|
9
|
+
class FCMResult; end
|
10
|
+
|
11
|
+
# Indicates that the notification was successfully sent to the corresponding
|
12
|
+
# registration ID
|
13
|
+
class FCMResultOK < FCMResult
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.is_a?(FCMResultOK) || super(other)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Indicates that the notification was successfully sent to the corresponding
|
21
|
+
# registration ID but FCM sent a canonical ID for it, so the received canonical
|
22
|
+
# ID should be used as registration ID ASAP.
|
23
|
+
class FCMCanonicalIDResult < FCMResult
|
24
|
+
|
25
|
+
# @return [String]. The suggested canonical ID from FCM
|
26
|
+
attr_reader :canonical_id
|
27
|
+
|
28
|
+
def initialize(canonical_id)
|
29
|
+
@canonical_id = canonical_id
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
(other.is_a?(FCMCanonicalIDResult) && @canonical_id == other.canonical_id) ||
|
34
|
+
super(other)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# An error occurred sending the notification to the registration ID.
|
39
|
+
class FCMResultError < FCMResult
|
40
|
+
|
41
|
+
# @return [String]. The error sent by FCM
|
42
|
+
attr_accessor :error
|
43
|
+
|
44
|
+
def initialize(error)
|
45
|
+
@error = error
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
(other.is_a?(FCMResultError) && @error == other.error) || super(other)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,9 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
Gem::Specification.new do |spec|
|
3
3
|
spec.name = "ruby-push-notifications"
|
4
|
-
spec.version = "1.
|
4
|
+
spec.version = "1.3.0"
|
5
5
|
spec.authors = ['Carlos Alonso']
|
6
|
-
spec.email = ['info@mrcalonso.com']
|
7
6
|
spec.summary = %q{iOS, Android and Windows Phone Push Notifications made easy!}
|
8
7
|
spec.description = %q{Easy to use gem to send iOS, Android and Windows Phone Push notifications}
|
9
8
|
spec.homepage = 'https://github.com/calonso/ruby-push-notifications'
|
@@ -19,7 +18,9 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.add_dependency 'builder', '~> 3.0'
|
20
19
|
|
21
20
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
21
|
+
# spec.add_development_dependency 'bundler', '~> 2.0'
|
22
22
|
spec.add_development_dependency 'rake', '~> 10.4'
|
23
|
+
spec.add_development_dependency 'simplecov', '~> 0.12.0'
|
23
24
|
spec.add_development_dependency 'rspec', '~> 3.2'
|
24
25
|
spec.add_development_dependency 'factory_girl', '~> 4.0'
|
25
26
|
spec.add_development_dependency 'webmock', '~> 1.20'
|
data/spec/factories.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
FactoryGirl.define do
|
2
4
|
sequence :apns_token do |i|
|
3
5
|
"ce8be627 2e43e855 16033e24 b4c28922 0eeda487 9c477160 b2545e95 b68b596#{i}"
|
@@ -7,6 +9,10 @@ FactoryGirl.define do
|
|
7
9
|
"APA91bHPRgkF3JUikC4ENAHEeMrd41Zxv3hVZjC9KtT8OvPVGJ-hQMRKRrZuJAEcl7B338qju59zJMjw2DELjzEvxwYv7hH5Ynpc1ODQ0aT4U4OFEeco8ohsN5PjL1iC2dNtk2BAokeMCg2ZXKqpc8FXKmhX94kIxQ#{i}"
|
8
10
|
end
|
9
11
|
|
12
|
+
sequence :fcm_registration_id do |i|
|
13
|
+
"egp-7jEODbM:APA91bGODoU9taMK10FDVvJI_ZhKXHl_GKoLKIDrQ0PTJn5SD2mFOtOiPxeTlkATAYI-eU4oucF6rPsAiXdgu9fFbmuDsprmx_bakCFPvIoN1Axg8SzqtnZpzagc0LZOJxNeZB-VVgcc#{i}"
|
14
|
+
end
|
15
|
+
|
10
16
|
sequence :mpns_device_url do |i|
|
11
17
|
"http://s.notify.live.net/u/1/bn1/HmQAAACP-0esPuxBSkzBNNXH4W0lV3lK-stEw6eRfpXX39uYbM7IwehXOTO9pRBjaaGECWOdD_7x5j5U4w4iXG4hGxer/d2luZG93c3Bob25lZGVmYXVsdA/EMDhx32Q5BG0DWnZpuVX1g/kRFAu0-jnhMQ-HG94rXzrbb0wQk#{i}"
|
12
18
|
end
|
@@ -1,26 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
FactoryGirl.define do
|
2
|
-
factory :apns_notification,
|
4
|
+
factory :apns_notification,
|
5
|
+
class: 'RubyPushNotifications::APNS::APNSNotification' do
|
3
6
|
tokens { [generate(:apns_token)] }
|
4
7
|
data a: 1
|
5
8
|
|
6
9
|
initialize_with { new tokens, data }
|
7
10
|
end
|
8
11
|
|
9
|
-
factory :gcm_notification,
|
12
|
+
factory :gcm_notification,
|
13
|
+
class: 'RubyPushNotifications::GCM::GCMNotification' do
|
10
14
|
registration_ids { [generate(:gcm_registration_id)] }
|
11
15
|
data a: 1
|
12
16
|
|
13
17
|
initialize_with { new registration_ids, data }
|
14
18
|
end
|
15
19
|
|
16
|
-
factory :
|
20
|
+
factory :fcm_notification,
|
21
|
+
class: 'RubyPushNotifications::FCM::FCMNotification' do
|
22
|
+
registration_ids { [generate(:fcm_registration_id)] }
|
23
|
+
data a:
|
24
|
+
{
|
25
|
+
notification: {
|
26
|
+
title: 'Greetings Test',
|
27
|
+
body: 'Remember to test! ALOT!',
|
28
|
+
forceStart: 1,
|
29
|
+
sound: 'default',
|
30
|
+
vibrate: 'true',
|
31
|
+
icon: 'fcm_push_icon'
|
32
|
+
},
|
33
|
+
android: {
|
34
|
+
priority: 'high',
|
35
|
+
vibrate: 'true'
|
36
|
+
},
|
37
|
+
data: {
|
38
|
+
url: 'https://www.google.com'
|
39
|
+
},
|
40
|
+
webpush: {
|
41
|
+
headers: {
|
42
|
+
TTL: '60'
|
43
|
+
}
|
44
|
+
},
|
45
|
+
priority: 'high'
|
46
|
+
}
|
47
|
+
|
48
|
+
initialize_with { new registration_ids, data }
|
49
|
+
end
|
50
|
+
|
51
|
+
factory :mpns_notification,
|
52
|
+
class: 'RubyPushNotifications::MPNS::MPNSNotification' do
|
17
53
|
device_urls { [generate(:mpns_device_url)] }
|
18
54
|
data message: { value1: 'hello' }
|
19
55
|
|
20
56
|
initialize_with { new device_urls, data }
|
21
57
|
end
|
22
58
|
|
23
|
-
factory :wns_notification,
|
59
|
+
factory :wns_notification,
|
60
|
+
class: 'RubyPushNotifications::WNS::WNSNotification' do
|
24
61
|
device_urls { [generate(:wns_device_url)] }
|
25
62
|
data message: { value1: 'hello' }
|
26
63
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
describe FCMConnection do
|
6
|
+
describe '::post' do
|
7
|
+
let(:body) { 'abc' }
|
8
|
+
let(:key) { 'def' }
|
9
|
+
let(:response) { JSON.dump a: 1 }
|
10
|
+
|
11
|
+
before do
|
12
|
+
stub_request(
|
13
|
+
:post,
|
14
|
+
'https://fcm.googleapis.com/fcm/send'
|
15
|
+
).to_return status: [200, 'OK'],
|
16
|
+
body: response
|
17
|
+
end
|
18
|
+
it 'runs the right request' do
|
19
|
+
FCMConnection.post body, key
|
20
|
+
url = 'https://fcm.googleapis.com/fcm/send'
|
21
|
+
headers = { 'Content-Type' => 'application/json',
|
22
|
+
'Authorization' => "key=#{key}" }
|
23
|
+
|
24
|
+
expect(
|
25
|
+
WebMock
|
26
|
+
).to have_requested(:post, url).with(body: body,
|
27
|
+
headers: headers).once
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when :url option is present' do
|
31
|
+
it 'posts data to a custom FCM endpoint' do
|
32
|
+
test_url = 'https://example.com/fcm/send'
|
33
|
+
stub_request(:post, test_url).to_return status: [200, 'OK'],
|
34
|
+
body: response
|
35
|
+
|
36
|
+
FCMConnection.post body, key, url: test_url
|
37
|
+
url = 'https://example.com/fcm/send'
|
38
|
+
headers = { 'Content-Type' => 'application/json',
|
39
|
+
'Authorization' => "key=#{key}" }
|
40
|
+
|
41
|
+
expect(
|
42
|
+
WebMock
|
43
|
+
).to have_requested(:post,
|
44
|
+
url).with(body: body,
|
45
|
+
headers: headers).once
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns the response encapsulated in a FCMResponse object' do
|
50
|
+
expect(FCMConnection.post(body, key)).to eq FCMResponse.new(200,
|
51
|
+
response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
describe FCMNotification do
|
6
|
+
let(:registration_ids) { %w[a] }
|
7
|
+
let(:data) do
|
8
|
+
{
|
9
|
+
a: {
|
10
|
+
notification: {
|
11
|
+
title: 'Greetings Test',
|
12
|
+
body: 'Remember to test! ALOT!',
|
13
|
+
forceStart: 1,
|
14
|
+
sound: 'default',
|
15
|
+
vibrate: 'true',
|
16
|
+
icon: 'fcm_push_icon'
|
17
|
+
},
|
18
|
+
android: {
|
19
|
+
priority: 'high',
|
20
|
+
vibrate: 'true'
|
21
|
+
},
|
22
|
+
data: {
|
23
|
+
url: 'https://www.google.com'
|
24
|
+
},
|
25
|
+
webpush: {
|
26
|
+
headers: {
|
27
|
+
TTL: '60'
|
28
|
+
}
|
29
|
+
},
|
30
|
+
priority: 'high'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:notification) do
|
36
|
+
build :fcm_notification,
|
37
|
+
registration_ids: registration_ids,
|
38
|
+
data: data
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'builds the right fcm json' do
|
42
|
+
test_case = { registration_ids: registration_ids }.merge(data)
|
43
|
+
expect(notification.as_fcm_json).to include(JSON.dump(test_case))
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'validates the registration_ids format'
|
47
|
+
|
48
|
+
# According to
|
49
|
+
# https://developer.android.com/google/fcm/server-ref.html#table1
|
50
|
+
it 'validates the data'
|
51
|
+
|
52
|
+
it_behaves_like 'a proper results manager' do
|
53
|
+
let(:success_count) { 2 }
|
54
|
+
let(:failures_count) { 1 }
|
55
|
+
let(:canonical_id) { 'abcd' }
|
56
|
+
let(:individual_results) { [FCMCanonicalIDResult.new(canonical_id)] }
|
57
|
+
let(:results) do
|
58
|
+
FCMResponse.new 200, JSON.dump(
|
59
|
+
success: success_count,
|
60
|
+
failure: failures_count,
|
61
|
+
results: [
|
62
|
+
registration_id: canonical_id
|
63
|
+
]
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
describe FCMPusher do
|
6
|
+
let(:fcm_key) { 'fcm key' }
|
7
|
+
let(:pusher) { FCMPusher.new fcm_key }
|
8
|
+
|
9
|
+
describe '#push' do
|
10
|
+
let(:notif1) { build :fcm_notification }
|
11
|
+
let(:notif2) { build :fcm_notification }
|
12
|
+
let(:response) { JSON.dump a: 1 }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_request(:post, 'https://fcm.googleapis.com/fcm/send').
|
16
|
+
to_return status: [200, 'OK'], body: response
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'submits every notification' do
|
20
|
+
pusher.push [notif1, notif2]
|
21
|
+
|
22
|
+
expect(WebMock).
|
23
|
+
to have_requested(:post, 'https://fcm.googleapis.com/fcm/send').
|
24
|
+
with(body: notif1.as_fcm_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => "key=#{fcm_key}" }).
|
25
|
+
once
|
26
|
+
|
27
|
+
expect(WebMock).
|
28
|
+
to have_requested(:post, 'https://fcm.googleapis.com/fcm/send').
|
29
|
+
with(body: notif2.as_fcm_json, headers: { 'Content-Type' => 'application/json', 'Authorization' => "key=#{fcm_key}" }).
|
30
|
+
once
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sets results to notifications' do
|
34
|
+
expect do
|
35
|
+
pusher.push [notif1, notif2]
|
36
|
+
end.to change { [notif1, notif2].map &:results }.from([nil, nil]).to [FCMResponse.new(200, response)] * 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'splits notifications with more than 1000 destinations in parts'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyPushNotifications
|
4
|
+
module FCM
|
5
|
+
describe FCMResponse do
|
6
|
+
describe 'success' do
|
7
|
+
let(:successful_messages) { 3 }
|
8
|
+
let(:failed_messages) { 1 }
|
9
|
+
let(:canonical_ids) { 2 }
|
10
|
+
let(:body) {
|
11
|
+
JSON.dump(
|
12
|
+
multicast_id: 123456789,
|
13
|
+
success: successful_messages,
|
14
|
+
failure: failed_messages,
|
15
|
+
canonical_ids: canonical_ids,
|
16
|
+
results: [
|
17
|
+
{ message_id: 1,
|
18
|
+
registration_id: 'new_reg_id' },
|
19
|
+
{ message_id: 2,
|
20
|
+
registration_id: 'new_reg_id_2' },
|
21
|
+
{ message_id: 3 },
|
22
|
+
{ message_id: 4,
|
23
|
+
error: 'NotRegistered' }
|
24
|
+
]
|
25
|
+
)
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:response) { FCMResponse.new 200, body }
|
29
|
+
|
30
|
+
it 'parses the number of successfully processed notifications' do
|
31
|
+
expect(response.success).to eq successful_messages
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'parses the number of failed messages' do
|
35
|
+
expect(response.failed).to eq failed_messages
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'parses the number of canonical ids received' do
|
39
|
+
expect(response.canonical_ids).to eq canonical_ids
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'parses the results' do
|
43
|
+
expect(
|
44
|
+
response.results
|
45
|
+
).to eq [FCMCanonicalIDResult.new('new_reg_id'),
|
46
|
+
FCMCanonicalIDResult.new('new_reg_id_2'),
|
47
|
+
FCMResultOK.new,
|
48
|
+
FCMResultError.new('NotRegistered')]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'errors' do
|
53
|
+
let(:dummy_response_body) { 'dummy response body' }
|
54
|
+
describe '400 Bad Request error code' do
|
55
|
+
it 'raises a MalformedFCMJSONError' do
|
56
|
+
expect do
|
57
|
+
FCMResponse.new 400, dummy_response_body
|
58
|
+
end.to raise_error MalformedFCMJSONError, dummy_response_body
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '401 Unauthorized error code' do
|
63
|
+
it 'raises a FCMAuthError' do
|
64
|
+
expect do
|
65
|
+
FCMResponse.new 401, dummy_response_body
|
66
|
+
end.to raise_error FCMAuthError, dummy_response_body
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '500 Internal Server Error error code' do
|
71
|
+
it 'raises a FCMInternalError' do
|
72
|
+
expect do
|
73
|
+
FCMResponse.new 500, dummy_response_body
|
74
|
+
end.to raise_error FCMInternalError, dummy_response_body
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '302 Found error code' do
|
79
|
+
it 'raises a UnexpectedFCMResponseError' do
|
80
|
+
expect do
|
81
|
+
FCMResponse.new 302, dummy_response_body
|
82
|
+
end.to raise_error UnexpectedFCMResponseError, '302'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
require 'simplecov'
|
3
4
|
SimpleCov.start
|
@@ -15,7 +16,7 @@ Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
|
|
15
16
|
def apns_binary(json, token, id)
|
16
17
|
json = JSON.dump(json) if json.is_a?(Hash)
|
17
18
|
[
|
18
|
-
2, 56+json.bytesize, 1, 32, token, 2, json.bytesize, json,
|
19
|
+
2, 56 + json.bytesize, 1, 32, token, 2, json.bytesize, json,
|
19
20
|
3, 4, id, 4, 4, (Time.now + RubyPushNotifications::APNS::APNSNotification::WEEKS_4).to_i, 5, 1, 10
|
20
21
|
].pack 'cNcnH64cna*cnNcnNcnc'
|
21
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-push-notifications
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Alonso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: builder
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.12.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.12.0
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rspec
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,8 +109,7 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '1.20'
|
97
111
|
description: Easy to use gem to send iOS, Android and Windows Phone Push notifications
|
98
|
-
email:
|
99
|
-
- info@mrcalonso.com
|
112
|
+
email:
|
100
113
|
executables: []
|
101
114
|
extensions: []
|
102
115
|
extra_rdoc_files: []
|
@@ -110,6 +123,7 @@ files:
|
|
110
123
|
- README.md
|
111
124
|
- Rakefile
|
112
125
|
- examples/apns.rb
|
126
|
+
- examples/fcm.rb
|
113
127
|
- examples/gcm.rb
|
114
128
|
- examples/mpns.rb
|
115
129
|
- examples/wns.rb
|
@@ -120,6 +134,13 @@ files:
|
|
120
134
|
- lib/ruby-push-notifications/apns/apns_notification.rb
|
121
135
|
- lib/ruby-push-notifications/apns/apns_pusher.rb
|
122
136
|
- lib/ruby-push-notifications/apns/apns_results.rb
|
137
|
+
- lib/ruby-push-notifications/fcm.rb
|
138
|
+
- lib/ruby-push-notifications/fcm/fcm_connection.rb
|
139
|
+
- lib/ruby-push-notifications/fcm/fcm_error.rb
|
140
|
+
- lib/ruby-push-notifications/fcm/fcm_notification.rb
|
141
|
+
- lib/ruby-push-notifications/fcm/fcm_pusher.rb
|
142
|
+
- lib/ruby-push-notifications/fcm/fcm_response.rb
|
143
|
+
- lib/ruby-push-notifications/fcm/fcm_result.rb
|
123
144
|
- lib/ruby-push-notifications/gcm.rb
|
124
145
|
- lib/ruby-push-notifications/gcm/gcm_connection.rb
|
125
146
|
- lib/ruby-push-notifications/gcm/gcm_error.rb
|
@@ -147,6 +168,10 @@ files:
|
|
147
168
|
- spec/ruby-push-notifications/apns/apns_connection_spec.rb
|
148
169
|
- spec/ruby-push-notifications/apns/apns_notification_spec.rb
|
149
170
|
- spec/ruby-push-notifications/apns/apns_pusher_spec.rb
|
171
|
+
- spec/ruby-push-notifications/fcm/fcm_connection_spec.rb
|
172
|
+
- spec/ruby-push-notifications/fcm/fcm_notification_spec.rb
|
173
|
+
- spec/ruby-push-notifications/fcm/fcm_pusher_spec.rb
|
174
|
+
- spec/ruby-push-notifications/fcm/fcm_response_spec.rb
|
150
175
|
- spec/ruby-push-notifications/gcm/gcm_connection_spec.rb
|
151
176
|
- spec/ruby-push-notifications/gcm/gcm_notification_spec.rb
|
152
177
|
- spec/ruby-push-notifications/gcm/gcm_pusher_spec.rb
|
@@ -184,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
209
|
version: '0'
|
185
210
|
requirements: []
|
186
211
|
rubyforge_project:
|
187
|
-
rubygems_version: 2.
|
212
|
+
rubygems_version: 2.5.2.3
|
188
213
|
signing_key:
|
189
214
|
specification_version: 4
|
190
215
|
summary: iOS, Android and Windows Phone Push Notifications made easy!
|
@@ -194,6 +219,10 @@ test_files:
|
|
194
219
|
- spec/ruby-push-notifications/apns/apns_connection_spec.rb
|
195
220
|
- spec/ruby-push-notifications/apns/apns_notification_spec.rb
|
196
221
|
- spec/ruby-push-notifications/apns/apns_pusher_spec.rb
|
222
|
+
- spec/ruby-push-notifications/fcm/fcm_connection_spec.rb
|
223
|
+
- spec/ruby-push-notifications/fcm/fcm_notification_spec.rb
|
224
|
+
- spec/ruby-push-notifications/fcm/fcm_pusher_spec.rb
|
225
|
+
- spec/ruby-push-notifications/fcm/fcm_response_spec.rb
|
197
226
|
- spec/ruby-push-notifications/gcm/gcm_connection_spec.rb
|
198
227
|
- spec/ruby-push-notifications/gcm/gcm_notification_spec.rb
|
199
228
|
- spec/ruby-push-notifications/gcm/gcm_pusher_spec.rb
|