fcm 1.0.3 → 1.0.5
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/Gemfile +1 -0
- data/README.md +51 -3
- data/fcm.gemspec +2 -2
- data/lib/fcm.rb +115 -54
- data/spec/fcm_spec.rb +116 -112
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3378dc24fca1ced3cbda70c31a427261044eaa4b9d0580884e73981a04b1ce3
|
4
|
+
data.tar.gz: 6801d93f67ace2faeda17ad8ccf99b363cc6526a99573d8e5938eca7563e78c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 657dd3db45b913993cbd876a918ed69affaefe076ca649722c0c6c61937978b20337f8677398f735e7ff40ea983193d58c420811a2ed90778bbe8efc00f4f033
|
7
|
+
data.tar.gz: 662b4fc1b8b04962f957518ebaf86d68ca92d905a989e6baed51d4705af55b83f30388030da66237fec93b7fe2d66c0fdd3b14132915e75d0dcccccf312eb58d
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,13 +19,56 @@ gem 'fcm'
|
|
19
19
|
|
20
20
|
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.
|
21
21
|
|
22
|
-
|
23
22
|
A version of supported Ruby, currently:
|
24
23
|
`ruby >= 2.4`
|
25
24
|
|
26
|
-
|
27
25
|
## Usage
|
28
26
|
|
27
|
+
## HTTP v1 API
|
28
|
+
|
29
|
+
To migrate to HTTP v1 see: https://firebase.google.com/docs/cloud-messaging/migrate-v1
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
fcm = FCM.new(
|
33
|
+
API_TOKEN,
|
34
|
+
GOOGLE_APPLICATION_CREDENTIALS_PATH,
|
35
|
+
FIREBASE_PROJECT_ID
|
36
|
+
)
|
37
|
+
message = {
|
38
|
+
'topic': "89023", # OR token if you want to send to a specific device
|
39
|
+
# 'token': "000iddqd",
|
40
|
+
'data': {
|
41
|
+
payload: {
|
42
|
+
data: {
|
43
|
+
id: 1
|
44
|
+
}
|
45
|
+
}.to_json
|
46
|
+
},
|
47
|
+
'notification': {
|
48
|
+
title: notification.title_th,
|
49
|
+
body: notification.body_th,
|
50
|
+
},
|
51
|
+
'android': {},
|
52
|
+
'apns': {
|
53
|
+
payload: {
|
54
|
+
aps: {
|
55
|
+
sound: "default",
|
56
|
+
category: "#{Time.zone.now.to_i}"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
},
|
60
|
+
'fcm_options': {
|
61
|
+
analytics_label: 'Label'
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
fcm.send_v1(message)
|
66
|
+
```
|
67
|
+
|
68
|
+
## HTTP Legacy Version
|
69
|
+
|
70
|
+
To migrate to HTTP v1 see: https://firebase.google.com/docs/cloud-messaging/migrate-v1
|
71
|
+
|
29
72
|
For your server to send a message to one or more devices, you must first initialise a new `FCM` class with your Firebase Cloud Messaging server 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`.
|
30
73
|
|
31
74
|
Example sending notifications:
|
@@ -166,6 +209,11 @@ You can find a guide to implement an Android Client app to receive notifications
|
|
166
209
|
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).
|
167
210
|
|
168
211
|
## ChangeLog
|
212
|
+
|
213
|
+
### 1.0.3
|
214
|
+
|
215
|
+
- Fix overly strict faraday depenecy
|
216
|
+
|
169
217
|
### 1.0.2
|
170
218
|
|
171
219
|
- Bug fix: retrieve notification key" params: https://github.com/spacialdb/fcm/commit/b328a75c11d779a06d0ceda83527e26aa0495774
|
@@ -202,7 +250,7 @@ Update version in `fcm.gemspec` with `VERSION` and update `README.md` `## Change
|
|
202
250
|
|
203
251
|
```bash
|
204
252
|
# set the version
|
205
|
-
# VERSION="1.0.
|
253
|
+
# VERSION="1.0.4"
|
206
254
|
gem build fcm.gemspec
|
207
255
|
git tag -a v${VERSION} -m "Releasing version v${VERSION}"
|
208
256
|
git push origin --tags
|
data/fcm.gemspec
CHANGED
@@ -3,10 +3,10 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "fcm"
|
6
|
-
s.version = "1.0.
|
6
|
+
s.version = "1.0.5"
|
7
7
|
s.platform = Gem::Platform::RUBY
|
8
8
|
s.authors = ["Kashif Rasul", "Shoaib Burq"]
|
9
|
-
s.email = ["kashif@
|
9
|
+
s.email = ["kashif@decision-labs.com", "shoaib@decision-labs.com"]
|
10
10
|
s.homepage = "https://github.com/spacialdb/fcm"
|
11
11
|
s.summary = %q{Reliably deliver messages and notifications via FCM}
|
12
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.}
|
data/lib/fcm.rb
CHANGED
@@ -1,24 +1,71 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "faraday"
|
2
|
+
require "cgi"
|
3
|
+
require "json"
|
4
|
+
require "googleauth"
|
4
5
|
|
5
6
|
class FCM
|
6
|
-
BASE_URI =
|
7
|
+
BASE_URI = "https://fcm.googleapis.com"
|
8
|
+
BASE_URI_V1 = "https://fcm.googleapis.com/v1/projects/"
|
7
9
|
DEFAULT_TIMEOUT = 30
|
8
10
|
FORMAT = :json
|
9
11
|
|
10
12
|
# constants
|
11
|
-
GROUP_NOTIFICATION_BASE_URI =
|
12
|
-
INSTANCE_ID_API =
|
13
|
+
GROUP_NOTIFICATION_BASE_URI = "https://android.googleapis.com"
|
14
|
+
INSTANCE_ID_API = "https://iid.googleapis.com"
|
13
15
|
TOPIC_REGEX = /[a-zA-Z0-9\-_.~%]+/
|
14
16
|
|
15
|
-
attr_accessor :timeout, :api_key
|
17
|
+
attr_accessor :timeout, :api_key, :json_key_path, :project_base_uri
|
16
18
|
|
17
|
-
def initialize(api_key, client_options = {})
|
19
|
+
def initialize(api_key, json_key_path = "", project_name = "", client_options = {})
|
18
20
|
@api_key = api_key
|
19
21
|
@client_options = client_options
|
22
|
+
@json_key_path = json_key_path
|
23
|
+
@project_base_uri = BASE_URI_V1 + project_name.to_s
|
20
24
|
end
|
21
25
|
|
26
|
+
# See https://firebase.google.com/docs/cloud-messaging/send-message
|
27
|
+
# {
|
28
|
+
# "token": "4sdsx",
|
29
|
+
# "notification": {
|
30
|
+
# "title": "Breaking News",
|
31
|
+
# "body": "New news story available."
|
32
|
+
# },
|
33
|
+
# "data": {
|
34
|
+
# "story_id": "story_12345"
|
35
|
+
# },
|
36
|
+
# "android": {
|
37
|
+
# "notification": {
|
38
|
+
# "click_action": "TOP_STORY_ACTIVITY",
|
39
|
+
# "body": "Check out the Top Story"
|
40
|
+
# }
|
41
|
+
# },
|
42
|
+
# "apns": {
|
43
|
+
# "payload": {
|
44
|
+
# "aps": {
|
45
|
+
# "category" : "NEW_MESSAGE_CATEGORY"
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
# }
|
49
|
+
# }
|
50
|
+
# fcm = FCM.new(api_key, json_key_path, project_name)
|
51
|
+
# fcm.send(
|
52
|
+
# { "token": "4sdsx",, "to" : "notification": {}.. }
|
53
|
+
# )
|
54
|
+
def send_notification_v1(message)
|
55
|
+
return if @project_base_uri.empty?
|
56
|
+
|
57
|
+
post_body = { 'message': message }
|
58
|
+
|
59
|
+
response = Faraday.post("#{@project_base_uri}/messages:send") do |req|
|
60
|
+
req.headers["Content-Type"] = "application/json"
|
61
|
+
req.headers["Authorization"] = "Bearer #{jwt_token}"
|
62
|
+
req.body = post_body.to_json
|
63
|
+
end
|
64
|
+
build_response(response)
|
65
|
+
end
|
66
|
+
|
67
|
+
alias send_v1 send_notification_v1
|
68
|
+
|
22
69
|
# See https://developers.google.com/cloud-messaging/http for more details.
|
23
70
|
# { "notification": {
|
24
71
|
# "title": "Portugal vs. Denmark",
|
@@ -35,68 +82,72 @@ class FCM
|
|
35
82
|
post_body = build_post_body(registration_ids, options)
|
36
83
|
|
37
84
|
for_uri(BASE_URI) do |connection|
|
38
|
-
response = connection.post(
|
85
|
+
response = connection.post("/fcm/send", post_body.to_json)
|
39
86
|
build_response(response, registration_ids)
|
40
87
|
end
|
41
88
|
end
|
89
|
+
|
42
90
|
alias send send_notification
|
43
91
|
|
44
92
|
def create_notification_key(key_name, project_id, registration_ids = [])
|
45
|
-
post_body = build_post_body(registration_ids, operation:
|
46
|
-
|
93
|
+
post_body = build_post_body(registration_ids, operation: "create",
|
94
|
+
notification_key_name: key_name)
|
47
95
|
|
48
96
|
extra_headers = {
|
49
|
-
|
97
|
+
"project_id" => project_id,
|
50
98
|
}
|
51
99
|
|
52
100
|
for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
|
53
|
-
response = connection.post(
|
101
|
+
response = connection.post("/gcm/notification", post_body.to_json)
|
54
102
|
build_response(response)
|
55
103
|
end
|
56
104
|
end
|
105
|
+
|
57
106
|
alias create create_notification_key
|
58
107
|
|
59
108
|
def add_registration_ids(key_name, project_id, notification_key, registration_ids)
|
60
|
-
post_body = build_post_body(registration_ids, operation:
|
61
|
-
|
62
|
-
|
109
|
+
post_body = build_post_body(registration_ids, operation: "add",
|
110
|
+
notification_key_name: key_name,
|
111
|
+
notification_key: notification_key)
|
63
112
|
|
64
113
|
extra_headers = {
|
65
|
-
|
114
|
+
"project_id" => project_id,
|
66
115
|
}
|
67
116
|
|
68
117
|
for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
|
69
|
-
response = connection.post(
|
118
|
+
response = connection.post("/gcm/notification", post_body.to_json)
|
70
119
|
build_response(response)
|
71
120
|
end
|
72
121
|
end
|
122
|
+
|
73
123
|
alias add add_registration_ids
|
74
124
|
|
75
125
|
def remove_registration_ids(key_name, project_id, notification_key, registration_ids)
|
76
|
-
post_body = build_post_body(registration_ids, operation:
|
77
|
-
|
78
|
-
|
126
|
+
post_body = build_post_body(registration_ids, operation: "remove",
|
127
|
+
notification_key_name: key_name,
|
128
|
+
notification_key: notification_key)
|
79
129
|
|
80
130
|
extra_headers = {
|
81
|
-
|
131
|
+
"project_id" => project_id,
|
82
132
|
}
|
83
133
|
|
84
134
|
for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
|
85
|
-
response = connection.post(
|
135
|
+
response = connection.post("/gcm/notification", post_body.to_json)
|
86
136
|
build_response(response)
|
87
137
|
end
|
88
138
|
end
|
139
|
+
|
89
140
|
alias remove remove_registration_ids
|
90
141
|
|
91
142
|
def recover_notification_key(key_name, project_id)
|
92
|
-
params = {notification_key_name: key_name}
|
93
|
-
|
143
|
+
params = { notification_key_name: key_name }
|
144
|
+
|
94
145
|
extra_headers = {
|
95
|
-
|
146
|
+
"project_id" => project_id,
|
96
147
|
}
|
97
148
|
|
98
149
|
for_uri(GROUP_NOTIFICATION_BASE_URI, extra_headers) do |connection|
|
99
|
-
response = connection.get(
|
150
|
+
response = connection.get("/gcm/notification", params)
|
100
151
|
build_response(response)
|
101
152
|
end
|
102
153
|
end
|
@@ -114,11 +165,11 @@ class FCM
|
|
114
165
|
end
|
115
166
|
|
116
167
|
def batch_topic_subscription(topic, registration_ids)
|
117
|
-
manage_topics_relationship(topic, registration_ids,
|
168
|
+
manage_topics_relationship(topic, registration_ids, "Add")
|
118
169
|
end
|
119
170
|
|
120
171
|
def batch_topic_unsubscription(topic, registration_ids)
|
121
|
-
manage_topics_relationship(topic, registration_ids,
|
172
|
+
manage_topics_relationship(topic, registration_ids, "Remove")
|
122
173
|
end
|
123
174
|
|
124
175
|
def manage_topics_relationship(topic, registration_ids, action)
|
@@ -132,35 +183,35 @@ class FCM
|
|
132
183
|
|
133
184
|
def send_to_topic(topic, options = {})
|
134
185
|
if topic.gsub(TOPIC_REGEX, "").length == 0
|
135
|
-
send_with_notification_key(
|
186
|
+
send_with_notification_key("/topics/" + topic, options)
|
136
187
|
end
|
137
188
|
end
|
138
189
|
|
139
|
-
def get_instance_id_info
|
190
|
+
def get_instance_id_info(iid_token, options = {})
|
140
191
|
params = {
|
141
|
-
query: options
|
192
|
+
query: options,
|
142
193
|
}
|
143
|
-
|
194
|
+
|
144
195
|
for_uri(INSTANCE_ID_API) do |connection|
|
145
|
-
response = connection.get(
|
196
|
+
response = connection.get("/iid/info/" + iid_token, params)
|
146
197
|
build_response(response)
|
147
198
|
end
|
148
199
|
end
|
149
200
|
|
150
|
-
def subscribe_instance_id_to_topic
|
201
|
+
def subscribe_instance_id_to_topic(iid_token, topic_name)
|
151
202
|
batch_subscribe_instance_ids_to_topic([iid_token], topic_name)
|
152
203
|
end
|
153
204
|
|
154
|
-
def unsubscribe_instance_id_from_topic
|
205
|
+
def unsubscribe_instance_id_from_topic(iid_token, topic_name)
|
155
206
|
batch_unsubscribe_instance_ids_from_topic([iid_token], topic_name)
|
156
207
|
end
|
157
208
|
|
158
|
-
def batch_subscribe_instance_ids_to_topic
|
159
|
-
manage_topics_relationship(topic_name, instance_ids,
|
209
|
+
def batch_subscribe_instance_ids_to_topic(instance_ids, topic_name)
|
210
|
+
manage_topics_relationship(topic_name, instance_ids, "Add")
|
160
211
|
end
|
161
212
|
|
162
|
-
def batch_unsubscribe_instance_ids_from_topic
|
163
|
-
manage_topics_relationship(topic_name, instance_ids,
|
213
|
+
def batch_unsubscribe_instance_ids_from_topic(instance_ids, topic_name)
|
214
|
+
manage_topics_relationship(topic_name, instance_ids, "Remove")
|
164
215
|
end
|
165
216
|
|
166
217
|
def send_to_topic_condition(condition, options = {})
|
@@ -174,7 +225,7 @@ class FCM
|
|
174
225
|
|
175
226
|
def for_uri(uri, extra_headers = {})
|
176
227
|
connection = ::Faraday.new(:url => uri) do |faraday|
|
177
|
-
faraday.adapter
|
228
|
+
faraday.adapter Faraday.default_adapter
|
178
229
|
faraday.headers["Content-Type"] = "application/json"
|
179
230
|
faraday.headers["Authorization"] = "key=#{api_key}"
|
180
231
|
extra_headers.each do |key, value|
|
@@ -194,18 +245,18 @@ class FCM
|
|
194
245
|
response_hash = { body: body, headers: response.headers, status_code: response.status }
|
195
246
|
case response.status
|
196
247
|
when 200
|
197
|
-
response_hash[:response] =
|
248
|
+
response_hash[:response] = "success"
|
198
249
|
body = JSON.parse(body) unless body.empty?
|
199
250
|
response_hash[:canonical_ids] = build_canonical_ids(body, registration_ids) unless registration_ids.empty?
|
200
251
|
response_hash[:not_registered_ids] = build_not_registered_ids(body, registration_ids) unless registration_ids.empty?
|
201
252
|
when 400
|
202
|
-
response_hash[:response] =
|
253
|
+
response_hash[:response] = "Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields."
|
203
254
|
when 401
|
204
|
-
response_hash[:response] =
|
255
|
+
response_hash[:response] = "There was an error authenticating the sender account."
|
205
256
|
when 503
|
206
|
-
response_hash[:response] =
|
257
|
+
response_hash[:response] = "Server is temporarily unavailable."
|
207
258
|
when 500..599
|
208
|
-
response_hash[:response] =
|
259
|
+
response_hash[:response] = "There was an internal error in the FCM server while trying to process the request."
|
209
260
|
end
|
210
261
|
response_hash
|
211
262
|
end
|
@@ -213,9 +264,9 @@ class FCM
|
|
213
264
|
def build_canonical_ids(body, registration_ids)
|
214
265
|
canonical_ids = []
|
215
266
|
unless body.empty?
|
216
|
-
if body[
|
217
|
-
body[
|
218
|
-
canonical_ids << { old: registration_ids[index], new: result[
|
267
|
+
if body["canonical_ids"] > 0
|
268
|
+
body["results"].each_with_index do |result, index|
|
269
|
+
canonical_ids << { old: registration_ids[index], new: result["registration_id"] } if has_canonical_id?(result)
|
219
270
|
end
|
220
271
|
end
|
221
272
|
end
|
@@ -225,8 +276,8 @@ class FCM
|
|
225
276
|
def build_not_registered_ids(body, registration_id)
|
226
277
|
not_registered_ids = []
|
227
278
|
unless body.empty?
|
228
|
-
if body[
|
229
|
-
body[
|
279
|
+
if body["failure"] > 0
|
280
|
+
body["results"].each_with_index do |result, index|
|
230
281
|
not_registered_ids << registration_id[index] if is_not_registered?(result)
|
231
282
|
end
|
232
283
|
end
|
@@ -236,17 +287,17 @@ class FCM
|
|
236
287
|
|
237
288
|
def execute_notification(body)
|
238
289
|
for_uri(BASE_URI) do |connection|
|
239
|
-
response = connection.post(
|
290
|
+
response = connection.post("/fcm/send", body.to_json)
|
240
291
|
build_response(response)
|
241
292
|
end
|
242
293
|
end
|
243
294
|
|
244
295
|
def has_canonical_id?(result)
|
245
|
-
!result[
|
296
|
+
!result["registration_id"].nil?
|
246
297
|
end
|
247
298
|
|
248
299
|
def is_not_registered?(result)
|
249
|
-
result[
|
300
|
+
result["error"] == "NotRegistered"
|
250
301
|
end
|
251
302
|
|
252
303
|
def validate_condition?(condition)
|
@@ -265,4 +316,14 @@ class FCM
|
|
265
316
|
topics = condition.scan(/(?:^|\S|\s)'([^']*?)'(?:$|\S|\s)/).flatten
|
266
317
|
topics.all? { |topic| topic.gsub(TOPIC_REGEX, "").length == 0 }
|
267
318
|
end
|
319
|
+
|
320
|
+
def jwt_token
|
321
|
+
scope = "https://www.googleapis.com/auth/firebase.messaging"
|
322
|
+
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
|
323
|
+
json_key_io: File.open(@json_key_path),
|
324
|
+
scope: scope,
|
325
|
+
)
|
326
|
+
token = authorizer.fetch_access_token!
|
327
|
+
token["access_token"]
|
328
|
+
end
|
268
329
|
end
|
data/spec/fcm_spec.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
describe FCM do
|
4
4
|
let(:send_url) { "#{FCM::BASE_URI}/fcm/send" }
|
5
5
|
let(:group_notification_base_uri) { "#{FCM::GROUP_NOTIFICATION_BASE_URI}/gcm/notification" }
|
6
|
-
let(:api_key) {
|
7
|
-
let(:registration_id) {
|
8
|
-
let(:registration_ids) { [
|
9
|
-
let(:key_name) {
|
6
|
+
let(:api_key) { "AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA" }
|
7
|
+
let(:registration_id) { "42" }
|
8
|
+
let(:registration_ids) { ["42"] }
|
9
|
+
let(:key_name) { "appUser-Chris" }
|
10
10
|
let(:project_id) { "123456789" } # https://developers.google.com/cloud-messaging/gcm#senderid
|
11
11
|
let(:notification_key) { "APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ" }
|
12
12
|
let(:valid_topic) { "TopicA" }
|
@@ -15,15 +15,19 @@ describe FCM do
|
|
15
15
|
let(:invalid_condition) { "'TopicA' in topics and some other text ('TopicB' in topics || 'TopicC' in topics)" }
|
16
16
|
let(:invalid_condition_topic) { "'TopicA$' in topics" }
|
17
17
|
|
18
|
-
it
|
18
|
+
it "should raise an error if the api key is not provided" do
|
19
19
|
expect { FCM.new }.to raise_error(ArgumentError)
|
20
20
|
end
|
21
21
|
|
22
|
-
it
|
22
|
+
it "should raise error if time_to_live is given" do
|
23
23
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#ttl
|
24
24
|
end
|
25
25
|
|
26
|
-
describe
|
26
|
+
describe "#send_v1" do
|
27
|
+
pending "should send message"
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "sending notification" do
|
27
31
|
let(:valid_request_body) do
|
28
32
|
{ registration_ids: registration_ids }
|
29
33
|
end
|
@@ -32,39 +36,39 @@ describe FCM do
|
|
32
36
|
end
|
33
37
|
let(:valid_request_headers) do
|
34
38
|
{
|
35
|
-
|
36
|
-
|
39
|
+
"Content-Type" => "application/json",
|
40
|
+
"Authorization" => "key=#{api_key}",
|
37
41
|
}
|
38
42
|
end
|
39
43
|
|
40
44
|
let(:stub_fcm_send_request) do
|
41
45
|
stub_request(:post, send_url).with(
|
42
46
|
body: valid_request_body.to_json,
|
43
|
-
headers: valid_request_headers
|
47
|
+
headers: valid_request_headers,
|
44
48
|
).to_return(
|
45
49
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
46
|
-
body:
|
50
|
+
body: "{}",
|
47
51
|
headers: {},
|
48
|
-
status: 200
|
52
|
+
status: 200,
|
49
53
|
)
|
50
54
|
end
|
51
55
|
|
52
56
|
let(:stub_fcm_send_request_with_string) do
|
53
57
|
stub_request(:post, send_url).with(
|
54
58
|
body: valid_request_body_with_string.to_json,
|
55
|
-
headers: valid_request_headers
|
59
|
+
headers: valid_request_headers,
|
56
60
|
).to_return(
|
57
|
-
body:
|
61
|
+
body: "{}",
|
58
62
|
headers: {},
|
59
|
-
status: 200
|
63
|
+
status: 200,
|
60
64
|
)
|
61
65
|
end
|
62
66
|
|
63
67
|
let(:stub_fcm_send_request_with_basic_auth) do
|
64
68
|
uri = URI.parse(send_url)
|
65
|
-
uri.user =
|
66
|
-
uri.password =
|
67
|
-
stub_request(:post, uri.to_s).to_return(body:
|
69
|
+
uri.user = "a"
|
70
|
+
uri.password = "b"
|
71
|
+
stub_request(:post, uri.to_s).to_return(body: "{}", headers: {}, status: 200)
|
68
72
|
end
|
69
73
|
|
70
74
|
before(:each) do
|
@@ -73,30 +77,30 @@ describe FCM do
|
|
73
77
|
stub_fcm_send_request_with_basic_auth
|
74
78
|
end
|
75
79
|
|
76
|
-
it
|
80
|
+
it "should send notification using POST to FCM server" do
|
77
81
|
fcm = FCM.new(api_key)
|
78
|
-
fcm.send(registration_ids).should eq(response:
|
82
|
+
fcm.send(registration_ids).should eq(response: "success", body: "{}", headers: {}, status_code: 200, canonical_ids: [], not_registered_ids: [])
|
79
83
|
stub_fcm_send_request.should have_been_made.times(1)
|
80
84
|
end
|
81
85
|
|
82
|
-
it
|
86
|
+
it "should send notification using POST to FCM if id provided as string" do
|
83
87
|
fcm = FCM.new(api_key)
|
84
|
-
fcm.send(registration_id).should eq(response:
|
88
|
+
fcm.send(registration_id).should eq(response: "success", body: "{}", headers: {}, status_code: 200, canonical_ids: [], not_registered_ids: [])
|
85
89
|
stub_fcm_send_request.should have_been_made.times(1)
|
86
90
|
end
|
87
91
|
|
88
|
-
context
|
92
|
+
context "send notification with data" do
|
89
93
|
let!(:stub_with_data) do
|
90
94
|
stub_request(:post, send_url)
|
91
95
|
.with(body: '{"registration_ids":["42"],"data":{"score":"5x1","time":"15:10"}}',
|
92
96
|
headers: valid_request_headers)
|
93
|
-
.to_return(status: 200, body:
|
97
|
+
.to_return(status: 200, body: "", headers: {})
|
94
98
|
end
|
95
99
|
before do
|
96
100
|
end
|
97
|
-
it
|
101
|
+
it "should send the data in a post request to fcm" do
|
98
102
|
fcm = FCM.new(api_key)
|
99
|
-
fcm.send(registration_ids, data: { score:
|
103
|
+
fcm.send(registration_ids, data: { score: "5x1", time: "15:10" })
|
100
104
|
stub_with_data.should have_been_requested
|
101
105
|
end
|
102
106
|
end
|
@@ -106,25 +110,25 @@ describe FCM do
|
|
106
110
|
stub_request(:post, send_url)
|
107
111
|
.with(body: '{"to":"/topics/TopicA","data":{"score":"5x1","time":"15:10"}}',
|
108
112
|
headers: valid_request_headers)
|
109
|
-
.to_return(status: 200, body:
|
113
|
+
.to_return(status: 200, body: "", headers: {})
|
110
114
|
end
|
111
115
|
let!(:stub_with_invalid_topic) do
|
112
116
|
stub_request(:post, send_url)
|
113
117
|
.with(body: '{"condition":"/topics/TopicA$","data":{"score":"5x1","time":"15:10"}}',
|
114
118
|
headers: valid_request_headers)
|
115
|
-
.to_return(status: 200, body:
|
119
|
+
.to_return(status: 200, body: "", headers: {})
|
116
120
|
end
|
117
121
|
|
118
122
|
describe "#send_to_topic" do
|
119
|
-
it
|
123
|
+
it "should send the data in a post request to fcm" do
|
120
124
|
fcm = FCM.new(api_key)
|
121
|
-
fcm.send_to_topic(valid_topic, data: { score:
|
125
|
+
fcm.send_to_topic(valid_topic, data: { score: "5x1", time: "15:10" })
|
122
126
|
stub_with_valid_topic.should have_been_requested
|
123
127
|
end
|
124
128
|
|
125
|
-
it
|
129
|
+
it "should not send to invalid topics" do
|
126
130
|
fcm = FCM.new(api_key)
|
127
|
-
fcm.send_to_topic(invalid_topic, data: { score:
|
131
|
+
fcm.send_to_topic(invalid_topic, data: { score: "5x1", time: "15:10" })
|
128
132
|
stub_with_invalid_topic.should_not have_been_requested
|
129
133
|
end
|
130
134
|
end
|
@@ -135,143 +139,143 @@ describe FCM do
|
|
135
139
|
stub_request(:post, send_url)
|
136
140
|
.with(body: '{"condition":"\'TopicA\' in topics && (\'TopicB\' in topics || \'TopicC\' in topics)","data":{"score":"5x1","time":"15:10"}}',
|
137
141
|
headers: valid_request_headers)
|
138
|
-
.to_return(status: 200, body:
|
142
|
+
.to_return(status: 200, body: "", headers: {})
|
139
143
|
end
|
140
144
|
let!(:stub_with_invalid_condition) do
|
141
145
|
stub_request(:post, send_url)
|
142
146
|
.with(body: '{"condition":"\'TopicA\' in topics and some other text (\'TopicB\' in topics || \'TopicC\' in topics)","data":{"score":"5x1","time":"15:10"}}',
|
143
147
|
headers: valid_request_headers)
|
144
|
-
.to_return(status: 200, body:
|
148
|
+
.to_return(status: 200, body: "", headers: {})
|
145
149
|
end
|
146
150
|
let!(:stub_with_invalid_condition_topic) do
|
147
151
|
stub_request(:post, send_url)
|
148
152
|
.with(body: '{"condition":"\'TopicA$\' in topics","data":{"score":"5x1","time":"15:10"}}',
|
149
153
|
headers: valid_request_headers)
|
150
|
-
.to_return(status: 200, body:
|
154
|
+
.to_return(status: 200, body: "", headers: {})
|
151
155
|
end
|
152
156
|
|
153
157
|
describe "#send_to_topic_condition" do
|
154
|
-
it
|
158
|
+
it "should send the data in a post request to fcm" do
|
155
159
|
fcm = FCM.new(api_key)
|
156
|
-
fcm.send_to_topic_condition(valid_condition, data: { score:
|
160
|
+
fcm.send_to_topic_condition(valid_condition, data: { score: "5x1", time: "15:10" })
|
157
161
|
stub_with_valid_condition.should have_been_requested
|
158
162
|
end
|
159
163
|
|
160
|
-
it
|
164
|
+
it "should not send to invalid conditions" do
|
161
165
|
fcm = FCM.new(api_key)
|
162
|
-
fcm.send_to_topic_condition(invalid_condition, data: { score:
|
166
|
+
fcm.send_to_topic_condition(invalid_condition, data: { score: "5x1", time: "15:10" })
|
163
167
|
stub_with_invalid_condition.should_not have_been_requested
|
164
168
|
end
|
165
169
|
|
166
|
-
it
|
170
|
+
it "should not send to invalid topics in a condition" do
|
167
171
|
fcm = FCM.new(api_key)
|
168
|
-
fcm.send_to_topic_condition(invalid_condition_topic, data: { score:
|
172
|
+
fcm.send_to_topic_condition(invalid_condition_topic, data: { score: "5x1", time: "15:10" })
|
169
173
|
stub_with_invalid_condition_topic.should_not have_been_requested
|
170
174
|
end
|
171
175
|
end
|
172
176
|
end
|
173
177
|
|
174
|
-
context
|
178
|
+
context "when send_notification responds with failure" do
|
175
179
|
let(:mock_request_attributes) do
|
176
180
|
{
|
177
181
|
body: valid_request_body.to_json,
|
178
|
-
headers: valid_request_headers
|
182
|
+
headers: valid_request_headers,
|
179
183
|
}
|
180
184
|
end
|
181
185
|
|
182
186
|
subject { FCM.new(api_key) }
|
183
187
|
|
184
|
-
context
|
188
|
+
context "on failure code 400" do
|
185
189
|
before do
|
186
190
|
stub_request(:post, send_url).with(
|
187
191
|
mock_request_attributes
|
188
192
|
).to_return(
|
189
193
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
190
|
-
body:
|
194
|
+
body: "{}",
|
191
195
|
headers: {},
|
192
|
-
status: 400
|
196
|
+
status: 400,
|
193
197
|
)
|
194
198
|
end
|
195
|
-
it
|
196
|
-
subject.send(registration_ids).should eq(body:
|
199
|
+
it "should not send notification due to 400" do
|
200
|
+
subject.send(registration_ids).should eq(body: "{}",
|
197
201
|
headers: {},
|
198
|
-
response:
|
202
|
+
response: "Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields.",
|
199
203
|
status_code: 400)
|
200
204
|
end
|
201
205
|
end
|
202
206
|
|
203
|
-
context
|
207
|
+
context "on failure code 401" do
|
204
208
|
before do
|
205
209
|
stub_request(:post, send_url).with(
|
206
210
|
mock_request_attributes
|
207
211
|
).to_return(
|
208
212
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
209
|
-
body:
|
213
|
+
body: "{}",
|
210
214
|
headers: {},
|
211
|
-
status: 401
|
215
|
+
status: 401,
|
212
216
|
)
|
213
217
|
end
|
214
218
|
|
215
|
-
it
|
216
|
-
subject.send(registration_ids).should eq(body:
|
219
|
+
it "should not send notification due to 401" do
|
220
|
+
subject.send(registration_ids).should eq(body: "{}",
|
217
221
|
headers: {},
|
218
|
-
response:
|
222
|
+
response: "There was an error authenticating the sender account.",
|
219
223
|
status_code: 401)
|
220
224
|
end
|
221
225
|
end
|
222
226
|
|
223
|
-
context
|
227
|
+
context "on failure code 503" do
|
224
228
|
before do
|
225
229
|
stub_request(:post, send_url).with(
|
226
230
|
mock_request_attributes
|
227
231
|
).to_return(
|
228
232
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
229
|
-
body:
|
233
|
+
body: "{}",
|
230
234
|
headers: {},
|
231
|
-
status: 503
|
235
|
+
status: 503,
|
232
236
|
)
|
233
237
|
end
|
234
238
|
|
235
|
-
it
|
236
|
-
subject.send(registration_ids).should eq(body:
|
239
|
+
it "should not send notification due to 503" do
|
240
|
+
subject.send(registration_ids).should eq(body: "{}",
|
237
241
|
headers: {},
|
238
|
-
response:
|
242
|
+
response: "Server is temporarily unavailable.",
|
239
243
|
status_code: 503)
|
240
244
|
end
|
241
245
|
end
|
242
246
|
|
243
|
-
context
|
247
|
+
context "on failure code 5xx" do
|
244
248
|
before do
|
245
249
|
stub_request(:post, send_url).with(
|
246
250
|
mock_request_attributes
|
247
251
|
).to_return(
|
248
252
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
249
253
|
body: '{"body-key" => "Body value"}',
|
250
|
-
headers: {
|
251
|
-
status: 599
|
254
|
+
headers: { "header-key" => "Header value" },
|
255
|
+
status: 599,
|
252
256
|
)
|
253
257
|
end
|
254
258
|
|
255
|
-
it
|
259
|
+
it "should not send notification due to 599" do
|
256
260
|
subject.send(registration_ids).should eq(body: '{"body-key" => "Body value"}',
|
257
|
-
headers: {
|
258
|
-
response:
|
261
|
+
headers: { "header-key" => "Header value" },
|
262
|
+
response: "There was an internal error in the FCM server while trying to process the request.",
|
259
263
|
status_code: 599)
|
260
264
|
end
|
261
265
|
end
|
262
266
|
end
|
263
267
|
|
264
|
-
context
|
268
|
+
context "when send_notification responds canonical_ids" do
|
265
269
|
let(:mock_request_attributes) do
|
266
270
|
{
|
267
271
|
body: valid_request_body.to_json,
|
268
|
-
headers: valid_request_headers
|
272
|
+
headers: valid_request_headers,
|
269
273
|
}
|
270
274
|
end
|
271
275
|
|
272
276
|
let(:valid_response_body_with_canonical_ids) do
|
273
277
|
{
|
274
|
-
failure: 0, canonical_ids: 1, results: [{ registration_id:
|
278
|
+
failure: 0, canonical_ids: 1, results: [{ registration_id: "43", message_id: "0:1385025861956342%572c22801bb3" }],
|
275
279
|
}
|
276
280
|
end
|
277
281
|
|
@@ -284,35 +288,35 @@ describe FCM do
|
|
284
288
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
285
289
|
body: valid_response_body_with_canonical_ids.to_json,
|
286
290
|
headers: {},
|
287
|
-
status: 200
|
291
|
+
status: 200,
|
288
292
|
)
|
289
293
|
end
|
290
294
|
|
291
|
-
it
|
295
|
+
it "should contain canonical_ids" do
|
292
296
|
response = subject.send(registration_ids)
|
293
297
|
|
294
298
|
response.should eq(headers: {},
|
295
|
-
canonical_ids: [{ old:
|
299
|
+
canonical_ids: [{ old: "42", new: "43" }],
|
296
300
|
not_registered_ids: [],
|
297
301
|
status_code: 200,
|
298
|
-
response:
|
302
|
+
response: "success",
|
299
303
|
body: '{"failure":0,"canonical_ids":1,"results":[{"registration_id":"43","message_id":"0:1385025861956342%572c22801bb3"}]}')
|
300
304
|
end
|
301
305
|
end
|
302
306
|
|
303
|
-
context
|
307
|
+
context "when send_notification responds with NotRegistered" do
|
304
308
|
subject { FCM.new(api_key) }
|
305
309
|
|
306
310
|
let(:mock_request_attributes) do
|
307
311
|
{
|
308
312
|
body: valid_request_body.to_json,
|
309
|
-
headers: valid_request_headers
|
313
|
+
headers: valid_request_headers,
|
310
314
|
}
|
311
315
|
end
|
312
316
|
|
313
317
|
let(:valid_response_body_with_not_registered_ids) do
|
314
318
|
{
|
315
|
-
canonical_ids: 0, failure: 1, results: [{ error:
|
319
|
+
canonical_ids: 0, failure: 1, results: [{ error: "NotRegistered" }],
|
316
320
|
}
|
317
321
|
end
|
318
322
|
|
@@ -322,31 +326,31 @@ describe FCM do
|
|
322
326
|
).to_return(
|
323
327
|
body: valid_response_body_with_not_registered_ids.to_json,
|
324
328
|
headers: {},
|
325
|
-
status: 200
|
329
|
+
status: 200,
|
326
330
|
)
|
327
331
|
end
|
328
332
|
|
329
|
-
it
|
333
|
+
it "should contain not_registered_ids" do
|
330
334
|
response = subject.send(registration_ids)
|
331
335
|
response.should eq(
|
332
336
|
headers: {},
|
333
337
|
canonical_ids: [],
|
334
338
|
not_registered_ids: registration_ids,
|
335
339
|
status_code: 200,
|
336
|
-
response:
|
337
|
-
body: '{"canonical_ids":0,"failure":1,"results":[{"error":"NotRegistered"}]}'
|
340
|
+
response: "success",
|
341
|
+
body: '{"canonical_ids":0,"failure":1,"results":[{"error":"NotRegistered"}]}',
|
338
342
|
)
|
339
343
|
end
|
340
344
|
end
|
341
345
|
end
|
342
346
|
|
343
|
-
describe
|
347
|
+
describe "sending group notifications" do
|
344
348
|
# TODO: refactor to should_behave_like
|
345
349
|
let(:valid_request_headers) do
|
346
350
|
{
|
347
351
|
"Authorization" => "key=#{api_key}",
|
348
|
-
"Content-Type" =>
|
349
|
-
"Project-Id" => project_id
|
352
|
+
"Content-Type" => "application/json",
|
353
|
+
"Project-Id" => project_id,
|
350
354
|
}
|
351
355
|
end
|
352
356
|
let(:valid_response_body) do
|
@@ -357,24 +361,24 @@ describe FCM do
|
|
357
361
|
{
|
358
362
|
registration_ids: registration_ids,
|
359
363
|
operation: "create",
|
360
|
-
notification_key_name: key_name
|
364
|
+
notification_key_name: key_name,
|
361
365
|
}
|
362
366
|
end
|
363
367
|
|
364
368
|
subject { FCM.new(api_key) }
|
365
369
|
|
366
370
|
# ref: https://firebase.google.com/docs/cloud-messaging/notifications#managing-device-groups-on-the-app-server
|
367
|
-
context
|
371
|
+
context "create" do
|
368
372
|
let(:valid_request_body) do
|
369
373
|
default_valid_request_body.merge({
|
370
|
-
operation: "create"
|
374
|
+
operation: "create",
|
371
375
|
})
|
372
376
|
end
|
373
377
|
|
374
378
|
let(:mock_request_attributes) do
|
375
379
|
{
|
376
380
|
body: valid_request_body.to_json,
|
377
|
-
headers: valid_request_headers
|
381
|
+
headers: valid_request_headers,
|
378
382
|
}
|
379
383
|
end
|
380
384
|
|
@@ -384,33 +388,33 @@ describe FCM do
|
|
384
388
|
).to_return(
|
385
389
|
body: valid_response_body.to_json,
|
386
390
|
headers: {},
|
387
|
-
status: 200
|
391
|
+
status: 200,
|
388
392
|
)
|
389
393
|
end
|
390
394
|
|
391
|
-
it
|
395
|
+
it "should send a post request" do
|
392
396
|
response = subject.create(key_name, project_id, registration_ids)
|
393
397
|
response.should eq(
|
394
398
|
headers: {},
|
395
399
|
status_code: 200,
|
396
|
-
response:
|
397
|
-
body: valid_response_body.to_json
|
400
|
+
response: "success",
|
401
|
+
body: valid_response_body.to_json,
|
398
402
|
)
|
399
403
|
end
|
400
404
|
end # create context
|
401
405
|
|
402
|
-
context
|
406
|
+
context "add" do
|
403
407
|
let(:valid_request_body) do
|
404
408
|
default_valid_request_body.merge({
|
405
409
|
operation: "add",
|
406
|
-
notification_key: notification_key
|
410
|
+
notification_key: notification_key,
|
407
411
|
})
|
408
412
|
end
|
409
413
|
|
410
414
|
let(:mock_request_attributes) do
|
411
415
|
{
|
412
416
|
body: valid_request_body.to_json,
|
413
|
-
headers: valid_request_headers
|
417
|
+
headers: valid_request_headers,
|
414
418
|
}
|
415
419
|
end
|
416
420
|
|
@@ -420,33 +424,33 @@ describe FCM do
|
|
420
424
|
).to_return(
|
421
425
|
body: valid_response_body.to_json,
|
422
426
|
headers: {},
|
423
|
-
status: 200
|
427
|
+
status: 200,
|
424
428
|
)
|
425
429
|
end
|
426
430
|
|
427
|
-
it
|
431
|
+
it "should send a post request" do
|
428
432
|
response = subject.add(key_name, project_id, notification_key, registration_ids)
|
429
433
|
response.should eq(
|
430
434
|
headers: {},
|
431
435
|
status_code: 200,
|
432
|
-
response:
|
433
|
-
body: valid_response_body.to_json
|
436
|
+
response: "success",
|
437
|
+
body: valid_response_body.to_json,
|
434
438
|
)
|
435
439
|
end
|
436
440
|
end # add context
|
437
441
|
|
438
|
-
context
|
442
|
+
context "remove" do
|
439
443
|
let(:valid_request_body) do
|
440
444
|
default_valid_request_body.merge({
|
441
445
|
operation: "remove",
|
442
|
-
notification_key: notification_key
|
446
|
+
notification_key: notification_key,
|
443
447
|
})
|
444
448
|
end
|
445
449
|
|
446
450
|
let(:mock_request_attributes) do
|
447
451
|
{
|
448
452
|
body: valid_request_body.to_json,
|
449
|
-
headers: valid_request_headers
|
453
|
+
headers: valid_request_headers,
|
450
454
|
}
|
451
455
|
end
|
452
456
|
|
@@ -456,17 +460,17 @@ describe FCM do
|
|
456
460
|
).to_return(
|
457
461
|
body: valid_response_body.to_json,
|
458
462
|
headers: {},
|
459
|
-
status: 200
|
463
|
+
status: 200,
|
460
464
|
)
|
461
465
|
end
|
462
466
|
|
463
|
-
it
|
467
|
+
it "should send a post request" do
|
464
468
|
response = subject.remove(key_name, project_id, notification_key, registration_ids)
|
465
469
|
response.should eq(
|
466
470
|
headers: {},
|
467
471
|
status_code: 200,
|
468
|
-
response:
|
469
|
-
body: valid_response_body.to_json
|
472
|
+
response: "success",
|
473
|
+
body: valid_response_body.to_json,
|
470
474
|
)
|
471
475
|
end
|
472
476
|
end # remove context
|
@@ -477,11 +481,11 @@ describe FCM do
|
|
477
481
|
uri = "#{FCM::GROUP_NOTIFICATION_BASE_URI}/gcm/notification"
|
478
482
|
endpoint = stub_request(:get, uri).with(
|
479
483
|
headers: {
|
480
|
-
|
481
|
-
|
482
|
-
|
484
|
+
"Content-Type" => "application/json",
|
485
|
+
"Authorization" => "key=TEST_SERVER_KEY",
|
486
|
+
"project_id" => "TEST_PROJECT_ID",
|
483
487
|
},
|
484
|
-
query: {notification_key_name: "TEST_KEY_NAME"}
|
488
|
+
query: { notification_key_name: "TEST_KEY_NAME" },
|
485
489
|
)
|
486
490
|
client = FCM.new("TEST_SERVER_KEY")
|
487
491
|
|
@@ -491,7 +495,7 @@ describe FCM do
|
|
491
495
|
end
|
492
496
|
end
|
493
497
|
|
494
|
-
describe
|
498
|
+
describe "subscribing to a topic" do
|
495
499
|
# TODO
|
496
500
|
end
|
497
501
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fcm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kashif Rasul
|
8
8
|
- Shoaib Burq
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-11-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|
@@ -29,8 +29,8 @@ description: fcm provides ruby bindings to Firebase Cloud Messaging (FCM) a cros
|
|
29
29
|
messaging solution that lets you reliably deliver messages and notifications at
|
30
30
|
no cost to Android, iOS or Web browsers.
|
31
31
|
email:
|
32
|
-
- kashif@
|
33
|
-
- shoaib@
|
32
|
+
- kashif@decision-labs.com
|
33
|
+
- shoaib@decision-labs.com
|
34
34
|
executables: []
|
35
35
|
extensions: []
|
36
36
|
extra_rdoc_files: []
|
@@ -50,7 +50,7 @@ homepage: https://github.com/spacialdb/fcm
|
|
50
50
|
licenses:
|
51
51
|
- MIT
|
52
52
|
metadata: {}
|
53
|
-
post_install_message:
|
53
|
+
post_install_message:
|
54
54
|
rdoc_options: []
|
55
55
|
require_paths:
|
56
56
|
- lib
|
@@ -65,8 +65,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: '0'
|
67
67
|
requirements: []
|
68
|
-
rubygems_version: 3.0.3
|
69
|
-
signing_key:
|
68
|
+
rubygems_version: 3.0.3.1
|
69
|
+
signing_key:
|
70
70
|
specification_version: 4
|
71
71
|
summary: Reliably deliver messages and notifications via FCM
|
72
72
|
test_files:
|