fcm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +128 -0
- data/Rakefile +15 -0
- data/fcm.gemspec +26 -0
- data/lib/fcm.rb +179 -0
- data/spec/fcm_spec.rb +241 -0
- data/spec/spec_helper.rb +13 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 566f06986ceeeb5c1feb04bd49988c7d1a1698ed
|
4
|
+
data.tar.gz: 60daa74b40a83b9536a54db75b04d8b02b1526f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6c58de63c80e324d3425278ebebe22c617be80d2514a8d0a6944bda90a3c6bd06d2de3bb4afd137e1528fc363f97eb8e8671f3e0f7cf73a71381a0a75f60e107
|
7
|
+
data.tar.gz: aaf37a387be61be0dbb3a094404204dfcb433549c68757dd4863ea97dbf80be236dcbd7706e298ac5148643af217aa76ad73a7e6eef14a06e009adbb650b2aba
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
|
4
|
+
# rdoc generated
|
5
|
+
rdoc
|
6
|
+
|
7
|
+
# yard generated
|
8
|
+
doc
|
9
|
+
.yardoc
|
10
|
+
|
11
|
+
# bundler
|
12
|
+
.bundle
|
13
|
+
|
14
|
+
# jeweler generated
|
15
|
+
pkg
|
16
|
+
|
17
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
18
|
+
#
|
19
|
+
# * Create a file at ~/.gitignore
|
20
|
+
# * Include files you want ignored
|
21
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
22
|
+
#
|
23
|
+
# After doing this, these files will be ignored in all your git projects,
|
24
|
+
# saving you from having to 'pollute' every project you touch with them
|
25
|
+
#
|
26
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
27
|
+
#
|
28
|
+
# For MacOS:
|
29
|
+
#
|
30
|
+
.DS_Store
|
31
|
+
#
|
32
|
+
# For TextMate
|
33
|
+
#*.tmproj
|
34
|
+
#tmtags
|
35
|
+
#
|
36
|
+
# For emacs:
|
37
|
+
#*~
|
38
|
+
#\#*
|
39
|
+
#.\#*
|
40
|
+
#
|
41
|
+
# For vim:
|
42
|
+
#*.swp
|
43
|
+
|
44
|
+
bin
|
45
|
+
cache
|
46
|
+
gems
|
47
|
+
specifications
|
48
|
+
Gemfile.lock
|
49
|
+
.rvmrc
|
50
|
+
spec/reports
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Kashif Rasul and Shoaib Burq
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# Firebase Cloud Messaging (FCM) for Android and iOS
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/fcm.svg)](http://badge.fury.io/rb/fcm) [![Build Status](https://secure.travis-ci.org/spacialdb/fcm.png?branch=master)](http://travis-ci.org/spacialdb/fcm)
|
3
|
+
|
4
|
+
The FCM gem lets your ruby backend send notifications to Android and iOS devices via [
|
5
|
+
Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/).
|
6
|
+
|
7
|
+
##Installation
|
8
|
+
|
9
|
+
$ gem install fcm
|
10
|
+
|
11
|
+
or in your `Gemfile` just include it:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'fcm'
|
15
|
+
```
|
16
|
+
|
17
|
+
##Requirements
|
18
|
+
|
19
|
+
For Android you will need a device running 2.3 (or newer) that also have the Google Play Store app installed, or an emulator running Android 2.3 with Google APIs. iOS devices are also supported.
|
20
|
+
|
21
|
+
One of the following, tested Ruby versions:
|
22
|
+
|
23
|
+
* `2.0.0`
|
24
|
+
* `2.1.9`
|
25
|
+
* `2.2.5`
|
26
|
+
* `2.3.1`
|
27
|
+
|
28
|
+
##Usage
|
29
|
+
|
30
|
+
For your server to send a message to one or more devices, you must first initialise a new `FCM` class with your Firebase server Api key, and then call the `send` method on this and give it 1 or more (up to 1000) registration tokens as an array of strings. You can also optionally send further [HTTP message parameters](https://firebase.google.com/docs/cloud-messaging/http-server-ref) like `data` or `time_to_live` etc. as a hash via the second optional argument to `send`.
|
31
|
+
|
32
|
+
Example sending notifications:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'fcm'
|
36
|
+
|
37
|
+
fcm = FCM.new("my_api_key")
|
38
|
+
# you can set option parameters in here
|
39
|
+
# - all options are pass to HTTParty method arguments
|
40
|
+
# - ref: https://github.com/jnunemaker/httparty/blob/master/lib/httparty.rb#L29-L60
|
41
|
+
# fcm = FCM.new("my_api_key", timeout: 3)
|
42
|
+
|
43
|
+
registration_ids= ["12", "13"] # an array of one or more client registration tokens
|
44
|
+
options = {data: {score: "123"}, collapse_key: "updated_score"}
|
45
|
+
response = fcm.send(registration_ids, options)
|
46
|
+
```
|
47
|
+
|
48
|
+
Currently `response` is just a hash containing the response `body`, `headers` and `status`. Check [here](https://firebase.google.com/docs/cloud-messaging/server#response) to see how to interpret the responses.
|
49
|
+
|
50
|
+
## Device Group Messaging
|
51
|
+
|
52
|
+
With [device group messaging](https://firebase.google.com/docs/cloud-messaging/notifications), you can send a single message to multiple instance of an app running on devices belonging to a group. Typically, "group" refers a set of different devices that belong to a single user. However, a group could also represent a set of devices where the app instance functions in a highly correlated manner. To use this feature, you will first need an initialised `FCM` class.
|
53
|
+
|
54
|
+
### Generate a Notification Key for device group
|
55
|
+
Then you will need a notification key which you can create for a particular `key_name` which needs to be uniquely named per app in case you have multiple apps for the same `project_id`. This ensures that notifications only go to the intended target app. The `create` method will do this and return the token `notification_key`, that represents the device group, in the response:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
response = fcm.create(key_name: "appUser-Chris",
|
59
|
+
project_id: "my_project_id",
|
60
|
+
registration_ids:["4", "8", "15", "16", "23", "42"])
|
61
|
+
```
|
62
|
+
|
63
|
+
### Send to Notification Key
|
64
|
+
Now you can send a message to a particular `notification_key` via the `send_with_notification_key` method. This allows the server to send a single data to multiple app instances (typically on multiple devices) owned by a single user (instead of sending to some registration tokens). Note: the maximum number of members allowed for a `notification_key` is 20.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
response = fcm.send_with_notification_key("notification_key", {
|
68
|
+
data: {score: "3x1"},
|
69
|
+
collapse_key: "updated_score"})
|
70
|
+
```
|
71
|
+
|
72
|
+
### Add/Remove Registration Tokens
|
73
|
+
|
74
|
+
You can also add/remove registration Tokens to/from a particular `notification_key` of some `project_id`. For example:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
response = fcm.add(key_name: "appUser-Chris",
|
78
|
+
project_id: "my_project_id",
|
79
|
+
notification_key:"appUser-Chris-key",
|
80
|
+
registration_ids:["7", "3"])
|
81
|
+
|
82
|
+
response = fcm.remove(key_name: "appUser-Chris",
|
83
|
+
project_id: "my_project_id",
|
84
|
+
notification_key:"appUser-Chris-key",
|
85
|
+
registration_ids:["8", "15"])
|
86
|
+
```
|
87
|
+
|
88
|
+
## Send Messages to Topics
|
89
|
+
|
90
|
+
FCM [topic messaging](https://firebase.google.com/docs/cloud-messaging/topic-messaging) allows your app server to send a message to multiple devices that have opted in to a particular topic. Based on the publish/subscribe model, topic messaging supports unlimited subscriptions per app. Sending to a topic is very similar to sending to an individual device or to a user group, in the sense that you can use the `fcm.send_with_notification_key()` method where the `noticiation_key` matches the regular expression `"/topics/[a-zA-Z0-9-_.~%]+"`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
response = fcm.send_with_notification_key("/topics/yourTopic", {
|
94
|
+
data: {message: "This is a FCM Topic Message!"})
|
95
|
+
```
|
96
|
+
|
97
|
+
Or you can use the helper:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
response = fcm.send_to_topic("yourTopic", {
|
101
|
+
data: {message: "This is a FCM Topic Message!"})
|
102
|
+
```
|
103
|
+
|
104
|
+
## Mobile Clients
|
105
|
+
|
106
|
+
You can find a guide to implement an Android Client app to receive notifications here: [Set up a FCM Client App on Android](https://firebase.google.com/docs/cloud-messaging/android/client).
|
107
|
+
|
108
|
+
The guide to set up an iOS app to get notifications is here: [Setting up a FCM Client App on iOS](https://firebase.google.com/docs/cloud-messaging/ios/client).
|
109
|
+
|
110
|
+
## ChangeLog
|
111
|
+
|
112
|
+
### 0.0.1
|
113
|
+
|
114
|
+
* Initial version.
|
115
|
+
|
116
|
+
|
117
|
+
##MIT License
|
118
|
+
|
119
|
+
* Copyright (c) 2016 Kashif Rasul and Shoaib Burq. See LICENSE.txt for details.
|
120
|
+
|
121
|
+
##Many thanks to all the contributors
|
122
|
+
|
123
|
+
* [Contributors](https://github.com/spacialdb/fcm/contributors)
|
124
|
+
|
125
|
+
## Donations
|
126
|
+
We accept tips through [Gratipay](https://gratipay.com/spacialdb/).
|
127
|
+
|
128
|
+
[![Gratipay](https://img.shields.io/gratipay/spacialdb.svg)](https://www.gittip.com/spacialdb/)
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rake/tasklib"
|
4
|
+
require 'ci/reporter/rake/rspec'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec => ["ci:setup:rspec"]) do |t|
|
7
|
+
t.pattern = 'spec/**/*_spec.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
11
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
12
|
+
spec.rspec_opts = ['--format documentation']
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => :spec
|
data/fcm.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "fcm"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Kashif Rasul", "Shoaib Burq"]
|
9
|
+
s.email = ["kashif@spacialdb.com", "shoaib@spacialdb.com"]
|
10
|
+
s.homepage = "https://github.com/spacialdb/fcm"
|
11
|
+
s.summary = %q{Reliably deliver messages and notifications via FCM}
|
12
|
+
s.description = %q{fcm provides ruby bindings to Firebase Cloud Messaging (FCM) a cross-platform messaging solution that lets you reliably deliver messages and notifications at no cost to Android, iOS or Web browsers.}
|
13
|
+
s.license = "MIT"
|
14
|
+
|
15
|
+
s.required_ruby_version = '>= 2.0.0'
|
16
|
+
|
17
|
+
s.rubyforge_project = "fcm"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
s.add_dependency('httparty')
|
25
|
+
s.add_dependency('json')
|
26
|
+
end
|
data/lib/fcm.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'cgi'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class FCM
|
6
|
+
include HTTParty
|
7
|
+
base_uri 'https://fcm.googleapis.com/fcm'
|
8
|
+
default_timeout 30
|
9
|
+
format :json
|
10
|
+
|
11
|
+
attr_accessor :timeout, :api_key
|
12
|
+
|
13
|
+
def initialize(api_key, client_options = {})
|
14
|
+
@api_key = api_key
|
15
|
+
@client_options = client_options
|
16
|
+
end
|
17
|
+
|
18
|
+
# {
|
19
|
+
# "collapse_key": "score_update",
|
20
|
+
# "time_to_live": 108,
|
21
|
+
# "delay_while_idle": true,
|
22
|
+
# "registration_ids": ["4", "8", "15", "16", "23", "42"],
|
23
|
+
# "data" : {
|
24
|
+
# "score": "5x1",
|
25
|
+
# "time": "15:10"
|
26
|
+
# }
|
27
|
+
# }
|
28
|
+
# fcm = FCM.new("API_KEY")
|
29
|
+
# fcm.send(registration_ids: ["4sdsx", "8sdsd"], {data: {score: "5x1"}})
|
30
|
+
def send_notification(registration_ids, options = {})
|
31
|
+
post_body = build_post_body(registration_ids, options)
|
32
|
+
|
33
|
+
params = {
|
34
|
+
body: post_body.to_json,
|
35
|
+
headers: {
|
36
|
+
'Authorization' => "key=#{@api_key}",
|
37
|
+
'Content-Type' => 'application/json'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
response = self.class.post('/send', params.merge(@client_options))
|
41
|
+
build_response(response, registration_ids)
|
42
|
+
end
|
43
|
+
alias send send_notification
|
44
|
+
|
45
|
+
def create_notification_key(key_name, project_id, registration_ids = [])
|
46
|
+
post_body = build_post_body(registration_ids, operation: 'create',
|
47
|
+
notification_key_name: key_name)
|
48
|
+
|
49
|
+
params = {
|
50
|
+
body: post_body.to_json,
|
51
|
+
headers: {
|
52
|
+
'Content-Type' => 'application/json',
|
53
|
+
'project_id' => project_id,
|
54
|
+
'Authorization' => "key=#{@api_key}"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
response = self.class.post('/notification', params.merge(@client_options))
|
59
|
+
build_response(response)
|
60
|
+
end
|
61
|
+
alias create create_notification_key
|
62
|
+
|
63
|
+
def add_registration_ids(key_name, project_id, notification_key, registration_ids)
|
64
|
+
post_body = build_post_body(registration_ids, operation: 'add',
|
65
|
+
notification_key_name: key_name,
|
66
|
+
notification_key: notification_key)
|
67
|
+
|
68
|
+
params = {
|
69
|
+
body: post_body.to_json,
|
70
|
+
headers: {
|
71
|
+
'Content-Type' => 'application/json',
|
72
|
+
'project_id' => project_id,
|
73
|
+
'Authorization' => "key=#{@api_key}"
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
response = self.class.post('/notification', params.merge(@client_options))
|
78
|
+
build_response(response)
|
79
|
+
end
|
80
|
+
alias add add_registration_ids
|
81
|
+
|
82
|
+
def remove_registration_ids(key_name, project_id, notification_key, registration_ids)
|
83
|
+
post_body = build_post_body(registration_ids, operation: 'remove',
|
84
|
+
notification_key_name: key_name,
|
85
|
+
notification_key: notification_key)
|
86
|
+
|
87
|
+
params = {
|
88
|
+
body: post_body.to_json,
|
89
|
+
headers: {
|
90
|
+
'Content-Type' => 'application/json',
|
91
|
+
'project_id' => project_id,
|
92
|
+
'Authorization' => "key=#{@api_key}"
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
response = self.class.post('/notification', params.merge(@client_options))
|
97
|
+
build_response(response)
|
98
|
+
end
|
99
|
+
alias remove remove_registration_ids
|
100
|
+
|
101
|
+
def send_with_notification_key(notification_key, options = {})
|
102
|
+
body = { to: notification_key }.merge(options)
|
103
|
+
|
104
|
+
params = {
|
105
|
+
body: body.to_json,
|
106
|
+
headers: {
|
107
|
+
'Authorization' => "key=#{@api_key}",
|
108
|
+
'Content-Type' => 'application/json'
|
109
|
+
}
|
110
|
+
}
|
111
|
+
response = self.class.post('/send', params.merge(@client_options))
|
112
|
+
build_response(response)
|
113
|
+
end
|
114
|
+
|
115
|
+
def send_to_topic(topic, options = {})
|
116
|
+
if topic =~ /[a-zA-Z0-9\-_.~%]+/
|
117
|
+
send_with_notification_key('/topics/' + topic, options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def build_post_body(registration_ids, options = {})
|
124
|
+
{ registration_ids: registration_ids }.merge(options)
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_response(response, registration_ids = [])
|
128
|
+
body = response.body || {}
|
129
|
+
response_hash = { body: body, headers: response.headers, status_code: response.code }
|
130
|
+
case response.code
|
131
|
+
when 200
|
132
|
+
response_hash[:response] = 'success'
|
133
|
+
body = JSON.parse(body) unless body.empty?
|
134
|
+
response_hash[:canonical_ids] = build_canonical_ids(body, registration_ids) unless registration_ids.empty?
|
135
|
+
response_hash[:not_registered_ids] = build_not_registered_ids(body, registration_ids) unless registration_ids.empty?
|
136
|
+
when 400
|
137
|
+
response_hash[:response] = 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.'
|
138
|
+
when 401
|
139
|
+
response_hash[:response] = 'There was an error authenticating the sender account.'
|
140
|
+
when 503
|
141
|
+
response_hash[:response] = 'Server is temporarily unavailable.'
|
142
|
+
when 500..599
|
143
|
+
response_hash[:response] = 'There was an internal error in the FCM server while trying to process the request.'
|
144
|
+
end
|
145
|
+
response_hash
|
146
|
+
end
|
147
|
+
|
148
|
+
def build_canonical_ids(body, registration_ids)
|
149
|
+
canonical_ids = []
|
150
|
+
unless body.empty?
|
151
|
+
if body['canonical_ids'] > 0
|
152
|
+
body['results'].each_with_index do |result, index|
|
153
|
+
canonical_ids << { old: registration_ids[index], new: result['registration_id'] } if has_canonical_id?(result)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
canonical_ids
|
158
|
+
end
|
159
|
+
|
160
|
+
def build_not_registered_ids(body, registration_id)
|
161
|
+
not_registered_ids = []
|
162
|
+
unless body.empty?
|
163
|
+
if body['failure'] > 0
|
164
|
+
body['results'].each_with_index do |result, index|
|
165
|
+
not_registered_ids << registration_id[index] if is_not_registered?(result)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
not_registered_ids
|
170
|
+
end
|
171
|
+
|
172
|
+
def has_canonical_id?(result)
|
173
|
+
!result['registration_id'].nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
def is_not_registered?(result)
|
177
|
+
result['error'] == 'NotRegistered'
|
178
|
+
end
|
179
|
+
end
|
data/spec/fcm_spec.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCM do
|
4
|
+
let(:send_url) { "#{FCM.base_uri}/send" }
|
5
|
+
|
6
|
+
it 'should raise an error if the api key is not provided' do
|
7
|
+
expect { FCM.new }.to raise_error
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should raise error if time_to_live is given' do
|
11
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#ttl
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'sending notification' do
|
15
|
+
let(:api_key) { 'AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA' }
|
16
|
+
let(:registration_ids) { ['42'] }
|
17
|
+
let(:valid_request_body) do
|
18
|
+
{ registration_ids: registration_ids }
|
19
|
+
end
|
20
|
+
let(:valid_request_headers) do
|
21
|
+
{
|
22
|
+
'Content-Type' => 'application/json',
|
23
|
+
'Authorization' => "key=#{api_key}"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:stub_fcm_send_request) do
|
28
|
+
stub_request(:post, send_url).with(
|
29
|
+
body: valid_request_body.to_json,
|
30
|
+
headers: valid_request_headers
|
31
|
+
).to_return(
|
32
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
33
|
+
body: '{}',
|
34
|
+
headers: {},
|
35
|
+
status: 200
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:stub_fcm_send_request_with_basic_auth) do
|
40
|
+
uri = URI.parse(send_url)
|
41
|
+
uri.user = 'a'
|
42
|
+
uri.password = 'b'
|
43
|
+
stub_request(:post, uri.to_s).to_return(body: '{}', headers: {}, status: 200)
|
44
|
+
end
|
45
|
+
|
46
|
+
before(:each) do
|
47
|
+
stub_fcm_send_request
|
48
|
+
stub_fcm_send_request_with_basic_auth
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should send notification using POST to FCM server' do
|
52
|
+
fcm = FCM.new(api_key)
|
53
|
+
fcm.send(registration_ids).should eq(response: 'success', body: '{}', headers: {}, status_code: 200, canonical_ids: [], not_registered_ids: [])
|
54
|
+
stub_fcm_send_request.should have_been_made.times(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'send notification with data' do
|
58
|
+
let!(:stub_with_data) do
|
59
|
+
stub_request(:post, send_url)
|
60
|
+
.with(body: '{"registration_ids":["42"],"data":{"score":"5x1","time":"15:10"}}',
|
61
|
+
headers: valid_request_headers)
|
62
|
+
.to_return(status: 200, body: '', headers: {})
|
63
|
+
end
|
64
|
+
before do
|
65
|
+
end
|
66
|
+
it 'should send the data in a post request to fcm' do
|
67
|
+
fcm = FCM.new(api_key)
|
68
|
+
fcm.send(registration_ids, data: { score: '5x1', time: '15:10' })
|
69
|
+
stub_with_data.should have_been_requested
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when send_notification responds with failure' do
|
74
|
+
let(:mock_request_attributes) do
|
75
|
+
{
|
76
|
+
body: valid_request_body.to_json,
|
77
|
+
headers: valid_request_headers
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
subject { FCM.new(api_key) }
|
82
|
+
|
83
|
+
context 'on failure code 400' do
|
84
|
+
before do
|
85
|
+
stub_request(:post, send_url).with(
|
86
|
+
mock_request_attributes
|
87
|
+
).to_return(
|
88
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
89
|
+
body: '{}',
|
90
|
+
headers: {},
|
91
|
+
status: 400
|
92
|
+
)
|
93
|
+
end
|
94
|
+
it 'should not send notification due to 400' do
|
95
|
+
subject.send(registration_ids).should eq(body: '{}',
|
96
|
+
headers: {},
|
97
|
+
response: 'Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.',
|
98
|
+
status_code: 400)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'on failure code 401' do
|
103
|
+
before do
|
104
|
+
stub_request(:post, send_url).with(
|
105
|
+
mock_request_attributes
|
106
|
+
).to_return(
|
107
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
108
|
+
body: '{}',
|
109
|
+
headers: {},
|
110
|
+
status: 401
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should not send notification due to 401' do
|
115
|
+
subject.send(registration_ids).should eq(body: '{}',
|
116
|
+
headers: {},
|
117
|
+
response: 'There was an error authenticating the sender account.',
|
118
|
+
status_code: 401)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'on failure code 503' do
|
123
|
+
before do
|
124
|
+
stub_request(:post, send_url).with(
|
125
|
+
mock_request_attributes
|
126
|
+
).to_return(
|
127
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
128
|
+
body: '{}',
|
129
|
+
headers: {},
|
130
|
+
status: 503
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should not send notification due to 503' do
|
135
|
+
subject.send(registration_ids).should eq(body: '{}',
|
136
|
+
headers: {},
|
137
|
+
response: 'Server is temporarily unavailable.',
|
138
|
+
status_code: 503)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'on failure code 5xx' do
|
143
|
+
before do
|
144
|
+
stub_request(:post, send_url).with(
|
145
|
+
mock_request_attributes
|
146
|
+
).to_return(
|
147
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
148
|
+
body: '{"body-key" => "Body value"}',
|
149
|
+
headers: { 'header-key' => 'Header value' },
|
150
|
+
status: 599
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should not send notification due to 599' do
|
155
|
+
subject.send(registration_ids).should eq(body: '{"body-key" => "Body value"}',
|
156
|
+
headers: { 'header-key' => ['Header value'] },
|
157
|
+
response: 'There was an internal error in the FCM server while trying to process the request.',
|
158
|
+
status_code: 599)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when send_notification responds canonical_ids' do
|
164
|
+
let(:mock_request_attributes) do
|
165
|
+
{
|
166
|
+
body: valid_request_body.to_json,
|
167
|
+
headers: valid_request_headers
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
let(:valid_response_body_with_canonical_ids) do
|
172
|
+
{
|
173
|
+
failure: 0, canonical_ids: 1, results: [{ registration_id: '43', message_id: '0:1385025861956342%572c22801bb3' }]
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
subject { FCM.new(api_key) }
|
178
|
+
|
179
|
+
before do
|
180
|
+
stub_request(:post, send_url).with(
|
181
|
+
mock_request_attributes
|
182
|
+
).to_return(
|
183
|
+
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
184
|
+
body: valid_response_body_with_canonical_ids.to_json,
|
185
|
+
headers: {},
|
186
|
+
status: 200
|
187
|
+
)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should contain canonical_ids' do
|
191
|
+
response = subject.send(registration_ids)
|
192
|
+
|
193
|
+
response.should eq(headers: {},
|
194
|
+
canonical_ids: [{ old: '42', new: '43' }],
|
195
|
+
not_registered_ids: [],
|
196
|
+
status_code: 200,
|
197
|
+
response: 'success',
|
198
|
+
body: '{"failure":0,"canonical_ids":1,"results":[{"registration_id":"43","message_id":"0:1385025861956342%572c22801bb3"}]}')
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'when send_notification responds with NotRegistered' do
|
203
|
+
subject { FCM.new(api_key) }
|
204
|
+
|
205
|
+
let(:mock_request_attributes) do
|
206
|
+
{
|
207
|
+
body: valid_request_body.to_json,
|
208
|
+
headers: valid_request_headers
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
let(:valid_response_body_with_not_registered_ids) do
|
213
|
+
{
|
214
|
+
canonical_ids: 0, failure: 1, results: [{ error: 'NotRegistered' }]
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
before do
|
219
|
+
stub_request(:post, send_url).with(
|
220
|
+
mock_request_attributes
|
221
|
+
).to_return(
|
222
|
+
body: valid_response_body_with_not_registered_ids.to_json,
|
223
|
+
headers: {},
|
224
|
+
status: 200
|
225
|
+
)
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'should contain not_registered_ids' do
|
229
|
+
response = subject.send(registration_ids)
|
230
|
+
response.should eq(
|
231
|
+
headers: {},
|
232
|
+
canonical_ids: [],
|
233
|
+
not_registered_ids: registration_ids,
|
234
|
+
status_code: 200,
|
235
|
+
response: 'success',
|
236
|
+
body: '{"canonical_ids":0,"failure":1,"results":[{"error":"NotRegistered"}]}'
|
237
|
+
)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
require 'fcm'
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.expect_with :rspec do |c|
|
10
|
+
c.syntax = [:should, :expect]
|
11
|
+
end
|
12
|
+
# config.filter_run :focus
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fcm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kashif Rasul
|
8
|
+
- Shoaib Burq
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: json
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
description: fcm provides ruby bindings to Firebase Cloud Messaging (FCM) a cross-platform
|
43
|
+
messaging solution that lets you reliably deliver messages and notifications at
|
44
|
+
no cost to Android, iOS or Web browsers.
|
45
|
+
email:
|
46
|
+
- kashif@spacialdb.com
|
47
|
+
- shoaib@spacialdb.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- ".gitignore"
|
53
|
+
- ".rspec"
|
54
|
+
- ".travis.yml"
|
55
|
+
- Gemfile
|
56
|
+
- LICENSE.txt
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- fcm.gemspec
|
60
|
+
- lib/fcm.rb
|
61
|
+
- spec/fcm_spec.rb
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
homepage: https://github.com/spacialdb/fcm
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.0.0
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project: fcm
|
83
|
+
rubygems_version: 2.6.4
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Reliably deliver messages and notifications via FCM
|
87
|
+
test_files:
|
88
|
+
- spec/fcm_spec.rb
|
89
|
+
- spec/spec_helper.rb
|