ruby-push-notifications 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|